From 3f6bf3f207d17c4f9aa68eacc45d29d9e9f379d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Sun, 13 Oct 2024 15:34:54 +0200 Subject: [PATCH 001/169] init: adds EPM-MB, multipaged types and primitives --- Cargo.lock | 23 + Cargo.toml | 2 + .../election-provider-multi-block/Cargo.toml | 72 ++ .../election-provider-multi-block/README.md | 1 + .../integration-tests/Cargo.toml | 54 + .../integration-tests/src/lib.rs | 125 ++ .../integration-tests/src/mock.rs | 998 +++++++++++++++ .../src/benchmarking.rs | 318 +++++ .../src/helpers.rs | 331 +++++ .../election-provider-multi-block/src/lib.rs | 1139 +++++++++++++++++ .../src/mock/mod.rs | 594 +++++++++ .../src/mock/staking.rs | 235 ++++ .../src/signed/benchmarking.rs | 66 + .../src/signed/mod.rs | 676 ++++++++++ .../src/signed/tests.rs | 309 +++++ .../src/types.rs | 255 ++++ .../src/unsigned/benchmarking.rs | 88 ++ .../src/unsigned/miner.rs | 811 ++++++++++++ .../src/unsigned/mod.rs | 360 ++++++ .../src/unsigned/tests.rs | 216 ++++ .../src/unsigned/weights.rs | 85 ++ .../src/verifier/benchmarking.rs | 315 +++++ .../src/verifier/impls.rs | 764 +++++++++++ .../src/verifier/mod.rs | 287 +++++ .../src/verifier/tests.rs | 121 ++ .../src/verifier/weights.rs | 249 ++++ .../src/weights.rs | 188 +++ .../election-provider-support/Cargo.toml | 2 + .../solution-type/src/codec.rs | 1 + .../solution-type/src/single_page.rs | 2 +- .../election-provider-support/src/lib.rs | 335 +++-- .../election-provider-support/src/onchain.rs | 199 +-- .../primitives/npos-elections/src/lib.rs | 56 +- substrate/primitives/staking/Cargo.toml | 2 + substrate/primitives/staking/src/lib.rs | 63 + 35 files changed, 9154 insertions(+), 188 deletions(-) create mode 100644 substrate/frame/election-provider-multi-block/Cargo.toml create mode 100644 substrate/frame/election-provider-multi-block/README.md create mode 100644 substrate/frame/election-provider-multi-block/integration-tests/Cargo.toml create mode 100644 substrate/frame/election-provider-multi-block/integration-tests/src/lib.rs create mode 100644 substrate/frame/election-provider-multi-block/integration-tests/src/mock.rs create mode 100644 substrate/frame/election-provider-multi-block/src/benchmarking.rs create mode 100644 substrate/frame/election-provider-multi-block/src/helpers.rs create mode 100644 substrate/frame/election-provider-multi-block/src/lib.rs create mode 100644 substrate/frame/election-provider-multi-block/src/mock/mod.rs create mode 100644 substrate/frame/election-provider-multi-block/src/mock/staking.rs create mode 100644 substrate/frame/election-provider-multi-block/src/signed/benchmarking.rs create mode 100644 substrate/frame/election-provider-multi-block/src/signed/mod.rs create mode 100644 substrate/frame/election-provider-multi-block/src/signed/tests.rs create mode 100644 substrate/frame/election-provider-multi-block/src/types.rs create mode 100644 substrate/frame/election-provider-multi-block/src/unsigned/benchmarking.rs create mode 100644 substrate/frame/election-provider-multi-block/src/unsigned/miner.rs create mode 100644 substrate/frame/election-provider-multi-block/src/unsigned/mod.rs create mode 100644 substrate/frame/election-provider-multi-block/src/unsigned/tests.rs create mode 100644 substrate/frame/election-provider-multi-block/src/unsigned/weights.rs create mode 100644 substrate/frame/election-provider-multi-block/src/verifier/benchmarking.rs create mode 100644 substrate/frame/election-provider-multi-block/src/verifier/impls.rs create mode 100644 substrate/frame/election-provider-multi-block/src/verifier/mod.rs create mode 100644 substrate/frame/election-provider-multi-block/src/verifier/tests.rs create mode 100644 substrate/frame/election-provider-multi-block/src/verifier/weights.rs create mode 100644 substrate/frame/election-provider-multi-block/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index ca71985b69f07..ba5f04aaf9997 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6288,6 +6288,7 @@ dependencies = [ "sp-io 30.0.0", "sp-npos-elections", "sp-runtime 31.0.1", + "sp-std 14.0.0", ] [[package]] @@ -11447,6 +11448,27 @@ dependencies = [ "sp-tracing 16.0.0", ] +[[package]] +name = "pallet-election-provider-multi-block" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-election-provider-support", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "parity-scale-codec", + "parking_lot 0.12.3", + "scale-info", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-npos-elections", + "sp-runtime 31.0.1", + "sp-std 14.0.0", + "sp-tracing 16.0.0", +] + [[package]] name = "pallet-election-provider-multi-phase" version = "27.0.0" @@ -22707,6 +22729,7 @@ dependencies = [ "serde", "sp-core 28.0.0", "sp-runtime 31.0.1", + "sp-std 14.0.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index fde05e90ca6e6..b493fceec83e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -339,6 +339,7 @@ members = [ "substrate/frame/core-fellowship", "substrate/frame/delegated-staking", "substrate/frame/democracy", + "substrate/frame/election-provider-multi-block", "substrate/frame/election-provider-multi-phase", "substrate/frame/election-provider-multi-phase/test-staking-e2e", "substrate/frame/election-provider-support", @@ -911,6 +912,7 @@ pallet-default-config-example = { path = "substrate/frame/examples/default-confi pallet-delegated-staking = { path = "substrate/frame/delegated-staking", default-features = false } pallet-democracy = { path = "substrate/frame/democracy", default-features = false } pallet-dev-mode = { path = "substrate/frame/examples/dev-mode", default-features = false } +pallet-election-provider-multi-block = { path = "substrate/frame/election-provider-multi-block", default-features = false } pallet-election-provider-multi-phase = { path = "substrate/frame/election-provider-multi-phase", default-features = false } pallet-election-provider-support-benchmarking = { path = "substrate/frame/election-provider-support/benchmarking", default-features = false } pallet-elections-phragmen = { path = "substrate/frame/elections-phragmen", default-features = false } diff --git a/substrate/frame/election-provider-multi-block/Cargo.toml b/substrate/frame/election-provider-multi-block/Cargo.toml new file mode 100644 index 0000000000000..e665a714008b5 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/Cargo.toml @@ -0,0 +1,72 @@ +[package] +name = "pallet-election-provider-multi-block" +version = "4.0.0-dev" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +homepage = "https://substrate.dev" +repository.workspace = true +description = "FRAME pallet election provider multi-block" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ + "derive", +] } +scale-info = { version = "2.10.0", default-features = false, features = [ + "derive", +] } +log = { version = "0.4.17", default-features = false } + +frame-support = { path = "../support", default-features = false } +frame-system = { path = "../system", default-features = false } +frame-benchmarking = { path = "../benchmarking", default-features = false } + +sp-io = { path = "../../primitives/io", default-features = false } +sp-std = { path = "../../primitives/std", default-features = false } +sp-core = { path = "../../primitives/core", default-features = false } +sp-runtime = { path = "../../primitives/runtime", default-features = false } + +frame-election-provider-support = { default-features = false, path = "../election-provider-support" } +sp-npos-elections = { default-features = false, path = "../../primitives/npos-elections" } + +[dev-dependencies] +sp-tracing = { path = "../../primitives/tracing" } +pallet-balances = { path = "../balances", default-features = false } +parking_lot = "0.12.1" + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking/std", + "frame-election-provider-support/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-balances/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-npos-elections/std", + "sp-runtime/std", + "sp-std/std", +] + +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-election-provider-support/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] + +try-runtime = [ + "frame-election-provider-support/try-runtime", + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-balances/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/substrate/frame/election-provider-multi-block/README.md b/substrate/frame/election-provider-multi-block/README.md new file mode 100644 index 0000000000000..9aff2b4bb152a --- /dev/null +++ b/substrate/frame/election-provider-multi-block/README.md @@ -0,0 +1 @@ +# Election Provider multi-block diff --git a/substrate/frame/election-provider-multi-block/integration-tests/Cargo.toml b/substrate/frame/election-provider-multi-block/integration-tests/Cargo.toml new file mode 100644 index 0000000000000..15aa841666209 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/integration-tests/Cargo.toml @@ -0,0 +1,54 @@ +[package] +name = "pallet-election-tests" +version = "1.0.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true +description = "FRAME election provider multi block pallet tests with staking pallet, bags-list and session pallets" +publish = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dev-dependencies] +parking_lot = { workspace = true, default-features = true } +codec = { features = ["derive"], workspace = true, default-features = true } +scale-info = { features = ["derive"], workspace = true, default-features = true } +log = { workspace = true } + +sp-runtime = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } +sp-std = { workspace = true, default-features = true } +sp-staking = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-npos-elections = { workspace = true } +sp-tracing = { workspace = true, default-features = true } + +frame-system = { workspace = true, default-features = true } +frame-support = { workspace = true, default-features = true } +frame-election-provider-support = { workspace = true, default-features = true } + +pallet-election-provider-multi-block = { workspace = true, default-features = true } +pallet-staking = { workspace = true, default-features = true } +pallet-nomination-pools = { workspace = true, default-features = true } +pallet-bags-list = { workspace = true, default-features = true } +pallet-balances = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } +pallet-session = { workspace = true, default-features = true } + +[features] +try-runtime = [ + "frame-election-provider-support/try-runtime", + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-bags-list/try-runtime", + "pallet-balances/try-runtime", + #"pallet-election-provider-multi-block/try-runtime", + "pallet-nomination-pools/try-runtime", + "pallet-session/try-runtime", + "pallet-staking/try-runtime", + "pallet-timestamp/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/substrate/frame/election-provider-multi-block/integration-tests/src/lib.rs b/substrate/frame/election-provider-multi-block/integration-tests/src/lib.rs new file mode 100644 index 0000000000000..3cd56def8e066 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/integration-tests/src/lib.rs @@ -0,0 +1,125 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg(test)] +mod mock; + +pub(crate) const LOG_TARGET: &str = "integration-tests::epm-staking"; + +use mock::*; + +use frame_election_provider_support::{bounds::ElectionBoundsBuilder, ElectionDataProvider}; + +use frame_support::{assert_ok, traits::UnixTime}; + +// syntactic sugar for logging. +#[macro_export] +macro_rules! log { + ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: crate::LOG_TARGET, + concat!("🛠️ ", $patter) $(, $values)* + ) + }; +} + +fn log_current_time() { + log!( + info, + "block: {:?}, session: {:?}, era: {:?}, EPM phase: {:?} ts: {:?}", + System::block_number(), + Session::current_index(), + Staking::current_era(), + ElectionProvider::current_phase(), + Timestamp::now() + ); +} + +#[test] +fn block_progression_works() { + let (mut ext, _pool_state, _) = ExtBuilder::default().build_offchainify(); + ext.execute_with(|| {}) +} + +#[test] +fn verify_snapshot() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(Pages::get(), 3); + + // manually get targets and voters from staking to see the inspect the issue with the + // DataProvider. + let bounds = ElectionBoundsBuilder::default() + .targets_count((TargetSnapshotPerBlock::get() as u32).into()) + .voters_count((VoterSnapshotPerBlock::get() as u32).into()) + .build(); + + assert_ok!(::electable_targets(bounds.targets, 2)); + assert_ok!(::electing_voters(bounds.voters, 2)); + }) +} + +mod staking_integration { + use super::*; + use pallet_election_provider_multi_block::Phase; + + #[test] + fn call_elect_multi_block() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(Pages::get(), 3); + assert_eq!(ElectionProvider::current_round(), 0); + assert_eq!(Staking::current_era(), Some(0)); + + let export_starts_at = election_prediction() - Pages::get(); + + assert!(Staking::election_data_lock().is_none()); + + // check that the election data provider lock is set during the snapshot phase and + // released afterwards. + roll_to_phase(Phase::Snapshot(Pages::get() - 1), false); + assert!(Staking::election_data_lock().is_some()); + + roll_one(None, false); + assert!(Staking::election_data_lock().is_some()); + roll_one(None, false); + assert!(Staking::election_data_lock().is_some()); + // snapshot phase done, election data lock was released. + roll_one(None, false); + assert_eq!(ElectionProvider::current_phase(), Phase::Signed); + assert!(Staking::election_data_lock().is_none()); + + // last block where phase is waiting for unsignned submissions. + roll_to(election_prediction() - 4, false); + assert_eq!(ElectionProvider::current_phase(), Phase::Unsigned(17)); + + // staking prepares first page of exposures. + roll_to(export_starts_at, false); + assert_eq!(ElectionProvider::current_phase(), Phase::Export(export_starts_at)); + + // staking prepares second page of exposures. + roll_to(election_prediction() - 2, false); + assert_eq!(ElectionProvider::current_phase(), Phase::Export(export_starts_at)); + + // staking prepares third page of exposures. + roll_to(election_prediction() - 1, false); + + // election successfully, round & era progressed. + assert_eq!(ElectionProvider::current_phase(), Phase::Off); + assert_eq!(ElectionProvider::current_round(), 1); + assert_eq!(Staking::current_era(), Some(1)); + }) + } +} diff --git a/substrate/frame/election-provider-multi-block/integration-tests/src/mock.rs b/substrate/frame/election-provider-multi-block/integration-tests/src/mock.rs new file mode 100644 index 0000000000000..29dd2733250e3 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/integration-tests/src/mock.rs @@ -0,0 +1,998 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![allow(dead_code)] + +use frame_support::{ + assert_ok, parameter_types, traits, + traits::{Hooks, VariantCountOf}, + weights::constants, +}; +use frame_system::EnsureRoot; +use sp_core::{ConstU32, Get}; +use sp_npos_elections::VoteWeight; +use sp_runtime::{ + offchain::{ + testing::{OffchainState, PoolState, TestOffchainExt, TestTransactionPoolExt}, + OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, + }, + testing, + traits::Zero, + transaction_validity, BuildStorage, PerU16, Perbill, Percent, +}; +use sp_staking::{ + offence::{OffenceDetails, OnOffenceHandler}, + EraIndex, SessionIndex, +}; +use std::collections::BTreeMap; + +use codec::Decode; +use frame_election_provider_support::{ + bounds::ElectionBoundsBuilder, onchain, ElectionDataProvider, ExtendedBalance, PageIndex, + SequentialPhragmen, Weight, +}; +use sp_npos_elections::ElectionScore; + +use pallet_election_provider_multi_block::{ + self as epm_core_pallet, + signed::{self as epm_signed_pallet}, + unsigned::{self as epm_unsigned_pallet, miner}, + verifier::{self as epm_verifier_pallet}, + Config, Phase, +}; + +use pallet_staking::StakerStatus; +use parking_lot::RwLock; +use std::sync::Arc; + +use frame_support::derive_impl; + +use crate::{log, log_current_time}; + +pub const INIT_TIMESTAMP: BlockNumber = 30_000; +pub const BLOCK_TIME: BlockNumber = 1000; + +type Block = frame_system::mocking::MockBlockU32; +type Extrinsic = testing::TestXt; +pub(crate) type T = Runtime; + +frame_support::construct_runtime!( + pub enum Runtime { + System: frame_system, + + // EPM core and sub-pallets + ElectionProvider: epm_core_pallet, + VerifierPallet: epm_verifier_pallet, + SignedPallet: epm_signed_pallet, + UnsignedPallet: epm_unsigned_pallet, + + Pools: pallet_nomination_pools, + Staking: pallet_staking, + Balances: pallet_balances, + BagsList: pallet_bags_list, + Session: pallet_session, + Historical: pallet_session::historical, + Timestamp: pallet_timestamp, + } +); + +pub(crate) type AccountId = u64; +pub(crate) type AccountIndex = u32; +pub(crate) type BlockNumber = u32; +pub(crate) type Balance = u64; +pub(crate) type VoterIndex = u16; +pub(crate) type TargetIndex = u16; +pub(crate) type Moment = u32; + +pub type Solver = SequentialPhragmen; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type Block = Block; + type AccountData = pallet_balances::AccountData; +} + +const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); +parameter_types! { + pub static ExistentialDeposit: Balance = 1; + pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights + ::with_sensible_defaults( + Weight::from_parts(2u64 * constants::WEIGHT_REF_TIME_PER_SECOND, u64::MAX), + NORMAL_DISPATCH_RATIO, + ); +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Runtime { + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type MaxFreezes = VariantCountOf; + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; + type FreezeIdentifier = RuntimeFreezeReason; +} + +impl pallet_timestamp::Config for Runtime { + type Moment = Moment; + type OnTimestampSet = (); + type MinimumPeriod = traits::ConstU32<5>; + type WeightInfo = (); +} + +parameter_types! { + pub static Period: u32 = 30; + pub static Offset: u32 = 0; +} + +sp_runtime::impl_opaque_keys! { + pub struct SessionKeys { + pub other: OtherSessionHandler, + } +} + +impl pallet_session::Config for Runtime { + type SessionManager = pallet_session::historical::NoteHistoricalRoot; + type Keys = SessionKeys; + type ShouldEndSession = pallet_session::PeriodicSessions; + type NextSessionRotation = pallet_session::PeriodicSessions; + type SessionHandler = (OtherSessionHandler,); + type RuntimeEvent = RuntimeEvent; + type ValidatorId = AccountId; + type ValidatorIdOf = pallet_staking::StashOf; + type WeightInfo = (); +} +impl pallet_session::historical::Config for Runtime { + type FullIdentification = pallet_staking::Exposure; + type FullIdentificationOf = pallet_staking::ExposureOf; +} + +frame_election_provider_support::generate_solution_type!( + #[compact] + pub struct MockNposSolution::< + VoterIndex = VoterIndex, + TargetIndex = TargetIndex, + Accuracy = PerU16, + MaxVoters = ConstU32::<2_000> + >(6) +); + +parameter_types! { + pub static SignedPhase: BlockNumber = 10; + pub static UnsignedPhase: BlockNumber = 10; + pub static SignedValidationPhase: BlockNumber = Pages::get().into(); + pub static Lookhaead: BlockNumber = Pages::get(); + pub static VoterSnapshotPerBlock: VoterIndex = 4; + pub static TargetSnapshotPerBlock: TargetIndex = 8; + pub static Pages: PageIndex = 3; + pub static ExportPhaseLimit: BlockNumber = (Pages::get() * 2u32).into(); + + // TODO: remove what's not needed from here down: + + // we expect a minimum of 3 blocks in signed phase and unsigned phases before trying + // entering in emergency phase after the election failed. + pub static MinBlocksBeforeEmergency: BlockNumber = 3; + #[derive(Debug)] + pub static MaxVotesPerVoter: u32 = 16; + pub static SignedFixedDeposit: Balance = 1; + pub static SignedDepositIncreaseFactor: Percent = Percent::from_percent(10); + pub static ElectionBounds: frame_election_provider_support::bounds::ElectionBounds = ElectionBoundsBuilder::default() + .voters_count(1_000.into()).targets_count(1_000.into()).build(); +} + +pub struct EPMBenchmarkingConfigs; +impl pallet_election_provider_multi_block::BenchmarkingConfig for EPMBenchmarkingConfigs { + const VOTERS: u32 = 100; + const TARGETS: u32 = 50; + const VOTERS_PER_PAGE: [u32; 2] = [1, 5]; + const TARGETS_PER_PAGE: [u32; 2] = [1, 8]; +} + +impl epm_core_pallet::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type SignedPhase = SignedPhase; + type UnsignedPhase = UnsignedPhase; + type SignedValidationPhase = SignedValidationPhase; + type Lookhaead = Lookhaead; + type VoterSnapshotPerBlock = VoterSnapshotPerBlock; + type TargetSnapshotPerBlock = TargetSnapshotPerBlock; + type Pages = Pages; + type ExportPhaseLimit = ExportPhaseLimit; + type MaxWinnersPerPage = MaxWinnersPerPage; + type MaxBackersPerWinner = MaxBackersPerWinner; + type MinerConfig = Self; + type Fallback = frame_election_provider_support::NoElection<( + AccountId, + BlockNumber, + Staking, + MaxWinnersPerPage, + MaxBackersPerWinner, + )>; + type Verifier = VerifierPallet; + type DataProvider = Staking; + type BenchmarkingConfig = EPMBenchmarkingConfigs; + type WeightInfo = (); +} + +parameter_types! { + pub static SolutionImprovementThreshold: Perbill = Perbill::zero(); + pub static MaxWinnersPerPage: u32 = 4; + pub static MaxBackersPerWinner: u32 = 16; +} + +impl epm_verifier_pallet::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ForceOrigin = frame_system::EnsureRoot; + type SolutionImprovementThreshold = SolutionImprovementThreshold; + type SolutionDataProvider = SignedPallet; + type WeightInfo = (); +} + +parameter_types! { + pub static DepositBase: Balance = 10; + pub static DepositPerPage: Balance = 1; + pub static Reward: Balance = 10; + pub static MaxSubmissions: u32 = 5; +} + +impl epm_signed_pallet::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type EstimateCallFee = ConstU32<8>; + type OnSlash = (); // burn + type DepositBase = ConstDepositBase; + type DepositPerPage = DepositPerPage; + type Reward = Reward; + type MaxSubmissions = MaxSubmissions; + type RuntimeHoldReason = RuntimeHoldReason; + type WeightInfo = (); +} + +pub struct ConstDepositBase; +impl sp_runtime::traits::Convert for ConstDepositBase { + fn convert(_a: usize) -> Balance { + DepositBase::get() + } +} + +parameter_types! { + pub static OffchainRepeatInterval: BlockNumber = 0; + pub static TransactionPriority: transaction_validity::TransactionPriority = 1; + pub static MinerMaxLength: u32 = 256; + pub static MinerMaxWeight: Weight = BlockWeights::get().max_block; +} + +impl epm_unsigned_pallet::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type OffchainRepeatInterval = OffchainRepeatInterval; + type MinerTxPriority = TransactionPriority; + type MaxLength = MinerMaxLength; + type MaxWeight = MinerMaxWeight; + type WeightInfo = (); +} + +impl miner::Config for Runtime { + type AccountId = AccountId; + type Solution = MockNposSolution; + type Solver = Solver; + type Pages = Pages; + type MaxVotesPerVoter = ConstU32<16>; + type MaxWinnersPerPage = MaxWinnersPerPage; + type MaxBackersPerWinner = MaxBackersPerWinner; + type VoterSnapshotPerBlock = VoterSnapshotPerBlock; + type TargetSnapshotPerBlock = TargetSnapshotPerBlock; + type MaxWeight = MinerMaxWeight; + type MaxLength = MinerMaxLength; +} + +const THRESHOLDS: [VoteWeight; 9] = [10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]; + +parameter_types! { + pub static BagThresholds: &'static [sp_npos_elections::VoteWeight] = &THRESHOLDS; + pub const SessionsPerEra: sp_staking::SessionIndex = 2; + pub const BondingDuration: sp_staking::EraIndex = 28; + pub const SlashDeferDuration: sp_staking::EraIndex = 7; // 1/4 the bonding duration. +} + +impl pallet_bags_list::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type ScoreProvider = Staking; + type BagThresholds = BagThresholds; + type Score = VoteWeight; +} + +pub struct BalanceToU256; +impl sp_runtime::traits::Convert for BalanceToU256 { + fn convert(n: Balance) -> sp_core::U256 { + n.into() + } +} + +pub struct U256ToBalance; +impl sp_runtime::traits::Convert for U256ToBalance { + fn convert(n: sp_core::U256) -> Balance { + n.try_into().unwrap() + } +} + +parameter_types! { + pub const PoolsPalletId: frame_support::PalletId = frame_support::PalletId(*b"py/nopls"); + pub static MaxUnbonding: u32 = 8; +} + +impl pallet_nomination_pools::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type Currency = Balances; + type RuntimeFreezeReason = RuntimeFreezeReason; + type RewardCounter = sp_runtime::FixedU128; + type BalanceToU256 = BalanceToU256; + type U256ToBalance = U256ToBalance; + type StakeAdapter = pallet_nomination_pools::adapter::TransferStake; + type PostUnbondingPoolsWindow = ConstU32<2>; + type PalletId = PoolsPalletId; + type MaxMetadataLen = ConstU32<256>; + type MaxUnbonding = MaxUnbonding; + type MaxPointsToBalance = frame_support::traits::ConstU8<10>; + type AdminOrigin = frame_system::EnsureRoot; +} + +parameter_types! { + pub static MaxUnlockingChunks: u32 = 32; + pub MaxControllersInDeprecationBatch: u32 = 5900; + pub static MaxValidatorSet: u32 = 500; +} + +/// Upper limit on the number of NPOS nominations. +const MAX_QUOTA_NOMINATIONS: u32 = 16; +/// Disabling factor set explicitly to byzantine threshold +pub(crate) const SLASHING_DISABLING_FACTOR: usize = 3; + +#[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] +impl pallet_staking::Config for Runtime { + type Currency = Balances; + type CurrencyBalance = Balance; + type UnixTime = Timestamp; + type SessionsPerEra = SessionsPerEra; + type BondingDuration = BondingDuration; + type SlashDeferDuration = SlashDeferDuration; + type AdminOrigin = EnsureRoot; // root can cancel slashes + type SessionInterface = Self; + type EraPayout = (); + type NextNewSession = Session; + type MaxExposurePageSize = ConstU32<256>; + type MaxValidatorSet = MaxValidatorSet; + type ElectionProvider = ElectionProvider; + type GenesisElectionProvider = onchain::OnChainExecution; + type VoterList = BagsList; + type NominationsQuota = pallet_staking::FixedNominationsQuota; + type TargetList = pallet_staking::UseValidatorsMap; + type MaxUnlockingChunks = MaxUnlockingChunks; + type EventListeners = Pools; + type WeightInfo = pallet_staking::weights::SubstrateWeight; + type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy; + type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; +} + +impl frame_system::offchain::SendTransactionTypes for Runtime +where + RuntimeCall: From, +{ + type OverarchingCall = RuntimeCall; + type Extrinsic = Extrinsic; +} + +pub struct OnChainSeqPhragmen; + +parameter_types! { + pub static VotersBound: u32 = 600; + pub static TargetsBound: u32 = 400; +} + +impl onchain::Config for OnChainSeqPhragmen { + type System = Runtime; + type Solver = Solver; + type DataProvider = Staking; + type WeightInfo = (); + type Bounds = ElectionBounds; + type MaxBackersPerWinner = MaxBackersPerWinner; + type MaxWinnersPerPage = MaxWinnersPerPage; +} + +pub struct OtherSessionHandler; +impl traits::OneSessionHandler for OtherSessionHandler { + type Key = testing::UintAuthorityId; + + fn on_genesis_session<'a, I: 'a>(_: I) + where + I: Iterator, + AccountId: 'a, + { + } + + fn on_new_session<'a, I: 'a>(_: bool, _: I, _: I) + where + I: Iterator, + AccountId: 'a, + { + } + + fn on_disabled(_validator_index: u32) {} +} + +impl sp_runtime::BoundToRuntimeAppPublic for OtherSessionHandler { + type Public = testing::UintAuthorityId; +} + +pub struct StakingExtBuilder { + validator_count: u32, + minimum_validator_count: u32, + min_nominator_bond: Balance, + min_validator_bond: Balance, + status: BTreeMap>, + stakes: BTreeMap, + stakers: Vec<(AccountId, AccountId, Balance, StakerStatus)>, +} + +impl Default for StakingExtBuilder { + fn default() -> Self { + let stakers = vec![ + // (stash, ctrl, stake, status) + (11, 11, 1000, StakerStatus::::Validator), + (21, 21, 1000, StakerStatus::::Validator), + (31, 31, 500, StakerStatus::::Validator), + (41, 41, 1500, StakerStatus::::Validator), + (51, 51, 1500, StakerStatus::::Validator), + (61, 61, 1500, StakerStatus::::Validator), + (71, 71, 1500, StakerStatus::::Validator), + (81, 81, 1500, StakerStatus::::Validator), + (91, 91, 1500, StakerStatus::::Validator), + (101, 101, 500, StakerStatus::::Validator), + // idle validators + (201, 201, 1000, StakerStatus::::Idle), + (301, 301, 1000, StakerStatus::::Idle), + // nominators + (10, 10, 2000, StakerStatus::::Nominator(vec![11, 21])), + (20, 20, 2000, StakerStatus::::Nominator(vec![31])), + (30, 30, 2000, StakerStatus::::Nominator(vec![91, 101])), + (40, 40, 2000, StakerStatus::::Nominator(vec![11, 101])), + ]; + + Self { + validator_count: 6, + minimum_validator_count: 0, + min_nominator_bond: ExistentialDeposit::get(), + min_validator_bond: ExistentialDeposit::get(), + status: Default::default(), + stakes: Default::default(), + stakers, + } + } +} + +impl StakingExtBuilder { + pub fn validator_count(mut self, n: u32) -> Self { + self.validator_count = n; + self + } +} + +pub struct EpmExtBuilder {} + +impl Default for EpmExtBuilder { + fn default() -> Self { + EpmExtBuilder {} + } +} + +impl EpmExtBuilder { + pub fn disable_emergency_throttling(self) -> Self { + ::set(0); + self + } + + pub fn phases(self, signed: BlockNumber, unsigned: BlockNumber) -> Self { + ::set(signed); + ::set(unsigned); + self + } +} + +pub struct BalancesExtBuilder { + balances: Vec<(AccountId, Balance)>, +} + +impl Default for BalancesExtBuilder { + fn default() -> Self { + let balances = vec![ + // (account_id, balance) + (1, 10), + (2, 20), + (3, 300), + (4, 400), + // nominators + (10, 10_000), + (20, 10_000), + (30, 10_000), + (40, 10_000), + (50, 10_000), + (60, 10_000), + (70, 10_000), + (80, 10_000), + (90, 10_000), + (100, 10_000), + (200, 10_000), + // validators + (11, 1000), + (21, 2000), + (31, 3000), + (41, 4000), + (51, 5000), + (61, 6000), + (71, 7000), + (81, 8000), + (91, 9000), + (101, 10000), + (201, 20000), + (301, 20000), + // This allows us to have a total_payout different from 0. + (999, 1_000_000_000_000), + ]; + Self { balances } + } +} + +pub struct ExtBuilder { + staking_builder: StakingExtBuilder, + epm_builder: EpmExtBuilder, + balances_builder: BalancesExtBuilder, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { + staking_builder: StakingExtBuilder::default(), + epm_builder: EpmExtBuilder::default(), + balances_builder: BalancesExtBuilder::default(), + } + } +} + +impl ExtBuilder { + pub fn build(&self) -> sp_io::TestExternalities { + sp_tracing::try_init_simple(); + let mut storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + let _ = pallet_balances::GenesisConfig:: { + balances: self.balances_builder.balances.clone(), + } + .assimilate_storage(&mut storage); + + let mut stakers = self.staking_builder.stakers.clone(); + self.staking_builder.status.clone().into_iter().for_each(|(stash, status)| { + let (_, _, _, ref mut prev_status) = stakers + .iter_mut() + .find(|s| s.0 == stash) + .expect("set_status staker should exist; qed"); + *prev_status = status; + }); + // replaced any of the stakes if needed. + self.staking_builder.stakes.clone().into_iter().for_each(|(stash, stake)| { + let (_, _, ref mut prev_stake, _) = stakers + .iter_mut() + .find(|s| s.0 == stash) + .expect("set_stake staker should exits; qed."); + *prev_stake = stake; + }); + + let _ = pallet_staking::GenesisConfig:: { + stakers: stakers.clone(), + validator_count: self.staking_builder.validator_count, + minimum_validator_count: self.staking_builder.minimum_validator_count, + slash_reward_fraction: Perbill::from_percent(10), + min_nominator_bond: self.staking_builder.min_nominator_bond, + min_validator_bond: self.staking_builder.min_validator_bond, + ..Default::default() + } + .assimilate_storage(&mut storage); + + let _ = pallet_session::GenesisConfig:: { + // set the keys for the first session. + keys: stakers + .into_iter() + .map(|(id, ..)| (id, id, SessionKeys { other: (id as u64).into() })) + .collect(), + ..Default::default() + } + .assimilate_storage(&mut storage); + + let mut ext = sp_io::TestExternalities::from(storage); + + // We consider all test to start after timestamp is initialized This must be ensured by + // having `timestamp::on_initialize` called before `staking::on_initialize`. + ext.execute_with(|| { + System::set_block_number(1); + Session::on_initialize(1); + >::on_initialize(1); + Timestamp::set_timestamp(INIT_TIMESTAMP); + }); + + ext + } + + pub fn staking(mut self, builder: StakingExtBuilder) -> Self { + self.staking_builder = builder; + self + } + + pub fn epm(mut self, builder: EpmExtBuilder) -> Self { + self.epm_builder = builder; + self + } + + pub fn balances(mut self, builder: BalancesExtBuilder) -> Self { + self.balances_builder = builder; + self + } + + pub fn build_offchainify( + self, + ) -> (sp_io::TestExternalities, Arc>, Arc>) { + // add offchain and pool externality extensions. + let mut ext = self.build(); + let (offchain, offchain_state) = TestOffchainExt::new(); + let (pool, pool_state) = TestTransactionPoolExt::new(); + + ext.register_extension(OffchainDbExt::new(offchain.clone())); + ext.register_extension(OffchainWorkerExt::new(offchain)); + ext.register_extension(TransactionPoolExt::new(pool)); + + (ext, pool_state, offchain_state) + } + + pub fn build_and_execute(self, test: impl FnOnce() -> ()) { + let mut ext = self.build(); + ext.execute_with(test); + + #[cfg(feature = "try-runtime")] + ext.execute_with(|| { + let bn = System::block_number(); + + assert_ok!(>::try_state(bn)); + assert_ok!(>::try_state(bn)); + assert_ok!(>::try_state(bn)); + }); + } +} + +// Progress to given block, triggering session and era changes as we progress and ensuring that +// there is a solution queued when expected. +pub fn roll_to(n: BlockNumber, delay_solution: bool) { + for b in (System::block_number()) + 1..=n { + System::set_block_number(b); + Session::on_initialize(b); + Timestamp::set_timestamp(System::block_number() * BLOCK_TIME + INIT_TIMESTAMP); + + if ElectionProvider::current_phase() == Phase::Signed && !delay_solution { + let _ = try_submit_paged_solution().map_err(|e| { + log!(info, "failed to mine/queue solution: {:?}", e); + }); + }; + + ElectionProvider::on_initialize(b); + VerifierPallet::on_initialize(b); + SignedPallet::on_initialize(b); + UnsignedPallet::on_initialize(b); + + Staking::on_initialize(b); + if b != n { + Staking::on_finalize(System::block_number()); + } + + log_current_time(); + } +} + +// Progress to given block, triggering session and era changes as we progress and ensuring that +// there is a solution queued when expected. +pub fn roll_to_with_ocw(n: BlockNumber, pool: Arc>, delay_solution: bool) { + for b in (System::block_number()) + 1..=n { + System::set_block_number(b); + Session::on_initialize(b); + Timestamp::set_timestamp(System::block_number() * BLOCK_TIME + INIT_TIMESTAMP); + + ElectionProvider::on_initialize(b); + VerifierPallet::on_initialize(b); + SignedPallet::on_initialize(b); + UnsignedPallet::on_initialize(b); + + ElectionProvider::offchain_worker(b); + + if !delay_solution && pool.read().transactions.len() > 0 { + // decode submit_unsigned callable that may be queued in the pool by ocw. skip all + // other extrinsics in the pool. + for encoded in &pool.read().transactions { + let _extrinsic = Extrinsic::decode(&mut &encoded[..]).unwrap(); + + // TODO(gpestana): fix when EPM sub-pallets are ready + //let _ = match extrinsic.call { + // RuntimeCall::ElectionProvider( + // call @ Call::submit_unsigned { .. }, + // ) => { + // // call submit_unsigned callable in OCW pool. + // assert_ok!(call.dispatch_bypass_filter(RuntimeOrigin::none())); + // }, + // _ => (), + //}; + } + + pool.try_write().unwrap().transactions.clear(); + } + + Staking::on_initialize(b); + if b != n { + Staking::on_finalize(System::block_number()); + } + + log_current_time(); + } +} +// helper to progress one block ahead. +pub fn roll_one(pool: Option>>, delay_solution: bool) { + let bn = System::block_number().saturating_add(1); + match pool { + None => roll_to(bn, delay_solution), + Some(pool) => roll_to_with_ocw(bn, pool, delay_solution), + } +} + +/// Progresses from the current block number (whatever that may be) to the block where the session +/// `session_index` starts. +pub(crate) fn start_session( + session_index: SessionIndex, + pool: Arc>, + delay_solution: bool, +) { + let end = if Offset::get().is_zero() { + Period::get() * session_index + } else { + Offset::get() * session_index + Period::get() * session_index + }; + + assert!(end >= System::block_number()); + + roll_to_with_ocw(end, pool, delay_solution); + + // session must have progressed properly. + assert_eq!( + Session::current_index(), + session_index, + "current session index = {}, expected = {}", + Session::current_index(), + session_index, + ); +} + +/// Go one session forward. +pub(crate) fn advance_session(pool: Arc>) { + let current_index = Session::current_index(); + start_session(current_index + 1, pool, false); +} + +pub(crate) fn advance_session_delayed_solution(pool: Arc>) { + let current_index = Session::current_index(); + start_session(current_index + 1, pool, true); +} + +pub(crate) fn start_next_active_era(pool: Arc>) -> Result<(), ()> { + start_active_era(active_era() + 1, pool, false) +} + +pub(crate) fn start_next_active_era_delayed_solution( + pool: Arc>, +) -> Result<(), ()> { + start_active_era(active_era() + 1, pool, true) +} + +pub(crate) fn advance_eras(n: usize, pool: Arc>) { + for _ in 0..n { + assert_ok!(start_next_active_era(pool.clone())); + } +} + +/// Progress until the given era. +pub(crate) fn start_active_era( + era_index: EraIndex, + pool: Arc>, + delay_solution: bool, +) -> Result<(), ()> { + let era_before = current_era(); + + start_session((era_index * >::get()).into(), pool, delay_solution); + + log!( + info, + "start_active_era - era_before: {}, current era: {} -> progress to: {} -> after era: {}", + era_before, + active_era(), + era_index, + current_era(), + ); + + // if the solution was not delayed, era should have progressed. + if !delay_solution && (active_era() != era_index || current_era() != active_era()) { + Err(()) + } else { + Ok(()) + } +} + +pub(crate) fn active_era() -> EraIndex { + Staking::active_era().unwrap().index +} + +pub(crate) fn current_era() -> EraIndex { + Staking::current_era().unwrap() +} + +// Fast forward until a given election phase. +pub fn roll_to_phase(phase: Phase, delay: bool) { + while ElectionProvider::current_phase() != phase { + roll_to(System::block_number() + 1, delay); + } +} + +pub fn election_prediction() -> BlockNumber { + <::DataProvider as ElectionDataProvider>::next_election_prediction( + System::block_number(), + ) +} + +parameter_types! { + pub static LastSolutionSubmittedFor: Option = None; +} + +pub(crate) fn try_submit_paged_solution() -> Result<(), ()> { + let submit = || { + // TODO: to finish. + let (paged_solution, _) = + miner::Miner::<::MinerConfig>::mine_paged_solution_with_snapshot( + voters_snapshot, + targets_snapshot, + Pages::get(), + round, + desired_targets, + false, + ) + .unwrap(); + + let _ = SignedPallet::register(RuntimeOrigin::signed(10), paged_solution.score).unwrap(); + + for (idx, page) in paged_solution.solution_pages.into_iter().enumerate() {} + log!( + info, + "submitter: successfully submitted {} pages with {:?} score in round {}.", + Pages::get(), + paged_solution.score, + ElectionProvider::current_round(), + ); + }; + + match LastSolutionSubmittedFor::get() { + Some(submitted_at) => { + if submitted_at == ElectionProvider::current_round() { + // solution already submitted in this round, do nothing. + } else { + // haven't submit in this round, submit it. + submit() + } + }, + // never submitted, do it. + None => submit(), + }; + LastSolutionSubmittedFor::set(Some(ElectionProvider::current_round())); + + Ok(()) +} + +pub(crate) fn on_offence_now( + offenders: &[OffenceDetails>], + slash_fraction: &[Perbill], +) { + let now = Staking::active_era().unwrap().index; + let _ = Staking::on_offence( + offenders, + slash_fraction, + Staking::eras_start_session_index(now).unwrap(), + ); +} + +// Add offence to validator, slash it. +pub(crate) fn add_slash(who: &AccountId) { + on_offence_now( + &[OffenceDetails { + offender: (*who, Staking::eras_stakers(active_era(), who)), + reporters: vec![], + }], + &[Perbill::from_percent(10)], + ); +} + +// Slashes 1/2 of the active set. Returns the `AccountId`s of the slashed validators. +pub(crate) fn slash_half_the_active_set() -> Vec { + let mut slashed = Session::validators(); + slashed.truncate(slashed.len() / 2); + + for v in slashed.iter() { + add_slash(v); + } + + slashed +} + +// Slashes a percentage of the active nominators that haven't been slashed yet, with +// a minimum of 1 validator slash. +pub(crate) fn slash_percentage(percentage: Perbill) -> Vec { + let validators = Session::validators(); + let mut remaining_slashes = (percentage * validators.len() as u32).max(1); + let mut slashed = vec![]; + + for v in validators.into_iter() { + if remaining_slashes != 0 { + add_slash(&v); + slashed.push(v); + remaining_slashes -= 1; + } + } + slashed +} + +pub(crate) fn set_minimum_election_score( + _minimal_stake: ExtendedBalance, + _sum_stake: ExtendedBalance, + _sum_stake_squared: ExtendedBalance, +) -> Result<(), ()> { + todo!() +} + +pub(crate) fn staked_amount_for(account_id: AccountId) -> Balance { + pallet_staking::asset::staked::(&account_id) +} + +pub(crate) fn staking_events() -> Vec> { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let RuntimeEvent::Staking(inner) = e { Some(inner) } else { None }) + .collect::>() +} + +pub(crate) fn epm_events() -> Vec> { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map( + |e| { + if let RuntimeEvent::ElectionProvider(inner) = e { + Some(inner) + } else { + None + } + }, + ) + .collect::>() +} diff --git a/substrate/frame/election-provider-multi-block/src/benchmarking.rs b/substrate/frame/election-provider-multi-block/src/benchmarking.rs new file mode 100644 index 0000000000000..d0d9a98b6bae3 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/benchmarking.rs @@ -0,0 +1,318 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Benchmarking for the Elections Multiblock pallet and sub-pallets. + +use super::*; +use crate::Snapshot; + +use frame_benchmarking::v2::*; +use frame_support::{assert_ok, traits::OnInitialize}; + +/// Seed to generate account IDs. +const SEED: u32 = 999; +/// Default page to use in the benchmarking. +const PAGE: u32 = 0; +/// Minimum number of voters in the data provider and per snapshot page. +const MIN_VOTERS: u32 = 10; + +#[benchmarks( + where T: ConfigCore + ConfigSigned + ConfigVerifier, +)] +mod benchmarks { + use super::*; + + #[benchmark] + fn create_targets_snapshot_paged( + t: Linear< + { T::BenchmarkingConfig::TARGETS_PER_PAGE[0] }, + { T::BenchmarkingConfig::TARGETS_PER_PAGE[1] }, + >, + ) -> Result<(), BenchmarkError> { + helpers::setup_data_provider::( + T::BenchmarkingConfig::VOTERS, + T::BenchmarkingConfig::TARGETS.max(t), + ); + + assert!(Snapshot::::targets().is_none()); + + #[block] + { + assert_ok!(PalletCore::::create_targets_snapshot_inner(t)); + } + + assert!(Snapshot::::targets().is_some()); + + Ok(()) + } + + #[benchmark] + fn create_voters_snapshot_paged( + v: Linear< + { T::BenchmarkingConfig::VOTERS_PER_PAGE[0] }, + { T::BenchmarkingConfig::VOTERS_PER_PAGE[1] }, + >, + ) -> Result<(), BenchmarkError> { + helpers::setup_data_provider::( + T::BenchmarkingConfig::VOTERS.max(v), + T::BenchmarkingConfig::TARGETS, + ); + + assert!(Snapshot::::voters(PAGE).is_none()); + + #[block] + { + assert_ok!(PalletCore::::create_voters_snapshot_inner(PAGE, v)); + } + + assert!(Snapshot::::voters(PAGE).is_some()); + + Ok(()) + } + + #[benchmark] + fn on_initialize_start_signed() -> Result<(), BenchmarkError> { + assert!(PalletCore::::current_phase() == Phase::Off); + + #[block] + { + let _ = PalletCore::::start_signed_phase(); + } + + assert!(PalletCore::::current_phase() == Phase::Signed); + + Ok(()) + } + + #[benchmark] + fn on_phase_transition() -> Result<(), BenchmarkError> { + assert!(PalletCore::::current_phase() == Phase::Off); + + #[block] + { + let _ = PalletCore::::phase_transition(Phase::Snapshot(0)); + } + + assert!(PalletCore::::current_phase() == Phase::Snapshot(0)); + + Ok(()) + } + + #[benchmark] + fn on_initialize_start_export() -> Result<(), BenchmarkError> { + #[block] + { + let _ = PalletCore::::do_export_phase(1u32.into(), 100u32.into()); + } + + Ok(()) + } + + #[benchmark] + fn on_initialize_do_nothing() -> Result<(), BenchmarkError> { + assert!(PalletCore::::current_phase() == Phase::Off); + + #[block] + { + let _ = PalletCore::::on_initialize(0u32.into()); + } + + assert!(PalletCore::::current_phase() == Phase::Off); + + Ok(()) + } + + impl_benchmark_test_suite!( + PalletCore, + crate::mock::ExtBuilder::default(), + crate::mock::Runtime, + exec_name = build_and_execute + ); +} + +/// Helper fns to use across the benchmarking of the core pallet and its sub-pallets. +pub(crate) mod helpers { + use super::*; + use crate::{signed::pallet::Submissions, unsigned::miner::Miner, SolutionOf}; + use frame_election_provider_support::ElectionDataProvider; + use frame_support::traits::tokens::Precision; + use sp_std::vec::Vec; + + pub(crate) fn setup_funded_account( + domain: &'static str, + id: u32, + balance_factor: u32, + ) -> T::AccountId { + use frame_support::traits::fungible::{Balanced, Inspect}; + + let account = frame_benchmarking::account::(domain, id, SEED); + let funds = (T::Currency::minimum_balance() + 1u32.into()) * balance_factor.into(); + // increase issuance to ensure a sane voter weight. + let _ = T::Currency::deposit(&account, funds.into(), Precision::Exact); + + account + } + + /// Generates and adds `v` voters and `t` targets in the data provider stores. The voters + /// nominate `DataProvider::MaxVotesPerVoter` targets. + pub(crate) fn setup_data_provider(v: u32, t: u32) { + ::DataProvider::clear(); + + log!(info, "setup_data_provider with v: {}, t: {}", v, t,); + + // generate and add targets. + let mut targets = (0..t) + .map(|i| { + let target = setup_funded_account::("Target", i, 200); + ::DataProvider::add_target(target.clone()); + target + }) + .collect::>(); + + // we should always have enough voters to fill. + assert!( + targets.len() > + <::DataProvider as ElectionDataProvider>::MaxVotesPerVoter::get() + as usize + ); + + targets.truncate( + <::DataProvider as ElectionDataProvider>::MaxVotesPerVoter::get() as usize, + ); + + // generate and add voters with `MaxVotesPerVoter` nominations from the list of targets. + (0..v.max(MIN_VOTERS)).for_each(|i| { + let voter = setup_funded_account::("Voter", i, 2_000); + <::DataProvider as ElectionDataProvider>::add_voter( + voter, + 1_000, + targets.clone().try_into().unwrap(), + ); + }); + } + + /// Generates the full paged snapshot for both targets and voters. + pub(crate) fn setup_snapshot(v: u32, t: u32) -> Result<(), &'static str> { + // set desired targets to match the size off the target page. + ::set_desired_targets(t); + + log!( + info, + "setting up the snapshot. voters/page: {:?}, targets/page: {:?} (desired_targets: {:?})", + v, + t, + ::desired_targets(), + ); + + let _ = PalletCore::::create_targets_snapshot_inner(t) + .map_err(|_| "error creating the target snapshot, most likely `T::TargetSnapshotPerBlock` config needs to be adjusted")?; + + for page in 0..T::Pages::get() { + let _ = PalletCore::::create_voters_snapshot_inner(page, v) + .map_err(|_| "error creating the voter snapshot, most likely `T::VoterSnapshotPerBlock` config needs to be adjusted")?; + } + + Ok(()) + } + + /// Mines a full solution for the current snapshot and submits `maybe_page`. Otherwise submits + /// all pages. `valid` defines whether the solution is valid or not. + pub(crate) fn mine_and_submit< + T: ConfigCore + ConfigUnsigned + ConfigSigned + ConfigVerifier, + >( + maybe_page: Option, + valid: bool, + ) -> Result { + // ensure that the number of desired targets fits within the bound of max winners per page, + // otherwise preemptively since the feasibilty check will fail. + ensure!( + ::desired_targets() + .expect("desired_targets is set") <= + ::MaxWinnersPerPage::get(), + "`MaxWinnersPerPage` must be equal or higher than desired_targets. fix the configs.", + ); + + let submitter = setup_funded_account::("Submitter", 1, 1_000); + + let (mut solution, _) = + Miner::::mine_paged_solution(T::Pages::get(), true).map_err( + |e| { + log!(info, "ERR:: {:?}", e); + "error mining solution" + }, + )?; + + // if the submission is full and the fesibility check must fail, mess up with the solution's + // claimed score to fail the verification (worst case scenario in terms of async solution + // verification). + let claimed_score = if maybe_page.is_none() && !valid { + solution.score.sum_stake += 1_000_000; + solution.score + } else { + solution.score + }; + + // set transition to phase to ensure the page mutation works. + PalletCore::::phase_transition(Phase::Signed); + + // first register submission. + PalletSigned::::do_register(&submitter, claimed_score, PalletCore::::current_round()) + .map_err(|_| "error registering solution")?; + + for page in maybe_page + .map(|p| sp_std::vec![p]) + .unwrap_or((0..T::Pages::get()).rev().collect::>()) + .into_iter() + { + let paged_solution = + solution.solution_pages.get(page as usize).ok_or("page out of bounds")?; + + // if it is a single page submission and submission should be invalid, make the paged + // paged submission invalid by tweaking the current snapshot. + if maybe_page.is_some() && !valid { + ensure_solution_invalid::(&paged_solution)?; + } + + // process and submit only onle page. + Submissions::::try_mutate_page( + &submitter, + PalletCore::::current_round(), + page, + Some(paged_solution.clone()), + ) + .map_err(|_| "error storing page")?; + } + + Ok(submitter) + } + + /// ensures `solution` will be considered invalid in the feasibility check by tweaking the + /// snapshot which was used to compute the solution and remove one of the targets from the + /// snapshot. NOTE: we expect that the `solution` was generated based on the current snapshot + /// state. + fn ensure_solution_invalid( + solution: &SolutionOf, + ) -> Result<(), &'static str> { + let new_count_targets = solution.unique_targets().len().saturating_sub(1); + + // remove a target from the snapshot to invalidate solution. + let _ = PalletCore::::create_targets_snapshot_inner(new_count_targets as u32) + .map_err(|_| "error regenerating the target snapshot")?; + + Ok(()) + } +} diff --git a/substrate/frame/election-provider-multi-block/src/helpers.rs b/substrate/frame/election-provider-multi-block/src/helpers.rs new file mode 100644 index 0000000000000..61c6f0b907ae3 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/helpers.rs @@ -0,0 +1,331 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 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. + +//! Some helper functions/macros for this crate. + +use crate::{ + types::{AllTargetPagesOf, AllVoterPagesOf, MaxWinnersPerPageOf, MinerVoterOf}, + SolutionTargetIndexOf, SolutionVoterIndexOf, +}; +use frame_election_provider_support::{PageIndex, VoteWeight}; +use frame_support::{traits::Get, BoundedVec}; +use sp_runtime::SaturatedConversion; +use sp_std::{cmp::Reverse, collections::btree_map::BTreeMap, vec, vec::Vec}; + +#[macro_export] +macro_rules! log { + ($level:tt, $pattern:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: $crate::LOG_TARGET, + concat!("[#{:?}]: 🎯🎯🎯 🗳 ", $pattern), >::block_number() $(, $values)* + ) + }; +} + +macro_rules! sublog { + ($level:tt, $sub_pallet:tt, $pattern:expr $(, $values:expr)* $(,)?) => { + #[cfg(not(feature = "std"))] + log!($level, $pattern $(, $values )*); + #[cfg(feature = "std")] + log::$level!( + target: format!("{}::{}", $crate::LOG_TARGET, $sub_pallet).as_ref(), + concat!("[#{:?}]: 🎯🎯🎯 🗳 ", $pattern), >::block_number() $(, $values )* + ) + }; +} + +use crate::unsigned::miner::Config as MinerConfig; + +/// Generate a btree-map cache of the voters and their indices within the provided `snapshot`. +/// +/// This does not care about pagination. `snapshot` might be a single page or the entire blob of +/// voters. +/// +/// This can be used to efficiently build index getter closures. +pub fn generate_voter_cache>( + snapshot: &BoundedVec, AnyBound>, +) -> BTreeMap { + let mut cache: BTreeMap = BTreeMap::new(); + snapshot.iter().enumerate().for_each(|(i, (x, _, _))| { + let _existed = cache.insert(x.clone(), i); + // if a duplicate exists, we only consider the last one. Defensive only, should never + // happen. + debug_assert!(_existed.is_none()); + }); + + cache +} + +pub fn generate_target_cache( + snapshot: &Vec, +) -> BTreeMap { + let mut cache: BTreeMap = BTreeMap::new(); + snapshot.iter().enumerate().for_each(|(i, x)| { + let _existed = cache.insert(x.clone(), i); + // if a duplicate exists, we only consider the last one. Defensive only, should never + // happen. + debug_assert!(_existed.is_none()); + }); + + cache +} + +pub fn generate_voter_staked_cache( + snapshot: &Vec<&MinerVoterOf>, +) -> BTreeMap { + let mut cache: BTreeMap = BTreeMap::new(); + snapshot.into_iter().enumerate().for_each(|(i, (x, _, _))| { + let _existed = cache.insert(x.clone(), i); + // if a duplicate exists, we only consider the last one. Defensive only, should never + // happen. + debug_assert!(_existed.is_none()); + }); + cache +} + +/// Generate an efficient closure of voters and the page in which they live in. +/// +/// The bucketing of voters into a page number is based on their position in the snapshot's page. +pub fn generate_voter_page_fn( + paged_snapshot: &AllVoterPagesOf, +) -> impl Fn(&T::AccountId) -> Option { + let mut cache: BTreeMap = BTreeMap::new(); + paged_snapshot + .iter() + .enumerate() + .map(|(page, whatever)| (page.saturated_into::(), whatever)) + .for_each(|(page, page_voters)| { + page_voters.iter().for_each(|(v, _, _)| { + let _existed = cache.insert(v.clone(), page); + // if a duplicate exists, we only consider the last one. Defensive only, should + // never happen. + debug_assert!(_existed.is_none()); + }); + }); + + move |who| cache.get(who).copied() +} + +pub fn generate_target_page_fn( + paged_snapshot: &AllTargetPagesOf, +) -> impl Fn(&T::AccountId) -> Option { + let mut cache: BTreeMap = BTreeMap::new(); + paged_snapshot + .iter() + .enumerate() + .map(|(page, whatever)| (page.saturated_into::(), whatever)) + .for_each(|(page, page_voters)| { + page_voters.iter().for_each(|t| { + let _existed = cache.insert(t.clone(), page); + // if a duplicate exists, we only consider the last one. Defensive only, should + // never happen. + debug_assert!(_existed.is_none()); + }); + }); + move |who| cache.get(who).copied() +} + +/// stake. +/// +/// The bucketing of voters into a page number is based on their relative stake in the assignments +/// set (the larger the stake, the higher the page). +pub fn generate_voter_page_stake_fn( + paged_snapshot: &AllVoterPagesOf, +) -> impl Fn(&T::AccountId) -> Option { + let mut cache: BTreeMap = BTreeMap::new(); + let mut sorted_by_weight: Vec<(VoteWeight, T::AccountId)> = vec![]; + + // sort voter stakes. + let _ = paged_snapshot.to_vec().iter().flatten().for_each(|voter| { + let pos = sorted_by_weight + .binary_search_by_key(&Reverse(&voter.1), |(weight, _)| Reverse(weight)) + .unwrap_or_else(|pos| pos); + sorted_by_weight.insert(pos, (voter.1, voter.0.clone())); + }); + + sorted_by_weight.iter().enumerate().for_each(|(idx, voter)| { + let page = idx + .saturating_div(MaxWinnersPerPageOf::::get() as usize) + .min(T::Pages::get() as usize); + let _existed = cache.insert(voter.1.clone(), page as PageIndex); + debug_assert!(_existed.is_none()); + }); + + move |who| cache.get(who).copied() +} + +/// Create a function that returns the index of a voter in the snapshot. +/// +/// The returning index type is the same as the one defined in `T::Solution::Voter`. +/// +/// ## Warning +/// +/// Note that this will represent the snapshot data from which the `cache` is generated. +pub fn voter_index_fn( + cache: &BTreeMap, +) -> impl Fn(&T::AccountId) -> Option> + '_ { + move |who| { + cache + .get(who) + .and_then(|i| >>::try_into(*i).ok()) + } +} + +/// Create a function that returns the index of a voter in the snapshot. +/// +/// Same as [`voter_index_fn`] but the returned function owns all its necessary data; nothing is +/// borrowed. +pub fn voter_index_fn_owned( + cache: BTreeMap, +) -> impl Fn(&T::AccountId) -> Option> { + move |who| { + cache + .get(who) + .and_then(|i| >>::try_into(*i).ok()) + } +} + +pub fn target_index_fn_owned( + cache: BTreeMap, +) -> impl Fn(&T::AccountId) -> Option> { + move |who| { + cache + .get(who) + .and_then(|i| >>::try_into(*i).ok()) + } +} + +/// Create a function that returns the index of a target in the snapshot. +/// +/// The returned index type is the same as the one defined in `T::Solution::Target`. +/// +/// Note: to the extent possible, the returned function should be cached and reused. Producing that +/// function requires a `O(n log n)` data transform. Each invocation of that function completes +/// in `O(log n)`. +pub fn target_index_fn( + snapshot: &Vec, +) -> impl Fn(&T::AccountId) -> Option> + '_ { + let cache: BTreeMap<_, _> = + snapshot.iter().enumerate().map(|(idx, account_id)| (account_id, idx)).collect(); + move |who| { + cache + .get(who) + .and_then(|i| >>::try_into(*i).ok()) + } +} + +/// Create a function that can map a voter index ([`SolutionVoterIndexOf`]) to the actual voter +/// account using a linearly indexible snapshot. +pub fn voter_at_fn( + snapshot: &Vec>, +) -> impl Fn(SolutionVoterIndexOf) -> Option + '_ { + move |i| { + as TryInto>::try_into(i) + .ok() + .and_then(|i| snapshot.get(i).map(|(x, _, _)| x).cloned()) + } +} + +/// Create a function that can map a target index ([`SolutionTargetIndexOf`]) to the actual target +/// account using a linearly indexible snapshot. +pub fn target_at_fn( + snapshot: &Vec, +) -> impl Fn(SolutionTargetIndexOf) -> Option + '_ { + move |i| { + as TryInto>::try_into(i) + .ok() + .and_then(|i| snapshot.get(i).cloned()) + } +} + +/// Same as [`voter_index_fn`], but the returning index is converted into usize, if possible. +/// +/// ## Warning +/// +/// Note that this will represent the snapshot data from which the `cache` is generated. +pub fn voter_index_fn_usize( + cache: &BTreeMap, +) -> impl Fn(&T::AccountId) -> Option + '_ { + move |who| cache.get(who).cloned() +} + +pub fn target_index_fn_usize( + cache: &BTreeMap, +) -> impl Fn(&T::AccountId) -> Option + '_ { + move |who| cache.get(who).cloned() +} + +/// Create a function to get the stake of a voter. +pub fn stake_of_fn<'a, T: MinerConfig, AnyBound: Get>( + snapshot: &'a BoundedVec, AnyBound>, + cache: &'a BTreeMap, +) -> impl Fn(&T::AccountId) -> VoteWeight + 'a { + move |who| { + if let Some(index) = cache.get(who) { + snapshot.get(*index).map(|(_, x, _)| x).cloned().unwrap_or_default() + } else { + 0 + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::mock::*; + + use sp_runtime::bounded_vec; + + type TargetsPerSnapshotPageOf = ::TargetSnapshotPerBlock; + + #[test] + fn paged_targets_indexing_works() { + ExtBuilder::default().build_and_execute(|| { + let all_target_pages: BoundedVec< + BoundedVec>, + ::Pages, + > = bounded_vec![ + bounded_vec![], // page 0 + bounded_vec![11, 61], // page 1 + bounded_vec![51, 101, 31, 41, 21, 81, 71, 91], // page 2 + ]; + + // flatten all targets. + let all_targets = all_target_pages.iter().cloned().flatten().collect::>(); + + // `targets_index_fn` get the snapshot page of the target. + let targets_index_fn = generate_target_page_fn::(&all_target_pages); + let all_targets_cache = generate_target_cache::(&all_targets); + let target_index = target_index_fn_usize::(&all_targets_cache); + + // check that the `targets_index_page` is correct. + for (page, targets) in all_target_pages.iter().enumerate() { + for t in targets { + assert_eq!(targets_index_fn(&t), Some(page as PageIndex)); + } + } + + // check that the `generate_target_cache` ad `target_index_fn_usize` return the correct + // btree cache/idx. + for (idx, target) in all_targets.iter().enumerate() { + assert_eq!(all_targets_cache.get(target), Some(idx).as_ref()); + assert_eq!(target_index(target), Some(idx)); + } + }) + } +} diff --git a/substrate/frame/election-provider-multi-block/src/lib.rs b/substrate/frame/election-provider-multi-block/src/lib.rs new file mode 100644 index 0000000000000..fd1b7cc05f0ac --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/lib.rs @@ -0,0 +1,1139 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Multi-phase, multi-block election provider pallet +//! +//! This pallet manages the NPoS election across its different phases, with the ability to accept +//! both on-chain and off-chain solutions. The off-chain solutions may be submitted as a signed or +//! unsigned transaction. Crucially, supports paginated, multi-block elections. The goal of +//! supporting paged elections is to scale the elections linearly with the number of blocks +//! allocated to the election. +//! +//! **PoV-friendly**: The work performed in a block by this pallet must be measurable and the time +//! and computation required per block must be deterministic with regards to the number of voters, +//! targets and any other configurations that are set apriori. These assumptions make this pallet +//! suitable to run on a parachain. +//! +//! ## Pallet organization and sub-pallets +//! +//! This pallet consists of a `core` pallet and multiple sub-pallets. Conceptually, the pallets have +//! a hierarquical relationship, where the `core` pallet sets and manages shared state between all +//! pallets. Its "child" pallets can not depend on the `core` pallet and iteract with it through +//! trait interfaces: +//! +//!```text +//! pallet-core +//! | +//! | +//! - pallet-verifier +//! | +//! - pallet-signed +//! | +//! - pallet-unsigned +//! ``` +//! +//! Each sub-pallet has a specific set of tasks and implement one or more interfaces to expose their +//! functionality to the core pallet: +//! - The [`verifier`] pallet provides an implementation of the [`verifier::Verifier`] trait, which +//! exposes the functionality to verify NPoS solutions in a multi-block context. In addition, it +//! implements [`verifier::AsyncVerifier`] which verifies multi-paged solution asynchronously. +//! - The [`signed`] pallet implements the signed phase, where off-chain entities commit to and +//! submit their election solutions. This pallet implements the +//! [`verifier::SolutionDataProvider`], which is used by the [`verifier`] pallet to fetch solution +//! data. +//! - The [`unsigned`] pallet implements the unsigned phase, where block authors can calculate and +//! submit through inherent paged solutions. This pallet also implements the +//! [`verifier::SolutionDataProvider`] interface. +//! +//! ### Pallet ordering +//! +//! Due to the assumptions of when the `on_initialize` hook must be called by the executor for the +//! core pallet and each sub-pallets, it is crucial the the pallets are ordered correctly in the +//! construct runtime. The ordering must be the following: +//! +//! ```text +//! 1. pallet-core +//! 2. pallet-verifier +//! 3. pallet-signed +//! 4. pallet-unsigned +//! ``` +//! +//! ## Election Phases +//! +//! This pallet manages the election phases which signal to the other sub-pallets which actions to +//! take at a given block. The election phases are the following: +//! +//! ```text +//! // ----------- ----------- -------------- ------------ -------- +//! // | | | | | | | +//! // Off Snapshot Signed SignedValidation Unsigned elect() Export +//! ``` +//! +//! Each phase duration depends on the estimate block number election, which can be fetched from +//! [`pallet::Config::DataProvider`]. +//! +//! > to-finish + +#![cfg_attr(not(feature = "std"), no_std)] +// TODO: remove +#![allow(dead_code)] + +use frame_election_provider_support::{ + bounds::ElectionBoundsBuilder, BoundedSupportsOf, ElectionDataProvider, ElectionProvider, + LockableElectionDataProvider, PageIndex, VoterOf, Weight, +}; +use frame_support::{ + defensive, ensure, + traits::{Defensive, DefensiveSaturating, Get}, + BoundedVec, +}; +use sp_runtime::traits::Zero; + +use frame_system::pallet_prelude::BlockNumberFor; + +#[macro_use] +pub mod helpers; + +pub mod signed; +pub mod types; +pub mod unsigned; +pub mod verifier; +pub mod weights; + +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarking; + +#[cfg(test)] +mod mock; + +pub use pallet::*; +pub use types::*; + +pub use crate::{unsigned::miner, verifier::Verifier, weights::WeightInfo}; + +/// Internal crate re-exports to use across benchmarking and tests. +#[cfg(any(test, feature = "runtime-benchmarks"))] +use crate::verifier::Pallet as PalletVerifier; + +const LOG_TARGET: &'static str = "runtime::multiblock-election"; + +/// Page configured for the election. +pub type PagesOf = ::Pages; + +/// Trait defining the benchmarking configs. +pub trait BenchmarkingConfig { + /// Range of voters registerd in the system. + const VOTERS: u32; + /// Range of targets registered in the system. + const TARGETS: u32; + /// Range of voters per snapshot page. + const VOTERS_PER_PAGE: [u32; 2]; + /// Range of targets per snapshot page. + const TARGETS_PER_PAGE: [u32; 2]; +} + +#[frame_support::pallet(dev_mode)] +pub mod pallet { + + use super::*; + use frame_support::{ + pallet_prelude::{ValueQuery, *}, + sp_runtime::Saturating, + Twox64Concat, + }; + use frame_system::pallet_prelude::BlockNumberFor; + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + + IsType<::RuntimeEvent> + + TryInto>; + + /// Duration of the signed phase; + #[pallet::constant] + type SignedPhase: Get>; + + /// Duration of the unsigned phase; + #[pallet::constant] + type UnsignedPhase: Get>; + + /// Duration of the signed validation phase. + /// + /// The duration of this phase SHOULD NOT be less than `T::Pages` and there is no point in + /// it being more than the maximum number of pages per submission. + #[pallet::constant] + type SignedValidationPhase: Get>; + + /// The number of blocks that the election should be ready before the election deadline. + #[pallet::constant] + type Lookhaead: Get>; + + /// The number of snapshot voters to fetch per block. + #[pallet::constant] + type VoterSnapshotPerBlock: Get; + + /// The number of snapshot targets to fetch per block. + #[pallet::constant] + type TargetSnapshotPerBlock: Get; + + /// Maximum number of supports (i.e. winners/validators/targets) that can be represented + /// in one page of a solution. + type MaxWinnersPerPage: Get; + + /// Maximum number of voters that can support a single target, across ALL the solution + /// pages. Thus, this can only be verified when processing the last solution page. + /// + /// This limit must be set so that the memory limits of the rest of the system are + /// respected. + type MaxBackersPerWinner: Get; + + /// The number of pages. + /// + /// A solution may contain at MOST this many pages. + #[pallet::constant] + type Pages: Get; + + /// The limit number of blocks that the `Phase::Export` will be open for. + /// + /// The export phase will terminate if it has been open for `T::ExportPhaseLimit` blocks or + /// the `EPM::call(0)` is called. + type ExportPhaseLimit: Get>; + + /// Something that will provide the election data. + type DataProvider: LockableElectionDataProvider< + AccountId = Self::AccountId, + BlockNumber = BlockNumberFor, + >; + + // The miner configuration. + type MinerConfig: miner::Config< + AccountId = AccountIdOf, + Pages = Self::Pages, + MaxVotesPerVoter = ::MaxVotesPerVoter, + TargetSnapshotPerBlock = Self::TargetSnapshotPerBlock, + VoterSnapshotPerBlock = Self::VoterSnapshotPerBlock, + MaxWinnersPerPage = Self::MaxWinnersPerPage, + MaxBackersPerWinner = Self::MaxBackersPerWinner, + >; + + /// Something that implements a fallback election. + /// + /// This provider must run the election in one block, thus it has at most 1 page. + type Fallback: ElectionProvider< + AccountId = Self::AccountId, + BlockNumber = BlockNumberFor, + DataProvider = Self::DataProvider, + MaxWinnersPerPage = ::MaxWinnersPerPage, + MaxBackersPerWinner = ::MaxBackersPerWinner, + Pages = ConstU32<1>, + >; + + /// Something that implements an election solution verifier. + type Verifier: verifier::Verifier< + AccountId = Self::AccountId, + Solution = SolutionOf, + > + verifier::AsyncVerifier; + + /// Benchmarking configurations for this and sub-pallets. + type BenchmarkingConfig: BenchmarkingConfig; + + /// The weights for this pallet. + type WeightInfo: WeightInfo; + } + + // Expose miner configs over the metadata such that they can be re-implemented. + #[pallet::extra_constants] + impl Pallet { + #[pallet::constant_name(MinerMaxVotesPerVoter)] + fn max_votes_per_voter() -> u32 { + ::MaxVotesPerVoter::get() + } + + #[pallet::constant_name(MinerMaxBackersPerWinner)] + fn max_backers_per_winner() -> u32 { + ::MaxBackersPerWinner::get() + } + + #[pallet::constant_name(MinerMaxWinnersPerPage)] + fn max_winners_per_page() -> u32 { + ::MaxWinnersPerPage::get() + } + } + + /// Election failure strategy. + #[pallet::storage] + pub(crate) type ElectionFailure = + StorageValue<_, ElectionFailureStrategy, ValueQuery>; + + /// Current phase. + #[pallet::storage] + pub(crate) type CurrentPhase = StorageValue<_, Phase>, ValueQuery>; + + /// Current round + #[pallet::storage] + pub(crate) type Round = StorageValue<_, u32, ValueQuery>; + + /// Paginated target snapshot. + #[pallet::storage] + pub(crate) type PagedTargetSnapshot = + StorageMap<_, Twox64Concat, PageIndex, BoundedVec>; + + /// Paginated voter snapshot. + #[pallet::storage] + pub(crate) type PagedVoterSnapshot = + StorageMap<_, Twox64Concat, PageIndex, VoterPageOf>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// There was a phase transition in a given round. + PhaseTransitioned { + from: Phase>, + to: Phase>, + round: u32, + }, + } + + #[pallet::error] + pub enum Error {} + + #[pallet::call] + impl Pallet {} + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(now: BlockNumberFor) -> Weight { + // ---------- ---------- ---------- ----------- ---------- -------- + // | | | | | | | + // Off Snapshot (Signed SigValid) Unsigned Export elect() + + use sp_runtime::traits::One; + + let export_deadline = T::ExportPhaseLimit::get().saturating_add(T::Lookhaead::get()); + let unsigned_deadline = export_deadline.saturating_add(T::UnsignedPhase::get()); + let signed_validation_deadline = + unsigned_deadline.saturating_add(T::SignedValidationPhase::get()); + let signed_deadline = signed_validation_deadline.saturating_add(T::SignedPhase::get()); + let snapshot_deadline = signed_deadline + .saturating_add(T::Pages::get().into()) + .saturating_add(One::one()); + + let next_election = T::DataProvider::next_election_prediction(now) + .saturating_sub(T::Lookhaead::get()) + .max(now); + + let remaining_blocks = next_election - now; + let current_phase = Self::current_phase(); + + log!( + trace, + "now {:?} - current phase {:?} | \ + snapshot_deadline: {:?} (at #{:?}), signed_deadline: {:?} (at #{:?}), \ + signed_validation_deadline: {:?} (at #{:?}), unsigned_deadline: {:?} (at #{:?}) \ + export_deadline: {:?} (at #{:?}) - [next election at #{:?}, remaining: {:?}]", + now, + current_phase, + snapshot_deadline, + next_election.saturating_sub(snapshot_deadline), + signed_deadline, + next_election.saturating_sub(signed_deadline), + signed_validation_deadline, + next_election.saturating_sub(signed_validation_deadline), + unsigned_deadline, + next_election.saturating_sub(unsigned_deadline), + export_deadline, + next_election.saturating_sub(export_deadline), + next_election, + remaining_blocks, + ); + + match current_phase { + // start snapshot. + Phase::Off + if remaining_blocks <= snapshot_deadline && + remaining_blocks > signed_deadline => + // allocate one extra block for the target snapshot. + Self::try_progress_snapshot(T::Pages::get() + 1), + + // continue snapshot. + Phase::Snapshot(x) if x > 0 => Self::try_progress_snapshot(x.saturating_sub(1)), + + // start unsigned phase if snapshot is ready and signed phase is disabled. + Phase::Snapshot(0) if T::SignedPhase::get().is_zero() => { + Self::phase_transition(Phase::Unsigned(now)); + T::WeightInfo::on_phase_transition() + }, + + // start signed phase. The `signed` pallet will take further actions now. + Phase::Snapshot(0) + if remaining_blocks <= signed_deadline && + remaining_blocks > signed_validation_deadline => + Self::start_signed_phase(), + + // start signed validation. The `signed` pallet will take further actions now. + Phase::Signed + if remaining_blocks <= signed_validation_deadline && + remaining_blocks > unsigned_deadline => + { + Self::phase_transition(Phase::SignedValidation(now)); + T::WeightInfo::on_phase_transition() + }, + + // start unsigned phase. The `unsigned` pallet will take further actions now. + Phase::Signed | Phase::SignedValidation(_) | Phase::Snapshot(0) + if remaining_blocks <= unsigned_deadline && remaining_blocks > Zero::zero() => + { + Self::phase_transition(Phase::Unsigned(now)); + T::WeightInfo::on_phase_transition() + }, + + // EPM is "serving" the staking pallet with the election results. + Phase::Export(started_at) => Self::do_export_phase(now, started_at), + + _ => T::WeightInfo::on_initialize_do_nothing(), + } + } + + fn integrity_test() { + // the signed validator phase must not be less than the number of pages of a + // submission. + assert!( + T::SignedValidationPhase::get() <= T::Pages::get().into(), + "signed validaton phase ({:?}) should not be less than the number of pages per submission ({:?})", + T::SignedValidationPhase::get(), + T::Pages::get(), + ); + } + } + + #[pallet::pallet] + pub struct Pallet(PhantomData); +} + +/// Wrapper struct for working with snapshots. +/// +/// It manages the following storage items: +/// +/// - [`PagedVoterSnapshot`]: Paginated map of voters. +/// - [`PagedTargetSnapshot`]: Paginated map of targets. +/// +/// To ensure correctness and data consistency, all the reads and writes to storage items related +/// to the snapshot and "wrapped" by this struct must be performed through the methods exposed by +/// the implementation of [`Snapshot`]. +pub(crate) struct Snapshot(sp_std::marker::PhantomData); +impl Snapshot { + /// Returns the targets snapshot. + /// + /// TODO(gpestana): consider paginating targets (update: a lot of shenenigans on the assignments + /// converstion and target/voter index. Hard to ensure that no more than 1 snapshot page is + /// fetched when both voter and target snapshots are paged.) + fn targets() -> Option> { + PagedTargetSnapshot::::get(Pallet::::lsp()) + } + + /// Sets a page of targets in the snapshot's storage. + fn set_targets(targets: BoundedVec) { + PagedTargetSnapshot::::insert(Pallet::::lsp(), targets); + } + + /// Returns whether the target snapshot exists in storage. + fn targets_snapshot_exists() -> bool { + !PagedTargetSnapshot::::iter_keys().count().is_zero() + } + + /// Return the number of desired targets, which is defined by [`T::DataProvider`]. + fn desired_targets() -> Option { + match T::DataProvider::desired_targets() { + Ok(desired) => Some(desired), + Err(err) => { + defensive!( + "error fetching the desired targets from the election data provider {}", + err + ); + None + }, + } + } + + /// Returns the voters of a specific `page` index in the current snapshot. + fn voters(page: PageIndex) -> Option> { + PagedVoterSnapshot::::get(page) + } + + /// Sets a single page of voters in the snapshot's storage. + fn set_voters( + page: PageIndex, + voters: BoundedVec, T::VoterSnapshotPerBlock>, + ) { + PagedVoterSnapshot::::insert(page, voters); + } + + /// Clears all data related to a snapshot. + /// + /// At the end of a round, all the snapshot related data must be cleared and the election phase + /// has transitioned to `Phase::Off`. + fn kill() { + let _ = PagedVoterSnapshot::::clear(u32::MAX, None); + let _ = PagedTargetSnapshot::::clear(u32::MAX, None); + + debug_assert_eq!(>::get(), Phase::Off); + } + + #[allow(dead_code)] + #[cfg(any(test, debug_assertions))] + pub(crate) fn ensure() -> Result<(), &'static str> { + let pages = T::Pages::get(); + ensure!(pages > 0, "number pages must be higer than 0."); + + // target snapshot exists (one page only); + ensure!(Self::targets().is_some(), "target snapshot does not exist."); + + // ensure that snapshot pages exist as expected. + for page in (crate::Pallet::::lsp()..=crate::Pallet::::msp()).rev() { + ensure!( + Self::voters(page).is_some(), + "at least one page of the snapshot does not exist" + ); + } + + Ok(()) + } +} + +impl Pallet { + /// Return the current election phase. + pub fn current_phase() -> Phase> { + >::get() + } + + /// Return the current election round. + pub fn current_round() -> u32 { + >::get() + } + + /// Phase transition helper. + pub(crate) fn phase_transition(to: Phase>) { + Self::deposit_event(Event::PhaseTransitioned { + from: >::get(), + to, + round: >::get(), + }); + >::put(to); + } + + /// Return the most significant page of the snapshot. + /// + /// Based on the contract with `ElectionDataProvider`, tis is the first page to be filled. + pub fn msp() -> PageIndex { + T::Pages::get().checked_sub(1).defensive_unwrap_or_default() + } + + /// Return the least significant page of the snapshot. + /// + /// Based on the contract with `ElectionDataProvider`, tis is the last page to be filled. + pub fn lsp() -> PageIndex { + Zero::zero() + } + + /// Creates and stores the target snapshot. + /// + /// Note: currently, the pallet uses single page target page only. + fn create_targets_snapshot() -> Result> { + let stored_count = Self::create_targets_snapshot_inner(T::TargetSnapshotPerBlock::get())?; + log!(info, "created target snapshot with {} targets.", stored_count); + + Ok(stored_count) + } + + fn create_targets_snapshot_inner(targets_per_page: u32) -> Result> { + // set target count bound as the max number of targets per block. + let bounds = ElectionBoundsBuilder::default() + .targets_count(targets_per_page.into()) + .build() + .targets; + + let targets: BoundedVec<_, T::TargetSnapshotPerBlock> = + T::DataProvider::electable_targets(bounds, Zero::zero()) + .and_then(|t| { + t.try_into().map_err(|e| { + log!(error, "too many targets? err: {:?}", e); + "too many targets returned by the data provider." + }) + }) + .map_err(|e| { + log!(info, "error fetching electable targets from data provider: {:?}", e); + ElectionError::::DataProvider + })?; + + let count = targets.len() as u32; + Snapshot::::set_targets(targets); + + Ok(count) + } + + /// Creates and store a single page of the voter snapshot. + fn create_voters_snapshot(remaining_pages: u32) -> Result> { + ensure!(remaining_pages < T::Pages::get(), ElectionError::::RequestedPageExceeded); + + let paged_voters_count = + Self::create_voters_snapshot_inner(remaining_pages, T::VoterSnapshotPerBlock::get())?; + log!(info, "created voter snapshot with {} voters.", paged_voters_count); + + Ok(paged_voters_count) + } + + fn create_voters_snapshot_inner( + remaining_pages: u32, + voters_per_page: u32, + ) -> Result> { + // set voter count bound as the max number of voters per page. + let bounds = ElectionBoundsBuilder::default() + .voters_count(voters_per_page.into()) + .build() + .voters; + + let paged_voters: BoundedVec<_, T::VoterSnapshotPerBlock> = + T::DataProvider::electing_voters(bounds, remaining_pages) + .and_then(|v| { + v.try_into().map_err(|_| "too many voters returned by the data provider") + }) + .map_err(|_| ElectionError::::DataProvider)?; + + let count = paged_voters.len() as u32; + Snapshot::::set_voters(remaining_pages, paged_voters); + + Ok(count) + } + + /// Tries to progress the snapshot. + /// + /// The first (and only) target page is fetched from the [`DataProvider`] at the same block when + /// the msp of the voter snaphot. + fn try_progress_snapshot(remaining_pages: PageIndex) -> Weight { + let _ = ::set_lock(); + + debug_assert!( + CurrentPhase::::get().is_snapshot() || + !Snapshot::::targets_snapshot_exists() && + remaining_pages == T::Pages::get() + 1, + ); + + if !Snapshot::::targets_snapshot_exists() { + // first block for single target snapshot. + match Self::create_targets_snapshot() { + Ok(target_count) => { + log!(info, "target snapshot created with {} targets", target_count); + Self::phase_transition(Phase::Snapshot(remaining_pages.saturating_sub(1))); + T::WeightInfo::create_targets_snapshot_paged(T::TargetSnapshotPerBlock::get()) + }, + Err(err) => { + log!(error, "error preparing targets snapshot: {:?}", err); + // TODO: T::WeightInfo::snapshot_error(); + Weight::default() + }, + } + } else { + // progress voter snapshot. + match Self::create_voters_snapshot(remaining_pages) { + Ok(voter_count) => { + log!( + info, + "voter snapshot progressed: page {} with {} voters", + remaining_pages, + voter_count, + ); + Self::phase_transition(Phase::Snapshot(remaining_pages)); + T::WeightInfo::create_voters_snapshot_paged(T::VoterSnapshotPerBlock::get()) + }, + Err(err) => { + log!(error, "error preparing voter snapshot: {:?}", err); + // TODO: T::WeightInfo::snapshot_error(); + Weight::default() + }, + } + } + } + + pub(crate) fn start_signed_phase() -> Weight { + // done with the snapshot, release the data provider lock. + ::unlock(); + Self::phase_transition(Phase::Signed); + + T::WeightInfo::on_initialize_start_signed() + } + + pub(crate) fn do_export_phase(now: BlockNumberFor, started_at: BlockNumberFor) -> Weight { + if now > started_at + T::ExportPhaseLimit::get() { + log!( + error, + "phase `Export` has been open for too long ({:?} blocks). election round failed.", + T::ExportPhaseLimit::get(), + ); + + match ElectionFailure::::get() { + ElectionFailureStrategy::Restart => Self::reset_round(), + ElectionFailureStrategy::Emergency => Self::phase_transition(Phase::Emergency), + } + } + + T::WeightInfo::on_initialize_start_export() + } + + /// Performs all tasks required after a successful election: + /// + /// 1. Increment round. + /// 2. Change phase to [`Phase::Off`]. + /// 3. Clear all snapshot data. + fn rotate_round() { + >::mutate(|r| r.defensive_saturating_accrue(1)); + Self::phase_transition(Phase::Off); + + Snapshot::::kill(); + ::kill(); + } + + /// Performs all tasks required after an unsuccessful election: + /// + /// 1. Change phase to [`Phase::Off`]. + /// 2. Clear all snapshot data. + fn reset_round() { + Self::phase_transition(Phase::Off); + Snapshot::::kill(); + + ::kill(); + } +} + +impl ElectionProvider for Pallet { + type AccountId = T::AccountId; + type BlockNumber = BlockNumberFor; + type Error = ElectionError; + type MaxWinnersPerPage = ::MaxWinnersPerPage; + type MaxBackersPerWinner = ::MaxBackersPerWinner; + type Pages = T::Pages; + type DataProvider = T::DataProvider; + + /// Important note: we do exect the caller of `elect` to reach page 0. + fn elect(remaining: PageIndex) -> Result, Self::Error> { + T::Verifier::get_queued_solution(remaining) + .ok_or(ElectionError::::SupportPageNotAvailable(remaining)) + .or_else(|err| { + log!( + error, + "elect(): (page {:?}) election provider failed due to {:?}, trying fallback.", + remaining, + err + ); + T::Fallback::elect(remaining).map_err(|fe| ElectionError::::Fallback(fe)) + }) + .map(|supports| { + if remaining.is_zero() { + log!(info, "elect(): provided the last supports page, rotating round."); + Self::rotate_round(); + } else { + // Phase::Export is on while the election is calling all pages of `elect`. + if !Self::current_phase().is_export() { + let now = >::block_number(); + Self::phase_transition(Phase::Export(now)); + } + } + supports.into() + }) + .map_err(|err| { + log!(error, "elect(): fetching election page {} and fallback failed.", remaining); + + match ElectionFailure::::get() { + // force emergency phase for testing. + ElectionFailureStrategy::Restart => Self::reset_round(), + ElectionFailureStrategy::Emergency => Self::phase_transition(Phase::Emergency), + } + err + }) + } +} + +#[cfg(test)] +mod phase_transition { + use super::*; + use crate::mock::*; + + use frame_support::assert_ok; + + #[test] + fn single_page() { + // ---------- ---------- -------------- ----------- + // | | | | | + // Snapshot Signed SignedValidation Unsigned elect() + let (mut ext, _) = ExtBuilder::default() + .pages(1) + .signed_phase(3) + .validate_signed_phase(1) + .lookahead(0) + .build_offchainify(1); + + ext.execute_with(|| { + assert_eq!(System::block_number(), 0); + assert_eq!(Pages::get(), 1); + assert_eq!(>::get(), 0); + assert_eq!(>::get(), Phase::Off); + + let next_election = <::DataProvider as ElectionDataProvider>::next_election_prediction( + System::block_number() + ); + assert_eq!(next_election, 30); + + // representing the blocknumber when the phase transition happens. + let export_deadline = next_election - (ExportPhaseLimit::get() + Lookhaead::get()); + let expected_unsigned = export_deadline - UnsignedPhase::get(); + let expected_validate = expected_unsigned - SignedValidationPhase::get(); + let expected_signed = expected_validate - SignedPhase::get(); + let expected_snapshot = expected_signed - Pages::get() as u64; + + // tests transition phase boundaries. + roll_to(expected_snapshot); + assert_eq!(>::get(), Phase::Snapshot(Pages::get() - 1)); + + roll_to(expected_signed); + assert_eq!(>::get(), Phase::Signed); + + roll_to(expected_validate); + let start_validate = System::block_number(); + assert_eq!(>::get(), Phase::SignedValidation(start_validate)); + + roll_to(expected_unsigned); + let start_unsigned = System::block_number(); + assert_eq!(>::get(), Phase::Unsigned(start_unsigned)); + + roll_to(next_election + 1); + assert_eq!(>::get(), Phase::Unsigned(start_unsigned)); + + // unsigned phase until elect() is called. + roll_to(next_election + 3); + assert_eq!(>::get(), Phase::Unsigned(start_unsigned)); + + assert_ok!(MultiPhase::elect(0)); + + // election done, go to off phase. + assert_eq!(>::get(), Phase::Off); + }) + } + + #[test] + fn multi_page() { + let (mut ext, _) = ExtBuilder::default() + .pages(2) + .signed_phase(3) + .validate_signed_phase(1) + .lookahead(0) + .build_offchainify(1); + + ext.execute_with(|| { + assert_eq!(System::block_number(), 0); + assert_eq!(Pages::get(), 2); + assert_eq!(>::get(), 0); + assert_eq!(>::get(), Phase::Off); + + let next_election = <::DataProvider as ElectionDataProvider>::next_election_prediction( + System::block_number() + ); + assert_eq!(next_election, 30); + + // representing the blocknumber when the phase transition happens. + let export_deadline = next_election - (ExportPhaseLimit::get() + Lookhaead::get()); + let expected_unsigned = export_deadline - UnsignedPhase::get(); + let expected_validate = expected_unsigned - SignedValidationPhase::get(); + let expected_signed = expected_validate - SignedPhase::get(); + let expected_snapshot = expected_signed - Pages::get() as u64; + + // two blocks for snapshot. + roll_to(expected_snapshot); + assert_eq!(>::get(), Phase::Snapshot(Pages::get() - 1)); + + roll_to(expected_snapshot + 1); + assert_eq!(>::get(), Phase::Snapshot(0)); + + roll_to(expected_signed); + assert_eq!(>::get(), Phase::Signed); + + roll_to(expected_signed + 1); + assert_eq!(>::get(), Phase::Signed); + + // two blocks for validate signed. + roll_to(expected_validate); + let start_validate = System::block_number(); + assert_eq!(>::get(), Phase::SignedValidation(start_validate)); + + // now in unsigned until elect() is called. + roll_to(expected_validate + 2); + let start_unsigned = System::block_number(); + assert_eq!(>::get(), Phase::Unsigned(start_unsigned - 1)); + + }) + } + + #[test] + fn emergency_phase_works() { + let (mut ext, _) = ExtBuilder::default().build_offchainify(1); + ext.execute_with(|| { + let next_election = <::DataProvider as ElectionDataProvider>::next_election_prediction( + System::block_number() + ); + + // if election fails, enters in emergency phase. + ElectionFailure::::set(ElectionFailureStrategy::Emergency); + + compute_snapshot_checked(); + roll_to(next_election); + + // election will fail due to inexistent solution. + assert!(MultiPhase::elect(Pallet::::msp()).is_err()); + // thus entering in emergency phase. + assert_eq!(>::get(), Phase::Emergency); + }) + } + + #[test] + fn restart_after_elect_fails_works() { + let (mut ext, _) = ExtBuilder::default().build_offchainify(1); + ext.execute_with(|| { + let next_election = <::DataProvider as ElectionDataProvider>::next_election_prediction( + System::block_number() + ); + + // if election fails, restart the election round. + ElectionFailure::::set(ElectionFailureStrategy::Restart); + + compute_snapshot_checked(); + roll_to(next_election); + + // election will fail due to inexistent solution. + assert!(MultiPhase::elect(Pallet::::msp()).is_err()); + // thus restarting from Off phase. + assert_eq!(>::get(), Phase::Off); + }) + } +} + +#[cfg(test)] +mod snapshot { + use super::*; + use crate::mock::*; + + use frame_support::{assert_noop, assert_ok}; + + #[test] + fn setters_getters_work() { + ExtBuilder::default().build_and_execute(|| { + let v = BoundedVec::<_, _>::try_from(vec![]).unwrap(); + + assert!(Snapshot::::targets().is_none()); + assert!(Snapshot::::voters(0).is_none()); + assert!(Snapshot::::voters(1).is_none()); + + Snapshot::::set_targets(v.clone()); + assert!(Snapshot::::targets().is_some()); + + Snapshot::::kill(); + assert!(Snapshot::::targets().is_none()); + assert!(Snapshot::::voters(0).is_none()); + assert!(Snapshot::::voters(1).is_none()); + }) + } + + #[test] + fn targets_voters_snapshot_boundary_checks_works() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(Pages::get(), 3); + assert_eq!(MultiPhase::msp(), 2); + assert_eq!(MultiPhase::lsp(), 0); + + assert_ok!(MultiPhase::create_targets_snapshot()); + + assert_ok!(MultiPhase::create_voters_snapshot(2)); + assert_ok!(MultiPhase::create_voters_snapshot(1)); + assert_ok!(MultiPhase::create_voters_snapshot(0)); + + assert_noop!( + MultiPhase::create_voters_snapshot(3), + ElectionError::::RequestedPageExceeded + ); + assert_noop!( + MultiPhase::create_voters_snapshot(10), + ElectionError::::RequestedPageExceeded + ); + }) + } + + #[test] + fn create_targets_snapshot_works() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(MultiPhase::msp(), 2); + + let no_bounds = ElectionBoundsBuilder::default().build().targets; + let all_targets = + ::electable_targets(no_bounds, 0); + assert_eq!(all_targets.unwrap(), Targets::get()); + assert_eq!(Targets::get().len(), 8); + + // sets max targets per page to 2. + TargetSnapshotPerBlock::set(2); + + let result_and_count = MultiPhase::create_targets_snapshot(); + assert_eq!(result_and_count.unwrap(), 2); + assert_eq!(Snapshot::::targets().unwrap().to_vec(), vec![10, 20]); + + // sets max targets per page to 4. + TargetSnapshotPerBlock::set(4); + + let result_and_count = MultiPhase::create_targets_snapshot(); + assert_eq!(result_and_count.unwrap(), 4); + assert_eq!(Snapshot::::targets().unwrap().to_vec(), vec![10, 20, 30, 40]); + + Snapshot::::kill(); + + TargetSnapshotPerBlock::set(6); + + let result_and_count = MultiPhase::create_targets_snapshot(); + assert_eq!(result_and_count.unwrap(), 6); + assert_eq!(Snapshot::::targets().unwrap().to_vec(), vec![10, 20, 30, 40, 50, 60]); + + // reset storage. + Snapshot::::kill(); + }) + } + + #[test] + fn voters_snapshot_works() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(MultiPhase::msp(), 2); + + let no_bounds = ElectionBoundsBuilder::default().build().voters; + let all_voters = ::electing_voters(no_bounds, 0); + assert_eq!(all_voters.unwrap(), Voters::get()); + assert_eq!(Voters::get().len(), 16); + + // sets max voters per page to 7. + VoterSnapshotPerBlock::set(7); + + let voters_page = |page: PageIndex| { + Snapshot::::voters(page) + .unwrap() + .iter() + .map(|v| v.0) + .collect::>() + }; + + // page `msp`. + let result_and_count = MultiPhase::create_voters_snapshot(MultiPhase::msp()); + assert_eq!(result_and_count.unwrap(), 7); + assert_eq!(voters_page(MultiPhase::msp()), vec![1, 2, 3, 4, 5, 6, 7]); + + let result_and_count = MultiPhase::create_voters_snapshot(1); + assert_eq!(result_and_count.unwrap(), 7); + assert_eq!(voters_page(1), vec![8, 10, 20, 30, 40, 50, 60]); + + // page `lsp`. + let result_and_count = MultiPhase::create_voters_snapshot(MultiPhase::lsp()); + assert_eq!(result_and_count.unwrap(), 2); + assert_eq!(voters_page(MultiPhase::lsp()), vec![70, 80]); + }) + } + + #[test] + fn try_progress_snapshot_works() {} +} + +#[cfg(test)] +mod election_provider { + use super::*; + use crate::{mock::*, unsigned::miner::Miner}; + use frame_support::testing_prelude::*; + + #[test] + fn snapshot_to_supports_conversions_work() { + type VotersPerPage = ::VoterSnapshotPerBlock; + type TargetsPerPage = ::TargetSnapshotPerBlock; + type Pages = ::Pages; + + ExtBuilder::default() + .pages(2) + .snasphot_voters_page(4) + .snasphot_targets_page(4) + .desired_targets(2) + .build_and_execute(|| { + assert_eq!(MultiPhase::msp(), 1); + + let all_targets: BoundedVec = + bounded_vec![10, 20, 30, 40]; + + let all_voter_pages: BoundedVec< + BoundedVec, VotersPerPage>, + Pages, + > = bounded_vec![ + bounded_vec![ + (1, 100, bounded_vec![10, 20]), + (2, 20, bounded_vec![30]), + (3, 30, bounded_vec![10]), + (10, 10, bounded_vec![10]) + ], + bounded_vec![ + (20, 20, bounded_vec![20]), + (30, 30, bounded_vec![30]), + (40, 40, bounded_vec![40]) + ], + ]; + + Snapshot::::set_targets(all_targets.clone()); + Snapshot::::set_voters(0, all_voter_pages[0].clone()); + Snapshot::::set_voters(1, all_voter_pages[1].clone()); + + let desired_targets = Snapshot::::desired_targets().unwrap(); + let (results, _) = Miner::::mine_paged_solution_with_snapshot( + &all_voter_pages, + &all_targets, + Pages::get(), + current_round(), + desired_targets, + false, + ) + .unwrap(); + + let supports_page_zero = + PalletVerifier::::feasibility_check(results.solution_pages[0].clone(), 0) + .unwrap(); + let supports_page_one = + PalletVerifier::::feasibility_check(results.solution_pages[1].clone(), 1) + .unwrap(); + + use frame_election_provider_support::{BoundedSupports, TryIntoBoundedSupports}; + use sp_npos_elections::{Support, Supports}; + + let s0: Supports = vec![ + (10, Support { total: 90, voters: vec![(3, 30), (10, 10), (1, 50)] }), + (20, Support { total: 50, voters: vec![(1, 50)] }), + ]; + let bs0: BoundedSupports<_, _, _> = s0.try_into_bounded_supports().unwrap(); + + let s1: Supports = + vec![(20, Support { total: 20, voters: vec![(20, 20)] })]; + let bs1: BoundedSupports<_, _, _> = s1.try_into_bounded_supports().unwrap(); + + assert_eq!(supports_page_zero, bs0); + assert_eq!(supports_page_one, bs1); + }) + } +} diff --git a/substrate/frame/election-provider-multi-block/src/mock/mod.rs b/substrate/frame/election-provider-multi-block/src/mock/mod.rs new file mode 100644 index 0000000000000..e094bac3f6919 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/mock/mod.rs @@ -0,0 +1,594 @@ +// 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. + +#![allow(unused)] + +mod staking; + +use frame_election_provider_support::{bounds::ElectionBounds, onchain, SequentialPhragmen}; +use sp_npos_elections::ElectionScore; +pub use staking::*; + +use crate::{ + self as epm, + signed::{self as signed_pallet}, + unsigned::{ + self as unsigned_pallet, + miner::{self, Miner, MinerError, OffchainWorkerMiner}, + }, + verifier::{self as verifier_pallet}, + Config, *, +}; +use frame_support::{derive_impl, pallet_prelude::*, parameter_types}; +use parking_lot::RwLock; +use sp_runtime::{ + offchain::{ + testing::{PoolState, TestOffchainExt, TestTransactionPoolExt}, + OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, + }, + BuildStorage, Perbill, +}; +use std::sync::Arc; + +frame_support::construct_runtime!( + pub struct Runtime { + System: frame_system, + Balances: pallet_balances, + MultiPhase: epm, + VerifierPallet: verifier_pallet, + SignedPallet: signed_pallet, + UnsignedPallet: unsigned_pallet, + } +); + +pub type AccountId = u64; +pub type Balance = u128; +pub type BlockNumber = u64; +pub type VoterIndex = u32; +pub type TargetIndex = u16; +pub type T = Runtime; +pub type Block = frame_system::mocking::MockBlock; +pub(crate) type Solver = SequentialPhragmen; + +frame_election_provider_support::generate_solution_type!( + #[compact] + pub struct TestNposSolution::< + VoterIndex = VoterIndex, + TargetIndex = TargetIndex, + Accuracy = sp_runtime::PerU16, + MaxVoters = frame_support::traits::ConstU32::<2_000> + >(16) +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type Block = Block; + type AccountData = pallet_balances::AccountData; +} + +parameter_types! { + pub const ExistentialDeposit: Balance = 1; +} + +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type DoneSlashHandler = (); + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = (); +} + +parameter_types! { + pub static SignedPhase: BlockNumber = 3; + pub static UnsignedPhase: BlockNumber = 5; + pub static SignedValidationPhase: BlockNumber = Pages::get().into(); + pub static Lookhaead: BlockNumber = 0; + pub static VoterSnapshotPerBlock: VoterIndex = 5; + pub static TargetSnapshotPerBlock: TargetIndex = 8; + pub static Pages: PageIndex = 3; + pub static ExportPhaseLimit: BlockNumber = (Pages::get() * 2u32).into(); +} + +pub struct EPMBenchmarkingConfigs; +impl BenchmarkingConfig for EPMBenchmarkingConfigs { + const VOTERS: u32 = 100; + const TARGETS: u32 = 50; + const VOTERS_PER_PAGE: [u32; 2] = [1, 5]; + const TARGETS_PER_PAGE: [u32; 2] = [1, 8]; +} + +impl Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type SignedPhase = SignedPhase; + type UnsignedPhase = UnsignedPhase; + type SignedValidationPhase = SignedValidationPhase; + type Lookhaead = Lookhaead; + type VoterSnapshotPerBlock = VoterSnapshotPerBlock; + type TargetSnapshotPerBlock = TargetSnapshotPerBlock; + type MaxBackersPerWinner = MaxBackersPerWinner; + type MaxWinnersPerPage = MaxWinnersPerPage; + type Pages = Pages; + type ExportPhaseLimit = ExportPhaseLimit; + type DataProvider = MockStaking; + type MinerConfig = Self; + type Fallback = MockFallback; + type Verifier = VerifierPallet; + type BenchmarkingConfig = EPMBenchmarkingConfigs; + type WeightInfo = (); +} + +parameter_types! { + pub static SolutionImprovementThreshold: Perbill = Perbill::zero(); + pub static MaxWinnersPerPage: u32 = 100; + pub static MaxBackersPerWinner: u32 = 1000; +} + +impl crate::verifier::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ForceOrigin = frame_system::EnsureRoot; + type SolutionImprovementThreshold = SolutionImprovementThreshold; + type SolutionDataProvider = SignedPallet; + type WeightInfo = (); +} + +parameter_types! { + pub static DepositBase: Balance = 10; + pub static DepositPerPage: Balance = 1; + pub static Reward: Balance = 10; + pub static MaxSubmissions: u32 = 5; +} + +impl crate::signed::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type EstimateCallFee = ConstU32<8>; + type OnSlash = (); // burn + type DepositBase = ConstDepositBase; + type DepositPerPage = DepositPerPage; + type Reward = Reward; + type MaxSubmissions = MaxSubmissions; + type RuntimeHoldReason = RuntimeHoldReason; + type WeightInfo = (); +} + +parameter_types! { + pub OffchainRepeatInterval: BlockNumber = 10; + pub MinerTxPriority: u64 = 0; + pub MinerSolutionMaxLength: u32 = 10; + pub MinerSolutionMaxWeight: Weight = Default::default(); +} + +impl crate::unsigned::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type OffchainRepeatInterval = OffchainRepeatInterval; + type MinerTxPriority = MinerTxPriority; + type MaxLength = MinerSolutionMaxLength; + type MaxWeight = MinerSolutionMaxWeight; + type WeightInfo = (); +} + +impl miner::Config for Runtime { + type AccountId = AccountId; + type Solution = TestNposSolution; + type Solver = Solver; + type Pages = Pages; + type MaxVotesPerVoter = MaxVotesPerVoter; + type MaxWinnersPerPage = MaxWinnersPerPage; + type MaxBackersPerWinner = MaxBackersPerWinner; + type VoterSnapshotPerBlock = VoterSnapshotPerBlock; + type TargetSnapshotPerBlock = TargetSnapshotPerBlock; + type MaxWeight = MinerSolutionMaxWeight; + type MaxLength = MinerSolutionMaxLength; +} + +pub type Extrinsic = sp_runtime::testing::TestXt; + +impl frame_system::offchain::SendTransactionTypes for Runtime +where + RuntimeCall: From, +{ + type OverarchingCall = RuntimeCall; + type Extrinsic = Extrinsic; +} + +pub struct ConstDepositBase; +impl sp_runtime::traits::Convert for ConstDepositBase { + fn convert(_a: usize) -> Balance { + DepositBase::get() + } +} + +parameter_types! { + pub static OnChainElectionBounds: ElectionBounds = ElectionBoundsBuilder::default().build(); + pub static MaxVotesPerVoter: u32 = ::LIMIT as u32; + pub static FallbackEnabled: bool = true; +} + +impl onchain::Config for Runtime { + type System = Runtime; + type Solver = Solver; + type MaxWinnersPerPage = MaxWinnersPerPage; + type MaxBackersPerWinner = MaxBackersPerWinner; + type Bounds = OnChainElectionBounds; + type DataProvider = MockStaking; + type WeightInfo = (); +} + +pub struct MockFallback; +impl ElectionProvider for MockFallback { + type AccountId = AccountId; + type BlockNumber = BlockNumberFor; + type Error = &'static str; + type DataProvider = MockStaking; + type Pages = ConstU32<1>; + type MaxWinnersPerPage = MaxWinnersPerPage; + type MaxBackersPerWinner = MaxBackersPerWinner; + + fn elect(remaining: PageIndex) -> Result, Self::Error> { + if FallbackEnabled::get() { + onchain::OnChainExecution::::elect(remaining) + .map_err(|_| "fallback election failed") + } else { + Err("fallback election failed (forced in mock)") + } + } +} + +#[derive(Default)] +pub struct ExtBuilder { + with_verifier: bool, +} + +// TODO(gpestana): separate ext builder into separate builders for each pallet. +impl ExtBuilder { + pub(crate) fn pages(self, pages: u32) -> Self { + Pages::set(pages); + self + } + + pub(crate) fn snasphot_voters_page(self, voters: VoterIndex) -> Self { + VoterSnapshotPerBlock::set(voters); + self + } + + pub(crate) fn snasphot_targets_page(self, targets: TargetIndex) -> Self { + TargetSnapshotPerBlock::set(targets); + self + } + + pub(crate) fn signed_phase(self, blocks: BlockNumber) -> Self { + SignedPhase::set(blocks); + self + } + + pub(crate) fn validate_signed_phase(self, blocks: BlockNumber) -> Self { + SignedValidationPhase::set(blocks); + self + } + + pub(crate) fn unsigned_phase(self, blocks: BlockNumber) -> Self { + UnsignedPhase::set(blocks); + self + } + + pub(crate) fn lookahead(self, blocks: BlockNumber) -> Self { + Lookhaead::set(blocks); + self + } + + pub(crate) fn max_winners_per_page(self, max: u32) -> Self { + MaxWinnersPerPage::set(max); + self + } + + pub(crate) fn max_backers_per_winner(self, max: u32) -> Self { + MaxBackersPerWinner::set(max); + self + } + + pub(crate) fn desired_targets(self, desired: u32) -> Self { + DesiredTargets::set(desired); + self + } + + pub(crate) fn signed_max_submissions(self, max: u32) -> Self { + MaxSubmissions::set(max); + self + } + + pub(crate) fn verifier() -> Self { + ExtBuilder { with_verifier: true } + } + + pub(crate) fn build(self) -> sp_io::TestExternalities { + sp_tracing::try_init_simple(); + + let mut storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); + let _ = pallet_balances::GenesisConfig:: { + balances: vec![ + (10, 100_000), + (20, 100_000), + (30, 100_000), + (40, 100_000), + (50, 100_000), + (60, 100_000), + (70, 100_000), + (80, 100_000), + (90, 100_000), + (91, 100), + (92, 100), + (93, 100), + (94, 100), + (95, 100), + (96, 100), + (97, 100), + (99, 100), + (999, 100), + (9999, 100), + ], + } + .assimilate_storage(&mut storage); + + if self.with_verifier { + // nothing special for now + } + + sp_io::TestExternalities::from(storage) + } + + pub fn build_offchainify( + self, + iters: u32, + ) -> (sp_io::TestExternalities, Arc>) { + let mut ext = self.build(); + let (offchain, offchain_state) = TestOffchainExt::new(); + let (pool, pool_state) = TestTransactionPoolExt::new(); + + let mut seed = [0_u8; 32]; + seed[0..4].copy_from_slice(&iters.to_le_bytes()); + offchain_state.write().seed = seed; + + ext.register_extension(OffchainDbExt::new(offchain.clone())); + ext.register_extension(OffchainWorkerExt::new(offchain)); + ext.register_extension(TransactionPoolExt::new(pool)); + + (ext, pool_state) + } + + pub(crate) fn build_and_execute(self, test: impl FnOnce() -> ()) { + let mut ext = self.build(); + ext.execute_with(test); + + #[cfg(feature = "try-runtime")] + ext.execute_with(|| { + //MultiPhase::do_try_state().unwrap(); + // etc.. + + let _ = VerifierPallet::do_try_state() + .map_err(|err| println!(" 🕵️‍♂️ Verifier `try_state` failure: {:?}", err)); + }); + } +} + +pub(crate) fn compute_snapshot_checked() { + let msp = crate::Pallet::::msp(); + + for page in (0..=Pages::get()).rev() { + CurrentPhase::::set(Phase::Snapshot(page)); + crate::Pallet::::try_progress_snapshot(page); + + assert!(Snapshot::::targets_snapshot_exists()); + + if page <= msp { + assert!(Snapshot::::voters(page).is_some()); + } + } +} + +pub(crate) fn mine_and_verify_all() -> Result< + Vec< + frame_election_provider_support::BoundedSupports< + AccountId, + MaxWinnersPerPage, + MaxBackersPerWinner, + >, + >, + &'static str, +> { + let msp = crate::Pallet::::msp(); + let mut paged_supports = vec![]; + + for page in (0..=msp).rev() { + let (_, score, solution) = + OffchainWorkerMiner::::mine(page).map_err(|e| "error mining")?; + + let supports = + ::verify_synchronous(solution, score, page) + .map_err(|_| "error verifying paged solution")?; + + paged_supports.push(supports); + } + + Ok(paged_supports) +} + +pub(crate) fn roll_to(n: BlockNumber) { + for bn in (System::block_number()) + 1..=n { + System::set_block_number(bn); + + MultiPhase::on_initialize(bn); + VerifierPallet::on_initialize(bn); + SignedPallet::on_initialize(bn); + UnsignedPallet::on_initialize(bn); + UnsignedPallet::offchain_worker(bn); + + // TODO: add try-checks for all pallets here too, as we progress the blocks. + log!( + info, + "Block: {}, Phase: {:?}, Round: {:?}, Election at {:?}", + bn, + >::get(), + >::get(), + election_prediction() + ); + } +} + +// Fast forward until a given election phase. +pub fn roll_to_phase(phase: Phase) { + while MultiPhase::current_phase() != phase { + roll_to(System::block_number() + 1); + } +} + +pub fn roll_one_with_ocw(maybe_pool: Option>>) { + use sp_runtime::traits::Dispatchable; + let bn = System::block_number() + 1; + // if there's anything in the submission pool, submit it. + if let Some(ref pool) = maybe_pool { + pool.read() + .transactions + .clone() + .into_iter() + .map(|uxt| ::decode(&mut &*uxt).unwrap()) + .for_each(|xt| { + xt.call.dispatch(frame_system::RawOrigin::None.into()).unwrap(); + }); + pool.try_write().unwrap().transactions.clear(); + } + + roll_to(bn); +} + +pub fn roll_to_phase_with_ocw( + phase: Phase, + maybe_pool: Option>>, +) { + while MultiPhase::current_phase() != phase { + roll_one_with_ocw(maybe_pool.clone()); + } +} + +pub fn roll_to_with_ocw(n: BlockNumber, maybe_pool: Option>>) { + let now = System::block_number(); + for _i in now + 1..=n { + roll_one_with_ocw(maybe_pool.clone()); + } +} + +pub fn election_prediction() -> BlockNumber { + <::DataProvider as ElectionDataProvider>::next_election_prediction( + System::block_number(), + ) +} + +pub fn current_phase() -> Phase { + MultiPhase::current_phase() +} + +pub fn current_round() -> u32 { + Pallet::::current_round() +} + +pub fn call_elect() -> Result<(), crate::ElectionError> { + for p in (0..=Pallet::::msp()).rev() { + ::elect(p)?; + } + Ok(()) +} + +pub fn assert_snapshots() -> Result<(), &'static str> { + Snapshot::::ensure() +} + +pub fn clear_snapshot() { + let _ = crate::PagedVoterSnapshot::::clear(u32::MAX, None); + let _ = crate::PagedTargetSnapshot::::clear(u32::MAX, None); +} + +pub fn balances(who: AccountId) -> (Balance, Balance) { + (Balances::free_balance(who), Balances::reserved_balance(who)) +} + +pub fn mine_full(pages: PageIndex) -> Result, MinerError> { + let (targets, voters) = + OffchainWorkerMiner::::fetch_snapshots().map_err(|_| MinerError::DataProvider)?; + + let reduce = false; + let round = crate::Pallet::::current_round(); + let desired_targets = ::desired_targets() + .map_err(|_| MinerError::DataProvider)?; + + Miner::::mine_paged_solution_with_snapshot( + &targets, + &voters, + Pages::get(), + round, + desired_targets, + reduce, + ) + .map(|(s, _)| s) +} + +pub fn mine( + page: PageIndex, +) -> Result<(ElectionScore, SolutionOf<::MinerConfig>), ()> { + let (_, partial_score, partial_solution) = + OffchainWorkerMiner::::mine(page).map_err(|_| ())?; + + Ok((partial_score, partial_solution)) +} + +// Pallet events filters. + +pub(crate) fn unsigned_events() -> Vec> { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map( + |e| if let RuntimeEvent::UnsignedPallet(inner) = e { Some(inner) } else { None }, + ) + .collect() +} + +pub(crate) fn signed_events() -> Vec> { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let RuntimeEvent::SignedPallet(inner) = e { Some(inner) } else { None }) + .collect() +} + +// TODO fix or use macro. +pub(crate) fn filter_events( + types: Vec, +) -> Vec { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if types.contains(&e) { Some(e) } else { None }) + .collect() +} diff --git a/substrate/frame/election-provider-multi-block/src/mock/staking.rs b/substrate/frame/election-provider-multi-block/src/mock/staking.rs new file mode 100644 index 0000000000000..52fc297700420 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/mock/staking.rs @@ -0,0 +1,235 @@ +// 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 sp_runtime::bounded_vec; + +use frame_election_provider_support::{ + bounds::CountBound, data_provider, DataProviderBounds, ElectionDataProvider, + LockableElectionDataProvider, PageIndex, VoterOf as VoterOfProvider, +}; + +use super::{AccountId, BlockNumber, MaxVotesPerVoter, T}; + +// alias for a voter of EPM-MB. +type VoterOf = frame_election_provider_support::VoterOf<::DataProvider>; + +frame_support::parameter_types! { + pub static Targets: Vec = vec![10, 20, 30, 40, 50, 60, 70, 80]; + pub static Voters: Vec> = vec![ + (1, 10, bounded_vec![10, 20]), + (2, 10, bounded_vec![30, 40]), + (3, 10, bounded_vec![40]), + (4, 10, bounded_vec![10, 20, 40]), + (5, 10, bounded_vec![10, 30, 40]), + (6, 10, bounded_vec![20, 30, 40]), + (7, 10, bounded_vec![20, 30]), + (8, 10, bounded_vec![10]), + (10, 10, bounded_vec![10]), + (20, 20, bounded_vec![20]), + (30, 30, bounded_vec![30]), + (40, 40, bounded_vec![40]), + (50, 50, bounded_vec![50]), + (60, 60, bounded_vec![60]), + (70, 70, bounded_vec![70]), + (80, 80, bounded_vec![80]), + ]; + pub static EpochLength: u64 = 30; + pub static DesiredTargets: u32 = 5; + + pub static LastIteratedTargetIndex: Option = None; + pub static LastIteratedVoterIndex: Option = None; + + pub static ElectionDataLock: Option<()> = None; // not locker. +} + +pub struct MockStaking; +impl ElectionDataProvider for MockStaking { + type AccountId = AccountId; + type BlockNumber = BlockNumber; + type MaxVotesPerVoter = MaxVotesPerVoter; + + fn electable_targets( + bounds: DataProviderBounds, + remaining: PageIndex, + ) -> data_provider::Result> { + let mut targets = Targets::get(); + + // drop previously processed targets. + if let Some(last_index) = LastIteratedTargetIndex::get() { + targets = targets.iter().skip(last_index).cloned().collect::>(); + } + + // take as many targets as requested. + if let Some(max_len) = bounds.count { + targets.truncate(max_len.0 as usize); + } + + assert!(!bounds.exhausted(None, CountBound(targets.len() as u32).into(),)); + + // update the last iterated target index accordingly. + if remaining > 0 { + if let Some(last) = targets.last().cloned() { + LastIteratedTargetIndex::set(Some( + Targets::get().iter().position(|v| v == &last).map(|i| i + 1).unwrap(), + )); + } else { + // no more targets to process, do nothing. + } + } else { + LastIteratedTargetIndex::set(None); + } + + Ok(targets) + } + + /// Note: electing voters bounds are only constrained by the count of voters. + fn electing_voters( + bounds: DataProviderBounds, + remaining: PageIndex, + ) -> data_provider::Result>> { + let mut voters = Voters::get(); + + // skip the already iterated voters in previous pages. + if let Some(index) = LastIteratedVoterIndex::get() { + voters = voters.iter().skip(index).cloned().collect::>(); + } + + // take as many voters as permitted by the bounds. + if let Some(max_len) = bounds.count { + voters.truncate(max_len.0 as usize); + } + + assert!(!bounds.exhausted(None, CountBound(voters.len() as u32).into())); + + // update the last iterater voter index accordingly. + if remaining > 0 { + if let Some(last) = voters.last().cloned() { + LastIteratedVoterIndex::set(Some( + Voters::get().iter().position(|v| v == &last).map(|i| i + 1).unwrap(), + )); + } else { + // no more voters to process, do nothing. + } + } else { + LastIteratedVoterIndex::set(None); + } + + Ok(voters) + } + + fn desired_targets() -> data_provider::Result { + Ok(DesiredTargets::get()) + } + + fn next_election_prediction(now: Self::BlockNumber) -> Self::BlockNumber { + now + EpochLength::get() - now % EpochLength::get() + } +} + +impl LockableElectionDataProvider for MockStaking { + fn set_lock() -> data_provider::Result<()> { + ElectionDataLock::get() + .ok_or("lock is already set") + .map(|_| ElectionDataLock::set(Some(()))) + } + + fn unlock() { + ElectionDataLock::set(None); + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::mock::{ExtBuilder, Pages}; + + #[test] + fn multi_page_targets() { + ExtBuilder::default().build_and_execute(|| { + // no bounds. + let targets = + ::electable_targets(Default::default(), 0); + assert_eq!(targets.unwrap().len(), 8); + assert_eq!(LastIteratedTargetIndex::get(), None); + + // 2 targets per page. + let bounds: DataProviderBounds = + DataProviderBounds { count: Some(2.into()), size: None }; + + let mut all_targets = vec![]; + for page in (0..(Pages::get())).rev() { + let mut targets = + ::electable_targets(bounds, page).unwrap(); + assert_eq!(targets.len(), bounds.count.unwrap().0 as usize); + + all_targets.append(&mut targets); + } + + assert_eq!(all_targets, vec![10, 20, 30, 40, 50, 60]); + assert_eq!(LastIteratedTargetIndex::get(), None); + }) + } + + #[test] + fn multi_page_voters() { + ExtBuilder::default().build_and_execute(|| { + // no bounds. + let voters = + ::electing_voters(Default::default(), 0); + assert_eq!(voters.unwrap().len(), 16); + assert_eq!(LastIteratedVoterIndex::get(), None); + + // 2 voters per page. + let bounds: DataProviderBounds = + DataProviderBounds { count: Some(2.into()), size: None }; + + let mut all_voters = vec![]; + for page in (0..(Pages::get())).rev() { + let mut voters = + ::electing_voters(bounds, page).unwrap(); + + assert_eq!(voters.len(), bounds.count.unwrap().0 as usize); + + all_voters.append(&mut voters); + } + + let mut expected_voters = Voters::get(); + expected_voters.truncate(6); + + assert_eq!(all_voters, expected_voters); + assert_eq!(LastIteratedVoterIndex::get(), None); + + // bound based on the *encoded size* of the voters, per page. + let bounds: DataProviderBounds = + DataProviderBounds { count: None, size: Some(100.into()) }; + + let mut all_voters = vec![]; + for page in (0..(Pages::get())).rev() { + let mut voters = + ::electing_voters(bounds, page).unwrap(); + + all_voters.append(&mut voters); + } + + let mut expected_voters = Voters::get(); + expected_voters.truncate(all_voters.len()); + + assert_eq!(all_voters, expected_voters); + assert_eq!(LastIteratedVoterIndex::get(), None); + }) + } +} diff --git a/substrate/frame/election-provider-multi-block/src/signed/benchmarking.rs b/substrate/frame/election-provider-multi-block/src/signed/benchmarking.rs new file mode 100644 index 0000000000000..9629acce2ed38 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/signed/benchmarking.rs @@ -0,0 +1,66 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Benchmarking for the Elections Multiblock Unsigned sub-pallet. + +use super::*; +use crate::{benchmarking::helpers, BenchmarkingConfig, ConfigCore, ConfigSigned, ConfigUnsigned}; +use frame_benchmarking::v2::*; + +#[benchmarks( + where T: ConfigCore + ConfigSigned + ConfigUnsigned, +)] +mod benchmarks { + use super::*; + + #[benchmark] + fn verify_page( + v: Linear< + { ::BenchmarkingConfig::VOTERS_PER_PAGE[0] }, + { ::BenchmarkingConfig::VOTERS_PER_PAGE[1] }, + >, + t: Linear< + { ::BenchmarkingConfig::TARGETS_PER_PAGE[0] }, + { ::BenchmarkingConfig::TARGETS_PER_PAGE[1] }, + >, + ) -> Result<(), BenchmarkError> { + helpers::setup_data_provider::( + ::BenchmarkingConfig::VOTERS, + ::BenchmarkingConfig::TARGETS, + ); + + if let Err(err) = helpers::setup_snapshot::(v, t) { + log!(error, "error setting up snapshot: {:?}.", err); + return Err(BenchmarkError::Stop("snapshot error")); + } + + #[block] + { + // TODO + let _ = 1 + 2; + } + + Ok(()) + } + + impl_benchmark_test_suite!( + PalletSigned, + crate::mock::ExtBuilder::default(), + crate::mock::Runtime, + exec_name = build_and_execute + ); +} diff --git a/substrate/frame/election-provider-multi-block/src/signed/mod.rs b/substrate/frame/election-provider-multi-block/src/signed/mod.rs new file mode 100644 index 0000000000000..3ec1c824597d0 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/signed/mod.rs @@ -0,0 +1,676 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Signed sub-pallet +//! +//! The main goal of the signed sub-pallet is to keep and manage a list of sorted score commitments +//! and correponding paged solutions during the [`crate::Phase::Signed`]. +//! +//! Accounts may submit up to [`Config::MaxSubmissions`] score commitments per election round and +//! this pallet ensures that the scores are stored under the map `SortedScores` are sorted and keyed +//! by the correct round number. +//! +//! Each submitter must hold a deposit per submission that is calculated based on the number of +//! pages required for a full submission and the number of submissions in the queue. The deposit is +//! returned in case the claimed score is correct after the solution verification. Note that if a +//! commitment and corresponding solution are not verified during the verification phase, the +//! submitter is not slashed and the deposits returned. +//! +//! When the time to evaluate the signed submission comes, the solutions are checked from best to +//! worse, which may result in one of three scenarios: +//! +//! 1. If the committed score and page submissions are correct, the submitter is rewarded. +//! 2. Any queued score that was not evaluated, the hold deposit is returned. +//! 3. Any invalid solution results in a 100% slash of the hold submission deposit. +//! +//! Once the [`crate::Phase::SignedValidation`] phase starts, the async verifier is notified to +//! start verifying the best queued solution. +//! +//! TODO: +//! - Be more efficient with cleaning up the submission storage by e.g. expose an extrinsic that +//! allows anyone to clean up the submissions storage with a small reward from the submission +//! deposit (clean up storage submissions and all corresponding metadata). + +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarking; + +#[cfg(test)] +mod tests; + +use crate::{ + signed::pallet::Submissions, + types::AccountIdOf, + verifier::{AsyncVerifier, SolutionDataProvider, VerificationResult}, + PageIndex, PagesOf, SolutionOf, +}; + +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{ + traits::{ + fungible::{ + hold::Balanced as FnBalanced, Credit, Inspect as FnInspect, MutateHold as FnMutateHold, + }, + tokens::Precision, + Defensive, + }, + RuntimeDebugNoBound, +}; +use scale_info::TypeInfo; +use sp_npos_elections::ElectionScore; +use sp_runtime::BoundedVec; +use sp_std::vec::Vec; + +// public re-exports. +pub use pallet::{ + Call, Config, Error, Event, HoldReason, Pallet, __substrate_call_check, + __substrate_event_check, tt_default_parts, tt_default_parts_v2, tt_error_token, +}; + +/// Alias for the pallet's balance type. +type BalanceOf = <::Currency as FnInspect>>::Balance; +/// Alias for the pallet's hold credit type. +pub type CreditOf = Credit, ::Currency>; + +/// Metadata of a registered submission. +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Default, RuntimeDebugNoBound)] +#[cfg_attr(test, derive(frame_support::PartialEqNoBound, frame_support::EqNoBound))] +#[codec(mel_bound(T: Config))] +#[scale_info(skip_type_params(T))] +pub struct SubmissionMetadata { + /// The score that this submission is proposing. + claimed_score: ElectionScore, + /// A bit-wise bounded vec representing the submitted pages thus far. + pages: BoundedVec>, + /// The amount held for this submission. + deposit: BalanceOf, +} + +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use core::marker::PhantomData; + + use crate::verifier::{AsyncVerifier, Verifier}; + + use super::*; + use frame_support::{ + pallet_prelude::{ValueQuery, *}, + traits::{Defensive, EstimateCallFee, OnUnbalanced}, + Twox64Concat, + }; + use frame_system::{ + ensure_signed, + pallet_prelude::{BlockNumberFor, OriginFor}, + WeightInfo, + }; + use sp_npos_elections::ElectionScore; + use sp_runtime::traits::Convert; + + #[pallet::config] + #[pallet::disable_frame_system_supertrait_check] + pub trait Config: crate::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The currency type. + type Currency: FnMutateHold + + FnBalanced; + + /// Something that can predict the fee of a call. Used to sensibly distribute rewards. + type EstimateCallFee: EstimateCallFee, BalanceOf>; + + /// Handler for the unbalanced reduction that happens when submitters are slashed. + type OnSlash: OnUnbalanced>; + + /// Something that calculates the signed base deposit based on the size of the current + /// queued solution proposals. + /// TODO: rename to `Deposit` or other? + type DepositBase: Convert>; + + /// Per-page deposit for a signed solution. + #[pallet::constant] + type DepositPerPage: Get>; + + /// Reward for an accepted solution. + #[pallet::constant] + type Reward: Get>; + + /// The maximum number of signed submissions per round. + #[pallet::constant] + type MaxSubmissions: Get; + + /// The pallet's hold reason. + type RuntimeHoldReason: From; + + type WeightInfo: WeightInfo; + } + + /// A sorted list of the current submissions scores corresponding to solution commitments + /// submitted in the signed phase, keyed by round. + /// + /// This pallet *MUST* ensure the bounded vec of scores is always sorted after mutation. + #[pallet::storage] + type SortedScores = StorageMap< + _, + Twox64Concat, + u32, + BoundedVec<(T::AccountId, ElectionScore), T::MaxSubmissions>, + ValueQuery, + >; + + /// A triple-map from (round, account, page) to a submitted solution. + #[pallet::storage] + type SubmissionStorage = StorageNMap< + _, + ( + NMapKey, + NMapKey, + NMapKey, + ), + SolutionOf, + OptionQuery, + >; + + /// A double-map from (`round`, `account_id`) to a submission metadata of a registered + /// solution commitment. + #[pallet::storage] + type SubmissionMetadataStorage = + StorageDoubleMap<_, Twox64Concat, u32, Twox64Concat, T::AccountId, SubmissionMetadata>; + + #[pallet::pallet] + pub struct Pallet(PhantomData); + + /// A reason for this pallet placing a hold on funds. + #[pallet::composite_enum] + pub enum HoldReason { + /// Deposit for registering an election solution. + ElectionSolutionSubmission, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A score commitment has been successfully registered. + Registered { round: u32, who: AccountIdOf, claimed_score: ElectionScore }, + /// A submission page was stored successfully. + PageStored { round: u32, who: AccountIdOf, page: PageIndex }, + /// Retracted a submission successfully. + Bailed { round: u32, who: AccountIdOf }, + /// A submission has been cleared by request. + SubmissionCleared { round: u32, submitter: AccountIdOf, reward: Option> }, + } + + #[pallet::error] + pub enum Error { + /// The election system is not expecting signed submissions. + NotAcceptingSubmissions, + /// Duplicate registering for a given round, + DuplicateRegister, + /// The submissions queue is full. Reject submission. + SubmissionsQueueFull, + /// Submission with a page index higher than the supported. + BadPageIndex, + /// A page submission was attempted for a submission that was not previously registered. + SubmissionNotRegistered, + /// A submission score is not high enough. + SubmissionScoreTooLow, + /// Bad timing for force clearing a stored submission. + CannotClear, + } + + /// Wrapper for signed submissions. + /// + /// It handle 3 storage items: + /// + /// 1. [`SortedScores`]: A flat, striclty sorted, vector with all the submission's scores. The + /// vector contains a tuple of `submitter_id` and `claimed_score`. + /// 2. [`SubmissionStorage`]: Paginated map with all submissions, keyed by round, submitter and + /// page index. + /// 3. [`SubmissionMetadataStorage`]: Double map with submissions metadata, keyed by submitter + /// ID and round. + /// + /// Invariants: + /// - TODO + pub(crate) struct Submissions(core::marker::PhantomData); + impl Submissions { + /// Generic mutation helper with checks. + /// + /// All the mutation functions must be done through this function. + fn mutate_checked R>(_round: u32, mutate: F) -> R { + let result = mutate(); + + #[cfg(debug_assertions)] + assert!(Self::sanity_check_round(crate::Pallet::::current_round()).is_ok()); + + result + } + + /// Try to register a submission commitment. + /// + /// The submission is not accepted if one of these invariants fails: + /// - The claimed score is not higher than the minimum expected score. + /// - The queue is full and the election score is strictly worse than all the current + /// queued solutions. + /// + /// A queued solution may be discarded if the queue is full and the new submission has a + /// better score. + /// + /// It must ensure that the metadata queue is sorted by election score. + fn try_register( + who: &T::AccountId, + round: u32, + metadata: SubmissionMetadata, + ) -> DispatchResult { + Self::mutate_checked(round, || Self::try_register_inner(who, round, metadata)) + } + + fn try_register_inner( + who: &T::AccountId, + round: u32, + metadata: SubmissionMetadata, + ) -> DispatchResult { + let mut scores = SortedScores::::get(round); + scores.iter().try_for_each(|(account, _)| -> DispatchResult { + ensure!(account != who, Error::::DuplicateRegister); + Ok(()) + })?; + + // most likely checked before, but double-checking. + debug_assert!(!SubmissionMetadataStorage::::contains_key(round, who)); + + // the submission score must be higher than the minimum trusted score. Note that since + // there is no queued solution yet, the check is performed against the minimum score + // only. TODO: consider rename `ensure_score_improves`. + ensure!( + ::ensure_score_improves(metadata.claimed_score), + Error::::SubmissionScoreTooLow, + ); + + let pos = + match scores.binary_search_by_key(&metadata.claimed_score, |(_, score)| *score) { + // in the unlikely event that election scores already exists in the storage, we + // store the submissions next to one other. + Ok(pos) | Err(pos) => pos, + }; + + let submission = (who.clone(), metadata.claimed_score); + + match scores.force_insert_keep_right(pos, submission) { + // entry inserted without discarding. + Ok(None) => Ok(()), + // entry inserted but queue was full, clear the discarded submission. + Ok(Some((discarded, _s))) => { + let _ = + SubmissionStorage::::clear_prefix((round, &discarded), u32::MAX, None); + // unreserve deposit + let _ = T::Currency::release_all( + &HoldReason::ElectionSolutionSubmission.into(), + &who, + Precision::Exact, + ) + .defensive()?; + + Ok(()) + }, + Err(_) => Err(Error::::SubmissionsQueueFull), + }?; + + SortedScores::::insert(round, scores); + SubmissionMetadataStorage::::insert(round, who, metadata); + + Ok(()) + } + + /// Store a paged solution for `who` in a given `round`. + /// + /// If `maybe_solution` is None, it will delete the given page from the submission store. + /// Successive calls to this with the same page index will replace the existing page + /// submission. + pub(crate) fn try_mutate_page( + who: &T::AccountId, + round: u32, + page: PageIndex, + maybe_solution: Option>, + ) -> DispatchResult { + Self::mutate_checked(round, || { + Self::try_mutate_page_inner(who, round, page, maybe_solution) + }) + } + + fn try_mutate_page_inner( + who: &T::AccountId, + round: u32, + page: PageIndex, + maybe_solution: Option>, + ) -> DispatchResult { + ensure!( + crate::Pallet::::current_phase().is_signed(), + Error::::NotAcceptingSubmissions + ); + ensure!(page < T::Pages::get(), Error::::BadPageIndex); + + ensure!( + SubmissionMetadataStorage::::contains_key(round, who), + Error::::SubmissionNotRegistered + ); + + // TODO: update the held deposit to account for the paged submission deposit. + + SubmissionStorage::::mutate_exists((round, who, page), |maybe_old_solution| { + *maybe_old_solution = maybe_solution + }); + + Ok(()) + } + + /// Clears all the stored data from the leader. + /// + /// Returns the submission metadata of the cleared submission, if any. + pub(crate) fn take_leader_data( + round: u32, + ) -> Option<(T::AccountId, SubmissionMetadata)> { + Self::mutate_checked(round, || { + SortedScores::::mutate(round, |scores| scores.pop()).and_then( + |(submitter, _score)| { + let _ = SubmissionStorage::::clear_prefix( + (round, &submitter), + u32::MAX, + None, + ); // TODO: handle error. + + SubmissionMetadataStorage::::take(round, &submitter) + .map(|metadata| (submitter, metadata)) + }, + ) + }) + } + + /// Returns the leader submitter for the current round and corresponding claimed score. + pub(crate) fn leader(round: u32) -> Option<(T::AccountId, ElectionScore)> { + SortedScores::::get(round).last().cloned() + } + + /// Returns a submission page for a given round, submitter and page index. + pub(crate) fn get_page( + who: &T::AccountId, + round: u32, + page: PageIndex, + ) -> Option> { + SubmissionStorage::::get((round, who, page)) + } + } + + #[allow(dead_code)] + impl Submissions { + /// Returns the metadata of a submitter for a given account. + pub(crate) fn metadata_for( + round: u32, + who: &T::AccountId, + ) -> Option> { + SubmissionMetadataStorage::::get(round, who) + } + + /// Returns the scores for a given round. + pub(crate) fn scores_for( + round: u32, + ) -> BoundedVec<(T::AccountId, ElectionScore), T::MaxSubmissions> { + SortedScores::::get(round) + } + + /// Returns the submission of a submitter for a given round and page. + pub(crate) fn submission_for( + who: T::AccountId, + round: u32, + page: PageIndex, + ) -> Option> { + SubmissionStorage::::get((round, who, page)) + } + } + + #[cfg(debug_assertions)] + impl Submissions { + fn sanity_check_round(_round: u32) -> Result<(), &'static str> { + // TODO + Ok(()) + } + } + + impl Pallet { + pub(crate) fn do_register( + who: &T::AccountId, + claimed_score: ElectionScore, + round: u32, + ) -> DispatchResult { + let deposit = T::DepositBase::convert( + SubmissionMetadataStorage::::iter_key_prefix(round).count(), + ); + + T::Currency::hold(&HoldReason::ElectionSolutionSubmission.into(), &who, deposit)?; + + let pages: BoundedVec<_, T::Pages> = (0..T::Pages::get()) + .map(|_| false) + .collect::>() + .try_into() + .expect("bounded vec constructed from bound; qed."); + + let metadata = SubmissionMetadata { pages, claimed_score, deposit }; + + let _ = Submissions::::try_register(&who, round, metadata)?; + Ok(()) + } + } + + #[pallet::call] + impl Pallet { + /// Submit a score commitment for a solution in the current round. + /// + /// The scores must be kept sorted in the `SortedScores` storage map. + #[pallet::call_index(1)] + pub fn register(origin: OriginFor, claimed_score: ElectionScore) -> DispatchResult { + let who = ensure_signed(origin)?; + + ensure!( + crate::Pallet::::current_phase().is_signed(), + Error::::NotAcceptingSubmissions + ); + + let round = crate::Pallet::::current_round(); + ensure!( + !SubmissionMetadataStorage::::contains_key(round, who.clone()), + Error::::DuplicateRegister + ); + + Self::do_register(&who, claimed_score, round)?; + + Self::deposit_event(Event::::Registered { round, who, claimed_score }); + Ok(()) + } + + /// Submit a page for a solution. + /// + /// To submit a solution page successfull, the submitter must have registered the + /// commitment before. + /// + /// TODO: for security reasons, we have to ensure that ALL submitters "space" to + /// submit their pages and be verified. + #[pallet::call_index(2)] + pub fn submit_page( + origin: OriginFor, + page: PageIndex, + maybe_solution: Option>, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + ensure!( + crate::Pallet::::current_phase().is_signed(), + Error::::NotAcceptingSubmissions + ); + + let round = crate::Pallet::::current_round(); + Submissions::::try_mutate_page(&who, round, page, maybe_solution)?; + + Self::deposit_event(Event::::PageStored { + round: crate::Pallet::::current_round(), + who, + page, + }); + + Ok(()) + } + + /// Unregister a submission. + /// + /// This will fully remove the solution and corresponding metadata from storage and refund + /// the submission deposit. + /// + /// NOTE: should we refund the deposit? there's an attack vector where an attacker can + /// register with a set of very high elections core and then retract all submission just + /// before the signed phase ends. This may end up depriving other honest miners from + /// registering their solution. + #[pallet::call_index(3)] + pub fn bail(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + + ensure!( + crate::Pallet::::current_phase().is_signed(), + Error::::NotAcceptingSubmissions + ); + + // TODO + // 1. clear all storage items related to `who` + // 2. return deposit + + Self::deposit_event(Event::::Bailed { + round: crate::Pallet::::current_round(), + who, + }); + + Ok(()) + } + + /// Force clean submissions storage. + /// + /// Allows any account to receive a reward for requesting the submission storage and + /// corresponding metadata to be cleaned. This extrinsic will fail if the signed or signed + /// validated phases are active to prevent disruption in the election progress. + /// + /// A successfull call will result in a reward that is taken from the cleared submission + /// deposit and the return of the call fees. + #[pallet::call_index(4)] + pub fn force_clear_submission( + origin: OriginFor, + submitter: T::AccountId, + ) -> DispatchResult { + let _who = ensure_signed(origin); + + // prevent cleaning up submissions storage during the signed and signed validation + // phase. + ensure!( + !crate::Pallet::::current_phase().is_signed() && + !crate::Pallet::::current_phase().is_signed_validation_open_at(None), + Error::::CannotClear, + ); + + // TODO: + // 1. clear the submission, if it exists + // 2. clear the submission metadata + // 3. reward caller as a portions of the submittion's deposit + let reward = Default::default(); + // 4. return fees. + + Self::deposit_event(Event::::SubmissionCleared { + round: crate::Pallet::::current_round(), + submitter, + reward, + }); + + Ok(()) + } + } + + #[pallet::hooks] + impl Hooks> for Pallet { + /// The `on_initialize` signals the [`AsyncVerifier`] whenever it should start or stop the + /// asynchronous verification of the stored submissions. + /// + /// - Start async verification at the beginning of the [`crate::Phase::SignedValidation`]. + /// - Stopns async verification at the beginning of the [`crate::Phase::Unsigned`]. + fn on_initialize(now: BlockNumberFor) -> Weight { + // TODO: match + if crate::Pallet::::current_phase().is_signed_validation_open_at(Some(now)) { + let _ = ::start().defensive(); + }; + + if crate::Pallet::::current_phase().is_unsigned_open_at(now) { + sublog!(info, "signed", "signed validation phase ended, signaling the verifier."); + ::stop(); + } + + if crate::Pallet::::current_phase() == crate::Phase::Off { + sublog!(info, "signed", "clear up storage for pallets."); + + // TODO: optimize. + let _ = SubmissionMetadataStorage::::clear(u32::MAX, None); + let _ = SubmissionStorage::::clear(u32::MAX, None); + let _ = SortedScores::::clear(u32::MAX, None); + } + + Weight::default() + } + } +} + +impl SolutionDataProvider for Pallet { + type Solution = SolutionOf; + + fn get_paged_solution(page: PageIndex) -> Option { + let round = crate::Pallet::::current_round(); + + Submissions::::leader(round).map(|(who, _score)| { + sublog!(info, "signed", "returning page {} of leader's {:?} solution", page, who); + Submissions::::get_page(&who, round, page).unwrap_or_default() + }) + } + + fn get_score() -> Option { + let round = crate::Pallet::::current_round(); + Submissions::::leader(round).map(|(_who, score)| score) + } + + fn report_result(result: VerificationResult) { + let round = crate::Pallet::::current_round(); + match result { + VerificationResult::Queued => {}, + VerificationResult::Rejected => { + if let Some((_offender, _metadata)) = Submissions::::take_leader_data(round) { + // TODO: slash offender + } else { + // no signed submission in storage, signal async verifier to stop and move on. + let _ = ::stop(); + }; + + if crate::Pallet::::current_phase().is_signed_validation_open_at(None) && + Submissions::::leader(round).is_some() + { + let _ = ::start().defensive(); + } + }, + VerificationResult::DataUnavailable => { + // signed pallet did not have the required data. + }, + } + } +} diff --git a/substrate/frame/election-provider-multi-block/src/signed/tests.rs b/substrate/frame/election-provider-multi-block/src/signed/tests.rs new file mode 100644 index 0000000000000..864a94eb1a8c4 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/signed/tests.rs @@ -0,0 +1,309 @@ +// 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 super::*; +use crate::{mock::*, verifier::SolutionDataProvider, Phase, Verifier}; +use frame_support::{assert_noop, assert_ok, testing_prelude::*}; +use sp_npos_elections::ElectionScore; + +mod calls { + use super::*; + use sp_core::bounded_vec; + + #[test] + fn register_works() { + ExtBuilder::default().build_and_execute(|| { + roll_to_phase(Phase::Signed); + assert_ok!(assert_snapshots()); + + assert_eq!(balances(99), (100, 0)); + let score = ElectionScore { minimal_stake: 100, ..Default::default() }; + + assert_ok!(SignedPallet::register(RuntimeOrigin::signed(99), score)); + assert_eq!(balances(99), (90, 10)); + + assert_eq!( + Submissions::::metadata_for(current_round(), &99).unwrap(), + SubmissionMetadata { + claimed_score: score, + deposit: 10, + pages: bounded_vec![false, false, false], + } + ); + + assert_eq!( + signed_events(), + vec![Event::Registered { round: 0, who: 99, claimed_score: score }], + ); + + // duplicate submission for the same round fails. + assert_noop!( + SignedPallet::register(RuntimeOrigin::signed(99), score), + Error::::DuplicateRegister, + ); + + // if claimed score if below the minimum score, submission will fail. + ::set_minimum_score(ElectionScore { + minimal_stake: 20, + ..Default::default() + }); + + let low_score = ElectionScore { minimal_stake: 10, ..Default::default() }; + assert_noop!( + SignedPallet::register(RuntimeOrigin::signed(97), low_score), + Error::::SubmissionScoreTooLow, + ); + }) + } + + #[test] + fn register_sorted_works() { + ExtBuilder::default().signed_max_submissions(3).build_and_execute(|| { + // try register 5 submissions: + // - 3 are stored. + // - one submission is registered after queue is full while the score improves current + // submission in the queue; other submission is discarded. + // - one submission is registered after queue is full while the score does not improve + // the current submission in the queue; submission is discarded. + + roll_to_phase(Phase::Signed); + + let score = ElectionScore { minimal_stake: 40, ..Default::default() }; + assert_ok!(SignedPallet::register(RuntimeOrigin::signed(40), score)); + + let score = ElectionScore { minimal_stake: 30, ..Default::default() }; + assert_ok!(SignedPallet::register(RuntimeOrigin::signed(30), score)); + + let score = ElectionScore { minimal_stake: 20, ..Default::default() }; + assert_ok!(SignedPallet::register(RuntimeOrigin::signed(20), score)); + + // submission queue is full, next submissions will only be accepted if the submitted + // score improves the current lower score. + + // registration discarded. + let score = ElectionScore { minimal_stake: 10, ..Default::default() }; + assert_noop!( + SignedPallet::register(RuntimeOrigin::signed(10), score), + Error::::SubmissionsQueueFull + ); + + // higher score is successfully registered. + let higher_score = ElectionScore { minimal_stake: 50, ..Default::default() }; + assert_ok!(SignedPallet::register(RuntimeOrigin::signed(50), higher_score)); + + assert_eq!(Submissions::::leader(current_round()).unwrap(), (50, higher_score),); + + assert_eq!( + signed_events(), + vec![ + Event::Registered { + round: 0, + who: 40, + claimed_score: ElectionScore { + minimal_stake: 40, + sum_stake: 0, + sum_stake_squared: 0 + } + }, + Event::Registered { + round: 0, + who: 30, + claimed_score: ElectionScore { + minimal_stake: 30, + sum_stake: 0, + sum_stake_squared: 0 + } + }, + Event::Registered { + round: 0, + who: 20, + claimed_score: ElectionScore { + minimal_stake: 20, + sum_stake: 0, + sum_stake_squared: 0 + } + }, + Event::Registered { + round: 0, + who: 50, + claimed_score: ElectionScore { + minimal_stake: 50, + sum_stake: 0, + sum_stake_squared: 0 + } + }, + ], + ); + }) + } + + #[test] + fn submit_page_works() { + ExtBuilder::default().build_and_execute(|| { + // bad timing. + assert_noop!( + SignedPallet::submit_page(RuntimeOrigin::signed(40), 0, None), + Error::::NotAcceptingSubmissions + ); + + roll_to_phase(Phase::Signed); + + // submission not registered before. + assert_noop!( + SignedPallet::submit_page(RuntimeOrigin::signed(10), 0, None), + Error::::SubmissionNotRegistered + ); + + let score = ElectionScore { minimal_stake: 10, ..Default::default() }; + assert_ok!(SignedPallet::register(RuntimeOrigin::signed(10), score)); + + // now submission works since there is a registered commitment. + assert_ok!(SignedPallet::submit_page( + RuntimeOrigin::signed(10), + 0, + Some(Default::default()) + )); + + assert_eq!( + Submissions::::submission_for(10, current_round(), 0), + Some(Default::default()), + ); + + // tries to submit a page out of bounds. + assert_noop!( + SignedPallet::submit_page(RuntimeOrigin::signed(10), 10, Some(Default::default())), + Error::::BadPageIndex, + ); + + assert_eq!( + signed_events(), + vec![ + Event::Registered { + round: 0, + who: 10, + claimed_score: ElectionScore { + minimal_stake: 10, + sum_stake: 0, + sum_stake_squared: 0 + } + }, + Event::PageStored { round: 0, who: 10, page: 0 } + ], + ); + }) + } +} + +mod solution_data_provider { + use super::*; + + #[test] + fn higher_score_works() { + ExtBuilder::default().build_and_execute(|| { + roll_to_phase(Phase::Signed); + + assert_eq!(::get_score(), None); + + let higher_score = ElectionScore { minimal_stake: 40, ..Default::default() }; + assert_ok!(SignedPallet::register(RuntimeOrigin::signed(40), higher_score)); + + let score = ElectionScore { minimal_stake: 30, ..Default::default() }; + assert_ok!(SignedPallet::register(RuntimeOrigin::signed(30), score)); + + assert_eq!(::get_score(), Some(higher_score)); + }) + } + + #[test] + fn get_page_works() { + ExtBuilder::default().build_and_execute(|| { + roll_to_phase(Phase::Signed); + assert_eq!(::get_score(), None); + }) + } +} + +mod e2e { + use super::*; + + type MaxSubmissions = ::MaxSubmissions; + + mod simple_e2e_works { + use super::*; + + #[test] + fn submit_solution_happy_path_works() { + ExtBuilder::default().build_and_execute(|| { + roll_to_phase(Phase::Signed); + + let current_round = MultiPhase::current_round(); + assert!(Submissions::::metadata_for(current_round, &10).is_none()); + + let claimed_score = ElectionScore { minimal_stake: 100, ..Default::default() }; + + // register submission + assert_ok!(SignedPallet::register(RuntimeOrigin::signed(10), claimed_score,)); + + // metadata and claimed scores have been stored as expected. + assert_eq!( + Submissions::::metadata_for(current_round, &10), + Some(SubmissionMetadata { + claimed_score, + deposit: 10, + pages: bounded_vec![false, false, false], + }) + ); + let expected_scores: BoundedVec<(AccountId, ElectionScore), MaxSubmissions> = + bounded_vec![(10, claimed_score)]; + assert_eq!(Submissions::::scores_for(current_round), expected_scores); + + // submit all pages of a noop solution; + let solution = TestNposSolution::default(); + for page in (0..=MultiPhase::msp()).into_iter().rev() { + assert_ok!(SignedPallet::submit_page( + RuntimeOrigin::signed(10), + page, + Some(solution.clone()) + )); + + assert_eq!( + Submissions::::submission_for(10, current_round, page), + Some(solution.clone()) + ); + } + + assert_eq!( + signed_events(), + vec![ + Event::Registered { + round: 0, + who: 10, + claimed_score: ElectionScore { + minimal_stake: 100, + sum_stake: 0, + sum_stake_squared: 0 + } + }, + Event::PageStored { round: 0, who: 10, page: 2 }, + Event::PageStored { round: 0, who: 10, page: 1 }, + Event::PageStored { round: 0, who: 10, page: 0 }, + ] + ); + }) + } + } +} diff --git a/substrate/frame/election-provider-multi-block/src/types.rs b/substrate/frame/election-provider-multi-block/src/types.rs new file mode 100644 index 0000000000000..e903db68e2d77 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/types.rs @@ -0,0 +1,255 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Types for the multi-block election provider pallet and sub-pallets. + +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; + +use crate::{unsigned::miner::Config as MinerConfig, Verifier}; +use frame_election_provider_support::{ElectionProvider, NposSolution, PageIndex}; +use frame_support::{ + BoundedVec, CloneNoBound, DebugNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, + RuntimeDebugNoBound, +}; +use sp_npos_elections::ElectionScore; +use sp_runtime::SaturatedConversion; +use sp_std::{boxed::Box, vec::Vec}; + +/// The main account ID type. +pub type AccountIdOf = ::AccountId; + +/// Supports that are returned from a given [`Verifier`]. +pub type SupportsOf = frame_election_provider_support::BoundedSupports< + ::AccountId, + ::MaxWinnersPerPage, + ::MaxBackersPerWinner, +>; + +/// Supports that are returned from a given [`miner::Config`]. +pub type MinerSupportsOf = frame_election_provider_support::BoundedSupports< + ::AccountId, + ::MaxWinnersPerPage, + ::MaxBackersPerWinner, +>; + +/// The voter index. Derived from the solution of the Miner config. +pub type SolutionVoterIndexOf = <::Solution as NposSolution>::VoterIndex; +/// The target index. Derived from the solution of the Miner config. +pub type SolutionTargetIndexOf = <::Solution as NposSolution>::TargetIndex; + +/// The solution type used by this crate. +pub type SolutionOf = ::Solution; + +/// Alias for an error of a fallback election provider. +type FallbackErrorOf = <::Fallback as ElectionProvider>::Error; + +/// Alias for a voter, parameterized by this crate's config. +pub(crate) type VoterOf = + frame_election_provider_support::VoterOf<::DataProvider>; + +/// Same as [`VoterOf`], but parameterized by the `miner::Config`. +pub(crate) type MinerVoterOf = frame_election_provider_support::Voter< + ::AccountId, + ::MaxVotesPerVoter, +>; + +/// Alias for a page of voters, parameterized by this crate's config. +pub(crate) type VoterPageOf = + BoundedVec, ::VoterSnapshotPerBlock>; +/// Alias for a page of targets, parameterized by this crate's config. +pub(crate) type TargetPageOf = + BoundedVec, ::TargetSnapshotPerBlock>; + +/// Same as [`VoterPageOf`], but parameterized by [`miner::Config`]. +pub(crate) type VoterPageMinerOf = + BoundedVec, ::VoterSnapshotPerBlock>; +/// Same as [`TargetPageOf`], but parameterized by []`miner::Config`]. +pub(crate) type TargetPageMinerOf = + BoundedVec<::AccountId, ::TargetSnapshotPerBlock>; + +pub(crate) type MaxWinnersPerPageOf = ::MaxWinnersPerPage; + +/// Alias for all pages of voters, parameterized by the miner's Config. +pub(crate) type AllVoterPagesOf = BoundedVec, ::Pages>; +pub(crate) type AllTargetPagesOf = BoundedVec, ::Pages>; + +/// Edges from voters to nominated targets that are part of the winner set. +pub type AssignmentOf = + sp_npos_elections::Assignment<::AccountId, SolutionAccuracyOf>; + +// Accuracy of the election. +pub type SolutionAccuracyOf = <::Solution as NposSolution>::Accuracy; + +/// Encodes the length of a page of either a solution or a snapshot. +/// +/// This is stored automatically on-chain, and it contains the **size of the entire snapshot page**. +/// This is also used in dispatchables as weight witness data and should **only contain the size of +/// the presented solution page**, not the entire snapshot or page snaphsot. +#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, Debug, Default, TypeInfo)] +pub struct PageSize { + /// The length of voters. + #[codec(compact)] + pub voters: u32, + /// The length of targets. + #[codec(compact)] + pub targets: u32, +} + +/// Strategies for when the election fails. +#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, MaxEncodedLen, Debug, TypeInfo)] +pub enum ElectionFailureStrategy { + /// Enters in emergency phase when election fails. + Emergency, + /// Restarts the election phase without starting a new era. + Restart, +} + +impl Default for ElectionFailureStrategy { + fn default() -> Self { + ElectionFailureStrategy::Restart + } +} + +/// Current phase of an election. +#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, MaxEncodedLen, Debug, TypeInfo)] +pub enum Phase { + /// Election has halted -- nothing will happen. + Halted, + /// The election is off. + Off, + /// Signed phase is open. + Signed, + /// The signed validations phase + SignedValidation(Bn), + Unsigned(Bn), + /// Preparing the paged target and voter snapshots. + Snapshot(PageIndex), + /// Exporting the paged election result (i.e. most likely staking is requesting election + /// pages). It includes the block at which the export phase started. + Export(Bn), + /// Emergency phase, something went wrong and the election is halted. + Emergency, +} + +impl Default for Phase { + fn default() -> Self { + Phase::Off + } +} + +impl Phase { + pub(crate) fn is_signed(&self) -> bool { + matches!(self, Phase::Signed) + } + + pub(crate) fn is_snapshot(&self) -> bool { + matches!(self, Phase::Snapshot(_)) + } + + /// Returns whether the validation phase is ongoing. + pub(crate) fn is_signed_validation_open_at(&self, at: Option) -> bool { + match at { + Some(at) => matches!(self, Phase::SignedValidation(real) if *real == at), + None => matches!(self, Phase::SignedValidation(_)), + } + } + + pub(crate) fn is_unsigned_open_at(&self, at: Bn) -> bool { + matches!(self, Phase::Unsigned(real) if *real == at) + } + + pub(crate) fn is_unsigned(&self) -> bool { + matches!(self, Phase::Unsigned(_)) + } + + pub(crate) fn is_export(&self) -> bool { + matches!(self, Phase::Export(_)) + } +} + +#[derive(DebugNoBound, PartialEq)] +pub enum ElectionError { + /// Error returned by the election data provider. + DataProvider, + /// The data provider returned data that exceeded the boundaries defined in the contract with + /// the election provider. + DataProviderBoundariesExceeded, + /// The support `page_index` was not available at request. + SupportPageNotAvailable(PageIndex), + /// The requested page exceeds the number of election pages defined of the current election + /// config. + RequestedPageExceeded, + /// The fallback election error'ed. + Fallback(FallbackErrorOf), +} + +/// A paged raw solution which contains a set of paginated solutions to be submitted. +/// +/// A raw solution has not been checked for correctness. +#[derive( + TypeInfo, + Encode, + Decode, + RuntimeDebugNoBound, + CloneNoBound, + EqNoBound, + PartialEqNoBound, + MaxEncodedLen, + DefaultNoBound, +)] +#[codec(mel_bound(T: MinerConfig))] +#[scale_info(skip_type_params(T))] +pub struct PagedRawSolution { + pub solution_pages: BoundedVec, T::Pages>, + pub score: ElectionScore, + pub round: u32, +} + +/// A helper trait to deal with the page index of partial solutions. +/// +/// This should only be called on the `Vec` or similar types. If the solution is *full*, +/// it returns a normal iterator that is just mapping the index (usize) to `PageIndex`. +/// +/// if the solution is partial, it shifts the indices sufficiently so that the most significant page +/// of the solution matches with the most significant page of the snapshot onchain. +pub trait Pagify { + fn pagify(&self, bound: PageIndex) -> Box + '_>; + fn into_pagify(self, bound: PageIndex) -> Box>; +} + +impl Pagify for Vec { + fn pagify(&self, desired_pages: PageIndex) -> Box + '_> { + Box::new( + self.into_iter() + .enumerate() + .map(|(p, s)| (p.saturated_into::(), s)) + .map(move |(p, s)| { + let desired_pages_usize = desired_pages as usize; + // TODO: this could be an error. + debug_assert!(self.len() <= desired_pages_usize); + let padding = desired_pages_usize.saturating_sub(self.len()); + let new_page = p.saturating_add(padding.saturated_into::()); + (new_page, s) + }), + ) + } + + fn into_pagify(self, _: PageIndex) -> Box> { + todo!() + } +} diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/benchmarking.rs b/substrate/frame/election-provider-multi-block/src/unsigned/benchmarking.rs new file mode 100644 index 0000000000000..ba2d769a11bc0 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/unsigned/benchmarking.rs @@ -0,0 +1,88 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Benchmarking for the Elections Multiblock Unsigned sub-pallet. + +use super::*; +use crate::{ + benchmarking::helpers, signed::Config as ConfigSigned, unsigned::Config, BenchmarkingConfig, + Config as ConfigCore, ConfigVerifier, Pallet as PalletCore, Phase, +}; +use frame_system::RawOrigin; + +use frame_benchmarking::v2::*; + +#[benchmarks( + where T: Config + ConfigCore + ConfigSigned + ConfigVerifier, +)] +mod benchmarks { + use super::*; + + #[benchmark] + fn submit_page_unsigned( + v: Linear< + { ::BenchmarkingConfig::VOTERS_PER_PAGE[0] }, + { ::BenchmarkingConfig::VOTERS_PER_PAGE[1] }, + >, + t: Linear< + { ::BenchmarkingConfig::TARGETS_PER_PAGE[0] }, + { ::BenchmarkingConfig::TARGETS_PER_PAGE[1] }, + >, + ) -> Result<(), BenchmarkError> { + // configs necessary to proceed with the unsigned submission. + PalletCore::::phase_transition(Phase::Unsigned(0u32.into())); + + helpers::setup_data_provider::( + ::BenchmarkingConfig::VOTERS.max(v), + ::BenchmarkingConfig::TARGETS.max(t), + ); + + if let Err(err) = helpers::setup_snapshot::(v, t) { + log!(error, "error setting up snapshot: {:?}.", err); + return Err(BenchmarkError::Stop("snapshot error")); + } + + // the last page (0) will also perfom a full feasibility check for all the pages in the + // queue. For this benchmark, we want to ensure that we do not call `submit_page_unsigned` + // on the last page, to avoid this extra step. + assert!(T::Pages::get() >= 2); + + let (claimed_full_score, partial_score, paged_solution) = + OffchainWorkerMiner::::mine(PalletCore::::msp()).map_err(|err| { + log!(error, "mine error: {:?}", err); + BenchmarkError::Stop("miner error") + })?; + + #[extrinsic_call] + _( + RawOrigin::None, + PalletCore::::msp(), + paged_solution, + partial_score, + claimed_full_score, + ); + + Ok(()) + } + + impl_benchmark_test_suite!( + PalletUnsigned, + crate::mock::ExtBuilder::default(), + crate::mock::Runtime, + exec_name = build_and_execute + ); +} diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs b/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs new file mode 100644 index 0000000000000..a83cbdafb09c8 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs @@ -0,0 +1,811 @@ +// 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. + +//! # NPoS miner + +use crate::{ + helpers, + types::{PageSize, Pagify}, + unsigned::{pallet::Config as UnsignedConfig, Call}, + verifier::FeasibilityError, + AssignmentOf, MinerSupportsOf, MinerVoterOf, Pallet as EPM, Snapshot, +}; + +use frame_election_provider_support::{ + ElectionDataProvider, IndexAssignmentOf, NposSolution, NposSolver, PageIndex, + TryIntoBoundedSupports, Weight, +}; +use frame_support::{ensure, traits::Get, BoundedVec}; +use scale_info::TypeInfo; +use sp_npos_elections::{ElectionResult, ElectionScore, ExtendedBalance, Support}; +use sp_runtime::{offchain::storage::StorageValueRef, SaturatedConversion}; +use sp_std::{prelude::ToOwned, vec, vec::Vec}; + +pub type TargetSnaphsotOf = + BoundedVec<::AccountId, ::TargetSnapshotPerBlock>; +pub type VoterSnapshotPagedOf = BoundedVec< + BoundedVec, ::VoterSnapshotPerBlock>, + ::Pages, +>; + +#[derive(Debug, Eq, PartialEq, Clone)] +pub enum MinerError { + /// An internal error in the NPoS elections crate. + NposElections(sp_npos_elections::Error), + /// Snapshot data was unavailable. + SnapshotUnAvailable(SnapshotType), + /// An error from the election solver. + Solver, + /// The solution generated from the miner is not feasible. + Feasibility(FeasibilityError), + InvalidPage, + SubmissionFailed, + NotEnoughTargets, + DataProvider, +} + +impl From for MinerError { + fn from(e: sp_npos_elections::Error) -> Self { + MinerError::NposElections(e) + } +} + +impl From for MinerError { + fn from(e: FeasibilityError) -> Self { + MinerError::Feasibility(e) + } +} + +impl From for MinerError { + fn from(typ: SnapshotType) -> Self { + MinerError::SnapshotUnAvailable(typ) + } +} + +/// The type of the snapshot. +/// +/// Used to express errors. +#[derive(Debug, Eq, PartialEq, Clone)] +pub enum SnapshotType { + /// Voters at the given page missing. + Voters(PageIndex), + /// Targets are missing. + Targets, + // Desired targets are missing. + DesiredTargets, +} + +/// Reports the trimming result of a mined solution +#[derive(Debug, Clone, PartialEq)] +pub struct TrimmingStatus { + weight: usize, + length: usize, +} + +impl Default for TrimmingStatus { + fn default() -> Self { + Self { weight: 0, length: 0 } + } +} + +use crate::PagedRawSolution; +use codec::{EncodeLike, MaxEncodedLen}; + +pub trait Config { + type AccountId: Ord + Clone + codec::Codec + core::fmt::Debug; + + type Solution: codec::Codec + + sp_std::fmt::Debug + + Default + + PartialEq + + Eq + + Clone + + Sized + + Ord + + NposSolution + + TypeInfo + + EncodeLike + + MaxEncodedLen; + + type Solver: NposSolver< + AccountId = Self::AccountId, + Accuracy = ::Accuracy, + >; + + type Pages: Get; + + type MaxVotesPerVoter: Get; + type MaxWinnersPerPage: Get; + type MaxBackersPerWinner: Get; + + type VoterSnapshotPerBlock: Get; + type TargetSnapshotPerBlock: Get; + + type MaxWeight: Get; + type MaxLength: Get; +} + +pub struct Miner(sp_std::marker::PhantomData); + +impl Miner { + pub fn mine_paged_solution_with_snapshot( + all_voter_pages: &BoundedVec< + BoundedVec, T::VoterSnapshotPerBlock>, + T::Pages, + >, + all_targets: &BoundedVec, + pages: PageIndex, + round: u32, + desired_targets: u32, + do_reduce: bool, + ) -> Result<(PagedRawSolution, TrimmingStatus), MinerError> { + // useless to proceed if the solution will not be feasible. + ensure!(all_targets.len() >= desired_targets as usize, MinerError::NotEnoughTargets); + + // flatten pages of voters and target snapshots. + let all_voters: Vec> = + all_voter_pages.iter().cloned().flatten().collect::>(); + + // these closures generate an efficient index mapping of each tvoter -> the snaphot + // that they are part of. this needs to be the same indexing fn in the verifier side to + // sync when reconstructing the assingments page from a solution. + //let binding_targets = all_targets.clone(); + let voters_page_fn = helpers::generate_voter_page_fn::(&all_voter_pages); + let targets_index_fn = helpers::target_index_fn::(&all_targets); + + // run the election with all voters and targets. + let ElectionResult { winners: _, assignments } = ::solve( + desired_targets as usize, + all_targets.clone().to_vec(), + all_voters.clone(), + ) + .map_err(|_| MinerError::Solver)?; + + if do_reduce { + // TODO(gpestana): reduce and trim. + } + // split assignments into `T::Pages pages. + let mut paged_assignments: BoundedVec>, T::Pages> = + BoundedVec::with_bounded_capacity(pages as usize); + + paged_assignments.bounded_resize(pages as usize, vec![]); + + // adds assignment to the correct page, based on the voter's snapshot page. + for assignment in assignments { + let page = voters_page_fn(&assignment.who).ok_or(MinerError::InvalidPage)?; + let assignment_page = + paged_assignments.get_mut(page as usize).ok_or(MinerError::InvalidPage)?; + assignment_page.push(assignment); + } + + // convert each page of assignments to a paged `T::Solution`. + let solution_pages: BoundedVec<::Solution, T::Pages> = paged_assignments + .clone() + .into_iter() + .enumerate() + .map(|(page_index, assignment_page)| { + let page: PageIndex = page_index.saturated_into(); + let voter_snapshot_page = all_voter_pages + .get(page as usize) + .ok_or(MinerError::SnapshotUnAvailable(SnapshotType::Voters(page)))?; + + let voters_index_fn = { + let cache = helpers::generate_voter_cache::(&voter_snapshot_page); + helpers::voter_index_fn_owned::(cache) + }; + + <::Solution>::from_assignment( + &assignment_page, + &voters_index_fn, + &targets_index_fn, + ) + .map_err(|e| MinerError::NposElections(e)) + }) + .collect::, _>>()? + .try_into() + .expect("paged_assignments is bound by `T::Pages. qed."); + + // TODO(gpestana): trim again? + let trimming_status = Default::default(); + + let mut paged_solution = + PagedRawSolution { solution_pages, score: Default::default(), round }; + + // everytthing's ready - calculate final solution score. + paged_solution.score = + Self::compute_score(all_voter_pages, all_targets, &paged_solution, desired_targets)?; + + Ok((paged_solution, trimming_status)) + } + + /// Take the given raw paged solution and compute its score. This will replicate what the chain + /// would do as closely as possible, and expects all the corresponding snapshot data to be + /// available. + fn compute_score( + voters: &VoterSnapshotPagedOf, + targets: &TargetSnaphsotOf, + paged_solution: &PagedRawSolution, + desired_targets: u32, + ) -> Result { + use sp_npos_elections::EvaluateSupport; + use sp_std::collections::btree_map::BTreeMap; + + let all_supports = + Self::feasibility_check(voters, targets, paged_solution, desired_targets)?; + let mut total_backings: BTreeMap = BTreeMap::new(); + all_supports.into_iter().map(|x| x.0).flatten().for_each(|(who, support)| { + let backing = total_backings.entry(who).or_default(); + *backing = backing.saturating_add(support.total); + }); + + let all_supports = total_backings + .into_iter() + .map(|(who, total)| (who, Support { total, ..Default::default() })) + .collect::>(); + + Ok((&all_supports).evaluate()) + } + + // Checks the feasibility of a paged solution and calculates the score associated with the + // page. + pub fn compute_partial_score( + voters: &VoterSnapshotPagedOf, + targets: &TargetSnaphsotOf, + solution: &::Solution, + desired_targets: u32, + page: PageIndex, + ) -> Result { + let supports = Self::feasibility_check_partial( + voters, + targets, + solution.clone(), + desired_targets, + page, + )?; + let score = sp_npos_elections::evaluate_support( + supports.clone().into_iter().map(|(_, backings)| backings), + ); + + Ok(score) + } + + /// Perform the feasibility check on all pages of a solution, one by one, and returns the + /// supports of the full solution. + pub fn feasibility_check( + voters: &VoterSnapshotPagedOf, + targets: &TargetSnaphsotOf, + paged_solution: &PagedRawSolution, + desired_targets: u32, + ) -> Result>, MinerError> { + // check every solution page for feasibility. + paged_solution + .solution_pages + .pagify(T::Pages::get()) + .map(|(page_index, page_solution)| { + Self::feasibility_check_partial( + voters, + targets, + page_solution.clone(), + desired_targets, + page_index as PageIndex, + ) + }) + .collect::, _>>() + .map_err(|err| MinerError::from(err)) + } + + /// Performs the feasibility check of a single page, returns the supports of the partial + /// feasibility check. + pub fn feasibility_check_partial( + voters: &VoterSnapshotPagedOf, + targets: &TargetSnaphsotOf, + partial_solution: ::Solution, + desired_targets: u32, + page: PageIndex, + ) -> Result, FeasibilityError> { + // TODO: double check page index if tests ERR. + let voters_page: BoundedVec, ::VoterSnapshotPerBlock> = voters + .get(page as usize) + .ok_or(FeasibilityError::Incomplete) + .map(|v| v.to_owned())?; + + let voter_cache = helpers::generate_voter_cache::(&voters_page); + let voter_at = helpers::voter_at_fn::(&voters_page); + let target_at = helpers::target_at_fn::(targets); + let voter_index = helpers::voter_index_fn_usize::(&voter_cache); + + // Then convert solution -> assignment. This will fail if any of the indices are + // gibberish. + let assignments = partial_solution + .into_assignment(voter_at, target_at) + .map_err::(Into::into)?; + + // Ensure that assignments are all correct. + let _ = assignments + .iter() + .map(|ref assignment| { + // Check that assignment.who is actually a voter (defensive-only). NOTE: while + // using the index map from `voter_index` is better than a blind linear search, + // this *still* has room for optimization. Note that we had the index when we + // did `solution -> assignment` and we lost it. Ideal is to keep the index + // around. + + // Defensive-only: must exist in the snapshot. + let snapshot_index = + voter_index(&assignment.who).ok_or(FeasibilityError::InvalidVoter)?; + // Defensive-only: index comes from the snapshot, must exist. + let (_voter, _stake, targets) = + voters_page.get(snapshot_index).ok_or(FeasibilityError::InvalidVoter)?; + debug_assert!(*_voter == assignment.who); + + // Check that all of the targets are valid based on the snapshot. + if assignment.distribution.iter().any(|(t, _)| !targets.contains(t)) { + return Err(FeasibilityError::InvalidVote) + } + Ok(()) + }) + .collect::>()?; + + // ----- Start building support. First, we need one more closure. + let stake_of = helpers::stake_of_fn::(&voters_page, &voter_cache); + + // This might fail if the normalization fails. Very unlikely. See `integrity_test`. + let staked_assignments = + sp_npos_elections::assignment_ratio_to_staked_normalized(assignments, stake_of) + .map_err::(Into::into)?; + + let supports = sp_npos_elections::to_supports(&staked_assignments); + + // Check the maximum number of backers per winner. If this is a single-page solution, this + // is enough to check `MaxBackersPerWinner`. Else, this is just a heuristic, and needs to be + // checked again at the end (via `QueuedSolutionBackings`). + ensure!( + supports + .iter() + .all(|(_, s)| (s.voters.len() as u32) <= T::MaxBackersPerWinner::get()), + FeasibilityError::TooManyBackings + ); + + // supports per page must not be higher than the desired targets, otherwise final solution + // will also be higher than desired_targets. + ensure!((supports.len() as u32) <= desired_targets, FeasibilityError::WrongWinnerCount); + + // almost-defensive-only: `MaxBackersPerWinner` is already checked. A sane value of + // `MaxWinnersPerPage` should be more than any possible value of `desired_targets()`, which + // is ALSO checked, so this conversion can almost never fail. + let bounded_supports = supports + .try_into_bounded_supports() + .map_err(|_| FeasibilityError::WrongWinnerCount)?; + + Ok(bounded_supports) + } + + /// Greedily reduce the size of the solution to fit into the block w.r.t length. + /// + /// The length of the solution is largely a function of the number of voters. The number of + /// winners cannot be changed Thus, to reduce the solution size, we need to strip voters. + /// + /// Note that this solution is already computed, and winners are elected based on the merit of + /// the total stake in the system. Nevertheless, some of the voters may be removed here. + /// + /// Sometimes, removing a voter can cause a validator to also be implicitly removed, if + /// that voter was the only backer of that winner. In such cases, this solution is invalid, + /// which will be caught prior to submission. + /// + /// The score must be computed **after** this step. If this step reduces the score too much, + /// then the solution must be discarded. + pub fn trim_assignments_length( + max_allowed_length: u32, + assignments: &mut Vec>, + encoded_size_of: impl Fn( + &[IndexAssignmentOf], + ) -> Result, + ) -> Result { + // Perform a binary search for the max subset of which can fit into the allowed + // length. Having discovered that, we can truncate efficiently. + let max_allowed_length: usize = max_allowed_length.saturated_into(); + let mut high = assignments.len(); + let mut low = 0; + + // not much we can do if assignments are already empty. + if high == low { + return Ok(0) + } + + while high - low > 1 { + let test = (high + low) / 2; + if encoded_size_of(&assignments[..test])? <= max_allowed_length { + low = test; + } else { + high = test; + } + } + let maximum_allowed_voters = if low < assignments.len() && + encoded_size_of(&assignments[..low + 1])? <= max_allowed_length + { + low + 1 + } else { + low + }; + + // ensure our post-conditions are correct + //debug_assert!( + // encoded_size_of(&assignments[..maximum_allowed_voters]).unwrap() <= max_allowed_length + //); + debug_assert!(if maximum_allowed_voters < assignments.len() { + encoded_size_of(&assignments[..maximum_allowed_voters + 1]).unwrap() > + max_allowed_length + } else { + true + }); + + // NOTE: before this point, every access was immutable. + // after this point, we never error. + // check before edit. + + let remove = assignments.len().saturating_sub(maximum_allowed_voters); + assignments.truncate(maximum_allowed_voters); + + Ok(remove) + } + + /// Greedily reduce the size of the solution to fit into the block w.r.t. weight. + /// + /// The weight of the solution is foremost a function of the number of voters (i.e. + /// `assignments.len()`). Aside from this, the other components of the weight are invariant. The + /// number of winners shall not be changed (otherwise the solution is invalid) and the + /// `ElectionSize` is merely a representation of the total number of stakers. + /// + /// Thus, we reside to stripping away some voters from the `assignments`. + /// + /// Note that the solution is already computed, and the winners are elected based on the merit + /// of the entire stake in the system. Nonetheless, some of the voters will be removed further + /// down the line. + /// + /// Indeed, the score must be computed **after** this step. If this step reduces the score too + /// much or remove a winner, then the solution must be discarded **after** this step. + pub fn trim_assignments_weight( + desired_targets: u32, + size: PageSize, + max_weight: Weight, + assignments: &mut Vec>, + ) -> usize { + let maximum_allowed_voters = + Self::maximum_voter_for_weight(desired_targets, size, max_weight); + let removing: usize = + assignments.len().saturating_sub(maximum_allowed_voters.saturated_into()); + assignments.truncate(maximum_allowed_voters as usize); + + removing + } + + /// Find the maximum `len` that a solution can have in order to fit into the block weight. + /// + /// This only returns a value between zero and `size.nominators`. + pub fn maximum_voter_for_weight( + _desired_winners: u32, + size: PageSize, + max_weight: Weight, + ) -> u32 { + if size.voters < 1 { + return size.voters + } + + let max_voters = size.voters.max(1); + let mut voters = max_voters; + + // helper closures. + let weight_with = |_active_voters: u32| -> Weight { + Weight::zero() // TODO + }; + + let next_voters = |current_weight: Weight, voters: u32, step: u32| -> Result { + if current_weight.all_lt(max_weight) { + let next_voters = voters.checked_add(step); + match next_voters { + Some(voters) if voters < max_voters => Ok(voters), + _ => Err(()), + } + } else if current_weight.any_gt(max_weight) { + voters.checked_sub(step).ok_or(()) + } else { + // If any of the constituent weights is equal to the max weight, we're at max + Ok(voters) + } + }; + + // First binary-search the right amount of voters + let mut step = voters / 2; + let mut current_weight = weight_with(voters); + + while step > 0 { + match next_voters(current_weight, voters, step) { + // proceed with the binary search + Ok(next) if next != voters => { + voters = next; + }, + // we are out of bounds, break out of the loop. + Err(()) => break, + // we found the right value - early exit the function. + Ok(next) => return next, + } + step /= 2; + current_weight = weight_with(voters); + } + + // Time to finish. We might have reduced less than expected due to rounding error. Increase + // one last time if we have any room left, the reduce until we are sure we are below limit. + while voters < max_voters && weight_with(voters + 1).all_lt(max_weight) { + voters += 1; + } + while voters.checked_sub(1).is_some() && weight_with(voters).any_gt(max_weight) { + voters -= 1; + } + + let final_decision = voters.min(size.voters); + debug_assert!( + weight_with(final_decision).all_lte(max_weight), + "weight_with({}) <= {}", + final_decision, + max_weight, + ); + final_decision + } +} + +/// Errors associated with the off-chain worker miner. +#[derive( + frame_support::DebugNoBound, frame_support::EqNoBound, frame_support::PartialEqNoBound, +)] +pub enum OffchainMinerError { + Miner(MinerError), + PoolSubmissionFailed, + NotUnsignedPhase, + StorageError, + PageOutOfBounds, + Snapshots, +} + +impl From for OffchainMinerError { + fn from(e: MinerError) -> Self { + OffchainMinerError::Miner(e) + } +} + +/// A miner used in the context of the offchain worker for unsigned submissions. +pub(crate) struct OffchainWorkerMiner(sp_std::marker::PhantomData); + +impl OffchainWorkerMiner { + /// The off-chain storage lock to work with unsigned submissions. + pub(crate) const OFFCHAIN_LOCK: &'static [u8] = b"parity/multi-block-unsigned-election/lock"; + + /// The off-chain storage ID prefix for each of the solution's pages. Each page will be + /// prefixed by this ID, followed by the page index. The full page ID for a given index can be + /// generated by [`Self::page_cache_id`]. + pub(crate) const OFFCHAIN_CACHED_SOLUTION: &'static [u8] = + b"parity/multi-block-unsigned-election/solution"; + + /// The off-chain storage ID for the solution's full score. + pub(crate) const OFFCHAIN_CACHED_SCORE: &'static [u8] = + b"parity/multi-block-unsigned-election/score"; + + /// Mine a solution. + /// + /// Mines a new solution with [`crate::Pallet::Pages`] pages and computes the partial score + /// of the page with `page` index. + pub fn mine( + page: PageIndex, + ) -> Result< + (ElectionScore, ElectionScore, ::Solution), + OffchainMinerError, + > { + let reduce = true; + + let (all_voter_pages, all_targets) = Self::fetch_snapshots()?; + let round = crate::Pallet::::current_round(); + let desired_targets = + <::DataProvider as ElectionDataProvider>::desired_targets() + .map_err(|_| MinerError::DataProvider)?; + + let (solution, _trimming_status) = + Miner::::mine_paged_solution_with_snapshot( + &all_voter_pages, + &all_targets, + T::Pages::get(), + round, + desired_targets, + reduce, + )?; + + let partial_solution = solution + .solution_pages + .get(page as usize) + .ok_or(OffchainMinerError::PageOutOfBounds)?; + + let partial_score = Miner::::compute_partial_score( + &all_voter_pages, + &all_targets, + &partial_solution, + desired_targets, + page, + )?; + + Ok((solution.score, partial_score, partial_solution.clone())) + } + + pub(crate) fn fetch_snapshots() -> Result< + (VoterSnapshotPagedOf, TargetSnaphsotOf), + OffchainMinerError, + > { + // prepare range to fetch all pages of the target and voter snapshot. + let paged_range = 0..EPM::::msp() + 1; + + // fetch all pages of the voter snapshot and collect them in a bounded vec. + let all_voter_pages: BoundedVec<_, T::Pages> = paged_range + .map(|page| { + Snapshot::::voters(page) + .ok_or(MinerError::SnapshotUnAvailable(SnapshotType::Voters(page))) + }) + .collect::, _>>()? + .try_into() + .expect("range was constructed from the bounded vec bounds; qed."); + + // fetch all pages of the target snapshot and collect them in a bounded vec. + let all_targets = Snapshot::::targets() + .ok_or(MinerError::SnapshotUnAvailable(SnapshotType::Targets))?; + + Ok((all_voter_pages, all_targets)) + } + + /// Fetches from the local storage or mines a new solution. + /// + /// Calculates and returns the partial score of paged solution of the given `page` index. + pub fn fetch_or_mine( + page: PageIndex, + ) -> Result< + (ElectionScore, ElectionScore, ::Solution), + OffchainMinerError, + > { + let cache_id = Self::paged_cache_id(page)?; + let score_storage = StorageValueRef::persistent(&Self::OFFCHAIN_CACHED_SCORE); + let maybe_storage = StorageValueRef::persistent(&cache_id); + + let (full_score, paged_solution, partial_score) = + if let Ok(Some((solution_page, partial_score))) = + maybe_storage.get::<(::Solution, ElectionScore)>() + { + sublog!(debug, "unsigned::ocw-miner", "offchain restoring a solution from cache."); + + let full_score = score_storage + .get() + .map_err(|_| OffchainMinerError::StorageError)? + .ok_or(OffchainMinerError::StorageError)?; + + (full_score, solution_page, partial_score) + } else { + // no solution cached, compute it first. + sublog!(debug, "unsigned::ocw-miner", "offchain miner computing a new solution."); + + // fetch snapshots. + let (all_voter_pages, all_targets) = Self::fetch_snapshots()?; + let round = crate::Pallet::::current_round(); + let desired_targets = + <::DataProvider as ElectionDataProvider>::desired_targets() + .map_err(|_| MinerError::DataProvider)?; + + let reduce = false; // TODO + + let (solution, _trimming_status) = + Miner::::mine_paged_solution_with_snapshot( + &all_voter_pages, + &all_targets, + T::Pages::get(), + round, + desired_targets, + reduce, + )?; + + // caches the solution score. + score_storage + .mutate::<_, (), _>(|_| Ok(solution.score.clone())) + .map_err(|_| OffchainMinerError::StorageError)?; + + let mut solution_page = Default::default(); + let mut partial_score_r: ElectionScore = Default::default(); + + // caches each of the individual pages and their partial score under its own key. + for (idx, paged_solution) in solution.solution_pages.into_iter().enumerate() { + let partial_score = Miner::::compute_partial_score( + &all_voter_pages, + &all_targets, + &paged_solution, + desired_targets, + idx as u32, + )?; + + let cache_id = Self::paged_cache_id(idx as PageIndex)?; + let storage = StorageValueRef::persistent(&cache_id); + storage + .mutate::<_, (), _>(|_| Ok((paged_solution.clone(), partial_score))) + .map_err(|_| OffchainMinerError::StorageError)?; + + // save to return the requested paged solution and partial score. + if idx as PageIndex == page { + solution_page = paged_solution; + partial_score_r = partial_score; + } + } + (solution.score, solution_page, partial_score_r) + }; + + Ok((full_score, partial_score, paged_solution)) + } + + /// Clears all local storage items related to the unsigned off-chain miner. + pub(crate) fn clear_cache() { + let mut score_storage = StorageValueRef::persistent(&Self::OFFCHAIN_CACHED_SCORE); + score_storage.clear(); + + for idx in (0..::Pages::get()).into_iter() { + let cache_id = Self::paged_cache_id(idx as PageIndex) + .expect("page index was calculated based on the msp."); + let mut page_storage = StorageValueRef::persistent(&cache_id); + + page_storage.clear(); + } + + sublog!(debug, "unsigned", "offchain miner cache cleared."); + } + + /// Generate the page cache ID based on the `page` index and the + /// [`Self::OFFCHAIN_CACHED_SOLUTION`] prefix. + fn paged_cache_id(page: PageIndex) -> Result, OffchainMinerError> { + let mut id = Self::OFFCHAIN_CACHED_SOLUTION.to_vec(); + id.push(page.try_into().map_err(|_| OffchainMinerError::PageOutOfBounds)?); + Ok(id) + } + + /// Submits a paged solution through the [`Call::submit_page_unsigned`] callable as an + /// inherent. + pub(crate) fn submit_paged_call( + page: PageIndex, + solution: ::Solution, + partial_score: ElectionScore, + claimed_full_score: ElectionScore, + ) -> Result<(), OffchainMinerError> { + sublog!( + debug, + "unsigned::ocw-miner", + "miner submitting a solution as an unsigned transaction, page: {:?}", + page, + ); + + let call = Call::submit_page_unsigned { page, solution, partial_score, claimed_full_score }; + frame_system::offchain::SubmitTransaction::>::submit_unsigned_transaction( + call.into(), + ) + .map(|_| { + sublog!( + debug, + "unsigned::ocw-miner", + "miner submitted a solution as an unsigned transaction, page {:?}", + page + ); + }) + .map_err(|_| OffchainMinerError::PoolSubmissionFailed) + } +} diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs b/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs new file mode 100644 index 0000000000000..a675907e13e49 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs @@ -0,0 +1,360 @@ +// 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. + +//! # Unsigned sub-pallet +//! +//! The main goal of this sub-pallet is to manage the unsigned phase submissions by an off-chain +//! worker. It implements the `offchain_worker` hook which will compute and store +//! in the off-chain cache a paged solution and try to submit it if: +//! +//! - Current phase is [`crate::Phase::Unsigned`]; +//! - The score of the computed solution is better than the minimum score defined by the verifier +//! pallet and the current election score stored by the [`crate::signed::Pallet`]. +//! +//! During the unsigned phase, multiple block builders will collaborate to submit the full +//! solution, one page per block. +//! +//! ## Sync/Async off-chain worker +//! +//! The unsigned phase relies on a mix of sync and async checks to ensure that the paged unsigned +//! submissions (and final solution) are correct, namely: +//! +//! - Synchronous checks: each block builder will compute the *full* election solution. However, +//! only one page +//! is verified through the [Verifier::verify_synchronous] and submitted through the +//! [`Call::submit_page_unsigned`] callable as an inherent. +//! - Asynchronous checks: once all pages are submitted, the [`Call::submit_page_unsigned`] will +//! call [`verifier::AsyncVerifier::force_finalize_verification`] to ensure that the full solution +//! submitted by all the block builders is good. +//! +//! In sum, each submitted page is verified using the synchronous verification implemented by the +//! verifier pallet (i.e. [`verifier::Verifier::verify_synchronous`]). The pages are submitted by +//! order from [`crate::Pallet::msp`] down to [`crate::Pallet::lsp`]. After successfully submitting +//! the last page, the [`verifier::AsyncVerifier::force_finalize_verification`], which will perform +//! the last feasibility checks over the full stored solution. +//! +//! At each block of the unsigned phase, the block builder running the node with the off-chain +//! worker enabled will compute a solution based on the round's snapshot. The solution is pagified +//! and stored in the local cache. +//! +//! The off-chain miner will *always* compute a new solution regardless of whether there +//! is a queued solution for the current era. The solution will be added to the storage through the +//! inherent [`Call::submit_page_unsigned`] only if the computed (total) solution score is strictly +//! better than the current queued solution. + +pub mod miner; +pub mod weights; + +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarking; + +#[cfg(test)] +mod tests; + +use crate::{ + unsigned::{ + miner::{OffchainMinerError, OffchainWorkerMiner}, + weights::WeightInfo, + }, + verifier, Phase, SolutionOf, Verifier, +}; +use frame_election_provider_support::PageIndex; +use frame_support::{ + ensure, + pallet_prelude::{TransactionValidity, ValidTransaction}, + traits::Get, +}; +use frame_system::{offchain::SendTransactionTypes, pallet_prelude::BlockNumberFor}; +use sp_npos_elections::ElectionScore; +use sp_runtime::SaturatedConversion; + +// public re-exports. +pub use pallet::{ + Call, Config, Event, Pallet, __substrate_call_check, __substrate_event_check, + __substrate_validate_unsigned_check, tt_default_parts, tt_default_parts_v2, tt_error_token, +}; + +#[frame_support::pallet(dev_mode)] +pub(crate) mod pallet { + + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::{ + ensure_none, + pallet_prelude::{BlockNumberFor, OriginFor}, + }; + + #[pallet::config] + #[pallet::disable_frame_system_supertrait_check] + pub trait Config: crate::Config + SendTransactionTypes> { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The off-chain worker interval between retrying to submit a solution. + type OffchainRepeatInterval: Get>; + + /// The priority of the unsigned tx submitted. + type MinerTxPriority: Get; + + /// Maximum length of the solution that the miner is allowed to generate. + /// + /// Solutions are trimmed to respect this. + type MaxLength: Get; + + /// Maximum weight of the solution that the miner is allowed to generate. + /// + /// Solutions are trimmed to respect this. + /// + /// The weight is computed using `solution_weight`. + type MaxWeight: Get; + + /// The weights for this pallet. + type WeightInfo: WeightInfo; + } + + #[pallet::pallet] + pub struct Pallet(PhantomData); + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Unsigned solution submitted successfully. + UnsignedSolutionSubmitted { at: BlockNumberFor, page: PageIndex }, + } + + #[pallet::validate_unsigned] + impl ValidateUnsigned for Pallet { + type Call = Call; + + fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { + if let Call::submit_page_unsigned { page, partial_score, .. } = call { + ValidTransaction::with_tag_prefix("ElectionOffchainWorker") + // priority increases propotional to the `solution.minimal_stake`. + .priority( + T::MinerTxPriority::get() + .saturating_add(partial_score.minimal_stake.saturated_into()), + ) + // deduplicates unsigned solutions since each validator should calculate at most + // one paged solution per block. + .and_provides(page) + // transaction stays in the pool as long as the unsigned phase. + .longevity(T::UnsignedPhase::get().saturated_into::()) + .propagate(false) + .build() + } else { + sublog!(info, "unsigned", "validate_unsigned ERROR"); + InvalidTransaction::Call.into() + } + } + } + + #[pallet::call] + impl Pallet { + /// Submit a paged unsigned solution. + /// + /// The dispatch origin fo this call must be __none__. + /// + /// This submission is checked on the fly. Moreover, this unsigned solution is only + /// validated when submitted to the pool from the **local** node. Effectively, this means + /// that only active validators can submit this transaction when authoring a block (similar + /// to an inherent). + /// + /// To prevent any incorrect solution (and thus wasted time/weight), this transaction will + /// panic if the solution submitted by the validator is invalid in any way, effectively + /// putting their authoring reward at risk. + /// + /// No deposit or reward is associated with this submission. + #[pallet::call_index(1)] + #[pallet::weight(::WeightInfo::submit_page_unsigned( + ::MaxBackersPerWinner::get(), + ::MaxWinnersPerPage::get(), + ))] + pub fn submit_page_unsigned( + origin: OriginFor, + page: PageIndex, + solution: SolutionOf, + partial_score: ElectionScore, + claimed_full_score: ElectionScore, + ) -> DispatchResult { + ensure_none(origin)?; + let error_message = "Invalid unsigned submission must produce invalid block and \ + deprive validator from their authoring reward."; + + sublog!( + info, + "unsigned", + "submitting page {:?} with partial score {:?}", + page, + partial_score + ); + + // Check if score is an improvement, the current phase, page index and other paged + // solution metadata checks. + Self::pre_dispatch_checks(page, &claimed_full_score).expect(error_message); + + // The verifier will store the paged solution, if valid. + let _ = ::verify_synchronous( + solution, + partial_score, + page, + ) + .expect(error_message); + + // if all pages have been submitted, request an async verification finalization which + // will work on the queued paged solutions. + if ::next_missing_solution_page().is_none() { + ::force_finalize_verification( + claimed_full_score, + ) + .expect(error_message); + sublog!(info, "unsigned", "validate_unsigned last page verify OK"); + } else { + sublog!(info, "unsigned", "submit_page_unsigned: page {:?} submitted", page); + } + + Self::deposit_event(Event::UnsignedSolutionSubmitted { + at: >::block_number(), + page, + }); + + Ok(()) + } + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(_n: BlockNumberFor) -> Weight { + if crate::Pallet::::current_phase() == Phase::Off { + T::DbWeight::get().reads_writes(1, 1) + } else { + Default::default() + } + } + + /// The off-chain worker implementation + /// + /// The off-chain worker for this pallet will run IFF: + /// + /// - It can obtain the off-chain worker lock; + /// - The current block is part of the unsigned phase; + fn offchain_worker(now: BlockNumberFor) { + use sp_runtime::offchain::storage_lock::{BlockAndTime, StorageLock}; + + let mut lock = + StorageLock::>>::with_block_deadline( + miner::OffchainWorkerMiner::::OFFCHAIN_LOCK, + T::UnsignedPhase::get().saturated_into(), + ); + + if crate::Pallet::::current_phase().is_unsigned() { + match lock.try_lock() { + Ok(_guard) => { + sublog!(info, "unsigned", "obtained offchain lock at {:?}", now); + let _ = Self::do_sync_offchain_worker(now).map_err(|e| { + sublog!(debug, "unsigned", "offchain worker error."); + e + }); + }, + Err(deadline) => { + sublog!( + debug, + "unsigned", + "offchain worker lock not released, deadline is {:?}", + deadline + ); + }, + }; + } + } + + fn integrity_test() { + // TODO + } + + #[cfg(feature = "try-runtime")] + fn try_state(_n: BlockNumberFor) -> Result<(), sp_runtime::TryRuntimeError> { + todo!() + } + } +} + +impl Pallet { + /// Perform the off-chain worker workload. + /// + /// If the current block is part of the unsigned phase and there are missing solution pages: + /// + /// 1. Compute or restore a mined solution; + /// 2. Pagify the solution; + /// 3. Calculate the partial score for the page to submit; + /// 4. Verify if the *total* solution is strictly better than the current queued solution or + /// better than the minimum score, of no queued solution exists. + /// 5. Submits the paged solution as an inherent through the [`Call::submit_page_unsigned`] + /// callable. + pub fn do_sync_offchain_worker(_now: BlockNumberFor) -> Result<(), OffchainMinerError> { + let missing_solution_page = ::next_missing_solution_page(); + + match (crate::Pallet::::current_phase(), missing_solution_page) { + (Phase::Unsigned(_), Some(page)) => { + let (full_score, partial_score, partial_solution) = + OffchainWorkerMiner::::fetch_or_mine(page).map_err(|err| { + sublog!(error, "unsigned", "OCW mine error: {:?}", err); + err + })?; + + // submit page only if full score improves the current queued score. + if ::ensure_score_improves(full_score) { + OffchainWorkerMiner::::submit_paged_call( + page, + partial_solution, + partial_score, + full_score, + )?; + } else { + sublog!( + debug, + "unsigned", + "unsigned solution with score {:?} does not improve current queued solution; skip it.", + full_score + ); + } + }, + (Phase::Export(_), _) | (Phase::Unsigned(_), None) => { + // Unsigned phase is over or unsigned solution is no more required, clear the + // cache. + OffchainWorkerMiner::::clear_cache(); + }, + _ => (), // nothing to do here. + } + + Ok(()) + } + + /// Ihnerent pre-dispatch checks. + pub(crate) fn pre_dispatch_checks( + page: PageIndex, + claimed_full_score: &ElectionScore, + ) -> Result<(), ()> { + // timing and metadata checks. + ensure!(crate::Pallet::::current_phase().is_unsigned(), ()); + ensure!(page <= crate::Pallet::::msp(), ()); + + // full solution score check. + ensure!(::ensure_score_improves(*claimed_full_score), ()); + + Ok(()) + } +} diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/tests.rs b/substrate/frame/election-provider-multi-block/src/unsigned/tests.rs new file mode 100644 index 0000000000000..b37cc6bf43459 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/unsigned/tests.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. + +use super::*; +use crate::{ + mock::*, unsigned::miner::Config, PagedTargetSnapshot, PagedVoterSnapshot, Phase, Snapshot, + Verifier, +}; + +use frame_election_provider_support::ElectionProvider; +use frame_support::assert_ok; + +mod calls { + use super::*; + + #[test] + fn unsigned_submission_works() { + let (mut ext, pool) = ExtBuilder::default().build_offchainify(0); + ext.execute_with(|| { + // election predicted at 30. + assert_eq!(election_prediction(), 30); + + // no solution available until the unsigned phase. + assert!(::queued_score().is_none()); + assert!(::get_queued_solution(2).is_none()); + + // progress through unsigned phase just before the election. + roll_to_with_ocw(29, Some(pool.clone())); + + // successful submission events for all 3 pages, as expected. + assert_eq!( + unsigned_events(), + [ + Event::UnsignedSolutionSubmitted { at: 19, page: 2 }, + Event::UnsignedSolutionSubmitted { at: 20, page: 1 }, + Event::UnsignedSolutionSubmitted { at: 21, page: 0 } + ] + ); + // now, solution exists. + assert!(::queued_score().is_some()); + assert!(::get_queued_solution(2).is_some()); + assert!(::get_queued_solution(1).is_some()); + assert!(::get_queued_solution(0).is_some()); + + // roll to election prediction bn. + roll_to_with_ocw(election_prediction(), Some(pool.clone())); + + // still in unsigned phase (after unsigned submissions have been submitted and before + // the election happened). + assert!(current_phase().is_unsigned()); + + // elect() works as expected. + assert!(call_elect().is_ok()); + + assert_eq!(current_phase(), Phase::Off); + }) + } + + #[test] + fn unsigned_submission_no_snapshot() { + let (mut ext, pool) = ExtBuilder::default().build_offchainify(1); + ext.execute_with(|| { + // election predicted at 30. + assert_eq!(election_prediction(), 30); + + roll_to_phase_with_ocw(Phase::Signed, Some(pool.clone())); + + // no solution available until the unsigned phase. + assert!(::queued_score().is_none()); + assert!(::get_queued_solution(2).is_none()); + + // but snapshot exists. + assert!(PagedVoterSnapshot::::get(crate::Pallet::::lsp()).is_some()); + assert!(PagedTargetSnapshot::::get(crate::Pallet::::lsp()).is_some()); + // so let's clear it. + clear_snapshot(); + assert!(PagedVoterSnapshot::::get(crate::Pallet::::lsp()).is_none()); + assert!(PagedTargetSnapshot::::get(crate::Pallet::::lsp()).is_none()); + + // progress through unsigned phase just before the election. + roll_to_with_ocw(29, Some(pool.clone())); + + // snapshot was not available, so unsigned submissions and thus no solution queued. + assert_eq!(unsigned_events().len(), 0); + // no solution available until the unsigned phase. + assert!(::queued_score().is_none()); + assert!(::get_queued_solution(2).is_none()); + + // call elect (which fails) to restart the phase. + assert!(call_elect().is_err()); + assert_eq!(current_phase(), Phase::Off); + + roll_to_phase_with_ocw(Phase::Signed, Some(pool.clone())); + + // snapshot exists now. + assert!(PagedVoterSnapshot::::get(crate::Pallet::::lsp()).is_some()); + assert!(PagedTargetSnapshot::::get(crate::Pallet::::lsp()).is_some()); + + roll_to_with_ocw(election_prediction() - 1, Some(pool.clone())); + + // successful submission events for all 3 pages, as expected. + assert_eq!( + unsigned_events(), + [ + Event::UnsignedSolutionSubmitted { at: 49, page: 2 }, + Event::UnsignedSolutionSubmitted { at: 50, page: 1 }, + Event::UnsignedSolutionSubmitted { at: 51, page: 0 } + ] + ); + // now, solution exists. + assert!(::queued_score().is_some()); + assert!(::get_queued_solution(2).is_some()); + assert!(::get_queued_solution(1).is_some()); + assert!(::get_queued_solution(0).is_some()); + + // elect() works as expected. + assert_ok!(::elect(2)); + assert_ok!(::elect(1)); + assert_ok!(::elect(0)); + + assert_eq!(current_phase(), Phase::Off); + }) + } +} + +mod miner { + use super::*; + + type OffchainSolver = ::Solver; + + #[test] + fn snapshot_idx_based_works() { + ExtBuilder::default().build_and_execute(|| { + roll_to_phase(Phase::Signed); + + let mut all_voter_pages = vec![]; + let mut all_target_pages = vec![]; + + for page in (0..Pages::get()).rev() { + all_voter_pages.push(Snapshot::::voters(page).unwrap()); + all_target_pages.push(Snapshot::::targets().unwrap()); + } + }) + } + + #[test] + fn desired_targets_bounds_works() { + ExtBuilder::default() + .max_winners_per_page(3) + .desired_targets(3) + .build_and_execute(|| { + // max winner per page == desired_targets, OK. + compute_snapshot_checked(); + assert_ok!(mine_and_verify_all()); + + // max winner per page > desired_targets, OK. + MaxWinnersPerPage::set(4); + compute_snapshot_checked(); + assert_ok!(mine_and_verify_all()); + + // max winner per page < desired_targets, fails. + MaxWinnersPerPage::set(2); + compute_snapshot_checked(); + assert!(mine_and_verify_all().is_err()); + }) + } + + #[test] + fn fetch_or_mine() { + let (mut ext, _) = ExtBuilder::default().build_offchainify(1); + + ext.execute_with(|| { + let msp = crate::Pallet::::msp(); + assert_eq!(msp, 2); + + // no snapshot available, calling mine_paged_solution should fail. + assert!(::queued_score().is_none()); + assert!(::get_queued_solution(msp).is_none()); + + assert!(OffchainWorkerMiner::::fetch_or_mine(0).is_err()); + compute_snapshot_checked(); + + let (full_score_2, partial_score_2, _) = + OffchainWorkerMiner::::fetch_or_mine(msp).unwrap(); + let (full_score_1, partial_score_1, _) = + OffchainWorkerMiner::::fetch_or_mine(msp - 1).unwrap(); + let (full_score_0, partial_score_0, _) = + OffchainWorkerMiner::::fetch_or_mine(0).unwrap(); + + assert!(full_score_2 == full_score_1 && full_score_2 == full_score_0); + assert!( + full_score_2.sum_stake == full_score_1.sum_stake && + full_score_2.sum_stake == full_score_0.sum_stake + ); + + assert_eq!( + partial_score_0.sum_stake + partial_score_1.sum_stake + partial_score_2.sum_stake, + full_score_0.sum_stake + ); + }) + } +} diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/weights.rs b/substrate/frame/election-provider-multi-block/src/unsigned/weights.rs new file mode 100644 index 0000000000000..fc15d96bbae4e --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/unsigned/weights.rs @@ -0,0 +1,85 @@ + +//! Autogenerated weights for `pallet_epm_unsigned` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-08-02, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `gpestanas-MBP.lan`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: 1024 + +// Executed Command: +// /Users/gpestana/cargo_target/debug/staking-node +// benchmark +// pallet +// --chain +// dev +// --pallet +// pallet-epm-unsigned +// --extrinsic +// * +// --steps +// 2 +// --repeat +// 1 +// --output +// unsigned_weights.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +pub trait WeightInfo { + fn submit_page_unsigned(v: u32, t: u32) -> Weight; +} + +/// Weight functions for `pallet_epm_unsigned`. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: `ElectionProviderMultiBlock::CurrentPhase` (r:1 w:0) + /// Proof: `ElectionProviderMultiBlock::CurrentPhase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ElectionVerifierPallet::QueuedSolutionScore` (r:1 w:0) + /// Proof: `ElectionVerifierPallet::QueuedSolutionScore` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ElectionVerifierPallet::MinimumScore` (r:1 w:0) + /// Proof: `ElectionVerifierPallet::MinimumScore` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ElectionProviderMultiBlock::PagedTargetSnapshot` (r:1 w:0) + /// Proof: `ElectionProviderMultiBlock::PagedTargetSnapshot` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ElectionProviderMultiBlock::PagedVoterSnapshot` (r:1 w:0) + /// Proof: `ElectionProviderMultiBlock::PagedVoterSnapshot` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Staking::ValidatorCount` (r:1 w:0) + /// Proof: `Staking::ValidatorCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `ElectionVerifierPallet::QueuedValidVariant` (r:1 w:0) + /// Proof: `ElectionVerifierPallet::QueuedValidVariant` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ElectionVerifierPallet::QueuedSolutionY` (r:0 w:1) + /// Proof: `ElectionVerifierPallet::QueuedSolutionY` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ElectionVerifierPallet::LastStoredPage` (r:0 w:1) + /// Proof: `ElectionVerifierPallet::LastStoredPage` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ElectionVerifierPallet::QueuedSolutionBackings` (r:0 w:1) + /// Proof: `ElectionVerifierPallet::QueuedSolutionBackings` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `v` is `[32, 1024]`. + /// The range of component `t` is `[512, 2048]`. + fn submit_page_unsigned(v: u32, t: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `11869 + t * (10 ±0) + v * (71 ±0)` + // Estimated: `15334 + t * (10 ±0) + v * (71 ±0)` + // Minimum execution time: 1_382_000_000 picoseconds. + Weight::from_parts(3_157_322_580, 0) + .saturating_add(Weight::from_parts(0, 15334)) + // Standard Error: 80_316 + .saturating_add(Weight::from_parts(4_146_169, 0).saturating_mul(v.into())) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(Weight::from_parts(0, 10).saturating_mul(t.into())) + .saturating_add(Weight::from_parts(0, 71).saturating_mul(v.into())) + } +} + + +impl WeightInfo for () { + fn submit_page_unsigned(_v: u32, _t: u32) -> Weight { + Default::default() + } +} diff --git a/substrate/frame/election-provider-multi-block/src/verifier/benchmarking.rs b/substrate/frame/election-provider-multi-block/src/verifier/benchmarking.rs new file mode 100644 index 0000000000000..8ac6d05250916 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/verifier/benchmarking.rs @@ -0,0 +1,315 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Benchmarking for the Elections Multiblock Verifier sub-pallet. + +use super::*; +use crate::{ + benchmarking::helpers, + signed::pallet::Submissions, + unsigned::miner::OffchainWorkerMiner, + verifier::{AsyncVerifier, Status, Verifier}, + BenchmarkingConfig, ConfigCore, ConfigSigned, ConfigUnsigned, ConfigVerifier, PalletCore, + PalletVerifier, +}; +use frame_support::assert_ok; +use frame_system::RawOrigin; + +use frame_benchmarking::v2::*; + +#[benchmarks( + where T: ConfigCore + ConfigSigned + ConfigUnsigned + ConfigVerifier, +)] +mod benchmarks { + use super::*; + use frame_support::traits::Hooks; + + #[benchmark] + fn on_initialize_ongoing( + v: Linear< + { ::BenchmarkingConfig::VOTERS_PER_PAGE[0] }, + { ::BenchmarkingConfig::VOTERS_PER_PAGE[1] }, + >, + t: Linear< + { ::BenchmarkingConfig::TARGETS_PER_PAGE[0] }, + { ::BenchmarkingConfig::TARGETS_PER_PAGE[1] }, + >, + ) -> Result<(), BenchmarkError> { + helpers::setup_data_provider::( + ::BenchmarkingConfig::VOTERS.max(v), + ::BenchmarkingConfig::TARGETS.max(t), + ); + + if let Err(err) = helpers::setup_snapshot::(v, t) { + log!(error, "error setting up snapshot: {:?}.", err); + return Err(BenchmarkError::Stop("snapshot error")); + } + + let valid_solution = true; + let submitter = helpers::mine_and_submit::(Some(PalletCore::::msp()), valid_solution) + .map_err(|err| { + log!(error, "error mining and storing paged solutions, {:?}", err); + BenchmarkError::Stop("mine and store error") + })?; + + // page is ready for async verification. + assert!(Submissions::::get_page( + &submitter, + PalletCore::::current_round(), + PalletCore::::msp() + ) + .is_some()); + + // no backings for pages yet in storage. + assert!(PalletVerifier::::pages_backed() == 0); + + // set verifier status to pick first submitted page to verify. + as AsyncVerifier>::set_status( + Status::Ongoing(crate::Pallet::::msp()), + ); + + #[block] + { + PalletVerifier::::on_initialize(0u32.into()); + } + + // backings from submitted and verified page is in storage now + assert!(PalletVerifier::::pages_backed() == 1); + + Ok(()) + } + + #[benchmark] + fn on_initialize_ongoing_failed( + v: Linear< + { ::BenchmarkingConfig::VOTERS_PER_PAGE[0] }, + { ::BenchmarkingConfig::VOTERS_PER_PAGE[1] }, + >, + t: Linear< + { ::BenchmarkingConfig::TARGETS_PER_PAGE[0] }, + { ::BenchmarkingConfig::TARGETS_PER_PAGE[1] }, + >, + ) -> Result<(), BenchmarkError> { + helpers::setup_data_provider::( + ::BenchmarkingConfig::VOTERS.max(v), + ::BenchmarkingConfig::TARGETS.max(t), + ); + + if let Err(err) = helpers::setup_snapshot::(v, t) { + log!(error, "error setting up snapshot: {:?}.", err); + return Err(BenchmarkError::Stop("snapshot error")); + } + + let valid_solution = false; + let submitter = helpers::mine_and_submit::(Some(PalletCore::::msp()), valid_solution) + .map_err(|err| { + log!(error, "error mining and storing paged solutions, {:?}", err); + BenchmarkError::Stop("mine and store error") + })?; + + // page is ready for async verification. + assert!(Submissions::::get_page( + &submitter, + PalletCore::::current_round(), + PalletCore::::msp() + ) + .is_some()); + + // no backings for pages in storage. + assert!(PalletVerifier::::pages_backed() == 0); + + // set verifier status to pick first submitted page to verify. + as AsyncVerifier>::set_status( + Status::Ongoing(crate::Pallet::::msp()), + ); + + #[block] + { + PalletVerifier::::on_initialize(0u32.into()); + } + + // no backings for pages in storage due to failure. + assert!(PalletVerifier::::pages_backed() == 0); + + Ok(()) + } + + #[benchmark] + fn on_initialize_ongoing_finalize( + v: Linear< + { ::BenchmarkingConfig::VOTERS_PER_PAGE[0] }, + { ::BenchmarkingConfig::VOTERS_PER_PAGE[1] }, + >, + t: Linear< + { ::BenchmarkingConfig::TARGETS_PER_PAGE[0] }, + { ::BenchmarkingConfig::TARGETS_PER_PAGE[1] }, + >, + ) -> Result<(), BenchmarkError> { + helpers::setup_data_provider::( + ::BenchmarkingConfig::VOTERS.max(v), + ::BenchmarkingConfig::TARGETS.max(t), + ); + + if let Err(err) = helpers::setup_snapshot::(v, t) { + log!(error, "error setting up snapshot: {:?}.", err); + return Err(BenchmarkError::Stop("snapshot error")); + } + + // submit all pages with a valid solution. + let valid_solution = true; + let submitter = helpers::mine_and_submit::(None, valid_solution).map_err(|err| { + log!(error, "error mining and storing paged solutions, {:?}", err); + BenchmarkError::Stop("mine and store error") + })?; + + // all pages are ready for async verification. + for page in 0..T::Pages::get() { + assert!(Submissions::::get_page(&submitter, PalletCore::::current_round(), page) + .is_some()); + } + + // no backings for pages in storage. + assert!(PalletVerifier::::pages_backed() == 0); + // no queued score yet. + assert!( as Verifier>::queued_score().is_none()); + + // process all paged solutions but lsp. + for page in (1..T::Pages::get()).rev() { + as AsyncVerifier>::set_status(Status::Ongoing(page)); + Pallet::::on_initialize(0u32.into()); + } + + assert!(PalletVerifier::::pages_backed() as u32 == T::Pages::get().saturating_sub(1)); + + // set verifier status to pick last submitted page to verify. + as AsyncVerifier>::set_status(Status::Ongoing(PalletCore::::lsp())); + + #[block] + { + PalletVerifier::::on_initialize(0u32.into()); + } + + // OK, so score is queued. + assert!( as Verifier>::queued_score().is_some()); + + Ok(()) + } + + #[benchmark] + fn on_initialize_ongoing_finalize_failed( + v: Linear< + { ::BenchmarkingConfig::VOTERS_PER_PAGE[0] }, + { ::BenchmarkingConfig::VOTERS_PER_PAGE[1] }, + >, + t: Linear< + { ::BenchmarkingConfig::TARGETS_PER_PAGE[0] }, + { ::BenchmarkingConfig::TARGETS_PER_PAGE[1] }, + >, + ) -> Result<(), BenchmarkError> { + helpers::setup_data_provider::( + ::BenchmarkingConfig::VOTERS.max(v), + ::BenchmarkingConfig::TARGETS.max(t), + ); + + if let Err(err) = helpers::setup_snapshot::(v, t) { + log!(error, "error setting up snapshot: {:?}.", err); + return Err(BenchmarkError::Stop("snapshot error")); + } + + #[block] + { + let _ = 1 + 2; + } + + Ok(()) + } + + #[benchmark] + fn finalize_async_verification( + v: Linear< + { ::BenchmarkingConfig::VOTERS_PER_PAGE[0] }, + { ::BenchmarkingConfig::VOTERS_PER_PAGE[1] }, + >, + t: Linear< + { ::BenchmarkingConfig::TARGETS_PER_PAGE[0] }, + { ::BenchmarkingConfig::TARGETS_PER_PAGE[1] }, + >, + ) -> Result<(), BenchmarkError> { + helpers::setup_data_provider::( + ::BenchmarkingConfig::VOTERS.max(v), + ::BenchmarkingConfig::TARGETS.max(t), + ); + + if let Err(err) = helpers::setup_snapshot::(v, t) { + log!(error, "error setting up snapshot: {:?}.", err); + return Err(BenchmarkError::Stop("snapshot error")); + } + + #[block] + { + let _ = 1 + 2; + } + + Ok(()) + } + + #[benchmark] + fn verify_sync_paged( + v: Linear< + { ::BenchmarkingConfig::VOTERS_PER_PAGE[0] }, + { ::BenchmarkingConfig::VOTERS_PER_PAGE[1] }, + >, + t: Linear< + { ::BenchmarkingConfig::TARGETS_PER_PAGE[0] }, + { ::BenchmarkingConfig::TARGETS_PER_PAGE[1] }, + >, + ) -> Result<(), BenchmarkError> { + helpers::setup_data_provider::( + ::BenchmarkingConfig::VOTERS.max(v), + ::BenchmarkingConfig::TARGETS.max(t), + ); + + if let Err(err) = helpers::setup_snapshot::(v, t) { + log!(error, "error setting up snapshot: {:?}.", err); + return Err(BenchmarkError::Stop("snapshot error")); + } + + let (_claimed_full_score, partial_score, paged_solution) = + OffchainWorkerMiner::::mine(PalletCore::::msp()).map_err(|err| { + log!(error, "mine error: {:?}", err); + BenchmarkError::Stop("miner error") + })?; + + #[block] + { + assert_ok!(PalletVerifier::::do_verify_sync( + paged_solution, + partial_score, + PalletCore::::msp() + )); + } + + Ok(()) + } + + impl_benchmark_test_suite!( + PalletVerifier, + crate::mock::ExtBuilder::default(), + crate::mock::Runtime, + exec_name = build_and_execute + ); +} diff --git a/substrate/frame/election-provider-multi-block/src/verifier/impls.rs b/substrate/frame/election-provider-multi-block/src/verifier/impls.rs new file mode 100644 index 0000000000000..dc108ec3cdc06 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/verifier/impls.rs @@ -0,0 +1,764 @@ +// ohis 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. + +// TODO(gpestana): clean up imports. +use frame_election_provider_support::{NposSolution, PageIndex, TryIntoBoundedSupports}; +use frame_support::{ + ensure, + pallet_prelude::Weight, + traits::{Defensive, TryCollect}, + BoundedVec, +}; +use sp_runtime::{traits::Zero, Perbill}; +use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; + +use super::*; +use pallet::*; + +use crate::{helpers, unsigned::miner, verifier::weights::WeightInfo, MinerSupportsOf, SolutionOf}; + +#[frame_support::pallet(dev_mode)] +pub(crate) mod pallet { + use super::*; + use frame_support::pallet_prelude::{ValueQuery, *}; + use frame_system::pallet_prelude::*; + + #[pallet::config] + #[pallet::disable_frame_system_supertrait_check] + pub trait Config: crate::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Origin that can control this pallet. This must be a *trusted origin* since the + /// actions taken by this origin are not checked (e.g. `set_emergency_solution`). + type ForceOrigin: EnsureOrigin; + + /// Minimum improvement to a solution that defines a new solution as "better". + type SolutionImprovementThreshold: Get; + + /// Something that can provide the solution data to the verifier. + type SolutionDataProvider: crate::verifier::SolutionDataProvider< + Solution = SolutionOf, + >; + + /// The weight information of this pallet. + type WeightInfo: WeightInfo; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A verificaction failed at the given page. + VerificationFailed(PageIndex, FeasibilityError), + /// The final verifications of the `finalize_verification` failed. If this error happened, + /// all the single pages passed the feasibility checks. + FinalVerificationFailed(FeasibilityError), + /// The given page has been correctly verified, with the number of backers that are part of + /// the page. + Verified(PageIndex, u32), + /// A new solution with the given score has replaced the previous best solution, if any. + Queued(ElectionScore, Option), + /// The solution data was not available for a specific page. + SolutionDataUnavailable(PageIndex), + } + + /// A wrapper type of the storage items related to the queued solution. + /// + /// It manages the following storage types: + /// + /// - [`QueuedSolutionX`]: variant X of the queued solution. + /// - [`QueuedSolutionY`]: variant Y of the queued solution. + /// - [`QueuedValidVariant`]: pointer to which variant is the currently valid. + /// - [`QueuedSolutionScore`]: the soltution score of the current valid variant. + /// - [`QueuedSolutionBackings`]. + /// + /// Note that, as an async verification is progressing, the paged solution is kept in the + /// invalid variant storage. A solution is considered valid only when all the single page and + /// full solution checks have been perform based on the stored [`QueuedSolutionBackings`]. for + /// the corresponding in-verification solution. After the solution verification is successful, + /// the election score can be calculated and stored. + /// + /// ### Invariants + /// + /// - [`QueuedSolutionScore`] must be always the correct queued score of a variant corresponding + /// to the [`QueuedValidVariant`]. + /// - [`QueuedSolution`] must always be [`Config::SolutionImprovementThreshold`] better than + /// [`MininumScore`]. + /// - The [`QueuedSolutionBackings`] are always the backings corresponding to the *invalid* + /// variant. + pub struct QueuedSolution(sp_std::marker::PhantomData); + + impl QueuedSolution { + fn mutate_checked(mutate: impl FnOnce() -> R) -> R { + let r = mutate(); + #[cfg(debug_assertions)] + assert!(Self::sanity_check().is_ok()); + r + } + + /// Clear all relevant data of an invalid solution. + /// + /// This should be called when a solution being verified is deemed infeasible. + pub(crate) fn clear_invalid_and_backings() { + let _ = match Self::invalid() { + SolutionPointer::X => QueuedSolutionX::::clear(u32::MAX, None), + SolutionPointer::Y => QueuedSolutionY::::clear(u32::MAX, None), + }; + let _ = QueuedSolutionBackings::::clear(u32::MAX, None); + } + + /// Clear all relevant storage items. + pub(crate) fn kill() { + Self::mutate_checked(|| { + let _ = QueuedSolutionX::::clear(u32::MAX, None); + let _ = QueuedSolutionY::::clear(u32::MAX, None); + QueuedValidVariant::::kill(); + let _ = QueuedSolutionBackings::::clear(u32::MAX, None); + QueuedSolutionScore::::kill(); + }) + } + + /// Finalize a correct solution. + /// + /// It should be called at the end of the verification process of a valid solution to update + /// the queued solution score and flip the invalid variant. + pub(crate) fn finalize_solution(score: ElectionScore) { + sublog!( + info, + "verifier", + "finalizing verification of a correct solution, replacing old score {:?} with {:?}", + QueuedSolutionScore::::get(), + score + ); + + Self::mutate_checked(|| { + QueuedValidVariant::::mutate(|v| *v = v.other()); + QueuedSolutionScore::::put(score); + // TODO: should clear the invalid backings too? + }) + } + + /// Write a single page of a valid solution into the `invalid` variant of the storage. + /// + /// It should be called only once the page has been verified to be 100% correct. + pub(crate) fn set_page(page: PageIndex, supports: MinerSupportsOf) { + Self::mutate_checked(|| { + let backings: BoundedVec<_, _> = supports + .iter() + .map(|(x, s)| (x.clone(), PartialBackings {total: s.total, backers: s.voters.len() as u32})) + .try_collect() + .expect("`SupportsOf` is bounded by as Verifier>::MaxWinnersPerPage which is ensured by an integrity test; qed."); + + QueuedSolutionBackings::::insert(page, backings); + + // update the last stored page. + RemainingUnsignedPages::::mutate(|remaining| { + remaining.retain(|p| *p != page); + sublog!(debug, "verifier", "updated remaining pages, current: {:?}", remaining); + }); + + // store the new page into the invalid variant storage type. + match Self::invalid() { + SolutionPointer::X => QueuedSolutionX::::insert(page, supports), + SolutionPointer::Y => QueuedSolutionY::::insert(page, supports), + } + }) + } + + /// Computes the score and the winner count of a stored variant solution. + pub(crate) fn compute_current_score() -> Result<(ElectionScore, u32), FeasibilityError> { + // ensures that all the pages are complete; + if QueuedSolutionBackings::::iter_keys().count() != T::Pages::get() as usize { + return Err(FeasibilityError::Incomplete) + } + + let mut supports: BTreeMap = Default::default(); + for (who, PartialBackings { backers, total }) in + QueuedSolutionBackings::::iter().map(|(_, backings)| backings).flatten() + { + let entry = supports.entry(who).or_default(); + entry.total = entry.total.saturating_add(total); + entry.backers = entry.backers.saturating_add(backers); + + if entry.backers > T::MaxBackersPerWinner::get() { + return Err(FeasibilityError::TooManyBackings) + } + } + + let winners_count = supports.len() as u32; + let score = sp_npos_elections::evaluate_support( + supports.into_iter().map(|(_, backings)| backings), + ); + + Ok((score, winners_count)) + } + + /// Returns the current queued score, if any. + pub(crate) fn queued_score() -> Option { + QueuedSolutionScore::::get() + } + + /// Returns the current *valid* paged queued solution, if any. + pub(crate) fn get_queued_solution( + page: PageIndex, + ) -> Option> { + match Self::valid() { + SolutionPointer::X => QueuedSolutionX::::get(page), + SolutionPointer::Y => QueuedSolutionY::::get(page), + } + } + + /// Returns the pointer for the valid solution storage. + pub(crate) fn valid() -> SolutionPointer { + QueuedValidVariant::::get() + } + + /// Returns the pointer for the invalid solution storage. + pub(crate) fn invalid() -> SolutionPointer { + Self::valid().other() + } + + #[allow(dead_code)] + pub(crate) fn sanity_check() -> Result<(), &'static str> { + // TODO(gpestana) + Ok(()) + } + } + + /// Supports of the solution of the variant X. + /// + /// A potential valid or invalid solution may be stored in this variant during the round. + #[pallet::storage] + pub type QueuedSolutionX = + StorageMap<_, Twox64Concat, PageIndex, MinerSupportsOf>; + + /// Supports of the solution of the variant Y. + /// + /// A potential valid or invalid solution may be stored in this variant during the round. + #[pallet::storage] + pub type QueuedSolutionY = + StorageMap<_, Twox64Concat, PageIndex, MinerSupportsOf>; + + /// The `(amount, count)` of backings, keyed by page. + /// + /// This is stored to facilitate the `MaxBackersPerWinner` check at the end of an async + /// verification. Once the solution is valid (i.e. verified), the solution backings are not + /// useful anymore and can be cleared. + #[pallet::storage] + pub(crate) type QueuedSolutionBackings = StorageMap< + _, + Twox64Concat, + PageIndex, + BoundedVec<(T::AccountId, PartialBackings), T::MaxWinnersPerPage>, + >; + + /// The score of the current valid solution. + #[pallet::storage] + type QueuedSolutionScore = StorageValue<_, ElectionScore>; + + /// Pointer for the storage variant (X or Y) that stores the current valid variant. + #[pallet::storage] + type QueuedValidVariant = StorageValue<_, SolutionPointer, ValueQuery>; + + /// The minimum score that each solution must have to be considered feasible. + #[pallet::storage] + pub(crate) type MinimumScore = StorageValue<_, ElectionScore>; + + /// Current status of the verification process. + #[pallet::storage] + pub(crate) type VerificationStatus = StorageValue<_, Status, ValueQuery>; + + // For unsigned page solutions only. + #[pallet::storage] + pub(crate) type RemainingUnsignedPages = StorageValue<_, Vec, ValueQuery>; + + #[pallet::pallet] + pub struct Pallet(PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(n: BlockNumberFor) -> Weight { + Self::do_on_initialize(n) + } + + fn integrity_test() { + // TODO(gpestana): add more integrity tests related to queued solution et al. + assert_eq!(T::MaxWinnersPerPage::get(), ::MaxWinnersPerPage::get()); + assert_eq!( + T::MaxBackersPerWinner::get(), + ::MaxBackersPerWinner::get() + ); + } + + #[cfg(feature = "try-runtime")] + fn try_state(_n: BlockNumberFor) -> Result<(), sp_runtime::TryRuntimeError> { + Self::do_try_state() + } + } +} + +impl Verifier for Pallet { + type AccountId = T::AccountId; + type Solution = SolutionOf; + type MaxWinnersPerPage = T::MaxWinnersPerPage; + type MaxBackersPerWinner = T::MaxBackersPerWinner; + + fn set_minimum_score(score: ElectionScore) { + MinimumScore::::put(score); + } + + fn queued_score() -> Option { + QueuedSolution::::queued_score() + } + + fn ensure_score_improves(claimed_score: ElectionScore) -> bool { + Self::ensure_score_quality(claimed_score).is_ok() + } + + fn get_queued_solution(page_index: PageIndex) -> Option> { + QueuedSolution::::get_queued_solution(page_index) + } + + fn next_missing_solution_page() -> Option { + let next_missing = RemainingUnsignedPages::::get().last().copied(); + sublog!(debug, "verifier", "next missing page: {:?}", next_missing); + + next_missing + } + + fn kill() { + QueuedSolution::::kill(); + >::put(Status::Nothing); + } + + fn verify_synchronous( + partial_solution: Self::Solution, + partial_score: ElectionScore, + page: PageIndex, + ) -> Result, FeasibilityError> { + let maybe_current_score = Self::queued_score(); + match Self::do_verify_sync(partial_solution, partial_score, page) { + Ok(supports) => { + sublog!( + info, + "verifier", + "queued sync solution with score {:?} (page {:?})", + partial_score, + page + ); + Self::deposit_event(Event::::Verified(page, supports.len() as u32)); + Self::deposit_event(Event::::Queued(partial_score, maybe_current_score)); + Ok(supports) + }, + Err(err) => { + sublog!( + info, + "verifier", + "sync verification failed with {:?} (page: {:?})", + err, + page + ); + Self::deposit_event(Event::::VerificationFailed(page, err.clone())); + Err(err) + }, + } + } + + fn feasibility_check( + solution: Self::Solution, + page: PageIndex, + ) -> Result, FeasibilityError> { + let targets = + crate::Snapshot::::targets().ok_or(FeasibilityError::SnapshotUnavailable)?; + + // prepare range to fetch all pages of the target and voter snapshot. + let paged_range = 0..crate::Pallet::::msp() + 1; + + // fetch all pages of the voter snapshot and collect them in a bounded vec. + let all_voter_pages: BoundedVec<_, T::Pages> = paged_range + .map(|page| { + crate::Snapshot::::voters(page).ok_or(FeasibilityError::SnapshotUnavailable) + }) + .collect::, _>>()? + .try_into() + .expect("range was constructed from the bounded vec bounds; qed."); + + let desired_targets = + crate::Snapshot::::desired_targets().ok_or(FeasibilityError::SnapshotUnavailable)?; + + miner::Miner::::feasibility_check_partial( + &all_voter_pages, + &targets, + solution, + desired_targets, + page, + ) + } +} + +impl AsyncVerifier for Pallet { + type SolutionDataProvider = T::SolutionDataProvider; + + fn force_finalize_verification(claimed_score: ElectionScore) -> Result<(), FeasibilityError> { + Self::finalize_async_verification(claimed_score) + } + + fn status() -> Status { + VerificationStatus::::get() + } + + fn start() -> Result<(), &'static str> { + if let Status::Nothing = Self::status() { + let claimed_score = Self::SolutionDataProvider::get_score().unwrap_or_default(); + + if Self::ensure_score_quality(claimed_score).is_err() { + Self::deposit_event(Event::::VerificationFailed( + crate::Pallet::::msp(), + FeasibilityError::ScoreTooLow, + )); + // report to the solution data provider that the page verification failed. + T::SolutionDataProvider::report_result(VerificationResult::Rejected); + // despite the verification failed, this was a successful `start` operation. + Ok(()) + } else { + VerificationStatus::::put(Status::Ongoing(crate::Pallet::::msp())); + Ok(()) + } + } else { + sublog!(warn, "verifier", "tries to start election while ongoing, ignored."); + Err("verification ongoing") + } + } + + fn stop() { + sublog!(warn, "verifier", "stop signal received. clearing everything."); + // TODO(gpestana): debug asserts + QueuedSolution::::clear_invalid_and_backings(); + + // if a verification is ongoing, signal the solution rejection to the solution data + // provider and reset the current status. + VerificationStatus::::mutate(|status| { + if matches!(status, Status::Ongoing(_)) { + T::SolutionDataProvider::report_result(VerificationResult::Rejected); + }; + *status = Status::Nothing; + }); + } + + // Sets current verifications status. + #[cfg(any(test, feature = "runtime-benchmarks"))] + fn set_status(status: Status) { + VerificationStatus::::put(status); + } +} + +impl Pallet { + fn do_on_initialize(_now: crate::BlockNumberFor) -> Weight { + let max_backers_winner = T::MaxBackersPerWinner::get(); + let max_winners_page = T::MaxWinnersPerPage::get(); + + match crate::Pallet::::current_phase() { + // reset remaining unsigned pages after snapshot is created. + crate::Phase::Snapshot(page) if page == crate::Pallet::::lsp() => { + RemainingUnsignedPages::::put( + (crate::Pallet::::lsp()..crate::Pallet::::msp() + 1).collect::>(), + ); + + sublog!( + debug, + "verifier", + "reset remaining unsgined pages to {:?}", + RemainingUnsignedPages::::get() + ); + }, + _ => (), + } + + if let Status::Ongoing(current_page) = >::get() { + let maybe_page_solution = + ::get_paged_solution(current_page); + + if maybe_page_solution.is_none() { + sublog!( + error, + "verifier", + "T::SolutionDataProvider failed to deliver page {} at {:?}.", + current_page, + crate::Pallet::::current_phase(), + ); + // reset election data and notify the `T::SolutionDataProvider`. + QueuedSolution::::clear_invalid_and_backings(); + VerificationStatus::::put(Status::Nothing); + T::SolutionDataProvider::report_result(VerificationResult::DataUnavailable); + + Self::deposit_event(Event::::SolutionDataUnavailable(current_page)); + + return ::WeightInfo::on_initialize_ongoing_failed( + max_backers_winner, + max_winners_page, + ); + } + + let page_solution = maybe_page_solution.expect("page solution checked to exist; qed."); + let maybe_supports = Self::feasibility_check(page_solution, current_page); + + // TODO: can refator out some of these code blocks to clean up the code. + let weight_consumed = match maybe_supports { + Ok(supports) => { + Self::deposit_event(Event::::Verified(current_page, supports.len() as u32)); + QueuedSolution::::set_page(current_page, supports); + + if current_page > crate::Pallet::::lsp() { + // election didn't finish, tick forward. + VerificationStatus::::put(Status::Ongoing( + current_page.saturating_sub(1), + )); + ::WeightInfo::on_initialize_ongoing( + max_backers_winner, + max_winners_page, + ) + } else { + // last page, finalize everything. At this point, the solution data + // provider should have a score ready for us. Otherwise, a default score + // will reset the whole election which is the desired behaviour. + let claimed_score = + T::SolutionDataProvider::get_score().defensive_unwrap_or_default(); + + // reset the election status. + VerificationStatus::::put(Status::Nothing); + + match Self::finalize_async_verification(claimed_score) { + Ok(_) => { + T::SolutionDataProvider::report_result(VerificationResult::Queued); + ::WeightInfo::on_initialize_ongoing_finalize( + max_backers_winner, + max_winners_page, + ) + }, + Err(_) => { + T::SolutionDataProvider::report_result( + VerificationResult::Rejected, + ); + // kill the solution in case of error. + QueuedSolution::::clear_invalid_and_backings(); + ::WeightInfo::on_initialize_ongoing_finalize_failed( + max_backers_winner, + max_winners_page, + ) + }, + } + } + }, + Err(err) => { + // the paged solution is invalid. + Self::deposit_event(Event::::VerificationFailed(current_page, err)); + VerificationStatus::::put(Status::Nothing); + QueuedSolution::::clear_invalid_and_backings(); + T::SolutionDataProvider::report_result(VerificationResult::Rejected); + + // TODO: may need to be a differnt another branch. + ::WeightInfo::on_initialize_ongoing_finalize_failed( + max_backers_winner, + max_winners_page, + ) + }, + }; + + weight_consumed + } else { + // nothing to do yet. + // TOOD(return weight reads=1) + Default::default() + } + } + + pub(crate) fn do_verify_sync( + partial_solution: SolutionOf, + partial_score: ElectionScore, + page: PageIndex, + ) -> Result, FeasibilityError> { + let _ = Self::ensure_score_quality(partial_score)?; + let supports = Self::feasibility_check(partial_solution.clone(), page)?; + + // TODO: implement fn evaluate on `BondedSupports`; remove extra clone. + let real_score = sp_npos_elections::evaluate_support( + supports.clone().into_iter().map(|(_, backings)| backings), + ); + ensure!(real_score == partial_score, FeasibilityError::InvalidScore); + + // queue valid solution of single page. + QueuedSolution::::set_page(page, supports.clone()); + + Ok(supports) + } + + pub(crate) fn finalize_async_verification( + claimed_score: ElectionScore, + ) -> Result<(), FeasibilityError> { + let outcome = QueuedSolution::::compute_current_score() + .and_then(|(final_score, winner_count)| { + let desired_targets = crate::Snapshot::::desired_targets().unwrap_or_default(); + + match (final_score == claimed_score, winner_count <= desired_targets) { + (true, true) => { + QueuedSolution::::finalize_solution(final_score); + Self::deposit_event(Event::::Queued( + final_score, + QueuedSolution::::queued_score(), + )); + + Ok(()) + }, + (false, true) => Err(FeasibilityError::InvalidScore), + (true, false) => Err(FeasibilityError::WrongWinnerCount), + (false, false) => Err(FeasibilityError::InvalidScore), + } + }) + .map_err(|err| { + sublog!(warn, "verifier", "finalizing the solution was invalid due to {:?}", err); + Self::deposit_event(Event::::VerificationFailed(Zero::zero(), err.clone())); + err + }); + + sublog!(debug, "verifier", "finalize verification outcome: {:?}", outcome); + outcome + } + + pub fn ensure_score_quality(score: ElectionScore) -> Result<(), FeasibilityError> { + let is_improvement = ::queued_score().map_or(true, |best_score| { + score.strict_threshold_better(best_score, T::SolutionImprovementThreshold::get()) + }); + ensure!(is_improvement, FeasibilityError::ScoreTooLow); + + let is_greater_than_min_trusted = MinimumScore::::get() + .map_or(true, |min_score| score.strict_threshold_better(min_score, Perbill::zero())); + ensure!(is_greater_than_min_trusted, FeasibilityError::ScoreTooLow); + + Ok(()) + } + + pub(crate) fn feasibility_check_old( + partial_solution: SolutionOf, + page: PageIndex, + ) -> Result, FeasibilityError> { + // Read the corresponding snapshots. + let snapshot_targets = + crate::Snapshot::::targets().ok_or(FeasibilityError::SnapshotUnavailable)?; + let snapshot_voters = + crate::Snapshot::::voters(page).ok_or(FeasibilityError::SnapshotUnavailable)?; + + let voter_cache = helpers::generate_voter_cache::(&snapshot_voters); + let voter_at = helpers::voter_at_fn::(&snapshot_voters); + let target_at = helpers::target_at_fn::(&snapshot_targets); + let voter_index = helpers::voter_index_fn_usize::(&voter_cache); + + // Then convert solution -> assignment. This will fail if any of the indices are + // gibberish. + let assignments = partial_solution + .into_assignment(voter_at, target_at) + .map_err::(Into::into)?; + + // Ensure that assignments are all correct. + let _ = assignments + .iter() + .map(|ref assignment| { + // Check that assignment.who is actually a voter (defensive-only). NOTE: while + // using the index map from `voter_index` is better than a blind linear search, + // this *still* has room for optimization. Note that we had the index when we + // did `solution -> assignment` and we lost it. Ideal is to keep the index + // around. + + // Defensive-only: must exist in the snapshot. + let snapshot_index = + voter_index(&assignment.who).ok_or(FeasibilityError::InvalidVoter)?; + // Defensive-only: index comes from the snapshot, must exist. + let (_voter, _stake, targets) = + snapshot_voters.get(snapshot_index).ok_or(FeasibilityError::InvalidVoter)?; + debug_assert!(*_voter == assignment.who); + + // Check that all of the targets are valid based on the snapshot. + if assignment.distribution.iter().any(|(t, _)| !targets.contains(t)) { + return Err(FeasibilityError::InvalidVote) + } + Ok(()) + }) + .collect::>()?; + + // ----- Start building support. First, we need one more closure. + let stake_of = helpers::stake_of_fn::(&snapshot_voters, &voter_cache); + + // This might fail if the normalization fails. Very unlikely. See `integrity_test`. + let staked_assignments = + sp_npos_elections::assignment_ratio_to_staked_normalized(assignments, stake_of) + .map_err::(Into::into)?; + + let supports = sp_npos_elections::to_supports(&staked_assignments); + + // Check the maximum number of backers per winner. If this is a single-page solution, this + // is enough to check `MaxBackersPerWinner`. Else, this is just a heuristic, and needs to be + // checked again at the end (via `QueuedSolutionBackings`). + ensure!( + supports + .iter() + .all(|(_, s)| (s.voters.len() as u32) <= T::MaxBackersPerWinner::get()), + FeasibilityError::TooManyBackings + ); + + // Ensure some heuristics. These conditions must hold in the **entire** support, this is + // just a single page. But, they must hold in a single page as well. + let desired_targets = + crate::Snapshot::::desired_targets().ok_or(FeasibilityError::SnapshotUnavailable)?; + + // supports per page must not be higher than the desired targets, otherwise final solution + // will also be higher than desired_targets. + ensure!((supports.len() as u32) <= desired_targets, FeasibilityError::WrongWinnerCount); + + // almost-defensive-only: `MaxBackersPerWinner` is already checked. A sane value of + // `MaxWinnersPerPage` should be more than any possible value of `desired_targets()`, which + // is ALSO checked, so this conversion can almost never fail. + let bounded_supports = supports.try_into_bounded_supports().map_err(|e| { + log!(info, "ERR: {:?}", e); + FeasibilityError::WrongWinnerCount + })?; + + Ok(bounded_supports) + } + + /// Returns the number backings/pages verified and stored. + #[cfg(any(test, feature = "runtime-benchmarks"))] + pub(crate) fn pages_backed() -> usize { + QueuedSolutionBackings::::iter_keys().count() + } +} + +#[cfg(feature = "try-runtime")] +impl Pallet { + pub(crate) fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> { + Self::check_variants() + } + + /// Invariants: + /// + /// 1. The valid and invalid solution pointers are always different. + fn check_variants() -> Result<(), sp_runtime::TryRuntimeError> { + ensure!( + QueuedSolution::::valid() != QueuedSolution::::invalid(), + "valid and invalid solution pointers are the same" + ); + Ok(()) + } +} diff --git a/substrate/frame/election-provider-multi-block/src/verifier/mod.rs b/substrate/frame/election-provider-multi-block/src/verifier/mod.rs new file mode 100644 index 0000000000000..8b7e095ac6371 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/verifier/mod.rs @@ -0,0 +1,287 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Verifier sub-pallet +//! +//! This pallet implements the NPoS solution verification logic. It supports both synchronous and +//! asynchronous verification of paged solutions. Moreover, it manages and ultimately stores +//! the best correct solution in a round, which can be requested by the election provider at the +//! time of the election. +//! +//! The paged solution data to be verified async is retrieved through the +//! [`Config::SolutionDataProvider`] implementor which most likely is the signed pallet. +//! +//! ## Feasibility check +//! +//! The goal of the feasibility of a solution is to ensure that a provided +//! [`crate::Config::Solution`] is correct based on the voter and target snapshots of a given round +//! kept by the parent pallet. The correctness of a solution is defined as: +//! +//! - The edges of a solution (voter -> targets) match the expected by the current snapshot. This +//! check can be performed at the page level. +//! - The total number of winners in the solution is sufficient. This check can only be performed +//! when the full paged solution is available;; +//! - The election score is higher than the expected minimum score. This check can only be performed +//! when the full paged solution is available;; +//! - All of the bounds of the election are respected, namely: +//! * [`Verifier::MaxBackersPerWinner`] - which set the total max of voters are backing a target, +//! per election. This check can only be performed when the full paged solution is available; +//! * [`Verifier::MaxWinnersPerPage`] - which ensure that a paged solution has a bound on the +//! number of targets. This check can be performed at the page level. +//! +//! Some checks can be performed at the page level (e.g. correct edges check) while others can only +//! be performed when the full solution is available. +//! +//! ## Sync and Async verification modes +//! +//! 1. Single-page, synchronous verification. This mode is used when a single page needs to be +//! verified on the fly, e.g. unsigned submission. +//! 2. Multi-page, asynchronous verification. This mode is used in the context of the multi-paged +//! signed solutions. +//! +//! The [`crate::verifier::Verifier`] and [`crate::verifier::AsyncVerifier`] traits define the +//! interface of each of the verification modes and this pallet implements both traits. +//! +//! ## Queued solution +//! +//! Once a solution has been succefully verified, it is stored in a queue. This pallet implements +//! the [`SolutionDataProvider`] trait which allows the parent pallet to request a correct +//! solution for the current round. + +mod impls; +pub mod weights; + +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarking; + +#[cfg(test)] +mod tests; + +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::traits::Get; +use sp_npos_elections::{ElectionScore, ExtendedBalance}; +use sp_runtime::RuntimeDebug; + +// public re-exports. +pub use impls::pallet::{ + Call, Config, Event, Pallet, __substrate_call_check, __substrate_event_check, tt_default_parts, + tt_default_parts_v2, tt_error_token, +}; + +use crate::{PageIndex, SupportsOf}; + +/// Errors related to the solution feasibility checks. +#[derive(Debug, Eq, PartialEq, codec::Encode, codec::Decode, scale_info::TypeInfo, Clone)] +pub enum FeasibilityError { + /// Election score is too low to be accepted. + ScoreTooLow, + /// Ongoing verification was not completed. + Incomplete, + /// Solution exceeds the number of backers per winner for at least one winner. + TooManyBackings, + /// Solution exceeds the number of winners. + WrongWinnerCount, + /// Snapshot is not available. + SnapshotUnavailable, + /// A voter is invalid. + InvalidVoter, + /// A vote is invalid. + InvalidVote, + /// Solution with an invalid score. + InvalidScore, + /// Internal election error. + #[codec(skip)] + NposElection(sp_npos_elections::Error), +} + +impl From for FeasibilityError { + fn from(err: sp_npos_elections::Error) -> Self { + FeasibilityError::NposElection(err) + } +} + +/// The status of this pallet. +/// +/// This pallet is either processing an async verification or doing nothing. A single page +/// verification can only be done while the pallet has status [`Status::Nothing`]. +#[derive(Encode, Decode, scale_info::TypeInfo, Clone, Copy, MaxEncodedLen, RuntimeDebug)] +pub enum Status { + /// A paged solution is ongoing and the next page to be verified is indicated in the inner + /// value. + Ongoing(PageIndex), + /// Nothing is happening. + Nothing, +} + +impl Default for Status { + fn default() -> Self { + Status::Nothing + } +} + +/// Pointer to the current valid solution of `QueuedSolution`. +#[derive(Encode, Decode, scale_info::TypeInfo, Clone, Copy, MaxEncodedLen, Debug, PartialEq)] +pub enum SolutionPointer { + X, + Y, +} + +impl Default for SolutionPointer { + fn default() -> Self { + SolutionPointer::X + } +} + +impl SolutionPointer { + pub fn other(&self) -> SolutionPointer { + match *self { + SolutionPointer::X => SolutionPointer::Y, + SolutionPointer::Y => SolutionPointer::X, + } + } +} + +/// A type that represents a *partial* backing of a winner. It does not contain the +/// supports normally associated with a list of backings. +#[derive(Debug, Default, Encode, Decode, MaxEncodedLen, scale_info::TypeInfo)] +pub struct PartialBackings { + /// Total backing of a particular winner. + total: ExtendedBalance, + /// Number of backers. + backers: u32, +} + +impl sp_npos_elections::Backings for PartialBackings { + fn total(&self) -> ExtendedBalance { + self.total + } +} + +/// The interface of something that can verify solutions for election in a multi-block context. +pub trait Verifier { + /// The account ID type. + type AccountId; + + /// The solution type; + type Solution; + + /// Maximum number of winners that a page supports. + /// + /// Note: This must always be greater or equal to `T::DataProvider::desired_targets()`. + type MaxWinnersPerPage: Get; + + /// Maximum number of backers that each winner can have. + type MaxBackersPerWinner: Get; + + /// Sets the minimum score that an election must have from now on. + fn set_minimum_score(score: ElectionScore); + + /// Fetches the current queued election score, if any. + /// + /// Returns `None` if not score is queued. + fn queued_score() -> Option; + + /// Check if a claimed score improves the current queued score. + fn ensure_score_improves(claimed_score: ElectionScore) -> bool; + + /// Returns the next missing solution page. + fn next_missing_solution_page() -> Option; + + /// Clears all the storage items related to the verifier pallet. + fn kill(); + + /// Get a single page of the best verified solutions, if any. + fn get_queued_solution(page_index: PageIndex) -> Option>; + + /// Perform the feasibility check on a given single-page solution. + /// + /// This will perform: + /// 1. feasibility-check + /// 2. claimed score is correct and it is an improvements + /// 3. check if bounds are correct + /// 4. store the solution if all checks pass + fn verify_synchronous( + partial_solution: Self::Solution, + claimed_score: ElectionScore, + page: PageIndex, + ) -> Result, FeasibilityError>; + + /// Just perform a single-page feasibility-check, based on the standards of this pallet. + /// + /// No score check is part of this. + fn feasibility_check( + partial_solution: Self::Solution, + page: PageIndex, + ) -> Result, FeasibilityError>; +} + +/// Something that can verify a solution asynchronously. +pub trait AsyncVerifier: Verifier { + /// The data provider that can provide the candidate solution to verify. The result of the + /// verification is returned back to this entity. + type SolutionDataProvider: SolutionDataProvider; + + /// Forces finalizing the async verification. + fn force_finalize_verification(claimed_score: ElectionScore) -> Result<(), FeasibilityError>; + + /// Returns the status of the current verification. + fn status() -> Status; + + /// Start a verification process. + fn start() -> Result<(), &'static str>; // new error type? + + /// Stop the verification. + /// + /// An implementation must ensure that all related state and storage items are cleaned. + fn stop(); + + /// Sets current status. Only used for benchmarks and tests. + #[cfg(any(test, feature = "runtime-benchmarks"))] + fn set_status(status: Status); +} + +/// Encapsulates the result of the verification of a candidate solution. +#[derive(Clone, Copy, RuntimeDebug)] +#[cfg_attr(test, derive(PartialEq, Eq))] +pub enum VerificationResult { + /// Solution is valid and is queued. + Queued, + /// Solution is rejected, for whichever of the multiple reasons that it could be. + Rejected, + /// The data needed (solution pages or the score) was unavailable. This should rarely happen. + DataUnavailable, +} + +/// Something that provides paged solution data for the verifier. +/// +/// This can be implemented by [`crate::signed::Pallet`] where signed solutions are queued and +/// sorted based on the solution's score. +pub trait SolutionDataProvider { + // The solution type. + type Solution; + + /// Returns the `page`th page of the current best solution that the data provider has in store, + /// if it exists. Otherwise it returns `None`. + fn get_paged_solution(page: PageIndex) -> Option; + + /// Get the claimed score of the current best solution. + fn get_score() -> Option; + + /// Hook to report back the results of the verification of the current candidate solution that + /// is being exposed via [`Self::get_paged_solution`] and [`Self::get_score`]. + fn report_result(result: VerificationResult); +} diff --git a/substrate/frame/election-provider-multi-block/src/verifier/tests.rs b/substrate/frame/election-provider-multi-block/src/verifier/tests.rs new file mode 100644 index 0000000000000..66b4696edf4a7 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/verifier/tests.rs @@ -0,0 +1,121 @@ +// 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::{ + mock::*, + verifier::{impls::pallet::*, *}, + Phase, +}; +use frame_support::assert_noop; +use sp_npos_elections::ElectionScore; + +mod solution { + use super::*; + + #[test] + fn variant_flipping_works() { + ExtBuilder::default().build_and_execute(|| { + assert!(QueuedSolution::::valid() != QueuedSolution::::invalid()); + + let valid_before = QueuedSolution::::valid(); + let invalid_before = valid_before.other(); + + let mock_score = ElectionScore { minimal_stake: 10, ..Default::default() }; + + // queue solution and flip variant. + QueuedSolution::::finalize_solution(mock_score); + + // solution has been queued + assert_eq!(QueuedSolution::::queued_score().unwrap(), mock_score); + // variant has flipped. + assert_eq!(QueuedSolution::::valid(), invalid_before); + assert_eq!(QueuedSolution::::invalid(), valid_before); + }) + } +} + +mod feasibility_check { + use super::*; + + #[test] + fn winner_indices_page_in_bounds() { + ExtBuilder::default().pages(1).desired_targets(2).build_and_execute(|| { + roll_to_phase(Phase::Signed); + let mut solution = mine_full(1).unwrap(); + assert_eq!(crate::Snapshot::::targets().unwrap().len(), 8); + + // swap all votes from 3 to 4 to invalidate index 4. + solution.solution_pages[0] + .votes1 + .iter_mut() + .filter(|(_, t)| *t == TargetIndex::from(3u16)) + .for_each(|(_, t)| *t += 1); + + assert_noop!( + VerifierPallet::feasibility_check(solution.solution_pages[0].clone(), 0), + FeasibilityError::InvalidVote, + ); + }) + } +} + +mod sync_verifier { + use super::*; + + #[test] + fn sync_verifier_simple_works() { + ExtBuilder::default().build_and_execute(|| {}) + } + + #[test] + fn next_missing_solution_works() { + ExtBuilder::default().build_and_execute(|| { + let supports: SupportsOf> = Default::default(); + let msp = crate::Pallet::::msp(); + assert!(msp == ::Pages::get() - 1 && msp == 2); + + // run to snapshot phase to reset `RemainingUnsignedPages`. + roll_to_phase(Phase::Snapshot(crate::Pallet::::lsp())); + + // msp page is the next missing. + assert_eq!(::next_missing_solution_page(), Some(msp)); + + // X is the current valid solution, let's work with it. + assert_eq!(QueuedSolution::::valid(), SolutionPointer::X); + + // set msp and check the next missing page again. + QueuedSolution::::set_page(msp, supports.clone()); + assert_eq!(::next_missing_solution_page(), Some(msp - 1)); + + QueuedSolution::::set_page(msp - 1, supports.clone()); + assert_eq!(::next_missing_solution_page(), Some(0)); + + // set last page, missing page after is None as solution is complete. + QueuedSolution::::set_page(0, supports.clone()); + assert_eq!(::next_missing_solution_page(), None); + }) + } +} + +mod async_verifier { + use super::*; + + #[test] + fn async_verifier_simple_works() { + ExtBuilder::default().build_and_execute(|| {}) + } +} diff --git a/substrate/frame/election-provider-multi-block/src/verifier/weights.rs b/substrate/frame/election-provider-multi-block/src/verifier/weights.rs new file mode 100644 index 0000000000000..1fd78c0df43c4 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/verifier/weights.rs @@ -0,0 +1,249 @@ + +//! Autogenerated weights for `pallet_epm_verifier` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-08-06, STEPS: `3`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `gpestanas-MBP.Home`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 + +// Executed Command: +// /Users/gpestana/cargo_target/debug/staking-node +// benchmark +// pallet +// --wasm-execution +// compiled +// --pallet +// pallet-epm-verifier +// --extrinsic +// * +// --steps +// 3 +// --repeat +// 1 +// --output +// epm_verifier_weights.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +pub trait WeightInfo { + fn on_initialize_ongoing(v: u32, t: u32) -> Weight; + fn on_initialize_ongoing_failed(v: u32, t: u32) -> Weight; + fn on_initialize_ongoing_finalize(v: u32, t: u32) -> Weight; + fn on_initialize_ongoing_finalize_failed(v: u32, t: u32) -> Weight; + fn finalize_async_verification(v: u32, t: u32, ) -> Weight; + fn verify_sync_paged(v: u32, t: u32, ) -> Weight; +} + +/// Weight functions for `pallet_epm_verifier`. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: `ElectionVerifierPallet::VerificationStatus` (r:1 w:1) + /// Proof: `ElectionVerifierPallet::VerificationStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ElectionProviderMultiBlock::Round` (r:1 w:0) + /// Proof: `ElectionProviderMultiBlock::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ElectionSignedPallet::SortedScores` (r:1 w:0) + /// Proof: `ElectionSignedPallet::SortedScores` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ElectionSignedPallet::SubmissionStorage` (r:1 w:0) + /// Proof: `ElectionSignedPallet::SubmissionStorage` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ElectionProviderMultiBlock::PagedTargetSnapshot` (r:1 w:0) + /// Proof: `ElectionProviderMultiBlock::PagedTargetSnapshot` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ElectionProviderMultiBlock::PagedVoterSnapshot` (r:1 w:0) + /// Proof: `ElectionProviderMultiBlock::PagedVoterSnapshot` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Staking::ValidatorCount` (r:1 w:0) + /// Proof: `Staking::ValidatorCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `ElectionVerifierPallet::QueuedValidVariant` (r:1 w:0) + /// Proof: `ElectionVerifierPallet::QueuedValidVariant` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ElectionVerifierPallet::QueuedSolutionY` (r:0 w:1) + /// Proof: `ElectionVerifierPallet::QueuedSolutionY` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ElectionVerifierPallet::LastStoredPage` (r:0 w:1) + /// Proof: `ElectionVerifierPallet::LastStoredPage` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ElectionVerifierPallet::QueuedSolutionBackings` (r:0 w:1) + /// Proof: `ElectionVerifierPallet::QueuedSolutionBackings` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `v` is `[32, 1024]`. + /// The range of component `t` is `[512, 2048]`. + fn on_initialize_ongoing(v: u32, t: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `12992 + t * (26 ±0) + v * (80 ±0)` + // Estimated: `15414 + t * (27 ±1) + v * (80 ±2)` + // Minimum execution time: 2_036_000_000 picoseconds. + Weight::from_parts(2_036_000_000, 0) + .saturating_add(Weight::from_parts(0, 15414)) + // Standard Error: 3_307_370 + .saturating_add(Weight::from_parts(20_614_626, 0).saturating_mul(v.into())) + // Standard Error: 1_618_727 + .saturating_add(Weight::from_parts(1_324_037, 0).saturating_mul(t.into())) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(Weight::from_parts(0, 27).saturating_mul(t.into())) + .saturating_add(Weight::from_parts(0, 80).saturating_mul(v.into())) + } + /// Storage: `ElectionVerifierPallet::VerificationStatus` (r:1 w:1) + /// Proof: `ElectionVerifierPallet::VerificationStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ElectionProviderMultiBlock::Round` (r:1 w:0) + /// Proof: `ElectionProviderMultiBlock::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ElectionSignedPallet::SortedScores` (r:1 w:1) + /// Proof: `ElectionSignedPallet::SortedScores` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ElectionSignedPallet::SubmissionStorage` (r:1 w:1) + /// Proof: `ElectionSignedPallet::SubmissionStorage` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ElectionProviderMultiBlock::PagedTargetSnapshot` (r:1 w:0) + /// Proof: `ElectionProviderMultiBlock::PagedTargetSnapshot` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ElectionProviderMultiBlock::PagedVoterSnapshot` (r:1 w:0) + /// Proof: `ElectionProviderMultiBlock::PagedVoterSnapshot` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ElectionVerifierPallet::QueuedValidVariant` (r:1 w:0) + /// Proof: `ElectionVerifierPallet::QueuedValidVariant` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ElectionSignedPallet::SubmissionMetadataStorage` (r:1 w:1) + /// Proof: `ElectionSignedPallet::SubmissionMetadataStorage` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ElectionProviderMultiBlock::CurrentPhase` (r:1 w:0) + /// Proof: `ElectionProviderMultiBlock::CurrentPhase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// The range of component `v` is `[32, 1024]`. + /// The range of component `t` is `[512, 2048]`. + fn on_initialize_ongoing_failed(v: u32, _t: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0 + t * (4 ±0) + v * (112 ±0)` + // Estimated: `7604 + v * (108 ±2)` + // Minimum execution time: 1_034_000_000 picoseconds. + Weight::from_parts(1_576_541_397, 0) + .saturating_add(Weight::from_parts(0, 7604)) + // Standard Error: 296_982 + .saturating_add(Weight::from_parts(3_076_310, 0).saturating_mul(v.into())) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(Weight::from_parts(0, 108).saturating_mul(v.into())) + } + /// Storage: `ElectionVerifierPallet::VerificationStatus` (r:1 w:1) + /// Proof: `ElectionVerifierPallet::VerificationStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ElectionProviderMultiBlock::Round` (r:1 w:0) + /// Proof: `ElectionProviderMultiBlock::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ElectionSignedPallet::SortedScores` (r:1 w:0) + /// Proof: `ElectionSignedPallet::SortedScores` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ElectionSignedPallet::SubmissionStorage` (r:1 w:0) + /// Proof: `ElectionSignedPallet::SubmissionStorage` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ElectionProviderMultiBlock::PagedTargetSnapshot` (r:1 w:0) + /// Proof: `ElectionProviderMultiBlock::PagedTargetSnapshot` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ElectionProviderMultiBlock::PagedVoterSnapshot` (r:1 w:0) + /// Proof: `ElectionProviderMultiBlock::PagedVoterSnapshot` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Staking::ValidatorCount` (r:1 w:0) + /// Proof: `Staking::ValidatorCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `ElectionVerifierPallet::QueuedValidVariant` (r:1 w:1) + /// Proof: `ElectionVerifierPallet::QueuedValidVariant` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ElectionVerifierPallet::QueuedSolutionBackings` (r:3 w:1) + /// Proof: `ElectionVerifierPallet::QueuedSolutionBackings` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ElectionVerifierPallet::QueuedSolutionScore` (r:1 w:1) + /// Proof: `ElectionVerifierPallet::QueuedSolutionScore` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ElectionVerifierPallet::QueuedSolutionY` (r:0 w:1) + /// Proof: `ElectionVerifierPallet::QueuedSolutionY` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ElectionVerifierPallet::LastStoredPage` (r:0 w:1) + /// Proof: `ElectionVerifierPallet::LastStoredPage` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// The range of component `v` is `[32, 1024]`. + /// The range of component `t` is `[512, 2048]`. + fn on_initialize_ongoing_finalize(v: u32, t: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0 + t * (41 ±0) + v * (125 ±0)` + // Estimated: `79043 + t * (10 ±8) + v * (85 ±17)` + // Minimum execution time: 1_724_000_000 picoseconds. + Weight::from_parts(1_466_010_752, 0) + .saturating_add(Weight::from_parts(0, 79043)) + // Standard Error: 199_409 + .saturating_add(Weight::from_parts(3_322_580, 0).saturating_mul(v.into())) + // Standard Error: 128_785 + .saturating_add(Weight::from_parts(128_906, 0).saturating_mul(t.into())) + .saturating_add(T::DbWeight::get().reads(12)) + .saturating_add(T::DbWeight::get().writes(6)) + .saturating_add(Weight::from_parts(0, 10).saturating_mul(t.into())) + .saturating_add(Weight::from_parts(0, 85).saturating_mul(v.into())) + } + /// The range of component `v` is `[32, 1024]`. + /// The range of component `t` is `[512, 2048]`. + fn on_initialize_ongoing_finalize_failed(_v: u32, _t: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_000_000 picoseconds. + Weight::from_parts(4_659_677, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + /// The range of component `v` is `[32, 1024]`. + /// The range of component `t` is `[512, 2048]`. + fn finalize_async_verification(v: u32, t: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_000_000 picoseconds. + Weight::from_parts(3_354_301, 0) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 2_197 + .saturating_add(Weight::from_parts(907, 0).saturating_mul(v.into())) + // Standard Error: 1_419 + .saturating_add(Weight::from_parts(65, 0).saturating_mul(t.into())) + } + /// Storage: `ElectionVerifierPallet::QueuedSolutionScore` (r:1 w:0) + /// Proof: `ElectionVerifierPallet::QueuedSolutionScore` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ElectionVerifierPallet::MinimumScore` (r:1 w:0) + /// Proof: `ElectionVerifierPallet::MinimumScore` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ElectionProviderMultiBlock::PagedTargetSnapshot` (r:1 w:0) + /// Proof: `ElectionProviderMultiBlock::PagedTargetSnapshot` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ElectionProviderMultiBlock::PagedVoterSnapshot` (r:1 w:0) + /// Proof: `ElectionProviderMultiBlock::PagedVoterSnapshot` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Staking::ValidatorCount` (r:1 w:0) + /// Proof: `Staking::ValidatorCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `ElectionVerifierPallet::QueuedValidVariant` (r:1 w:0) + /// Proof: `ElectionVerifierPallet::QueuedValidVariant` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ElectionVerifierPallet::QueuedSolutionY` (r:0 w:1) + /// Proof: `ElectionVerifierPallet::QueuedSolutionY` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ElectionVerifierPallet::LastStoredPage` (r:0 w:1) + /// Proof: `ElectionVerifierPallet::LastStoredPage` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ElectionVerifierPallet::QueuedSolutionBackings` (r:0 w:1) + /// Proof: `ElectionVerifierPallet::QueuedSolutionBackings` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `v` is `[32, 1024]`. + /// The range of component `t` is `[512, 2048]`. + fn verify_sync_paged(v: u32, t: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `15968 + t * (24 ±0) + v * (73 ±0)` + // Estimated: `18127 + t * (25 ±2) + v * (72 ±3)` + // Minimum execution time: 1_403_000_000 picoseconds. + Weight::from_parts(1_403_000_000, 0) + .saturating_add(Weight::from_parts(0, 18127)) + // Standard Error: 3_979_877 + .saturating_add(Weight::from_parts(24_084_766, 0).saturating_mul(v.into())) + // Standard Error: 1_947_873 + .saturating_add(Weight::from_parts(1_727_080, 0).saturating_mul(t.into())) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(Weight::from_parts(0, 25).saturating_mul(t.into())) + .saturating_add(Weight::from_parts(0, 72).saturating_mul(v.into())) + } +} + +impl WeightInfo for () { + fn on_initialize_ongoing(_v: u32, _t: u32) -> Weight { + Default::default() + } + + fn on_initialize_ongoing_failed(_v: u32, _t: u32) -> Weight { + Default::default() + } + + fn on_initialize_ongoing_finalize(_v: u32, _t: u32) -> Weight { + Default::default() + } + + fn on_initialize_ongoing_finalize_failed(_v: u32, _t: u32) -> Weight { + Default::default() + } + + fn finalize_async_verification(_v: u32, _t: u32, ) -> Weight { + Default::default() + } + + fn verify_sync_paged(_v: u32, _t: u32, ) -> Weight { + Default::default() + } +} + diff --git a/substrate/frame/election-provider-multi-block/src/weights.rs b/substrate/frame/election-provider-multi-block/src/weights.rs new file mode 100644 index 0000000000000..ce764ca6f80b8 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/weights.rs @@ -0,0 +1,188 @@ + +//! Autogenerated weights for `pallet_epm_core` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-08-02, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `gpestanas-MBP.lan`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: 1024 + +// Executed Command: +// /Users/gpestana/cargo_target/debug/staking-node +// benchmark +// pallet +// --chain +// dev +// --pallet +// pallet-epm-core +// --extrinsic +// * +// --steps +// 2 +// --repeat +// 1 +// --output +// core_weights.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +pub trait WeightInfo { + fn create_voters_snapshot_paged(t: u32) -> Weight; + fn create_targets_snapshot_paged(v: u32) -> Weight; + fn on_initialize_start_signed() -> Weight; + fn on_initialize_do_nothing() -> Weight; + fn on_phase_transition() -> Weight; + fn on_initialize_start_export() -> Weight; +} + +/// Weight functions for `pallet_epm_core`. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: `Staking::CounterForValidators` (r:1 w:0) + /// Proof: `Staking::CounterForValidators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::TargetSnapshotStatus` (r:1 w:1) + /// Proof: `Staking::TargetSnapshotStatus` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) + /// Storage: `Staking::Validators` (r:2049 w:0) + /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) + /// Storage: `ElectionProviderMultiBlock::PagedTargetSnapshot` (r:0 w:1) + /// Proof: `ElectionProviderMultiBlock::PagedTargetSnapshot` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `t` is `[512, 2048]`. + fn create_targets_snapshot_paged(t: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1041 + t * (46 ±0)` + // Estimated: `3510 + t * (2520 ±0)` + // Minimum execution time: 47_198_000_000 picoseconds. + Weight::from_parts(3_209_333_333, 0) + .saturating_add(Weight::from_parts(0, 3510)) + // Standard Error: 1_207_323 + .saturating_add(Weight::from_parts(86_960_937, 0).saturating_mul(t.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(t.into()))) + .saturating_add(T::DbWeight::get().writes(2)) + .saturating_add(Weight::from_parts(0, 2520).saturating_mul(t.into())) + } + /// Storage: `VoterList::CounterForListNodes` (r:1 w:0) + /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::VoterSnapshotStatus` (r:1 w:1) + /// Proof: `Staking::VoterSnapshotStatus` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) + /// Storage: `VoterList::ListBags` (r:200 w:0) + /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) + /// Storage: `VoterList::ListNodes` (r:1025 w:0) + /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) + /// Storage: `Staking::Bonded` (r:1024 w:0) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Staking::Ledger` (r:1024 w:0) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Staking::Nominators` (r:1024 w:0) + /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) + /// Storage: `Staking::Validators` (r:1000 w:0) + /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) + /// Storage: `Staking::MinimumActiveStake` (r:0 w:1) + /// Proof: `Staking::MinimumActiveStake` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `ElectionProviderMultiBlock::PagedVoterSnapshot` (r:0 w:1) + /// Proof: `ElectionProviderMultiBlock::PagedVoterSnapshot` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `v` is `[32, 1024]`. + fn create_voters_snapshot_paged(v: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `73175 + v * (946 ±0)` + // Estimated: `512390 + v * (3566 ±0)` + // Minimum execution time: 13_398_000_000 picoseconds. + Weight::from_parts(4_906_354_838, 0) + .saturating_add(Weight::from_parts(0, 512390)) + // Standard Error: 534_281 + .saturating_add(Weight::from_parts(260_582_661, 0).saturating_mul(v.into())) + .saturating_add(T::DbWeight::get().reads(208)) + .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) + .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(Weight::from_parts(0, 3566).saturating_mul(v.into())) + } + /// Storage: `ElectionProviderMultiBlock::CurrentPhase` (r:1 w:1) + /// Proof: `ElectionProviderMultiBlock::CurrentPhase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ElectionProviderMultiBlock::Round` (r:1 w:0) + /// Proof: `ElectionProviderMultiBlock::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Staking::ElectionDataLock` (r:0 w:1) + /// Proof: `Staking::ElectionDataLock` (`max_values`: Some(1), `max_size`: Some(0), added: 495, mode: `MaxEncodedLen`) + fn on_initialize_start_signed() -> Weight { + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `1561` + // Minimum execution time: 66_000_000 picoseconds. + Weight::from_parts(66_000_000, 0) + .saturating_add(Weight::from_parts(0, 1561)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `ElectionProviderMultiBlock::CurrentPhase` (r:1 w:1) + /// Proof: `ElectionProviderMultiBlock::CurrentPhase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ElectionProviderMultiBlock::Round` (r:1 w:0) + /// Proof: `ElectionProviderMultiBlock::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn on_phase_transition() -> Weight { + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `1561` + // Minimum execution time: 62_000_000 picoseconds. + Weight::from_parts(62_000_000, 0) + .saturating_add(Weight::from_parts(0, 1561)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + fn on_initialize_start_export() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_000_000 picoseconds. + Weight::from_parts(3_000_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + /// Storage: `Staking::CurrentEra` (r:1 w:0) + /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::CurrentPlannedSession` (r:1 w:0) + /// Proof: `Staking::CurrentPlannedSession` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::ErasStartSessionIndex` (r:1 w:0) + /// Proof: `Staking::ErasStartSessionIndex` (`max_values`: None, `max_size`: Some(16), added: 2491, mode: `MaxEncodedLen`) + /// Storage: `Staking::ForceEra` (r:1 w:0) + /// Proof: `Staking::ForceEra` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) + /// Storage: `ElectionProviderMultiBlock::CurrentPhase` (r:1 w:0) + /// Proof: `ElectionProviderMultiBlock::CurrentPhase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn on_initialize_do_nothing() -> Weight { + // Proof Size summary in bytes: + // Measured: `502` + // Estimated: `3481` + // Minimum execution time: 111_000_000 picoseconds. + Weight::from_parts(111_000_000, 0) + .saturating_add(Weight::from_parts(0, 3481)) + .saturating_add(T::DbWeight::get().reads(5)) + } +} + +impl WeightInfo for () { + fn create_voters_snapshot_paged(_v: u32) -> Weight { + Default::default() + } + + fn create_targets_snapshot_paged(_t: u32) -> Weight { + Default::default() + } + + fn on_initialize_start_signed() -> Weight { + Default::default() + } + + fn on_initialize_do_nothing() -> Weight { + Default::default() + } + + fn on_phase_transition() -> Weight { + Default::default() + } + + fn on_initialize_start_export() -> Weight { + Default::default() + } +} diff --git a/substrate/frame/election-provider-support/Cargo.toml b/substrate/frame/election-provider-support/Cargo.toml index cae20d1b46a48..da50d73328aac 100644 --- a/substrate/frame/election-provider-support/Cargo.toml +++ b/substrate/frame/election-provider-support/Cargo.toml @@ -24,6 +24,7 @@ sp-arithmetic = { workspace = true } sp-npos-elections = { workspace = true } sp-runtime = { workspace = true } sp-core = { workspace = true } +sp-std = { workspace = true } [dev-dependencies] rand = { features = ["small_rng"], workspace = true, default-features = true } @@ -43,6 +44,7 @@ std = [ "sp-io/std", "sp-npos-elections/std", "sp-runtime/std", + "sp-std/std", ] runtime-benchmarks = [ "frame-support/runtime-benchmarks", diff --git a/substrate/frame/election-provider-support/solution-type/src/codec.rs b/substrate/frame/election-provider-support/solution-type/src/codec.rs index 16d5f17469b7e..c1dd62fe55506 100644 --- a/substrate/frame/election-provider-support/solution-type/src/codec.rs +++ b/substrate/frame/election-provider-support/solution-type/src/codec.rs @@ -33,6 +33,7 @@ pub(crate) fn codec_and_info_impl( let scale_info = scale_info_impl(&ident, &voter_type, &target_type, &weight_type, count); quote! { + impl _fepsp::codec::EncodeLike for #ident {} #encode #decode #scale_info diff --git a/substrate/frame/election-provider-support/solution-type/src/single_page.rs b/substrate/frame/election-provider-support/solution-type/src/single_page.rs index de59df162c8ad..35ac5a7394f3f 100644 --- a/substrate/frame/election-provider-support/solution-type/src/single_page.rs +++ b/substrate/frame/election-provider-support/solution-type/src/single_page.rs @@ -189,7 +189,7 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { fn max_encoded_len() -> usize { use frame_support::traits::Get; use _fepsp::codec::Encode; - let s: u32 = #max_voters::get(); + let s: u32 = <#max_voters as _feps::Get>::get(); let max_element_size = // the first voter.. #voter_type::max_encoded_len() diff --git a/substrate/frame/election-provider-support/src/lib.rs b/substrate/frame/election-provider-support/src/lib.rs index cb3249e388a31..09b0d65b3e6fb 100644 --- a/substrate/frame/election-provider-support/src/lib.rs +++ b/substrate/frame/election-provider-support/src/lib.rs @@ -21,10 +21,9 @@ //! within FRAME pallets. //! //! Something that will provide the functionality of election will implement -//! [`ElectionProvider`] and its parent-trait [`ElectionProviderBase`], whilst needing an -//! associated [`ElectionProviderBase::DataProvider`], which needs to be -//! fulfilled by an entity implementing [`ElectionDataProvider`]. Most often, *the data provider is* -//! the receiver of the election, resulting in a diagram as below: +//! [`ElectionProvider`], whilst needing an associated [`ElectionProvider::DataProvider`], which +//! needs to be fulfilled by an entity implementing [`ElectionDataProvider`]. Most often, *the data +//! provider is* the receiver of the election, resulting in a diagram as below: //! //! ```ignore //! ElectionDataProvider @@ -109,12 +108,12 @@ //! fn desired_targets() -> data_provider::Result { //! Ok(1) //! } -//! fn electing_voters(bounds: DataProviderBounds) +//! fn electing_voters(bounds: DataProviderBounds, _remaining_pages: PageIndex) //! -> data_provider::Result>> //! { //! Ok(Default::default()) //! } -//! fn electable_targets(bounds: DataProviderBounds) -> data_provider::Result> { +//! fn electable_targets(bounds: DataProviderBounds, _remaining_pages: PageIndex) -> data_provider::Result> { //! Ok(vec![10, 20, 30]) //! } //! fn next_election_prediction(now: BlockNumber) -> BlockNumber { @@ -126,40 +125,50 @@ //! //! mod generic_election_provider { //! use super::*; +//! use sp_runtime::traits::Zero; //! //! pub struct GenericElectionProvider(std::marker::PhantomData); //! //! pub trait Config { //! type DataProvider: ElectionDataProvider; +//! type MaxWinnersPerPage: Get; +//! type MaxBackersPerWinner: Get; +//! type Pages: Get; //! } //! -//! impl ElectionProviderBase for GenericElectionProvider { +//! impl ElectionProvider for GenericElectionProvider { //! type AccountId = AccountId; //! type BlockNumber = BlockNumber; //! type Error = &'static str; +//! type MaxBackersPerWinner = T::MaxBackersPerWinner; +//! type MaxWinnersPerPage = T::MaxWinnersPerPage; +//! type Pages = T::Pages; //! type DataProvider = T::DataProvider; -//! type MaxWinners = ConstU32<{ u32::MAX }>; -//! -//! } //! -//! impl ElectionProvider for GenericElectionProvider { -//! fn ongoing() -> bool { false } -//! fn elect() -> Result, Self::Error> { -//! Self::DataProvider::electable_targets(DataProviderBounds::default()) -//! .map_err(|_| "failed to elect") -//! .map(|t| bounded_vec![(t[0], Support::default())]) +//! fn elect(remaining_pages: PageIndex) -> Result, Self::Error> { +//! unimplemented!() //! } //! } //! } //! //! mod runtime { +//! use frame_support::parameter_types; //! use super::generic_election_provider; //! use super::data_provider_mod; //! use super::AccountId; //! +//! parameter_types! { +//! pub static MaxWinnersPerPage: u32 = 10; +//! pub static MaxBackersPerWinner: u32 = 20; +//! pub static Pages: u32 = 2; +//! } +//! //! struct Runtime; //! impl generic_election_provider::Config for Runtime { //! type DataProvider = data_provider_mod::Pallet; +//! type MaxWinnersPerPage = MaxWinnersPerPage; +//! type MaxBackersPerWinner = MaxBackersPerWinner; +//! type Pages = Pages; //! } //! //! impl data_provider_mod::Config for Runtime { @@ -181,21 +190,23 @@ extern crate alloc; use alloc::{boxed::Box, vec::Vec}; use core::fmt::Debug; +use sp_core::ConstU32; use sp_runtime::{ traits::{Bounded, Saturating, Zero}, RuntimeDebug, }; pub use bounds::DataProviderBounds; -pub use codec::{Decode, Encode}; +pub use codec::{Decode, Encode, MaxEncodedLen}; /// Re-export the solution generation macro. pub use frame_election_provider_solution_type::generate_solution_type; -pub use frame_support::{traits::Get, weights::Weight, BoundedVec}; +pub use frame_support::{traits::Get, weights::Weight, BoundedVec, DefaultNoBound}; +use scale_info::TypeInfo; /// Re-export some type as they are used in the interface. pub use sp_arithmetic::PerThing; pub use sp_npos_elections::{ - Assignment, BalancingConfig, BoundedSupports, ElectionResult, Error, ExtendedBalance, - IdentifierT, PerThing128, Support, Supports, VoteWeight, + Assignment, BalancingConfig, ElectionResult, Error, ExtendedBalance, IdentifierT, PerThing128, + Support, Supports, VoteWeight, }; pub use traits::NposSolution; @@ -251,7 +262,9 @@ pub struct IndexAssignment { pub distribution: Vec<(TargetIndex, P)>, } -impl IndexAssignment { +impl + IndexAssignment +{ pub fn new( assignment: &Assignment, voter_index: impl Fn(&AccountId) -> Option, @@ -298,8 +311,10 @@ pub trait ElectionDataProvider { /// /// This should be implemented as a self-weighing function. The implementor should register its /// appropriate weight at the end of execution with the system pallet directly. - fn electable_targets(bounds: DataProviderBounds) - -> data_provider::Result>; + fn electable_targets( + bounds: DataProviderBounds, + remaining_pages: PageIndex, + ) -> data_provider::Result>; /// All the voters that participate in the election, thus "electing". /// @@ -307,7 +322,10 @@ pub trait ElectionDataProvider { /// /// This should be implemented as a self-weighing function. The implementor should register its /// appropriate weight at the end of execution with the system pallet directly. - fn electing_voters(bounds: DataProviderBounds) -> data_provider::Result>>; + fn electing_voters( + bounds: DataProviderBounds, + remaining_pages: PageIndex, + ) -> data_provider::Result>>; /// The number of targets to elect. /// @@ -361,28 +379,39 @@ pub trait ElectionDataProvider { /// Clear all voters and targets. #[cfg(any(feature = "runtime-benchmarks", test))] fn clear() {} + + #[cfg(any(feature = "runtime-benchmarks", test))] + fn set_desired_targets(_count: u32) {} } -/// Base trait for types that can provide election -pub trait ElectionProviderBase { - /// The account identifier type. +/// An [`ElectionDataProvider`] that exposes for an external entity to request a lock/unlock on +/// updates in the election data. +pub trait LockableElectionDataProvider: ElectionDataProvider { + fn set_lock() -> data_provider::Result<()>; + fn unlock(); +} + +/// Something that can compute the result of an election and pass it back to the caller in a paged +/// way. +pub trait ElectionProvider { + /// The account ID identifier; type AccountId; /// The block number type. type BlockNumber; - /// The error type that is returned by the provider. - type Error: Debug; + /// The error type returned by the provider; + type Error: Debug + PartialEq; - /// The upper bound on election winners that can be returned. - /// - /// # WARNING - /// - /// when communicating with the data provider, one must ensure that - /// `DataProvider::desired_targets` returns a value less than this bound. An - /// implementation can chose to either return an error and/or sort and - /// truncate the output to meet this bound. - type MaxWinners: Get; + /// The maximum number of winners per page in results returned by this election provider. + type MaxWinnersPerPage: Get; + + /// The maximum number of backers that a single page may have in results returned by this + /// election provider. + type MaxBackersPerWinner: Get; + + /// The number of pages that this election provider supports. + type Pages: Get; /// The data provider of the election. type DataProvider: ElectionDataProvider< @@ -390,11 +419,29 @@ pub trait ElectionProviderBase { BlockNumber = Self::BlockNumber, >; + /// Elect a new set of winners. + /// + /// The result is returned in a target major format, namely as vector of supports. + /// + /// This should be implemented as a self-weighing function. The implementor should register its + /// appropriate weight at the end of execution with the system pallet directly. + fn elect(remaining: PageIndex) -> Result, Self::Error>; + + /// The index of the *most* significant page that this election provider supports. + fn msp() -> PageIndex { + Self::Pages::get().saturating_sub(1) + } + + /// The index of the *least* significant page that this election provider supports. + fn lsp() -> PageIndex { + Zero::zero() + } + /// checked call to `Self::DataProvider::desired_targets()` ensuring the value never exceeds - /// [`Self::MaxWinners`]. + /// [`Self::MaxWinnersPerPage`]. fn desired_targets_checked() -> data_provider::Result { Self::DataProvider::desired_targets().and_then(|desired_targets| { - if desired_targets <= Self::MaxWinners::get() { + if desired_targets <= Self::MaxWinnersPerPage::get() { Ok(desired_targets) } else { Err("desired_targets must not be greater than MaxWinners.") @@ -403,30 +450,13 @@ pub trait ElectionProviderBase { } } -/// Elect a new set of winners, bounded by `MaxWinners`. -/// -/// It must always use [`ElectionProviderBase::DataProvider`] to fetch the data it needs. -/// -/// This election provider that could function asynchronously. This implies that this election might -/// needs data ahead of time (ergo, receives no arguments to `elect`), and might be `ongoing` at -/// times. -pub trait ElectionProvider: ElectionProviderBase { - /// Indicate if this election provider is currently ongoing an asynchronous election or not. - fn ongoing() -> bool; - - /// Performs the election. This should be implemented as a self-weighing function. The - /// implementor should register its appropriate weight at the end of execution with the - /// system pallet directly. - fn elect() -> Result, Self::Error>; -} - /// A (almost) marker trait that signifies an election provider as working synchronously. i.e. being /// *instant*. /// -/// This must still use the same data provider as with [`ElectionProviderBase::DataProvider`]. +/// This must still use the same data provider as with [`ElectionProvider::DataProvider`]. /// However, it can optionally overwrite the amount of voters and targets that are fetched from the /// data provider at runtime via `forced_input_voters_bound` and `forced_input_target_bound`. -pub trait InstantElectionProvider: ElectionProviderBase { +pub trait InstantElectionProvider: ElectionProvider { fn instant_elect( forced_input_voters_bound: DataProviderBounds, forced_input_target_bound: DataProviderBounds, @@ -436,39 +466,33 @@ pub trait InstantElectionProvider: ElectionProviderBase { /// An election provider that does nothing whatsoever. pub struct NoElection(core::marker::PhantomData); -impl ElectionProviderBase - for NoElection<(AccountId, BlockNumber, DataProvider, MaxWinners)> +impl ElectionProvider + for NoElection<(AccountId, BlockNumber, DataProvider, MaxWinnersPerPage, MaxBackersPerWinner)> where DataProvider: ElectionDataProvider, - MaxWinners: Get, + MaxWinnersPerPage: Get, + MaxBackersPerWinner: Get, { type AccountId = AccountId; type BlockNumber = BlockNumber; type Error = &'static str; - type MaxWinners = MaxWinners; + type Pages = ConstU32<1>; type DataProvider = DataProvider; -} + type MaxWinnersPerPage = MaxWinnersPerPage; + type MaxBackersPerWinner = MaxBackersPerWinner; -impl ElectionProvider - for NoElection<(AccountId, BlockNumber, DataProvider, MaxWinners)> -where - DataProvider: ElectionDataProvider, - MaxWinners: Get, -{ - fn ongoing() -> bool { - false - } - - fn elect() -> Result, Self::Error> { + fn elect(_remaining_pages: PageIndex) -> Result, Self::Error> { Err("`NoElection` cannot do anything.") } } -impl InstantElectionProvider - for NoElection<(AccountId, BlockNumber, DataProvider, MaxWinners)> +impl + InstantElectionProvider + for NoElection<(AccountId, BlockNumber, DataProvider, MaxWinnersPerPage, MaxBackersPerWinner)> where DataProvider: ElectionDataProvider, - MaxWinners: Get, + MaxWinnersPerPage: Get, + MaxBackersPerWinner: Get, { fn instant_elect( _: DataProviderBounds, @@ -674,12 +698,161 @@ pub type Voter = (AccountId, VoteWeight, BoundedVec = Voter<::AccountId, ::MaxVotesPerVoter>; -/// Same as `BoundedSupports` but parameterized by a `ElectionProviderBase`. +/// A bounded vector of supports. Bounded equivalent to [`sp_npos_elections::Supports`]. +#[derive(Default, RuntimeDebug, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)] +#[codec(mel_bound(AccountId: MaxEncodedLen, Bound: Get))] +#[scale_info(skip_type_params(Bound))] +pub struct BoundedSupport> { + /// Total support. + pub total: ExtendedBalance, + /// Support from voters. + pub voters: BoundedVec<(AccountId, ExtendedBalance), Bound>, +} + +impl> sp_npos_elections::Backings for BoundedSupport { + fn total(&self) -> ExtendedBalance { + self.total + } +} + +impl> PartialEq for BoundedSupport { + fn eq(&self, other: &Self) -> bool { + self.total == other.total && self.voters == other.voters + } +} + +impl> From> for Support { + fn from(b: BoundedSupport) -> Self { + Support { total: b.total, voters: b.voters.into_inner() } + } +} + +impl> Clone for BoundedSupport { + fn clone(&self) -> Self { + Self { voters: self.voters.clone(), total: self.total } + } +} + +impl> TryFrom> + for BoundedSupport +{ + type Error = &'static str; + fn try_from(s: sp_npos_elections::Support) -> Result { + let voters = s.voters.try_into().map_err(|_| "voters bound not respected")?; + Ok(Self { voters, total: s.total }) + } +} + +/// A bounded vector of [`BoundedSupport`]. +#[derive(Encode, Decode, TypeInfo, DefaultNoBound, MaxEncodedLen)] +#[codec(mel_bound(AccountId: MaxEncodedLen, BOuter: Get, BInner: Get))] +#[scale_info(skip_type_params(BOuter, BInner))] +pub struct BoundedSupports, BInner: Get>( + pub BoundedVec<(AccountId, BoundedSupport), BOuter>, +); + +impl, BInner: Get> Debug + for BoundedSupports +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + for s in self.0.iter() { + write!(f, "({:?}, {:?}, {:?}) ", s.0, s.1.total, s.1.voters)?; + } + Ok(()) + } +} + +impl, BInner: Get> PartialEq + for BoundedSupports +{ + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl, BInner: Get> + From), BOuter>> + for BoundedSupports +{ + fn from(t: BoundedVec<(AccountId, BoundedSupport), BOuter>) -> Self { + Self(t) + } +} + +impl, BInner: Get> Clone + for BoundedSupports +{ + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl, BInner: Get> sp_std::ops::Deref + for BoundedSupports +{ + type Target = BoundedVec<(AccountId, BoundedSupport), BOuter>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl, BInner: Get> IntoIterator + for BoundedSupports +{ + type Item = (AccountId, BoundedSupport); + type IntoIter = sp_std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +/// An extension trait to convert from [`sp_npos_elections::Supports`] into +/// [`BoundedSupports`]. +pub trait TryIntoBoundedSupports, BInner: Get> { + /// Perform the conversion. + fn try_into_bounded_supports(self) -> Result, ()>; +} + +impl, BInner: Get> + TryIntoBoundedSupports for sp_npos_elections::Supports +{ + fn try_into_bounded_supports(self) -> Result, ()> { + let inner_bounded_supports = self + .into_iter() + .map(|(a, s)| s.try_into().map(|s| (a, s))) + .collect::, _>>() + .map_err(|_| ())?; + let outer_bounded_supports: BoundedVec<_, BOuter> = + inner_bounded_supports.try_into().map_err(|_| ())?; + Ok(outer_bounded_supports.into()) + } +} + +pub trait TryIntoSupports, BInner: Get> { + fn try_into_supports(self) -> Result, ()>; +} + +impl, BInner: Get> TryIntoSupports + for BoundedSupports +{ + fn try_into_supports(self) -> Result, ()> { + // TODO + Ok(Default::default()) + } +} + +/// Same as `BoundedSupports` but parameterized by an `ElectionProvider`. pub type BoundedSupportsOf = BoundedSupports< - ::AccountId, - ::MaxWinners, + ::AccountId, + ::MaxWinnersPerPage, + ::MaxBackersPerWinner, >; +/// A page index for the multi-block elections pagination. +pub type PageIndex = u32; + sp_core::generate_feature_enabled_macro!( runtime_benchmarks_enabled, feature = "runtime-benchmarks", diff --git a/substrate/frame/election-provider-support/src/onchain.rs b/substrate/frame/election-provider-support/src/onchain.rs index 1063d5d35aee7..f7349d5fd0cda 100644 --- a/substrate/frame/election-provider-support/src/onchain.rs +++ b/substrate/frame/election-provider-support/src/onchain.rs @@ -21,14 +21,15 @@ use crate::{ bounds::{DataProviderBounds, ElectionBounds, ElectionBoundsBuilder}, - BoundedSupportsOf, Debug, ElectionDataProvider, ElectionProvider, ElectionProviderBase, - InstantElectionProvider, NposSolver, WeightInfo, + BoundedSupportsOf, Debug, ElectionDataProvider, ElectionProvider, InstantElectionProvider, + NposSolver, PageIndex, TryIntoBoundedSupports, WeightInfo, Zero, }; use alloc::collections::btree_map::BTreeMap; use core::marker::PhantomData; use frame_support::{dispatch::DispatchClass, traits::Get}; +use frame_system::pallet_prelude::BlockNumberFor; use sp_npos_elections::{ - assignment_ratio_to_staked_normalized, to_supports, BoundedSupports, ElectionResult, VoteWeight, + assignment_ratio_to_staked_normalized, to_supports, ElectionResult, VoteWeight, }; /// Errors of the on-chain election. @@ -41,6 +42,8 @@ pub enum Error { /// Configurational error caused by `desired_targets` requested by data provider exceeding /// `MaxWinners`. TooManyWinners, + /// Single page election called with multi-page configs. + SinglePageExpected, } impl From for Error { @@ -71,6 +74,18 @@ pub trait Config { Error = sp_npos_elections::Error, >; + /// Maximum number of backers allowed per target. + /// + /// If the bounds are exceeded due to the data returned by the data provider, the election will + /// fail. + type MaxBackersPerWinner: Get; + + /// Maximum number of winners in an election. + /// + /// If the bounds are exceeded due to the data returned by the data provider, the election will + /// fail. + type MaxWinnersPerPage: Get; + /// Something that provides the data for election. type DataProvider: ElectionDataProvider< AccountId = ::AccountId, @@ -80,78 +95,64 @@ pub trait Config { /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; - /// Upper bound on maximum winners from electable targets. - /// - /// As noted in the documentation of [`ElectionProviderBase::MaxWinners`], this value should - /// always be more than `DataProvider::desired_target`. - type MaxWinners: Get; - /// Elections bounds, to use when calling into [`Config::DataProvider`]. It might be overwritten /// in the `InstantElectionProvider` impl. type Bounds: Get; } -/// Same as `BoundedSupportsOf` but for `onchain::Config`. -pub type OnChainBoundedSupportsOf = BoundedSupports< - <::System as frame_system::Config>::AccountId, - ::MaxWinners, ->; - -fn elect_with_input_bounds( - bounds: ElectionBounds, -) -> Result, Error> { - let (voters, targets) = T::DataProvider::electing_voters(bounds.voters) - .and_then(|voters| Ok((voters, T::DataProvider::electable_targets(bounds.targets)?))) - .map_err(Error::DataProvider)?; - - let desired_targets = T::DataProvider::desired_targets().map_err(Error::DataProvider)?; - - if desired_targets > T::MaxWinners::get() { - // early exit - return Err(Error::TooManyWinners) - } - - let voters_len = voters.len() as u32; - let targets_len = targets.len() as u32; +impl OnChainExecution { + fn elect_with( + bounds: ElectionBounds, + remaining: PageIndex, + ) -> Result, Error> { + let (voters, targets) = T::DataProvider::electing_voters(bounds.voters, remaining) + .and_then(|voters| { + Ok((voters, T::DataProvider::electable_targets(bounds.targets, remaining)?)) + }) + .map_err(Error::DataProvider)?; + + let desired_targets = T::DataProvider::desired_targets().map_err(Error::DataProvider)?; + + if desired_targets > T::MaxWinnersPerPage::get() { + // early exit + return Err(Error::TooManyWinners) + } - let stake_map: BTreeMap<_, _> = voters - .iter() - .map(|(validator, vote_weight, _)| (validator.clone(), *vote_weight)) - .collect(); + let voters_len = voters.len() as u32; + let targets_len = targets.len() as u32; - let stake_of = |w: &::AccountId| -> VoteWeight { - stake_map.get(w).cloned().unwrap_or_default() - }; + let stake_map: BTreeMap<_, _> = voters + .iter() + .map(|(validator, vote_weight, _)| (validator.clone(), *vote_weight)) + .collect(); - let ElectionResult { winners: _, assignments } = - T::Solver::solve(desired_targets as usize, targets, voters).map_err(Error::from)?; + let stake_of = |w: &::AccountId| -> VoteWeight { + stake_map.get(w).cloned().unwrap_or_default() + }; - let staked = assignment_ratio_to_staked_normalized(assignments, &stake_of)?; + let ElectionResult { winners: _, assignments } = + T::Solver::solve(desired_targets as usize, targets, voters).map_err(Error::from)?; - let weight = T::Solver::weight::( - voters_len, - targets_len, - ::MaxVotesPerVoter::get(), - ); - frame_system::Pallet::::register_extra_weight_unchecked( - weight, - DispatchClass::Mandatory, - ); + let staked = assignment_ratio_to_staked_normalized(assignments, &stake_of)?; - // defensive: Since npos solver returns a result always bounded by `desired_targets`, this is - // never expected to happen as long as npos solver does what is expected for it to do. - let supports: OnChainBoundedSupportsOf = - to_supports(&staked).try_into().map_err(|_| Error::TooManyWinners)?; + let weight = T::Solver::weight::( + voters_len, + targets_len, + ::MaxVotesPerVoter::get(), + ); + frame_system::Pallet::::register_extra_weight_unchecked( + weight, + DispatchClass::Mandatory, + ); - Ok(supports) -} + // defensive: Since npos solver returns a result always bounded by `desired_targets`, this + // is never expected to happen as long as npos solver does what is expected for it to do. + let supports: BoundedSupportsOf = to_supports(&staked) + .try_into_bounded_supports() + .map_err(|_| Error::TooManyWinners)?; -impl ElectionProviderBase for OnChainExecution { - type AccountId = ::AccountId; - type BlockNumber = frame_system::pallet_prelude::BlockNumberFor; - type Error = Error; - type MaxWinners = T::MaxWinners; - type DataProvider = T::DataProvider; + Ok(supports) + } } impl InstantElectionProvider for OnChainExecution { @@ -164,18 +165,27 @@ impl InstantElectionProvider for OnChainExecution { .targets_or_lower(forced_input_targets_bounds) .build(); - elect_with_input_bounds::(elections_bounds) + // NOTE: instant provider is *always* single page. + Self::elect_with(elections_bounds, Zero::zero()) } } impl ElectionProvider for OnChainExecution { - fn ongoing() -> bool { - false - } + type AccountId = ::AccountId; + type BlockNumber = BlockNumberFor; + type Error = Error; + type MaxWinnersPerPage = T::MaxWinnersPerPage; + type MaxBackersPerWinner = T::MaxBackersPerWinner; + type Pages = sp_core::ConstU32<1>; + type DataProvider = T::DataProvider; + + fn elect(remaining_pages: PageIndex) -> Result, Self::Error> { + if remaining_pages > 0 { + return Err(Error::SinglePageExpected) + } - fn elect() -> Result, Self::Error> { let election_bounds = ElectionBoundsBuilder::from(T::Bounds::get()).build(); - elect_with_input_bounds::(election_bounds) + Self::elect_with(election_bounds, Zero::zero()) } } @@ -231,7 +241,8 @@ mod tests { struct PhragMMSParams; parameter_types! { - pub static MaxWinners: u32 = 10; + pub static MaxWinnersPerPage: u32 = 10; + pub static MaxBackersPerWinner: u32 = 20; pub static DesiredTargets: u32 = 2; pub static Bounds: ElectionBounds = ElectionBoundsBuilder::default().voters_count(600.into()).targets_count(400.into()).build(); } @@ -240,17 +251,19 @@ mod tests { type System = Runtime; type Solver = SequentialPhragmen; type DataProvider = mock_data_provider::DataProvider; - type WeightInfo = (); - type MaxWinners = MaxWinners; + type MaxWinnersPerPage = MaxWinnersPerPage; + type MaxBackersPerWinner = MaxBackersPerWinner; type Bounds = Bounds; + type WeightInfo = (); } impl Config for PhragMMSParams { type System = Runtime; type Solver = PhragMMS; type DataProvider = mock_data_provider::DataProvider; + type MaxWinnersPerPage = MaxWinnersPerPage; + type MaxBackersPerWinner = MaxBackersPerWinner; type WeightInfo = (); - type MaxWinners = MaxWinners; type Bounds = Bounds; } @@ -259,14 +272,17 @@ mod tests { use sp_runtime::bounded_vec; use super::*; - use crate::{data_provider, VoterOf}; + use crate::{data_provider, PageIndex, VoterOf}; pub struct DataProvider; impl ElectionDataProvider for DataProvider { type AccountId = AccountId; type BlockNumber = BlockNumber; type MaxVotesPerVoter = ConstU32<2>; - fn electing_voters(_: DataProviderBounds) -> data_provider::Result>> { + fn electing_voters( + _: DataProviderBounds, + _remaining_pages: PageIndex, + ) -> data_provider::Result>> { Ok(vec![ (1, 10, bounded_vec![10, 20]), (2, 20, bounded_vec![30, 20]), @@ -274,7 +290,10 @@ mod tests { ]) } - fn electable_targets(_: DataProviderBounds) -> data_provider::Result> { + fn electable_targets( + _: DataProviderBounds, + _remaining_pages: PageIndex, + ) -> data_provider::Result> { Ok(vec![10, 20, 30]) } @@ -291,12 +310,19 @@ mod tests { #[test] fn onchain_seq_phragmen_works() { sp_io::TestExternalities::new_empty().execute_with(|| { + let expected_suports = vec![ + ( + 10 as AccountId, + Support { total: 25, voters: vec![(1 as AccountId, 10), (3, 15)] }, + ), + (30, Support { total: 35, voters: vec![(2, 20), (3, 15)] }), + ] + .try_into_bounded_supports() + .unwrap(); + assert_eq!( - as ElectionProvider>::elect().unwrap(), - vec![ - (10, Support { total: 25, voters: vec![(1, 10), (3, 15)] }), - (30, Support { total: 35, voters: vec![(2, 20), (3, 15)] }) - ] + as ElectionProvider>::elect(0).unwrap(), + expected_suports, ); }) } @@ -306,10 +332,10 @@ mod tests { sp_io::TestExternalities::new_empty().execute_with(|| { // given desired targets larger than max winners DesiredTargets::set(10); - MaxWinners::set(9); + MaxWinnersPerPage::set(9); assert_noop!( - as ElectionProvider>::elect(), + as ElectionProvider>::elect(0), Error::TooManyWinners, ); }) @@ -319,11 +345,16 @@ mod tests { fn onchain_phragmms_works() { sp_io::TestExternalities::new_empty().execute_with(|| { assert_eq!( - as ElectionProvider>::elect().unwrap(), + as ElectionProvider>::elect(0).unwrap(), vec![ - (10, Support { total: 25, voters: vec![(1, 10), (3, 15)] }), + ( + 10 as AccountId, + Support { total: 25, voters: vec![(1 as AccountId, 10), (3, 15)] } + ), (30, Support { total: 35, voters: vec![(2, 20), (3, 15)] }) ] + .try_into_bounded_supports() + .unwrap() ); }) } diff --git a/substrate/primitives/npos-elections/src/lib.rs b/substrate/primitives/npos-elections/src/lib.rs index 82ac40fe27378..9a9d33aa8e20d 100644 --- a/substrate/primitives/npos-elections/src/lib.rs +++ b/substrate/primitives/npos-elections/src/lib.rs @@ -83,7 +83,7 @@ use scale_info::TypeInfo; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use sp_arithmetic::{traits::Zero, Normalizable, PerThing, Rational128, ThresholdOrd}; -use sp_core::{bounded::BoundedVec, RuntimeDebug}; +use sp_core::RuntimeDebug; #[cfg(test)] mod mock; @@ -110,7 +110,7 @@ pub use reduce::reduce; pub use traits::{IdentifierT, PerThing128}; /// The errors that might occur in this crate and `frame-election-provider-solution-type`. -#[derive(Eq, PartialEq, RuntimeDebug)] +#[derive(Eq, PartialEq, RuntimeDebug, Clone)] pub enum Error { /// While going from solution indices to ratio, the weight of all the edges has gone above the /// total. @@ -444,6 +444,12 @@ impl Default for Support { } } +impl Backings for &Support { + fn total(&self) -> ExtendedBalance { + self.total + } +} + /// A target-major representation of the the election outcome. /// /// Essentially a flat variant of [`SupportMap`]. @@ -451,11 +457,6 @@ impl Default for Support { /// The main advantage of this is that it is encodable. pub type Supports = Vec<(A, Support)>; -/// Same as `Supports` but bounded by `B`. -/// -/// To note, the inner `Support` is still unbounded. -pub type BoundedSupports = BoundedVec<(A, Support), B>; - /// Linkage from a winner to their [`Support`]. /// /// This is more helpful than a normal [`Supports`] as it allows faster error checking. @@ -499,23 +500,34 @@ pub trait EvaluateSupport { impl EvaluateSupport for Supports { fn evaluate(&self) -> ElectionScore { - let mut minimal_stake = ExtendedBalance::max_value(); - let mut sum_stake: ExtendedBalance = Zero::zero(); - // NOTE: The third element might saturate but fine for now since this will run on-chain and - // need to be fast. - let mut sum_stake_squared: ExtendedBalance = Zero::zero(); - - for (_, support) in self { - sum_stake = sum_stake.saturating_add(support.total); - let squared = support.total.saturating_mul(support.total); - sum_stake_squared = sum_stake_squared.saturating_add(squared); - if support.total < minimal_stake { - minimal_stake = support.total; - } - } + evaluate_support(self.iter().map(|(_, s)| s)) + } +} + +/// Generic representation of a support. +pub trait Backings { + /// The total backing of an individual target. + fn total(&self) -> ExtendedBalance; +} - ElectionScore { minimal_stake, sum_stake, sum_stake_squared } +/// General evaluation of a list of backings that returns an election score. +pub fn evaluate_support(backings: impl Iterator) -> ElectionScore { + let mut minimal_stake = ExtendedBalance::max_value(); + let mut sum_stake: ExtendedBalance = Zero::zero(); + // NOTE: The third element might saturate but fine for now since this will run on-chain and + // need to be fast. + let mut sum_stake_squared: ExtendedBalance = Zero::zero(); + + for support in backings { + sum_stake = sum_stake.saturating_add(support.total()); + let squared = support.total().saturating_mul(support.total()); + sum_stake_squared = sum_stake_squared.saturating_add(squared); + if support.total() < minimal_stake { + minimal_stake = support.total(); + } } + + ElectionScore { minimal_stake, sum_stake, sum_stake_squared } } /// Converts raw inputs to types used in this crate. diff --git a/substrate/primitives/staking/Cargo.toml b/substrate/primitives/staking/Cargo.toml index 35e7e4f604136..80220cd1f6fcc 100644 --- a/substrate/primitives/staking/Cargo.toml +++ b/substrate/primitives/staking/Cargo.toml @@ -23,6 +23,7 @@ impl-trait-for-tuples = { workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } +sp-std = { workspace = true } [features] default = ["std"] @@ -32,5 +33,6 @@ std = [ "serde/std", "sp-core/std", "sp-runtime/std", + "sp-std/std", ] runtime-benchmarks = ["sp-runtime/runtime-benchmarks"] diff --git a/substrate/primitives/staking/src/lib.rs b/substrate/primitives/staking/src/lib.rs index 17010a8907fc2..8dd29bb835de6 100644 --- a/substrate/primitives/staking/src/lib.rs +++ b/substrate/primitives/staking/src/lib.rs @@ -357,6 +357,8 @@ pub struct IndividualExposure { /// A snapshot of the stake backing a single validator in the system. #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +#[codec(mel_bound(T: Config))] +#[scale_info(skip_type_params(T))] pub struct Exposure { /// The total balance backing this validator. #[codec(compact)] @@ -432,6 +434,44 @@ impl Default for ExposurePage { } } +impl + From>> for ExposurePage +{ + fn from(exposures: Vec>) -> Self { + let mut page: Self = Default::default(); + + let _ = exposures + .into_iter() + .map(|e| { + page.page_total += e.value.clone(); + page.others.push(e) + }) + .collect::>(); + + page + } +} + +impl< + A, + B: Default + + HasCompact + + core::fmt::Debug + + sp_std::ops::AddAssign + + sp_std::ops::SubAssign + + Clone, + > ExposurePage +{ + /// Split the current exposure page into two pages where the new page takes up to `num` + /// individual exposures. The remaining individual exposures are left in `self`. + pub fn from_split_others(&mut self, num: usize) -> Self { + let new: ExposurePage<_, _> = self.others.split_off(num).into(); + self.page_total -= new.page_total.clone(); + + new + } +} + /// Metadata for Paged Exposure of a validator such as total stake across pages and page count. /// /// In combination with the associated `ExposurePage`s, it can be used to reconstruct a full @@ -449,6 +489,7 @@ impl Default for ExposurePage { TypeInfo, Default, MaxEncodedLen, + Copy, )] pub struct PagedExposureMetadata { /// The total balance backing this validator. @@ -463,6 +504,28 @@ pub struct PagedExposureMetadata { pub page_count: Page, } +impl PagedExposureMetadata +where + Balance: HasCompact + + codec::MaxEncodedLen + + sp_std::ops::Add + + sp_std::ops::Sub + + PartialEq + + Copy, +{ + pub fn merge(self, other: Self) -> Self { + debug_assert!(self.own == other.own); + + Self { + total: self.total + other.total - self.own, + own: self.own, + nominator_count: self.nominator_count + other.nominator_count, + // TODO: merge the pages correctly. + page_count: self.page_count + other.page_count, + } + } +} + /// A type that belongs only in the context of an `Agent`. /// /// `Agent` is someone that manages delegated funds from [`Delegator`] accounts. It can From 8a682b8b9d74d462d8f8151778a369ea50499f12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Mon, 14 Oct 2024 00:47:07 +0200 Subject: [PATCH 002/169] refactors staking pallet with new paginated types --- Cargo.lock | 3229 ++++++++++--------- substrate/frame/staking/src/benchmarking.rs | 5 +- substrate/frame/staking/src/lib.rs | 60 +- substrate/frame/staking/src/mock.rs | 27 +- substrate/frame/staking/src/pallet/impls.rs | 267 +- substrate/frame/staking/src/pallet/mod.rs | 220 +- substrate/frame/staking/src/tests.rs | 347 +- substrate/primitives/staking/src/lib.rs | 3 +- 8 files changed, 2393 insertions(+), 1765 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ba5f04aaf9997..f9dd4d9781b63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,7 +27,7 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ - "gimli 0.28.0", + "gimli 0.28.1", ] [[package]] @@ -36,6 +36,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "adler32" version = "1.2.0" @@ -54,9 +60,9 @@ dependencies = [ [[package]] name = "aes" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher 0.4.4", @@ -74,7 +80,7 @@ dependencies = [ "cipher 0.4.4", "ctr", "ghash", - "subtle 2.5.0", + "subtle 2.6.1", ] [[package]] @@ -112,18 +118,18 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.4" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "allocator-api2" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "alloy-primitives" @@ -147,13 +153,12 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.3" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc0fac0fc16baf1f63f78b47c3d24718f3619b0714076f6a02957d808d52cbef" +checksum = "26154390b1d205a4a7ac7352aa2eb4f81f391399d4e2f546fb81a2f8bb383f62" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "bytes", - "smol_str", ] [[package]] @@ -166,7 +171,7 @@ dependencies = [ "dunce", "heck 0.4.1", "proc-macro-error", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", "syn-solidity", @@ -223,57 +228,58 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.11" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.1" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.1" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "approx" @@ -293,7 +299,7 @@ dependencies = [ "include_dir", "itertools 0.10.5", "proc-macro-error", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -483,7 +489,7 @@ dependencies = [ "num-bigint", "num-traits", "paste", - "rustc_version 0.4.0", + "rustc_version 0.4.1", "zeroize", ] @@ -527,7 +533,7 @@ checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" dependencies = [ "num-bigint", "num-traits", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 1.0.109", ] @@ -558,20 +564,6 @@ dependencies = [ "hashbrown 0.13.2", ] -[[package]] -name = "ark-scale" -version = "0.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51bd73bb6ddb72630987d37fa963e99196896c0d0ea81b7c894567e74a2f83af" -dependencies = [ - "ark-ec", - "ark-ff 0.4.2", - "ark-serialize 0.4.2", - "ark-std 0.4.0", - "parity-scale-codec", - "scale-info", -] - [[package]] name = "ark-scale" version = "0.0.12" @@ -629,7 +621,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 1.0.109", ] @@ -670,15 +662,15 @@ dependencies = [ [[package]] name = "array-bytes" -version = "6.2.2" +version = "6.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f840fb7195bcfc5e17ea40c26e5ce6d5b9ce5d584466e17703209657e459ae0" +checksum = "5d5dde061bd34119e902bbb2d9b90c5692635cf59fb91d582c2b68043f1b8293" [[package]] name = "arrayref" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" @@ -697,9 +689,9 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "asn1-rs" @@ -719,11 +711,11 @@ dependencies = [ [[package]] name = "asn1-rs" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ad1373757efa0f70ec53939aabc7152e1591cb485208052993070ac8d2429d" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" dependencies = [ - "asn1-rs-derive 0.5.0", + "asn1-rs-derive 0.5.1", "asn1-rs-impl 0.2.0", "displaydoc", "nom", @@ -739,7 +731,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 1.0.109", "synstructure 0.12.6", @@ -747,11 +739,11 @@ dependencies = [ [[package]] name = "asn1-rs-derive" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7378575ff571966e99a744addeff0bff98b8ada0dedf1956d59e634db95eaac1" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", "synstructure 0.13.1", @@ -763,7 +755,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 1.0.109", ] @@ -774,21 +766,22 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] [[package]] name = "assert_cmd" -version = "2.0.14" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed72493ac66d5804837f480ab3766c72bdfab91a65e565fc54fa9e42db0073a8" +checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d" dependencies = [ "anstyle", "bstr", "doc-comment", - "predicates 3.0.3", + "libc", + "predicates 3.1.2", "predicates-core", "predicates-tree", "wait-timeout", @@ -1134,12 +1127,11 @@ dependencies = [ [[package]] name = "async-channel" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f2776ead772134d55b62dd45e59a79e21612d85d0af729b8b7d3967d601a62a" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" dependencies = [ "concurrent-queue", - "event-listener 5.2.0", "event-listener-strategy", "futures-core", "pin-project-lite", @@ -1147,15 +1139,14 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.5.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" dependencies = [ - "async-lock 2.8.0", "async-task", "concurrent-queue", - "fastrand 1.9.0", - "futures-lite 1.13.0", + "fastrand 2.1.1", + "futures-lite 2.3.0", "slab", ] @@ -1184,16 +1175,16 @@ dependencies = [ [[package]] name = "async-global-executor" -version = "2.3.1" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ - "async-channel 1.9.0", + "async-channel 2.3.1", "async-executor", - "async-io 1.13.0", - "async-lock 2.8.0", + "async-io 2.3.4", + "async-lock 3.4.0", "blocking", - "futures-lite 1.13.0", + "futures-lite 2.3.0", "once_cell", ] @@ -1211,17 +1202,17 @@ dependencies = [ "log", "parking", "polling 2.8.0", - "rustix 0.37.23", + "rustix 0.37.27", "slab", - "socket2 0.4.9", + "socket2 0.4.10", "waker-fn", ] [[package]] name = "async-io" -version = "2.3.3" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964" +checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" dependencies = [ "async-lock 3.4.0", "cfg-if", @@ -1229,11 +1220,11 @@ dependencies = [ "futures-io", "futures-lite 2.3.0", "parking", - "polling 3.4.0", - "rustix 0.38.21", + "polling 3.7.3", + "rustix 0.38.37", "slab", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1251,19 +1242,18 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ - "event-listener 5.2.0", + "event-listener 5.3.1", "event-listener-strategy", "pin-project-lite", ] [[package]] name = "async-net" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4051e67316bc7eff608fe723df5d32ed639946adcd69e07df41fd42a7b411f1f" +checksum = "0434b1ed18ce1cf5769b8ac540e33f01fa9471058b5e89da9e06f3c882a8c12f" dependencies = [ "async-io 1.13.0", - "autocfg", "blocking", "futures-lite 1.13.0", ] @@ -1274,26 +1264,25 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" dependencies = [ - "async-io 2.3.3", + "async-io 2.3.4", "blocking", "futures-lite 2.3.0", ] [[package]] name = "async-process" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" +checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" dependencies = [ "async-io 1.13.0", "async-lock 2.8.0", - "autocfg", + "async-signal", "blocking", "cfg-if", - "event-listener 2.5.3", + "event-listener 3.1.0", "futures-lite 1.13.0", - "rustix 0.37.23", - "signal-hook", + "rustix 0.38.37", "windows-sys 0.48.0", ] @@ -1303,53 +1292,53 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" dependencies = [ - "async-channel 2.3.0", - "async-io 2.3.3", + "async-channel 2.3.1", + "async-io 2.3.4", "async-lock 3.4.0", "async-signal", "async-task", "blocking", "cfg-if", - "event-listener 5.2.0", + "event-listener 5.3.1", "futures-lite 2.3.0", - "rustix 0.38.21", + "rustix 0.38.37", "tracing", ] [[package]] name = "async-signal" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb3634b73397aa844481f814fad23bbf07fdb0eabec10f2eb95e58944b1ec32" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" dependencies = [ - "async-io 2.3.3", + "async-io 2.3.4", "async-lock 3.4.0", "atomic-waker", "cfg-if", "futures-core", "futures-io", - "rustix 0.38.21", + "rustix 0.38.37", "signal-hook-registry", "slab", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "async-std" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615" dependencies = [ "async-attributes", "async-channel 1.9.0", "async-global-executor", - "async-io 1.13.0", - "async-lock 2.8.0", + "async-io 2.3.4", + "async-lock 3.4.0", "crossbeam-utils", "futures-channel", "futures-core", "futures-io", - "futures-lite 1.13.0", + "futures-lite 2.3.0", "gloo-timers", "kv-log-macro", "log", @@ -1363,9 +1352,9 @@ dependencies = [ [[package]] name = "async-stream" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" dependencies = [ "async-stream-impl", "futures-core", @@ -1374,11 +1363,11 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -1391,11 +1380,11 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.82" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -1421,9 +1410,9 @@ checksum = "a8ab6b55fe97976e46f91ddbed8d147d966475dc29b2032757ba47e02376fbc3" [[package]] name = "atomic-waker" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "attohttpc" @@ -1431,7 +1420,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d9a9bf8b79a749ee0b911b91b671cc2b6c670bdbc7e3dfd537576ddc94bb2a2" dependencies = [ - "http 0.2.9", + "http 0.2.12", "log", "url", ] @@ -1449,21 +1438,20 @@ dependencies = [ [[package]] name = "auto_impl" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fee3da8ef1276b0bee5dd1c7258010d8fffd31801447323115a25560e1327b89" +checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ - "proc-macro-error", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", - "syn 1.0.109", + "syn 2.0.79", ] [[package]] name = "autocfg" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backoff" @@ -1486,7 +1474,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.7.4", "object 0.32.2", "rustc-demangle", ] @@ -1554,15 +1542,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" -[[package]] -name = "basic-toml" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2db21524cad41c5591204d22d75e1970a2d1f71060214ca931dc7d5afe2c14e5" -dependencies = [ - "serde", -] - [[package]] name = "beef" version = "0.5.2" @@ -1607,7 +1586,7 @@ dependencies = [ "lazycell", "peeking_take_while", "prettyplease", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "regex", "rustc-hash 1.1.0", @@ -1617,11 +1596,11 @@ dependencies = [ [[package]] name = "bip39" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f" +checksum = "33415e24172c1b7d6066f6d999545375ab8e1d95421d6784bdfff9496f292387" dependencies = [ - "bitcoin_hashes 0.11.0", + "bitcoin_hashes", "serde", "unicode-normalization", ] @@ -1647,12 +1626,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" -[[package]] -name = "bitcoin_hashes" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" - [[package]] name = "bitcoin_hashes" version = "0.13.0" @@ -1737,8 +1710,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" dependencies = [ "arrayref", - "arrayvec 0.7.4", - "constant_time_eq 0.3.0", + "arrayvec 0.7.6", + "constant_time_eq 0.3.1", ] [[package]] @@ -1754,13 +1727,13 @@ dependencies = [ [[package]] name = "blake2s_simd" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6637f448b9e61dfadbdcbae9a885fadee1f3eaffb1f8d3c1965d3ade8bdfd44f" +checksum = "94230421e395b9920d23df13ea5d77a20e1725331f90fbbf6df6040b33f756ae" dependencies = [ "arrayref", - "arrayvec 0.7.4", - "constant_time_eq 0.2.6", + "arrayvec 0.7.6", + "constant_time_eq 0.3.1", ] [[package]] @@ -1770,10 +1743,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" dependencies = [ "arrayref", - "arrayvec 0.7.4", + "arrayvec 0.7.6", "cc", "cfg-if", - "constant_time_eq 0.3.0", + "constant_time_eq 0.3.1", ] [[package]] @@ -1803,24 +1776,22 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "blocking" -version = "1.3.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" dependencies = [ - "async-channel 1.9.0", - "async-lock 2.8.0", + "async-channel 2.3.1", "async-task", - "atomic-waker", - "fastrand 1.9.0", - "futures-lite 1.13.0", - "log", + "futures-io", + "futures-lite 2.3.0", + "piper", ] [[package]] name = "bounded-collections" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32385ecb91a31bddaf908e8dcf4a15aef1bcd3913cc03ebfad02ff6d568abc1" +checksum = "db436177db0d505b1507f03aca56a41442ae6efdf8b6eaa855d73e52c5b078dc" dependencies = [ "log", "parity-scale-codec", @@ -2569,12 +2540,12 @@ dependencies = [ [[package]] name = "bstr" -version = "1.6.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" dependencies = [ "memchr", - "regex-automata 0.3.6", + "regex-automata 0.4.8", "serde", ] @@ -2589,9 +2560,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.13.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byte-slice-cast" @@ -2607,9 +2578,9 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" [[package]] name = "bytemuck" -version = "1.13.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" [[package]] name = "byteorder" @@ -2665,18 +2636,18 @@ dependencies = [ [[package]] name = "camino" -version = "1.1.6" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" dependencies = [ "serde", ] [[package]] name = "cargo-platform" -version = "0.1.3" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cfa25e60aea747ec7e1124f238816749faa93759c6ff5b31f1ccdda137f4479" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" dependencies = [ "serde", ] @@ -2689,7 +2660,7 @@ checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" dependencies = [ "camino", "cargo-platform", - "semver 1.0.18", + "semver 1.0.23", "serde", "serde_json", "thiserror", @@ -2709,9 +2680,9 @@ checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" [[package]] name = "cc" -version = "1.1.24" +version = "1.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" +checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" dependencies = [ "jobserver", "libc", @@ -2735,9 +2706,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.15.5" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03915af431787e6ffdcc74c645077518c6b6e01f80b761e0fbbfa288536311b3" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" dependencies = [ "smallvec", ] @@ -2821,9 +2792,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", @@ -2831,14 +2802,14 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] name = "ciborium" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", @@ -2847,15 +2818,15 @@ dependencies = [ [[package]] name = "ciborium-io" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", @@ -2929,9 +2900,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.6.1" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", @@ -2967,48 +2938,48 @@ dependencies = [ "once_cell", "strsim 0.10.0", "termcolor", - "textwrap 0.16.0", + "textwrap 0.16.1", ] [[package]] name = "clap" -version = "4.5.13" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", - "clap_derive 4.5.13", + "clap_derive 4.5.18", ] [[package]] name = "clap-num" -version = "1.0.2" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488557e97528174edaa2ee268b23a809e0c598213a4bbcb4f34575a46fda147e" +checksum = "0e063d263364859dc54fb064cedb7c122740cd4733644b14b176c097f51e8ab7" dependencies = [ "num-traits", ] [[package]] name = "clap_builder" -version = "4.5.13" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ "anstream", "anstyle", - "clap_lex 0.7.0", + "clap_lex 0.7.2", "strsim 0.11.1", "terminal_size", ] [[package]] name = "clap_complete" -version = "4.5.13" +version = "4.5.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa3c596da3cf0983427b0df0dba359df9182c13bd5b519b585a482b0c351f4e8" +checksum = "9646e2e245bf62f45d39a0f3f36f1171ad1ea0d6967fd114bca72cb02a8fcdfb" dependencies = [ - "clap 4.5.13", + "clap 4.5.20", ] [[package]] @@ -3019,19 +2990,19 @@ checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" dependencies = [ "heck 0.4.1", "proc-macro-error", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 1.0.109", ] [[package]] name = "clap_derive" -version = "4.5.13" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck 0.5.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -3047,19 +3018,18 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "coarsetime" -version = "0.1.23" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90d114103adbc625300f346d4d09dfb4ab1c4a8df6868435dd903392ecf4354" +checksum = "13b3839cf01bb7960114be3ccf2340f541b6d0c81f8690b007b2b39f750f7e5d" dependencies = [ "libc", - "once_cell", - "wasi", + "wasix", "wasm-bindgen", ] @@ -3207,47 +3177,46 @@ dependencies = [ [[package]] name = "color-print" -version = "0.3.4" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2a5e6504ed8648554968650feecea00557a3476bc040d0ffc33080e66b646d0" +checksum = "1ee543c60ff3888934877a5671f45494dd27ed4ba25c6670b9a7576b7ed7a8c0" dependencies = [ "color-print-proc-macro", ] [[package]] name = "color-print-proc-macro" -version = "0.3.4" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51beaa537d73d2d1ff34ee70bc095f170420ab2ec5d687ecd3ec2b0d092514b" +checksum = "77ff1a80c5f3cb1ca7c06ffdd71b6a6dd6d8f896c42141fbd43f50ed28dcdb93" dependencies = [ "nom", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", - "syn 1.0.109", + "syn 2.0.79", ] [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "colored" -version = "2.0.4" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" dependencies = [ - "is-terminal", "lazy_static", "windows-sys 0.48.0", ] [[package]] name = "combine" -version = "4.6.6" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ "bytes", "memchr", @@ -3255,12 +3224,12 @@ dependencies = [ [[package]] name = "comfy-table" -version = "7.1.0" +version = "7.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c64043d6c7b7a4c58e39e7efccfdea7b93d885a795d0c054a69dbbf4dd52686" +checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7" dependencies = [ - "strum 0.25.0", - "strum_macros 0.25.3", + "strum 0.26.3", + "strum_macros 0.26.4", "unicode-width", ] @@ -3305,7 +3274,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a54b9c40054eb8999c5d1d36fdc90e4e5f7ff0d1d9621706f360b3cbc8beb828" dependencies = [ "convert_case 0.4.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 1.0.109", ] @@ -3317,16 +3286,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5437e327e861081c91270becff184859f706e3e50f5301a9d4dc8eb50752c3" dependencies = [ "convert_case 0.6.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 1.0.109", ] [[package]] name = "concurrent-queue" -version = "2.2.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] @@ -3356,9 +3325,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.10.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5104de16b218eddf8e34ffe2f86f74bfa4e61e95a1b89732fccf6325efd0557" +checksum = "0121754e84117e65f9d90648ee6aa4882a6e63110307ab73967a4c5e7e69e586" dependencies = [ "cfg-if", "cpufeatures", @@ -3369,29 +3338,27 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const-random" -version = "0.1.15" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368a7a772ead6ce7e1de82bfb04c485f3db8ec744f72925af5735e29a22cc18e" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" dependencies = [ "const-random-macro", - "proc-macro-hack", ] [[package]] name = "const-random-macro" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d7d6ab3c3a2282db210df5f02c4dab6e0a7057af0fb7ebd4070f30fe05c0ddb" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ "getrandom", "once_cell", - "proc-macro-hack", "tiny-keccak", ] @@ -3403,21 +3370,15 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "constant_time_eq" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a53c0a4d288377e7415b53dcfc3c04da5cdc2cc95c8d5ac178b58f0b861ad6" - -[[package]] -name = "constant_time_eq" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "constcat" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f272d0c4cf831b4fa80ee529c7707f76585986e910e1fbce1d7921970bc1a241" +checksum = "cd7e35aee659887cbfb97aaf227ac12cad1a9d7c71e55ff3376839ed4e282d08" [[package]] name = "contracts-rococo-runtime" @@ -3516,9 +3477,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core2" @@ -3739,9 +3700,9 @@ dependencies = [ [[package]] name = "cpp_demangle" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8227005286ec39567949b33df9896bcadfa6051bccca2488129f108ca23119" +checksum = "96e58d342ad113c2b878f16d5d034c03be492ae460cdbc02b7f0f2284d310c7d" dependencies = [ "cfg-if", ] @@ -3758,9 +3719,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] @@ -3859,15 +3820,15 @@ dependencies = [ "itertools 0.10.5", "log", "smallvec", - "wasmparser", + "wasmparser 0.102.0", "wasmtime-types", ] [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] @@ -3881,7 +3842,7 @@ dependencies = [ "anes", "cast", "ciborium", - "clap 4.5.13", + "clap 4.5.20", "criterion-plot", "futures", "is-terminal", @@ -3912,46 +3873,37 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.15" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset 0.9.0", - "scopeguard", ] [[package]] name = "crossbeam-queue" -version = "0.3.8" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" @@ -3961,13 +3913,13 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.5.2" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array 0.14.7", "rand_core 0.6.4", - "subtle 2.5.0", + "subtle 2.6.1", "zeroize", ] @@ -3999,7 +3951,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ "generic-array 0.14.7", - "subtle 2.5.0", + "subtle 2.6.1", ] [[package]] @@ -4015,7 +3967,7 @@ dependencies = [ name = "cumulus-client-cli" version = "0.7.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.20", "parity-scale-codec", "sc-chain-spec", "sc-cli", @@ -4387,8 +4339,8 @@ dependencies = [ name = "cumulus-pallet-parachain-system-proc-macro" version = "0.6.0" dependencies = [ - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -4478,7 +4430,7 @@ name = "cumulus-pov-validator" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.5.13", + "clap 4.5.20", "parity-scale-codec", "polkadot-node-primitives", "polkadot-parachain-primitives", @@ -4617,7 +4569,7 @@ dependencies = [ "async-trait", "cumulus-primitives-core", "futures", - "jsonrpsee-core 0.24.3", + "jsonrpsee-core 0.24.6", "parity-scale-codec", "polkadot-overseer", "sc-client-api", @@ -4672,7 +4624,7 @@ dependencies = [ "either", "futures", "futures-timer", - "jsonrpsee 0.24.3", + "jsonrpsee 0.24.6", "parity-scale-codec", "pin-project", "polkadot-overseer", @@ -4798,7 +4750,7 @@ name = "cumulus-test-service" version = "0.1.0" dependencies = [ "async-trait", - "clap 4.5.13", + "clap 4.5.20", "criterion", "cumulus-client-cli", "cumulus-client-collator", @@ -4821,7 +4773,7 @@ dependencies = [ "frame-system", "frame-system-rpc-runtime-api", "futures", - "jsonrpsee 0.24.3", + "jsonrpsee 0.24.6", "pallet-timestamp", "pallet-transaction-payment", "parachains-common", @@ -4876,9 +4828,9 @@ dependencies = [ [[package]] name = "curl" -version = "0.4.46" +version = "0.4.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e2161dd6eba090ff1594084e95fd67aeccf04382ffea77999ea94ed42ec67b6" +checksum = "d9fb4d13a1be2b58f14d60adba57c9834b78c62fd86c3e76a148f732686e9265" dependencies = [ "curl-sys", "libc", @@ -4891,9 +4843,9 @@ dependencies = [ [[package]] name = "curl-sys" -version = "0.4.72+curl-8.6.0" +version = "0.4.77+curl-8.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29cbdc8314c447d11e8fd156dcdd031d9e02a7a976163e396b548c03153bc9ea" +checksum = "f469e8a5991f277a208224f6c7ad72ecb5f986e36d09ae1f2c1bb9259478a480" dependencies = [ "cc", "libc", @@ -4914,7 +4866,7 @@ dependencies = [ "byteorder", "digest 0.9.0", "rand_core 0.5.1", - "subtle 2.5.0", + "subtle 2.6.1", "zeroize", ] @@ -4929,18 +4881,18 @@ dependencies = [ "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", - "rustc_version 0.4.0", - "subtle 2.5.0", + "rustc_version 0.4.1", + "subtle 2.6.1", "zeroize", ] [[package]] name = "curve25519-dalek-derive" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -4960,9 +4912,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.106" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28403c86fc49e3401fdf45499ba37fad6493d9329449d6449d7f0e10f4654d28" +checksum = "54ccead7d199d584d139148b04b4a368d1ec7556a1d9ea2548febb1b9d49f9a4" dependencies = [ "cc", "cxxbridge-flags", @@ -4972,14 +4924,14 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.106" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78da94fef01786dc3e0c76eafcd187abcaa9972c78e05ff4041e24fdf059c285" +checksum = "c77953e99f01508f89f55c494bfa867171ef3a6c8cea03d26975368f2121a5c1" dependencies = [ "cc", "codespan-reporting", "once_cell", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "scratch", "syn 2.0.79", @@ -4987,17 +4939,17 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.106" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2a6f5e1dfb4b34292ad4ea1facbfdaa1824705b231610087b00b17008641809" +checksum = "65777e06cc48f0cb0152024c77d6cf9e4bdb4408e7b48bea993d42fa0f5b02b6" [[package]] name = "cxxbridge-macro" -version = "1.0.106" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c49547d73ba8dcfd4ad7325d64c6d5391ff4224d498fc39a6f3f49825a530d" +checksum = "98532a60dedaebc4848cb2cba5023337cc9ea3af16a5b062633fabfd9f18fb60" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -5030,7 +4982,7 @@ checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "strsim 0.10.0", "syn 1.0.109", @@ -5044,7 +4996,7 @@ checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "strsim 0.11.1", "syn 2.0.79", @@ -5074,28 +5026,28 @@ dependencies = [ [[package]] name = "dashmap" -version = "5.5.1" +version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd72493923899c6f10c641bdbdeddc7183d6396641d99c1a0d1597f37f92e28" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", "hashbrown 0.14.5", "lock_api", "once_cell", - "parking_lot_core 0.9.8", + "parking_lot_core 0.9.10", ] [[package]] name = "data-encoding" -version = "2.4.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "data-encoding-macro" -version = "0.1.13" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c904b33cc60130e1aeea4956ab803d08a3f4a0ca82d64ed757afac3891f2bb99" +checksum = "f1559b6cba622276d6d63706db152618eeb15b89b3e4041446b05876e352e639" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -5103,9 +5055,9 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.11" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fdf3fce3ce863539ec1d7fd1b6dcc3c645663376b43ed376bbf887733e4f772" +checksum = "332d754c0af53bc87c108fed664d121ecf59207ec4196041f04d6ab9002ad33f" dependencies = [ "data-encoding", "syn 1.0.109", @@ -5122,9 +5074,9 @@ dependencies = [ [[package]] name = "der" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "zeroize", @@ -5150,7 +5102,7 @@ version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" dependencies = [ - "asn1-rs 0.6.1", + "asn1-rs 0.6.2", "displaydoc", "nom", "num-bigint", @@ -5173,7 +5125,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 1.0.109", ] @@ -5184,7 +5136,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -5195,7 +5147,7 @@ version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -5206,22 +5158,22 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] [[package]] name = "derive_more" -version = "0.99.17" +version = "0.99.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ "convert_case 0.4.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", - "rustc_version 0.4.0", - "syn 1.0.109", + "rustc_version 0.4.1", + "syn 2.0.79", ] [[package]] @@ -5263,7 +5215,7 @@ dependencies = [ "block-buffer 0.10.4", "const-oid", "crypto-common", - "subtle 2.5.0", + "subtle 2.6.1", ] [[package]] @@ -5310,20 +5262,20 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] [[package]] name = "dissimilar" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86e3bdc80eee6e16b2b6b0f87fbc98c04bee3455e35174c0de1a125d0688c632" +checksum = "59f8e79d1fbf76bdfbde321e902714bf6c49df88a7dda6fc682fc2979226962d" [[package]] name = "dleq_vrf" @@ -5332,22 +5284,24 @@ source = "git+https://github.com/w3f/ring-vrf?rev=0fef826#0fef8266d851932ad25d6b dependencies = [ "ark-ec", "ark-ff 0.4.2", - "ark-scale 0.0.12", + "ark-scale", "ark-secret-scalar", "ark-serialize 0.4.2", "ark-std 0.4.0", "ark-transcript", - "arrayvec 0.7.4", + "arrayvec 0.7.6", "zeroize", ] [[package]] name = "dlmalloc" -version = "0.2.4" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "203540e710bfadb90e5e29930baf5d10270cec1f43ab34f46f78b147b2de715a" +checksum = "d9b5e0d321d61de16390ed273b647ce51605b575916d3c25e6ddf27a1e140035" dependencies = [ + "cfg-if", "libc", + "windows-sys 0.59.0", ] [[package]] @@ -5374,12 +5328,12 @@ dependencies = [ "common-path", "derive-syn-parse", "once_cell", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "regex", "syn 2.0.79", "termcolor", - "toml 0.8.12", + "toml 0.8.19", "walkdir", ] @@ -5400,9 +5354,9 @@ checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" [[package]] name = "downcast-rs" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "dtoa" @@ -5412,9 +5366,9 @@ checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" [[package]] name = "dunce" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "dyn-clonable" @@ -5432,22 +5386,22 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "558e40ea573c374cf53507fd240b7ee2f5477df7cfebdb97323ec61c719399c5" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 1.0.109", ] [[package]] name = "dyn-clone" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "ecdsa" -version = "0.16.8" +version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", "digest 0.10.7", @@ -5460,9 +5414,9 @@ dependencies = [ [[package]] name = "ed25519" -version = "2.2.2" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ "pkcs8", "signature", @@ -5479,7 +5433,7 @@ dependencies = [ "rand_core 0.6.4", "serde", "sha2 0.10.8", - "subtle 2.5.0", + "subtle 2.6.1", "zeroize", ] @@ -5534,7 +5488,7 @@ dependencies = [ "rand_core 0.6.4", "sec1", "serdect", - "subtle 2.5.0", + "subtle 2.6.1", "zeroize", ] @@ -5580,9 +5534,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] @@ -5594,59 +5548,59 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 1.0.109", ] [[package]] name = "enum-as-inner" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" dependencies = [ - "heck 0.4.1", - "proc-macro2 1.0.86", + "heck 0.5.0", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] [[package]] name = "enumflags2" -version = "0.7.7" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c041f5090df68b32bcd905365fd51769c8b9d553fe87fde0b683534f10c01bd2" +checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" dependencies = [ "enumflags2_derive", ] [[package]] name = "enumflags2_derive" -version = "0.7.7" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" +checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] [[package]] name = "enumn" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fd000fd6988e73bbe993ea3db9b1aa64906ab88766d654973924340c8cddb42" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] [[package]] name = "env_filter" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" dependencies = [ "log", "regex", @@ -5664,9 +5618,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" dependencies = [ "humantime", "is-terminal", @@ -5677,9 +5631,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.3" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" dependencies = [ "anstream", "anstyle", @@ -5717,11 +5671,12 @@ dependencies = [ [[package]] name = "erased-serde" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b73807008a3c7f171cc40312f37d95ef0396e048b5848d775f54b1a4dd4a0d3" +checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" dependencies = [ "serde", + "typeid", ] [[package]] @@ -5736,32 +5691,32 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.2" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ - "errno-dragonfly", "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "ethabi-decode" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "09d398648d65820a727d6a81e58b962f874473396a047e4c30bafe3240953417" dependencies = [ - "cc", - "libc", + "ethereum-types 0.14.1", + "tiny-keccak", ] [[package]] -name = "ethabi-decode" -version = "1.1.0" +name = "ethbloom" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9af52ec57c5147716872863c2567c886e7d62f539465b94352dbc0108fe5293" +checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" dependencies = [ - "ethereum-types", + "crunchy", + "fixed-hash", "tiny-keccak", ] @@ -5780,13 +5735,25 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "ethereum-types" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" +dependencies = [ + "ethbloom 0.13.0", + "fixed-hash", + "primitive-types 0.12.2", + "uint 0.9.5", +] + [[package]] name = "ethereum-types" version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ab15ed80916029f878e0267c3a9f92b67df55e79af370bf66199059ae2b4ee3" dependencies = [ - "ethbloom", + "ethbloom 0.14.1", "fixed-hash", "impl-codec 0.7.0", "impl-rlp", @@ -5804,7 +5771,18 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" -version = "4.0.3" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" dependencies = [ @@ -5814,9 +5792,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.2.0" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" dependencies = [ "concurrent-queue", "parking", @@ -5829,7 +5807,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ - "event-listener 5.2.0", + "event-listener 5.3.1", "pin-project-lite", ] @@ -5852,16 +5830,16 @@ dependencies = [ "file-guard", "fs-err", "prettyplease", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] [[package]] name = "eyre" -version = "0.6.8" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" dependencies = [ "indenter", "once_cell", @@ -5890,9 +5868,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "fastrlp" @@ -5900,7 +5878,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "auto_impl", "bytes", ] @@ -5922,9 +5900,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb42427514b063d97ce21d5199f36c0c307d981434a6be32582bc79fe5bd2303" dependencies = [ "expander", - "indexmap 2.2.3", - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "indexmap 2.6.0", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -5962,7 +5940,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ "rand_core 0.6.4", - "subtle 2.5.0", + "subtle 2.6.1", ] [[package]] @@ -5980,9 +5958,9 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.5" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "file-guard" @@ -6000,20 +5978,20 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84f2e425d9790201ba4af4630191feac6dcc98765b118d4d18e91d23c2353866" dependencies = [ - "env_logger 0.10.1", + "env_logger 0.10.2", "log", ] [[package]] name = "filetime" -version = "0.2.22" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", - "windows-sys 0.48.0", + "libredox", + "windows-sys 0.59.0", ] [[package]] @@ -6090,12 +6068,12 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.27" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.8.0", ] [[package]] @@ -6113,6 +6091,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" + [[package]] name = "foreign-types" version = "0.3.2" @@ -6203,7 +6187,7 @@ dependencies = [ "Inflector", "array-bytes", "chrono", - "clap 4.5.13", + "clap 4.5.20", "comfy-table", "frame-benchmarking", "frame-support", @@ -6264,8 +6248,8 @@ dependencies = [ "frame-election-provider-support", "frame-support", "parity-scale-codec", - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.87", "quote 1.0.37", "scale-info", "sp-arithmetic 23.0.0", @@ -6295,7 +6279,7 @@ dependencies = [ name = "frame-election-solution-type-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 4.5.13", + "clap 4.5.20", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-support", @@ -6378,7 +6362,7 @@ dependencies = [ name = "frame-omni-bencher" version = "0.1.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.20", "cumulus-primitives-proof-size-hostfunction", "frame-benchmarking-cli", "log", @@ -6394,7 +6378,7 @@ version = "0.35.0" dependencies = [ "futures", "indicatif", - "jsonrpsee 0.24.3", + "jsonrpsee 0.24.6", "log", "parity-scale-codec", "serde", @@ -6473,8 +6457,8 @@ dependencies = [ "macro_magic", "parity-scale-codec", "pretty_assertions", - "proc-macro-warning 1.0.0", - "proc-macro2 1.0.86", + "proc-macro-warning 1.0.2", + "proc-macro2 1.0.87", "quote 1.0.37", "regex", "scale-info", @@ -6492,8 +6476,8 @@ name = "frame-support-procedural-tools" version = "10.0.0" dependencies = [ "frame-support-procedural-tools-derive", - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -6502,7 +6486,7 @@ dependencies = [ name = "frame-support-procedural-tools-derive" version = "11.0.0" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -6627,9 +6611,12 @@ dependencies = [ [[package]] name = "fs-err" -version = "2.9.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0845fa252299212f0389d64ba26f34fa32cfe41588355f21ed507c59a0f64541" +checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" +dependencies = [ + "autocfg", +] [[package]] name = "fs2" @@ -6647,7 +6634,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29f9df8a11882c4e3335eb2d18a0137c505d9ca927470b0cac9c6f0ae07d28f7" dependencies = [ - "rustix 0.38.21", + "rustix 0.38.37", "windows-sys 0.48.0", ] @@ -6665,9 +6652,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -6690,9 +6677,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -6700,15 +6687,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -6718,9 +6705,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" @@ -6743,7 +6730,7 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ - "fastrand 2.1.0", + "fastrand 2.1.1", "futures-core", "futures-io", "parking", @@ -6752,11 +6739,11 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -6768,20 +6755,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35bd3cf68c183738046838e300353e4716c674dc5e56890de4826801a6622a28" dependencies = [ "futures-io", - "rustls 0.21.7", + "rustls 0.21.12", ] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-timer" @@ -6791,9 +6778,9 @@ checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -6861,9 +6848,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -6882,11 +6869,11 @@ dependencies = [ [[package]] name = "ghash" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" dependencies = [ - "opaque-debug 0.3.0", + "opaque-debug 0.3.1", "polyval", ] @@ -6903,9 +6890,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" dependencies = [ "fallible-iterator 0.3.0", "stable_deref_trait", @@ -6925,9 +6912,9 @@ checksum = "9985c9503b412198aa4197559e9a318524ebc4519c229bfa05a535828c950b9d" [[package]] name = "gloo-timers" -version = "0.2.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" dependencies = [ "futures-channel", "futures-core", @@ -6982,9 +6969,9 @@ dependencies = [ [[package]] name = "governor" -version = "0.6.0" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "821239e5672ff23e2a7060901fa622950bbd80b649cdaadd78d1c1767ed14eb4" +checksum = "68a7f542ee6b35af73b06abc0dad1c1bae89964e4e253bc4b587b91c9637867b" dependencies = [ "cfg-if", "dashmap", @@ -6993,9 +6980,11 @@ dependencies = [ "no-std-compat", "nonzero_ext", "parking_lot 0.12.3", + "portable-atomic", "quanta", "rand", "smallvec", + "spinning_top", ] [[package]] @@ -7006,7 +6995,7 @@ checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", "rand_core 0.6.4", - "subtle 2.5.0", + "subtle 2.6.1", ] [[package]] @@ -7020,8 +7009,8 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http 0.2.9", - "indexmap 2.2.3", + "http 0.2.12", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -7030,9 +7019,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" dependencies = [ "atomic-waker", "bytes", @@ -7040,7 +7029,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.1.0", - "indexmap 2.2.3", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -7049,15 +7038,19 @@ dependencies = [ [[package]] name = "half" -version = "1.8.2" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] [[package]] name = "handlebars" -version = "5.1.0" +version = "5.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab283476b99e66691dee3f1640fea91487a8d81f50fb5ecc75538f8f8879a1e4" +checksum = "d08485b96a0e6393e9e4d1b8d48cf74ad6c063cd905eb33f42c1ce3f0377539b" dependencies = [ "log", "pest", @@ -7111,6 +7104,17 @@ dependencies = [ "serde", ] +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "hashlink" version = "0.8.4" @@ -7156,6 +7160,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "hex" version = "0.4.3" @@ -7164,9 +7174,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hex-conservative" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ed443af458ccb6d81c1e7e661545f94d3176752fb1df2f543b902a1e0f51e2" +checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" [[package]] name = "hex-literal" @@ -7183,7 +7193,7 @@ dependencies = [ "async-trait", "cfg-if", "data-encoding", - "enum-as-inner 0.6.0", + "enum-as-inner 0.6.1", "futures-channel", "futures-io", "futures-util", @@ -7269,14 +7279,14 @@ dependencies = [ [[package]] name = "honggfuzz" -version = "0.5.55" +version = "0.5.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "848e9c511092e0daa0a35a63e8e6e475a3e8f870741448b9f6028d69b142f18e" +checksum = "7c76b6234c13c9ea73946d1379d33186151148e0da231506b964b44f3d023505" dependencies = [ "arbitrary", "lazy_static", - "memmap2 0.5.10", - "rustc_version 0.4.0", + "memmap2 0.9.5", + "rustc_version 0.4.1", ] [[package]] @@ -7292,9 +7302,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.9" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -7314,20 +7324,20 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http 0.2.9", + "http 0.2.12", "pin-project-lite", ] [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http 1.1.0", @@ -7342,7 +7352,7 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "pin-project-lite", ] @@ -7354,9 +7364,9 @@ checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -7372,17 +7382,17 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.29" +version = "0.14.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" +checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", "h2 0.3.26", - "http 0.2.9", - "http-body 0.4.5", + "http 0.2.12", + "http-body 0.4.6", "httparse", "httpdate", "itoa", @@ -7396,16 +7406,16 @@ dependencies = [ [[package]] name = "hyper" -version = "1.3.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.5", + "h2 0.4.6", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "httparse", "httpdate", "itoa", @@ -7422,10 +7432,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", - "http 0.2.9", - "hyper 0.14.29", + "http 0.2.12", + "hyper 0.14.30", "log", - "rustls 0.21.7", + "rustls 0.21.12", "rustls-native-certs 0.6.3", "tokio", "tokio-rustls 0.24.1", @@ -7433,21 +7443,21 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.2" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.3.1", + "hyper 1.4.1", "hyper-util", "log", - "rustls 0.23.10", + "rustls 0.23.14", "rustls-pki-types", "tokio", "tokio-rustls 0.26.0", "tower-service", - "webpki-roots 0.26.3", + "webpki-roots 0.26.6", ] [[package]] @@ -7456,7 +7466,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.29", + "hyper 0.14.30", "pin-project-lite", "tokio", "tokio-io-timeout", @@ -7469,7 +7479,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper 0.14.29", + "hyper 0.14.30", "native-tls", "tokio", "tokio-native-tls", @@ -7477,36 +7487,35 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.5" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" dependencies = [ "bytes", "futures-channel", "futures-util", "http 1.1.0", - "http-body 1.0.0", - "hyper 1.3.1", + "http-body 1.0.1", + "hyper 1.4.1", "pin-project-lite", "socket2 0.5.7", "tokio", - "tower", "tower-service", "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.57" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows 0.48.0", + "windows-core 0.52.0", ] [[package]] @@ -7571,7 +7580,7 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6b0422c86d7ce0e97169cc42e04ae643caf278874a7a3c87b8150a220dc7e1e" dependencies = [ - "async-io 2.3.3", + "async-io 2.3.4", "core-foundation", "fnv", "futures", @@ -7594,8 +7603,8 @@ dependencies = [ "attohttpc", "bytes", "futures", - "http 0.2.9", - "hyper 0.14.29", + "http 0.2.12", + "hyper 0.14.30", "log", "rand", "tokio", @@ -7676,27 +7685,27 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 1.0.109", ] [[package]] name = "include_dir" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" dependencies = [ "include_dir_macros", ] [[package]] name = "include_dir_macros" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", ] @@ -7719,12 +7728,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.3" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.0", ] [[package]] @@ -7735,9 +7744,9 @@ checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" [[package]] name = "indicatif" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" +checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" dependencies = [ "console", "instant", @@ -7799,7 +7808,7 @@ dependencies = [ "socket2 0.5.7", "widestring", "windows-sys 0.48.0", - "winreg 0.50.0", + "winreg", ] [[package]] @@ -7827,30 +7836,36 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.8.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "is-terminal" -version = "0.4.9" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ - "hermit-abi 0.3.9", - "rustix 0.38.21", - "windows-sys 0.48.0", + "hermit-abi 0.4.0", + "libc", + "windows-sys 0.52.0", ] [[package]] name = "is_executable" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa9acdc6d67b75e626ad644734e8bc6df893d9cd2a834129065d3dd6158ea9c8" +checksum = "d4a1b5bad6f9072935961dfbf1cced2f3d129963d091b6f69f007fe04e758ae2" dependencies = [ "winapi", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "isahc" version = "1.7.2" @@ -7865,7 +7880,7 @@ dependencies = [ "encoding_rs", "event-listener 2.5.3", "futures-lite 1.13.0", - "http 0.2.9", + "http 0.2.12", "log", "mime", "once_cell", @@ -7905,11 +7920,20 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jemalloc_pprof" @@ -7959,9 +7983,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -8032,16 +8056,16 @@ dependencies = [ [[package]] name = "jsonrpsee" -version = "0.24.3" +version = "0.24.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ec465b607a36dc5dd45d48b7689bc83f679f66a3ac6b6b21cc787a11e0f8685" +checksum = "02f01f48e04e0d7da72280ab787c9943695699c9b32b99158ece105e8ad0afea" dependencies = [ - "jsonrpsee-core 0.24.3", - "jsonrpsee-http-client 0.24.3", + "jsonrpsee-core 0.24.6", + "jsonrpsee-http-client 0.24.6", "jsonrpsee-proc-macros", "jsonrpsee-server", - "jsonrpsee-types 0.24.3", - "jsonrpsee-ws-client 0.24.3", + "jsonrpsee-types 0.24.6", + "jsonrpsee-ws-client 0.24.6", "tokio", "tracing", ] @@ -8053,10 +8077,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4978087a58c3ab02efc5b07c5e5e2803024536106fd5506f558db172c889b3aa" dependencies = [ "futures-util", - "http 0.2.9", + "http 0.2.12", "jsonrpsee-core 0.22.5", "pin-project", - "rustls-native-certs 0.7.0", + "rustls-native-certs 0.7.3", "rustls-pki-types", "soketto 0.7.1", "thiserror", @@ -8078,7 +8102,7 @@ dependencies = [ "http 1.1.0", "jsonrpsee-core 0.23.2", "pin-project", - "rustls 0.23.10", + "rustls 0.23.14", "rustls-pki-types", "rustls-platform-verifier", "soketto 0.8.0", @@ -8092,16 +8116,16 @@ dependencies = [ [[package]] name = "jsonrpsee-client-transport" -version = "0.24.3" +version = "0.24.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f0977f9c15694371b8024c35ab58ca043dbbf4b51ccb03db8858a021241df1" +checksum = "d80eccbd47a7b9f1e67663fd846928e941cb49c65236e297dd11c9ea3c5e3387" dependencies = [ "base64 0.22.1", "futures-util", "http 1.1.0", - "jsonrpsee-core 0.24.3", + "jsonrpsee-core 0.24.6", "pin-project", - "rustls 0.23.10", + "rustls 0.23.14", "rustls-pki-types", "rustls-platform-verifier", "soketto 0.8.0", @@ -8124,7 +8148,7 @@ dependencies = [ "beef", "futures-timer", "futures-util", - "hyper 0.14.29", + "hyper 0.14.30", "jsonrpsee-types 0.22.5", "pin-project", "rustc-hash 1.1.0", @@ -8160,18 +8184,18 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.24.3" +version = "0.24.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e942c55635fbf5dc421938b8558a8141c7e773720640f4f1dbe1f4164ca4e221" +checksum = "3c2709a32915d816a6e8f625bf72cf74523ebe5d8829f895d6b041b1d3137818" dependencies = [ "async-trait", "bytes", "futures-timer", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", - "jsonrpsee-types 0.24.3", + "jsonrpsee-types 0.24.6", "parking_lot 0.12.3", "pin-project", "rand", @@ -8191,7 +8215,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ccf93fc4a0bfe05d851d37d7c32b7f370fe94336b52a2f0efc5f1981895c2e5" dependencies = [ "async-trait", - "hyper 0.14.29", + "hyper 0.14.30", "hyper-rustls 0.24.2", "jsonrpsee-core 0.22.5", "jsonrpsee-types 0.22.5", @@ -8206,19 +8230,19 @@ dependencies = [ [[package]] name = "jsonrpsee-http-client" -version = "0.24.3" +version = "0.24.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33774602df12b68a2310b38a535733c477ca4a498751739f89fe8dbbb62ec4c" +checksum = "cc54db939002b030e794fbfc9d5a925aa2854889c5a2f0352b0bffa54681707e" dependencies = [ "async-trait", "base64 0.22.1", - "http-body 1.0.0", - "hyper 1.3.1", - "hyper-rustls 0.27.2", + "http-body 1.0.1", + "hyper 1.4.1", + "hyper-rustls 0.27.3", "hyper-util", - "jsonrpsee-core 0.24.3", - "jsonrpsee-types 0.24.3", - "rustls 0.23.10", + "jsonrpsee-core 0.24.6", + "jsonrpsee-types 0.24.6", + "rustls 0.23.14", "rustls-platform-verifier", "serde", "serde_json", @@ -8231,31 +8255,31 @@ dependencies = [ [[package]] name = "jsonrpsee-proc-macros" -version = "0.24.3" +version = "0.24.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b07a2daf52077ab1b197aea69a5c990c060143835bf04c77070e98903791715" +checksum = "3a9a4b2eaba8cc928f49c4ccf4fcfa65b690a73997682da99ed08f3393b51f07" dependencies = [ "heck 0.5.0", - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] [[package]] name = "jsonrpsee-server" -version = "0.24.3" +version = "0.24.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038fb697a709bec7134e9ccbdbecfea0e2d15183f7140254afef7c5610a3f488" +checksum = "e30110d0f2d7866c8cc6c86483bdab2eb9f4d2f0e20db55518b2bca84651ba8e" dependencies = [ "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", - "hyper 1.3.1", + "hyper 1.4.1", "hyper-util", - "jsonrpsee-core 0.24.3", - "jsonrpsee-types 0.24.3", + "jsonrpsee-core 0.24.6", + "jsonrpsee-types 0.24.6", "pin-project", "route-recognizer", "serde", @@ -8297,9 +8321,9 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.24.3" +version = "0.24.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b67d6e008164f027afbc2e7bb79662650158d26df200040282d2aa1cbb093b" +checksum = "1ca331cd7b3fe95b33432825c2d4c9f5a43963e207fdc01ae67f9fd80ab0930f" dependencies = [ "http 1.1.0", "serde", @@ -8322,14 +8346,14 @@ dependencies = [ [[package]] name = "jsonrpsee-ws-client" -version = "0.24.3" +version = "0.24.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "992bf67d1132f88edf4a4f8cff474cf01abb2be203004a2b8e11c2b20795b99e" +checksum = "755ca3da1c67671f1fae01cd1a47f41dfb2233a8f19a643e587ab0a663942044" dependencies = [ "http 1.1.0", - "jsonrpsee-client-transport 0.24.3", - "jsonrpsee-core 0.24.3", - "jsonrpsee-types 0.24.3", + "jsonrpsee-client-transport 0.24.6", + "jsonrpsee-core 0.24.6", + "jsonrpsee-types 0.24.6", "url", ] @@ -8363,9 +8387,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ "cpufeatures", ] @@ -8428,9 +8452,9 @@ dependencies = [ "either", "futures", "home", - "http 0.2.9", - "http-body 0.4.5", - "hyper 0.14.29", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.30", "hyper-rustls 0.24.2", "hyper-timeout", "jsonpath-rust", @@ -8439,8 +8463,8 @@ dependencies = [ "pem 3.0.4", "pin-project", "rand", - "rustls 0.21.7", - "rustls-pemfile 1.0.3", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", "secrecy", "serde", "serde_json", @@ -8462,7 +8486,7 @@ checksum = "b5bba93d054786eba7994d03ce522f368ef7d48c88a1826faa28478d85fb63ae" dependencies = [ "chrono", "form_urlencoded", - "http 0.2.9", + "http 0.2.12", "json-patch", "k8s-openapi", "once_cell", @@ -8550,9 +8574,9 @@ dependencies = [ [[package]] name = "landlock" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1530c5b973eeed4ac216af7e24baf5737645a6272e361f1fb95710678b67d9cc" +checksum = "9baa9eeb6e315942429397e617a190f4fdc696ef1ee0342939d641029cbb4ea7" dependencies = [ "enumflags2", "libc", @@ -8579,9 +8603,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libflate" @@ -8616,12 +8640,12 @@ dependencies = [ [[package]] name = "libloading" -version = "0.7.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "winapi", + "windows-targets 0.52.6", ] [[package]] @@ -8632,9 +8656,9 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libnghttp2-sys" -version = "0.1.9+1.58.0" +version = "0.1.10+1.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b57e858af2798e167e709b9d969325b6d8e9d50232fcbc494d7d54f976854a64" +checksum = "959c25552127d2e1fa72f0e52548ec04fc386e827ba71a7bd01db46a447dc135" dependencies = [ "cc", "libc", @@ -8671,7 +8695,7 @@ dependencies = [ "libp2p-wasm-ext", "libp2p-websocket", "libp2p-yamux", - "multiaddr 0.18.1", + "multiaddr 0.18.2", "pin-project", "rw-stream-sink", "thiserror", @@ -8714,7 +8738,7 @@ dependencies = [ "instant", "libp2p-identity", "log", - "multiaddr 0.18.1", + "multiaddr 0.18.2", "multihash 0.19.1", "multistream-select", "once_cell", @@ -8760,7 +8784,7 @@ dependencies = [ "libp2p-identity", "libp2p-swarm", "log", - "lru 0.12.3", + "lru 0.12.5", "quick-protobuf 0.8.1", "quick-protobuf-codec", "smallvec", @@ -8792,7 +8816,7 @@ version = "0.44.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16ea178dabba6dde6ffc260a8e0452ccdc8f79becf544946692fff9d412fc29d" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "asynchronous-codec", "bytes", "either", @@ -8865,7 +8889,7 @@ dependencies = [ "libp2p-core", "libp2p-identity", "log", - "multiaddr 0.18.1", + "multiaddr 0.18.2", "multihash 0.19.1", "once_cell", "quick-protobuf 0.8.1", @@ -8914,7 +8938,7 @@ dependencies = [ "quinn 0.10.2", "rand", "ring 0.16.20", - "rustls 0.21.7", + "rustls 0.21.12", "socket2 0.5.7", "thiserror", "tokio", @@ -8969,7 +8993,7 @@ checksum = "c4d5ec2a3df00c7836d7696c136274c9c59705bac69133253696a6c932cd1d74" dependencies = [ "heck 0.4.1", "proc-macro-warning 0.4.2", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -9003,8 +9027,8 @@ dependencies = [ "libp2p-identity", "rcgen", "ring 0.16.20", - "rustls 0.21.7", - "rustls-webpki 0.101.4", + "rustls 0.21.12", + "rustls-webpki 0.101.7", "thiserror", "x509-parser 0.15.1", "yasna", @@ -9058,7 +9082,7 @@ dependencies = [ "soketto 0.8.0", "thiserror", "url", - "webpki-roots 0.25.2", + "webpki-roots 0.25.4", ] [[package]] @@ -9074,6 +9098,17 @@ dependencies = [ "yamux", ] +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", + "redox_syscall 0.5.7", +] + [[package]] name = "librocksdb-sys" version = "0.11.0+8.1.1" @@ -9116,7 +9151,7 @@ checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" dependencies = [ "crunchy", "digest 0.9.0", - "subtle 2.5.0", + "subtle 2.6.1", ] [[package]] @@ -9139,9 +9174,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.12" +version = "1.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" +checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" dependencies = [ "cc", "libc", @@ -9175,9 +9210,9 @@ dependencies = [ [[package]] name = "linregress" -version = "0.5.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de0b5f52a9f84544d268f5fabb71b38962d6aa3c6600b8bcd27d44ccf9c9c45" +checksum = "a9eda9dcf4f2a99787827661f312ac3219292549c2ee992bf9a6248ffb066bf7" dependencies = [ "nalgebra", ] @@ -9196,9 +9231,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.10" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lioness" @@ -9245,7 +9280,7 @@ dependencies = [ "futures-timer", "hex-literal", "hickory-resolver", - "indexmap 2.2.3", + "indexmap 2.6.0", "libc", "mockall 0.13.0", "multiaddr 0.17.1", @@ -9255,7 +9290,7 @@ dependencies = [ "parking_lot 0.12.3", "pin-project", "prost 0.12.6", - "prost-build 0.13.2", + "prost-build 0.13.3", "rand", "rcgen", "ring 0.16.20", @@ -9290,9 +9325,9 @@ checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -9310,17 +9345,17 @@ dependencies = [ [[package]] name = "lru" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eedb2bdbad7e0634f83989bf596f497b070130daaa398ab22d84c39e266deec5" +checksum = "a4a83fb7698b3643a0e34f9ae6f2e8f0178c0fd42f8b59d493aa271ff3a5bf21" [[package]] name = "lru" -version = "0.12.3" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.14.5", + "hashbrown 0.15.0", ] [[package]] @@ -9334,19 +9369,18 @@ dependencies = [ [[package]] name = "lz4" -version = "1.24.0" +version = "1.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9e2dd86df36ce760a60f6ff6ad526f7ba1f14ba0356f8254fb6905e6494df1" +checksum = "4d1febb2b4a79ddd1980eede06a8f7902197960aa0383ffcfdd62fe723036725" dependencies = [ - "libc", "lz4-sys", ] [[package]] name = "lz4-sys" -version = "1.9.4" +version = "1.11.1+lz4-1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900" +checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" dependencies = [ "cc", "libc", @@ -9361,15 +9395,6 @@ dependencies = [ "libc", ] -[[package]] -name = "mach2" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" -dependencies = [ - "libc", -] - [[package]] name = "macro_magic" version = "0.5.1" @@ -9391,7 +9416,7 @@ dependencies = [ "const-random", "derive-syn-parse", "macro_magic_core_macros", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -9402,7 +9427,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -9469,9 +9494,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "matrixmultiply" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090126dc04f95dc0d1c1c91f61bdd474b3930ca064c1edc8a849da2c6cbe1e77" +checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" dependencies = [ "autocfg", "rawpointer", @@ -9485,11 +9510,11 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memfd" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc89ccdc6e10d6907450f753537ebc5c5d3460d2e4e62ea74bd571db62c0f9e" +checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" dependencies = [ - "rustix 0.37.23", + "rustix 0.38.37", ] [[package]] @@ -9503,9 +9528,9 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45fd3a57831bf88bc63f8cebc0cf956116276e97fef3966103e96416209f7c92" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" dependencies = [ "libc", ] @@ -9519,15 +9544,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - [[package]] name = "memory-db" version = "0.32.0" @@ -9587,6 +9603,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minicov" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c71e683cd655513b99affab7d317deb690528255a0d5f717f1024093c12b169" +dependencies = [ + "cc", + "walkdir", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -9597,11 +9623,11 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" name = "minimal-template-node" version = "0.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.20", "docify", "futures", "futures-timer", - "jsonrpsee 0.24.3", + "jsonrpsee 0.24.6", "minimal-template-runtime", "polkadot-sdk", "serde_json", @@ -9620,13 +9646,22 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + [[package]] name = "mio" version = "1.0.2" @@ -9646,7 +9681,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daa3eb39495d8e2e2947a1d862852c90cc6a4a8845f8b41c8829cb9fcc047f4a" dependencies = [ "arrayref", - "arrayvec 0.7.4", + "arrayvec 0.7.6", "bitflags 1.3.2", "blake2 0.10.6", "c2-chacha", @@ -9659,7 +9694,7 @@ dependencies = [ "rand", "rand_chacha", "rand_distr", - "subtle 2.5.0", + "subtle 2.6.1", "thiserror", "zeroize", ] @@ -9691,7 +9726,7 @@ dependencies = [ name = "mmr-rpc" version = "28.0.0" dependencies = [ - "jsonrpsee 0.24.3", + "jsonrpsee 0.24.6", "parity-scale-codec", "serde", "serde_json", @@ -9727,7 +9762,7 @@ dependencies = [ "downcast", "fragile", "mockall_derive 0.13.0", - "predicates 3.0.3", + "predicates 3.1.2", "predicates-tree", ] @@ -9738,7 +9773,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ce75669015c4f47b289fd4d4f56e894e4c96003ffdf3ac51313126f94c6cbb" dependencies = [ "cfg-if", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 1.0.109", ] @@ -9750,7 +9785,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "341014e7f530314e9a1fdbc7400b244efea7122662c96bfa248c31da5bfb2020" dependencies = [ "cfg-if", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -9782,9 +9817,9 @@ dependencies = [ [[package]] name = "multiaddr" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b852bc02a2da5feed68cd14fa50d0774b92790a5bdbfa932a813926c8472070" +checksum = "fe6351f60b488e04c1d21bc69e56b89cb3f5e8f5d22557d6e8031bdfd79b6961" dependencies = [ "arrayref", "byteorder", @@ -9795,7 +9830,7 @@ dependencies = [ "percent-encoding", "serde", "static_assertions", - "unsigned-varint 0.7.2", + "unsigned-varint 0.8.0", "url", ] @@ -9843,7 +9878,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835d6ff01d610179fbce3de1694d007e500bf33a7f29689838941d6bf783ae40" dependencies = [ "blake2b_simd 1.0.2", - "blake2s_simd 1.0.1", + "blake2s_simd 1.0.2", "blake3", "core2", "digest 0.10.7", @@ -9860,7 +9895,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfd8a792c1694c6da4f68db0a9d707c72bd260994da179e6030a5dcee00bb815" dependencies = [ "blake2b_simd 1.0.2", - "blake2s_simd 1.0.1", + "blake2s_simd 1.0.2", "blake3", "core2", "digest 0.10.7", @@ -9882,13 +9917,13 @@ dependencies = [ [[package]] name = "multihash-derive" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc076939022111618a5026d3be019fd8b366e76314538ff9a1b59ffbcbf98bcd" +checksum = "1d6d4752e6230d8ef7adf7bd5d8c4b1f6561c1014c5ba9a37445ccefe18aa1db" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 1.1.3", "proc-macro-error", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 1.0.109", "synstructure 0.12.6", @@ -9896,9 +9931,9 @@ dependencies = [ [[package]] name = "multimap" -version = "0.8.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" [[package]] name = "multistream-select" @@ -9916,13 +9951,12 @@ dependencies = [ [[package]] name = "nalgebra" -version = "0.32.3" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307ed9b18cc2423f29e83f84fd23a8e73628727990181f18641a8b5dc2ab1caa" +checksum = "3c4b5f057b303842cf3262c27e465f4c303572e7f6b0648f60e16248ac3397f4" dependencies = [ "approx", "matrixmultiply", - "nalgebra-macros", "num-complex", "num-rational", "num-traits", @@ -9930,17 +9964,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "nalgebra-macros" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91761aed67d03ad966ef783ae962ef9bbaca728d2dd7ceb7939ec110fffad998" -dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.37", - "syn 1.0.109", -] - [[package]] name = "names" version = "0.14.0" @@ -10029,9 +10052,9 @@ dependencies = [ [[package]] name = "netlink-sys" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6471bf08e7ac0135876a9581bf3217ef0333c191c128d34878079f42ee150411" +checksum = "416060d346fbaf1f23f9512963e3e878f1a78e707cb699ba9215761754244307" dependencies = [ "bytes", "futures", @@ -10042,9 +10065,9 @@ dependencies = [ [[package]] name = "network-interface" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae72fd9dbd7f55dda80c00d66acc3b2130436fcba9ea89118fc508eaae48dfb0" +checksum = "a4a43439bf756eed340bdf8feba761e2d50c7d47175d87545cd5cbe4a137c4d1" dependencies = [ "cc", "libc", @@ -10065,14 +10088,13 @@ dependencies = [ [[package]] name = "nix" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", - "static_assertions", ] [[package]] @@ -10115,7 +10137,7 @@ name = "node-bench" version = "0.9.0-dev" dependencies = [ "array-bytes", - "clap 4.5.13", + "clap 4.5.20", "derive_more", "fs_extra", "futures", @@ -10157,7 +10179,7 @@ dependencies = [ name = "node-rpc" version = "3.0.0-dev" dependencies = [ - "jsonrpsee 0.24.3", + "jsonrpsee 0.24.6", "mmr-rpc", "node-primitives", "pallet-transaction-payment-rpc", @@ -10191,7 +10213,7 @@ dependencies = [ name = "node-runtime-generate-bags" version = "3.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.20", "generate-bags", "kitchensink-runtime", ] @@ -10200,7 +10222,7 @@ dependencies = [ name = "node-template-release" version = "3.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.20", "flate2", "fs_extra", "glob", @@ -10310,9 +10332,9 @@ dependencies = [ [[package]] name = "num" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ "num-bigint", "num-complex", @@ -10324,20 +10346,19 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-complex" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", ] @@ -10354,7 +10375,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -10365,25 +10386,24 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "itoa", ] [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.43" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", @@ -10392,11 +10412,10 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ - "autocfg", "num-bigint", "num-integer", "num-traits", @@ -10404,9 +10423,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", @@ -10460,9 +10479,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.1" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] @@ -10478,24 +10497,24 @@ dependencies = [ [[package]] name = "oid-registry" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c958dd45046245b9c3c2547369bb634eb461670b2e7e0de552905801a648d1d" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" dependencies = [ - "asn1-rs 0.6.1", + "asn1-rs 0.6.2", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "oorandom" -version = "11.1.3" +version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] name = "opaque-debug" @@ -10505,15 +10524,15 @@ checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.64" +version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -10530,7 +10549,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -10543,9 +10562,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.102" +version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", @@ -10583,11 +10602,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7b1d40dd8f367db3c65bec8d3dd47d4a604ee8874480738f93191bddab4e0e0" dependencies = [ "expander", - "indexmap 2.2.3", + "indexmap 2.6.0", "itertools 0.11.0", "petgraph", - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 1.0.109", ] @@ -10603,9 +10622,9 @@ dependencies = [ [[package]] name = "os_str_bytes" -version = "6.5.1" +version = "6.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" [[package]] name = "overload" @@ -11256,7 +11275,7 @@ dependencies = [ "parity-wasm", "sp-runtime 31.0.1", "tempfile", - "toml 0.8.12", + "toml 0.8.19", "twox-hash", ] @@ -11301,7 +11320,7 @@ dependencies = [ name = "pallet-contracts-proc-macro" version = "18.0.0" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -12384,7 +12403,7 @@ dependencies = [ "sp-io 30.0.0", "sp-runtime 31.0.1", "tempfile", - "toml 0.8.12", + "toml 0.8.19", ] [[package]] @@ -12427,7 +12446,7 @@ dependencies = [ name = "pallet-revive-proc-macro" version = "0.1.0" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -12670,8 +12689,8 @@ dependencies = [ name = "pallet-staking-reward-curve" version = "11.0.0" dependencies = [ - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.87", "quote 1.0.37", "sp-runtime 31.0.1", "syn 2.0.79", @@ -12822,7 +12841,7 @@ dependencies = [ name = "pallet-transaction-payment-rpc" version = "30.0.0" dependencies = [ - "jsonrpsee 0.24.3", + "jsonrpsee 0.24.6", "pallet-transaction-payment-rpc-runtime-api", "parity-scale-codec", "sp-api 26.0.0", @@ -13067,7 +13086,7 @@ dependencies = [ name = "parachain-template-node" version = "0.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.20", "color-print", "cumulus-client-cli", "cumulus-client-collator", @@ -13082,7 +13101,7 @@ dependencies = [ "frame-benchmarking", "frame-benchmarking-cli", "futures", - "jsonrpsee 0.24.3", + "jsonrpsee 0.24.6", "log", "pallet-transaction-payment-rpc", "parachain-template-runtime", @@ -13264,7 +13283,7 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e69bf016dc406eff7d53a7d3f7cf1c2e72c82b9088aac1118591e36dd2cd3e9" dependencies = [ - "bitcoin_hashes 0.13.0", + "bitcoin_hashes", "rand", "rand_core 0.6.4", "serde", @@ -13279,9 +13298,9 @@ checksum = "16b56e3a2420138bdb970f84dfb9c774aea80fa0e7371549eedec0d80c209c67" [[package]] name = "parity-db" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59e9ab494af9e6e813c72170f0d3c1de1500990d62c97cc05cc7576f91aa402f" +checksum = "592a28a24b09c9dc20ac8afaa6839abc417c720afe42c12e1e4a9d6aa2508d2e" dependencies = [ "blake2 0.10.6", "crc32fast", @@ -13295,6 +13314,7 @@ dependencies = [ "rand", "siphasher 0.3.11", "snap", + "winapi", ] [[package]] @@ -13303,7 +13323,7 @@ version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "bitvec", "byte-slice-cast", "bytes", @@ -13318,8 +13338,8 @@ version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" dependencies = [ - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 1.0.109", ] @@ -13354,7 +13374,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", - "parking_lot_core 0.9.8", + "parking_lot_core 0.9.10", ] [[package]] @@ -13373,15 +13393,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", + "redox_syscall 0.5.7", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -13398,7 +13418,7 @@ checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", "rand_core 0.6.4", - "subtle 2.5.0", + "subtle 2.6.1", ] [[package]] @@ -13725,19 +13745,20 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.2" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a" +checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" dependencies = [ + "memchr", "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.2" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666d00490d4ac815001da55838c500eafb0320019bbaa44444137c48b443a853" +checksum = "4d3a6e3394ec80feb3b6393c725571754c6188490265c61aaf260810d6b95aa0" dependencies = [ "pest", "pest_generator", @@ -13745,22 +13766,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.2" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ca01446f50dbda87c1786af8770d535423fa8a53aec03b8f4e3d7eb10e0929" +checksum = "94429506bde1ca69d1b5601962c73f4172ab4726571a59ea95931218cb0e930e" dependencies = [ "pest", "pest_meta", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] [[package]] name = "pest_meta" -version = "2.7.2" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56af0a30af74d0445c0bf6d9d051c979b516a1a5af790d251daee76005420a48" +checksum = "ac8a071862e93690b6e34e9a5fb8e33ff3734473ac0245b27232222c4906a33f" dependencies = [ "once_cell", "pest", @@ -13769,30 +13790,30 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.2.3", + "indexmap 2.6.0", ] [[package]] name = "pin-project" -version = "1.1.3" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "baf123a161dde1e524adf36f90bc5d8d3462824a9c43553ad07a8183161189ec" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -13809,6 +13830,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand 2.1.1", + "futures-io", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -13821,21 +13853,21 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "platforms" -version = "3.0.2" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" +checksum = "0e4c7666f2019727f9e8e14bf14456e99c707d780922869f1ba473eee101fa49" [[package]] name = "plotters" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits", "plotters-backend", @@ -13846,15 +13878,15 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] @@ -14015,7 +14047,7 @@ name = "polkadot-cli" version = "7.0.0" dependencies = [ "cfg-if", - "clap 4.5.13", + "clap 4.5.20", "frame-benchmarking-cli", "futures", "log", @@ -14091,7 +14123,7 @@ dependencies = [ "fatality", "futures", "futures-timer", - "indexmap 2.2.3", + "indexmap 2.6.0", "parity-scale-codec", "polkadot-erasure-coding", "polkadot-node-network-protocol", @@ -14687,7 +14719,7 @@ dependencies = [ "futures", "futures-timer", "http-body-util", - "hyper 1.3.1", + "hyper 1.4.1", "hyper-util", "log", "parity-scale-codec", @@ -14871,7 +14903,7 @@ version = "0.1.0" dependencies = [ "assert_cmd", "async-trait", - "clap 4.5.13", + "clap 4.5.20", "color-print", "cumulus-client-cli", "cumulus-client-collator", @@ -14892,7 +14924,7 @@ dependencies = [ "frame-try-runtime", "futures", "futures-timer", - "jsonrpsee 0.24.3", + "jsonrpsee 0.24.6", "log", "nix 0.28.0", "pallet-transaction-payment", @@ -15057,7 +15089,7 @@ dependencies = [ name = "polkadot-rpc" version = "7.0.0" dependencies = [ - "jsonrpsee 0.24.3", + "jsonrpsee 0.24.6", "mmr-rpc", "pallet-transaction-payment-rpc", "polkadot-primitives", @@ -15836,14 +15868,14 @@ dependencies = [ name = "polkadot-statement-distribution" version = "7.0.0" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "assert_matches", "async-channel 1.9.0", "bitvec", "fatality", "futures", "futures-timer", - "indexmap 2.2.3", + "indexmap 2.6.0", "parity-scale-codec", "polkadot-node-network-protocol", "polkadot-node-primitives", @@ -15885,7 +15917,7 @@ dependencies = [ "async-trait", "bincode", "bitvec", - "clap 4.5.13", + "clap 4.5.20", "clap-num", "color-eyre", "colored", @@ -15987,7 +16019,7 @@ version = "1.0.0" dependencies = [ "assert_matches", "async-trait", - "clap 4.5.13", + "clap 4.5.20", "color-eyre", "futures", "futures-timer", @@ -16129,7 +16161,7 @@ dependencies = [ name = "polkadot-voter-bags" version = "7.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.20", "generate-bags", "sp-io 30.0.0", "westend-runtime", @@ -16140,7 +16172,7 @@ name = "polkadot-zombienet-sdk-tests" version = "0.1.0" dependencies = [ "anyhow", - "env_logger 0.11.3", + "env_logger 0.11.5", "log", "parity-scale-codec", "serde", @@ -16257,7 +16289,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c10b2654a8a10a83c260bfb93e97b262cf0017494ab94a65d389e0eda6de6c9c" dependencies = [ "polkavm-common 0.8.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -16269,7 +16301,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c4fdfc49717fb9a196e74a5d28e0bc764eb394a2c803eb11133a31ac996c60c" dependencies = [ "polkavm-common 0.9.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -16281,7 +16313,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d179eddaaef62ce5960faaa2ec9e8f131c81661c8b9365c4d55b275011688534" dependencies = [ "polkavm-common 0.12.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -16322,7 +16354,7 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c7be503e60cf56c0eb785f90aaba4b583b36bff00e93997d93fef97f9553c39" dependencies = [ - "gimli 0.28.0", + "gimli 0.28.1", "hashbrown 0.14.5", "log", "object 0.32.2", @@ -16337,10 +16369,10 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f917b16db9ab13819a738a321b48a2d0d20d9e32dedcff75054148676afbec4" dependencies = [ - "gimli 0.28.0", + "gimli 0.28.1", "hashbrown 0.14.5", "log", - "object 0.36.1", + "object 0.36.5", "polkavm-common 0.12.0", "regalloc2 0.9.3", "rustc-demangle", @@ -16376,16 +16408,17 @@ dependencies = [ [[package]] name = "polling" -version = "3.4.0" +version = "3.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30054e72317ab98eddd8561db0f6524df3367636884b7b21b703e4b280a84a14" +checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" dependencies = [ "cfg-if", "concurrent-queue", + "hermit-abi 0.4.0", "pin-project-lite", - "rustix 0.38.21", + "rustix 0.38.37", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -16395,27 +16428,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ "cpufeatures", - "opaque-debug 0.3.0", + "opaque-debug 0.3.1", "universal-hash", ] [[package]] name = "polyval" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", "cpufeatures", - "opaque-debug 0.3.0", + "opaque-debug 0.3.1", "universal-hash", ] [[package]] name = "portable-atomic" -version = "1.4.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f32154ba0af3a075eefa1eda8bb414ee928f62303a54ea85b8d6638ff1a6ee9e" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] name = "portpicker" @@ -16443,7 +16476,7 @@ dependencies = [ "findshlibs", "libc", "log", - "nix 0.26.2", + "nix 0.26.4", "once_cell", "parking_lot 0.12.3", "smallvec", @@ -16467,9 +16500,12 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "predicates" @@ -16487,27 +16523,26 @@ dependencies = [ [[package]] name = "predicates" -version = "3.0.3" +version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09963355b9f467184c04017ced4a2ba2d75cbcb4e7462690d388233253d4b1a9" +checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" dependencies = [ "anstyle", "difflib", - "itertools 0.10.5", "predicates-core", ] [[package]] name = "predicates-core" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" +checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" [[package]] name = "predicates-tree" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" dependencies = [ "predicates-core", "termtree", @@ -16515,9 +16550,9 @@ dependencies = [ [[package]] name = "pretty_assertions" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ "diff", "yansi", @@ -16525,11 +16560,11 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.12" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62" +checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "syn 2.0.79", ] @@ -16580,21 +16615,21 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.3.1" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" dependencies = [ - "once_cell", - "toml_edit 0.19.15", + "thiserror", + "toml 0.5.11", ] [[package]] name = "proc-macro-crate" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_edit 0.21.0", + "toml_edit 0.22.22", ] [[package]] @@ -16604,7 +16639,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 1.0.109", "version_check", @@ -16616,35 +16651,29 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "version_check", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - [[package]] name = "proc-macro-warning" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d1eaa7fa0aa1929ffdf7eeb6eac234dde6268914a14ad44d23521ab6a9b258e" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] [[package]] name = "proc-macro-warning" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b698b0b09d40e9b7c1a47b132d66a8b54bcd20583d9b6d06e4535e383b4405c" +checksum = "834da187cfe638ae8abb0203f0b33e5ccdb02a28e7199f2f47b3e2754f50edca" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -16660,9 +16689,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" dependencies = [ "unicode-ident", ] @@ -16679,7 +16708,7 @@ dependencies = [ "hex", "lazy_static", "procfs-core", - "rustix 0.38.21", + "rustix 0.38.37", ] [[package]] @@ -16695,9 +16724,9 @@ dependencies = [ [[package]] name = "prometheus" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c" +checksum = "3d33c28a30771f7f96db69893f78b857f7450d7e0237e9c8fc6427a81bae7ed1" dependencies = [ "cfg-if", "fnv", @@ -16725,28 +16754,28 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] [[package]] name = "prometheus-parse" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2aa5feb83bf4b2c8919eaf563f51dbab41183de73ba2353c0e03cd7b6bd892" +checksum = "811031bea65e5a401fb2e1f37d802cca6601e204ac463809a3189352d13b78a5" dependencies = [ "chrono", - "itertools 0.10.5", + "itertools 0.12.1", "once_cell", "regex", ] [[package]] name = "proptest" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" +checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" dependencies = [ "bit-set", "bit-vec", @@ -16756,7 +16785,7 @@ dependencies = [ "rand", "rand_chacha", "rand_xorshift", - "regex-syntax 0.8.2", + "regex-syntax 0.8.5", "rusty-fork", "tempfile", "unarray", @@ -16784,19 +16813,19 @@ dependencies = [ [[package]] name = "prost" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2ecbe40f08db5c006b5764a2645f7f3f141ce756412ac9e1dd6087e6d32995" +checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f" dependencies = [ "bytes", - "prost-derive 0.13.2", + "prost-derive 0.13.3", ] [[package]] name = "prost-build" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80b776a1b2dc779f5ee0641f8ade0125bc1298dd41a9a0c16d8bd57b42d222b1" +checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes", "heck 0.5.0", @@ -16807,7 +16836,7 @@ dependencies = [ "petgraph", "prettyplease", "prost 0.12.6", - "prost-types 0.12.4", + "prost-types 0.12.6", "regex", "syn 2.0.79", "tempfile", @@ -16815,20 +16844,20 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8650aabb6c35b860610e9cff5dc1af886c9e25073b7b1712a68972af4281302" +checksum = "0c1318b19085f08681016926435853bbf7858f9c082d0999b80550ff5d9abe15" dependencies = [ "bytes", "heck 0.5.0", - "itertools 0.12.1", + "itertools 0.13.0", "log", "multimap", "once_cell", "petgraph", "prettyplease", - "prost 0.13.2", - "prost-types 0.13.2", + "prost 0.13.3", + "prost-types 0.13.3", "regex", "syn 2.0.79", "tempfile", @@ -16842,7 +16871,7 @@ checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", "itertools 0.10.5", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 1.0.109", ] @@ -16855,47 +16884,47 @@ checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", "itertools 0.12.1", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] [[package]] name = "prost-derive" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" +checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" dependencies = [ "anyhow", - "itertools 0.12.1", - "proc-macro2 1.0.86", + "itertools 0.13.0", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] [[package]] name = "prost-types" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3235c33eb02c1f1e212abdbe34c78b264b038fb58ca612664343271e36e55ffe" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" dependencies = [ "prost 0.12.6", ] [[package]] name = "prost-types" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60caa6738c7369b940c3d49246a8d1749323674c65cb13010134f5c9bad5b519" +checksum = "4759aa0d3a6232fb8dbdb97b61de2c20047c68aca932c7ed76da9d788508d670" dependencies = [ - "prost 0.13.2", + "prost 0.13.3", ] [[package]] name = "psm" -version = "0.1.21" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +checksum = "aa37f80ca58604976033fae9515a8a2989fc13797d953f7c04fb8fa36a11f205" dependencies = [ "cc", ] @@ -16912,7 +16941,7 @@ dependencies = [ "log", "names", "prost 0.11.9", - "reqwest 0.11.20", + "reqwest 0.11.27", "thiserror", "url", "winapi", @@ -16932,13 +16961,12 @@ dependencies = [ [[package]] name = "quanta" -version = "0.11.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17e662a7a8291a865152364c20c7abc5e60486ab2001e8ec10b24862de0b9ab" +checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5" dependencies = [ "crossbeam-utils", "libc", - "mach2", "once_cell", "raw-cpuid", "wasi", @@ -17017,7 +17045,7 @@ dependencies = [ "quinn-proto 0.10.6", "quinn-udp 0.4.1", "rustc-hash 1.1.0", - "rustls 0.21.7", + "rustls 0.21.12", "thiserror", "tokio", "tracing", @@ -17032,9 +17060,9 @@ dependencies = [ "bytes", "pin-project-lite", "quinn-proto 0.11.8", - "quinn-udp 0.5.4", + "quinn-udp 0.5.5", "rustc-hash 2.0.0", - "rustls 0.23.10", + "rustls 0.23.14", "socket2 0.5.7", "thiserror", "tokio", @@ -17051,7 +17079,7 @@ dependencies = [ "rand", "ring 0.16.20", "rustc-hash 1.1.0", - "rustls 0.21.7", + "rustls 0.21.12", "slab", "thiserror", "tinyvec", @@ -17066,9 +17094,9 @@ checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" dependencies = [ "bytes", "rand", - "ring 0.17.7", + "ring 0.17.8", "rustc-hash 2.0.0", - "rustls 0.23.10", + "rustls 0.23.14", "slab", "thiserror", "tinyvec", @@ -17090,15 +17118,15 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" +checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" dependencies = [ "libc", "once_cell", "socket2 0.5.7", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -17116,7 +17144,7 @@ version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", ] [[package]] @@ -17191,11 +17219,11 @@ dependencies = [ [[package]] name = "raw-cpuid" -version = "10.7.0" +version = "11.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" +checksum = "1ab240315c661615f2ee9f0f2cd32d5a7343a84d5ebcccb99d46e6637565e7b0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", ] [[package]] @@ -17283,30 +17311,21 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.4.1" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", ] [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", - "redox_syscall 0.2.16", + "libredox", "thiserror", ] @@ -17337,7 +17356,7 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -17369,14 +17388,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.6" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.2", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -17390,19 +17409,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" - -[[package]] -name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax 0.8.5", ] [[package]] @@ -17413,15 +17426,15 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "relative-path" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e898588f33fdd5b9420719948f9f2a32c922a246964576f71ba7f24f80610fbc" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "relay-substrate-client" @@ -17436,7 +17449,7 @@ dependencies = [ "finality-relay", "frame-support", "futures", - "jsonrpsee 0.24.3", + "jsonrpsee 0.24.6", "log", "num-traits", "pallet-transaction-payment", @@ -17493,7 +17506,7 @@ dependencies = [ name = "remote-ext-tests-bags-list" version = "1.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.20", "frame-system", "log", "pallet-bags-list-remote-tests", @@ -17506,9 +17519,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.20" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ "base64 0.21.7", "bytes", @@ -17516,9 +17529,9 @@ dependencies = [ "futures-core", "futures-util", "h2 0.3.26", - "http 0.2.9", - "http-body 0.4.5", - "hyper 0.14.29", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.30", "hyper-rustls 0.24.2", "hyper-tls", "ipnet", @@ -17529,11 +17542,13 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.7", - "rustls-pemfile 1.0.3", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration", "tokio", "tokio-native-tls", "tokio-rustls 0.24.1", @@ -17542,15 +17557,15 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.25.2", - "winreg 0.50.0", + "webpki-roots 0.25.4", + "winreg", ] [[package]] name = "reqwest" -version = "0.12.5" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ "base64 0.22.1", "bytes", @@ -17558,10 +17573,10 @@ dependencies = [ "futures-core", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", - "hyper 1.3.1", - "hyper-rustls 0.27.2", + "hyper 1.4.1", + "hyper-rustls 0.27.3", "hyper-util", "ipnet", "js-sys", @@ -17571,13 +17586,13 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn 0.11.5", - "rustls 0.23.10", - "rustls-pemfile 2.0.0", + "rustls 0.23.14", + "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.1", "tokio", "tokio-rustls 0.26.0", "tower-service", @@ -17585,8 +17600,8 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.26.3", - "winreg 0.52.0", + "webpki-roots 0.26.6", + "windows-registry", ] [[package]] @@ -17606,7 +17621,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ "hmac 0.12.1", - "subtle 2.5.0", + "subtle 2.6.1", ] [[package]] @@ -17619,7 +17634,7 @@ dependencies = [ "ark-poly", "ark-serialize 0.4.2", "ark-std 0.4.0", - "arrayvec 0.7.4", + "arrayvec 0.7.6", "blake2 0.10.6", "common", "fflonk", @@ -17643,16 +17658,17 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", "getrandom", "libc", "spin 0.9.8", "untrusted 0.9.0", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -17914,13 +17930,13 @@ checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" [[package]] name = "rpassword" -version = "7.2.0" +version = "7.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6678cf63ab3491898c0d021b493c94c9b221d91295294a2a5746eacbe5928322" +checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f" dependencies = [ "libc", "rtoolbox", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -17932,7 +17948,7 @@ dependencies = [ "futures", "futures-timer", "rstest_macros", - "rustc_version 0.4.0", + "rustc_version 0.4.1", ] [[package]] @@ -17943,11 +17959,11 @@ checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" dependencies = [ "cfg-if", "glob", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "regex", "relative-path", - "rustc_version 0.4.0", + "rustc_version 0.4.1", "syn 2.0.79", "unicode-ident", ] @@ -17969,12 +17985,12 @@ dependencies = [ [[package]] name = "rtoolbox" -version = "0.0.1" +version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034e22c514f5c0cb8a10ff341b9b048b5ceb21591f31c8f44c43b960f9b3524a" +checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e" dependencies = [ "libc", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -18009,9 +18025,9 @@ checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" @@ -18051,11 +18067,11 @@ dependencies = [ [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.18", + "semver 1.0.23", ] [[package]] @@ -18069,9 +18085,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.15" +version = "0.36.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c37f1bd5ef1b5422177b7646cba67430579cfe2ace80f284fee876bca52ad941" +checksum = "305efbd14fde4139eb501df5f136994bb520b033fa9fbdce287507dc23b8c7ed" dependencies = [ "bitflags 1.3.2", "errno", @@ -18083,9 +18099,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.23" +version = "0.37.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" dependencies = [ "bitflags 1.3.2", "errno", @@ -18097,15 +18113,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.21" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags 2.6.0", "errno", "libc", - "linux-raw-sys 0.4.10", - "windows-sys 0.48.0", + "linux-raw-sys 0.4.14", + "windows-sys 0.52.0", ] [[package]] @@ -18121,13 +18137,13 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.7" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring 0.16.20", - "rustls-webpki 0.101.4", + "ring 0.17.8", + "rustls-webpki 0.101.7", "sct", ] @@ -18138,25 +18154,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ "log", - "ring 0.17.7", + "ring 0.17.8", "rustls-pki-types", - "rustls-webpki 0.102.4", - "subtle 2.5.0", + "rustls-webpki 0.102.8", + "subtle 2.6.1", "zeroize", ] [[package]] name = "rustls" -version = "0.23.10" +version = "0.23.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" +checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8" dependencies = [ "log", "once_cell", - "ring 0.17.7", + "ring 0.17.8", "rustls-pki-types", - "rustls-webpki 0.102.4", - "subtle 2.5.0", + "rustls-webpki 0.102.8", + "subtle 2.6.1", "zeroize", ] @@ -18167,19 +18183,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", - "rustls-pemfile 1.0.3", + "rustls-pemfile 1.0.4", "schannel", "security-framework", ] [[package]] name = "rustls-native-certs" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" dependencies = [ "openssl-probe", - "rustls-pemfile 2.0.0", + "rustls-pemfile 2.2.0", "rustls-pki-types", "schannel", "security-framework", @@ -18187,73 +18203,72 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ "base64 0.21.7", ] [[package]] name = "rustls-pemfile" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e4980fa29e4c4b212ffb3db068a564cbf560e51d3944b7c88bd8bf5bec64f4" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64 0.21.7", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.7.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" [[package]] name = "rustls-platform-verifier" -version = "0.3.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5f0d26fa1ce3c790f9590868f0109289a044acb954525f933e2aa3b871c157d" +checksum = "afbb878bdfdf63a336a5e63561b1835e7a8c91524f51621db870169eac84b490" dependencies = [ "core-foundation", "core-foundation-sys", "jni", "log", "once_cell", - "rustls 0.23.10", - "rustls-native-certs 0.7.0", + "rustls 0.23.14", + "rustls-native-certs 0.7.3", "rustls-platform-verifier-android", - "rustls-webpki 0.102.4", + "rustls-webpki 0.102.8", "security-framework", "security-framework-sys", - "webpki-roots 0.26.3", + "webpki-roots 0.26.6", "winapi", ] [[package]] name = "rustls-platform-verifier-android" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84e217e7fdc8466b5b35d30f8c0a30febd29173df4a3a0c2115d306b9c4117ad" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.101.4" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.16.20", - "untrusted 0.7.1", + "ring 0.17.8", + "untrusted 0.9.0", ] [[package]] name = "rustls-webpki" -version = "0.102.4" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ - "ring 0.17.7", + "ring 0.17.8", "rustls-pki-types", "untrusted 0.9.0", ] @@ -18311,9 +18326,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "safe-mix" @@ -18326,9 +18341,9 @@ dependencies = [ [[package]] name = "safe_arch" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f398075ce1e6a179b46f51bd88d0598b92b00d3551f1a2d4ac49e771b56ac354" +checksum = "c3460605018fdc9612bce72735cba0d27efbcd9904780d44c7e3a9948f96148a" dependencies = [ "bytemuck", ] @@ -18360,7 +18375,7 @@ checksum = "a3f01218e73ea57916be5f08987995ac802d6f4ede4ea5ce0242e468c590e4e2" dependencies = [ "log", "sp-core 33.0.1", - "sp-wasm-interface 21.0.0", + "sp-wasm-interface 21.0.1", "thiserror", ] @@ -18378,7 +18393,7 @@ dependencies = [ "multihash 0.19.1", "parity-scale-codec", "prost 0.12.6", - "prost-build 0.12.4", + "prost-build 0.12.6", "quickcheck", "rand", "sc-client-api", @@ -18442,10 +18457,10 @@ name = "sc-chain-spec" version = "28.0.0" dependencies = [ "array-bytes", - "clap 4.5.13", + "clap 4.5.20", "docify", "log", - "memmap2 0.9.3", + "memmap2 0.9.5", "parity-scale-codec", "regex", "sc-chain-spec-derive", @@ -18473,8 +18488,8 @@ dependencies = [ name = "sc-chain-spec-derive" version = "11.0.0" dependencies = [ - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -18485,7 +18500,7 @@ version = "0.36.0" dependencies = [ "array-bytes", "chrono", - "clap 4.5.13", + "clap 4.5.20", "fdlimit", "futures", "futures-timer", @@ -18693,7 +18708,7 @@ name = "sc-consensus-babe-rpc" version = "0.34.0" dependencies = [ "futures", - "jsonrpsee 0.24.3", + "jsonrpsee 0.24.6", "sc-consensus", "sc-consensus-babe", "sc-consensus-epochs", @@ -18765,7 +18780,7 @@ name = "sc-consensus-beefy-rpc" version = "13.0.0" dependencies = [ "futures", - "jsonrpsee 0.24.3", + "jsonrpsee 0.24.6", "log", "parity-scale-codec", "parking_lot 0.12.3", @@ -18850,7 +18865,7 @@ version = "0.19.0" dependencies = [ "finality-grandpa", "futures", - "jsonrpsee 0.24.3", + "jsonrpsee 0.24.6", "log", "parity-scale-codec", "sc-block-builder", @@ -18876,7 +18891,7 @@ dependencies = [ "async-trait", "futures", "futures-timer", - "jsonrpsee 0.24.3", + "jsonrpsee 0.24.6", "log", "parity-scale-codec", "sc-basic-authorship", @@ -19012,7 +19027,7 @@ dependencies = [ "sp-runtime-interface 27.0.0", "sp-trie 35.0.0", "sp-version 35.0.0", - "sp-wasm-interface 21.0.0", + "sp-wasm-interface 21.0.1", "tracing", ] @@ -19037,7 +19052,7 @@ dependencies = [ "polkavm 0.9.3", "sc-allocator 28.0.0", "sp-maybe-compressed-blob 11.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sp-wasm-interface 21.0.0", + "sp-wasm-interface 21.0.1", "thiserror", "wasm-instrument", ] @@ -19061,7 +19076,7 @@ dependencies = [ "log", "polkavm 0.9.3", "sc-executor-common 0.34.0", - "sp-wasm-interface 21.0.0", + "sp-wasm-interface 21.0.1", ] [[package]] @@ -19076,7 +19091,7 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", "paste", - "rustix 0.36.15", + "rustix 0.36.17", "sc-allocator 23.0.0", "sc-executor-common 0.29.0", "sc-runtime-test", @@ -19099,11 +19114,11 @@ dependencies = [ "libc", "log", "parking_lot 0.12.3", - "rustix 0.36.15", + "rustix 0.36.17", "sc-allocator 28.0.0", "sc-executor-common 0.34.0", "sp-runtime-interface 27.0.0", - "sp-wasm-interface 21.0.0", + "sp-wasm-interface 21.0.1", "wasmtime", ] @@ -19142,14 +19157,14 @@ name = "sc-mixnet" version = "0.4.0" dependencies = [ "array-bytes", - "arrayvec 0.7.4", + "arrayvec 0.7.6", "blake2 0.10.6", "bytes", "futures", "futures-timer", "log", "mixnet", - "multiaddr 0.18.1", + "multiaddr 0.18.2", "parity-scale-codec", "parking_lot 0.12.3", "sc-client-api", @@ -19193,7 +19208,7 @@ dependencies = [ "partial_sort", "pin-project", "prost 0.12.6", - "prost-build 0.12.4", + "prost-build 0.12.6", "rand", "sc-block-builder", "sc-client-api", @@ -19238,7 +19253,7 @@ dependencies = [ "futures", "libp2p-identity", "parity-scale-codec", - "prost-build 0.12.4", + "prost-build 0.12.6", "sc-consensus", "sc-network-types", "sp-consensus", @@ -19280,7 +19295,7 @@ dependencies = [ "log", "parity-scale-codec", "prost 0.12.6", - "prost-build 0.12.4", + "prost-build 0.12.6", "sc-client-api", "sc-network", "sc-network-types", @@ -19323,7 +19338,7 @@ dependencies = [ "mockall 0.11.4", "parity-scale-codec", "prost 0.12.6", - "prost-build 0.12.4", + "prost-build 0.12.6", "quickcheck", "sc-block-builder", "sc-client-api", @@ -19407,7 +19422,7 @@ dependencies = [ "libp2p-identity", "litep2p", "log", - "multiaddr 0.18.1", + "multiaddr 0.18.2", "multihash 0.19.1", "quickcheck", "rand", @@ -19425,7 +19440,7 @@ dependencies = [ "fnv", "futures", "futures-timer", - "hyper 0.14.29", + "hyper 0.14.30", "hyper-rustls 0.24.2", "log", "num_cpus", @@ -19470,7 +19485,7 @@ version = "29.0.0" dependencies = [ "assert_matches", "futures", - "jsonrpsee 0.24.3", + "jsonrpsee 0.24.6", "log", "parity-scale-codec", "parking_lot 0.12.3", @@ -19508,7 +19523,7 @@ dependencies = [ name = "sc-rpc-api" version = "0.33.0" dependencies = [ - "jsonrpsee 0.24.3", + "jsonrpsee 0.24.6", "parity-scale-codec", "sc-chain-spec", "sc-mixnet", @@ -19533,9 +19548,9 @@ dependencies = [ "governor", "http 1.1.0", "http-body-util", - "hyper 1.3.1", + "hyper 1.4.1", "ip_network", - "jsonrpsee 0.24.3", + "jsonrpsee 0.24.6", "log", "sc-rpc-api", "serde", @@ -19555,7 +19570,7 @@ dependencies = [ "futures", "futures-util", "hex", - "jsonrpsee 0.24.3", + "jsonrpsee 0.24.6", "log", "parity-scale-codec", "parking_lot 0.12.3", @@ -19609,7 +19624,7 @@ dependencies = [ "exit-future", "futures", "futures-timer", - "jsonrpsee 0.24.3", + "jsonrpsee 0.24.6", "log", "parity-scale-codec", "parking_lot 0.12.3", @@ -19735,7 +19750,7 @@ dependencies = [ name = "sc-storage-monitor" version = "0.16.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.20", "fs4", "log", "sp-core 28.0.0", @@ -19747,7 +19762,7 @@ dependencies = [ name = "sc-sync-state-rpc" version = "0.34.0" dependencies = [ - "jsonrpsee 0.24.3", + "jsonrpsee 0.24.6", "parity-scale-codec", "sc-chain-spec", "sc-client-api", @@ -19834,8 +19849,8 @@ dependencies = [ name = "sc-tracing-proc-macro" version = "11.0.0" dependencies = [ - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -19938,7 +19953,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bb22f574168103cdd3133b19281639ca65ad985e24612728f727339dcaf4021" dependencies = [ "darling 0.14.4", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 1.0.109", ] @@ -19965,8 +19980,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82ab7e60e2d9c8d47105f44527b26f04418e5e624ffc034f6b4a86c0ba19c5bf" dependencies = [ "darling 0.14.4", - "proc-macro-crate 1.3.1", - "proc-macro2 1.0.86", + "proc-macro-crate 1.1.3", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 1.0.109", ] @@ -19991,8 +20006,8 @@ version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d35494501194174bda522a32605929eefc9ecf7e0a326c26db1fdd85881eb62" dependencies = [ - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 1.0.109", ] @@ -20013,7 +20028,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "498d1aecf2ea61325d4511787c115791639c0fd21ef4f8e11e49dd09eff2bbac" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "scale-info", "syn 2.0.79", @@ -20022,9 +20037,9 @@ dependencies = [ [[package]] name = "scale-value" -version = "0.16.2" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4d772cfb7569e03868400344a1695d16560bf62b86b918604773607d39ec84" +checksum = "8cd6ab090d823e75cfdb258aad5fe92e13f2af7d04b43a55d607d25fcc38c811" dependencies = [ "base58", "blake2 0.10.6", @@ -20043,18 +20058,18 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "schemars" -version = "0.8.13" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763f8cd0d4c71ed8389c90cb8100cba87e763bd01a8e614d4f0af97bcd50a161" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" dependencies = [ "dyn-clone", "schemars_derive", @@ -20064,14 +20079,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.13" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0f696e21e10fa546b7ffb1c9672c6de8fbc7a81acf59524386d8639bf12737" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "serde_derive_internals", - "syn 1.0.109", + "syn 2.0.79", ] [[package]] @@ -20092,7 +20107,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "844b7645371e6ecdf61ff246ba1958c29e802881a749ae3fb1993675d210d28d" dependencies = [ "arrayref", - "arrayvec 0.7.4", + "arrayvec 0.7.6", "curve25519-dalek-ng", "merlin", "rand_core 0.6.4", @@ -20109,14 +20124,14 @@ checksum = "8de18f6d8ba0aad7045f5feae07ec29899c1112584a38509a84ad7b04451eaa0" dependencies = [ "aead", "arrayref", - "arrayvec 0.7.4", + "arrayvec 0.7.6", "curve25519-dalek 4.1.3", "getrandom_or_panic", "merlin", "rand_core 0.6.4", "serde_bytes", "sha2 0.10.8", - "subtle 2.5.0", + "subtle 2.6.1", "zeroize", ] @@ -20140,12 +20155,12 @@ checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" [[package]] name = "sct" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.16.20", - "untrusted 0.7.1", + "ring 0.17.8", + "untrusted 0.9.0", ] [[package]] @@ -20159,7 +20174,7 @@ dependencies = [ "generic-array 0.14.7", "pkcs8", "serdect", - "subtle 2.5.0", + "subtle 2.6.1", "zeroize", ] @@ -20202,9 +20217,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.6.0", "core-foundation", @@ -20216,9 +20231,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" dependencies = [ "core-foundation-sys", "libc", @@ -20253,9 +20268,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.18" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" dependencies = [ "serde", ] @@ -20317,9 +20332,9 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.12" +version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" dependencies = [ "serde", ] @@ -20330,20 +20345,20 @@ version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] [[package]] name = "serde_derive_internals" -version = "0.26.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", - "syn 1.0.109", + "syn 2.0.79", ] [[package]] @@ -20361,7 +20376,7 @@ version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.6.0", "itoa", "memchr", "ryu", @@ -20370,9 +20385,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -20395,7 +20410,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.6.0", "itoa", "ryu", "serde", @@ -20432,7 +20447,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -20447,7 +20462,7 @@ dependencies = [ "cfg-if", "cpufeatures", "digest 0.9.0", - "opaque-debug 0.3.0", + "opaque-debug 0.3.1", ] [[package]] @@ -20471,7 +20486,7 @@ dependencies = [ "cfg-if", "cpufeatures", "digest 0.9.0", - "opaque-debug 0.3.0", + "opaque-debug 0.3.1", ] [[package]] @@ -20494,7 +20509,7 @@ dependencies = [ "block-buffer 0.9.0", "digest 0.9.0", "keccak", - "opaque-debug 0.3.0", + "opaque-debug 0.3.1", ] [[package]] @@ -20509,9 +20524,9 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] @@ -20522,30 +20537,20 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "signal-hook" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" -dependencies = [ - "libc", - "signal-hook-registry", -] - [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] [[package]] name = "signature" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest 0.10.7", "rand_core 0.6.4", @@ -20553,9 +20558,9 @@ dependencies = [ [[package]] name = "simba" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae" +checksum = "b3a386a501cd104797982c15ae17aafe8b9261315b5d07e3ec803f2ea26be0fa" dependencies = [ "approx", "num-complex", @@ -20623,9 +20628,9 @@ dependencies = [ [[package]] name = "slotmap" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" dependencies = [ "version_check", ] @@ -20658,8 +20663,8 @@ dependencies = [ "async-fs 1.6.0", "async-io 1.13.0", "async-lock 2.8.0", - "async-net 1.7.0", - "async-process 1.7.0", + "async-net 1.8.0", + "async-process 1.8.1", "blocking", "futures-lite 1.13.0", ] @@ -20670,10 +20675,10 @@ version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f" dependencies = [ - "async-channel 2.3.0", + "async-channel 2.3.1", "async-executor", "async-fs 2.1.2", - "async-io 2.3.3", + "async-io 2.3.4", "async-lock 3.4.0", "async-net 2.0.0", "async-process 2.3.0", @@ -20681,22 +20686,13 @@ dependencies = [ "futures-lite 2.3.0", ] -[[package]] -name = "smol_str" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74212e6bbe9a4352329b2f68ba3130c15a3f26fe88ff22dbdc6cdd58fa85e99c" -dependencies = [ - "serde", -] - [[package]] name = "smoldot" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0bb30cf57b7b5f6109ce17c3164445e2d6f270af2cb48f6e4d31c2967c9a9f5" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "async-lock 2.8.0", "atomic-take", "base64 0.21.7", @@ -20750,7 +20746,7 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d1eaa97d77be4d026a1e7ffad1bb3b78448763b357ea6f8188d3e6f736a9b9" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "async-lock 3.4.0", "atomic-take", "base64 0.21.7", @@ -20820,7 +20816,7 @@ dependencies = [ "hex", "itertools 0.11.0", "log", - "lru 0.11.0", + "lru 0.11.1", "no-std-net", "parking_lot 0.12.3", "pin-project", @@ -20841,7 +20837,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5496f2d116b7019a526b1039ec2247dd172b8670633b1a64a614c9ea12c9d8c7" dependencies = [ - "async-channel 2.3.0", + "async-channel 2.3.1", "async-lock 3.4.0", "base64 0.21.7", "blake2-rfc", @@ -20856,7 +20852,7 @@ dependencies = [ "hex", "itertools 0.12.1", "log", - "lru 0.12.3", + "lru 0.12.5", "no-std-net", "parking_lot 0.12.3", "pin-project", @@ -20873,9 +20869,9 @@ dependencies = [ [[package]] name = "snap" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e9f0ab6ef7eb7353d9119c170a436d1bf248eea575ac42d19d12f4e34130831" +checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" [[package]] name = "snow" @@ -20888,10 +20884,10 @@ dependencies = [ "chacha20poly1305", "curve25519-dalek 4.1.3", "rand_core 0.6.4", - "ring 0.17.7", - "rustc_version 0.4.0", + "ring 0.17.8", + "rustc_version 0.4.1", "sha2 0.10.8", - "subtle 2.5.0", + "subtle 2.6.1", ] [[package]] @@ -20955,8 +20951,8 @@ name = "snowbridge-ethereum" version = "0.3.0" dependencies = [ "ethabi-decode", - "ethbloom", - "ethereum-types", + "ethbloom 0.14.1", + "ethereum-types 0.15.1", "hex-literal", "parity-bytes", "parity-scale-codec", @@ -21217,9 +21213,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ "libc", "winapi", @@ -21270,12 +21266,12 @@ dependencies = [ name = "solochain-template-node" version = "0.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.20", "frame-benchmarking-cli", "frame-metadata-hash-extension", "frame-system", "futures", - "jsonrpsee 0.24.3", + "jsonrpsee 0.24.6", "pallet-transaction-payment", "pallet-transaction-payment-rpc", "sc-basic-authorship", @@ -21402,8 +21398,8 @@ dependencies = [ "assert_matches", "blake2 0.10.6", "expander", - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -21417,8 +21413,8 @@ dependencies = [ "Inflector", "blake2 0.10.6", "expander", - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -21575,7 +21571,7 @@ version = "0.4.2" source = "git+https://github.com/paritytech/arkworks-substrate#caa2eed74beb885dd07c7db5f916f2281dad818f" dependencies = [ "ark-bls12-381-ext", - "sp-crypto-ec-utils 0.4.1", + "sp-crypto-ec-utils 0.10.0 (git+https://github.com/paritytech/polkadot-sdk)", ] [[package]] @@ -21584,7 +21580,7 @@ version = "0.4.2" source = "git+https://github.com/paritytech/arkworks-substrate#caa2eed74beb885dd07c7db5f916f2281dad818f" dependencies = [ "ark-ed-on-bls12-381-bandersnatch-ext", - "sp-crypto-ec-utils 0.4.1", + "sp-crypto-ec-utils 0.10.0 (git+https://github.com/paritytech/polkadot-sdk)", ] [[package]] @@ -21958,8 +21954,7 @@ dependencies = [ [[package]] name = "sp-crypto-ec-utils" -version = "0.4.1" -source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" +version = "0.10.0" dependencies = [ "ark-bls12-377", "ark-bls12-377-ext", @@ -21972,14 +21967,14 @@ dependencies = [ "ark-ed-on-bls12-377-ext", "ark-ed-on-bls12-381-bandersnatch", "ark-ed-on-bls12-381-bandersnatch-ext", - "ark-scale 0.0.11", - "sp-runtime-interface 17.0.0", - "sp-std 8.0.0", + "ark-scale", + "sp-runtime-interface 24.0.0", ] [[package]] name = "sp-crypto-ec-utils" version = "0.10.0" +source = "git+https://github.com/paritytech/polkadot-sdk#d1c115b6197bf6c45d5640594f0432e6c2781a4f" dependencies = [ "ark-bls12-377", "ark-bls12-377-ext", @@ -21992,8 +21987,8 @@ dependencies = [ "ark-ed-on-bls12-377-ext", "ark-ed-on-bls12-381-bandersnatch", "ark-ed-on-bls12-381-bandersnatch-ext", - "ark-scale 0.0.12", - "sp-runtime-interface 24.0.0", + "ark-scale", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk)", ] [[package]] @@ -22054,10 +22049,9 @@ dependencies = [ [[package]] name = "sp-debug-derive" -version = "8.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" +version = "14.0.0" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -22065,8 +22059,10 @@ dependencies = [ [[package]] name = "sp-debug-derive" version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d09fa0a5f7299fb81ee25ae3853d26200f7a348148aed6de76be905c007dbe" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -22074,32 +22070,30 @@ dependencies = [ [[package]] name = "sp-debug-derive" version = "14.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d09fa0a5f7299fb81ee25ae3853d26200f7a348148aed6de76be905c007dbe" +source = "git+https://github.com/paritytech/polkadot-sdk#d1c115b6197bf6c45d5640594f0432e6c2781a4f" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] [[package]] name = "sp-externalities" -version = "0.19.0" -source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" +version = "0.25.0" dependencies = [ "environmental", "parity-scale-codec", - "sp-std 8.0.0", - "sp-storage 13.0.0", + "sp-storage 19.0.0", ] [[package]] name = "sp-externalities" version = "0.25.0" +source = "git+https://github.com/paritytech/polkadot-sdk#d1c115b6197bf6c45d5640594f0432e6c2781a4f" dependencies = [ "environmental", "parity-scale-codec", - "sp-storage 19.0.0", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk)", ] [[package]] @@ -22222,7 +22216,7 @@ dependencies = [ "sp-runtime-interface 27.0.0", "sp-state-machine 0.40.0", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sp-tracing 17.0.0", + "sp-tracing 17.0.1", "sp-trie 34.0.0", "tracing", "tracing-core", @@ -22249,7 +22243,7 @@ dependencies = [ "sp-runtime-interface 27.0.0", "sp-state-machine 0.41.0", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sp-tracing 17.0.0", + "sp-tracing 17.0.1", "sp-trie 35.0.0", "tracing", "tracing-core", @@ -22395,7 +22389,7 @@ dependencies = [ name = "sp-npos-elections-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 4.5.13", + "clap 4.5.20", "honggfuzz", "rand", "sp-npos-elections", @@ -22549,24 +22543,6 @@ dependencies = [ "sp-weights 31.0.0", ] -[[package]] -name = "sp-runtime-interface" -version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" -dependencies = [ - "bytes", - "impl-trait-for-tuples", - "parity-scale-codec", - "primitive-types 0.12.2", - "sp-externalities 0.19.0", - "sp-runtime-interface-proc-macro 11.0.0", - "sp-std 8.0.0", - "sp-storage 13.0.0", - "sp-tracing 10.0.0", - "sp-wasm-interface 14.0.0", - "static_assertions", -] - [[package]] name = "sp-runtime-interface" version = "24.0.0" @@ -22591,6 +22567,25 @@ dependencies = [ "trybuild", ] +[[package]] +name = "sp-runtime-interface" +version = "24.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#d1c115b6197bf6c45d5640594f0432e6c2781a4f" +dependencies = [ + "bytes", + "impl-trait-for-tuples", + "parity-scale-codec", + "polkavm-derive 0.9.1", + "primitive-types 0.13.1", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk)", + "sp-runtime-interface-proc-macro 17.0.0 (git+https://github.com/paritytech/polkadot-sdk)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk)", + "static_assertions", +] + [[package]] name = "sp-runtime-interface" version = "26.0.0" @@ -22626,19 +22621,19 @@ dependencies = [ "sp-runtime-interface-proc-macro 18.0.0", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-storage 21.0.0", - "sp-tracing 17.0.0", - "sp-wasm-interface 21.0.0", + "sp-tracing 17.0.1", + "sp-wasm-interface 21.0.1", "static_assertions", ] [[package]] name = "sp-runtime-interface-proc-macro" -version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" +version = "17.0.0" dependencies = [ "Inflector", - "proc-macro-crate 1.3.1", - "proc-macro2 1.0.86", + "expander", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -22646,11 +22641,12 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" version = "17.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#d1c115b6197bf6c45d5640594f0432e6c2781a4f" dependencies = [ "Inflector", "expander", - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -22663,8 +22659,8 @@ checksum = "0195f32c628fee3ce1dfbbf2e7e52a30ea85f3589da9fe62a8b816d70fc06294" dependencies = [ "Inflector", "expander", - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -22845,41 +22841,40 @@ dependencies = [ [[package]] name = "sp-std" -version = "8.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" +version = "14.0.0" [[package]] name = "sp-std" version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f8ee986414b0a9ad741776762f4083cd3a5128449b982a3919c4df36874834" [[package]] name = "sp-std" version = "14.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f8ee986414b0a9ad741776762f4083cd3a5128449b982a3919c4df36874834" +source = "git+https://github.com/paritytech/polkadot-sdk#d1c115b6197bf6c45d5640594f0432e6c2781a4f" [[package]] name = "sp-storage" -version = "13.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" +version = "19.0.0" dependencies = [ "impl-serde 0.4.0", "parity-scale-codec", "ref-cast", "serde", - "sp-debug-derive 8.0.0", - "sp-std 8.0.0", + "sp-debug-derive 14.0.0", ] [[package]] name = "sp-storage" version = "19.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#d1c115b6197bf6c45d5640594f0432e6c2781a4f" dependencies = [ "impl-serde 0.4.0", "parity-scale-codec", "ref-cast", "serde", - "sp-debug-derive 14.0.0", + "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk)", ] [[package]] @@ -22934,49 +22929,48 @@ dependencies = [ [[package]] name = "sp-tracing" -version = "10.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" +version = "16.0.0" dependencies = [ "parity-scale-codec", - "sp-std 8.0.0", "tracing", "tracing-core", - "tracing-subscriber 0.2.25", + "tracing-subscriber 0.3.18", ] [[package]] name = "sp-tracing" version = "16.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0351810b9d074df71c4514c5228ed05c250607cba131c1c9d1526760ab69c05c" dependencies = [ "parity-scale-codec", + "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "tracing", "tracing-core", - "tracing-subscriber 0.3.18", + "tracing-subscriber 0.2.25", ] [[package]] name = "sp-tracing" version = "16.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0351810b9d074df71c4514c5228ed05c250607cba131c1c9d1526760ab69c05c" +source = "git+https://github.com/paritytech/polkadot-sdk#d1c115b6197bf6c45d5640594f0432e6c2781a4f" dependencies = [ "parity-scale-codec", - "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "tracing", "tracing-core", - "tracing-subscriber 0.2.25", + "tracing-subscriber 0.3.18", ] [[package]] name = "sp-tracing" -version = "17.0.0" +version = "17.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90b3decf116db9f1dfaf1f1597096b043d0e12c952d3bcdc018c6d6b77deec7e" +checksum = "cf641a1d17268c8fcfdb8e0fa51a79c2d4222f4cfda5f3944dbdbc384dced8d5" dependencies = [ "parity-scale-codec", "tracing", "tracing-core", - "tracing-subscriber 0.2.25", + "tracing-subscriber 0.3.18", ] [[package]] @@ -23138,8 +23132,8 @@ name = "sp-version-proc-macro" version = "13.0.0" dependencies = [ "parity-scale-codec", - "proc-macro-warning 1.0.0", - "proc-macro2 1.0.86", + "proc-macro-warning 1.0.2", + "proc-macro2 1.0.87", "quote 1.0.37", "sp-version 29.0.0", "syn 2.0.79", @@ -23152,54 +23146,52 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aee8f6730641a65fcf0c8f9b1e448af4b3bb083d08058b47528188bccc7b7a7" dependencies = [ "parity-scale-codec", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] [[package]] name = "sp-wasm-interface" -version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" +version = "20.0.0" dependencies = [ "anyhow", "impl-trait-for-tuples", "log", "parity-scale-codec", - "sp-std 8.0.0", "wasmtime", ] [[package]] name = "sp-wasm-interface" version = "20.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef97172c42eb4c6c26506f325f48463e9bc29b2034a587f1b9e48c751229bee" dependencies = [ "anyhow", "impl-trait-for-tuples", "log", "parity-scale-codec", + "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "wasmtime", ] [[package]] name = "sp-wasm-interface" version = "20.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ef97172c42eb4c6c26506f325f48463e9bc29b2034a587f1b9e48c751229bee" +source = "git+https://github.com/paritytech/polkadot-sdk#d1c115b6197bf6c45d5640594f0432e6c2781a4f" dependencies = [ "anyhow", "impl-trait-for-tuples", "log", "parity-scale-codec", - "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmtime", ] [[package]] name = "sp-wasm-interface" -version = "21.0.0" +version = "21.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b04b919e150b4736d85089d49327eab65507deb1485eec929af69daa2278eb3" +checksum = "b066baa6d57951600b14ffe1243f54c47f9c23dd89c262e17ca00ae8dca58be9" dependencies = [ "anyhow", "impl-trait-for-tuples", @@ -23276,11 +23268,20 @@ dependencies = [ "strum 0.24.1", ] +[[package]] +name = "spinning_top" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300" +dependencies = [ + "lock_api", +] + [[package]] name = "spki" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", @@ -23288,17 +23289,17 @@ dependencies = [ [[package]] name = "ss58-registry" -version = "1.43.0" +version = "1.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6915280e2d0db8911e5032a5c275571af6bdded2916abd691a659be25d3439" +checksum = "19409f13998e55816d1c728395af0b52ec066206341d939e22e7766df9b494b8" dependencies = [ "Inflector", "num-format", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "serde", "serde_json", - "unicode-xid 0.2.4", + "unicode-xid 0.2.6", ] [[package]] @@ -23319,7 +23320,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f07d54c4d01a1713eb363b55ba51595da15f6f1211435b71466460da022aa140" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 1.0.109", ] @@ -23334,7 +23335,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" name = "staging-chain-spec-builder" version = "1.6.1" dependencies = [ - "clap 4.5.13", + "clap 4.5.20", "log", "sc-chain-spec", "serde", @@ -23349,11 +23350,11 @@ version = "3.0.0-dev" dependencies = [ "array-bytes", "assert_cmd", - "clap 4.5.13", + "clap 4.5.20", "clap_complete", "criterion", "futures", - "jsonrpsee 0.24.3", + "jsonrpsee 0.24.6", "kitchensink-runtime", "log", "nix 0.28.0", @@ -23384,7 +23385,7 @@ dependencies = [ name = "staging-node-inspect" version = "0.12.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.20", "parity-scale-codec", "sc-cli", "sc-client-api", @@ -23514,7 +23515,7 @@ checksum = "70a2595fc3aa78f2d0e45dd425b22282dd863273761cc77780914b2cf3003acf" dependencies = [ "cfg_aliases 0.1.1", "memchr", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 1.0.109", ] @@ -23567,7 +23568,7 @@ checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" dependencies = [ "heck 0.3.3", "proc-macro-error", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 1.0.109", ] @@ -23581,12 +23582,6 @@ dependencies = [ "strum_macros 0.24.3", ] -[[package]] -name = "strum" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" - [[package]] name = "strum" version = "0.26.3" @@ -23603,25 +23598,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "rustversion", "syn 1.0.109", ] -[[package]] -name = "strum_macros" -version = "0.25.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" -dependencies = [ - "heck 0.4.1", - "proc-macro2 1.0.86", - "quote 1.0.37", - "rustversion", - "syn 2.0.79", -] - [[package]] name = "strum_macros" version = "0.26.4" @@ -23629,7 +23611,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ "heck 0.5.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "rustversion", "syn 2.0.79", @@ -23639,7 +23621,7 @@ dependencies = [ name = "subkey" version = "9.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.20", "sc-cli", ] @@ -23737,7 +23719,7 @@ version = "29.0.0" dependencies = [ "frame-support", "frame-system", - "jsonrpsee 0.24.3", + "jsonrpsee 0.24.6", "parity-scale-codec", "sc-rpc-api", "scale-info", @@ -23756,7 +23738,7 @@ dependencies = [ "docify", "frame-system-rpc-runtime-api", "futures", - "jsonrpsee 0.24.3", + "jsonrpsee 0.24.6", "log", "parity-scale-codec", "sc-rpc-api", @@ -23777,7 +23759,7 @@ name = "substrate-prometheus-endpoint" version = "0.17.0" dependencies = [ "http-body-util", - "hyper 1.3.1", + "hyper 1.4.1", "hyper-util", "log", "prometheus", @@ -23833,7 +23815,7 @@ name = "substrate-rpc-client" version = "0.33.0" dependencies = [ "async-trait", - "jsonrpsee 0.24.3", + "jsonrpsee 0.24.6", "log", "sc-rpc-api", "serde", @@ -23854,7 +23836,7 @@ dependencies = [ "sp-core 32.0.0", "sp-io 35.0.0", "sp-runtime 36.0.0", - "sp-wasm-interface 21.0.0", + "sp-wasm-interface 21.0.1", "thiserror", ] @@ -23862,7 +23844,7 @@ dependencies = [ name = "substrate-state-trie-migration-rpc" version = "27.0.0" dependencies = [ - "jsonrpsee 0.24.3", + "jsonrpsee 0.24.6", "parity-scale-codec", "sc-client-api", "sc-rpc-api", @@ -24019,7 +24001,7 @@ dependencies = [ "sp-version 29.0.0", "strum 0.26.3", "tempfile", - "toml 0.8.12", + "toml 0.8.19", "walkdir", "wasm-opt", ] @@ -24032,9 +24014,9 @@ checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "subtle-ng" @@ -24054,9 +24036,9 @@ dependencies = [ "log", "num-format", "rand", - "reqwest 0.12.5", + "reqwest 0.12.8", "scale-info", - "semver 1.0.18", + "semver 1.0.23", "serde", "serde_json", "sp-version 35.0.0", @@ -24115,7 +24097,7 @@ dependencies = [ "hex", "jsonrpsee 0.22.5", "parity-scale-codec", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "scale-info", "scale-typegen", @@ -24223,15 +24205,15 @@ dependencies = [ [[package]] name = "sval" -version = "2.6.1" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b031320a434d3e9477ccf9b5756d57d4272937b8d22cb88af80b7633a1b78b1" +checksum = "eaf38d1fa2ce984086ea42fb856a9f374d94680a4f796831a7fc868d7f2af1b9" [[package]] name = "sval_buffer" -version = "2.6.1" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf7e9412af26b342f3f2cc5cc4122b0105e9d16eb76046cd14ed10106cf6028" +checksum = "81682ff859964ca1d7cf3d3d0f9ec7204ea04c2c32acb8cc2cf68ecbd3127354" dependencies = [ "sval", "sval_ref", @@ -24239,18 +24221,18 @@ dependencies = [ [[package]] name = "sval_dynamic" -version = "2.6.1" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0ef628e8a77a46ed3338db8d1b08af77495123cc229453084e47cd716d403cf" +checksum = "2a213b93bb4c6f4c9f9b17f2e740e077fd18746bbf7c80c72bbadcac68fa7ee4" dependencies = [ "sval", ] [[package]] name = "sval_fmt" -version = "2.6.1" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dc09e9364c2045ab5fa38f7b04d077b3359d30c4c2b3ec4bae67a358bd64326" +checksum = "6902c6d3fb52c89206fe0dc93546c0123f7d48b5997fd14e61c9e64ff0b63275" dependencies = [ "itoa", "ryu", @@ -24259,55 +24241,65 @@ dependencies = [ [[package]] name = "sval_json" -version = "2.6.1" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ada6f627e38cbb8860283649509d87bc4a5771141daa41c78fd31f2b9485888d" +checksum = "11a28041ea78cdc394b930ae6b897d36246dc240a29a6edf82d76562487fb0b4" dependencies = [ "itoa", "ryu", "sval", ] +[[package]] +name = "sval_nested" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "850346e4b0742a7f2fd2697d703ff80084d0b658f0f2e336d71b8a06abf9b68e" +dependencies = [ + "sval", + "sval_buffer", + "sval_ref", +] + [[package]] name = "sval_ref" -version = "2.6.1" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703ca1942a984bd0d9b5a4c0a65ab8b4b794038d080af4eb303c71bc6bf22d7c" +checksum = "824afd97a8919f28a35b0fdea979845cc2ae461a8a3aaa129455cb89c88bb77a" dependencies = [ "sval", ] [[package]] name = "sval_serde" -version = "2.6.1" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830926cd0581f7c3e5d51efae4d35c6b6fc4db583842652891ba2f1bed8db046" +checksum = "8ada7520dd719ed672c786c7db7de4f5230f4d504b0821bd8305cd30ca442315" dependencies = [ "serde", "sval", - "sval_buffer", - "sval_fmt", + "sval_nested", ] [[package]] name = "symbolic-common" -version = "12.3.0" +version = "12.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167a4ffd7c35c143fd1030aa3c2caf76ba42220bd5a6b5f4781896434723b8c3" +checksum = "366f1b4c6baf6cfefc234bbd4899535fca0b06c74443039a73f6dfb2fad88d77" dependencies = [ "debugid", - "memmap2 0.5.10", + "memmap2 0.9.5", "stable_deref_trait", "uuid", ] [[package]] name = "symbolic-demangle" -version = "12.3.0" +version = "12.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e378c50e80686c1c5c205674e1f86a2858bec3d2a7dfdd690331a8a19330f293" +checksum = "aba05ba5b9962ea5617baf556293720a8b2d0a282aa14ee4bf10e22efc7da8c8" dependencies = [ - "cpp_demangle 0.4.3", + "cpp_demangle 0.4.4", "rustc-demangle", "symbolic-common", ] @@ -24329,7 +24321,7 @@ version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "unicode-ident", ] @@ -24340,7 +24332,7 @@ version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "unicode-ident", ] @@ -24352,16 +24344,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b837ef12ab88835251726eb12237655e61ec8dc8a280085d1961cdc3dfd047" dependencies = [ "paste", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "sync_wrapper" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] [[package]] name = "synstructure" @@ -24369,10 +24370,10 @@ version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 1.0.109", - "unicode-xid 0.2.4", + "unicode-xid 0.2.6", ] [[package]] @@ -24381,16 +24382,16 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] [[package]] name = "sysinfo" -version = "0.30.5" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb4f3438c8f6389c864e61221cbc97e9bca98b4daf39a5beb7bea660f528bb2" +checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3" dependencies = [ "cfg-if", "core-foundation-sys", @@ -24430,9 +24431,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tar" -version = "0.4.40" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" +checksum = "4ff6c40d3aedb5e06b57c6f669ad17ab063dd1e63d977c6a88e7f4dfa4f04020" dependencies = [ "filetime", "libc", @@ -24441,21 +24442,21 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.11" +version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.8.1" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", - "fastrand 2.1.0", - "redox_syscall 0.4.1", - "rustix 0.38.21", - "windows-sys 0.48.0", + "fastrand 2.1.1", + "once_cell", + "rustix 0.38.37", + "windows-sys 0.59.0", ] [[package]] @@ -24463,7 +24464,7 @@ name = "template-zombienet-tests" version = "0.0.0" dependencies = [ "anyhow", - "env_logger 0.11.3", + "env_logger 0.11.5", "log", "tokio", "zombienet-sdk", @@ -24471,21 +24472,21 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.2.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] [[package]] name = "terminal_size" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" dependencies = [ - "rustix 0.38.21", - "windows-sys 0.48.0", + "rustix 0.38.37", + "windows-sys 0.59.0", ] [[package]] @@ -24500,7 +24501,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dffced63c2b5c7be278154d76b479f9f9920ed34e7574201407f0b14e2bbb93" dependencies = [ - "env_logger 0.11.3", + "env_logger 0.11.5", "test-log-macros", "tracing-subscriber 0.3.18", ] @@ -24511,7 +24512,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -24532,7 +24533,7 @@ dependencies = [ name = "test-parachain-adder-collator" version = "1.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.20", "futures", "futures-timer", "log", @@ -24579,7 +24580,7 @@ dependencies = [ name = "test-parachain-undying-collator" version = "1.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.20", "futures", "futures-timer", "log", @@ -24647,46 +24648,46 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-core" -version = "1.0.38" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d97345f6437bb2004cd58819d8a9ef8e36cdd7661c2abc4bbde0a7c40d9f497" +checksum = "c001ee18b7e5e3f62cbf58c7fe220119e68d902bb7443179c0c8aef30090e999" dependencies = [ "thiserror-core-impl", ] [[package]] name = "thiserror-core-impl" -version = "1.0.38" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10ac1c5050e43014d16b2f94d0d2ce79e65ffdd8b38d8048f9c8f6a8a6da62ac" +checksum = "e4c60d69f36615a077cc7663b9cb8e42275722d23e58a7fa3d2c7f2915d09d04" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", - "syn 1.0.109", + "syn 2.0.79", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -24699,9 +24700,9 @@ checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", @@ -24801,9 +24802,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -24848,7 +24849,7 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -24880,7 +24881,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.7", + "rustls 0.21.12", "tokio", ] @@ -24901,7 +24902,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.10", + "rustls 0.23.14", "rustls-pki-types", "tokio", ] @@ -24920,9 +24921,9 @@ dependencies = [ [[package]] name = "tokio-test" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89b3cbabd3ae862100094ae433e1def582cf86451b4e9bf83aa7ac1d8a7d719" +checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7" dependencies = [ "async-stream", "bytes", @@ -24939,7 +24940,7 @@ checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", "log", - "rustls 0.21.7", + "rustls 0.21.12", "rustls-native-certs 0.6.3", "tokio", "tokio-rustls 0.24.1", @@ -24948,9 +24949,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", @@ -24984,21 +24985,21 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.12" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.12", + "toml_edit 0.22.22", ] [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] @@ -25009,35 +25010,24 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime", - "winnow 0.5.15", + "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.21.0" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.2.3", - "toml_datetime", - "winnow 0.5.15", -] - -[[package]] -name = "toml_edit" -version = "0.22.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" -dependencies = [ - "indexmap 2.2.3", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.18", + "winnow 0.6.20", ] [[package]] @@ -25068,8 +25058,8 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http 0.2.9", - "http-body 0.4.5", + "http 0.2.12", + "http-body 0.4.6", "http-range-header", "mime", "pin-project-lite", @@ -25087,7 +25077,7 @@ dependencies = [ "bitflags 2.6.0", "bytes", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", "pin-project-lite", "tower-layer", @@ -25096,15 +25086,15 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -25124,7 +25114,7 @@ version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -25165,20 +25155,20 @@ version = "5.0.0" dependencies = [ "assert_matches", "expander", - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] [[package]] name = "tracing-log" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" dependencies = [ - "lazy_static", "log", + "once_cell", "tracing-core", ] @@ -25221,7 +25211,7 @@ dependencies = [ "thread_local", "tracing", "tracing-core", - "tracing-log 0.1.3", + "tracing-log 0.1.4", "tracing-serde", ] @@ -25324,7 +25314,7 @@ dependencies = [ "lazy_static", "rand", "smallvec", - "socket2 0.4.9", + "socket2 0.4.10", "thiserror", "tinyvec", "tokio", @@ -25341,7 +25331,7 @@ dependencies = [ "async-trait", "cfg-if", "data-encoding", - "enum-as-inner 0.6.0", + "enum-as-inner 0.6.1", "futures-channel", "futures-io", "futures-util", @@ -25380,24 +25370,23 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "trybuild" -version = "1.0.89" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a9d3ba662913483d6722303f619e75ea10b7855b0f8e0d72799cf8621bb488f" +checksum = "207aa50d36c4be8d8c6ea829478be44a372c6a77669937bb39c698e52f1491e8" dependencies = [ - "basic-toml", "dissimilar", "glob", - "once_cell", "serde", "serde_derive", "serde_json", "termcolor", + "toml 0.8.19", ] [[package]] @@ -25415,11 +25404,11 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 0.2.9", + "http 0.2.12", "httparse", "log", "rand", - "rustls 0.21.7", + "rustls 0.21.12", "sha1", "thiserror", "url", @@ -25440,7 +25429,7 @@ dependencies = [ "log", "rand", "rustls 0.22.4", - "rustls-native-certs 0.7.0", + "rustls-native-certs 0.7.3", "rustls-pki-types", "sha1", "thiserror", @@ -25466,17 +25455,23 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "typeid" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e" + [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "uint" @@ -25510,15 +25505,15 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" @@ -25531,15 +25526,15 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.1.10" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-xid" @@ -25549,9 +25544,9 @@ checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "universal-hash" @@ -25560,7 +25555,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ "crypto-common", - "subtle 2.5.0", + "subtle 2.6.1", ] [[package]] @@ -25625,12 +25620,12 @@ dependencies = [ "flate2", "log", "once_cell", - "rustls 0.23.10", + "rustls 0.23.14", "rustls-pki-types", "serde", "serde_json", "url", - "webpki-roots 0.26.3", + "webpki-roots 0.26.6", ] [[package]] @@ -25653,15 +25648,15 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.4.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ "getrandom", ] @@ -25674,9 +25669,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec26a25bd6fca441cdd0f769fd7f891bae119f996de31f86a5eddccef54c1d" +checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" dependencies = [ "value-bag-serde1", "value-bag-sval2", @@ -25684,9 +25679,9 @@ dependencies = [ [[package]] name = "value-bag-serde1" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ead5b693d906686203f19a49e88c477fb8c15798b68cf72f60b4b5521b4ad891" +checksum = "ccacf50c5cb077a9abb723c5bcb5e0754c1a433f1e1de89edc328e2760b6328b" dependencies = [ "erased-serde", "serde", @@ -25695,9 +25690,9 @@ dependencies = [ [[package]] name = "value-bag-sval2" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9d0f4a816370c3a0d7d82d603b62198af17675b12fe5e91de6b47ceb505882" +checksum = "1785bae486022dfb9703915d42287dcb284c1ee37bd1080eeba78cc04721285b" dependencies = [ "sval", "sval_buffer", @@ -25722,9 +25717,9 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "void" @@ -25734,9 +25729,9 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "w3f-bls" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7335e4c132c28cc43caef6adb339789e599e39adbe78da0c4d547fad48cbc331" +checksum = "9c5da5fa2c6afa2c9158eaa7cd9aee249765eb32b5fb0c63ad8b9e79336a47ec" dependencies = [ "ark-bls12-377", "ark-bls12-381", @@ -25767,9 +25762,9 @@ dependencies = [ [[package]] name = "waker-fn" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" [[package]] name = "walkdir" @@ -25796,11 +25791,20 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasix" +version = "0.12.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d" +dependencies = [ + "wasi", +] + [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", "once_cell", @@ -25811,14 +25815,14 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", "wasm-bindgen-shared", @@ -25826,9 +25830,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.37" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if", "js-sys", @@ -25838,9 +25842,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote 1.0.37", "wasm-bindgen-macro-support", @@ -25848,11 +25852,11 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", "wasm-bindgen-backend", @@ -25861,18 +25865,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "wasm-bindgen-test" -version = "0.3.37" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e6e302a7ea94f83a6d09e78e7dc7d9ca7b186bc2829c24a22d0753efd680671" +checksum = "d381749acb0943d357dcbd8f0b100640679883fcdeeef04def49daf8d33a5426" dependencies = [ "console_error_panic_hook", "js-sys", + "minicov", "scoped-tls", "wasm-bindgen", "wasm-bindgen-futures", @@ -25881,21 +25886,23 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.37" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecb993dd8c836930ed130e020e77d9b2e65dd0fbab1b67c790b0f5d80b11a575" +checksum = "c97b2ef2c8d627381e51c071c2ab328eac606d3f69dd82bcbca20a9e389d95f0" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", + "syn 2.0.79", ] [[package]] name = "wasm-encoder" -version = "0.31.1" +version = "0.219.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41763f20eafed1399fff1afb466496d3a959f58241436cfdc17e3f5ca954de16" +checksum = "29cbbd772edcb8e7d524a82ee8cef8dd046fc14033796a754c3ad246d019fa54" dependencies = [ "leb128", + "wasmparser 0.219.1", ] [[package]] @@ -25928,9 +25935,9 @@ dependencies = [ [[package]] name = "wasm-opt" -version = "0.116.0" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc942673e7684671f0c5708fc18993569d184265fd5223bb51fc8e5b9b6cfd52" +checksum = "2fd87a4c135535ffed86123b6fb0f0a5a0bc89e50416c942c5f0662c645f679c" dependencies = [ "anyhow", "libc", @@ -25983,7 +25990,7 @@ dependencies = [ "sp-runtime 37.0.0", "sp-state-machine 0.41.0", "sp-version 35.0.0", - "sp-wasm-interface 21.0.0", + "sp-wasm-interface 21.0.1", "substrate-runtime-proposal-hash", "thiserror", "wasm-loader", @@ -26023,7 +26030,7 @@ version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50386c99b9c32bd2ed71a55b6dd4040af2580530fae8bdb9a6576571a80d0cca" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "multi-stash", "num-derive", "num-traits", @@ -26085,6 +26092,16 @@ dependencies = [ "url", ] +[[package]] +name = "wasmparser" +version = "0.219.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c771866898879073c53b565a6c7b49953795159836714ac56a5befb581227c5" +dependencies = [ + "bitflags 2.6.0", + "indexmap 2.6.0", +] + [[package]] name = "wasmparser-nostd" version = "0.100.2" @@ -26113,7 +26130,7 @@ dependencies = [ "rayon", "serde", "target-lexicon", - "wasmparser", + "wasmparser 0.102.0", "wasmtime-cache", "wasmtime-cranelift", "wasmtime-environ", @@ -26143,7 +26160,7 @@ dependencies = [ "directories-next", "file-per-thread-logger", "log", - "rustix 0.36.15", + "rustix 0.36.17", "serde", "sha2 0.10.8", "toml 0.5.11", @@ -26168,7 +26185,7 @@ dependencies = [ "object 0.30.4", "target-lexicon", "thiserror", - "wasmparser", + "wasmparser 0.102.0", "wasmtime-cranelift-shared", "wasmtime-environ", ] @@ -26203,7 +26220,7 @@ dependencies = [ "serde", "target-lexicon", "thiserror", - "wasmparser", + "wasmparser 0.102.0", "wasmtime-types", ] @@ -26239,7 +26256,7 @@ checksum = "6e0554b84c15a27d76281d06838aed94e13a77d7bf604bbbaf548aa20eb93846" dependencies = [ "object 0.30.4", "once_cell", - "rustix 0.36.15", + "rustix 0.36.17", ] [[package]] @@ -26267,10 +26284,10 @@ dependencies = [ "log", "mach", "memfd", - "memoffset 0.8.0", + "memoffset", "paste", "rand", - "rustix 0.36.15", + "rustix 0.36.17", "wasmtime-asm-macros", "wasmtime-environ", "wasmtime-jit-debug", @@ -26286,15 +26303,16 @@ dependencies = [ "cranelift-entity", "serde", "thiserror", - "wasmparser", + "wasmparser 0.102.0", ] [[package]] name = "wast" -version = "63.0.0" +version = "219.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2560471f60a48b77fccefaf40796fda61c97ce1e790b59dfcec9dc3995c9f63a" +checksum = "4f79a9d9df79986a68689a6b40bcc8d5d40d807487b235bebc2ac69a242b54a1" dependencies = [ + "bumpalo", "leb128", "memchr", "unicode-width", @@ -26303,18 +26321,18 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.70" +version = "1.219.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdc306c2c4c2f2bf2ba69e083731d0d2a77437fc6a350a19db139636e7e416c" +checksum = "8bc3cf014fb336883a411cd662f987abf6a1d2a27f2f0008616a0070bbf6bd0d" dependencies = [ "wast", ] [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" dependencies = [ "js-sys", "wasm-bindgen", @@ -26326,21 +26344,21 @@ version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" dependencies = [ - "ring 0.17.7", + "ring 0.17.8", "untrusted 0.9.0", ] [[package]] name = "webpki-roots" -version = "0.25.2" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "0.26.3" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" dependencies = [ "rustls-pki-types", ] @@ -26508,9 +26526,9 @@ dependencies = [ [[package]] name = "wide" -version = "0.7.11" +version = "0.7.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa469ffa65ef7e0ba0f164183697b89b854253fd31aeb92358b7b6155177d62f" +checksum = "b828f995bf1e9622031f8009f8481a85406ce1f4d4588ff746d872043e855690" dependencies = [ "bytemuck", "safe_arch", @@ -26518,9 +26536,9 @@ dependencies = [ [[package]] name = "widestring" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" [[package]] name = "winapi" @@ -26540,11 +26558,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "winapi", + "windows-sys 0.59.0", ] [[package]] @@ -26553,15 +26571,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows" version = "0.51.1" @@ -26579,7 +26588,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ "windows-core 0.52.0", - "windows-targets 0.52.0", + "windows-targets 0.52.6", ] [[package]] @@ -26597,7 +26606,37 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", ] [[package]] @@ -26624,7 +26663,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -26659,17 +26707,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -26686,9 +26735,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -26704,9 +26753,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -26722,9 +26771,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -26740,9 +26795,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -26758,9 +26813,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -26776,9 +26831,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -26794,24 +26849,24 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.5.15" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" dependencies = [ "memchr", ] [[package]] name = "winnow" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] @@ -26826,16 +26881,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "winreg" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "wyz" version = "0.5.1" @@ -26847,9 +26892,9 @@ dependencies = [ [[package]] name = "x25519-dalek" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ "curve25519-dalek 4.1.3", "rand_core 0.6.4", @@ -26880,12 +26925,12 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" dependencies = [ - "asn1-rs 0.6.1", + "asn1-rs 0.6.2", "data-encoding", "der-parser 9.0.0", "lazy_static", "nom", - "oid-registry 0.7.0", + "oid-registry 0.7.1", "rusticata-macros", "thiserror", "time", @@ -26893,11 +26938,13 @@ dependencies = [ [[package]] name = "xattr" -version = "1.0.1" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" dependencies = [ "libc", + "linux-raw-sys 0.4.14", + "rustix 0.38.37", ] [[package]] @@ -26985,7 +27032,7 @@ name = "xcm-procedural" version = "7.0.0" dependencies = [ "Inflector", - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "staging-xcm", "syn 2.0.79", @@ -27093,9 +27140,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.20" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" +checksum = "af4e2e2f7cba5a093896c1e150fbfe177d1883e7448200efb81d40b9d339ef26" [[package]] name = "xmltree" @@ -27123,9 +27170,9 @@ dependencies = [ [[package]] name = "yansi" -version = "0.5.1" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yap" @@ -27144,20 +27191,21 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -27177,7 +27225,7 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.87", "quote 1.0.37", "syn 2.0.79", ] @@ -27188,7 +27236,7 @@ version = "1.0.0" dependencies = [ "futures-util", "parity-scale-codec", - "reqwest 0.11.20", + "reqwest 0.11.27", "serde", "serde_json", "thiserror", @@ -27206,9 +27254,9 @@ checksum = "ebbfc98adb25076777967f7aad078e74029e129b102eb0812c425432f8c2be7b" dependencies = [ "anyhow", "lazy_static", - "multiaddr 0.18.1", + "multiaddr 0.18.2", "regex", - "reqwest 0.11.20", + "reqwest 0.11.27", "serde", "serde_json", "thiserror", @@ -27231,10 +27279,10 @@ dependencies = [ "hex", "libp2p", "libsecp256k1", - "multiaddr 0.18.1", + "multiaddr 0.18.2", "rand", "regex", - "reqwest 0.11.20", + "reqwest 0.11.27", "serde", "serde_json", "sha2 0.10.8", @@ -27277,7 +27325,7 @@ dependencies = [ "kube", "nix 0.27.1", "regex", - "reqwest 0.11.20", + "reqwest 0.11.27", "serde", "serde_json", "serde_yaml", @@ -27322,7 +27370,7 @@ dependencies = [ "nix 0.27.1", "rand", "regex", - "reqwest 0.11.20", + "reqwest 0.11.27", "thiserror", "tokio", "tracing", @@ -27369,11 +27417,10 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.8+zstd.1.5.5" +version = "2.0.13+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" +checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" dependencies = [ "cc", - "libc", "pkg-config", ] diff --git a/substrate/frame/staking/src/benchmarking.rs b/substrate/frame/staking/src/benchmarking.rs index a25085a180369..fea16c7cf6545 100644 --- a/substrate/frame/staking/src/benchmarking.rs +++ b/substrate/frame/staking/src/benchmarking.rs @@ -42,6 +42,7 @@ use frame_system::RawOrigin; const SEED: u32 = 0; const MAX_SPANS: u32 = 100; const MAX_SLASHES: u32 = 1000; +const SINGLE_PAGE: u32 = 0; type MaxValidators = <::BenchmarkingConfig as BenchmarkingConfig>::MaxValidators; type MaxNominators = <::BenchmarkingConfig as BenchmarkingConfig>::MaxNominators; @@ -827,7 +828,7 @@ benchmarks! { let num_voters = (v + n) as usize; }: { // default bounds are unbounded. - let voters = >::get_npos_voters(DataProviderBounds::default()); + let voters = >::get_npos_voters(DataProviderBounds::default(), SINGLE_PAGE); assert_eq!(voters.len(), num_voters); } @@ -842,7 +843,7 @@ benchmarks! { )?; }: { // default bounds are unbounded. - let targets = >::get_npos_targets(DataProviderBounds::default()); + let targets = >::get_npos_targets(DataProviderBounds::default(), SINGLE_PAGE); assert_eq!(targets.len() as u32, v); } diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index 19d999109d8dd..bf8417d1ac74d 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -309,6 +309,7 @@ extern crate alloc; use alloc::{collections::btree_map::BTreeMap, vec, vec::Vec}; use codec::{Decode, Encode, HasCompact, MaxEncodedLen}; +use frame_election_provider_support::ElectionProvider; use frame_support::{ defensive, defensive_assert, traits::{ @@ -347,9 +348,14 @@ macro_rules! log { }; } -/// Maximum number of winners (aka. active validators), as defined in the election provider of this -/// pallet. -pub type MaxWinnersOf = <::ElectionProvider as frame_election_provider_support::ElectionProviderBase>::MaxWinners; +/// Alias fo the maximum number of winners (aka. active validators), as defined in by this pallet's +/// config. +pub type MaxWinnersOf = ::MaxValidatorSet; + +/// Maximum number of exposures (validators) that each page of [`Config::ElectionProvider`] might +/// might return. +pub type MaxExposuresPerPageOf = + <::ElectionProvider as ElectionProvider>::MaxWinnersPerPage; /// Maximum number of nominations per nominator. pub type MaxNominationsOf = @@ -382,6 +388,13 @@ pub struct ActiveEraInfo { pub start: Option, } +/// Pointer to the last iterated indices for targets and voters used when generating the snapshot. +#[derive(Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub(crate) struct LastIteratedStakers { + voter: AccountId, + target: AccountId, +} + /// Reward points of an era. Used to split era total payout between validators. /// /// This points will be used to reward validators and their respective nominators. @@ -440,6 +453,23 @@ pub struct UnlockChunk { era: EraIndex, } +/// Status of a paged snapshot progress. +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub enum SnapshotStatus { + /// Paged snapshot is in progress, the `AccountId` was the last staker iterated. + Ongoing(AccountId), + /// All the stakers in the system have been consumed since the snapshot started. + Consumed, + /// Waiting for a new snapshot to be requested. + Waiting, +} + +impl Default for SnapshotStatus { + fn default() -> Self { + Self::Waiting + } +} + /// The ledger of a (bonded) stash. /// /// Note: All the reads and mutations to the [`Ledger`], [`Bonded`] and [`Payee`] storage items @@ -1237,9 +1267,21 @@ impl EraInfo { let (exposure_metadata, exposure_pages) = exposure.into_pages(page_size); defensive_assert!(exposure_pages.len() == expected_page_count, "unexpected page count"); - >::insert(era, &validator, &exposure_metadata); - exposure_pages.iter().enumerate().for_each(|(page, paged_exposure)| { - >::insert((era, &validator, page as Page), &paged_exposure); + // insert or update validator's overview. + let append_from = ErasStakersOverview::::mutate(era, &validator, |stored| { + if let Some(stored_overview) = stored { + let append_from = stored_overview.page_count; + *stored = Some(stored_overview.merge(exposure_metadata)); + append_from + } else { + *stored = Some(exposure_metadata); + Zero::zero() + } + }); + + exposure_pages.iter().enumerate().for_each(|(idx, paged_exposure)| { + let append_at = (append_from + idx as u32) as Page; + >::insert((era, &validator, append_at), &paged_exposure); }); } @@ -1247,6 +1289,12 @@ impl EraInfo { pub(crate) fn set_total_stake(era: EraIndex, total_stake: BalanceOf) { >::insert(era, total_stake); } + + pub(crate) fn add_total_stake(era: EraIndex, stake: BalanceOf) { + >::mutate(era, |total_stake| { + *total_stake += stake; + }); + } } /// Configurations of the benchmarking of the pallet. diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index 4a0209fc5b083..e9a761a59a81b 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -20,7 +20,7 @@ use crate::{self as pallet_staking, *}; use frame_election_provider_support::{ bounds::{ElectionBounds, ElectionBoundsBuilder}, - onchain, SequentialPhragmen, VoteWeight, + onchain, BoundedSupports, SequentialPhragmen, Support, TryIntoBoundedSupports, VoteWeight, }; use frame_support::{ assert_ok, derive_impl, ord_parameter_types, parameter_types, @@ -38,8 +38,9 @@ use sp_staking::{ OnStakingUpdate, }; -pub const INIT_TIMESTAMP: u64 = 30_000; -pub const BLOCK_TIME: u64 = 1000; +pub(crate) const INIT_TIMESTAMP: u64 = 30_000; +pub(crate) const BLOCK_TIME: u64 = 1000; +pub(crate) const SINGLE_PAGE: u32 = 0; /// The AccountId alias in this test module. pub(crate) type AccountId = u64; @@ -204,7 +205,7 @@ parameter_types! { pub static MaxExposurePageSize: u32 = 64; pub static MaxUnlockingChunks: u32 = 32; pub static RewardOnUnbalanceWasCalled: bool = false; - pub static MaxWinners: u32 = 100; + pub static MaxValidatorSet: u32 = 100; pub static ElectionsBounds: ElectionBounds = ElectionBoundsBuilder::default().build(); pub static AbsoluteMaxNominations: u32 = 16; } @@ -225,8 +226,9 @@ impl onchain::Config for OnChainSeqPhragmen { type Solver = SequentialPhragmen; type DataProvider = Staking; type WeightInfo = (); - type MaxWinners = MaxWinners; type Bounds = ElectionsBounds; + type MaxBackersPerWinner = ConstU32<{ u32::MAX }>; + type MaxWinnersPerPage = ConstU32<{ u32::MAX }>; } pub struct MockReward {} @@ -274,6 +276,7 @@ impl crate::pallet::pallet::Config for Test { type EraPayout = ConvertCurve; type NextNewSession = Session; type MaxExposurePageSize = MaxExposurePageSize; + type MaxValidatorSet = MaxValidatorSet; type ElectionProvider = onchain::OnChainExecution; type GenesisElectionProvider = Self::ElectionProvider; // NOTE: consider a macro and use `UseNominatorsAndValidatorsMap` as well. @@ -419,6 +422,10 @@ impl ExtBuilder { self.stakers.push((stash, ctrl, stake, status)); self } + pub fn exposures_page_size(self, max: u32) -> Self { + MaxExposurePageSize::set(max); + self + } pub fn balance_factor(mut self, factor: Balance) -> Self { self.balance_factor = factor; self @@ -928,3 +935,13 @@ pub(crate) fn staking_events_since_last_call() -> Vec> { pub(crate) fn balances(who: &AccountId) -> (Balance, Balance) { (Balances::free_balance(who), Balances::reserved_balance(who)) } + +pub(crate) fn to_bounded_supports( + supports: Vec<(AccountId, Support)>, +) -> BoundedSupports< + AccountId, + <::ElectionProvider as ElectionProvider>::MaxBackersPerWinner, + <::ElectionProvider as ElectionProvider>::MaxWinnersPerPage, +> { + supports.try_into_bounded_supports().unwrap() +} diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index 6c4fe8140e8ef..b6892c5676801 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -17,10 +17,14 @@ //! Implementations for the Staking FRAME Pallet. +// TODO: remove +#![allow(dead_code)] + use frame_election_provider_support::{ bounds::{CountBound, SizeBound}, data_provider, BoundedSupportsOf, DataProviderBounds, ElectionDataProvider, ElectionProvider, - ScoreProvider, SortedListProvider, VoteWeight, VoterOf, + LockableElectionDataProvider, PageIndex, ScoreProvider, SortedListProvider, VoteWeight, + VoterOf, }; use frame_support::{ defensive, @@ -52,8 +56,9 @@ use sp_staking::{ use crate::{ asset, election_size_tracker::StaticTracker, log, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraInfo, EraPayout, Exposure, ExposureOf, Forcing, IndividualExposure, - LedgerIntegrityState, MaxNominationsOf, MaxWinnersOf, Nominations, NominationsQuota, - PositiveImbalanceOf, RewardDestination, SessionInterface, StakingLedger, ValidatorPrefs, + LedgerIntegrityState, MaxExposuresPerPageOf, MaxNominationsOf, MaxWinnersOf, Nominations, + NominationsQuota, PositiveImbalanceOf, RewardDestination, SessionInterface, SnapshotStatus, + StakingLedger, ValidatorPrefs, }; use alloc::{boxed::Box, vec, vec::Vec}; @@ -480,6 +485,10 @@ impl Pallet { Self::set_force_era(Forcing::NotForcing); } + //TODO: we may want to keep track of the past 1/N era's stashes + // reset electable stashes. + ElectableStashes::::kill(); + maybe_new_era_validators } else { // Set initial era. @@ -616,9 +625,9 @@ impl Pallet { start_session_index: SessionIndex, exposures: BoundedVec< (T::AccountId, Exposure>), - MaxWinnersOf, + MaxExposuresPerPageOf, >, - ) -> BoundedVec> { + ) -> BoundedVec> { // Increment or set current era. let new_planned_era = CurrentEra::::mutate(|s| { *s = Some(s.map(|s| s + 1).unwrap_or(0)); @@ -635,6 +644,20 @@ impl Pallet { Self::store_stakers_info(exposures, new_planned_era) } + pub fn trigger_new_era_paged(start_session_index: SessionIndex) { + // Increment or set current era. + let new_planned_era = CurrentEra::::mutate(|s| { + *s = Some(s.map(|s| s + 1).unwrap_or(0)); + s.unwrap() + }); + ErasStartSessionIndex::::insert(&new_planned_era, &start_session_index); + + // Clean old era information. + if let Some(old_era) = new_planned_era.checked_sub(T::HistoryDepth::get() + 1) { + Self::clear_era_information(old_era); + } + } + /// Potentially plan a new era. /// /// Get election result from `T::ElectionProvider`. @@ -645,28 +668,29 @@ impl Pallet { start_session_index: SessionIndex, is_genesis: bool, ) -> Option>> { - let election_result: BoundedVec<_, MaxWinnersOf> = if is_genesis { - let result = ::elect().map_err(|e| { + let validators: BoundedVec> = if is_genesis { + // genesis election only use the lsp of the election result. + let result = ::elect(Zero::zero()).map_err(|e| { log!(warn, "genesis election provider failed due to {:?}", e); Self::deposit_event(Event::StakingElectionFailed); }); - result - .ok()? - .into_inner() - .try_into() - // both bounds checked in integrity test to be equal - .defensive_unwrap_or_default() + let (_, planned_era) = ElectingStartedAt::::get().unwrap_or_default(); + let exposures = Self::collect_exposures(result.ok().unwrap_or_default()); + Self::store_stakers_info_paged(exposures.clone(), planned_era); + + exposures + .into_iter() + .map(|(validator, _)| validator) + .try_collect() + .unwrap_or_default() } else { - let result = ::elect().map_err(|e| { - log!(warn, "election provider failed due to {:?}", e); - Self::deposit_event(Event::StakingElectionFailed); - }); - result.ok()? + ElectableStashes::::get() }; - let exposures = Self::collect_exposures(election_result); - if (exposures.len() as u32) < Self::minimum_validator_count().max(1) { + log!(info, "electable validators for session {:?}: {:?}", start_session_index, validators); + + if (validators.len() as u32) < Self::minimum_validator_count().max(1) { // Session will panic if we ever return an empty validator set, thus max(1) ^^. match CurrentEra::::get() { Some(current_era) if current_era > 0 => log!( @@ -674,7 +698,7 @@ impl Pallet { "chain does not have enough staking candidates to operate for era {:?} ({} \ elected, minimum is {})", CurrentEra::::get().unwrap_or(0), - exposures.len(), + validators.len(), Self::minimum_validator_count(), ), None => { @@ -685,7 +709,7 @@ impl Pallet { CurrentEra::::put(0); ErasStartSessionIndex::::insert(&0, &start_session_index); }, - _ => (), + _ => {}, } Self::deposit_event(Event::StakingElectionFailed); @@ -693,7 +717,82 @@ impl Pallet { } Self::deposit_event(Event::StakersElected); - Some(Self::trigger_new_era(start_session_index, exposures)) + Self::trigger_new_era_paged(start_session_index); + Some(validators) + } + + /// Paginated elect. + /// + /// TODO: rust-docs + pub(crate) fn do_elect_paged(page: PageIndex) { + let paged_result = match ::elect(page) { + Ok(result) => result, + Err(e) => { + log!(warn, "electiong provider page failed due to {:?} (page: {})", e, page); + // TODO: be resilient here, not all pages need to be submitted successfuly for an + // election to be OK, provided that the election score is good enough. + Self::deposit_event(Event::StakingElectionFailed); + return + }, + }; + + let new_planned_era = CurrentEra::::get().unwrap_or_default().saturating_add(1); + let stashes = + Self::store_stakers_info_paged(Self::collect_exposures(paged_result), new_planned_era); + + ElectableStashes::::mutate(|v| { + // TODO: be even more defensive and handle potential error? (should not happen if page + // bounds and T::MaxValidatorSet configs are in sync). + let _ = (*v).try_extend(stashes.into_iter()).defensive(); + }); + } + + /// Process the output of a paged election. + /// + /// Store staking information for the new planned era + pub fn store_stakers_info_paged( + exposures: BoundedVec< + (T::AccountId, Exposure>), + MaxExposuresPerPageOf, + >, + new_planned_era: EraIndex, + ) -> BoundedVec> { + // Populate elected stash, stakers, exposures, and the snapshot of validator prefs. + let mut total_stake: BalanceOf = Zero::zero(); + let mut elected_stashes = Vec::with_capacity(exposures.len()); + + exposures.into_iter().for_each(|(stash, exposure)| { + // build elected stash + elected_stashes.push(stash.clone()); + // accumulate total stake + total_stake = total_stake.saturating_add(exposure.total); + // store staker exposure for this era + EraInfo::::set_exposure(new_planned_era, &stash, exposure); + }); + + // TODO: correct?? + let elected_stashes: BoundedVec<_, MaxExposuresPerPageOf> = elected_stashes + .try_into() + .expect("elected_stashes.len() always equal to exposures.len(); qed"); + + EraInfo::::add_total_stake(new_planned_era, total_stake); + + // Collect the pref of all winners. + for stash in &elected_stashes { + let pref = Self::validators(stash); + >::insert(&new_planned_era, stash, pref); + } + + if new_planned_era > 0 { + log!( + info, + "updated validator set (current size {:?}) for era {:?}", + elected_stashes.len(), + new_planned_era, + ); + } + + elected_stashes } /// Process the output of the election. @@ -702,10 +801,10 @@ impl Pallet { pub fn store_stakers_info( exposures: BoundedVec< (T::AccountId, Exposure>), - MaxWinnersOf, + MaxExposuresPerPageOf, >, new_planned_era: EraIndex, - ) -> BoundedVec> { + ) -> BoundedVec> { // Populate elected stash, stakers, exposures, and the snapshot of validator prefs. let mut total_stake: BalanceOf = Zero::zero(); let mut elected_stashes = Vec::with_capacity(exposures.len()); @@ -719,7 +818,7 @@ impl Pallet { EraInfo::::set_exposure(new_planned_era, &stash, exposure); }); - let elected_stashes: BoundedVec<_, MaxWinnersOf> = elected_stashes + let elected_stashes: BoundedVec<_, MaxExposuresPerPageOf> = elected_stashes .try_into() .expect("elected_stashes.len() always equal to exposures.len(); qed"); @@ -747,7 +846,8 @@ impl Pallet { /// [`Exposure`]. fn collect_exposures( supports: BoundedSupportsOf, - ) -> BoundedVec<(T::AccountId, Exposure>), MaxWinnersOf> { + ) -> BoundedVec<(T::AccountId, Exposure>), MaxExposuresPerPageOf> + { let total_issuance = asset::total_issuance::(); let to_currency = |e: frame_election_provider_support::ExtendedBalance| { T::CurrencyToVote::to_currency(e, total_issuance) @@ -891,7 +991,10 @@ impl Pallet { /// nominators. /// /// This function is self-weighing as [`DispatchClass::Mandatory`]. - pub fn get_npos_voters(bounds: DataProviderBounds) -> Vec> { + pub fn get_npos_voters( + bounds: DataProviderBounds, + remaining_pages: PageIndex, + ) -> Vec> { let mut voters_size_tracker: StaticTracker = StaticTracker::default(); let final_predicted_len = { @@ -909,7 +1012,16 @@ impl Pallet { let mut nominators_taken = 0u32; let mut min_active_stake = u64::MAX; - let mut sorted_voters = T::VoterList::iter(); + let mut sorted_voters = match VoterSnapshotStatus::::get() { + // snapshot continues, start from last iterated voter in the list. + SnapshotStatus::Ongoing(start_at) => + T::VoterList::iter_from(&start_at).unwrap_or_else(|_| T::TargetList::iter()), + // all the voters have been consumed, return an empty iterator. + SnapshotStatus::Consumed => Box::new(vec![].into_iter()), + // start the snapshot processing, start from the beginning. + SnapshotStatus::Waiting => T::VoterList::iter(), + }; + while all_voters.len() < final_predicted_len as usize && voters_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * final_predicted_len as u32) { @@ -982,6 +1094,21 @@ impl Pallet { } } + match (remaining_pages, VoterSnapshotStatus::::get()) { + // last page requested, reset. + (0, _) => VoterSnapshotStatus::::set(SnapshotStatus::Waiting), + // all voters have been consumed, do nothing. + (_, SnapshotStatus::Consumed) => {}, + (_, SnapshotStatus::Waiting) | (_, SnapshotStatus::Ongoing(_)) => { + if let Some(last) = all_voters.last().map(|(x, _, _)| x).cloned() { + VoterSnapshotStatus::::set(SnapshotStatus::Ongoing(last)); + } else { + // no more to consume, next pages will be empty. + VoterSnapshotStatus::::set(SnapshotStatus::Consumed); + } + }, + }; + // all_voters should have not re-allocated. debug_assert!(all_voters.capacity() == final_predicted_len as usize); @@ -1006,7 +1133,10 @@ impl Pallet { /// Get the targets for an upcoming npos election. /// /// This function is self-weighing as [`DispatchClass::Mandatory`]. - pub fn get_npos_targets(bounds: DataProviderBounds) -> Vec { + pub fn get_npos_targets( + bounds: DataProviderBounds, + remaining_pages: PageIndex, + ) -> Vec { let mut targets_size_tracker: StaticTracker = StaticTracker::default(); let final_predicted_len = { @@ -1017,7 +1147,16 @@ impl Pallet { let mut all_targets = Vec::::with_capacity(final_predicted_len as usize); let mut targets_seen = 0; - let mut targets_iter = T::TargetList::iter(); + let mut targets_iter = match TargetSnapshotStatus::::get() { + // snapshot continues, start from last iterated target in the list. + SnapshotStatus::Ongoing(start_at) => + T::TargetList::iter_from(&start_at).unwrap_or_else(|_| T::TargetList::iter()), + // all the targets have been consumed, return an empty iterator. + SnapshotStatus::Consumed => Box::new(vec![].into_iter()), + // start the snapshot processing, start from the beginning. + SnapshotStatus::Waiting => T::TargetList::iter(), + }; + while all_targets.len() < final_predicted_len as usize && targets_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * final_predicted_len as u32) { @@ -1042,6 +1181,21 @@ impl Pallet { } } + match (remaining_pages, TargetSnapshotStatus::::get()) { + // last page requested, reset. + (0, _) => TargetSnapshotStatus::::set(SnapshotStatus::Waiting), + // all targets have been consumed, do nothing. + (_, SnapshotStatus::Consumed) => {}, + (_, SnapshotStatus::Waiting) | (_, SnapshotStatus::Ongoing(_)) => { + if let Some(last) = all_targets.last().cloned() { + TargetSnapshotStatus::::set(SnapshotStatus::Ongoing(last)); + } else { + // no more to consume, next pages will be empty. + TargetSnapshotStatus::::set(SnapshotStatus::Consumed); + } + }, + }; + Self::register_weight(T::WeightInfo::get_npos_targets(all_targets.len() as u32)); log!(info, "generated {} npos targets", all_targets.len()); @@ -1187,6 +1341,23 @@ impl Pallet { } } +// TODO(gpestana): add unit tests. +impl LockableElectionDataProvider for Pallet { + fn set_lock() -> data_provider::Result<()> { + match ElectionDataLock::::get() { + Some(_) => Err("lock already set"), + None => { + ElectionDataLock::::set(Some(())); + Ok(()) + }, + } + } + + fn unlock() { + ElectionDataLock::::set(None); + } +} + impl ElectionDataProvider for Pallet { type AccountId = T::AccountId; type BlockNumber = BlockNumberFor; @@ -1197,9 +1368,11 @@ impl ElectionDataProvider for Pallet { Ok(Self::validator_count()) } - fn electing_voters(bounds: DataProviderBounds) -> data_provider::Result>> { - // This can never fail -- if `maybe_max_len` is `Some(_)` we handle it. - let voters = Self::get_npos_voters(bounds); + fn electing_voters( + bounds: DataProviderBounds, + remaining_pages: PageIndex, + ) -> data_provider::Result>> { + let voters = Self::get_npos_voters(bounds, remaining_pages); debug_assert!(!bounds.exhausted( SizeBound(voters.encoded_size() as u32).into(), @@ -1209,12 +1382,14 @@ impl ElectionDataProvider for Pallet { Ok(voters) } - fn electable_targets(bounds: DataProviderBounds) -> data_provider::Result> { - let targets = Self::get_npos_targets(bounds); - + fn electable_targets( + bounds: DataProviderBounds, + remaining: PageIndex, + ) -> data_provider::Result> { + let targets = Self::get_npos_targets(bounds, remaining); // We can't handle this case yet -- return an error. WIP to improve handling this case in // . - if bounds.exhausted(None, CountBound(T::TargetList::count() as u32).into()) { + if bounds.exhausted(None, CountBound(targets.len() as u32).into()) { return Err("Target snapshot too big") } @@ -1276,7 +1451,7 @@ impl ElectionDataProvider for Pallet { #[cfg(feature = "runtime-benchmarks")] fn add_target(target: T::AccountId) { - let stake = MinValidatorBond::::get() * 100u32.into(); + let stake = (MinValidatorBond::::get() + 1u32.into()) * 100u32.into(); >::insert(target.clone(), target.clone()); >::insert(target.clone(), StakingLedger::::new(target.clone(), stake)); Self::do_add_validator( @@ -1329,6 +1504,11 @@ impl ElectionDataProvider for Pallet { ); }); } + + #[cfg(feature = "runtime-benchmarks")] + fn set_desired_targets(count: u32) { + ValidatorCount::::put(count); + } } /// In this implementation `new_session(session)` must be called before `end_session(session-1)` @@ -1836,7 +2016,9 @@ impl StakingInterface for Pallet { } fn election_ongoing() -> bool { - T::ElectionProvider::ongoing() + // TODO(gpestana) + //T::ElectionProvider::ongoing() + false } fn force_unstake(who: Self::AccountId) -> sp_runtime::DispatchResult { @@ -2071,11 +2253,6 @@ impl Pallet { ::TargetList::count() == Validators::::count(), "wrong external count" ); - ensure!( - ValidatorCount::::get() <= - ::MaxWinners::get(), - Error::::TooManyValidators - ); Ok(()) } diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index 28aa4f89b6227..f03b110c64db2 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -19,9 +19,7 @@ use alloc::vec::Vec; use codec::Codec; -use frame_election_provider_support::{ - ElectionProvider, ElectionProviderBase, SortedListProvider, VoteWeight, -}; +use frame_election_provider_support::{ElectionProvider, SortedListProvider, VoteWeight}; use frame_support::{ pallet_prelude::*, traits::{ @@ -33,8 +31,8 @@ use frame_support::{ }; use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; use sp_runtime::{ - traits::{SaturatedConversion, StaticLookup, Zero}, - ArithmeticError, Perbill, Percent, + traits::{One, SaturatedConversion, StaticLookup, Zero}, + ArithmeticError, Perbill, Percent, Saturating, }; use sp_staking::{ @@ -62,12 +60,11 @@ pub(crate) const SPECULATIVE_NUM_SPANS: u32 = 32; #[frame_support::pallet] pub mod pallet { - use frame_election_provider_support::ElectionDataProvider; - - use crate::{BenchmarkingConfig, PagedExposureMetadata}; - use super::*; + use crate::{BenchmarkingConfig, PagedExposureMetadata, SnapshotStatus}; + use frame_election_provider_support::{ElectionDataProvider, PageIndex}; + /// The in-code storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(15); @@ -136,6 +133,9 @@ pub mod pallet { AccountId = Self::AccountId, BlockNumber = BlockNumberFor, DataProvider = Pallet, + Pages = ConstU32<1>, + MaxWinnersPerPage = ::MaxWinnersPerPage, + MaxBackersPerWinner = ::MaxBackersPerWinner, >; /// Something that defines the maximum number of nominations per nominator. @@ -232,6 +232,11 @@ pub mod pallet { #[pallet::constant] type MaxExposurePageSize: Get; + /// The absolute maximum of next winner validators this pallet should return. + #[pallet::constant] + #[pallet::no_default] + type MaxValidatorSet: Get; + /// Something that provides a best-effort sorted list of voters aka electing nominators, /// used for NPoS election. /// @@ -735,6 +740,43 @@ pub mod pallet { #[pallet::storage] pub(crate) type ChillThreshold = StorageValue<_, Percent, OptionQuery>; + /// Voter snapshot progress status. + /// + /// If the status is `Ongoing`, it keeps track of the last voter account returned in the + /// snapshot. + #[pallet::storage] + pub(crate) type VoterSnapshotStatus = + StorageValue<_, SnapshotStatus, ValueQuery>; + + /// Target snapshot progress status. + /// + /// If the status is `Ongoing`, it keeps track of the last target account returned in the + /// snapshot. + #[pallet::storage] + pub(crate) type TargetSnapshotStatus = + StorageValue<_, SnapshotStatus, ValueQuery>; + + /// Keeps track of an ongoing multi-page election solution request and the block the first paged + /// was requested, if any. In addition, it also keeps track of the current era that is being + /// plannet. + #[pallet::storage] + pub(crate) type ElectingStartedAt = + StorageValue<_, (BlockNumberFor, EraIndex), OptionQuery>; + + // TODO: + // * maybe use pallet-paged-list? (https://paritytech.github.io/polkadot-sdk/master/pallet_paged_list/index.html) + #[pallet::storage] + pub(crate) type ElectableStashes = + StorageValue<_, BoundedVec, ValueQuery>; + + /// Lock for election data provider. + /// + /// While the lock is set, the data to build a snapshot is frozen, i.e. the returned data from + /// `ElectionDataProvider` implementation will not change. + #[pallet::storage] + #[pallet::getter(fn election_data_lock)] + pub(crate) type ElectionDataLock = StorageValue<_, (), OptionQuery>; + #[pallet::genesis_config] #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig { @@ -798,10 +840,6 @@ pub mod pallet { ), _ => Ok(()), }); - assert!( - ValidatorCount::::get() <= - ::MaxWinners::get() - ); } // all voters are reported to the `VoterList`. @@ -818,7 +856,11 @@ pub mod pallet { pub enum Event { /// The era payout has been set; the first balance is the validator-payout; the second is /// the remainder from the maximum amount of reward. - EraPaid { era_index: EraIndex, validator_payout: BalanceOf, remainder: BalanceOf }, + EraPaid { + era_index: EraIndex, + validator_payout: BalanceOf, + remainder: BalanceOf, + }, /// The nominator has been rewarded by this amount to this destination. Rewarded { stash: T::AccountId, @@ -826,43 +868,79 @@ pub mod pallet { amount: BalanceOf, }, /// A staker (validator or nominator) has been slashed by the given amount. - Slashed { staker: T::AccountId, amount: BalanceOf }, + 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 }, + 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 }, + OldSlashingReportDiscarded { + session_index: SessionIndex, + }, /// A new set of stakers was elected. StakersElected, /// An account has bonded this amount. \[stash, amount\] /// /// NOTE: This event is only emitted when funds are bonded via a dispatchable. Notably, /// it will not be emitted for staking rewards when they are added to stake. - Bonded { stash: T::AccountId, amount: BalanceOf }, + Bonded { + stash: T::AccountId, + amount: BalanceOf, + }, /// An account has unbonded this amount. - Unbonded { stash: T::AccountId, amount: BalanceOf }, + Unbonded { + stash: T::AccountId, + amount: BalanceOf, + }, /// An account has called `withdraw_unbonded` and removed unbonding chunks worth `Balance` /// from the unlocking queue. - Withdrawn { stash: T::AccountId, amount: BalanceOf }, + Withdrawn { + stash: T::AccountId, + amount: BalanceOf, + }, /// A nominator has been kicked from a validator. - Kicked { nominator: T::AccountId, stash: T::AccountId }, + Kicked { + nominator: T::AccountId, + stash: T::AccountId, + }, /// The election failed. No new era is planned. StakingElectionFailed, /// An account has stopped participating as either a validator or nominator. - Chilled { stash: T::AccountId }, + Chilled { + stash: T::AccountId, + }, /// The stakers' rewards are getting paid. - PayoutStarted { era_index: EraIndex, validator_stash: T::AccountId }, + PayoutStarted { + era_index: EraIndex, + validator_stash: T::AccountId, + }, /// A validator has set their preferences. - ValidatorPrefsSet { stash: T::AccountId, prefs: ValidatorPrefs }, + ValidatorPrefsSet { + stash: T::AccountId, + prefs: ValidatorPrefs, + }, /// Voters size limit reached. - SnapshotVotersSizeExceeded { size: u32 }, + SnapshotVotersSizeExceeded { + size: u32, + }, /// Targets size limit reached. - SnapshotTargetsSizeExceeded { size: u32 }, - /// A new force era mode was set. - ForceEra { mode: Forcing }, + SnapshotTargetsSizeExceeded { + size: u32, + }, + ForceEra { + mode: Forcing, + }, /// Report of a controller batch deprecation. - ControllerBatchDeprecated { failures: u32 }, + ControllerBatchDeprecated { + failures: u32, + }, } #[pallet::error] @@ -938,8 +1016,58 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { - fn on_initialize(_now: BlockNumberFor) -> Weight { - // just return the weight of the on_finalize. + /// Start fetching the election pages `Pages` blocks before the election prediction, so + /// that the `ElectableStashes` is ready with all the pages on time. + fn on_initialize(now: BlockNumberFor) -> Weight { + let pages: BlockNumberFor = + <::ElectionProvider as ElectionProvider>::Pages::get().into(); + + if let Some((started_at, planning_era)) = ElectingStartedAt::::get() { + let remaining_pages = + pages.saturating_sub(One::one()).saturating_sub(now.saturating_sub(started_at)); + + if remaining_pages == Zero::zero() { + Self::do_elect_paged(Zero::zero()); + + // last page, reset elect status and update era. + crate::log!(info, "elect(): finished fetching all paged solutions."); + CurrentEra::::set(Some(planning_era)); + ElectingStartedAt::::kill(); + } else { + crate::log!( + info, + "elect(): progressing with calling elect, remaining pages {:?}.", + remaining_pages + ); + Self::do_elect_paged(remaining_pages.saturated_into::()); + } + } else { + let next_election = ::next_election_prediction(now); + + if now == (next_election.saturating_sub(pages)) { + // start calling elect. + crate::log!( + info, + "elect(): next election in {:?} pages, start fetching solution pages.", + pages, + ); + Self::do_elect_paged(pages.saturated_into::().saturating_sub(1)); + + // set `ElectingStartedAt` only in multi-paged election. + if pages > One::one() { + ElectingStartedAt::::set(Some(( + now, + CurrentEra::::get().unwrap_or_default().saturating_add(1), + ))); + } else { + crate::log!(info, "elect(): finished fetching the single paged solution."); + } + } + }; + + // TODO: benchmarls of fetching/ not fetching election page on_initialize. + + // return the weight of the on_finalize. T::DbWeight::get().reads(1) } @@ -966,12 +1094,6 @@ pub mod pallet { // and that MaxNominations is always greater than 1, since we count on this. assert!(!MaxNominationsOf::::get().is_zero()); - // ensure election results are always bounded with the same value - assert!( - ::MaxWinners::get() == - ::MaxWinners::get() - ); - assert!( T::SlashDeferDuration::get() < T::BondingDuration::get() || T::BondingDuration::get() == 0, "As per documentation, slash defer duration ({}) should be less than bonding duration ({}).", @@ -1425,18 +1547,15 @@ pub mod pallet { #[pallet::compact] new: u32, ) -> DispatchResult { ensure_root(origin)?; - // ensure new validator count does not exceed maximum winners - // support by election provider. - ensure!( - new <= ::MaxWinners::get(), - Error::::TooManyValidators - ); + + ensure!(new <= T::MaxValidatorSet::get(), Error::::TooManyValidators); + ValidatorCount::::put(new); Ok(()) } /// Increments the ideal number of validators up to maximum of - /// `ElectionProviderBase::MaxWinners`. + /// `T::MaxValidatorSet`. /// /// The dispatch origin must be Root. /// @@ -1451,17 +1570,15 @@ pub mod pallet { ensure_root(origin)?; let old = ValidatorCount::::get(); let new = old.checked_add(additional).ok_or(ArithmeticError::Overflow)?; - ensure!( - new <= ::MaxWinners::get(), - Error::::TooManyValidators - ); + + ensure!(new <= T::MaxValidatorSet::get(), Error::::TooManyValidators); ValidatorCount::::put(new); Ok(()) } /// Scale up the ideal number of validators by a factor up to maximum of - /// `ElectionProviderBase::MaxWinners`. + /// `T::MaxValidatorSet`. /// /// The dispatch origin must be Root. /// @@ -1474,10 +1591,7 @@ pub mod pallet { let old = ValidatorCount::::get(); let new = old.checked_add(factor.mul_floor(old)).ok_or(ArithmeticError::Overflow)?; - ensure!( - new <= ::MaxWinners::get(), - Error::::TooManyValidators - ); + ensure!(new <= T::MaxValidatorSet::get(), Error::::TooManyValidators); ValidatorCount::::put(new); Ok(()) diff --git a/substrate/frame/staking/src/tests.rs b/substrate/frame/staking/src/tests.rs index 639f4096456fb..a153f065ed19d 100644 --- a/substrate/frame/staking/src/tests.rs +++ b/substrate/frame/staking/src/tests.rs @@ -2215,14 +2215,14 @@ fn bond_with_duplicate_vote_should_be_ignored_by_election_provider() { // winners should be 21 and 31. Otherwise this election is taking duplicates into // account. - let supports = ::ElectionProvider::elect().unwrap(); - assert_eq!( - supports, - vec![ - (21, Support { total: 1800, voters: vec![(21, 1000), (1, 400), (3, 400)] }), - (31, Support { total: 2200, voters: vec![(31, 1000), (1, 600), (3, 600)] }) - ], - ); + let supports = ::ElectionProvider::elect(SINGLE_PAGE).unwrap(); + + let expected_supports = vec![ + (21, Support { total: 1800, voters: vec![(21, 1000), (1, 400), (3, 400)] }), + (31, Support { total: 2200, voters: vec![(31, 1000), (1, 600), (3, 600)] }), + ]; + + assert_eq!(supports, to_bounded_supports(expected_supports)); }); } @@ -2267,14 +2267,13 @@ fn bond_with_duplicate_vote_should_be_ignored_by_election_provider_elected() { assert_ok!(Staking::nominate(RuntimeOrigin::signed(3), vec![21])); // winners should be 21 and 11. - let supports = ::ElectionProvider::elect().unwrap(); - assert_eq!( - supports, - vec![ - (11, Support { total: 1500, voters: vec![(11, 1000), (1, 500)] }), - (21, Support { total: 2500, voters: vec![(21, 1000), (1, 500), (3, 1000)] }) - ], - ); + let supports = ::ElectionProvider::elect(SINGLE_PAGE).unwrap(); + let expected_supports = vec![ + (11, Support { total: 1500, voters: vec![(11, 1000), (1, 500)] }), + (21, Support { total: 2500, voters: vec![(21, 1000), (1, 500), (3, 1000)] }), + ]; + + assert_eq!(supports, to_bounded_supports(expected_supports)); }); } @@ -3792,12 +3791,17 @@ fn six_session_delay() { // pallet-session is delaying session by one, thus the next session to plan is +2. assert_eq!(>::new_session(init_session + 2), None); + + // note a new election happens independently of the call to `new_session`. + Staking::do_elect_paged(0); assert_eq!( >::new_session(init_session + 3), Some(val_set.clone()) ); assert_eq!(>::new_session(init_session + 4), None); assert_eq!(>::new_session(init_session + 5), None); + + Staking::do_elect_paged(0); assert_eq!( >::new_session(init_session + 6), Some(val_set.clone()) @@ -5159,14 +5163,15 @@ mod election_data_provider { .build_and_execute(|| { // default bounds are unbounded. assert_ok!(::electing_voters( - DataProviderBounds::default() + DataProviderBounds::default(), + 0 )); assert_eq!(MinimumActiveStake::::get(), 10); // remove staker with lower bond by limiting the number of voters and check // `MinimumActiveStake` again after electing voters. let bounds = ElectionBoundsBuilder::default().voters_count(5.into()).build(); - assert_ok!(::electing_voters(bounds.voters)); + assert_ok!(::electing_voters(bounds.voters, 0)); assert_eq!(MinimumActiveStake::::get(), 50); }); } @@ -5177,7 +5182,8 @@ mod election_data_provider { ExtBuilder::default().has_stakers(false).build_and_execute(|| { // default bounds are unbounded. assert_ok!(::electing_voters( - DataProviderBounds::default() + DataProviderBounds::default(), + 0 )); assert_eq!(::VoterList::count(), 0); assert_eq!(MinimumActiveStake::::get(), 0); @@ -5193,9 +5199,11 @@ mod election_data_provider { assert_ok!(Staking::nominate(RuntimeOrigin::signed(4), vec![1])); assert_eq!(::VoterList::count(), 5); - let voters_before = - ::electing_voters(DataProviderBounds::default()) - .unwrap(); + let voters_before = ::electing_voters( + DataProviderBounds::default(), + 0, + ) + .unwrap(); assert_eq!(MinimumActiveStake::::get(), 5); // update minimum nominator bond. @@ -5205,9 +5213,11 @@ mod election_data_provider { // lower than `MinNominatorBond`. assert_eq!(::VoterList::count(), 5); - let voters = - ::electing_voters(DataProviderBounds::default()) - .unwrap(); + let voters = ::electing_voters( + DataProviderBounds::default(), + 0, + ) + .unwrap(); assert_eq!(voters_before, voters); // minimum active stake is lower than `MinNominatorBond`. @@ -5225,6 +5235,7 @@ mod election_data_provider { assert_eq!(Staking::weight_of(&101), 500); let voters = ::electing_voters( DataProviderBounds::default(), + 0, ) .unwrap(); assert_eq!(voters.len(), 5); @@ -5240,6 +5251,7 @@ mod election_data_provider { let voters = ::electing_voters( DataProviderBounds::default(), + 0, ) .unwrap(); // number of returned voters decreases since ledger entry of stash 101 is now @@ -5261,7 +5273,8 @@ mod election_data_provider { ExtBuilder::default().nominate(false).build_and_execute(|| { // default bounds are unbounded. assert!(>::iter().map(|(x, _)| x).all(|v| Staking::electing_voters( - DataProviderBounds::default() + DataProviderBounds::default(), + 0 ) .unwrap() .into_iter() @@ -5315,12 +5328,15 @@ mod election_data_provider { // 11 is taken; // we finish since the 2x limit is reached. assert_eq!( - Staking::electing_voters(bounds_builder.voters_count(2.into()).build().voters) - .unwrap() - .iter() - .map(|(stash, _, _)| stash) - .copied() - .collect::>(), + Staking::electing_voters( + bounds_builder.voters_count(2.into()).build().voters, + 0 + ) + .unwrap() + .iter() + .map(|(stash, _, _)| stash) + .copied() + .collect::>(), vec![11], ); }); @@ -5338,32 +5354,42 @@ mod election_data_provider { // if voter count limit is less.. assert_eq!( - Staking::electing_voters(bounds_builder.voters_count(1.into()).build().voters) - .unwrap() - .len(), + Staking::electing_voters( + bounds_builder.voters_count(1.into()).build().voters, + 0 + ) + .unwrap() + .len(), 1 ); // if voter count limit is equal.. assert_eq!( - Staking::electing_voters(bounds_builder.voters_count(5.into()).build().voters) - .unwrap() - .len(), + Staking::electing_voters( + bounds_builder.voters_count(5.into()).build().voters, + 0 + ) + .unwrap() + .len(), 5 ); // if voter count limit is more. assert_eq!( - Staking::electing_voters(bounds_builder.voters_count(55.into()).build().voters) - .unwrap() - .len(), + Staking::electing_voters( + bounds_builder.voters_count(55.into()).build().voters, + 0 + ) + .unwrap() + .len(), 5 ); // if target count limit is more.. assert_eq!( Staking::electable_targets( - bounds_builder.targets_count(6.into()).build().targets + bounds_builder.targets_count(6.into()).build().targets, + 0, ) .unwrap() .len(), @@ -5373,7 +5399,8 @@ mod election_data_provider { // if target count limit is equal.. assert_eq!( Staking::electable_targets( - bounds_builder.targets_count(4.into()).build().targets + bounds_builder.targets_count(4.into()).build().targets, + 0, ) .unwrap() .len(), @@ -5383,10 +5410,12 @@ mod election_data_provider { // if target limit count is less, then we return an error. assert_eq!( Staking::electable_targets( - bounds_builder.targets_count(1.into()).build().targets + bounds_builder.targets_count(1.into()).build().targets, + 0 ) - .unwrap_err(), - "Target snapshot too big" + .unwrap() + .len(), + 1, ); }); } @@ -5396,25 +5425,25 @@ mod election_data_provider { ExtBuilder::default().build_and_execute(|| { // voters: set size bounds that allows only for 1 voter. let bounds = ElectionBoundsBuilder::default().voters_size(26.into()).build(); - let elected = Staking::electing_voters(bounds.voters).unwrap(); + let elected = Staking::electing_voters(bounds.voters, 0).unwrap(); assert!(elected.encoded_size() == 26 as usize); let prev_len = elected.len(); // larger size bounds means more quota for voters. let bounds = ElectionBoundsBuilder::default().voters_size(100.into()).build(); - let elected = Staking::electing_voters(bounds.voters).unwrap(); + let elected = Staking::electing_voters(bounds.voters, 0).unwrap(); assert!(elected.encoded_size() <= 100 as usize); assert!(elected.len() > 1 && elected.len() > prev_len); // targets: set size bounds that allows for only one target to fit in the snapshot. let bounds = ElectionBoundsBuilder::default().targets_size(10.into()).build(); - let elected = Staking::electable_targets(bounds.targets).unwrap(); + let elected = Staking::electable_targets(bounds.targets, 0).unwrap(); assert!(elected.encoded_size() == 9 as usize); let prev_len = elected.len(); // larger size bounds means more space for targets. let bounds = ElectionBoundsBuilder::default().targets_size(100.into()).build(); - let elected = Staking::electable_targets(bounds.targets).unwrap(); + let elected = Staking::electable_targets(bounds.targets, 0).unwrap(); assert!(elected.encoded_size() <= 100 as usize); assert!(elected.len() > 1 && elected.len() > prev_len); }); @@ -5458,7 +5487,7 @@ mod election_data_provider { // even through 61 has nomination quota of 2 at the time of the election, all the // nominations (5) will be used. assert_eq!( - Staking::electing_voters(DataProviderBounds::default()) + Staking::electing_voters(DataProviderBounds::default(), 0) .unwrap() .iter() .map(|(stash, _, targets)| (*stash, targets.len())) @@ -5482,7 +5511,7 @@ mod election_data_provider { // nominations of controller 70 won't be added due to voter size limit exceeded. let bounds = ElectionBoundsBuilder::default().voters_size(100.into()).build(); assert_eq!( - Staking::electing_voters(bounds.voters) + Staking::electing_voters(bounds.voters, 0) .unwrap() .iter() .map(|(stash, _, targets)| (*stash, targets.len())) @@ -5499,7 +5528,7 @@ mod election_data_provider { // include the electing voters of 70. let bounds = ElectionBoundsBuilder::default().voters_size(1_000.into()).build(); assert_eq!( - Staking::electing_voters(bounds.voters) + Staking::electing_voters(bounds.voters, 0) .unwrap() .iter() .map(|(stash, _, targets)| (*stash, targets.len())) @@ -5985,7 +6014,7 @@ fn change_of_absolute_max_nominations() { let bounds = DataProviderBounds::default(); // 3 validators and 3 nominators - assert_eq!(Staking::electing_voters(bounds).unwrap().len(), 3 + 3); + assert_eq!(Staking::electing_voters(bounds, 0).unwrap().len(), 3 + 3); // abrupt change from 16 to 4, everyone should be fine. AbsoluteMaxNominations::set(4); @@ -5996,7 +6025,7 @@ fn change_of_absolute_max_nominations() { .collect::>(), vec![(101, 2), (71, 3), (61, 1)] ); - assert_eq!(Staking::electing_voters(bounds).unwrap().len(), 3 + 3); + assert_eq!(Staking::electing_voters(bounds, 0).unwrap().len(), 3 + 3); // abrupt change from 4 to 3, everyone should be fine. AbsoluteMaxNominations::set(3); @@ -6007,7 +6036,7 @@ fn change_of_absolute_max_nominations() { .collect::>(), vec![(101, 2), (71, 3), (61, 1)] ); - assert_eq!(Staking::electing_voters(bounds).unwrap().len(), 3 + 3); + assert_eq!(Staking::electing_voters(bounds, 0).unwrap().len(), 3 + 3); // abrupt change from 3 to 2, this should cause some nominators to be non-decodable, and // thus non-existent unless if they update. @@ -6024,7 +6053,7 @@ fn change_of_absolute_max_nominations() { // but its value cannot be decoded and default is returned. assert!(Nominators::::get(71).is_none()); - assert_eq!(Staking::electing_voters(bounds).unwrap().len(), 3 + 2); + assert_eq!(Staking::electing_voters(bounds, 0).unwrap().len(), 3 + 2); assert!(Nominators::::contains_key(101)); // abrupt change from 2 to 1, this should cause some nominators to be non-decodable, and @@ -6041,7 +6070,7 @@ fn change_of_absolute_max_nominations() { assert!(Nominators::::contains_key(61)); assert!(Nominators::::get(71).is_none()); assert!(Nominators::::get(61).is_some()); - assert_eq!(Staking::electing_voters(bounds).unwrap().len(), 3 + 1); + assert_eq!(Staking::electing_voters(bounds, 0).unwrap().len(), 3 + 1); // now one of them can revive themselves by re-nominating to a proper value. assert_ok!(Staking::nominate(RuntimeOrigin::signed(71), vec![1])); @@ -6083,7 +6112,7 @@ fn nomination_quota_max_changes_decoding() { vec![(70, 3), (101, 2), (50, 4), (30, 4), (60, 1)] ); // 4 validators and 4 nominators - assert_eq!(Staking::electing_voters(unbonded_election).unwrap().len(), 4 + 4); + assert_eq!(Staking::electing_voters(unbonded_election, 0).unwrap().len(), 4 + 4); }); } @@ -6476,7 +6505,7 @@ fn reducing_max_unlocking_chunks_abrupt() { #[test] fn cannot_set_unsupported_validator_count() { ExtBuilder::default().build_and_execute(|| { - MaxWinners::set(50); + MaxValidatorSet::set(50); // set validator count works assert_ok!(Staking::set_validator_count(RuntimeOrigin::root(), 30)); assert_ok!(Staking::set_validator_count(RuntimeOrigin::root(), 50)); @@ -6491,7 +6520,7 @@ fn cannot_set_unsupported_validator_count() { #[test] fn increase_validator_count_errors() { ExtBuilder::default().build_and_execute(|| { - MaxWinners::set(50); + MaxValidatorSet::set(50); assert_ok!(Staking::set_validator_count(RuntimeOrigin::root(), 40)); // increase works @@ -6509,7 +6538,7 @@ fn increase_validator_count_errors() { #[test] fn scale_validator_count_errors() { ExtBuilder::default().build_and_execute(|| { - MaxWinners::set(50); + MaxValidatorSet::set(50); assert_ok!(Staking::set_validator_count(RuntimeOrigin::root(), 20)); // scale value works @@ -8335,3 +8364,197 @@ mod byzantine_threshold_disabling_strategy { }); } } + +pub mod multi_page_staking { + use super::*; + use frame_election_provider_support::ElectionDataProvider; + + #[test] + fn multi_page_target_snapshot_works() { + ExtBuilder::default().nominate(true).build_and_execute(|| { + let bounds = ElectionBoundsBuilder::default().targets_count(2.into()).build().targets; + + // fetch from page 3 to 0. + assert_eq!( + ::electable_targets(bounds, 3).unwrap(), + vec![31, 21] + ); + assert_eq!( + ::electable_targets(bounds, 2).unwrap(), + vec![11] + ); + // all targets consumed now, thus remaining calls are empty vecs. + assert!(::electable_targets(bounds, 1) + .unwrap() + .is_empty()); + assert_eq!(TargetSnapshotStatus::::get(), SnapshotStatus::Consumed); + + assert!(::electable_targets(bounds, 0) + .unwrap() + .is_empty()); + + // once we reach page 0, the status reset. + assert_eq!(TargetSnapshotStatus::::get(), SnapshotStatus::Waiting); + // and requesting a nsew snapshot can restart + assert_eq!( + ::electable_targets(bounds, 1).unwrap(), + vec![31, 21] + ); + }) + } + + #[test] + fn multi_page_voter_snapshot_works() { + ExtBuilder::default().nominate(true).build_and_execute(|| { + let bounds = ElectionBoundsBuilder::default().voters_count(3.into()).build().voters; + + // fetch from page 3 to 0. + assert_eq!( + ::electing_voters(bounds, 3) + .unwrap() + .iter() + .map(|(x, _, _)| *x) + .collect::>(), + vec![11, 21, 31] + ); + assert_eq!( + ::electing_voters(bounds, 2) + .unwrap() + .iter() + .map(|(x, _, _)| *x) + .collect::>(), + vec![101] + ); + // all voters consumed now, thus remaining calls are empty vecs. + assert!(::electing_voters(bounds, 1) + .unwrap() + .is_empty()); + assert_eq!(VoterSnapshotStatus::::get(), SnapshotStatus::Consumed); + + assert!(::electing_voters(bounds, 0) + .unwrap() + .is_empty()); + + // once we reach page 0, the status reset. + assert_eq!(VoterSnapshotStatus::::get(), SnapshotStatus::Waiting); + // and requesting a nsew snapshot can restart + assert_eq!( + ::electing_voters(bounds, 1) + .unwrap() + .iter() + .map(|(x, _, _)| *x) + .collect::>(), + vec![11, 21, 31] + ); + }) + } + + #[test] + fn collect_exposures_multi_page_elect_works() { + ExtBuilder::default().exposures_page_size(2).build_and_execute(|| { + assert_eq!(MaxExposurePageSize::get(), 2); + + let exposure_one = Exposure { + total: 1000 + 700, + own: 1000, + others: vec![ + IndividualExposure { who: 101, value: 500 }, + IndividualExposure { who: 102, value: 100 }, + IndividualExposure { who: 103, value: 100 }, + ], + }; + + let exposure_two = Exposure { + total: 1000 + 1000, + own: 1000, + others: vec![ + IndividualExposure { who: 104, value: 500 }, + IndividualExposure { who: 105, value: 500 }, + ], + }; + + let exposure_three = Exposure { + total: 1000 + 500, + own: 1000, + others: vec![ + IndividualExposure { who: 110, value: 250 }, + IndividualExposure { who: 111, value: 250 }, + ], + }; + + let exposures_page_one = bounded_vec![(1, exposure_one), (2, exposure_two),]; + let exposures_page_two = bounded_vec![(1, exposure_three),]; + + assert_eq!( + Pallet::::store_stakers_info_paged(exposures_page_one, current_era()) + .to_vec(), + vec![1, 2] + ); + assert_eq!( + Pallet::::store_stakers_info_paged(exposures_page_two, current_era()) + .to_vec(), + vec![1] + ); + + // Stakers overview OK for validator 1. + assert_eq!( + ErasStakersOverview::::get(0, &1).unwrap(), + PagedExposureMetadata { total: 2200, own: 1000, nominator_count: 5, page_count: 3 }, + ); + // Stakers overview OK for validator 2. + assert_eq!( + ErasStakersOverview::::get(0, &2).unwrap(), + PagedExposureMetadata { total: 2000, own: 1000, nominator_count: 2, page_count: 1 }, + ); + + // validator 1 has 3 paged exposures. + assert!(ErasStakersPaged::::get((0, &1, 0)).is_some()); + assert!(ErasStakersPaged::::get((0, &1, 1)).is_some()); + assert!(ErasStakersPaged::::get((0, &1, 2)).is_some()); + assert!(ErasStakersPaged::::get((0, &1, 3)).is_none()); + assert_eq!(ErasStakersPaged::::iter_prefix_values((0, &1)).count(), 3); + + // validator 2 has 1 paged exposures. + assert!(ErasStakersPaged::::get((0, &2, 0)).is_some()); + assert!(ErasStakersPaged::::get((0, &2, 1)).is_none()); + assert_eq!(ErasStakersPaged::::iter_prefix_values((0, &2)).count(), 1); + + // exposures of validator 1. + assert_eq!( + ErasStakersPaged::::iter_prefix_values((0, &1)).collect::>(), + vec![ + ExposurePage { + page_total: 100, + others: vec![IndividualExposure { who: 103, value: 100 }] + }, + ExposurePage { + page_total: 500, + others: vec![ + IndividualExposure { who: 110, value: 250 }, + IndividualExposure { who: 111, value: 250 } + ] + }, + ExposurePage { + page_total: 600, + others: vec![ + IndividualExposure { who: 101, value: 500 }, + IndividualExposure { who: 102, value: 100 } + ] + }, + ], + ); + + // exposures of validator 2. + assert_eq!( + ErasStakersPaged::::iter_prefix_values((0, &2)).collect::>(), + vec![ExposurePage { + page_total: 1000, + others: vec![ + IndividualExposure { who: 104, value: 500 }, + IndividualExposure { who: 105, value: 500 } + ] + }], + ); + }) + } +} diff --git a/substrate/primitives/staking/src/lib.rs b/substrate/primitives/staking/src/lib.rs index 8dd29bb835de6..87c92e351e232 100644 --- a/substrate/primitives/staking/src/lib.rs +++ b/substrate/primitives/staking/src/lib.rs @@ -514,7 +514,8 @@ where + Copy, { pub fn merge(self, other: Self) -> Self { - debug_assert!(self.own == other.own); + // TODO hm check this + //debug_assert!(self.own == other.own); Self { total: self.total + other.total - self.own, From 94ff07368d647887f5233eb01b4b8131a658fd84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Mon, 14 Oct 2024 01:06:10 +0200 Subject: [PATCH 003/169] refactors election-multiphase pallet with new paginated types --- .../src/benchmarking.rs | 6 +- .../election-provider-multi-phase/src/lib.rs | 156 ++++++++++-------- .../election-provider-multi-phase/src/mock.rs | 43 +++-- .../src/signed.rs | 4 +- .../src/unsigned.rs | 26 ++- 5 files changed, 143 insertions(+), 92 deletions(-) diff --git a/substrate/frame/election-provider-multi-phase/src/benchmarking.rs b/substrate/frame/election-provider-multi-phase/src/benchmarking.rs index 2a3994ff2aa65..32a381ccab793 100644 --- a/substrate/frame/election-provider-multi-phase/src/benchmarking.rs +++ b/substrate/frame/election-provider-multi-phase/src/benchmarking.rs @@ -272,8 +272,8 @@ frame_benchmarking::benchmarks! { // we don't directly need the data-provider to be populated, but it is just easy to use it. set_up_data_provider::(v, t); // default bounds are unbounded. - let targets = T::DataProvider::electable_targets(DataProviderBounds::default())?; - let voters = T::DataProvider::electing_voters(DataProviderBounds::default())?; + let targets = T::DataProvider::electable_targets(DataProviderBounds::default(), Zero::zero())?; + let voters = T::DataProvider::electing_voters(DataProviderBounds::default(), Zero::zero())?; let desired_targets = T::DataProvider::desired_targets()?; assert!(Snapshot::::get().is_none()); }: { @@ -311,7 +311,7 @@ frame_benchmarking::benchmarks! { assert!(Snapshot::::get().is_some()); assert!(SnapshotMetadata::::get().is_some()); }: { - assert_ok!( as ElectionProvider>::elect()); + assert_ok!( as ElectionProvider>::elect(Zero::zero())); } verify { assert!(QueuedSolution::::get().is_none()); assert!(DesiredTargets::::get().is_none()); diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index 072cfe176b619..cf5bba280d8c4 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -235,13 +235,14 @@ use alloc::{boxed::Box, vec::Vec}; use codec::{Decode, Encode}; use frame_election_provider_support::{ bounds::{CountBound, ElectionBounds, ElectionBoundsBuilder, SizeBound}, - BoundedSupportsOf, DataProviderBounds, ElectionDataProvider, ElectionProvider, - ElectionProviderBase, InstantElectionProvider, NposSolution, + BoundedSupports, BoundedSupportsOf, DataProviderBounds, ElectionDataProvider, ElectionProvider, + InstantElectionProvider, NposSolution, PageIndex, TryIntoBoundedSupports, }; use frame_support::{ + defensive_assert, dispatch::DispatchClass, ensure, - traits::{Currency, DefensiveResult, Get, OnUnbalanced, ReservableCurrency}, + traits::{Currency, Get, OnUnbalanced, ReservableCurrency}, weights::Weight, DefaultNoBound, EqNoBound, PartialEqNoBound, }; @@ -251,7 +252,7 @@ use sp_arithmetic::{ traits::{CheckedAdd, Zero}, UpperOf, }; -use sp_npos_elections::{BoundedSupports, ElectionScore, IdentifierT, Supports, VoteWeight}; +use sp_npos_elections::{ElectionScore, IdentifierT, Supports, VoteWeight}; use sp_runtime::{ transaction_validity::{ InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity, @@ -270,6 +271,7 @@ mod mock; #[macro_use] pub mod helpers; +pub(crate) const SINGLE_PAGE: u32 = 0; const LOG_TARGET: &str = "runtime::election-provider"; pub mod migrations; @@ -287,7 +289,6 @@ pub use weights::WeightInfo; /// The solution type used by this crate. pub type SolutionOf = ::Solution; - /// The voter index. Derived from [`SolutionOf`]. pub type SolutionVoterIndexOf = as NposSolution>::VoterIndex; /// The target index. Derived from [`SolutionOf`]. @@ -296,7 +297,7 @@ pub type SolutionTargetIndexOf = as NposSolution>::TargetIndex pub type SolutionAccuracyOf = ::MinerConfig> as NposSolution>::Accuracy; /// The fallback election type. -pub type FallbackErrorOf = <::Fallback as ElectionProviderBase>::Error; +pub type FallbackErrorOf = <::Fallback as ElectionProvider>::Error; /// Configuration for the benchmarks of the pallet. pub trait BenchmarkingConfig { @@ -433,17 +434,18 @@ impl Default for RawSolution { DefaultNoBound, scale_info::TypeInfo, )] -#[scale_info(skip_type_params(AccountId, MaxWinners))] -pub struct ReadySolution +#[scale_info(skip_type_params(AccountId, MaxWinnersPerPage, MaxBackersPerWinner))] +pub struct ReadySolution where AccountId: IdentifierT, - MaxWinners: Get, + MaxWinnersPerPage: Get, + MaxBackersPerWinner: Get, { /// The final supports of the solution. /// /// This is target-major vector, storing each winners, total backing, and each individual /// backer. - pub supports: BoundedSupports, + pub supports: BoundedSupports, /// The score of the solution. /// /// This is needed to potentially challenge the solution. @@ -500,7 +502,6 @@ pub enum ElectionError { // NOTE: we have to do this manually because of the additional where clause needed on // `FallbackErrorOf`. -#[cfg(test)] impl PartialEq for ElectionError where FallbackErrorOf: PartialEq, @@ -615,7 +616,8 @@ pub mod pallet { type MinerConfig: crate::unsigned::MinerConfig< AccountId = Self::AccountId, MaxVotesPerVoter = ::MaxVotesPerVoter, - MaxWinners = Self::MaxWinners, + MaxWinnersPerPage = Self::MaxWinnersPerPage, + MaxBackersPerWinner = Self::MaxBackersPerWinner, >; /// Maximum number of signed submissions that can be queued. @@ -652,12 +654,20 @@ pub mod pallet { #[pallet::constant] type SignedDepositWeight: Get>; - /// The maximum number of winners that can be elected by this `ElectionProvider` - /// implementation. + /// Maximum number of winners that a page supports. /// /// Note: This must always be greater or equal to `T::DataProvider::desired_targets()`. - #[pallet::constant] - type MaxWinners: Get; + type MaxWinnersPerPage: Get; + + /// Maximum number of voters that can support a single target, across ALL the solution + /// pages. Thus, this can only be verified when processing the last solution page. + /// + /// This limit must be set so that the memory limits of the rest of the system are + /// respected. + type MaxBackersPerWinner: Get; + + /// Number of pages. + type Pages: Get; /// Something that calculates the signed deposit base based on the signed submissions queue /// size. @@ -685,7 +695,8 @@ pub mod pallet { AccountId = Self::AccountId, BlockNumber = BlockNumberFor, DataProvider = Self::DataProvider, - MaxWinners = Self::MaxWinners, + MaxBackersPerWinner = Self::MaxBackersPerWinner, + MaxWinnersPerPage = Self::MaxWinnersPerPage, >; /// Configuration of the governance-only fallback. @@ -696,7 +707,8 @@ pub mod pallet { AccountId = Self::AccountId, BlockNumber = BlockNumberFor, DataProvider = Self::DataProvider, - MaxWinners = Self::MaxWinners, + MaxWinnersPerPage = Self::MaxWinnersPerPage, + MaxBackersPerWinner = Self::MaxBackersPerWinner, >; /// OCW election solution miner algorithm implementation. @@ -733,7 +745,7 @@ pub mod pallet { #[pallet::constant_name(MinerMaxWinners)] fn max_winners() -> u32 { - ::MaxWinners::get() + ::MaxWinnersPerPage::get() } } @@ -978,8 +990,9 @@ pub mod pallet { T::ForceOrigin::ensure_origin(origin)?; ensure!(CurrentPhase::::get().is_emergency(), Error::::CallNotAllowed); - // bound supports with T::MaxWinners - let supports = supports.try_into().map_err(|_| Error::::TooManyWinners)?; + // bound supports with T::MaxWinnersPerPage. + let supports: BoundedSupports<_, _, _> = + supports.try_into_bounded_supports().map_err(|_| Error::::TooManyWinners)?; // Note: we don't `rotate_round` at this point; the next call to // `ElectionProvider::elect` will succeed and take care of that. @@ -1104,13 +1117,6 @@ pub mod pallet { Error::::FallbackFailed })?; - // transform BoundedVec<_, T::GovernanceFallback::MaxWinners> into - // `BoundedVec<_, T::MaxWinners>` - let supports: BoundedVec<_, T::MaxWinners> = supports - .into_inner() - .try_into() - .defensive_map_err(|_| Error::::BoundNotMet)?; - let solution = ReadySolution { supports, score: Default::default(), @@ -1266,7 +1272,7 @@ pub mod pallet { /// Always sorted by score. #[pallet::storage] pub type QueuedSolution = - StorageValue<_, ReadySolution>; + StorageValue<_, ReadySolution>; /// Snapshot data of the round. /// @@ -1398,7 +1404,8 @@ impl Pallet { /// Current best solution, signed or unsigned, queued to be returned upon `elect`. /// /// Always sorted by score. - pub fn queued_solution() -> Option> { + pub fn queued_solution( + ) -> Option> { QueuedSolution::::get() } @@ -1504,11 +1511,12 @@ impl Pallet { /// Parts of [`create_snapshot`] that happen outside of this pallet. /// /// Extracted for easier weight calculation. + /// + /// Note: this pallet only supports one page of voter and target snapshots. fn create_snapshot_external( ) -> Result<(Vec, Vec>, u32), ElectionError> { let election_bounds = T::ElectionBounds::get(); - - let targets = T::DataProvider::electable_targets(election_bounds.targets) + let targets = T::DataProvider::electable_targets(election_bounds.targets, SINGLE_PAGE) .and_then(|t| { election_bounds.ensure_targets_limits( CountBound(t.len() as u32), @@ -1518,7 +1526,7 @@ impl Pallet { }) .map_err(ElectionError::DataProvider)?; - let voters = T::DataProvider::electing_voters(election_bounds.voters) + let voters = T::DataProvider::electing_voters(election_bounds.voters, SINGLE_PAGE) .and_then(|v| { election_bounds.ensure_voters_limits( CountBound(v.len() as u32), @@ -1528,7 +1536,7 @@ impl Pallet { }) .map_err(ElectionError::DataProvider)?; - let mut desired_targets = as ElectionProviderBase>::desired_targets_checked() + let mut desired_targets = as ElectionProvider>::desired_targets_checked() .map_err(|e| ElectionError::DataProvider(e))?; // If `desired_targets` > `targets.len()`, cap `desired_targets` to that level and emit a @@ -1583,7 +1591,10 @@ impl Pallet { pub fn feasibility_check( raw_solution: RawSolution>, compute: ElectionCompute, - ) -> Result, FeasibilityError> { + ) -> Result< + ReadySolution, + FeasibilityError, + > { let desired_targets = DesiredTargets::::get().ok_or(FeasibilityError::SnapshotUnavailable)?; @@ -1755,29 +1766,29 @@ impl Pallet { } } -impl ElectionProviderBase for Pallet { +impl ElectionProvider for Pallet { type AccountId = T::AccountId; type BlockNumber = BlockNumberFor; type Error = ElectionError; - type MaxWinners = T::MaxWinners; + type MaxWinnersPerPage = T::MaxWinnersPerPage; + type MaxBackersPerWinner = T::MaxBackersPerWinner; + type Pages = T::Pages; type DataProvider = T::DataProvider; -} -impl ElectionProvider for Pallet { - fn ongoing() -> bool { - match CurrentPhase::::get() { - Phase::Off => false, - _ => true, - } - } + fn elect(remaining: PageIndex) -> Result, Self::Error> { + defensive_assert!(remaining.is_zero()); - fn elect() -> Result, Self::Error> { match Self::do_elect() { - Ok(supports) => { + Ok(bounded_supports) => { + // TODO:remove the bounded_supports.clone() + use frame_election_provider_support::TryIntoSupports; + let supports: Supports = + bounded_supports.clone().try_into_supports().unwrap(); + // All went okay, record the weight, put sign to be Off, clean snapshot, etc. Self::weigh_supports(&supports); Self::rotate_round(); - Ok(supports) + Ok(bounded_supports) }, Err(why) => { log!(error, "Entering emergency mode: {:?}", why); @@ -2068,7 +2079,7 @@ mod tests { assert_eq!(CurrentPhase::::get(), Phase::Unsigned((true, 25))); assert!(Snapshot::::get().is_some()); - assert_ok!(MultiPhase::elect()); + assert_ok!(MultiPhase::elect(SINGLE_PAGE)); assert!(CurrentPhase::::get().is_off()); assert!(Snapshot::::get().is_none()); @@ -2132,7 +2143,7 @@ mod tests { roll_to(30); assert!(CurrentPhase::::get().is_unsigned_open_at(20)); - assert_ok!(MultiPhase::elect()); + assert_ok!(MultiPhase::elect(SINGLE_PAGE)); assert!(CurrentPhase::::get().is_off()); assert!(Snapshot::::get().is_none()); @@ -2179,7 +2190,7 @@ mod tests { roll_to(30); assert!(CurrentPhase::::get().is_signed()); - assert_ok!(MultiPhase::elect()); + assert_ok!(MultiPhase::elect(SINGLE_PAGE)); assert!(CurrentPhase::::get().is_off()); assert!(Snapshot::::get().is_none()); @@ -2218,7 +2229,7 @@ mod tests { assert!(CurrentPhase::::get().is_off()); // This module is now only capable of doing on-chain backup. - assert_ok!(MultiPhase::elect()); + assert_ok!(MultiPhase::elect(SINGLE_PAGE)); assert!(CurrentPhase::::get().is_off()); @@ -2254,7 +2265,7 @@ mod tests { assert_eq!(Round::::get(), 1); // An unexpected call to elect. - assert_ok!(MultiPhase::elect()); + assert_ok!(MultiPhase::elect(SINGLE_PAGE)); // We surely can't have any feasible solutions. This will cause an on-chain election. assert_eq!( @@ -2305,7 +2316,7 @@ mod tests { } // an unexpected call to elect. - assert_ok!(MultiPhase::elect()); + assert_ok!(MultiPhase::elect(SINGLE_PAGE)); // all storage items must be cleared. assert_eq!(Round::::get(), 2); @@ -2376,7 +2387,7 @@ mod tests { )); roll_to(30); - assert_ok!(MultiPhase::elect()); + assert_ok!(MultiPhase::elect(SINGLE_PAGE)); assert_eq!( multi_phase_events(), @@ -2433,7 +2444,7 @@ mod tests { )); assert!(QueuedSolution::::get().is_some()); - assert_ok!(MultiPhase::elect()); + assert_ok!(MultiPhase::elect(SINGLE_PAGE)); assert_eq!( multi_phase_events(), @@ -2475,15 +2486,16 @@ mod tests { // Zilch solutions thus far, but we get a result. assert!(QueuedSolution::::get().is_none()); - let supports = MultiPhase::elect().unwrap(); + let supports = MultiPhase::elect(SINGLE_PAGE).unwrap(); - assert_eq!( - supports, - vec![ - (30, Support { total: 40, voters: vec![(2, 5), (4, 5), (30, 30)] }), - (40, Support { total: 60, voters: vec![(2, 5), (3, 10), (4, 5), (40, 40)] }) - ] - ); + let expected_supports = vec![ + (30, Support { total: 40, voters: vec![(2, 5), (4, 5), (30, 30)] }), + (40, Support { total: 60, voters: vec![(2, 5), (3, 10), (4, 5), (40, 40)] }), + ] + .try_into_bounded_supports() + .unwrap(); + + assert_eq!(supports, expected_supports); assert_eq!( multi_phase_events(), @@ -2517,7 +2529,10 @@ mod tests { // Zilch solutions thus far. assert!(QueuedSolution::::get().is_none()); - assert_eq!(MultiPhase::elect().unwrap_err(), ElectionError::Fallback("NoFallback.")); + assert_eq!( + MultiPhase::elect(SINGLE_PAGE).unwrap_err(), + ElectionError::Fallback("NoFallback.") + ); // phase is now emergency. assert_eq!(CurrentPhase::::get(), Phase::Emergency); // snapshot is still there until election finalizes. @@ -2551,7 +2566,10 @@ mod tests { // Zilch solutions thus far. assert!(QueuedSolution::::get().is_none()); - assert_eq!(MultiPhase::elect().unwrap_err(), ElectionError::Fallback("NoFallback.")); + assert_eq!( + MultiPhase::elect(SINGLE_PAGE).unwrap_err(), + ElectionError::Fallback("NoFallback.") + ); // phase is now emergency. assert_eq!(CurrentPhase::::get(), Phase::Emergency); @@ -2569,7 +2587,7 @@ mod tests { // something is queued now assert!(QueuedSolution::::get().is_some()); // next election call with fix everything.; - assert!(MultiPhase::elect().is_ok()); + assert!(MultiPhase::elect(SINGLE_PAGE).is_ok()); assert_eq!(CurrentPhase::::get(), Phase::Off); assert_eq!( @@ -2621,7 +2639,7 @@ mod tests { assert_eq!(CurrentPhase::::get(), Phase::Off); // On-chain backup works though. - let supports = MultiPhase::elect().unwrap(); + let supports = MultiPhase::elect(SINGLE_PAGE).unwrap(); assert!(supports.len() > 0); assert_eq!( @@ -2660,7 +2678,7 @@ mod tests { assert_eq!(CurrentPhase::::get(), Phase::Off); roll_to(29); - let err = MultiPhase::elect().unwrap_err(); + let err = MultiPhase::elect(SINGLE_PAGE).unwrap_err(); assert_eq!(err, ElectionError::Fallback("NoFallback.")); assert_eq!(CurrentPhase::::get(), Phase::Emergency); diff --git a/substrate/frame/election-provider-multi-phase/src/mock.rs b/substrate/frame/election-provider-multi-phase/src/mock.rs index 32a099e1a26f4..7792362286d0f 100644 --- a/substrate/frame/election-provider-multi-phase/src/mock.rs +++ b/substrate/frame/election-provider-multi-phase/src/mock.rs @@ -116,7 +116,7 @@ pub fn roll_to_round(n: u32) { while Round::::get() != n { roll_to_signed(); - frame_support::assert_ok!(MultiPhase::elect()); + frame_support::assert_ok!(MultiPhase::elect(Zero::zero())); } } @@ -295,7 +295,10 @@ parameter_types! { pub static MaxElectableTargets: TargetIndex = TargetIndex::max_value(); #[derive(Debug)] - pub static MaxWinners: u32 = 200; + pub static MaxWinnersPerPage: u32 = 200; + #[derive(Debug)] + pub static MaxBackersPerWinner: u32 = 200; + pub static Pages: u32 = 1; // `ElectionBounds` and `OnChainElectionsBounds` are defined separately to set them independently in the tests. pub static ElectionsBounds: ElectionBounds = ElectionBoundsBuilder::default().build(); pub static OnChainElectionsBounds: ElectionBounds = ElectionBoundsBuilder::default().build(); @@ -309,17 +312,24 @@ impl onchain::Config for OnChainSeqPhragmen { type Solver = SequentialPhragmen, Balancing>; type DataProvider = StakingMock; type WeightInfo = (); - type MaxWinners = MaxWinners; + type MaxWinnersPerPage = MaxWinnersPerPage; + type MaxBackersPerWinner = MaxBackersPerWinner; type Bounds = OnChainElectionsBounds; } pub struct MockFallback; -impl ElectionProviderBase for MockFallback { - type BlockNumber = BlockNumber; +impl ElectionProvider for MockFallback { type AccountId = AccountId; + type BlockNumber = BlockNumber; type Error = &'static str; + type MaxWinnersPerPage = MaxWinnersPerPage; + type MaxBackersPerWinner = MaxBackersPerWinner; + type Pages = Pages; type DataProvider = StakingMock; - type MaxWinners = MaxWinners; + + fn elect(_remaining: PageIndex) -> Result, Self::Error> { + unimplemented!() + } } impl InstantElectionProvider for MockFallback { @@ -361,7 +371,8 @@ impl MinerConfig for Runtime { type MaxLength = MinerMaxLength; type MaxWeight = MinerMaxWeight; type MaxVotesPerVoter = ::MaxVotesPerVoter; - type MaxWinners = MaxWinners; + type MaxWinnersPerPage = MaxWinnersPerPage; + type MaxBackersPerWinner = MaxBackersPerWinner; type Solution = TestNposSolution; fn solution_weight(v: u32, t: u32, a: u32, d: u32) -> Weight { @@ -403,7 +414,9 @@ impl crate::Config for Runtime { type GovernanceFallback = frame_election_provider_support::onchain::OnChainExecution; type ForceOrigin = frame_system::EnsureRoot; - type MaxWinners = MaxWinners; + type Pages = Pages; + type MaxWinnersPerPage = MaxWinnersPerPage; + type MaxBackersPerWinner = MaxBackersPerWinner; type MinerConfig = Self; type Solver = SequentialPhragmen, Balancing>; type ElectionBounds = ElectionsBounds; @@ -446,7 +459,12 @@ impl ElectionDataProvider for StakingMock { type AccountId = AccountId; type MaxVotesPerVoter = MaxNominations; - fn electable_targets(bounds: DataProviderBounds) -> data_provider::Result> { + fn electable_targets( + bounds: DataProviderBounds, + remaining_pages: PageIndex, + ) -> data_provider::Result> { + assert!(remaining_pages.is_zero()); + let targets = Targets::get(); if !DataProviderAllowBadData::get() && @@ -458,7 +476,12 @@ impl ElectionDataProvider for StakingMock { Ok(targets) } - fn electing_voters(bounds: DataProviderBounds) -> data_provider::Result>> { + fn electing_voters( + bounds: DataProviderBounds, + remaining_pages: PageIndex, + ) -> data_provider::Result>> { + assert!(remaining_pages.is_zero()); + let mut voters = Voters::get(); if !DataProviderAllowBadData::get() { diff --git a/substrate/frame/election-provider-multi-phase/src/signed.rs b/substrate/frame/election-provider-multi-phase/src/signed.rs index c685791bbdd9d..ef1538b1e4d39 100644 --- a/substrate/frame/election-provider-multi-phase/src/signed.rs +++ b/substrate/frame/election-provider-multi-phase/src/signed.rs @@ -490,7 +490,7 @@ impl Pallet { /// /// Infallible pub fn finalize_signed_phase_accept_solution( - ready_solution: ReadySolution, + ready_solution: ReadySolution, who: &T::AccountId, deposit: BalanceOf, call_fee: BalanceOf, @@ -665,7 +665,7 @@ mod tests { ExtBuilder::default().build_and_execute(|| { // given desired_targets bigger than MaxWinners DesiredTargets::set(4); - MaxWinners::set(3); + MaxWinnersPerPage::set(3); // snapshot not created because data provider returned an unexpected number of // desired_targets diff --git a/substrate/frame/election-provider-multi-phase/src/unsigned.rs b/substrate/frame/election-provider-multi-phase/src/unsigned.rs index 4c56f02db526b..bf0f555273db4 100644 --- a/substrate/frame/election-provider-multi-phase/src/unsigned.rs +++ b/substrate/frame/election-provider-multi-phase/src/unsigned.rs @@ -24,7 +24,9 @@ use crate::{ }; use alloc::{boxed::Box, vec::Vec}; use codec::Encode; -use frame_election_provider_support::{NposSolution, NposSolver, PerThing128, VoteWeight}; +use frame_election_provider_support::{ + NposSolution, NposSolver, PerThing128, TryIntoBoundedSupports, VoteWeight, +}; use frame_support::{ dispatch::DispatchResult, ensure, @@ -389,7 +391,7 @@ impl Pallet { // ensure score is being improved. Panic henceforth. ensure!( QueuedSolution::::get() - .map_or(true, |q: ReadySolution<_, _>| raw_solution.score > q.score), + .map_or(true, |q: ReadySolution<_, _, _>| raw_solution.score > q.score), Error::::PreDispatchWeakSubmission, ); @@ -423,8 +425,10 @@ pub trait MinerConfig { /// /// The weight is computed using `solution_weight`. type MaxWeight: Get; - /// The maximum number of winners that can be elected. - type MaxWinners: Get; + /// The maximum number of winners that can be elected per page (and overall). + type MaxWinnersPerPage: Get; + /// The maximum number of backers (edges) per winner in the last solution. + type MaxBackersPerWinner: Get; /// Something that can compute the weight of a solution. /// /// This weight estimate is then used to trim the solution, based on [`MinerConfig::MaxWeight`]. @@ -743,7 +747,10 @@ impl Miner { snapshot: RoundSnapshot>, current_round: u32, minimum_untrusted_score: Option, - ) -> Result, FeasibilityError> { + ) -> Result< + ReadySolution, + FeasibilityError, + > { let RawSolution { solution, score, round } = raw_solution; let RoundSnapshot { voters: snapshot_voters, targets: snapshot_targets } = snapshot; @@ -755,7 +762,10 @@ impl Miner { ensure!(winners.len() as u32 == desired_targets, FeasibilityError::WrongWinnerCount); // Fail early if targets requested by data provider exceed maximum winners supported. - ensure!(desired_targets <= T::MaxWinners::get(), FeasibilityError::TooManyDesiredTargets); + ensure!( + desired_targets <= T::MaxWinnersPerPage::get(), + FeasibilityError::TooManyDesiredTargets + ); // Ensure that the solution's score can pass absolute min-score. let submitted_score = raw_solution.score; @@ -812,9 +822,9 @@ impl Miner { let known_score = supports.evaluate(); ensure!(known_score == score, FeasibilityError::InvalidScore); - // Size of winners in miner solution is equal to `desired_targets` <= `MaxWinners`. + // Size of winners in miner solution is equal to `desired_targets` <= `MaxWinnersPerPage`. let supports = supports - .try_into() + .try_into_bounded_supports() .defensive_map_err(|_| FeasibilityError::BoundedConversionFailed)?; Ok(ReadySolution { supports, compute, score }) From 1527bd682e6c1340067e204cd22c5795d1017d57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Mon, 14 Oct 2024 01:44:04 +0200 Subject: [PATCH 004/169] nits for dependent pallets and runtimes --- polkadot/runtime/westend/src/lib.rs | 19 ++++++++++++----- substrate/frame/delegated-staking/src/mock.rs | 4 +++- substrate/frame/fast-unstake/src/mock.rs | 21 ++++++++++--------- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index fe1777bc94eef..45823e51f2322 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -575,7 +575,10 @@ parameter_types! { ElectionBoundsBuilder::default().voters_count(MaxElectingVoters::get().into()).build(); // Maximum winners that can be chosen as active validators pub const MaxActiveValidators: u32 = 1000; - + // One page only, fill the whole page with the `MaxActiveValidators`. + pub const MaxWinnersPerPage: u32 = MaxActiveValidators::get(); + // Unbonded, thus the max backers per winner maps to the max electing voters limit. + pub const MaxBackersPerWinner: u32 = MaxElectingVoters::get(); } frame_election_provider_support::generate_solution_type!( @@ -594,8 +597,9 @@ impl onchain::Config for OnChainSeqPhragmen { type Solver = SequentialPhragmen; type DataProvider = Staking; type WeightInfo = weights::frame_election_provider_support::WeightInfo; - type MaxWinners = MaxActiveValidators; type Bounds = ElectionBounds; + type MaxBackersPerWinner = MaxBackersPerWinner; + type MaxWinnersPerPage = MaxWinnersPerPage; } impl pallet_election_provider_multi_phase::MinerConfig for Runtime { @@ -608,7 +612,8 @@ impl pallet_election_provider_multi_phase::MinerConfig for Runtime { as frame_election_provider_support::ElectionDataProvider >::MaxVotesPerVoter; - type MaxWinners = MaxActiveValidators; + type MaxWinnersPerPage = MaxWinnersPerPage; + type MaxBackersPerWinner = MaxBackersPerWinner; // The unsigned submissions have to respect the weight of the submit_unsigned call, thus their // weight estimate function is wired to this call's weight. @@ -642,6 +647,9 @@ impl pallet_election_provider_multi_phase::Config for Runtime { type BetterSignedThreshold = (); type OffchainRepeat = OffchainRepeat; type MinerTxPriority = NposSolutionPriority; + type Pages = ConstU32<1>; + type MaxWinnersPerPage = MaxWinnersPerPage; + type MaxBackersPerWinner = MaxBackersPerWinner; type DataProvider = Staking; #[cfg(any(feature = "fast-runtime", feature = "runtime-benchmarks"))] type Fallback = onchain::OnChainExecution; @@ -650,7 +658,8 @@ impl pallet_election_provider_multi_phase::Config for Runtime { AccountId, BlockNumber, Staking, - MaxActiveValidators, + MaxWinnersPerPage, + MaxBackersPerWinner, )>; type GovernanceFallback = onchain::OnChainExecution; type Solver = SequentialPhragmen< @@ -661,7 +670,6 @@ impl pallet_election_provider_multi_phase::Config for Runtime { type BenchmarkingConfig = polkadot_runtime_common::elections::BenchmarkConfig; type ForceOrigin = EnsureRoot; type WeightInfo = weights::pallet_election_provider_multi_phase::WeightInfo; - type MaxWinners = MaxActiveValidators; type ElectionBounds = ElectionBounds; } @@ -748,6 +756,7 @@ impl pallet_staking::Config for Runtime { type GenesisElectionProvider = onchain::OnChainExecution; type VoterList = VoterList; type TargetList = UseValidatorsMap; + type MaxValidatorSet = MaxActiveValidators; type NominationsQuota = pallet_staking::FixedNominationsQuota<{ MaxNominations::get() }>; type MaxUnlockingChunks = frame_support::traits::ConstU32<32>; type HistoryDepth = frame_support::traits::ConstU32<84>; diff --git a/substrate/frame/delegated-staking/src/mock.rs b/substrate/frame/delegated-staking/src/mock.rs index 811d5739f4e98..396c8bf053dbb 100644 --- a/substrate/frame/delegated-staking/src/mock.rs +++ b/substrate/frame/delegated-staking/src/mock.rs @@ -96,7 +96,8 @@ impl onchain::Config for OnChainSeqPhragmen { type Solver = SequentialPhragmen; type DataProvider = Staking; type WeightInfo = (); - type MaxWinners = ConstU32<100>; + type MaxWinnersPerPage = ConstU32<100>; + type MaxBackersPerWinner = ConstU32<100>; type Bounds = ElectionsBoundsOnChain; } @@ -110,6 +111,7 @@ impl pallet_staking::Config for Runtime { type GenesisElectionProvider = Self::ElectionProvider; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type TargetList = pallet_staking::UseValidatorsMap; + type MaxValidatorSet = ConstU32<100>; type EventListeners = (Pools, DelegatedStaking); } diff --git a/substrate/frame/fast-unstake/src/mock.rs b/substrate/frame/fast-unstake/src/mock.rs index 757052e230a18..b5112f0cf4886 100644 --- a/substrate/frame/fast-unstake/src/mock.rs +++ b/substrate/frame/fast-unstake/src/mock.rs @@ -16,6 +16,7 @@ // limitations under the License. use crate::{self as fast_unstake}; +use frame_election_provider_support::PageIndex; use frame_support::{ assert_ok, derive_impl, pallet_prelude::*, @@ -82,23 +83,22 @@ parameter_types! { pub static BondingDuration: u32 = 3; pub static CurrentEra: u32 = 0; pub static Ongoing: bool = false; - pub static MaxWinners: u32 = 100; } pub struct MockElection; -impl frame_election_provider_support::ElectionProviderBase for MockElection { - type AccountId = AccountId; + +impl frame_election_provider_support::ElectionProvider for MockElection { type BlockNumber = BlockNumber; - type MaxWinners = MaxWinners; + type AccountId = AccountId; type DataProvider = Staking; + type MaxBackersPerWinner = ConstU32<100>; + type MaxWinnersPerPage = ConstU32<100>; + type Pages = ConstU32<1>; type Error = (); -} -impl frame_election_provider_support::ElectionProvider for MockElection { - fn ongoing() -> bool { - Ongoing::get() - } - fn elect() -> Result, Self::Error> { + fn elect( + _remaining_pages: PageIndex, + ) -> Result, Self::Error> { Err(()) } } @@ -114,6 +114,7 @@ impl pallet_staking::Config for Runtime { type GenesisElectionProvider = Self::ElectionProvider; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type TargetList = pallet_staking::UseValidatorsMap; + type MaxValidatorSet = ConstU32<100>; } parameter_types! { From 5cb1de1a9ae1a30626ea0096ad8e5f54ec19eb6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Mon, 14 Oct 2024 15:55:01 +0200 Subject: [PATCH 005/169] adds ElectionProvider::ongoing to trait and impls --- substrate/frame/election-provider-multi-block/src/lib.rs | 7 +++++++ .../frame/election-provider-multi-block/src/mock/mod.rs | 4 ++++ substrate/frame/election-provider-multi-phase/src/lib.rs | 7 +++++++ substrate/frame/election-provider-multi-phase/src/mock.rs | 4 ++++ substrate/frame/election-provider-support/src/lib.rs | 7 +++++++ substrate/frame/election-provider-support/src/onchain.rs | 4 ++++ substrate/frame/fast-unstake/src/mock.rs | 4 ++++ substrate/frame/staking/src/pallet/impls.rs | 4 +--- 8 files changed, 38 insertions(+), 3 deletions(-) diff --git a/substrate/frame/election-provider-multi-block/src/lib.rs b/substrate/frame/election-provider-multi-block/src/lib.rs index fd1b7cc05f0ac..5b813b39cd33e 100644 --- a/substrate/frame/election-provider-multi-block/src/lib.rs +++ b/substrate/frame/election-provider-multi-block/src/lib.rs @@ -764,6 +764,13 @@ impl ElectionProvider for Pallet { err }) } + + fn ongoing() -> bool { + match CurrentPhase::::get() { + Phase::Off => false, + _ => true, + } + } } #[cfg(test)] diff --git a/substrate/frame/election-provider-multi-block/src/mock/mod.rs b/substrate/frame/election-provider-multi-block/src/mock/mod.rs index e094bac3f6919..eaf5797a5215e 100644 --- a/substrate/frame/election-provider-multi-block/src/mock/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/mock/mod.rs @@ -255,6 +255,10 @@ impl ElectionProvider for MockFallback { Err("fallback election failed (forced in mock)") } } + + fn ongoing() -> bool { + false + } } #[derive(Default)] diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index cf5bba280d8c4..d92a8bfc4b61e 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -1797,6 +1797,13 @@ impl ElectionProvider for Pallet { }, } } + + fn ongoing() -> bool { + match CurrentPhase::::get() { + Phase::Off => false, + _ => true, + } + } } /// convert a DispatchError to a custom InvalidTransaction with the inner code being the error diff --git a/substrate/frame/election-provider-multi-phase/src/mock.rs b/substrate/frame/election-provider-multi-phase/src/mock.rs index 7792362286d0f..d6a34fca46166 100644 --- a/substrate/frame/election-provider-multi-phase/src/mock.rs +++ b/substrate/frame/election-provider-multi-phase/src/mock.rs @@ -330,6 +330,10 @@ impl ElectionProvider for MockFallback { fn elect(_remaining: PageIndex) -> Result, Self::Error> { unimplemented!() } + + fn ongoing() -> bool { + false + } } impl InstantElectionProvider for MockFallback { diff --git a/substrate/frame/election-provider-support/src/lib.rs b/substrate/frame/election-provider-support/src/lib.rs index 09b0d65b3e6fb..d360ad4c16e39 100644 --- a/substrate/frame/election-provider-support/src/lib.rs +++ b/substrate/frame/election-provider-support/src/lib.rs @@ -448,6 +448,9 @@ pub trait ElectionProvider { } }) } + + /// Indicate if this election provider is currently ongoing an asynchronous election or not. + fn ongoing() -> bool; } /// A (almost) marker trait that signifies an election provider as working synchronously. i.e. being @@ -484,6 +487,10 @@ where fn elect(_remaining_pages: PageIndex) -> Result, Self::Error> { Err("`NoElection` cannot do anything.") } + + fn ongoing() -> bool { + false + } } impl diff --git a/substrate/frame/election-provider-support/src/onchain.rs b/substrate/frame/election-provider-support/src/onchain.rs index f7349d5fd0cda..b7519d2dc5a9a 100644 --- a/substrate/frame/election-provider-support/src/onchain.rs +++ b/substrate/frame/election-provider-support/src/onchain.rs @@ -187,6 +187,10 @@ impl ElectionProvider for OnChainExecution { let election_bounds = ElectionBoundsBuilder::from(T::Bounds::get()).build(); Self::elect_with(election_bounds, Zero::zero()) } + + fn ongoing() -> bool { + false + } } #[cfg(test)] diff --git a/substrate/frame/fast-unstake/src/mock.rs b/substrate/frame/fast-unstake/src/mock.rs index b5112f0cf4886..c0c23af51fb53 100644 --- a/substrate/frame/fast-unstake/src/mock.rs +++ b/substrate/frame/fast-unstake/src/mock.rs @@ -101,6 +101,10 @@ impl frame_election_provider_support::ElectionProvider for MockElection { ) -> Result, Self::Error> { Err(()) } + + fn ongoing() -> bool { + Ongoing::get() + } } #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index b6892c5676801..ea6fd4915d00a 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -2016,9 +2016,7 @@ impl StakingInterface for Pallet { } fn election_ongoing() -> bool { - // TODO(gpestana) - //T::ElectionProvider::ongoing() - false + ::ongoing() } fn force_unstake(who: Self::AccountId) -> sp_runtime::DispatchResult { From 43eec4b403f9eb5ccc2b332742f14c82c502fc57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Fri, 18 Oct 2024 12:44:08 +0200 Subject: [PATCH 006/169] clean up sp-staking --- substrate/primitives/staking/src/lib.rs | 75 +++++++++++++------------ 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/substrate/primitives/staking/src/lib.rs b/substrate/primitives/staking/src/lib.rs index 87c92e351e232..191c1c3d8a312 100644 --- a/substrate/primitives/staking/src/lib.rs +++ b/substrate/primitives/staking/src/lib.rs @@ -346,7 +346,7 @@ pub trait StakingUnchecked: StakingInterface { } /// The amount of exposure for an era that an individual nominator has (susceptible to slashing). -#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, RuntimeDebug, TypeInfo, Copy)] pub struct IndividualExposure { /// The stash account of the nominator in question. pub who: AccountId, @@ -434,41 +434,16 @@ impl Default for ExposurePage { } } +/// Returns an exposure page from a set of individual exposures. impl From>> for ExposurePage { fn from(exposures: Vec>) -> Self { - let mut page: Self = Default::default(); - - let _ = exposures - .into_iter() - .map(|e| { - page.page_total += e.value.clone(); - page.others.push(e) - }) - .collect::>(); - - page - } -} - -impl< - A, - B: Default - + HasCompact - + core::fmt::Debug - + sp_std::ops::AddAssign - + sp_std::ops::SubAssign - + Clone, - > ExposurePage -{ - /// Split the current exposure page into two pages where the new page takes up to `num` - /// individual exposures. The remaining individual exposures are left in `self`. - pub fn from_split_others(&mut self, num: usize) -> Self { - let new: ExposurePage<_, _> = self.others.split_off(num).into(); - self.page_total -= new.page_total.clone(); - - new + exposures.into_iter().fold(ExposurePage::default(), |mut page, e| { + page.page_total += e.value.clone(); + page.others.push(e); + page + }) } } @@ -511,17 +486,20 @@ where + sp_std::ops::Add + sp_std::ops::Sub + PartialEq - + Copy, + + Copy + + sp_runtime::traits::Debug, { + /// Merge a paged exposure metadata page into self and return the result. pub fn merge(self, other: Self) -> Self { - // TODO hm check this + // TODO(gpestana): re-enable assert. //debug_assert!(self.own == other.own); Self { total: self.total + other.total - self.own, own: self.own, nominator_count: self.nominator_count + other.nominator_count, - // TODO: merge the pages correctly. + // TODO(gpestana): merge the pages efficiently so that we make sure all the slots in the + // page are filled. page_count: self.page_count + other.page_count, } } @@ -687,3 +665,30 @@ pub trait DelegationMigrator { } sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn individual_exposures_to_exposure_works() { + let exposure_1 = IndividualExposure { who: 1, value: 10u32 }; + let exposure_2 = IndividualExposure { who: 2, value: 20 }; + let exposure_3 = IndividualExposure { who: 3, value: 30 }; + + let exposure_page: ExposurePage = vec![exposure_1, exposure_2, exposure_3].into(); + + assert_eq!( + exposure_page, + ExposurePage { page_total: 60, others: vec![exposure_1, exposure_2, exposure_3] }, + ); + } + + #[test] + fn empty_individual_exposures_to_exposure_works() { + let empty_exposures: Vec> = vec![]; + + let exposure_page: ExposurePage = empty_exposures.into(); + assert_eq!(exposure_page, ExposurePage { page_total: 0, others: vec![] }); + } +} From 11dd78554b2e5c07a75eef69817a2f5a72cd0320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Fri, 18 Oct 2024 14:03:15 +0200 Subject: [PATCH 007/169] clean up and rustdoc for epm-support --- .../election-provider-multi-phase/src/lib.rs | 11 +-- .../election-provider-support/src/lib.rs | 93 ++++++++++++------- 2 files changed, 66 insertions(+), 38 deletions(-) diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index d92a8bfc4b61e..9de43a746d3a0 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -1674,7 +1674,9 @@ impl Pallet { } /// record the weight of the given `supports`. - fn weigh_supports(supports: &Supports) { + fn weigh_supports( + supports: &BoundedSupports, + ) { let active_voters = supports .iter() .map(|(_, x)| x) @@ -1780,13 +1782,8 @@ impl ElectionProvider for Pallet { match Self::do_elect() { Ok(bounded_supports) => { - // TODO:remove the bounded_supports.clone() - use frame_election_provider_support::TryIntoSupports; - let supports: Supports = - bounded_supports.clone().try_into_supports().unwrap(); - // All went okay, record the weight, put sign to be Off, clean snapshot, etc. - Self::weigh_supports(&supports); + Self::weigh_supports(&bounded_supports); Self::rotate_round(); Ok(bounded_supports) }, diff --git a/substrate/frame/election-provider-support/src/lib.rs b/substrate/frame/election-provider-support/src/lib.rs index d360ad4c16e39..56217c3d3052c 100644 --- a/substrate/frame/election-provider-support/src/lib.rs +++ b/substrate/frame/election-provider-support/src/lib.rs @@ -55,8 +55,22 @@ //! //! To accommodate both type of elections in one trait, the traits lean toward **stateful //! election**, as it is more general than the stateless. This is why [`ElectionProvider::elect`] -//! has no parameters. All value and type parameter must be provided by the [`ElectionDataProvider`] -//! trait, even if the election happens immediately. +//! does not receive election data as an input. All value and type parameter must be provided by the +//! [`ElectionDataProvider`] trait, even if the election happens immediately. +//! +//! ## Multi-page election +//! +//! Both [`ElectionDataProvider`] and [`ElectionProvider`] traits are parameterized by page, +//! supporting an election to be performed over multiple pages. This enables the +//! [`ElectionDataProvider`] implementor to provide all the election data over multiple pages. +//! Similarly [`ElectionProvider::elect`] is parameterized by page index. +//! +//! ## [`LockableElectionDataProvider`] for multi-page election +//! +//! The [`LockableElectionDataProvider`] trait exposes a way for election data providers to lock +//! and unlock election data mutations. This is an useful trait to ensure that the results of +//! calling [`ElectionDataProvider::electing_voters`] and +//! [`ElectionDataProvider::electable_targets`] remain consistent over multiple pages. //! //! ## Election Data //! @@ -103,17 +117,17 @@ //! impl ElectionDataProvider for Pallet { //! type AccountId = AccountId; //! type BlockNumber = BlockNumber; -//! type MaxVotesPerVoter = ConstU32<1>; +//! type MaxVotesPerVoter = ConstU32<100>; //! //! fn desired_targets() -> data_provider::Result { //! Ok(1) //! } -//! fn electing_voters(bounds: DataProviderBounds, _remaining_pages: PageIndex) +//! fn electing_voters(bounds: DataProviderBounds, _page: PageIndex) //! -> data_provider::Result>> //! { //! Ok(Default::default()) //! } -//! fn electable_targets(bounds: DataProviderBounds, _remaining_pages: PageIndex) -> data_provider::Result> { +//! fn electable_targets(bounds: DataProviderBounds, _page: PageIndex) -> data_provider::Result> { //! Ok(vec![10, 20, 30]) //! } //! fn next_election_prediction(now: BlockNumber) -> BlockNumber { @@ -145,7 +159,11 @@ //! type Pages = T::Pages; //! type DataProvider = T::DataProvider; //! -//! fn elect(remaining_pages: PageIndex) -> Result, Self::Error> { +//! fn elect(page: PageIndex) -> Result, Self::Error> { +//! unimplemented!() +//! } +//! +//! fn ongoing() -> bool { //! unimplemented!() //! } //! } @@ -245,6 +263,9 @@ mod mock; #[cfg(test)] mod tests; +/// A page index for the multi-block elections pagination. +pub type PageIndex = u32; + /// The [`IndexAssignment`] type is an intermediate between the assignments list /// ([`&[Assignment]`][Assignment]) and `SolutionOf`. /// @@ -306,29 +327,33 @@ pub trait ElectionDataProvider { /// Maximum number of votes per voter that this data provider is providing. type MaxVotesPerVoter: Get; - /// All possible targets for the election, i.e. the targets that could become elected, thus - /// "electable". + /// Returns the possible targets for the election associated with page `page`, i.e. the targets + /// that could become elected, thus "electable". /// + /// TODO(gpestana): remove self-weighing and return the weight. /// This should be implemented as a self-weighing function. The implementor should register its /// appropriate weight at the end of execution with the system pallet directly. fn electable_targets( bounds: DataProviderBounds, - remaining_pages: PageIndex, + page: PageIndex, ) -> data_provider::Result>; - /// All the voters that participate in the election, thus "electing". + /// All the voters that participate in the election associated with page `page`, thus + /// "electing". /// /// Note that if a notion of self-vote exists, it should be represented here. /// + /// TODO(gpestana): remove self-weighing and return the weight. /// This should be implemented as a self-weighing function. The implementor should register its /// appropriate weight at the end of execution with the system pallet directly. fn electing_voters( bounds: DataProviderBounds, - remaining_pages: PageIndex, + page: PageIndex, ) -> data_provider::Result>>; /// The number of targets to elect. /// + /// TODO(gpestana): remove self-weighting ?? /// This should be implemented as a self-weighing function. The implementor should register its /// appropriate weight at the end of execution with the system pallet directly. /// @@ -384,10 +409,16 @@ pub trait ElectionDataProvider { fn set_desired_targets(_count: u32) {} } -/// An [`ElectionDataProvider`] that exposes for an external entity to request a lock/unlock on +/// An [`ElectionDataProvider`] that exposes for an external entity to request lock/unlock on /// updates in the election data. +/// +/// This functionality is useful when requesting multi-pages of election data, so that the data +/// provided across pages (and potentially across blocks) is consistent. pub trait LockableElectionDataProvider: ElectionDataProvider { + /// Lock mutations in the election data provider. fn set_lock() -> data_provider::Result<()>; + + /// Unlocks mutations in the election data provider. fn unlock(); } @@ -404,10 +435,16 @@ pub trait ElectionProvider { type Error: Debug + PartialEq; /// The maximum number of winners per page in results returned by this election provider. + /// + /// A winner is an `AccountId` that is part of the final election result. type MaxWinnersPerPage: Get; /// The maximum number of backers that a single page may have in results returned by this /// election provider. + /// + /// A backer is an `AccountId` that "backs" one or more winners. For example, in the context of + /// nominated proof of stake, a backer is a voter that nominates a winner validator in the + /// election result. type MaxBackersPerWinner: Get; /// The number of pages that this election provider supports. @@ -421,11 +458,15 @@ pub trait ElectionProvider { /// Elect a new set of winners. /// + /// A complete election may require multiple calls to [`ElectionProvider::elect`] if + /// [`ElectionProvider::Pages`] is higher than one. + /// /// The result is returned in a target major format, namely as vector of supports. /// + /// TODO(gpestana): remove self-weighing? /// This should be implemented as a self-weighing function. The implementor should register its /// appropriate weight at the end of execution with the system pallet directly. - fn elect(remaining: PageIndex) -> Result, Self::Error>; + fn elect(page: PageIndex) -> Result, Self::Error>; /// The index of the *most* significant page that this election provider supports. fn msp() -> PageIndex { @@ -449,7 +490,7 @@ pub trait ElectionProvider { }) } - /// Indicate if this election provider is currently ongoing an asynchronous election or not. + /// Indicate whether this election provider is currently ongoing an asynchronous election. fn ongoing() -> bool; } @@ -484,7 +525,7 @@ where type MaxWinnersPerPage = MaxWinnersPerPage; type MaxBackersPerWinner = MaxBackersPerWinner; - fn elect(_remaining_pages: PageIndex) -> Result, Self::Error> { + fn elect(_page: PageIndex) -> Result, Self::Error> { Err("`NoElection` cannot do anything.") } @@ -751,6 +792,12 @@ impl> TryFrom> } /// A bounded vector of [`BoundedSupport`]. +/// +/// A [`BoundedSupports`] is a set of [`sp_npos_elections::Supports`] which are bounded in two +/// dimensions. `BInner` corresponds to the bound of the maximum backers per voter and `BOuter` +/// corresponds to the bound of the maximum winners that the bounded supports may contain. +/// +/// With the bounds, we control the maximum size of a bounded supports instance. #[derive(Encode, Decode, TypeInfo, DefaultNoBound, MaxEncodedLen)] #[codec(mel_bound(AccountId: MaxEncodedLen, BOuter: Get, BInner: Get))] #[scale_info(skip_type_params(BOuter, BInner))] @@ -837,19 +884,6 @@ impl, BInner: Get> } } -pub trait TryIntoSupports, BInner: Get> { - fn try_into_supports(self) -> Result, ()>; -} - -impl, BInner: Get> TryIntoSupports - for BoundedSupports -{ - fn try_into_supports(self) -> Result, ()> { - // TODO - Ok(Default::default()) - } -} - /// Same as `BoundedSupports` but parameterized by an `ElectionProvider`. pub type BoundedSupportsOf = BoundedSupports< ::AccountId, @@ -857,9 +891,6 @@ pub type BoundedSupportsOf = BoundedSupports< ::MaxBackersPerWinner, >; -/// A page index for the multi-block elections pagination. -pub type PageIndex = u32; - sp_core::generate_feature_enabled_macro!( runtime_benchmarks_enabled, feature = "runtime-benchmarks", From 5c1757342579757192d0db58ddf52c160b87c5e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Fri, 18 Oct 2024 14:08:49 +0200 Subject: [PATCH 008/169] nits onchain election provider --- .../frame/election-provider-support/src/onchain.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/substrate/frame/election-provider-support/src/onchain.rs b/substrate/frame/election-provider-support/src/onchain.rs index b7519d2dc5a9a..95f74ba298277 100644 --- a/substrate/frame/election-provider-support/src/onchain.rs +++ b/substrate/frame/election-provider-support/src/onchain.rs @@ -103,11 +103,11 @@ pub trait Config { impl OnChainExecution { fn elect_with( bounds: ElectionBounds, - remaining: PageIndex, + page: PageIndex, ) -> Result, Error> { - let (voters, targets) = T::DataProvider::electing_voters(bounds.voters, remaining) + let (voters, targets) = T::DataProvider::electing_voters(bounds.voters, page) .and_then(|voters| { - Ok((voters, T::DataProvider::electable_targets(bounds.targets, remaining)?)) + Ok((voters, T::DataProvider::electable_targets(bounds.targets, page)?)) }) .map_err(Error::DataProvider)?; @@ -179,8 +179,8 @@ impl ElectionProvider for OnChainExecution { type Pages = sp_core::ConstU32<1>; type DataProvider = T::DataProvider; - fn elect(remaining_pages: PageIndex) -> Result, Self::Error> { - if remaining_pages > 0 { + fn elect(page: PageIndex) -> Result, Self::Error> { + if page > 0 { return Err(Error::SinglePageExpected) } @@ -285,7 +285,7 @@ mod tests { type MaxVotesPerVoter = ConstU32<2>; fn electing_voters( _: DataProviderBounds, - _remaining_pages: PageIndex, + _page: PageIndex, ) -> data_provider::Result>> { Ok(vec![ (1, 10, bounded_vec![10, 20]), @@ -296,7 +296,7 @@ mod tests { fn electable_targets( _: DataProviderBounds, - _remaining_pages: PageIndex, + _page: PageIndex, ) -> data_provider::Result> { Ok(vec![10, 20, 30]) } From ee7bdd49cac91defd3aa059658809bd55419e6c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Fri, 18 Oct 2024 16:36:39 +0200 Subject: [PATCH 009/169] EPM nits and rustdoc improvements --- .../election-provider-multi-phase/src/lib.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index 9de43a746d3a0..c7f9e657e546c 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -189,6 +189,13 @@ //! Note that there could be an overlap between these sub-errors. For example, A //! `SnapshotUnavailable` can happen in both miner and feasibility check phase. //! +//! ## Multi-page election support +//! +//! Even though the [`frame_election_provider_support::ElectionDataProvider`] and +//! [`frame_election_provider_support::ElectionProvider`] traits support multi-paged election, this +//! pallet only supports a single page election flow. Thus, [`Config::Pages`] must be always set to +//! one, which is asserted by the [`frame_support::traits::Hooks::integrity_test`]. +//! //! ## Future Plans //! //! **Emergency-phase recovery script**: This script should be taken out of staking-miner in @@ -271,6 +278,7 @@ mod mock; #[macro_use] pub mod helpers; +/// This pallet only supports a single page election flow. pub(crate) const SINGLE_PAGE: u32 = 0; const LOG_TARGET: &str = "runtime::election-provider"; @@ -887,6 +895,9 @@ pub mod pallet { // `SignedMaxSubmissions` is a red flag that the developer does not understand how to // configure this pallet. assert!(T::SignedMaxSubmissions::get() >= T::SignedMaxRefunds::get()); + + // This pallet only supports single-page elections. + assert!(T::Pages::get() == 1u32); } #[cfg(feature = "try-runtime")] @@ -1777,8 +1788,9 @@ impl ElectionProvider for Pallet { type Pages = T::Pages; type DataProvider = T::DataProvider; - fn elect(remaining: PageIndex) -> Result, Self::Error> { - defensive_assert!(remaining.is_zero()); + fn elect(page: PageIndex) -> Result, Self::Error> { + // this pallet **MUST** only by used in the single-block mode. + defensive_assert!(page.is_zero()); match Self::do_elect() { Ok(bounded_supports) => { From f30b548b566f72da139949f6ac19f4326ed576d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Fri, 18 Oct 2024 18:24:23 +0200 Subject: [PATCH 010/169] clean up and rustdocs for EPM core and sub pallets --- Cargo.lock | 27 +++ Cargo.toml | 1 + .../integration-tests/Cargo.toml | 2 +- .../integration-tests/src/mock.rs | 12 +- .../election-provider-multi-block/src/lib.rs | 219 +++++++++++------- .../src/mock/mod.rs | 10 +- .../src/types.rs | 2 + .../src/unsigned/miner.rs | 1 + .../src/unsigned/tests.rs | 20 +- .../src/verifier/impls.rs | 92 +------- 10 files changed, 191 insertions(+), 195 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6aae9a6398c92..785dec8e9ef48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11544,6 +11544,33 @@ dependencies = [ "substrate-test-utils", ] +[[package]] +name = "pallet-epm-mb-integrity-tests" +version = "1.0.0" +dependencies = [ + "frame-election-provider-support", + "frame-support", + "frame-system", + "log", + "pallet-bags-list", + "pallet-balances", + "pallet-election-provider-multi-block", + "pallet-nomination-pools", + "pallet-session", + "pallet-staking", + "pallet-timestamp", + "parity-scale-codec", + "parking_lot 0.12.3", + "scale-info", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-npos-elections", + "sp-runtime 31.0.1", + "sp-staking", + "sp-std 14.0.0", + "sp-tracing 16.0.0", +] + [[package]] name = "pallet-example-basic" version = "27.0.0" diff --git a/Cargo.toml b/Cargo.toml index 308603b09d478..b21c7c1015231 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -341,6 +341,7 @@ members = [ "substrate/frame/democracy", "substrate/frame/election-provider-multi-block", "substrate/frame/election-provider-multi-phase", + "substrate/frame/election-provider-multi-block/integration-tests", "substrate/frame/election-provider-multi-phase/test-staking-e2e", "substrate/frame/election-provider-support", "substrate/frame/election-provider-support/benchmarking", diff --git a/substrate/frame/election-provider-multi-block/integration-tests/Cargo.toml b/substrate/frame/election-provider-multi-block/integration-tests/Cargo.toml index 15aa841666209..195e001026000 100644 --- a/substrate/frame/election-provider-multi-block/integration-tests/Cargo.toml +++ b/substrate/frame/election-provider-multi-block/integration-tests/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "pallet-election-tests" +name = "pallet-epm-mb-integrity-tests" version = "1.0.0" authors.workspace = true edition.workspace = true diff --git a/substrate/frame/election-provider-multi-block/integration-tests/src/mock.rs b/substrate/frame/election-provider-multi-block/integration-tests/src/mock.rs index 29dd2733250e3..61ab42251d7c3 100644 --- a/substrate/frame/election-provider-multi-block/integration-tests/src/mock.rs +++ b/substrate/frame/election-provider-multi-block/integration-tests/src/mock.rs @@ -45,7 +45,6 @@ use frame_election_provider_support::{ bounds::ElectionBoundsBuilder, onchain, ElectionDataProvider, ExtendedBalance, PageIndex, SequentialPhragmen, Weight, }; -use sp_npos_elections::ElectionScore; use pallet_election_provider_multi_block::{ self as epm_core_pallet, @@ -869,10 +868,15 @@ parameter_types! { pub(crate) fn try_submit_paged_solution() -> Result<(), ()> { let submit = || { // TODO: to finish. + let voters_snapshot = Default::default(); + let targets_snapshot = Default::default(); + let round = Default::default(); + let desired_targets = Default::default(); + let (paged_solution, _) = miner::Miner::<::MinerConfig>::mine_paged_solution_with_snapshot( - voters_snapshot, - targets_snapshot, + &voters_snapshot, + &targets_snapshot, Pages::get(), round, desired_targets, @@ -882,7 +886,7 @@ pub(crate) fn try_submit_paged_solution() -> Result<(), ()> { let _ = SignedPallet::register(RuntimeOrigin::signed(10), paged_solution.score).unwrap(); - for (idx, page) in paged_solution.solution_pages.into_iter().enumerate() {} + for (_idx, _page) in paged_solution.solution_pages.into_iter().enumerate() {} log!( info, "submitter: successfully submitted {} pages with {:?} score in round {}.", diff --git a/substrate/frame/election-provider-multi-block/src/lib.rs b/substrate/frame/election-provider-multi-block/src/lib.rs index 5b813b39cd33e..3308c3c261706 100644 --- a/substrate/frame/election-provider-multi-block/src/lib.rs +++ b/substrate/frame/election-provider-multi-block/src/lib.rs @@ -19,7 +19,7 @@ //! //! This pallet manages the NPoS election across its different phases, with the ability to accept //! both on-chain and off-chain solutions. The off-chain solutions may be submitted as a signed or -//! unsigned transaction. Crucially, supports paginated, multi-block elections. The goal of +//! unsigned transaction. Crucially, it supports paginated, multi-block elections. The goal of //! supporting paged elections is to scale the elections linearly with the number of blocks //! allocated to the election. //! @@ -54,8 +54,8 @@ //! - The [`signed`] pallet implements the signed phase, where off-chain entities commit to and //! submit their election solutions. This pallet implements the //! [`verifier::SolutionDataProvider`], which is used by the [`verifier`] pallet to fetch solution -//! data. -//! - The [`unsigned`] pallet implements the unsigned phase, where block authors can calculate and +//! data to perform the solution verification. +//! - The [`unsigned`] pallet implements the unsigned phase, where block authors can compute and //! submit through inherent paged solutions. This pallet also implements the //! [`verifier::SolutionDataProvider`] interface. //! @@ -77,6 +77,8 @@ //! This pallet manages the election phases which signal to the other sub-pallets which actions to //! take at a given block. The election phases are the following: //! +//! +//! // TODO(gpestana): use a diagram instead of text diagram. //! ```text //! // ----------- ----------- -------------- ------------ -------- //! // | | | | | | | @@ -86,22 +88,20 @@ //! Each phase duration depends on the estimate block number election, which can be fetched from //! [`pallet::Config::DataProvider`]. //! -//! > to-finish +//! TODO(gpestana): finish, add all info related to EPM-MB #![cfg_attr(not(feature = "std"), no_std)] -// TODO: remove -#![allow(dead_code)] use frame_election_provider_support::{ bounds::ElectionBoundsBuilder, BoundedSupportsOf, ElectionDataProvider, ElectionProvider, - LockableElectionDataProvider, PageIndex, VoterOf, Weight, + LockableElectionDataProvider, PageIndex, Weight, }; use frame_support::{ defensive, ensure, traits::{Defensive, DefensiveSaturating, Get}, BoundedVec, }; -use sp_runtime::traits::Zero; +use sp_runtime::traits::{One, Zero}; use frame_system::pallet_prelude::BlockNumberFor; @@ -129,6 +129,7 @@ pub use crate::{unsigned::miner, verifier::Verifier, weights::WeightInfo}; #[cfg(any(test, feature = "runtime-benchmarks"))] use crate::verifier::Pallet as PalletVerifier; +/// Log target for this the core EPM-MB pallet. const LOG_TARGET: &'static str = "runtime::multiblock-election"; /// Page configured for the election. @@ -146,7 +147,7 @@ pub trait BenchmarkingConfig { const TARGETS_PER_PAGE: [u32; 2]; } -#[frame_support::pallet(dev_mode)] +#[frame_support::pallet] pub mod pallet { use super::*; @@ -155,7 +156,6 @@ pub mod pallet { sp_runtime::Saturating, Twox64Concat, }; - use frame_system::pallet_prelude::BlockNumberFor; #[pallet::config] pub trait Config: frame_system::Config { @@ -164,20 +164,39 @@ pub mod pallet { + TryInto>; /// Duration of the signed phase; + /// + /// During the signed phase, staking miners may register their solutions and submit + /// paginated solutions. #[pallet::constant] type SignedPhase: Get>; /// Duration of the unsigned phase; + /// + /// During the unsigned phase, offchain workers of block producing validators compute and + /// submit paginated solutions. #[pallet::constant] type UnsignedPhase: Get>; /// Duration of the signed validation phase. /// - /// The duration of this phase SHOULD NOT be less than `T::Pages` and there is no point in - /// it being more than the maximum number of pages per submission. + /// During the signed validation phase, the async verifier verifies one or all the queued + /// solution submitions during the signed phase. Once one solution is accepted, this phase + /// terminates. + /// + /// The duration of this phase **SHOULD NOT** be less than `T::Pages` and there is no point + /// in it being more than the maximum number of pages per submission. #[pallet::constant] type SignedValidationPhase: Get>; + /// The limit number of blocks that the `Phase::Export` will be open for. + /// + /// During the export phase, this pallet is open to return paginated, verified solution + /// pages if at least one solution has been verified and accepted in the current era. + /// + /// The export phase will terminate if it has been open for `T::ExportPhaseLimit` blocks or + /// the `EPM::call(0)` is called. + type ExportPhaseLimit: Get>; + /// The number of blocks that the election should be ready before the election deadline. #[pallet::constant] type Lookhaead: Get>; @@ -194,7 +213,7 @@ pub mod pallet { /// in one page of a solution. type MaxWinnersPerPage: Get; - /// Maximum number of voters that can support a single target, across ALL the solution + /// Maximum number of voters that can support a single target, across **ALL(()) the solution /// pages. Thus, this can only be verified when processing the last solution page. /// /// This limit must be set so that the memory limits of the rest of the system are @@ -203,16 +222,10 @@ pub mod pallet { /// The number of pages. /// - /// A solution may contain at MOST this many pages. + /// A solution may contain at **MOST** this many pages. #[pallet::constant] type Pages: Get; - /// The limit number of blocks that the `Phase::Export` will be open for. - /// - /// The export phase will terminate if it has been open for `T::ExportPhaseLimit` blocks or - /// the `EPM::call(0)` is called. - type ExportPhaseLimit: Get>; - /// Something that will provide the election data. type DataProvider: LockableElectionDataProvider< AccountId = Self::AccountId, @@ -275,6 +288,8 @@ pub mod pallet { } /// Election failure strategy. + /// + /// This strategy defines the actions of this pallet once an election fails. #[pallet::storage] pub(crate) type ElectionFailure = StorageValue<_, ElectionFailureStrategy, ValueQuery>; @@ -283,14 +298,15 @@ pub mod pallet { #[pallet::storage] pub(crate) type CurrentPhase = StorageValue<_, Phase>, ValueQuery>; - /// Current round + /// Current round. #[pallet::storage] pub(crate) type Round = StorageValue<_, u32, ValueQuery>; - /// Paginated target snapshot. + /// Target snapshot. + /// + /// Note: The target snapshot is single-paged. #[pallet::storage] - pub(crate) type PagedTargetSnapshot = - StorageMap<_, Twox64Concat, PageIndex, BoundedVec>; + pub(crate) type TargetSnapshot = StorageValue<_, TargetPageOf, OptionQuery>; /// Paginated voter snapshot. #[pallet::storage] @@ -321,8 +337,6 @@ pub mod pallet { // | | | | | | | // Off Snapshot (Signed SigValid) Unsigned Export elect() - use sp_runtime::traits::One; - let export_deadline = T::ExportPhaseLimit::get().saturating_add(T::Lookhaead::get()); let unsigned_deadline = export_deadline.saturating_add(T::UnsignedPhase::get()); let signed_validation_deadline = @@ -364,9 +378,9 @@ pub mod pallet { match current_phase { // start snapshot. Phase::Off + // allocate one extra block for the (single-page) target snapshot. if remaining_blocks <= snapshot_deadline && remaining_blocks > signed_deadline => - // allocate one extra block for the target snapshot. Self::try_progress_snapshot(T::Pages::get() + 1), // continue snapshot. @@ -378,13 +392,13 @@ pub mod pallet { T::WeightInfo::on_phase_transition() }, - // start signed phase. The `signed` pallet will take further actions now. + // start signed phase. The `signed` sub-pallet will take further actions now. Phase::Snapshot(0) if remaining_blocks <= signed_deadline && remaining_blocks > signed_validation_deadline => Self::start_signed_phase(), - // start signed validation. The `signed` pallet will take further actions now. + // start signed validation. The `signed` sub-pallet will take further actions now. Phase::Signed if remaining_blocks <= signed_validation_deadline && remaining_blocks > unsigned_deadline => @@ -393,7 +407,7 @@ pub mod pallet { T::WeightInfo::on_phase_transition() }, - // start unsigned phase. The `unsigned` pallet will take further actions now. + // start unsigned phase. The `unsigned` sub-pallet will take further actions now. Phase::Signed | Phase::SignedValidation(_) | Phase::Snapshot(0) if remaining_blocks <= unsigned_deadline && remaining_blocks > Zero::zero() => { @@ -401,7 +415,14 @@ pub mod pallet { T::WeightInfo::on_phase_transition() }, - // EPM is "serving" the staking pallet with the election results. + // start export phase. + Phase::Unsigned(_) if now == next_election.saturating_sub(export_deadline) => { + Self::phase_transition(Phase::Export(now)); + T::WeightInfo::on_phase_transition() + }, + + // election solution **MAY** be ready, start export phase to allow external pallets + // to request paged election solutions. Phase::Export(started_at) => Self::do_export_phase(now, started_at), _ => T::WeightInfo::on_initialize_do_nothing(), @@ -429,7 +450,7 @@ pub mod pallet { /// It manages the following storage items: /// /// - [`PagedVoterSnapshot`]: Paginated map of voters. -/// - [`PagedTargetSnapshot`]: Paginated map of targets. +/// - [`TargetSnapshot`]: Single page, bounded list of targets. /// /// To ensure correctness and data consistency, all the reads and writes to storage items related /// to the snapshot and "wrapped" by this struct must be performed through the methods exposed by @@ -438,24 +459,24 @@ pub(crate) struct Snapshot(sp_std::marker::PhantomData); impl Snapshot { /// Returns the targets snapshot. /// - /// TODO(gpestana): consider paginating targets (update: a lot of shenenigans on the assignments - /// converstion and target/voter index. Hard to ensure that no more than 1 snapshot page is - /// fetched when both voter and target snapshots are paged.) - fn targets() -> Option> { - PagedTargetSnapshot::::get(Pallet::::lsp()) + /// The target snapshot is single paged. + fn targets() -> Option> { + TargetSnapshot::::get() } /// Sets a page of targets in the snapshot's storage. - fn set_targets(targets: BoundedVec) { - PagedTargetSnapshot::::insert(Pallet::::lsp(), targets); + /// + /// The target snapshot is single paged. + fn set_targets(targets: TargetPageOf) { + TargetSnapshot::::set(Some(targets)); } /// Returns whether the target snapshot exists in storage. fn targets_snapshot_exists() -> bool { - !PagedTargetSnapshot::::iter_keys().count().is_zero() + TargetSnapshot::::get().is_some() } - /// Return the number of desired targets, which is defined by [`T::DataProvider`]. + /// Returns the number of desired targets, as defined by [`T::DataProvider`]. fn desired_targets() -> Option { match T::DataProvider::desired_targets() { Ok(desired) => Some(desired), @@ -469,32 +490,29 @@ impl Snapshot { } } - /// Returns the voters of a specific `page` index in the current snapshot. + /// Returns the voters of a specific `page` index of the current snapshot, if any. fn voters(page: PageIndex) -> Option> { PagedVoterSnapshot::::get(page) } /// Sets a single page of voters in the snapshot's storage. - fn set_voters( - page: PageIndex, - voters: BoundedVec, T::VoterSnapshotPerBlock>, - ) { + fn set_voters(page: PageIndex, voters: VoterPageOf) { PagedVoterSnapshot::::insert(page, voters); } /// Clears all data related to a snapshot. /// - /// At the end of a round, all the snapshot related data must be cleared and the election phase - /// has transitioned to `Phase::Off`. + /// At the end of a round, all the snapshot related data must be cleared. Clearing the + /// snapshot data **MUST* only be performed only during `Phase::Off`. fn kill() { let _ = PagedVoterSnapshot::::clear(u32::MAX, None); - let _ = PagedTargetSnapshot::::clear(u32::MAX, None); + let _ = TargetSnapshot::::kill(); debug_assert_eq!(>::get(), Phase::Off); } - #[allow(dead_code)] #[cfg(any(test, debug_assertions))] + #[allow(dead_code)] pub(crate) fn ensure() -> Result<(), &'static str> { let pages = T::Pages::get(); ensure!(pages > 0, "number pages must be higer than 0."); @@ -537,24 +555,26 @@ impl Pallet { /// Return the most significant page of the snapshot. /// - /// Based on the contract with `ElectionDataProvider`, tis is the first page to be filled. + /// Based on the contract with `ElectionDataProvider`, this is the first page to be requested + /// and filled. pub fn msp() -> PageIndex { T::Pages::get().checked_sub(1).defensive_unwrap_or_default() } /// Return the least significant page of the snapshot. /// - /// Based on the contract with `ElectionDataProvider`, tis is the last page to be filled. + /// Based on the contract with `ElectionDataProvider`, this is the last page to be requested + /// and filled. pub fn lsp() -> PageIndex { Zero::zero() } /// Creates and stores the target snapshot. /// - /// Note: currently, the pallet uses single page target page only. + /// Note: the target snapshot is single paged. fn create_targets_snapshot() -> Result> { let stored_count = Self::create_targets_snapshot_inner(T::TargetSnapshotPerBlock::get())?; - log!(info, "created target snapshot with {} targets.", stored_count); + log!(trace, "created target snapshot with {} targets.", stored_count); Ok(stored_count) } @@ -569,13 +589,10 @@ impl Pallet { let targets: BoundedVec<_, T::TargetSnapshotPerBlock> = T::DataProvider::electable_targets(bounds, Zero::zero()) .and_then(|t| { - t.try_into().map_err(|e| { - log!(error, "too many targets? err: {:?}", e); - "too many targets returned by the data provider." - }) + t.try_into().map_err(|_| "too many targets returned by the data provider.") }) .map_err(|e| { - log!(info, "error fetching electable targets from data provider: {:?}", e); + log!(error, "error fetching electable targets from data provider: {:?}", e); ElectionError::::DataProvider })?; @@ -591,7 +608,7 @@ impl Pallet { let paged_voters_count = Self::create_voters_snapshot_inner(remaining_pages, T::VoterSnapshotPerBlock::get())?; - log!(info, "created voter snapshot with {} voters.", paged_voters_count); + log!(trace, "created voter snapshot with {} voters.", paged_voters_count); Ok(paged_voters_count) } @@ -621,8 +638,9 @@ impl Pallet { /// Tries to progress the snapshot. /// - /// The first (and only) target page is fetched from the [`DataProvider`] at the same block when - /// the msp of the voter snaphot. + /// The first call to this method will calculate and store the (single-paged) target snapshot. + /// The subsequent calls will fetch the voter pages. Thus, the caller must call this method + /// `T::Pages`..0 times. fn try_progress_snapshot(remaining_pages: PageIndex) -> Weight { let _ = ::set_lock(); @@ -636,7 +654,7 @@ impl Pallet { // first block for single target snapshot. match Self::create_targets_snapshot() { Ok(target_count) => { - log!(info, "target snapshot created with {} targets", target_count); + log!(trace, "target snapshot created with {} targets", target_count); Self::phase_transition(Phase::Snapshot(remaining_pages.saturating_sub(1))); T::WeightInfo::create_targets_snapshot_paged(T::TargetSnapshotPerBlock::get()) }, @@ -647,11 +665,11 @@ impl Pallet { }, } } else { - // progress voter snapshot. + // try progress voter snapshot. match Self::create_voters_snapshot(remaining_pages) { Ok(voter_count) => { log!( - info, + trace, "voter snapshot progressed: page {} with {} voters", remaining_pages, voter_count, @@ -668,15 +686,28 @@ impl Pallet { } } + /// Start the signed phase. + /// We expect the snapshot to be ready by now. Thus the the data provider lock should be + /// released and transition to the signed phase. pub(crate) fn start_signed_phase() -> Weight { - // done with the snapshot, release the data provider lock. + debug_assert!(Snapshot::::ensure().is_ok()); + ::unlock(); Self::phase_transition(Phase::Signed); T::WeightInfo::on_initialize_start_signed() } + /// Export phase. + /// + /// In practice, we just need to ensure the export phase does not remain open for too long. + /// During this phase, we expect the external entities to call [`ElectionProvider::elect`] for + /// all the solution pages. Once the least significant page is called, the phase should + /// transition to `Phase::Off`. Thus, if the export phase remains open for too long, it means + /// that the election failed. pub(crate) fn do_export_phase(now: BlockNumberFor, started_at: BlockNumberFor) -> Weight { + debug_assert!(Pallet::::current_phase().is_export()); + if now > started_at + T::ExportPhaseLimit::get() { log!( error, @@ -685,7 +716,7 @@ impl Pallet { ); match ElectionFailure::::get() { - ElectionFailureStrategy::Restart => Self::reset_round(), + ElectionFailureStrategy::Restart => Self::reset_round_restart(), ElectionFailureStrategy::Emergency => Self::phase_transition(Phase::Emergency), } } @@ -698,6 +729,7 @@ impl Pallet { /// 1. Increment round. /// 2. Change phase to [`Phase::Off`]. /// 3. Clear all snapshot data. + /// 4. Resets verifier. fn rotate_round() { >::mutate(|r| r.defensive_saturating_accrue(1)); Self::phase_transition(Phase::Off); @@ -706,14 +738,18 @@ impl Pallet { ::kill(); } - /// Performs all tasks required after an unsuccessful election: + /// Performs all tasks required after an unsuccessful election which should be self-healing + /// (i.e. the election should restart without entering in emergency phase). + /// + /// Note: the round should not restart as the previous election failed. /// /// 1. Change phase to [`Phase::Off`]. /// 2. Clear all snapshot data. - fn reset_round() { + /// 3. Resets verifier. + fn reset_round_restart() { Self::phase_transition(Phase::Off); - Snapshot::::kill(); + Snapshot::::kill(); ::kill(); } } @@ -727,8 +763,12 @@ impl ElectionProvider for Pallet { type Pages = T::Pages; type DataProvider = T::DataProvider; - /// Important note: we do exect the caller of `elect` to reach page 0. + /// Important note: we do exect the caller of `elect` to call pages down to `lsp == 0`. + /// Otherwise the export phase will not explicitly finish which will result in a failed + /// election. fn elect(remaining: PageIndex) -> Result, Self::Error> { + ensure!(Pallet::::current_phase().is_export(), ElectionError::ElectionNotReady); + T::Verifier::get_queued_solution(remaining) .ok_or(ElectionError::::SupportPageNotAvailable(remaining)) .or_else(|err| { @@ -742,7 +782,7 @@ impl ElectionProvider for Pallet { }) .map(|supports| { if remaining.is_zero() { - log!(info, "elect(): provided the last supports page, rotating round."); + log!(trace, "elect(): provided the last supports page, rotating round."); Self::rotate_round(); } else { // Phase::Export is on while the election is calling all pages of `elect`. @@ -758,7 +798,7 @@ impl ElectionProvider for Pallet { match ElectionFailure::::get() { // force emergency phase for testing. - ElectionFailureStrategy::Restart => Self::reset_round(), + ElectionFailureStrategy::Restart => Self::reset_round_restart(), ElectionFailureStrategy::Emergency => Self::phase_transition(Phase::Emergency), } err @@ -782,9 +822,6 @@ mod phase_transition { #[test] fn single_page() { - // ---------- ---------- -------------- ----------- - // | | | | | - // Snapshot Signed SignedValidation Unsigned elect() let (mut ext, _) = ExtBuilder::default() .pages(1) .signed_phase(3) @@ -825,16 +862,13 @@ mod phase_transition { let start_unsigned = System::block_number(); assert_eq!(>::get(), Phase::Unsigned(start_unsigned)); - roll_to(next_election + 1); - assert_eq!(>::get(), Phase::Unsigned(start_unsigned)); - - // unsigned phase until elect() is called. - roll_to(next_election + 3); - assert_eq!(>::get(), Phase::Unsigned(start_unsigned)); + // roll to export phase to call elect(). + roll_to_export(); + // elect() should work. assert_ok!(MultiPhase::elect(0)); - // election done, go to off phase. + // one page only -- election done, go to off phase. assert_eq!(>::get(), Phase::Off); }) } @@ -873,6 +907,9 @@ mod phase_transition { roll_to(expected_snapshot + 1); assert_eq!(>::get(), Phase::Snapshot(0)); + // ensure snapshot is sound by end of snapshot phase. + assert_ok!(Snapshot::::ensure()); + roll_to(expected_signed); assert_eq!(>::get(), Phase::Signed); @@ -944,16 +981,26 @@ mod snapshot { #[test] fn setters_getters_work() { - ExtBuilder::default().build_and_execute(|| { + ExtBuilder::default().pages(2).build_and_execute(|| { + let t = BoundedVec::<_, _>::try_from(vec![]).unwrap(); let v = BoundedVec::<_, _>::try_from(vec![]).unwrap(); assert!(Snapshot::::targets().is_none()); assert!(Snapshot::::voters(0).is_none()); assert!(Snapshot::::voters(1).is_none()); - Snapshot::::set_targets(v.clone()); + Snapshot::::set_targets(t.clone()); assert!(Snapshot::::targets().is_some()); + Snapshot::::set_voters(0, v.clone()); + Snapshot::::set_voters(1, v.clone()); + + assert!(Snapshot::::voters(0).is_some()); + assert!(Snapshot::::voters(1).is_some()); + + // ensure snapshot is sound. + assert_ok!(Snapshot::::ensure()); + Snapshot::::kill(); assert!(Snapshot::::targets().is_none()); assert!(Snapshot::::voters(0).is_none()); @@ -1088,7 +1135,7 @@ mod election_provider { bounded_vec![10, 20, 30, 40]; let all_voter_pages: BoundedVec< - BoundedVec, VotersPerPage>, + BoundedVec, VotersPerPage>, Pages, > = bounded_vec![ bounded_vec![ diff --git a/substrate/frame/election-provider-multi-block/src/mock/mod.rs b/substrate/frame/election-provider-multi-block/src/mock/mod.rs index eaf5797a5215e..d1b82d3a18877 100644 --- a/substrate/frame/election-provider-multi-block/src/mock/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/mock/mod.rs @@ -451,7 +451,7 @@ pub(crate) fn roll_to(n: BlockNumber) { // TODO: add try-checks for all pallets here too, as we progress the blocks. log!( - info, + trace, "Block: {}, Phase: {:?}, Round: {:?}, Election at {:?}", bn, >::get(), @@ -468,6 +468,12 @@ pub fn roll_to_phase(phase: Phase) { } } +pub fn roll_to_export() { + while !MultiPhase::current_phase().is_export() { + roll_to(System::block_number() + 1); + } +} + pub fn roll_one_with_ocw(maybe_pool: Option>>) { use sp_runtime::traits::Dispatchable; let bn = System::block_number() + 1; @@ -530,7 +536,7 @@ pub fn assert_snapshots() -> Result<(), &'static str> { pub fn clear_snapshot() { let _ = crate::PagedVoterSnapshot::::clear(u32::MAX, None); - let _ = crate::PagedTargetSnapshot::::clear(u32::MAX, None); + let _ = crate::TargetSnapshot::::kill(); } pub fn balances(who: AccountId) -> (Balance, Balance) { diff --git a/substrate/frame/election-provider-multi-block/src/types.rs b/substrate/frame/election-provider-multi-block/src/types.rs index e903db68e2d77..8f8e95d2a46e4 100644 --- a/substrate/frame/election-provider-multi-block/src/types.rs +++ b/substrate/frame/election-provider-multi-block/src/types.rs @@ -194,6 +194,8 @@ pub enum ElectionError { /// The requested page exceeds the number of election pages defined of the current election /// config. RequestedPageExceeded, + /// Election not ready yet. + ElectionNotReady, /// The fallback election error'ed. Fallback(FallbackErrorOf), } diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs b/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs index a83cbdafb09c8..2d5ce0b27d409 100644 --- a/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs +++ b/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs @@ -607,6 +607,7 @@ impl OffchainWorkerMiner { /// /// Mines a new solution with [`crate::Pallet::Pages`] pages and computes the partial score /// of the page with `page` index. + #[allow(dead_code)] pub fn mine( page: PageIndex, ) -> Result< diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/tests.rs b/substrate/frame/election-provider-multi-block/src/unsigned/tests.rs index b37cc6bf43459..3464eb0be4536 100644 --- a/substrate/frame/election-provider-multi-block/src/unsigned/tests.rs +++ b/substrate/frame/election-provider-multi-block/src/unsigned/tests.rs @@ -16,10 +16,7 @@ // limitations under the License. use super::*; -use crate::{ - mock::*, unsigned::miner::Config, PagedTargetSnapshot, PagedVoterSnapshot, Phase, Snapshot, - Verifier, -}; +use crate::{mock::*, PagedVoterSnapshot, Phase, Snapshot, TargetSnapshot, Verifier}; use frame_election_provider_support::ElectionProvider; use frame_support::assert_ok; @@ -59,11 +56,10 @@ mod calls { // roll to election prediction bn. roll_to_with_ocw(election_prediction(), Some(pool.clone())); - // still in unsigned phase (after unsigned submissions have been submitted and before - // the election happened). - assert!(current_phase().is_unsigned()); + // now in the export phase. + assert!(current_phase().is_export()); - // elect() works as expected. + // thus, elect() works as expected. assert!(call_elect().is_ok()); assert_eq!(current_phase(), Phase::Off); @@ -85,11 +81,11 @@ mod calls { // but snapshot exists. assert!(PagedVoterSnapshot::::get(crate::Pallet::::lsp()).is_some()); - assert!(PagedTargetSnapshot::::get(crate::Pallet::::lsp()).is_some()); + assert!(TargetSnapshot::::get().is_some()); // so let's clear it. clear_snapshot(); assert!(PagedVoterSnapshot::::get(crate::Pallet::::lsp()).is_none()); - assert!(PagedTargetSnapshot::::get(crate::Pallet::::lsp()).is_none()); + assert!(TargetSnapshot::::get().is_none()); // progress through unsigned phase just before the election. roll_to_with_ocw(29, Some(pool.clone())); @@ -108,7 +104,7 @@ mod calls { // snapshot exists now. assert!(PagedVoterSnapshot::::get(crate::Pallet::::lsp()).is_some()); - assert!(PagedTargetSnapshot::::get(crate::Pallet::::lsp()).is_some()); + assert!(TargetSnapshot::::get().is_some()); roll_to_with_ocw(election_prediction() - 1, Some(pool.clone())); @@ -140,8 +136,6 @@ mod calls { mod miner { use super::*; - type OffchainSolver = ::Solver; - #[test] fn snapshot_idx_based_works() { ExtBuilder::default().build_and_execute(|| { diff --git a/substrate/frame/election-provider-multi-block/src/verifier/impls.rs b/substrate/frame/election-provider-multi-block/src/verifier/impls.rs index dc108ec3cdc06..dfcb74cb675e0 100644 --- a/substrate/frame/election-provider-multi-block/src/verifier/impls.rs +++ b/substrate/frame/election-provider-multi-block/src/verifier/impls.rs @@ -16,7 +16,7 @@ // limitations under the License. // TODO(gpestana): clean up imports. -use frame_election_provider_support::{NposSolution, PageIndex, TryIntoBoundedSupports}; +use frame_election_provider_support::PageIndex; use frame_support::{ ensure, pallet_prelude::Weight, @@ -29,7 +29,7 @@ use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; use super::*; use pallet::*; -use crate::{helpers, unsigned::miner, verifier::weights::WeightInfo, MinerSupportsOf, SolutionOf}; +use crate::{unsigned::miner, verifier::weights::WeightInfo, MinerSupportsOf, SolutionOf}; #[frame_support::pallet(dev_mode)] pub(crate) mod pallet { @@ -651,95 +651,9 @@ impl Pallet { Ok(()) } - pub(crate) fn feasibility_check_old( - partial_solution: SolutionOf, - page: PageIndex, - ) -> Result, FeasibilityError> { - // Read the corresponding snapshots. - let snapshot_targets = - crate::Snapshot::::targets().ok_or(FeasibilityError::SnapshotUnavailable)?; - let snapshot_voters = - crate::Snapshot::::voters(page).ok_or(FeasibilityError::SnapshotUnavailable)?; - - let voter_cache = helpers::generate_voter_cache::(&snapshot_voters); - let voter_at = helpers::voter_at_fn::(&snapshot_voters); - let target_at = helpers::target_at_fn::(&snapshot_targets); - let voter_index = helpers::voter_index_fn_usize::(&voter_cache); - - // Then convert solution -> assignment. This will fail if any of the indices are - // gibberish. - let assignments = partial_solution - .into_assignment(voter_at, target_at) - .map_err::(Into::into)?; - - // Ensure that assignments are all correct. - let _ = assignments - .iter() - .map(|ref assignment| { - // Check that assignment.who is actually a voter (defensive-only). NOTE: while - // using the index map from `voter_index` is better than a blind linear search, - // this *still* has room for optimization. Note that we had the index when we - // did `solution -> assignment` and we lost it. Ideal is to keep the index - // around. - - // Defensive-only: must exist in the snapshot. - let snapshot_index = - voter_index(&assignment.who).ok_or(FeasibilityError::InvalidVoter)?; - // Defensive-only: index comes from the snapshot, must exist. - let (_voter, _stake, targets) = - snapshot_voters.get(snapshot_index).ok_or(FeasibilityError::InvalidVoter)?; - debug_assert!(*_voter == assignment.who); - - // Check that all of the targets are valid based on the snapshot. - if assignment.distribution.iter().any(|(t, _)| !targets.contains(t)) { - return Err(FeasibilityError::InvalidVote) - } - Ok(()) - }) - .collect::>()?; - - // ----- Start building support. First, we need one more closure. - let stake_of = helpers::stake_of_fn::(&snapshot_voters, &voter_cache); - - // This might fail if the normalization fails. Very unlikely. See `integrity_test`. - let staked_assignments = - sp_npos_elections::assignment_ratio_to_staked_normalized(assignments, stake_of) - .map_err::(Into::into)?; - - let supports = sp_npos_elections::to_supports(&staked_assignments); - - // Check the maximum number of backers per winner. If this is a single-page solution, this - // is enough to check `MaxBackersPerWinner`. Else, this is just a heuristic, and needs to be - // checked again at the end (via `QueuedSolutionBackings`). - ensure!( - supports - .iter() - .all(|(_, s)| (s.voters.len() as u32) <= T::MaxBackersPerWinner::get()), - FeasibilityError::TooManyBackings - ); - - // Ensure some heuristics. These conditions must hold in the **entire** support, this is - // just a single page. But, they must hold in a single page as well. - let desired_targets = - crate::Snapshot::::desired_targets().ok_or(FeasibilityError::SnapshotUnavailable)?; - - // supports per page must not be higher than the desired targets, otherwise final solution - // will also be higher than desired_targets. - ensure!((supports.len() as u32) <= desired_targets, FeasibilityError::WrongWinnerCount); - - // almost-defensive-only: `MaxBackersPerWinner` is already checked. A sane value of - // `MaxWinnersPerPage` should be more than any possible value of `desired_targets()`, which - // is ALSO checked, so this conversion can almost never fail. - let bounded_supports = supports.try_into_bounded_supports().map_err(|e| { - log!(info, "ERR: {:?}", e); - FeasibilityError::WrongWinnerCount - })?; - - Ok(bounded_supports) - } - /// Returns the number backings/pages verified and stored. #[cfg(any(test, feature = "runtime-benchmarks"))] + #[allow(dead_code)] pub(crate) fn pages_backed() -> usize { QueuedSolutionBackings::::iter_keys().count() } From e7bf7dc4ad5c3c0e48c9a5970544e25bd5821940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Fri, 18 Oct 2024 18:37:11 +0200 Subject: [PATCH 011/169] rustdocs and nits --- .../src/unsigned/miner.rs | 1 - .../src/unsigned/mod.rs | 7 ++----- .../src/verifier/impls.rs | 15 ++++++--------- .../src/verifier/mod.rs | 7 +++++-- 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs b/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs index 2d5ce0b27d409..b1ad4f6a2d798 100644 --- a/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs +++ b/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs @@ -317,7 +317,6 @@ impl Miner { desired_targets: u32, page: PageIndex, ) -> Result, FeasibilityError> { - // TODO: double check page index if tests ERR. let voters_page: BoundedVec, ::VoterSnapshotPerBlock> = voters .get(page as usize) .ok_or(FeasibilityError::Incomplete) diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs b/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs index a675907e13e49..55479cfadbedc 100644 --- a/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs @@ -78,7 +78,7 @@ use frame_support::{ pallet_prelude::{TransactionValidity, ValidTransaction}, traits::Get, }; -use frame_system::{offchain::SendTransactionTypes, pallet_prelude::BlockNumberFor}; +use frame_system::{ensure_none, offchain::SendTransactionTypes, pallet_prelude::BlockNumberFor}; use sp_npos_elections::ElectionScore; use sp_runtime::SaturatedConversion; @@ -93,10 +93,7 @@ pub(crate) mod pallet { use super::*; use frame_support::pallet_prelude::*; - use frame_system::{ - ensure_none, - pallet_prelude::{BlockNumberFor, OriginFor}, - }; + use frame_system::pallet_prelude::OriginFor; #[pallet::config] #[pallet::disable_frame_system_supertrait_check] diff --git a/substrate/frame/election-provider-multi-block/src/verifier/impls.rs b/substrate/frame/election-provider-multi-block/src/verifier/impls.rs index dfcb74cb675e0..e2d0085dedbee 100644 --- a/substrate/frame/election-provider-multi-block/src/verifier/impls.rs +++ b/substrate/frame/election-provider-multi-block/src/verifier/impls.rs @@ -15,7 +15,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -// TODO(gpestana): clean up imports. +use super::*; +use crate::{unsigned::miner, verifier::weights::WeightInfo, MinerSupportsOf, SolutionOf}; +use pallet::*; + use frame_election_provider_support::PageIndex; use frame_support::{ ensure, @@ -26,11 +29,6 @@ use frame_support::{ use sp_runtime::{traits::Zero, Perbill}; use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; -use super::*; -use pallet::*; - -use crate::{unsigned::miner, verifier::weights::WeightInfo, MinerSupportsOf, SolutionOf}; - #[frame_support::pallet(dev_mode)] pub(crate) mod pallet { use super::*; @@ -354,7 +352,7 @@ impl Verifier for Pallet { match Self::do_verify_sync(partial_solution, partial_score, page) { Ok(supports) => { sublog!( - info, + trace, "verifier", "queued sync solution with score {:?} (page {:?})", partial_score, @@ -366,7 +364,7 @@ impl Verifier for Pallet { }, Err(err) => { sublog!( - info, + trace, "verifier", "sync verification failed with {:?} (page: {:?})", err, @@ -446,7 +444,6 @@ impl AsyncVerifier for Pallet { fn stop() { sublog!(warn, "verifier", "stop signal received. clearing everything."); - // TODO(gpestana): debug asserts QueuedSolution::::clear_invalid_and_backings(); // if a verification is ongoing, signal the solution rejection to the solution data diff --git a/substrate/frame/election-provider-multi-block/src/verifier/mod.rs b/substrate/frame/election-provider-multi-block/src/verifier/mod.rs index 8b7e095ac6371..04d631af68d8c 100644 --- a/substrate/frame/election-provider-multi-block/src/verifier/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/verifier/mod.rs @@ -71,6 +71,7 @@ pub mod benchmarking; #[cfg(test)] mod tests; +use crate::{PageIndex, SupportsOf}; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::traits::Get; use sp_npos_elections::{ElectionScore, ExtendedBalance}; @@ -82,8 +83,6 @@ pub use impls::pallet::{ tt_default_parts_v2, tt_error_token, }; -use crate::{PageIndex, SupportsOf}; - /// Errors related to the solution feasibility checks. #[derive(Debug, Eq, PartialEq, codec::Encode, codec::Decode, scale_info::TypeInfo, Clone)] pub enum FeasibilityError { @@ -136,7 +135,9 @@ impl Default for Status { /// Pointer to the current valid solution of `QueuedSolution`. #[derive(Encode, Decode, scale_info::TypeInfo, Clone, Copy, MaxEncodedLen, Debug, PartialEq)] pub enum SolutionPointer { + /// Solution pointer variant `X`. X, + /// Solution pointer variant `Y`. Y, } @@ -147,6 +148,7 @@ impl Default for SolutionPointer { } impl SolutionPointer { + /// Returns the other variant of the current solution pointer in storage. pub fn other(&self) -> SolutionPointer { match *self { SolutionPointer::X => SolutionPointer::Y, @@ -166,6 +168,7 @@ pub struct PartialBackings { } impl sp_npos_elections::Backings for PartialBackings { + /// Returns the total backings of the winner. fn total(&self) -> ExtendedBalance { self.total } From bf67a3ea112ef9c0bd2e4455965f00450c1d6824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Fri, 18 Oct 2024 18:57:18 +0200 Subject: [PATCH 012/169] adds prdoc --- Cargo.lock | 2 +- prdoc/pr_6034.prdoc | 27 +++++++++++++++++++ .../election-provider-multi-block/Cargo.toml | 2 +- 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 prdoc/pr_6034.prdoc diff --git a/Cargo.lock b/Cargo.lock index 6453bdd43feb5..230adcc51494e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11477,7 +11477,7 @@ dependencies = [ [[package]] name = "pallet-election-provider-multi-block" -version = "4.0.0-dev" +version = "1.0.0" dependencies = [ "frame-benchmarking", "frame-election-provider-support", diff --git a/prdoc/pr_6034.prdoc b/prdoc/pr_6034.prdoc new file mode 100644 index 0000000000000..68ddea76f330c --- /dev/null +++ b/prdoc/pr_6034.prdoc @@ -0,0 +1,27 @@ +title: Adds multi-block election pallet and multi-block types + +doc: + - audience: Runtime Dev + description: | + This PR adds the `election-provider-multi-block` (EPM-MB) pallet, which is the multi-block + variant of the `election-provider-multi-phase` (EPM) pallet. In addition, it refactors the + types and structs required to run an election and updates the EPM, staking pallet and all + dependent pallets to use the multi-block types. + +crates: + - name: frame-election-provider-support + bump: major + - name: pallet-election-provider-multi-phase + bump: major + - name: pallet-election-provider-multi-block + bump: major + - name: pallet-staking + bump: major + - name: pallet-fast-unstake + bump: minor + - name: pallet-delegated-staking + bump: minor + - name: sp-npos-election + bump: major + - name: sp-staking + bump: major diff --git a/substrate/frame/election-provider-multi-block/Cargo.toml b/substrate/frame/election-provider-multi-block/Cargo.toml index e665a714008b5..705161b77ba0f 100644 --- a/substrate/frame/election-provider-multi-block/Cargo.toml +++ b/substrate/frame/election-provider-multi-block/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-election-provider-multi-block" -version = "4.0.0-dev" +version = "1.0.0" authors.workspace = true edition.workspace = true license = "Apache-2.0" From 345840d14617426dca93529fd866a072ae1154b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Fri, 18 Oct 2024 19:00:03 +0200 Subject: [PATCH 013/169] fixes prdoc --- prdoc/pr_6034.prdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prdoc/pr_6034.prdoc b/prdoc/pr_6034.prdoc index 68ddea76f330c..0b7ea15dc2597 100644 --- a/prdoc/pr_6034.prdoc +++ b/prdoc/pr_6034.prdoc @@ -21,7 +21,7 @@ crates: bump: minor - name: pallet-delegated-staking bump: minor - - name: sp-npos-election + - name: sp-npos-elections bump: major - name: sp-staking bump: major From 52dcee9652b6874264aa335a622c3ec282b9d92e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Sat, 19 Oct 2024 00:16:35 +0200 Subject: [PATCH 014/169] removes from all pallets --- .../src/signed/mod.rs | 6 ++- .../src/unsigned/mod.rs | 2 +- .../src/unsigned/weights.rs | 37 +++++++------------ .../src/verifier/impls.rs | 14 ++++--- .../src/weights.rs | 37 +++++++------------ 5 files changed, 43 insertions(+), 53 deletions(-) diff --git a/substrate/frame/election-provider-multi-block/src/signed/mod.rs b/substrate/frame/election-provider-multi-block/src/signed/mod.rs index 3ec1c824597d0..8246511b107da 100644 --- a/substrate/frame/election-provider-multi-block/src/signed/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/signed/mod.rs @@ -99,7 +99,7 @@ pub struct SubmissionMetadata { deposit: BalanceOf, } -#[frame_support::pallet(dev_mode)] +#[frame_support::pallet] pub mod pallet { use core::marker::PhantomData; @@ -479,6 +479,7 @@ pub mod pallet { /// /// The scores must be kept sorted in the `SortedScores` storage map. #[pallet::call_index(1)] + #[pallet::weight(Weight::default())] pub fn register(origin: OriginFor, claimed_score: ElectionScore) -> DispatchResult { let who = ensure_signed(origin)?; @@ -507,6 +508,7 @@ pub mod pallet { /// TODO: for security reasons, we have to ensure that ALL submitters "space" to /// submit their pages and be verified. #[pallet::call_index(2)] + #[pallet::weight(Weight::default())] pub fn submit_page( origin: OriginFor, page: PageIndex, @@ -541,6 +543,7 @@ pub mod pallet { /// before the signed phase ends. This may end up depriving other honest miners from /// registering their solution. #[pallet::call_index(3)] + #[pallet::weight(Weight::default())] pub fn bail(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; @@ -570,6 +573,7 @@ pub mod pallet { /// A successfull call will result in a reward that is taken from the cleared submission /// deposit and the return of the call fees. #[pallet::call_index(4)] + #[pallet::weight(Weight::default())] pub fn force_clear_submission( origin: OriginFor, submitter: T::AccountId, diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs b/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs index 55479cfadbedc..ef1180018b311 100644 --- a/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs @@ -88,7 +88,7 @@ pub use pallet::{ __substrate_validate_unsigned_check, tt_default_parts, tt_default_parts_v2, tt_error_token, }; -#[frame_support::pallet(dev_mode)] +#[frame_support::pallet] pub(crate) mod pallet { use super::*; diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/weights.rs b/substrate/frame/election-provider-multi-block/src/unsigned/weights.rs index fc15d96bbae4e..a593dfc8c7d9c 100644 --- a/substrate/frame/election-provider-multi-block/src/unsigned/weights.rs +++ b/substrate/frame/election-provider-multi-block/src/unsigned/weights.rs @@ -1,28 +1,19 @@ +// This file is part of Substrate. -//! Autogenerated weights for `pallet_epm_unsigned` -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-08-02, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `gpestanas-MBP.lan`, CPU: `` -//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: 1024 +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 -// Executed Command: -// /Users/gpestana/cargo_target/debug/staking-node -// benchmark -// pallet -// --chain -// dev -// --pallet -// pallet-epm-unsigned -// --extrinsic -// * -// --steps -// 2 -// --repeat -// 1 -// --output -// unsigned_weights.rs +// 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. #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] diff --git a/substrate/frame/election-provider-multi-block/src/verifier/impls.rs b/substrate/frame/election-provider-multi-block/src/verifier/impls.rs index e2d0085dedbee..48491cdacae5e 100644 --- a/substrate/frame/election-provider-multi-block/src/verifier/impls.rs +++ b/substrate/frame/election-provider-multi-block/src/verifier/impls.rs @@ -29,7 +29,7 @@ use frame_support::{ use sp_runtime::{traits::Zero, Perbill}; use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; -#[frame_support::pallet(dev_mode)] +#[frame_support::pallet] pub(crate) mod pallet { use super::*; use frame_support::pallet_prelude::{ValueQuery, *}; @@ -282,7 +282,8 @@ pub(crate) mod pallet { // For unsigned page solutions only. #[pallet::storage] - pub(crate) type RemainingUnsignedPages = StorageValue<_, Vec, ValueQuery>; + pub(crate) type RemainingUnsignedPages = + StorageValue<_, BoundedVec, ValueQuery>; #[pallet::pallet] pub struct Pallet(PhantomData); @@ -471,9 +472,12 @@ impl Pallet { match crate::Pallet::::current_phase() { // reset remaining unsigned pages after snapshot is created. crate::Phase::Snapshot(page) if page == crate::Pallet::::lsp() => { - RemainingUnsignedPages::::put( - (crate::Pallet::::lsp()..crate::Pallet::::msp() + 1).collect::>(), - ); + RemainingUnsignedPages::::mutate(|remaining| { + *remaining = BoundedVec::truncate_from( + (crate::Pallet::::lsp()..crate::Pallet::::msp() + 1) + .collect::>(), + ); + }); sublog!( debug, diff --git a/substrate/frame/election-provider-multi-block/src/weights.rs b/substrate/frame/election-provider-multi-block/src/weights.rs index ce764ca6f80b8..f11e82e578b3f 100644 --- a/substrate/frame/election-provider-multi-block/src/weights.rs +++ b/substrate/frame/election-provider-multi-block/src/weights.rs @@ -1,28 +1,19 @@ +// This file is part of Substrate. -//! Autogenerated weights for `pallet_epm_core` -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-08-02, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `gpestanas-MBP.lan`, CPU: `` -//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: 1024 +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 -// Executed Command: -// /Users/gpestana/cargo_target/debug/staking-node -// benchmark -// pallet -// --chain -// dev -// --pallet -// pallet-epm-core -// --extrinsic -// * -// --steps -// 2 -// --repeat -// 1 -// --output -// core_weights.rs +// 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. #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] From 6d506e2f6c40bbe6f3ea019a357d26b5b26c1016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Sat, 19 Oct 2024 01:06:35 +0200 Subject: [PATCH 015/169] Adds new tests for verifier pallet --- .../src/mock/mod.rs | 5 +++ .../src/signed/mod.rs | 5 +-- .../src/unsigned/mod.rs | 4 +- .../src/verifier/impls.rs | 7 +-- .../src/verifier/mod.rs | 5 ++- .../src/verifier/tests.rs | 43 ++++++++++++++++++- 6 files changed, 58 insertions(+), 11 deletions(-) diff --git a/substrate/frame/election-provider-multi-block/src/mock/mod.rs b/substrate/frame/election-provider-multi-block/src/mock/mod.rs index d1b82d3a18877..06cfc42e50190 100644 --- a/substrate/frame/election-provider-multi-block/src/mock/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/mock/mod.rs @@ -323,6 +323,11 @@ impl ExtBuilder { self } + pub(crate) fn solution_improvements_threshold(self, threshold: Perbill) -> Self { + SolutionImprovementThreshold::set(threshold); + self + } + pub(crate) fn verifier() -> Self { ExtBuilder { with_verifier: true } } diff --git a/substrate/frame/election-provider-multi-block/src/signed/mod.rs b/substrate/frame/election-provider-multi-block/src/signed/mod.rs index 8246511b107da..6426d0cab21e5 100644 --- a/substrate/frame/election-provider-multi-block/src/signed/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/signed/mod.rs @@ -292,10 +292,9 @@ pub mod pallet { debug_assert!(!SubmissionMetadataStorage::::contains_key(round, who)); // the submission score must be higher than the minimum trusted score. Note that since - // there is no queued solution yet, the check is performed against the minimum score - // only. TODO: consider rename `ensure_score_improves`. + // there is no queued solution yet, the check is performed against the minimum score. ensure!( - ::ensure_score_improves(metadata.claimed_score), + ::ensure_score_quality(metadata.claimed_score), Error::::SubmissionScoreTooLow, ); diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs b/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs index ef1180018b311..bd28330000853 100644 --- a/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs @@ -313,7 +313,7 @@ impl Pallet { })?; // submit page only if full score improves the current queued score. - if ::ensure_score_improves(full_score) { + if ::ensure_score_quality(full_score) { OffchainWorkerMiner::::submit_paged_call( page, partial_solution, @@ -350,7 +350,7 @@ impl Pallet { ensure!(page <= crate::Pallet::::msp(), ()); // full solution score check. - ensure!(::ensure_score_improves(*claimed_full_score), ()); + ensure!(::ensure_score_quality(*claimed_full_score), ()); Ok(()) } diff --git a/substrate/frame/election-provider-multi-block/src/verifier/impls.rs b/substrate/frame/election-provider-multi-block/src/verifier/impls.rs index 48491cdacae5e..c118debfd9074 100644 --- a/substrate/frame/election-provider-multi-block/src/verifier/impls.rs +++ b/substrate/frame/election-provider-multi-block/src/verifier/impls.rs @@ -146,7 +146,6 @@ pub(crate) mod pallet { Self::mutate_checked(|| { QueuedValidVariant::::mutate(|v| *v = v.other()); QueuedSolutionScore::::put(score); - // TODO: should clear the invalid backings too? }) } @@ -324,7 +323,7 @@ impl Verifier for Pallet { QueuedSolution::::queued_score() } - fn ensure_score_improves(claimed_score: ElectionScore) -> bool { + fn ensure_score_quality(claimed_score: ElectionScore) -> bool { Self::ensure_score_quality(claimed_score).is_ok() } @@ -639,6 +638,8 @@ impl Pallet { outcome } + /// Checks if `score` improves the current queued score by `T::SolutionImprovementThreshold` and + /// that it is higher than `MinimumScore`. pub fn ensure_score_quality(score: ElectionScore) -> Result<(), FeasibilityError> { let is_improvement = ::queued_score().map_or(true, |best_score| { score.strict_threshold_better(best_score, T::SolutionImprovementThreshold::get()) @@ -647,8 +648,8 @@ impl Pallet { let is_greater_than_min_trusted = MinimumScore::::get() .map_or(true, |min_score| score.strict_threshold_better(min_score, Perbill::zero())); - ensure!(is_greater_than_min_trusted, FeasibilityError::ScoreTooLow); + ensure!(is_greater_than_min_trusted, FeasibilityError::ScoreTooLow); Ok(()) } diff --git a/substrate/frame/election-provider-multi-block/src/verifier/mod.rs b/substrate/frame/election-provider-multi-block/src/verifier/mod.rs index 04d631af68d8c..47f73bbed861b 100644 --- a/substrate/frame/election-provider-multi-block/src/verifier/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/verifier/mod.rs @@ -198,8 +198,9 @@ pub trait Verifier { /// Returns `None` if not score is queued. fn queued_score() -> Option; - /// Check if a claimed score improves the current queued score. - fn ensure_score_improves(claimed_score: ElectionScore) -> bool; + /// Check if a claimed score improves the current queued score or if it is higher than a + /// potential minimum score. + fn ensure_score_quality(claimed_score: ElectionScore) -> bool; /// Returns the next missing solution page. fn next_missing_solution_page() -> Option; diff --git a/substrate/frame/election-provider-multi-block/src/verifier/tests.rs b/substrate/frame/election-provider-multi-block/src/verifier/tests.rs index 66b4696edf4a7..f0e3663883271 100644 --- a/substrate/frame/election-provider-multi-block/src/verifier/tests.rs +++ b/substrate/frame/election-provider-multi-block/src/verifier/tests.rs @@ -20,8 +20,49 @@ use crate::{ verifier::{impls::pallet::*, *}, Phase, }; -use frame_support::assert_noop; +use frame_support::{assert_err, assert_noop, assert_ok}; use sp_npos_elections::ElectionScore; +use sp_runtime::Perbill; + +#[test] +fn ensure_score_quality_works() { + ExtBuilder::default() + .solution_improvements_threshold(Perbill::from_percent(10)) + .build_and_execute(|| { + assert_eq!(MinimumScore::::get(), Default::default()); + assert!( as Verifier>::queued_score().is_none()); + + // if minimum score is not set and there's no queued score, any score has quality. + assert_ok!(Pallet::::ensure_score_quality(ElectionScore { + minimal_stake: 1, + sum_stake: 1, + sum_stake_squared: 1 + })); + + // if minimum score is set, the score being evaluated must be higher than the minimum + // score. + MinimumScore::::set( + ElectionScore { minimal_stake: 10, sum_stake: 20, sum_stake_squared: 300 }.into(), + ); + + // score is not higher than minimum score. + assert_err!( + Pallet::::ensure_score_quality(ElectionScore { + minimal_stake: 1, + sum_stake: 1, + sum_stake_squared: 1, + }), + FeasibilityError::ScoreTooLow + ); + + // if score improves the current one by the minimum solution improvement, we're gold. + assert_ok!(Pallet::::ensure_score_quality(ElectionScore { + minimal_stake: 11, + sum_stake: 22, + sum_stake_squared: 300 + })); + }) +} mod solution { use super::*; From 430532a4945519997a33ce20888b69ade9bda993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Sat, 19 Oct 2024 14:42:19 +0200 Subject: [PATCH 016/169] implements hold logic in signed pallet; finishes implementing bail call --- .../src/mock/mod.rs | 10 +- .../src/signed/mod.rs | 165 ++++++++++++------ .../src/signed/tests.rs | 84 ++++++++- 3 files changed, 199 insertions(+), 60 deletions(-) diff --git a/substrate/frame/election-provider-multi-block/src/mock/mod.rs b/substrate/frame/election-provider-multi-block/src/mock/mod.rs index 06cfc42e50190..e4c5462e8debc 100644 --- a/substrate/frame/election-provider-multi-block/src/mock/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/mock/mod.rs @@ -25,7 +25,7 @@ pub use staking::*; use crate::{ self as epm, - signed::{self as signed_pallet}, + signed::{self as signed_pallet, HoldReason}, unsigned::{ self as unsigned_pallet, miner::{self, Miner, MinerError, OffchainWorkerMiner}, @@ -33,7 +33,7 @@ use crate::{ verifier::{self as verifier_pallet}, Config, *, }; -use frame_support::{derive_impl, pallet_prelude::*, parameter_types}; +use frame_support::{derive_impl, pallet_prelude::*, parameter_types, traits::fungible::InspectHold}; use parking_lot::RwLock; use sp_runtime::{ offchain::{ @@ -544,8 +544,12 @@ pub fn clear_snapshot() { let _ = crate::TargetSnapshot::::kill(); } +/// Returns the free balance, and the total on-hold for the election submissions. pub fn balances(who: AccountId) -> (Balance, Balance) { - (Balances::free_balance(who), Balances::reserved_balance(who)) + ( + Balances::free_balance(who), + Balances::balance_on_hold(&HoldReason::ElectionSolutionSubmission.into(), &who), + ) } pub fn mine_full(pages: PageIndex) -> Result, MinerError> { diff --git a/substrate/frame/election-provider-multi-block/src/signed/mod.rs b/substrate/frame/election-provider-multi-block/src/signed/mod.rs index 6426d0cab21e5..f1eda029f97d0 100644 --- a/substrate/frame/election-provider-multi-block/src/signed/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/signed/mod.rs @@ -62,10 +62,11 @@ use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ traits::{ fungible::{ - hold::Balanced as FnBalanced, Credit, Inspect as FnInspect, MutateHold as FnMutateHold, + hold::Balanced as FnBalanced, Credit, Inspect as FnInspect, + InspectHold as FnInspectHold, MutateHold as FnMutateHold, }, tokens::Precision, - Defensive, + Defensive, DefensiveSaturating, }, RuntimeDebugNoBound, }; @@ -85,6 +86,16 @@ type BalanceOf = <::Currency as FnInspect>>::Bala /// Alias for the pallet's hold credit type. pub type CreditOf = Credit, ::Currency>; +/// Release strategy for currency held by this pallet. +pub(crate) enum ReleaseStrategy { + /// Releases all currency. + All, + /// Releases only the base deposit, + BaseDeposit, + /// Releases only the pages deposit. + PageDeposit, +} + /// Metadata of a registered submission. #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Default, RuntimeDebugNoBound)] #[cfg_attr(test, derive(frame_support::PartialEqNoBound, frame_support::EqNoBound))] @@ -108,7 +119,7 @@ pub mod pallet { use super::*; use frame_support::{ pallet_prelude::{ValueQuery, *}, - traits::{Defensive, EstimateCallFee, OnUnbalanced}, + traits::{tokens::Fortitude, Defensive, EstimateCallFee, OnUnbalanced}, Twox64Concat, }; use frame_system::{ @@ -127,7 +138,8 @@ pub mod pallet { /// The currency type. type Currency: FnMutateHold - + FnBalanced; + + FnBalanced + + FnInspectHold; /// Something that can predict the fee of a call. Used to sensibly distribute rewards. type EstimateCallFee: EstimateCallFee, BalanceOf>; @@ -137,7 +149,6 @@ pub mod pallet { /// Something that calculates the signed base deposit based on the size of the current /// queued solution proposals. - /// TODO: rename to `Deposit` or other? type DepositBase: Convert>; /// Per-page deposit for a signed solution. @@ -229,6 +240,8 @@ pub mod pallet { SubmissionScoreTooLow, /// Bad timing for force clearing a stored submission. CannotClear, + /// Error releasing held funds. + CannotReleaseFunds, } /// Wrapper for signed submissions. @@ -243,7 +256,12 @@ pub mod pallet { /// ID and round. /// /// Invariants: - /// - TODO + /// - [`SortedScores`] must be strictly sorted or empty. + /// - All registered scores in [`SortedScores`] must be higher than the minimum score. + /// - An entry in [`SortedScores`] for a given round must have an associated entry in + /// [`SubmissionMetadataStorage`]. + /// - For all registered submissions, there is a held deposit that matches that of the + /// submission metadata and the number of submitted pages. pub(crate) struct Submissions(core::marker::PhantomData); impl Submissions { /// Generic mutation helper with checks. @@ -282,7 +300,7 @@ pub mod pallet { round: u32, metadata: SubmissionMetadata, ) -> DispatchResult { - let mut scores = SortedScores::::get(round); + let mut scores = Submissions::::scores_for(round); scores.iter().try_for_each(|(account, _)| -> DispatchResult { ensure!(account != who, Error::::DuplicateRegister); Ok(()) @@ -292,7 +310,8 @@ pub mod pallet { debug_assert!(!SubmissionMetadataStorage::::contains_key(round, who)); // the submission score must be higher than the minimum trusted score. Note that since - // there is no queued solution yet, the check is performed against the minimum score. + // there is no queued solution yet, the check is only performed against the minimum + // score. ensure!( ::ensure_score_quality(metadata.claimed_score), Error::::SubmissionScoreTooLow, @@ -314,7 +333,7 @@ pub mod pallet { Ok(Some((discarded, _s))) => { let _ = SubmissionStorage::::clear_prefix((round, &discarded), u32::MAX, None); - // unreserve deposit + // unreserve full deposit let _ = T::Currency::release_all( &HoldReason::ElectionSolutionSubmission.into(), &who, @@ -327,6 +346,13 @@ pub mod pallet { Err(_) => Err(Error::::SubmissionsQueueFull), }?; + // hold deposit for this submission. + T::Currency::hold( + &HoldReason::ElectionSolutionSubmission.into(), + &who, + metadata.deposit, + )?; + SortedScores::::insert(round, scores); SubmissionMetadataStorage::::insert(round, who, metadata); @@ -355,22 +381,26 @@ pub mod pallet { page: PageIndex, maybe_solution: Option>, ) -> DispatchResult { - ensure!( - crate::Pallet::::current_phase().is_signed(), - Error::::NotAcceptingSubmissions - ); ensure!(page < T::Pages::get(), Error::::BadPageIndex); - ensure!( - SubmissionMetadataStorage::::contains_key(round, who), - Error::::SubmissionNotRegistered - ); + ensure!(Self::metadata_for(round, &who).is_some(), Error::::SubmissionNotRegistered); - // TODO: update the held deposit to account for the paged submission deposit. + let should_hold_extra = + SubmissionStorage::::mutate_exists((round, who, page), |maybe_old_solution| { + let exists = maybe_old_solution.is_some(); + *maybe_old_solution = maybe_solution; - SubmissionStorage::::mutate_exists((round, who, page), |maybe_old_solution| { - *maybe_old_solution = maybe_solution - }); + !exists + }); + + // the deposit per page is held IFF it is a new page being stored. + if should_hold_extra { + T::Currency::hold( + &HoldReason::ElectionSolutionSubmission.into(), + &who, + T::DepositPerPage::get(), + )?; + }; Ok(()) } @@ -388,7 +418,7 @@ pub mod pallet { (round, &submitter), u32::MAX, None, - ); // TODO: handle error. + ); SubmissionMetadataStorage::::take(round, &submitter) .map(|metadata| (submitter, metadata)) @@ -397,23 +427,57 @@ pub mod pallet { }) } - /// Returns the leader submitter for the current round and corresponding claimed score. - pub(crate) fn leader(round: u32) -> Option<(T::AccountId, ElectionScore)> { - SortedScores::::get(round).last().cloned() - } - - /// Returns a submission page for a given round, submitter and page index. - pub(crate) fn get_page( + /// Clear the submission of a registered submission and its correponding pages and release + /// the held deposit based on the `release_strategy`. + /// + /// The held deposit that is not released is burned as a penalty. + pub(crate) fn clear_submission_of( who: &T::AccountId, round: u32, - page: PageIndex, - ) -> Option> { - SubmissionStorage::::get((round, who, page)) + release_strategy: ReleaseStrategy, + ) -> DispatchResult { + let reason = HoldReason::ElectionSolutionSubmission; + + let base_deposit = if let Some(metadata) = Self::metadata_for(round, &who) { + metadata.deposit + } else { + return Err(Error::::SubmissionNotRegistered.into()); + }; + + Self::mutate_checked(round, || { + SubmissionMetadataStorage::::remove(round, who); + let _ = SubmissionStorage::::clear_prefix((round, &who), u32::MAX, None); + }); + + let burn_deposit = match release_strategy { + ReleaseStrategy::All => Zero::zero(), + ReleaseStrategy::BaseDeposit => { + let burn = T::Currency::balance_on_hold(&reason.into(), &who) + .defensive_saturating_sub(base_deposit); + burn + }, + ReleaseStrategy::PageDeposit => base_deposit, + }; + + T::Currency::burn_held( + &reason.into(), + &who, + burn_deposit, + Precision::Exact, + Fortitude::Force, + )?; + + // release remaining. + T::Currency::release_all(&reason.into(), &who, Precision::Exact)?; + + Ok(()) + } + + /// Returns the leader submitter for the current round and corresponding claimed score. + pub(crate) fn leader(round: u32) -> Option<(T::AccountId, ElectionScore)> { + Submissions::::scores_for(round).last().cloned() } - } - #[allow(dead_code)] - impl Submissions { /// Returns the metadata of a submitter for a given account. pub(crate) fn metadata_for( round: u32, @@ -430,17 +494,15 @@ pub mod pallet { } /// Returns the submission of a submitter for a given round and page. - pub(crate) fn submission_for( + pub(crate) fn page_submission_for( who: T::AccountId, round: u32, page: PageIndex, ) -> Option> { SubmissionStorage::::get((round, who, page)) } - } - #[cfg(debug_assertions)] - impl Submissions { + #[cfg(debug_assertions)] fn sanity_check_round(_round: u32) -> Result<(), &'static str> { // TODO Ok(()) @@ -453,12 +515,11 @@ pub mod pallet { claimed_score: ElectionScore, round: u32, ) -> DispatchResult { + // base deposit depends on the number of submissions for the current `round`. let deposit = T::DepositBase::convert( SubmissionMetadataStorage::::iter_key_prefix(round).count(), ); - T::Currency::hold(&HoldReason::ElectionSolutionSubmission.into(), &who, deposit)?; - let pages: BoundedVec<_, T::Pages> = (0..T::Pages::get()) .map(|_| false) .collect::>() @@ -489,7 +550,7 @@ pub mod pallet { let round = crate::Pallet::::current_round(); ensure!( - !SubmissionMetadataStorage::::contains_key(round, who.clone()), + Submissions::::metadata_for(round, &who).is_none(), Error::::DuplicateRegister ); @@ -535,12 +596,10 @@ pub mod pallet { /// Unregister a submission. /// /// This will fully remove the solution and corresponding metadata from storage and refund - /// the submission deposit. + /// the page submissions deposit only. /// - /// NOTE: should we refund the deposit? there's an attack vector where an attacker can - /// register with a set of very high elections core and then retract all submission just - /// before the signed phase ends. This may end up depriving other honest miners from - /// registering their solution. + /// Note: the base deposit will be burned to prevent the attack where rogue submitters + /// deprive honest submitters submitting a solution. #[pallet::call_index(3)] #[pallet::weight(Weight::default())] pub fn bail(origin: OriginFor) -> DispatchResult { @@ -551,14 +610,10 @@ pub mod pallet { Error::::NotAcceptingSubmissions ); - // TODO - // 1. clear all storage items related to `who` - // 2. return deposit + let round = crate::Pallet::::current_round(); + Submissions::::clear_submission_of(&who, round, ReleaseStrategy::PageDeposit)?; - Self::deposit_event(Event::::Bailed { - round: crate::Pallet::::current_round(), - who, - }); + Self::deposit_event(Event::::Bailed { round, who }); Ok(()) } @@ -644,7 +699,7 @@ impl SolutionDataProvider for Pallet { Submissions::::leader(round).map(|(who, _score)| { sublog!(info, "signed", "returning page {} of leader's {:?} solution", page, who); - Submissions::::get_page(&who, round, page).unwrap_or_default() + Submissions::::page_submission_for(who, round, page).unwrap_or_default() }) } diff --git a/substrate/frame/election-provider-multi-block/src/signed/tests.rs b/substrate/frame/election-provider-multi-block/src/signed/tests.rs index 864a94eb1a8c4..665a45d1c73e7 100644 --- a/substrate/frame/election-provider-multi-block/src/signed/tests.rs +++ b/substrate/frame/election-provider-multi-block/src/signed/tests.rs @@ -19,6 +19,7 @@ use super::*; use crate::{mock::*, verifier::SolutionDataProvider, Phase, Verifier}; use frame_support::{assert_noop, assert_ok, testing_prelude::*}; use sp_npos_elections::ElectionScore; +use sp_runtime::traits::Convert; mod calls { use super::*; @@ -179,7 +180,7 @@ mod calls { )); assert_eq!( - Submissions::::submission_for(10, current_round(), 0), + Submissions::::page_submission_for(10, current_round(), 0), Some(Default::default()), ); @@ -206,6 +207,85 @@ mod calls { ); }) } + + #[test] + fn bail_works() { + ExtBuilder::default().build_and_execute(|| { + // TODO + }) + } + + #[test] + fn force_clear_submission_works() { + ExtBuilder::default().build_and_execute(|| { + // TODO + }) + } +} + +mod deposit { + use super::*; + + #[test] + fn register_submit_bail_deposit_works() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(::Pages::get(), 3); + + roll_to_phase(Phase::Signed); + assert_ok!(assert_snapshots()); + + // expected base deposit with 0 submissions in the queue. + let base_deposit = ::DepositBase::convert(0); + let page_deposit = ::DepositPerPage::get(); + assert!(base_deposit != 0 && page_deposit != 0 && base_deposit != page_deposit); + + // 99 has 100 free balance and 0 held balance for elections. + assert_eq!(balances(99), (100, 0)); + + assert_ok!(SignedPallet::register(RuntimeOrigin::signed(99), Default::default())); + + // free balance and held deposit updated as expected. + assert_eq!(balances(99), (100 - base_deposit, base_deposit)); + + // submit page 2. + assert_ok!(SignedPallet::submit_page( + RuntimeOrigin::signed(99), + 2, + Some(Default::default()) + )); + + // free balance and held deposit updated as expected. + assert_eq!( + balances(99), + (100 - base_deposit - page_deposit, base_deposit + page_deposit) + ); + + // submit remaining pages. + assert_ok!(SignedPallet::submit_page( + RuntimeOrigin::signed(99), + 1, + Some(Default::default()) + )); + assert_ok!(SignedPallet::submit_page( + RuntimeOrigin::signed(99), + 0, + Some(Default::default()) + )); + + // free balance and held deposit updated as expected (ie. base_deposit + Pages * + // page_deposit) + assert_eq!( + balances(99), + (100 - base_deposit - (3 * page_deposit), base_deposit + (3 * page_deposit)) + ); + + // now if 99 bails, all the deposits are released. + assert_ok!(SignedPallet::bail(RuntimeOrigin::signed(99))); + + // the base deposit was burned after bail and all the pages deposit were released. + assert_eq!(balances(99), (100 - base_deposit, 0)); + }) + } } mod solution_data_provider { @@ -281,7 +361,7 @@ mod e2e { )); assert_eq!( - Submissions::::submission_for(10, current_round, page), + Submissions::::page_submission_for(10, current_round, page), Some(solution.clone()) ); } From 068e0fd4a23161df2caa2b4bc88ab53f7e12b12b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Mon, 21 Oct 2024 09:54:00 +0200 Subject: [PATCH 017/169] finishes signed pallet --- .../src/signed/mod.rs | 360 ++++++++++++------ .../src/signed/tests.rs | 20 +- .../src/types.rs | 4 + 3 files changed, 263 insertions(+), 121 deletions(-) diff --git a/substrate/frame/election-provider-multi-block/src/signed/mod.rs b/substrate/frame/election-provider-multi-block/src/signed/mod.rs index f1eda029f97d0..a13c2a105aedc 100644 --- a/substrate/frame/election-provider-multi-block/src/signed/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/signed/mod.rs @@ -17,33 +17,91 @@ //! # Signed sub-pallet //! -//! The main goal of the signed sub-pallet is to keep and manage a list of sorted score commitments -//! and correponding paged solutions during the [`crate::Phase::Signed`]. +//! The main goal of the signed sub-pallet is to manage a solution submissions from list of sorted +//! score commitments and correponding paged solutions during the [`crate::Phase::Signed`] and to +//! implement the [`SolutionDataProvider`] trait which exposes an interface for external entities to +//! fetch data related to signed submissions for the active round. +//! +//! ## Overview +//! +//! The core logic of this pallet is only active during [`Phase::Signed`]. During the signed phase, +//! accounts can register a solution for the current round and submit the solution's pages, one per +//! extrindic call. The main flow is the following: +//! +//! 1. [`Phase::Signed`] is enacted in the parent EPM pallet; +//! 2. Submitters call [`Call::register`] to register a solution with a given claimed score. This +//! pallet ensures that accepted submission registrations (encapsulated as +//! [`SubmissionMetadata`]) are kept sorted by claimed score in the [`SubmissionMetadata`] +//! storage map. This pallet accepts up to [`Config::MaxSubmissions`] active registrations per +//! round. +//! 3. Submitters that have successfully registered, may submit the solution pages through +//! [`Call::submit_page`], one page per call. +//! 4. Submitters may bail from a registered solution by calling [`Call::bail`]. Bailing from a +//! solution registration will result in a partial slash. +//! 5. This pallet implements the trait [`SolutionDataProvider`] which exposes methods for external +//! entities (e.g. verifier pallet) to query the data and metadata of the current best submitted +//! solution. +//! 6. Upon solution verification (performed by an external entity e.g. the verifier pallet), +//! [`SolutionDataProvider::report_result`] can be called to report the verification result of +//! the current best solution. Depending on the result, the corresponding submitter's deposit may +//! be fully slashed or the submitter may be rewarded with [`Config::Reward`]. //! //! Accounts may submit up to [`Config::MaxSubmissions`] score commitments per election round and //! this pallet ensures that the scores are stored under the map `SortedScores` are sorted and keyed //! by the correct round number. //! -//! Each submitter must hold a deposit per submission that is calculated based on the number of -//! pages required for a full submission and the number of submissions in the queue. The deposit is -//! returned in case the claimed score is correct after the solution verification. Note that if a -//! commitment and corresponding solution are not verified during the verification phase, the -//! submitter is not slashed and the deposits returned. +//! ## Reporting the verification result //! //! When the time to evaluate the signed submission comes, the solutions are checked from best to -//! worse, which may result in one of three scenarios: +//! worse. The [`SolutionDataProvider`] trait exposes the submission data and metadata to an +//! external entity that verifies the queued solutions until it accepts one solution (or none). The +//! verifier entity reports the result of the solution verification which may result in one of three +//! scenarios: +//! +//! 1. If the *best* committed score and page submissions are correct, the submitter is rewarded. +//! 2. Any queued score that was not evaluated, the held deposit is fully returned. +//! 3. Any invalid solution results in a 100% slash of the held deposit. +//! +//! ## Submission deposit +//! +//! Each submitter must hold a "base deposit" per submission that is calculated based on the number +//! of the number of submissions in the queue. In addition, for each solution page submitted there +//! is a fixed [`Config::PageDeposit`] deposit held. The held deposit may be returned or slashed at +//! by the end of the round, depending on the following: +//! +//! 1. If a submission is verified and accepted, the deposit is returned. +//! 2. If a submission is verified and not accepted, the whole deposit is slashed. +//! 3. If a submission is not verified, the deposit is returned. +//! 4. Bailing a registration will return the page deposit and burn the base balance. +//! +//! The deposit is burned when all the data from the submitter is cleared through the +//! [`Call::force_clear_submission`]. +//! +//! ## Submission reward //! -//! 1. If the committed score and page submissions are correct, the submitter is rewarded. -//! 2. Any queued score that was not evaluated, the hold deposit is returned. -//! 3. Any invalid solution results in a 100% slash of the hold submission deposit. +//! Exposing [`SolutionDataProvider::report_result`] allows an external verifier to signal whether +//! the current best solution is correct or not. If the solution is correct, the submitter is +//! rewarded and the pallet can start clearing up the state of the current round. //! -//! Once the [`crate::Phase::SignedValidation`] phase starts, the async verifier is notified to -//! start verifying the best queued solution. +//! ## Storage management //! -//! TODO: -//! - Be more efficient with cleaning up the submission storage by e.g. expose an extrinsic that -//! allows anyone to clean up the submissions storage with a small reward from the submission -//! deposit (clean up storage submissions and all corresponding metadata). +//! ### Storage mutations +//! +//! The [`Submissions`] wraps all the mutation and getters related to the sorted scores, metadata +//! and submissions storage types. All the mutations to those storage items *MUST* be performed +//! through [`Submissions`] to leverage the mutate checks and ensure the data consistency of the +//! submissions data. +//! +//! ### Clearing up the storage +//! +//! The [`SortedScores`] of the *active* submissions in a +//! given round. Each of the registered submissions may have one or more associated paged solution +//! stored in [`SubmissionsStorage`] and its corresponding [`SubmissionMetadata`]. +//! +//! This pallet never implicitly clears either the metadata or the paged submissions storage data. +//! The data is kept in storage until [`Call::force_clear_submission`] extrinsic is called. At that +//! time, the hold deposit may be slashed depending on the state of the `release_strategy` +//! associated with the metadata. #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking; @@ -60,13 +118,14 @@ use crate::{ use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ + defensive, traits::{ fungible::{ hold::Balanced as FnBalanced, Credit, Inspect as FnInspect, - InspectHold as FnInspectHold, MutateHold as FnMutateHold, + InspectHold as FnInspectHold, Mutate as FnMutate, MutateHold as FnMutateHold, }, tokens::Precision, - Defensive, DefensiveSaturating, + Defensive, DefensiveSaturating, Get, }, RuntimeDebugNoBound, }; @@ -87,13 +146,22 @@ type BalanceOf = <::Currency as FnInspect>>::Bala pub type CreditOf = Credit, ::Currency>; /// Release strategy for currency held by this pallet. +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound, PartialEq)] pub(crate) enum ReleaseStrategy { - /// Releases all currency. + /// Releases all held deposit. All, /// Releases only the base deposit, BaseDeposit, /// Releases only the pages deposit. PageDeposit, + /// Burn all held deposit. + BurnAll, +} + +impl Default for ReleaseStrategy { + fn default() -> Self { + Self::All + } } /// Metadata of a registered submission. @@ -108,6 +176,8 @@ pub struct SubmissionMetadata { pages: BoundedVec>, /// The amount held for this submission. deposit: BalanceOf, + /// Current release strategy for this metadata entry. + release_strategy: ReleaseStrategy, } #[frame_support::pallet] @@ -139,7 +209,8 @@ pub mod pallet { /// The currency type. type Currency: FnMutateHold + FnBalanced - + FnInspectHold; + + FnInspectHold + + FnMutate; /// Something that can predict the fee of a call. Used to sensibly distribute rewards. type EstimateCallFee: EstimateCallFee, BalanceOf>; @@ -182,6 +253,12 @@ pub mod pallet { ValueQuery, >; + /// A double-map from (`round`, `account_id`) to a submission metadata of a registered + /// solution commitment. + #[pallet::storage] + type SubmissionMetadataStorage = + StorageDoubleMap<_, Twox64Concat, u32, Twox64Concat, T::AccountId, SubmissionMetadata>; + /// A triple-map from (round, account, page) to a submitted solution. #[pallet::storage] type SubmissionStorage = StorageNMap< @@ -195,12 +272,6 @@ pub mod pallet { OptionQuery, >; - /// A double-map from (`round`, `account_id`) to a submission metadata of a registered - /// solution commitment. - #[pallet::storage] - type SubmissionMetadataStorage = - StorageDoubleMap<_, Twox64Concat, u32, Twox64Concat, T::AccountId, SubmissionMetadata>; - #[pallet::pallet] pub struct Pallet(PhantomData); @@ -331,8 +402,11 @@ pub mod pallet { Ok(None) => Ok(()), // entry inserted but queue was full, clear the discarded submission. Ok(Some((discarded, _s))) => { - let _ = - SubmissionStorage::::clear_prefix((round, &discarded), u32::MAX, None); + let _ = SubmissionStorage::::clear_prefix( + (round, &discarded), + u32::max_value(), + None, + ); // unreserve full deposit let _ = T::Currency::release_all( &HoldReason::ElectionSolutionSubmission.into(), @@ -405,30 +479,45 @@ pub mod pallet { Ok(()) } - /// Clears all the stored data from the leader. + /// Set metadata for submitter. + pub(crate) fn set_metadata( + round: u32, + who: &T::AccountId, + metadata: SubmissionMetadata, + ) { + debug_assert!(SortedScores::::get(round).iter().any(|(account, _)| who == account)); + + Self::mutate_checked(round, || { + SubmissionMetadataStorage::::insert(round, who, metadata); + }); + } + + /// Clears the leader's score data, effectively disabling the submittion. /// - /// Returns the submission metadata of the cleared submission, if any. - pub(crate) fn take_leader_data( + /// Returns the submission metadata of the disabled. + pub(crate) fn take_leader_score( round: u32, - ) -> Option<(T::AccountId, SubmissionMetadata)> { + ) -> Option<(T::AccountId, Option>)> { Self::mutate_checked(round, || { SortedScores::::mutate(round, |scores| scores.pop()).and_then( - |(submitter, _score)| { - let _ = SubmissionStorage::::clear_prefix( - (round, &submitter), - u32::MAX, - None, - ); - - SubmissionMetadataStorage::::take(round, &submitter) - .map(|metadata| (submitter, metadata)) + |(submitter, _)| { + Some((submitter.clone(), Self::metadata_for(round, &submitter))) }, ) }) } - /// Clear the submission of a registered submission and its correponding pages and release - /// the held deposit based on the `release_strategy`. + /// Clear the registed metadata of a submission and its score and release the held deposit + /// based on the `release_strategy`. + /// + /// Clearing a submission only clears the metadata and stored score of a solution. The + /// paged submissions must be cleared by explicitly calling + /// [`Call::force_clear_submission`]. + /// + /// Note: the deposit can never be released completely or burned completely since + /// an account may have lingering held deposit from previous or subsequent rounds. Thus, the + /// amount to release and burn must always be calculated explicitly based on the round's + /// metadata and release strategy. /// /// The held deposit that is not released is burned as a penalty. pub(crate) fn clear_submission_of( @@ -438,37 +527,41 @@ pub mod pallet { ) -> DispatchResult { let reason = HoldReason::ElectionSolutionSubmission; + // calculates current base held deposit for this round, if any. let base_deposit = if let Some(metadata) = Self::metadata_for(round, &who) { metadata.deposit } else { return Err(Error::::SubmissionNotRegistered.into()); }; + // calculates current held page deposit for this round. + let page_deposit = T::DepositPerPage::get().defensive_saturating_mul( + Submissions::::page_count_submission_for(round, who).into(), + ); + Self::mutate_checked(round, || { SubmissionMetadataStorage::::remove(round, who); - let _ = SubmissionStorage::::clear_prefix((round, &who), u32::MAX, None); + SortedScores::::get(round).retain(|(submitter, _)| submitter != who); }); - let burn_deposit = match release_strategy { - ReleaseStrategy::All => Zero::zero(), - ReleaseStrategy::BaseDeposit => { - let burn = T::Currency::balance_on_hold(&reason.into(), &who) - .defensive_saturating_sub(base_deposit); - burn - }, - ReleaseStrategy::PageDeposit => base_deposit, + let (burn, release) = match release_strategy { + ReleaseStrategy::All => + (Zero::zero(), base_deposit.defensive_saturating_add(page_deposit)), + ReleaseStrategy::BurnAll => + (base_deposit.defensive_saturating_add(page_deposit), Zero::zero()), + ReleaseStrategy::BaseDeposit => (page_deposit, base_deposit), + ReleaseStrategy::PageDeposit => (base_deposit, page_deposit), }; - T::Currency::burn_held( - &reason.into(), - &who, - burn_deposit, - Precision::Exact, - Fortitude::Force, - )?; + T::Currency::burn_held(&reason.into(), who, burn, Precision::Exact, Fortitude::Force)?; - // release remaining. - T::Currency::release_all(&reason.into(), &who, Precision::Exact)?; + T::Currency::release(&reason.into(), who, release, Precision::Exact)?; + + // clear the submission metadata for `who` in `round`. May be a noop. + Self::mutate_checked(round, || { + let _ = SubmissionMetadataStorage::::remove(round, who); + SortedScores::::get(round).retain(|(submitter, _)| submitter != who); + }); Ok(()) } @@ -495,13 +588,17 @@ pub mod pallet { /// Returns the submission of a submitter for a given round and page. pub(crate) fn page_submission_for( - who: T::AccountId, round: u32, + who: T::AccountId, page: PageIndex, ) -> Option> { SubmissionStorage::::get((round, who, page)) } + pub(crate) fn page_count_submission_for(round: u32, who: &T::AccountId) -> u32 { + SubmissionStorage::::iter_key_prefix((round, who)).count() as u32 + } + #[cfg(debug_assertions)] fn sanity_check_round(_round: u32) -> Result<(), &'static str> { // TODO @@ -526,7 +623,13 @@ pub mod pallet { .try_into() .expect("bounded vec constructed from bound; qed."); - let metadata = SubmissionMetadata { pages, claimed_score, deposit }; + let metadata = SubmissionMetadata { + pages, + claimed_score, + deposit, + // new submissions should receive back all held deposit. + release_strategy: ReleaseStrategy::All, + }; let _ = Submissions::::try_register(&who, round, metadata)?; Ok(()) @@ -564,9 +667,6 @@ pub mod pallet { /// /// To submit a solution page successfull, the submitter must have registered the /// commitment before. - /// - /// TODO: for security reasons, we have to ensure that ALL submitters "space" to - /// submit their pages and be verified. #[pallet::call_index(2)] #[pallet::weight(Weight::default())] pub fn submit_page( @@ -618,74 +718,74 @@ pub mod pallet { Ok(()) } - /// Force clean submissions storage. + /// Force clean submissions storage for a given (`sumitter`, `round`) tuple. /// - /// Allows any account to receive a reward for requesting the submission storage and - /// corresponding metadata to be cleaned. This extrinsic will fail if the signed or signed - /// validated phases are active to prevent disruption in the election progress. - /// - /// A successfull call will result in a reward that is taken from the cleared submission - /// deposit and the return of the call fees. + /// This pallet expects that submitted pages for `round` may exist IFF a corresponding + /// metadata exists. #[pallet::call_index(4)] #[pallet::weight(Weight::default())] pub fn force_clear_submission( origin: OriginFor, + round: u32, submitter: T::AccountId, - ) -> DispatchResult { + ) -> DispatchResultWithPostInfo { let _who = ensure_signed(origin); - // prevent cleaning up submissions storage during the signed and signed validation - // phase. - ensure!( - !crate::Pallet::::current_phase().is_signed() && - !crate::Pallet::::current_phase().is_signed_validation_open_at(None), - Error::::CannotClear, - ); + // force clearing submissions may happen only during phase off. + ensure!(crate::Pallet::::current_phase().is_off(), Error::::CannotClear); + + if let Some(metadata) = Submissions::::metadata_for(round, &submitter) { + Submissions::::mutate_checked(round, || { + // clear submission metadata from submitter for `round`. + let _ = Submissions::::clear_submission_of( + &submitter, + round, + metadata.release_strategy, + ); + + // clear all pages from submitter in `round`. + let _ = SubmissionStorage::::clear_prefix( + (round, &submitter), + u32::max_value(), + None, + ); + }); + } else { + debug_assert!( + Submissions::::page_count_submission_for(round, &submitter).is_zero() + ); - // TODO: - // 1. clear the submission, if it exists - // 2. clear the submission metadata - // 3. reward caller as a portions of the submittion's deposit - let reward = Default::default(); - // 4. return fees. + return Err(Error::::CannotClear.into()) + } Self::deposit_event(Event::::SubmissionCleared { round: crate::Pallet::::current_round(), submitter, - reward, + reward: None, }); - Ok(()) + Ok(Pays::No.into()) } } #[pallet::hooks] impl Hooks> for Pallet { /// The `on_initialize` signals the [`AsyncVerifier`] whenever it should start or stop the - /// asynchronous verification of the stored submissions. + /// asynchronous verification of stored submissions. /// /// - Start async verification at the beginning of the [`crate::Phase::SignedValidation`]. - /// - Stopns async verification at the beginning of the [`crate::Phase::Unsigned`]. + /// - Stops async verification at the beginning of the [`crate::Phase::Unsigned`]. fn on_initialize(now: BlockNumberFor) -> Weight { - // TODO: match if crate::Pallet::::current_phase().is_signed_validation_open_at(Some(now)) { + sublog!(debug, "signed", "signed validation phase started, signaling the verifier to start async verifiacton."); let _ = ::start().defensive(); }; if crate::Pallet::::current_phase().is_unsigned_open_at(now) { - sublog!(info, "signed", "signed validation phase ended, signaling the verifier."); + sublog!(debug, "signed", "signed validation phase ended, signaling the verifier to stop async verifiacton."); ::stop(); } - if crate::Pallet::::current_phase() == crate::Phase::Off { - sublog!(info, "signed", "clear up storage for pallets."); - - // TODO: optimize. - let _ = SubmissionMetadataStorage::::clear(u32::MAX, None); - let _ = SubmissionStorage::::clear(u32::MAX, None); - let _ = SortedScores::::clear(u32::MAX, None); - } - Weight::default() } } @@ -694,40 +794,62 @@ pub mod pallet { impl SolutionDataProvider for Pallet { type Solution = SolutionOf; + /// Returns a paged solution of the *best* solution in the queue. fn get_paged_solution(page: PageIndex) -> Option { let round = crate::Pallet::::current_round(); Submissions::::leader(round).map(|(who, _score)| { sublog!(info, "signed", "returning page {} of leader's {:?} solution", page, who); - Submissions::::page_submission_for(who, round, page).unwrap_or_default() + Submissions::::page_submission_for(round, who, page).unwrap_or_default() }) } + /// Returns the score of the *best* solution in the queueu. fn get_score() -> Option { let round = crate::Pallet::::current_round(); Submissions::::leader(round).map(|(_who, score)| score) } + /// Called by an external entity to report a verification result of the current *best* + /// solution. + /// + /// If the verification is rejected, update the leader's metadata to be slashed (i.e. set + /// release strategy to [`ReleaseStrategy::BurnAll`] in the leader's metadata). If successful + /// (represented by the variant [``VerificationResult::Queued]), reward the submitter and + /// signal the verifier to stop the async election verification. fn report_result(result: VerificationResult) { let round = crate::Pallet::::current_round(); - match result { - VerificationResult::Queued => {}, - VerificationResult::Rejected => { - if let Some((_offender, _metadata)) = Submissions::::take_leader_data(round) { - // TODO: slash offender - } else { - // no signed submission in storage, signal async verifier to stop and move on. - let _ = ::stop(); + + let (leader, mut metadata) = + if let Some((leader, maybe_metadata)) = Submissions::::take_leader_score(round) { + let metadata = match maybe_metadata { + Some(m) => m, + None => { + defensive!("unexpected: leader with inconsistent data (no metadata)."); + return; + }, }; + (leader, metadata) + } else { + // TODO(gpestana): turn into defensive. + sublog!(error, "signed", "unexpected: leader called without active submissions."); + return + }; - if crate::Pallet::::current_phase().is_signed_validation_open_at(None) && - Submissions::::leader(round).is_some() - { - let _ = ::start().defensive(); - } + match result { + VerificationResult::Queued => { + // solution was accepted by the verifier, reward leader and stop async + // verification. + // TODO(gpestana): think better about the reward minting process -- should we keep + // a pot for rewards instead of minting it in staking? + let _ = T::Currency::mint_into(&leader, T::Reward::get()).defensive(); + let _ = ::stop(); }, - VerificationResult::DataUnavailable => { - // signed pallet did not have the required data. + VerificationResult::Rejected | VerificationResult::DataUnavailable => { + // updates metadata release strategy so that all the deposit is burned when the + // leader's data is cleared. + metadata.release_strategy = ReleaseStrategy::BurnAll; + Submissions::::set_metadata(round, &leader, metadata); }, } } diff --git a/substrate/frame/election-provider-multi-block/src/signed/tests.rs b/substrate/frame/election-provider-multi-block/src/signed/tests.rs index 665a45d1c73e7..3346c84cc7dcf 100644 --- a/substrate/frame/election-provider-multi-block/src/signed/tests.rs +++ b/substrate/frame/election-provider-multi-block/src/signed/tests.rs @@ -21,6 +21,11 @@ use frame_support::{assert_noop, assert_ok, testing_prelude::*}; use sp_npos_elections::ElectionScore; use sp_runtime::traits::Convert; +#[test] +fn clear_submission_of_works() { + ExtBuilder::default().build_and_execute(|| {}); +} + mod calls { use super::*; use sp_core::bounded_vec; @@ -43,6 +48,7 @@ mod calls { claimed_score: score, deposit: 10, pages: bounded_vec![false, false, false], + release_strategy: Default::default(), } ); @@ -172,6 +178,9 @@ mod calls { let score = ElectionScore { minimal_stake: 10, ..Default::default() }; assert_ok!(SignedPallet::register(RuntimeOrigin::signed(10), score)); + // 0 pages submitted so far. + assert_eq!(Submissions::::page_count_submission_for(current_round(), &10), 0); + // now submission works since there is a registered commitment. assert_ok!(SignedPallet::submit_page( RuntimeOrigin::signed(10), @@ -180,16 +189,22 @@ mod calls { )); assert_eq!( - Submissions::::page_submission_for(10, current_round(), 0), + Submissions::::page_submission_for(current_round(), 10, 0), Some(Default::default()), ); + // 1 page submitted so far. + assert_eq!(Submissions::::page_count_submission_for(current_round(), &10), 1); + // tries to submit a page out of bounds. assert_noop!( SignedPallet::submit_page(RuntimeOrigin::signed(10), 10, Some(Default::default())), Error::::BadPageIndex, ); + // 1 successful page submitted so far. + assert_eq!(Submissions::::page_count_submission_for(current_round(), &10), 1); + assert_eq!( signed_events(), vec![ @@ -345,6 +360,7 @@ mod e2e { claimed_score, deposit: 10, pages: bounded_vec![false, false, false], + release_strategy: Default::default(), }) ); let expected_scores: BoundedVec<(AccountId, ElectionScore), MaxSubmissions> = @@ -361,7 +377,7 @@ mod e2e { )); assert_eq!( - Submissions::::page_submission_for(10, current_round, page), + Submissions::::page_submission_for(current_round, 10, page), Some(solution.clone()) ); } diff --git a/substrate/frame/election-provider-multi-block/src/types.rs b/substrate/frame/election-provider-multi-block/src/types.rs index 8f8e95d2a46e4..7f4c1d2cefdd2 100644 --- a/substrate/frame/election-provider-multi-block/src/types.rs +++ b/substrate/frame/election-provider-multi-block/src/types.rs @@ -153,6 +153,10 @@ impl Default for Phase { } impl Phase { + pub(crate) fn is_off(&self) -> bool { + matches!(self, Phase::Off) + } + pub(crate) fn is_signed(&self) -> bool { matches!(self, Phase::Signed) } From 385500d0904b7009c899905e33b7fbc634e72d1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 22 Oct 2024 09:29:36 +0200 Subject: [PATCH 018/169] updates inherent call in unsigned phase to new interfaces --- .../integration-tests/src/mock.rs | 13 ++++++++-- .../src/mock/mod.rs | 19 +++++++++++---- .../src/unsigned/miner.rs | 24 +++++++++---------- .../src/unsigned/mod.rs | 4 ++-- 4 files changed, 40 insertions(+), 20 deletions(-) diff --git a/substrate/frame/election-provider-multi-block/integration-tests/src/mock.rs b/substrate/frame/election-provider-multi-block/integration-tests/src/mock.rs index 61ab42251d7c3..66f79c612489f 100644 --- a/substrate/frame/election-provider-multi-block/integration-tests/src/mock.rs +++ b/substrate/frame/election-provider-multi-block/integration-tests/src/mock.rs @@ -387,14 +387,23 @@ impl pallet_staking::Config for Runtime { type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; } -impl frame_system::offchain::SendTransactionTypes for Runtime +impl frame_system::offchain::CreateTransactionBase for Runtime where RuntimeCall: From, { - type OverarchingCall = RuntimeCall; + type RuntimeCall = RuntimeCall; type Extrinsic = Extrinsic; } +impl frame_system::offchain::CreateInherent for Runtime +where + RuntimeCall: From, +{ + fn create_inherent(call: Self::RuntimeCall) -> Self::Extrinsic { + Extrinsic::new_bare(call) + } +} + pub struct OnChainSeqPhragmen; parameter_types! { diff --git a/substrate/frame/election-provider-multi-block/src/mock/mod.rs b/substrate/frame/election-provider-multi-block/src/mock/mod.rs index e4c5462e8debc..3a564adaa515c 100644 --- a/substrate/frame/election-provider-multi-block/src/mock/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/mock/mod.rs @@ -33,7 +33,9 @@ use crate::{ verifier::{self as verifier_pallet}, Config, *, }; -use frame_support::{derive_impl, pallet_prelude::*, parameter_types, traits::fungible::InspectHold}; +use frame_support::{ + derive_impl, pallet_prelude::*, parameter_types, traits::fungible::InspectHold, +}; use parking_lot::RwLock; use sp_runtime::{ offchain::{ @@ -206,14 +208,23 @@ impl miner::Config for Runtime { pub type Extrinsic = sp_runtime::testing::TestXt; -impl frame_system::offchain::SendTransactionTypes for Runtime +impl frame_system::offchain::CreateTransactionBase for Runtime where RuntimeCall: From, { - type OverarchingCall = RuntimeCall; + type RuntimeCall = RuntimeCall; type Extrinsic = Extrinsic; } +impl frame_system::offchain::CreateInherent for Runtime +where + RuntimeCall: From, +{ + fn create_inherent(call: Self::RuntimeCall) -> Self::Extrinsic { + Extrinsic::new_bare(call) + } +} + pub struct ConstDepositBase; impl sp_runtime::traits::Convert for ConstDepositBase { fn convert(_a: usize) -> Balance { @@ -490,7 +501,7 @@ pub fn roll_one_with_ocw(maybe_pool: Option>>) { .into_iter() .map(|uxt| ::decode(&mut &*uxt).unwrap()) .for_each(|xt| { - xt.call.dispatch(frame_system::RawOrigin::None.into()).unwrap(); + xt.function.dispatch(frame_system::RawOrigin::None.into()).unwrap(); }); pool.try_write().unwrap().transactions.clear(); } diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs b/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs index b1ad4f6a2d798..1a1145b280336 100644 --- a/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs +++ b/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs @@ -795,17 +795,17 @@ impl OffchainWorkerMiner { ); let call = Call::submit_page_unsigned { page, solution, partial_score, claimed_full_score }; - frame_system::offchain::SubmitTransaction::>::submit_unsigned_transaction( - call.into(), - ) - .map(|_| { - sublog!( - debug, - "unsigned::ocw-miner", - "miner submitted a solution as an unsigned transaction, page {:?}", - page - ); - }) - .map_err(|_| OffchainMinerError::PoolSubmissionFailed) + let xt = T::create_inherent(call.into()); + + frame_system::offchain::SubmitTransaction::>::submit_transaction(xt) + .map(|_| { + sublog!( + debug, + "unsigned::ocw-miner", + "miner submitted a solution as an unsigned transaction, page {:?}", + page + ); + }) + .map_err(|_| OffchainMinerError::PoolSubmissionFailed) } } diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs b/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs index bd28330000853..8d3b244b588a0 100644 --- a/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs @@ -78,7 +78,7 @@ use frame_support::{ pallet_prelude::{TransactionValidity, ValidTransaction}, traits::Get, }; -use frame_system::{ensure_none, offchain::SendTransactionTypes, pallet_prelude::BlockNumberFor}; +use frame_system::{ensure_none, offchain::CreateInherent, pallet_prelude::BlockNumberFor}; use sp_npos_elections::ElectionScore; use sp_runtime::SaturatedConversion; @@ -97,7 +97,7 @@ pub(crate) mod pallet { #[pallet::config] #[pallet::disable_frame_system_supertrait_check] - pub trait Config: crate::Config + SendTransactionTypes> { + pub trait Config: crate::Config + CreateInherent> { /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; From b4019bb9012f6f14befca10c345f76e77b530b0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 22 Oct 2024 09:56:09 +0200 Subject: [PATCH 019/169] licence header nit --- .../src/verifier/weights.rs | 37 +++++++------------ 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/substrate/frame/election-provider-multi-block/src/verifier/weights.rs b/substrate/frame/election-provider-multi-block/src/verifier/weights.rs index 1fd78c0df43c4..ba9968ec1407b 100644 --- a/substrate/frame/election-provider-multi-block/src/verifier/weights.rs +++ b/substrate/frame/election-provider-multi-block/src/verifier/weights.rs @@ -1,28 +1,19 @@ +// This file is part of Substrate. -//! Autogenerated weights for `pallet_epm_verifier` -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-08-06, STEPS: `3`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `gpestanas-MBP.Home`, CPU: `` -//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 -// Executed Command: -// /Users/gpestana/cargo_target/debug/staking-node -// benchmark -// pallet -// --wasm-execution -// compiled -// --pallet -// pallet-epm-verifier -// --extrinsic -// * -// --steps -// 3 -// --repeat -// 1 -// --output -// epm_verifier_weights.rs +// 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. #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] From 82abba1b285c0084af2ead96896836956870f41d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Thu, 24 Oct 2024 11:12:37 +0200 Subject: [PATCH 020/169] removes epm-mb --- Cargo.lock | 48 - Cargo.toml | 3 - prdoc/pr_6034.prdoc | 10 +- .../election-provider-multi-block/Cargo.toml | 72 - .../election-provider-multi-block/README.md | 1 - .../integration-tests/Cargo.toml | 54 - .../integration-tests/src/lib.rs | 125 -- .../integration-tests/src/mock.rs | 1011 -------------- .../src/benchmarking.rs | 318 ----- .../src/helpers.rs | 331 ----- .../election-provider-multi-block/src/lib.rs | 1193 ----------------- .../src/mock/mod.rs | 624 --------- .../src/mock/staking.rs | 235 ---- .../src/signed/benchmarking.rs | 66 - .../src/signed/mod.rs | 856 ------------ .../src/signed/tests.rs | 405 ------ .../src/types.rs | 261 ---- .../src/unsigned/benchmarking.rs | 88 -- .../src/unsigned/miner.rs | 811 ----------- .../src/unsigned/mod.rs | 357 ----- .../src/unsigned/tests.rs | 210 --- .../src/unsigned/weights.rs | 76 -- .../src/verifier/benchmarking.rs | 315 ----- .../src/verifier/impls.rs | 680 ---------- .../src/verifier/mod.rs | 291 ---- .../src/verifier/tests.rs | 162 --- .../src/verifier/weights.rs | 240 ---- .../src/weights.rs | 179 --- 28 files changed, 3 insertions(+), 9019 deletions(-) delete mode 100644 substrate/frame/election-provider-multi-block/Cargo.toml delete mode 100644 substrate/frame/election-provider-multi-block/README.md delete mode 100644 substrate/frame/election-provider-multi-block/integration-tests/Cargo.toml delete mode 100644 substrate/frame/election-provider-multi-block/integration-tests/src/lib.rs delete mode 100644 substrate/frame/election-provider-multi-block/integration-tests/src/mock.rs delete mode 100644 substrate/frame/election-provider-multi-block/src/benchmarking.rs delete mode 100644 substrate/frame/election-provider-multi-block/src/helpers.rs delete mode 100644 substrate/frame/election-provider-multi-block/src/lib.rs delete mode 100644 substrate/frame/election-provider-multi-block/src/mock/mod.rs delete mode 100644 substrate/frame/election-provider-multi-block/src/mock/staking.rs delete mode 100644 substrate/frame/election-provider-multi-block/src/signed/benchmarking.rs delete mode 100644 substrate/frame/election-provider-multi-block/src/signed/mod.rs delete mode 100644 substrate/frame/election-provider-multi-block/src/signed/tests.rs delete mode 100644 substrate/frame/election-provider-multi-block/src/types.rs delete mode 100644 substrate/frame/election-provider-multi-block/src/unsigned/benchmarking.rs delete mode 100644 substrate/frame/election-provider-multi-block/src/unsigned/miner.rs delete mode 100644 substrate/frame/election-provider-multi-block/src/unsigned/mod.rs delete mode 100644 substrate/frame/election-provider-multi-block/src/unsigned/tests.rs delete mode 100644 substrate/frame/election-provider-multi-block/src/unsigned/weights.rs delete mode 100644 substrate/frame/election-provider-multi-block/src/verifier/benchmarking.rs delete mode 100644 substrate/frame/election-provider-multi-block/src/verifier/impls.rs delete mode 100644 substrate/frame/election-provider-multi-block/src/verifier/mod.rs delete mode 100644 substrate/frame/election-provider-multi-block/src/verifier/tests.rs delete mode 100644 substrate/frame/election-provider-multi-block/src/verifier/weights.rs delete mode 100644 substrate/frame/election-provider-multi-block/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index c94f5df861df4..207fa75e6b8de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11594,27 +11594,6 @@ dependencies = [ "sp-tracing 16.0.0", ] -[[package]] -name = "pallet-election-provider-multi-block" -version = "1.0.0" -dependencies = [ - "frame-benchmarking", - "frame-election-provider-support", - "frame-support", - "frame-system", - "log", - "pallet-balances", - "parity-scale-codec", - "parking_lot 0.12.3", - "scale-info", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-npos-elections", - "sp-runtime 31.0.1", - "sp-std 14.0.0", - "sp-tracing 16.0.0", -] - [[package]] name = "pallet-election-provider-multi-phase" version = "27.0.0" @@ -11671,33 +11650,6 @@ dependencies = [ "substrate-test-utils", ] -[[package]] -name = "pallet-epm-mb-integrity-tests" -version = "1.0.0" -dependencies = [ - "frame-election-provider-support", - "frame-support", - "frame-system", - "log", - "pallet-bags-list", - "pallet-balances", - "pallet-election-provider-multi-block", - "pallet-nomination-pools", - "pallet-session", - "pallet-staking", - "pallet-timestamp", - "parity-scale-codec", - "parking_lot 0.12.3", - "scale-info", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-npos-elections", - "sp-runtime 31.0.1", - "sp-staking", - "sp-std 14.0.0", - "sp-tracing 16.0.0", -] - [[package]] name = "pallet-example-authorization-tx-extension" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 37ccfdf7f56f5..049de32b54cfc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -339,9 +339,7 @@ members = [ "substrate/frame/core-fellowship", "substrate/frame/delegated-staking", "substrate/frame/democracy", - "substrate/frame/election-provider-multi-block", "substrate/frame/election-provider-multi-phase", - "substrate/frame/election-provider-multi-block/integration-tests", "substrate/frame/election-provider-multi-phase/test-staking-e2e", "substrate/frame/election-provider-support", "substrate/frame/election-provider-support/benchmarking", @@ -917,7 +915,6 @@ pallet-default-config-example = { path = "substrate/frame/examples/default-confi pallet-delegated-staking = { path = "substrate/frame/delegated-staking", default-features = false } pallet-democracy = { path = "substrate/frame/democracy", default-features = false } pallet-dev-mode = { path = "substrate/frame/examples/dev-mode", default-features = false } -pallet-election-provider-multi-block = { path = "substrate/frame/election-provider-multi-block", default-features = false } pallet-election-provider-multi-phase = { path = "substrate/frame/election-provider-multi-phase", default-features = false } pallet-election-provider-support-benchmarking = { path = "substrate/frame/election-provider-support/benchmarking", default-features = false } pallet-elections-phragmen = { path = "substrate/frame/elections-phragmen", default-features = false } diff --git a/prdoc/pr_6034.prdoc b/prdoc/pr_6034.prdoc index 0b7ea15dc2597..ba3d14326509f 100644 --- a/prdoc/pr_6034.prdoc +++ b/prdoc/pr_6034.prdoc @@ -1,20 +1,16 @@ -title: Adds multi-block election pallet and multi-block types +title: Adds multi-block election types and refactors current single logic to support it doc: - audience: Runtime Dev description: | - This PR adds the `election-provider-multi-block` (EPM-MB) pallet, which is the multi-block - variant of the `election-provider-multi-phase` (EPM) pallet. In addition, it refactors the - types and structs required to run an election and updates the EPM, staking pallet and all - dependent pallets to use the multi-block types. + This PR adds election types and structs required to run a multi-block election. In additoin, + it EPM, staking pallet and all dependent pallets and logic to use the multi-block types. crates: - name: frame-election-provider-support bump: major - name: pallet-election-provider-multi-phase bump: major - - name: pallet-election-provider-multi-block - bump: major - name: pallet-staking bump: major - name: pallet-fast-unstake diff --git a/substrate/frame/election-provider-multi-block/Cargo.toml b/substrate/frame/election-provider-multi-block/Cargo.toml deleted file mode 100644 index 705161b77ba0f..0000000000000 --- a/substrate/frame/election-provider-multi-block/Cargo.toml +++ /dev/null @@ -1,72 +0,0 @@ -[package] -name = "pallet-election-provider-multi-block" -version = "1.0.0" -authors.workspace = true -edition.workspace = true -license = "Apache-2.0" -homepage = "https://substrate.dev" -repository.workspace = true -description = "FRAME pallet election provider multi-block" -readme = "README.md" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ - "derive", -] } -scale-info = { version = "2.10.0", default-features = false, features = [ - "derive", -] } -log = { version = "0.4.17", default-features = false } - -frame-support = { path = "../support", default-features = false } -frame-system = { path = "../system", default-features = false } -frame-benchmarking = { path = "../benchmarking", default-features = false } - -sp-io = { path = "../../primitives/io", default-features = false } -sp-std = { path = "../../primitives/std", default-features = false } -sp-core = { path = "../../primitives/core", default-features = false } -sp-runtime = { path = "../../primitives/runtime", default-features = false } - -frame-election-provider-support = { default-features = false, path = "../election-provider-support" } -sp-npos-elections = { default-features = false, path = "../../primitives/npos-elections" } - -[dev-dependencies] -sp-tracing = { path = "../../primitives/tracing" } -pallet-balances = { path = "../balances", default-features = false } -parking_lot = "0.12.1" - -[features] -default = ["std"] -std = [ - "codec/std", - "frame-benchmarking/std", - "frame-election-provider-support/std", - "frame-support/std", - "frame-system/std", - "log/std", - "pallet-balances/std", - "scale-info/std", - "sp-core/std", - "sp-io/std", - "sp-npos-elections/std", - "sp-runtime/std", - "sp-std/std", -] - -runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", - "frame-election-provider-support/runtime-benchmarks", - "pallet-balances/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", -] - -try-runtime = [ - "frame-election-provider-support/try-runtime", - "frame-support/try-runtime", - "frame-system/try-runtime", - "pallet-balances/try-runtime", - "sp-runtime/try-runtime", -] diff --git a/substrate/frame/election-provider-multi-block/README.md b/substrate/frame/election-provider-multi-block/README.md deleted file mode 100644 index 9aff2b4bb152a..0000000000000 --- a/substrate/frame/election-provider-multi-block/README.md +++ /dev/null @@ -1 +0,0 @@ -# Election Provider multi-block diff --git a/substrate/frame/election-provider-multi-block/integration-tests/Cargo.toml b/substrate/frame/election-provider-multi-block/integration-tests/Cargo.toml deleted file mode 100644 index 195e001026000..0000000000000 --- a/substrate/frame/election-provider-multi-block/integration-tests/Cargo.toml +++ /dev/null @@ -1,54 +0,0 @@ -[package] -name = "pallet-epm-mb-integrity-tests" -version = "1.0.0" -authors.workspace = true -edition.workspace = true -license = "Apache-2.0" -homepage.workspace = true -repository.workspace = true -description = "FRAME election provider multi block pallet tests with staking pallet, bags-list and session pallets" -publish = false - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dev-dependencies] -parking_lot = { workspace = true, default-features = true } -codec = { features = ["derive"], workspace = true, default-features = true } -scale-info = { features = ["derive"], workspace = true, default-features = true } -log = { workspace = true } - -sp-runtime = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } -sp-staking = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-npos-elections = { workspace = true } -sp-tracing = { workspace = true, default-features = true } - -frame-system = { workspace = true, default-features = true } -frame-support = { workspace = true, default-features = true } -frame-election-provider-support = { workspace = true, default-features = true } - -pallet-election-provider-multi-block = { workspace = true, default-features = true } -pallet-staking = { workspace = true, default-features = true } -pallet-nomination-pools = { workspace = true, default-features = true } -pallet-bags-list = { workspace = true, default-features = true } -pallet-balances = { workspace = true, default-features = true } -pallet-timestamp = { workspace = true, default-features = true } -pallet-session = { workspace = true, default-features = true } - -[features] -try-runtime = [ - "frame-election-provider-support/try-runtime", - "frame-support/try-runtime", - "frame-system/try-runtime", - "pallet-bags-list/try-runtime", - "pallet-balances/try-runtime", - #"pallet-election-provider-multi-block/try-runtime", - "pallet-nomination-pools/try-runtime", - "pallet-session/try-runtime", - "pallet-staking/try-runtime", - "pallet-timestamp/try-runtime", - "sp-runtime/try-runtime", -] diff --git a/substrate/frame/election-provider-multi-block/integration-tests/src/lib.rs b/substrate/frame/election-provider-multi-block/integration-tests/src/lib.rs deleted file mode 100644 index 3cd56def8e066..0000000000000 --- a/substrate/frame/election-provider-multi-block/integration-tests/src/lib.rs +++ /dev/null @@ -1,125 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#![cfg(test)] -mod mock; - -pub(crate) const LOG_TARGET: &str = "integration-tests::epm-staking"; - -use mock::*; - -use frame_election_provider_support::{bounds::ElectionBoundsBuilder, ElectionDataProvider}; - -use frame_support::{assert_ok, traits::UnixTime}; - -// syntactic sugar for logging. -#[macro_export] -macro_rules! log { - ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { - log::$level!( - target: crate::LOG_TARGET, - concat!("🛠️ ", $patter) $(, $values)* - ) - }; -} - -fn log_current_time() { - log!( - info, - "block: {:?}, session: {:?}, era: {:?}, EPM phase: {:?} ts: {:?}", - System::block_number(), - Session::current_index(), - Staking::current_era(), - ElectionProvider::current_phase(), - Timestamp::now() - ); -} - -#[test] -fn block_progression_works() { - let (mut ext, _pool_state, _) = ExtBuilder::default().build_offchainify(); - ext.execute_with(|| {}) -} - -#[test] -fn verify_snapshot() { - ExtBuilder::default().build_and_execute(|| { - assert_eq!(Pages::get(), 3); - - // manually get targets and voters from staking to see the inspect the issue with the - // DataProvider. - let bounds = ElectionBoundsBuilder::default() - .targets_count((TargetSnapshotPerBlock::get() as u32).into()) - .voters_count((VoterSnapshotPerBlock::get() as u32).into()) - .build(); - - assert_ok!(::electable_targets(bounds.targets, 2)); - assert_ok!(::electing_voters(bounds.voters, 2)); - }) -} - -mod staking_integration { - use super::*; - use pallet_election_provider_multi_block::Phase; - - #[test] - fn call_elect_multi_block() { - ExtBuilder::default().build_and_execute(|| { - assert_eq!(Pages::get(), 3); - assert_eq!(ElectionProvider::current_round(), 0); - assert_eq!(Staking::current_era(), Some(0)); - - let export_starts_at = election_prediction() - Pages::get(); - - assert!(Staking::election_data_lock().is_none()); - - // check that the election data provider lock is set during the snapshot phase and - // released afterwards. - roll_to_phase(Phase::Snapshot(Pages::get() - 1), false); - assert!(Staking::election_data_lock().is_some()); - - roll_one(None, false); - assert!(Staking::election_data_lock().is_some()); - roll_one(None, false); - assert!(Staking::election_data_lock().is_some()); - // snapshot phase done, election data lock was released. - roll_one(None, false); - assert_eq!(ElectionProvider::current_phase(), Phase::Signed); - assert!(Staking::election_data_lock().is_none()); - - // last block where phase is waiting for unsignned submissions. - roll_to(election_prediction() - 4, false); - assert_eq!(ElectionProvider::current_phase(), Phase::Unsigned(17)); - - // staking prepares first page of exposures. - roll_to(export_starts_at, false); - assert_eq!(ElectionProvider::current_phase(), Phase::Export(export_starts_at)); - - // staking prepares second page of exposures. - roll_to(election_prediction() - 2, false); - assert_eq!(ElectionProvider::current_phase(), Phase::Export(export_starts_at)); - - // staking prepares third page of exposures. - roll_to(election_prediction() - 1, false); - - // election successfully, round & era progressed. - assert_eq!(ElectionProvider::current_phase(), Phase::Off); - assert_eq!(ElectionProvider::current_round(), 1); - assert_eq!(Staking::current_era(), Some(1)); - }) - } -} diff --git a/substrate/frame/election-provider-multi-block/integration-tests/src/mock.rs b/substrate/frame/election-provider-multi-block/integration-tests/src/mock.rs deleted file mode 100644 index 66f79c612489f..0000000000000 --- a/substrate/frame/election-provider-multi-block/integration-tests/src/mock.rs +++ /dev/null @@ -1,1011 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#![allow(dead_code)] - -use frame_support::{ - assert_ok, parameter_types, traits, - traits::{Hooks, VariantCountOf}, - weights::constants, -}; -use frame_system::EnsureRoot; -use sp_core::{ConstU32, Get}; -use sp_npos_elections::VoteWeight; -use sp_runtime::{ - offchain::{ - testing::{OffchainState, PoolState, TestOffchainExt, TestTransactionPoolExt}, - OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, - }, - testing, - traits::Zero, - transaction_validity, BuildStorage, PerU16, Perbill, Percent, -}; -use sp_staking::{ - offence::{OffenceDetails, OnOffenceHandler}, - EraIndex, SessionIndex, -}; -use std::collections::BTreeMap; - -use codec::Decode; -use frame_election_provider_support::{ - bounds::ElectionBoundsBuilder, onchain, ElectionDataProvider, ExtendedBalance, PageIndex, - SequentialPhragmen, Weight, -}; - -use pallet_election_provider_multi_block::{ - self as epm_core_pallet, - signed::{self as epm_signed_pallet}, - unsigned::{self as epm_unsigned_pallet, miner}, - verifier::{self as epm_verifier_pallet}, - Config, Phase, -}; - -use pallet_staking::StakerStatus; -use parking_lot::RwLock; -use std::sync::Arc; - -use frame_support::derive_impl; - -use crate::{log, log_current_time}; - -pub const INIT_TIMESTAMP: BlockNumber = 30_000; -pub const BLOCK_TIME: BlockNumber = 1000; - -type Block = frame_system::mocking::MockBlockU32; -type Extrinsic = testing::TestXt; -pub(crate) type T = Runtime; - -frame_support::construct_runtime!( - pub enum Runtime { - System: frame_system, - - // EPM core and sub-pallets - ElectionProvider: epm_core_pallet, - VerifierPallet: epm_verifier_pallet, - SignedPallet: epm_signed_pallet, - UnsignedPallet: epm_unsigned_pallet, - - Pools: pallet_nomination_pools, - Staking: pallet_staking, - Balances: pallet_balances, - BagsList: pallet_bags_list, - Session: pallet_session, - Historical: pallet_session::historical, - Timestamp: pallet_timestamp, - } -); - -pub(crate) type AccountId = u64; -pub(crate) type AccountIndex = u32; -pub(crate) type BlockNumber = u32; -pub(crate) type Balance = u64; -pub(crate) type VoterIndex = u16; -pub(crate) type TargetIndex = u16; -pub(crate) type Moment = u32; - -pub type Solver = SequentialPhragmen; - -#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] -impl frame_system::Config for Runtime { - type Block = Block; - type AccountData = pallet_balances::AccountData; -} - -const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); -parameter_types! { - pub static ExistentialDeposit: Balance = 1; - pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights - ::with_sensible_defaults( - Weight::from_parts(2u64 * constants::WEIGHT_REF_TIME_PER_SECOND, u64::MAX), - NORMAL_DISPATCH_RATIO, - ); -} - -#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] -impl pallet_balances::Config for Runtime { - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - type MaxFreezes = VariantCountOf; - type RuntimeHoldReason = RuntimeHoldReason; - type RuntimeFreezeReason = RuntimeFreezeReason; - type FreezeIdentifier = RuntimeFreezeReason; -} - -impl pallet_timestamp::Config for Runtime { - type Moment = Moment; - type OnTimestampSet = (); - type MinimumPeriod = traits::ConstU32<5>; - type WeightInfo = (); -} - -parameter_types! { - pub static Period: u32 = 30; - pub static Offset: u32 = 0; -} - -sp_runtime::impl_opaque_keys! { - pub struct SessionKeys { - pub other: OtherSessionHandler, - } -} - -impl pallet_session::Config for Runtime { - type SessionManager = pallet_session::historical::NoteHistoricalRoot; - type Keys = SessionKeys; - type ShouldEndSession = pallet_session::PeriodicSessions; - type NextSessionRotation = pallet_session::PeriodicSessions; - type SessionHandler = (OtherSessionHandler,); - type RuntimeEvent = RuntimeEvent; - type ValidatorId = AccountId; - type ValidatorIdOf = pallet_staking::StashOf; - type WeightInfo = (); -} -impl pallet_session::historical::Config for Runtime { - type FullIdentification = pallet_staking::Exposure; - type FullIdentificationOf = pallet_staking::ExposureOf; -} - -frame_election_provider_support::generate_solution_type!( - #[compact] - pub struct MockNposSolution::< - VoterIndex = VoterIndex, - TargetIndex = TargetIndex, - Accuracy = PerU16, - MaxVoters = ConstU32::<2_000> - >(6) -); - -parameter_types! { - pub static SignedPhase: BlockNumber = 10; - pub static UnsignedPhase: BlockNumber = 10; - pub static SignedValidationPhase: BlockNumber = Pages::get().into(); - pub static Lookhaead: BlockNumber = Pages::get(); - pub static VoterSnapshotPerBlock: VoterIndex = 4; - pub static TargetSnapshotPerBlock: TargetIndex = 8; - pub static Pages: PageIndex = 3; - pub static ExportPhaseLimit: BlockNumber = (Pages::get() * 2u32).into(); - - // TODO: remove what's not needed from here down: - - // we expect a minimum of 3 blocks in signed phase and unsigned phases before trying - // entering in emergency phase after the election failed. - pub static MinBlocksBeforeEmergency: BlockNumber = 3; - #[derive(Debug)] - pub static MaxVotesPerVoter: u32 = 16; - pub static SignedFixedDeposit: Balance = 1; - pub static SignedDepositIncreaseFactor: Percent = Percent::from_percent(10); - pub static ElectionBounds: frame_election_provider_support::bounds::ElectionBounds = ElectionBoundsBuilder::default() - .voters_count(1_000.into()).targets_count(1_000.into()).build(); -} - -pub struct EPMBenchmarkingConfigs; -impl pallet_election_provider_multi_block::BenchmarkingConfig for EPMBenchmarkingConfigs { - const VOTERS: u32 = 100; - const TARGETS: u32 = 50; - const VOTERS_PER_PAGE: [u32; 2] = [1, 5]; - const TARGETS_PER_PAGE: [u32; 2] = [1, 8]; -} - -impl epm_core_pallet::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type SignedPhase = SignedPhase; - type UnsignedPhase = UnsignedPhase; - type SignedValidationPhase = SignedValidationPhase; - type Lookhaead = Lookhaead; - type VoterSnapshotPerBlock = VoterSnapshotPerBlock; - type TargetSnapshotPerBlock = TargetSnapshotPerBlock; - type Pages = Pages; - type ExportPhaseLimit = ExportPhaseLimit; - type MaxWinnersPerPage = MaxWinnersPerPage; - type MaxBackersPerWinner = MaxBackersPerWinner; - type MinerConfig = Self; - type Fallback = frame_election_provider_support::NoElection<( - AccountId, - BlockNumber, - Staking, - MaxWinnersPerPage, - MaxBackersPerWinner, - )>; - type Verifier = VerifierPallet; - type DataProvider = Staking; - type BenchmarkingConfig = EPMBenchmarkingConfigs; - type WeightInfo = (); -} - -parameter_types! { - pub static SolutionImprovementThreshold: Perbill = Perbill::zero(); - pub static MaxWinnersPerPage: u32 = 4; - pub static MaxBackersPerWinner: u32 = 16; -} - -impl epm_verifier_pallet::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type ForceOrigin = frame_system::EnsureRoot; - type SolutionImprovementThreshold = SolutionImprovementThreshold; - type SolutionDataProvider = SignedPallet; - type WeightInfo = (); -} - -parameter_types! { - pub static DepositBase: Balance = 10; - pub static DepositPerPage: Balance = 1; - pub static Reward: Balance = 10; - pub static MaxSubmissions: u32 = 5; -} - -impl epm_signed_pallet::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type EstimateCallFee = ConstU32<8>; - type OnSlash = (); // burn - type DepositBase = ConstDepositBase; - type DepositPerPage = DepositPerPage; - type Reward = Reward; - type MaxSubmissions = MaxSubmissions; - type RuntimeHoldReason = RuntimeHoldReason; - type WeightInfo = (); -} - -pub struct ConstDepositBase; -impl sp_runtime::traits::Convert for ConstDepositBase { - fn convert(_a: usize) -> Balance { - DepositBase::get() - } -} - -parameter_types! { - pub static OffchainRepeatInterval: BlockNumber = 0; - pub static TransactionPriority: transaction_validity::TransactionPriority = 1; - pub static MinerMaxLength: u32 = 256; - pub static MinerMaxWeight: Weight = BlockWeights::get().max_block; -} - -impl epm_unsigned_pallet::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type OffchainRepeatInterval = OffchainRepeatInterval; - type MinerTxPriority = TransactionPriority; - type MaxLength = MinerMaxLength; - type MaxWeight = MinerMaxWeight; - type WeightInfo = (); -} - -impl miner::Config for Runtime { - type AccountId = AccountId; - type Solution = MockNposSolution; - type Solver = Solver; - type Pages = Pages; - type MaxVotesPerVoter = ConstU32<16>; - type MaxWinnersPerPage = MaxWinnersPerPage; - type MaxBackersPerWinner = MaxBackersPerWinner; - type VoterSnapshotPerBlock = VoterSnapshotPerBlock; - type TargetSnapshotPerBlock = TargetSnapshotPerBlock; - type MaxWeight = MinerMaxWeight; - type MaxLength = MinerMaxLength; -} - -const THRESHOLDS: [VoteWeight; 9] = [10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]; - -parameter_types! { - pub static BagThresholds: &'static [sp_npos_elections::VoteWeight] = &THRESHOLDS; - pub const SessionsPerEra: sp_staking::SessionIndex = 2; - pub const BondingDuration: sp_staking::EraIndex = 28; - pub const SlashDeferDuration: sp_staking::EraIndex = 7; // 1/4 the bonding duration. -} - -impl pallet_bags_list::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type WeightInfo = (); - type ScoreProvider = Staking; - type BagThresholds = BagThresholds; - type Score = VoteWeight; -} - -pub struct BalanceToU256; -impl sp_runtime::traits::Convert for BalanceToU256 { - fn convert(n: Balance) -> sp_core::U256 { - n.into() - } -} - -pub struct U256ToBalance; -impl sp_runtime::traits::Convert for U256ToBalance { - fn convert(n: sp_core::U256) -> Balance { - n.try_into().unwrap() - } -} - -parameter_types! { - pub const PoolsPalletId: frame_support::PalletId = frame_support::PalletId(*b"py/nopls"); - pub static MaxUnbonding: u32 = 8; -} - -impl pallet_nomination_pools::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type WeightInfo = (); - type Currency = Balances; - type RuntimeFreezeReason = RuntimeFreezeReason; - type RewardCounter = sp_runtime::FixedU128; - type BalanceToU256 = BalanceToU256; - type U256ToBalance = U256ToBalance; - type StakeAdapter = pallet_nomination_pools::adapter::TransferStake; - type PostUnbondingPoolsWindow = ConstU32<2>; - type PalletId = PoolsPalletId; - type MaxMetadataLen = ConstU32<256>; - type MaxUnbonding = MaxUnbonding; - type MaxPointsToBalance = frame_support::traits::ConstU8<10>; - type AdminOrigin = frame_system::EnsureRoot; -} - -parameter_types! { - pub static MaxUnlockingChunks: u32 = 32; - pub MaxControllersInDeprecationBatch: u32 = 5900; - pub static MaxValidatorSet: u32 = 500; -} - -/// Upper limit on the number of NPOS nominations. -const MAX_QUOTA_NOMINATIONS: u32 = 16; -/// Disabling factor set explicitly to byzantine threshold -pub(crate) const SLASHING_DISABLING_FACTOR: usize = 3; - -#[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] -impl pallet_staking::Config for Runtime { - type Currency = Balances; - type CurrencyBalance = Balance; - type UnixTime = Timestamp; - type SessionsPerEra = SessionsPerEra; - type BondingDuration = BondingDuration; - type SlashDeferDuration = SlashDeferDuration; - type AdminOrigin = EnsureRoot; // root can cancel slashes - type SessionInterface = Self; - type EraPayout = (); - type NextNewSession = Session; - type MaxExposurePageSize = ConstU32<256>; - type MaxValidatorSet = MaxValidatorSet; - type ElectionProvider = ElectionProvider; - type GenesisElectionProvider = onchain::OnChainExecution; - type VoterList = BagsList; - type NominationsQuota = pallet_staking::FixedNominationsQuota; - type TargetList = pallet_staking::UseValidatorsMap; - type MaxUnlockingChunks = MaxUnlockingChunks; - type EventListeners = Pools; - type WeightInfo = pallet_staking::weights::SubstrateWeight; - type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy; - type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; -} - -impl frame_system::offchain::CreateTransactionBase for Runtime -where - RuntimeCall: From, -{ - type RuntimeCall = RuntimeCall; - type Extrinsic = Extrinsic; -} - -impl frame_system::offchain::CreateInherent for Runtime -where - RuntimeCall: From, -{ - fn create_inherent(call: Self::RuntimeCall) -> Self::Extrinsic { - Extrinsic::new_bare(call) - } -} - -pub struct OnChainSeqPhragmen; - -parameter_types! { - pub static VotersBound: u32 = 600; - pub static TargetsBound: u32 = 400; -} - -impl onchain::Config for OnChainSeqPhragmen { - type System = Runtime; - type Solver = Solver; - type DataProvider = Staking; - type WeightInfo = (); - type Bounds = ElectionBounds; - type MaxBackersPerWinner = MaxBackersPerWinner; - type MaxWinnersPerPage = MaxWinnersPerPage; -} - -pub struct OtherSessionHandler; -impl traits::OneSessionHandler for OtherSessionHandler { - type Key = testing::UintAuthorityId; - - fn on_genesis_session<'a, I: 'a>(_: I) - where - I: Iterator, - AccountId: 'a, - { - } - - fn on_new_session<'a, I: 'a>(_: bool, _: I, _: I) - where - I: Iterator, - AccountId: 'a, - { - } - - fn on_disabled(_validator_index: u32) {} -} - -impl sp_runtime::BoundToRuntimeAppPublic for OtherSessionHandler { - type Public = testing::UintAuthorityId; -} - -pub struct StakingExtBuilder { - validator_count: u32, - minimum_validator_count: u32, - min_nominator_bond: Balance, - min_validator_bond: Balance, - status: BTreeMap>, - stakes: BTreeMap, - stakers: Vec<(AccountId, AccountId, Balance, StakerStatus)>, -} - -impl Default for StakingExtBuilder { - fn default() -> Self { - let stakers = vec![ - // (stash, ctrl, stake, status) - (11, 11, 1000, StakerStatus::::Validator), - (21, 21, 1000, StakerStatus::::Validator), - (31, 31, 500, StakerStatus::::Validator), - (41, 41, 1500, StakerStatus::::Validator), - (51, 51, 1500, StakerStatus::::Validator), - (61, 61, 1500, StakerStatus::::Validator), - (71, 71, 1500, StakerStatus::::Validator), - (81, 81, 1500, StakerStatus::::Validator), - (91, 91, 1500, StakerStatus::::Validator), - (101, 101, 500, StakerStatus::::Validator), - // idle validators - (201, 201, 1000, StakerStatus::::Idle), - (301, 301, 1000, StakerStatus::::Idle), - // nominators - (10, 10, 2000, StakerStatus::::Nominator(vec![11, 21])), - (20, 20, 2000, StakerStatus::::Nominator(vec![31])), - (30, 30, 2000, StakerStatus::::Nominator(vec![91, 101])), - (40, 40, 2000, StakerStatus::::Nominator(vec![11, 101])), - ]; - - Self { - validator_count: 6, - minimum_validator_count: 0, - min_nominator_bond: ExistentialDeposit::get(), - min_validator_bond: ExistentialDeposit::get(), - status: Default::default(), - stakes: Default::default(), - stakers, - } - } -} - -impl StakingExtBuilder { - pub fn validator_count(mut self, n: u32) -> Self { - self.validator_count = n; - self - } -} - -pub struct EpmExtBuilder {} - -impl Default for EpmExtBuilder { - fn default() -> Self { - EpmExtBuilder {} - } -} - -impl EpmExtBuilder { - pub fn disable_emergency_throttling(self) -> Self { - ::set(0); - self - } - - pub fn phases(self, signed: BlockNumber, unsigned: BlockNumber) -> Self { - ::set(signed); - ::set(unsigned); - self - } -} - -pub struct BalancesExtBuilder { - balances: Vec<(AccountId, Balance)>, -} - -impl Default for BalancesExtBuilder { - fn default() -> Self { - let balances = vec![ - // (account_id, balance) - (1, 10), - (2, 20), - (3, 300), - (4, 400), - // nominators - (10, 10_000), - (20, 10_000), - (30, 10_000), - (40, 10_000), - (50, 10_000), - (60, 10_000), - (70, 10_000), - (80, 10_000), - (90, 10_000), - (100, 10_000), - (200, 10_000), - // validators - (11, 1000), - (21, 2000), - (31, 3000), - (41, 4000), - (51, 5000), - (61, 6000), - (71, 7000), - (81, 8000), - (91, 9000), - (101, 10000), - (201, 20000), - (301, 20000), - // This allows us to have a total_payout different from 0. - (999, 1_000_000_000_000), - ]; - Self { balances } - } -} - -pub struct ExtBuilder { - staking_builder: StakingExtBuilder, - epm_builder: EpmExtBuilder, - balances_builder: BalancesExtBuilder, -} - -impl Default for ExtBuilder { - fn default() -> Self { - Self { - staking_builder: StakingExtBuilder::default(), - epm_builder: EpmExtBuilder::default(), - balances_builder: BalancesExtBuilder::default(), - } - } -} - -impl ExtBuilder { - pub fn build(&self) -> sp_io::TestExternalities { - sp_tracing::try_init_simple(); - let mut storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); - - let _ = pallet_balances::GenesisConfig:: { - balances: self.balances_builder.balances.clone(), - } - .assimilate_storage(&mut storage); - - let mut stakers = self.staking_builder.stakers.clone(); - self.staking_builder.status.clone().into_iter().for_each(|(stash, status)| { - let (_, _, _, ref mut prev_status) = stakers - .iter_mut() - .find(|s| s.0 == stash) - .expect("set_status staker should exist; qed"); - *prev_status = status; - }); - // replaced any of the stakes if needed. - self.staking_builder.stakes.clone().into_iter().for_each(|(stash, stake)| { - let (_, _, ref mut prev_stake, _) = stakers - .iter_mut() - .find(|s| s.0 == stash) - .expect("set_stake staker should exits; qed."); - *prev_stake = stake; - }); - - let _ = pallet_staking::GenesisConfig:: { - stakers: stakers.clone(), - validator_count: self.staking_builder.validator_count, - minimum_validator_count: self.staking_builder.minimum_validator_count, - slash_reward_fraction: Perbill::from_percent(10), - min_nominator_bond: self.staking_builder.min_nominator_bond, - min_validator_bond: self.staking_builder.min_validator_bond, - ..Default::default() - } - .assimilate_storage(&mut storage); - - let _ = pallet_session::GenesisConfig:: { - // set the keys for the first session. - keys: stakers - .into_iter() - .map(|(id, ..)| (id, id, SessionKeys { other: (id as u64).into() })) - .collect(), - ..Default::default() - } - .assimilate_storage(&mut storage); - - let mut ext = sp_io::TestExternalities::from(storage); - - // We consider all test to start after timestamp is initialized This must be ensured by - // having `timestamp::on_initialize` called before `staking::on_initialize`. - ext.execute_with(|| { - System::set_block_number(1); - Session::on_initialize(1); - >::on_initialize(1); - Timestamp::set_timestamp(INIT_TIMESTAMP); - }); - - ext - } - - pub fn staking(mut self, builder: StakingExtBuilder) -> Self { - self.staking_builder = builder; - self - } - - pub fn epm(mut self, builder: EpmExtBuilder) -> Self { - self.epm_builder = builder; - self - } - - pub fn balances(mut self, builder: BalancesExtBuilder) -> Self { - self.balances_builder = builder; - self - } - - pub fn build_offchainify( - self, - ) -> (sp_io::TestExternalities, Arc>, Arc>) { - // add offchain and pool externality extensions. - let mut ext = self.build(); - let (offchain, offchain_state) = TestOffchainExt::new(); - let (pool, pool_state) = TestTransactionPoolExt::new(); - - ext.register_extension(OffchainDbExt::new(offchain.clone())); - ext.register_extension(OffchainWorkerExt::new(offchain)); - ext.register_extension(TransactionPoolExt::new(pool)); - - (ext, pool_state, offchain_state) - } - - pub fn build_and_execute(self, test: impl FnOnce() -> ()) { - let mut ext = self.build(); - ext.execute_with(test); - - #[cfg(feature = "try-runtime")] - ext.execute_with(|| { - let bn = System::block_number(); - - assert_ok!(>::try_state(bn)); - assert_ok!(>::try_state(bn)); - assert_ok!(>::try_state(bn)); - }); - } -} - -// Progress to given block, triggering session and era changes as we progress and ensuring that -// there is a solution queued when expected. -pub fn roll_to(n: BlockNumber, delay_solution: bool) { - for b in (System::block_number()) + 1..=n { - System::set_block_number(b); - Session::on_initialize(b); - Timestamp::set_timestamp(System::block_number() * BLOCK_TIME + INIT_TIMESTAMP); - - if ElectionProvider::current_phase() == Phase::Signed && !delay_solution { - let _ = try_submit_paged_solution().map_err(|e| { - log!(info, "failed to mine/queue solution: {:?}", e); - }); - }; - - ElectionProvider::on_initialize(b); - VerifierPallet::on_initialize(b); - SignedPallet::on_initialize(b); - UnsignedPallet::on_initialize(b); - - Staking::on_initialize(b); - if b != n { - Staking::on_finalize(System::block_number()); - } - - log_current_time(); - } -} - -// Progress to given block, triggering session and era changes as we progress and ensuring that -// there is a solution queued when expected. -pub fn roll_to_with_ocw(n: BlockNumber, pool: Arc>, delay_solution: bool) { - for b in (System::block_number()) + 1..=n { - System::set_block_number(b); - Session::on_initialize(b); - Timestamp::set_timestamp(System::block_number() * BLOCK_TIME + INIT_TIMESTAMP); - - ElectionProvider::on_initialize(b); - VerifierPallet::on_initialize(b); - SignedPallet::on_initialize(b); - UnsignedPallet::on_initialize(b); - - ElectionProvider::offchain_worker(b); - - if !delay_solution && pool.read().transactions.len() > 0 { - // decode submit_unsigned callable that may be queued in the pool by ocw. skip all - // other extrinsics in the pool. - for encoded in &pool.read().transactions { - let _extrinsic = Extrinsic::decode(&mut &encoded[..]).unwrap(); - - // TODO(gpestana): fix when EPM sub-pallets are ready - //let _ = match extrinsic.call { - // RuntimeCall::ElectionProvider( - // call @ Call::submit_unsigned { .. }, - // ) => { - // // call submit_unsigned callable in OCW pool. - // assert_ok!(call.dispatch_bypass_filter(RuntimeOrigin::none())); - // }, - // _ => (), - //}; - } - - pool.try_write().unwrap().transactions.clear(); - } - - Staking::on_initialize(b); - if b != n { - Staking::on_finalize(System::block_number()); - } - - log_current_time(); - } -} -// helper to progress one block ahead. -pub fn roll_one(pool: Option>>, delay_solution: bool) { - let bn = System::block_number().saturating_add(1); - match pool { - None => roll_to(bn, delay_solution), - Some(pool) => roll_to_with_ocw(bn, pool, delay_solution), - } -} - -/// Progresses from the current block number (whatever that may be) to the block where the session -/// `session_index` starts. -pub(crate) fn start_session( - session_index: SessionIndex, - pool: Arc>, - delay_solution: bool, -) { - let end = if Offset::get().is_zero() { - Period::get() * session_index - } else { - Offset::get() * session_index + Period::get() * session_index - }; - - assert!(end >= System::block_number()); - - roll_to_with_ocw(end, pool, delay_solution); - - // session must have progressed properly. - assert_eq!( - Session::current_index(), - session_index, - "current session index = {}, expected = {}", - Session::current_index(), - session_index, - ); -} - -/// Go one session forward. -pub(crate) fn advance_session(pool: Arc>) { - let current_index = Session::current_index(); - start_session(current_index + 1, pool, false); -} - -pub(crate) fn advance_session_delayed_solution(pool: Arc>) { - let current_index = Session::current_index(); - start_session(current_index + 1, pool, true); -} - -pub(crate) fn start_next_active_era(pool: Arc>) -> Result<(), ()> { - start_active_era(active_era() + 1, pool, false) -} - -pub(crate) fn start_next_active_era_delayed_solution( - pool: Arc>, -) -> Result<(), ()> { - start_active_era(active_era() + 1, pool, true) -} - -pub(crate) fn advance_eras(n: usize, pool: Arc>) { - for _ in 0..n { - assert_ok!(start_next_active_era(pool.clone())); - } -} - -/// Progress until the given era. -pub(crate) fn start_active_era( - era_index: EraIndex, - pool: Arc>, - delay_solution: bool, -) -> Result<(), ()> { - let era_before = current_era(); - - start_session((era_index * >::get()).into(), pool, delay_solution); - - log!( - info, - "start_active_era - era_before: {}, current era: {} -> progress to: {} -> after era: {}", - era_before, - active_era(), - era_index, - current_era(), - ); - - // if the solution was not delayed, era should have progressed. - if !delay_solution && (active_era() != era_index || current_era() != active_era()) { - Err(()) - } else { - Ok(()) - } -} - -pub(crate) fn active_era() -> EraIndex { - Staking::active_era().unwrap().index -} - -pub(crate) fn current_era() -> EraIndex { - Staking::current_era().unwrap() -} - -// Fast forward until a given election phase. -pub fn roll_to_phase(phase: Phase, delay: bool) { - while ElectionProvider::current_phase() != phase { - roll_to(System::block_number() + 1, delay); - } -} - -pub fn election_prediction() -> BlockNumber { - <::DataProvider as ElectionDataProvider>::next_election_prediction( - System::block_number(), - ) -} - -parameter_types! { - pub static LastSolutionSubmittedFor: Option = None; -} - -pub(crate) fn try_submit_paged_solution() -> Result<(), ()> { - let submit = || { - // TODO: to finish. - let voters_snapshot = Default::default(); - let targets_snapshot = Default::default(); - let round = Default::default(); - let desired_targets = Default::default(); - - let (paged_solution, _) = - miner::Miner::<::MinerConfig>::mine_paged_solution_with_snapshot( - &voters_snapshot, - &targets_snapshot, - Pages::get(), - round, - desired_targets, - false, - ) - .unwrap(); - - let _ = SignedPallet::register(RuntimeOrigin::signed(10), paged_solution.score).unwrap(); - - for (_idx, _page) in paged_solution.solution_pages.into_iter().enumerate() {} - log!( - info, - "submitter: successfully submitted {} pages with {:?} score in round {}.", - Pages::get(), - paged_solution.score, - ElectionProvider::current_round(), - ); - }; - - match LastSolutionSubmittedFor::get() { - Some(submitted_at) => { - if submitted_at == ElectionProvider::current_round() { - // solution already submitted in this round, do nothing. - } else { - // haven't submit in this round, submit it. - submit() - } - }, - // never submitted, do it. - None => submit(), - }; - LastSolutionSubmittedFor::set(Some(ElectionProvider::current_round())); - - Ok(()) -} - -pub(crate) fn on_offence_now( - offenders: &[OffenceDetails>], - slash_fraction: &[Perbill], -) { - let now = Staking::active_era().unwrap().index; - let _ = Staking::on_offence( - offenders, - slash_fraction, - Staking::eras_start_session_index(now).unwrap(), - ); -} - -// Add offence to validator, slash it. -pub(crate) fn add_slash(who: &AccountId) { - on_offence_now( - &[OffenceDetails { - offender: (*who, Staking::eras_stakers(active_era(), who)), - reporters: vec![], - }], - &[Perbill::from_percent(10)], - ); -} - -// Slashes 1/2 of the active set. Returns the `AccountId`s of the slashed validators. -pub(crate) fn slash_half_the_active_set() -> Vec { - let mut slashed = Session::validators(); - slashed.truncate(slashed.len() / 2); - - for v in slashed.iter() { - add_slash(v); - } - - slashed -} - -// Slashes a percentage of the active nominators that haven't been slashed yet, with -// a minimum of 1 validator slash. -pub(crate) fn slash_percentage(percentage: Perbill) -> Vec { - let validators = Session::validators(); - let mut remaining_slashes = (percentage * validators.len() as u32).max(1); - let mut slashed = vec![]; - - for v in validators.into_iter() { - if remaining_slashes != 0 { - add_slash(&v); - slashed.push(v); - remaining_slashes -= 1; - } - } - slashed -} - -pub(crate) fn set_minimum_election_score( - _minimal_stake: ExtendedBalance, - _sum_stake: ExtendedBalance, - _sum_stake_squared: ExtendedBalance, -) -> Result<(), ()> { - todo!() -} - -pub(crate) fn staked_amount_for(account_id: AccountId) -> Balance { - pallet_staking::asset::staked::(&account_id) -} - -pub(crate) fn staking_events() -> Vec> { - System::events() - .into_iter() - .map(|r| r.event) - .filter_map(|e| if let RuntimeEvent::Staking(inner) = e { Some(inner) } else { None }) - .collect::>() -} - -pub(crate) fn epm_events() -> Vec> { - System::events() - .into_iter() - .map(|r| r.event) - .filter_map( - |e| { - if let RuntimeEvent::ElectionProvider(inner) = e { - Some(inner) - } else { - None - } - }, - ) - .collect::>() -} diff --git a/substrate/frame/election-provider-multi-block/src/benchmarking.rs b/substrate/frame/election-provider-multi-block/src/benchmarking.rs deleted file mode 100644 index d0d9a98b6bae3..0000000000000 --- a/substrate/frame/election-provider-multi-block/src/benchmarking.rs +++ /dev/null @@ -1,318 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! # Benchmarking for the Elections Multiblock pallet and sub-pallets. - -use super::*; -use crate::Snapshot; - -use frame_benchmarking::v2::*; -use frame_support::{assert_ok, traits::OnInitialize}; - -/// Seed to generate account IDs. -const SEED: u32 = 999; -/// Default page to use in the benchmarking. -const PAGE: u32 = 0; -/// Minimum number of voters in the data provider and per snapshot page. -const MIN_VOTERS: u32 = 10; - -#[benchmarks( - where T: ConfigCore + ConfigSigned + ConfigVerifier, -)] -mod benchmarks { - use super::*; - - #[benchmark] - fn create_targets_snapshot_paged( - t: Linear< - { T::BenchmarkingConfig::TARGETS_PER_PAGE[0] }, - { T::BenchmarkingConfig::TARGETS_PER_PAGE[1] }, - >, - ) -> Result<(), BenchmarkError> { - helpers::setup_data_provider::( - T::BenchmarkingConfig::VOTERS, - T::BenchmarkingConfig::TARGETS.max(t), - ); - - assert!(Snapshot::::targets().is_none()); - - #[block] - { - assert_ok!(PalletCore::::create_targets_snapshot_inner(t)); - } - - assert!(Snapshot::::targets().is_some()); - - Ok(()) - } - - #[benchmark] - fn create_voters_snapshot_paged( - v: Linear< - { T::BenchmarkingConfig::VOTERS_PER_PAGE[0] }, - { T::BenchmarkingConfig::VOTERS_PER_PAGE[1] }, - >, - ) -> Result<(), BenchmarkError> { - helpers::setup_data_provider::( - T::BenchmarkingConfig::VOTERS.max(v), - T::BenchmarkingConfig::TARGETS, - ); - - assert!(Snapshot::::voters(PAGE).is_none()); - - #[block] - { - assert_ok!(PalletCore::::create_voters_snapshot_inner(PAGE, v)); - } - - assert!(Snapshot::::voters(PAGE).is_some()); - - Ok(()) - } - - #[benchmark] - fn on_initialize_start_signed() -> Result<(), BenchmarkError> { - assert!(PalletCore::::current_phase() == Phase::Off); - - #[block] - { - let _ = PalletCore::::start_signed_phase(); - } - - assert!(PalletCore::::current_phase() == Phase::Signed); - - Ok(()) - } - - #[benchmark] - fn on_phase_transition() -> Result<(), BenchmarkError> { - assert!(PalletCore::::current_phase() == Phase::Off); - - #[block] - { - let _ = PalletCore::::phase_transition(Phase::Snapshot(0)); - } - - assert!(PalletCore::::current_phase() == Phase::Snapshot(0)); - - Ok(()) - } - - #[benchmark] - fn on_initialize_start_export() -> Result<(), BenchmarkError> { - #[block] - { - let _ = PalletCore::::do_export_phase(1u32.into(), 100u32.into()); - } - - Ok(()) - } - - #[benchmark] - fn on_initialize_do_nothing() -> Result<(), BenchmarkError> { - assert!(PalletCore::::current_phase() == Phase::Off); - - #[block] - { - let _ = PalletCore::::on_initialize(0u32.into()); - } - - assert!(PalletCore::::current_phase() == Phase::Off); - - Ok(()) - } - - impl_benchmark_test_suite!( - PalletCore, - crate::mock::ExtBuilder::default(), - crate::mock::Runtime, - exec_name = build_and_execute - ); -} - -/// Helper fns to use across the benchmarking of the core pallet and its sub-pallets. -pub(crate) mod helpers { - use super::*; - use crate::{signed::pallet::Submissions, unsigned::miner::Miner, SolutionOf}; - use frame_election_provider_support::ElectionDataProvider; - use frame_support::traits::tokens::Precision; - use sp_std::vec::Vec; - - pub(crate) fn setup_funded_account( - domain: &'static str, - id: u32, - balance_factor: u32, - ) -> T::AccountId { - use frame_support::traits::fungible::{Balanced, Inspect}; - - let account = frame_benchmarking::account::(domain, id, SEED); - let funds = (T::Currency::minimum_balance() + 1u32.into()) * balance_factor.into(); - // increase issuance to ensure a sane voter weight. - let _ = T::Currency::deposit(&account, funds.into(), Precision::Exact); - - account - } - - /// Generates and adds `v` voters and `t` targets in the data provider stores. The voters - /// nominate `DataProvider::MaxVotesPerVoter` targets. - pub(crate) fn setup_data_provider(v: u32, t: u32) { - ::DataProvider::clear(); - - log!(info, "setup_data_provider with v: {}, t: {}", v, t,); - - // generate and add targets. - let mut targets = (0..t) - .map(|i| { - let target = setup_funded_account::("Target", i, 200); - ::DataProvider::add_target(target.clone()); - target - }) - .collect::>(); - - // we should always have enough voters to fill. - assert!( - targets.len() > - <::DataProvider as ElectionDataProvider>::MaxVotesPerVoter::get() - as usize - ); - - targets.truncate( - <::DataProvider as ElectionDataProvider>::MaxVotesPerVoter::get() as usize, - ); - - // generate and add voters with `MaxVotesPerVoter` nominations from the list of targets. - (0..v.max(MIN_VOTERS)).for_each(|i| { - let voter = setup_funded_account::("Voter", i, 2_000); - <::DataProvider as ElectionDataProvider>::add_voter( - voter, - 1_000, - targets.clone().try_into().unwrap(), - ); - }); - } - - /// Generates the full paged snapshot for both targets and voters. - pub(crate) fn setup_snapshot(v: u32, t: u32) -> Result<(), &'static str> { - // set desired targets to match the size off the target page. - ::set_desired_targets(t); - - log!( - info, - "setting up the snapshot. voters/page: {:?}, targets/page: {:?} (desired_targets: {:?})", - v, - t, - ::desired_targets(), - ); - - let _ = PalletCore::::create_targets_snapshot_inner(t) - .map_err(|_| "error creating the target snapshot, most likely `T::TargetSnapshotPerBlock` config needs to be adjusted")?; - - for page in 0..T::Pages::get() { - let _ = PalletCore::::create_voters_snapshot_inner(page, v) - .map_err(|_| "error creating the voter snapshot, most likely `T::VoterSnapshotPerBlock` config needs to be adjusted")?; - } - - Ok(()) - } - - /// Mines a full solution for the current snapshot and submits `maybe_page`. Otherwise submits - /// all pages. `valid` defines whether the solution is valid or not. - pub(crate) fn mine_and_submit< - T: ConfigCore + ConfigUnsigned + ConfigSigned + ConfigVerifier, - >( - maybe_page: Option, - valid: bool, - ) -> Result { - // ensure that the number of desired targets fits within the bound of max winners per page, - // otherwise preemptively since the feasibilty check will fail. - ensure!( - ::desired_targets() - .expect("desired_targets is set") <= - ::MaxWinnersPerPage::get(), - "`MaxWinnersPerPage` must be equal or higher than desired_targets. fix the configs.", - ); - - let submitter = setup_funded_account::("Submitter", 1, 1_000); - - let (mut solution, _) = - Miner::::mine_paged_solution(T::Pages::get(), true).map_err( - |e| { - log!(info, "ERR:: {:?}", e); - "error mining solution" - }, - )?; - - // if the submission is full and the fesibility check must fail, mess up with the solution's - // claimed score to fail the verification (worst case scenario in terms of async solution - // verification). - let claimed_score = if maybe_page.is_none() && !valid { - solution.score.sum_stake += 1_000_000; - solution.score - } else { - solution.score - }; - - // set transition to phase to ensure the page mutation works. - PalletCore::::phase_transition(Phase::Signed); - - // first register submission. - PalletSigned::::do_register(&submitter, claimed_score, PalletCore::::current_round()) - .map_err(|_| "error registering solution")?; - - for page in maybe_page - .map(|p| sp_std::vec![p]) - .unwrap_or((0..T::Pages::get()).rev().collect::>()) - .into_iter() - { - let paged_solution = - solution.solution_pages.get(page as usize).ok_or("page out of bounds")?; - - // if it is a single page submission and submission should be invalid, make the paged - // paged submission invalid by tweaking the current snapshot. - if maybe_page.is_some() && !valid { - ensure_solution_invalid::(&paged_solution)?; - } - - // process and submit only onle page. - Submissions::::try_mutate_page( - &submitter, - PalletCore::::current_round(), - page, - Some(paged_solution.clone()), - ) - .map_err(|_| "error storing page")?; - } - - Ok(submitter) - } - - /// ensures `solution` will be considered invalid in the feasibility check by tweaking the - /// snapshot which was used to compute the solution and remove one of the targets from the - /// snapshot. NOTE: we expect that the `solution` was generated based on the current snapshot - /// state. - fn ensure_solution_invalid( - solution: &SolutionOf, - ) -> Result<(), &'static str> { - let new_count_targets = solution.unique_targets().len().saturating_sub(1); - - // remove a target from the snapshot to invalidate solution. - let _ = PalletCore::::create_targets_snapshot_inner(new_count_targets as u32) - .map_err(|_| "error regenerating the target snapshot")?; - - Ok(()) - } -} diff --git a/substrate/frame/election-provider-multi-block/src/helpers.rs b/substrate/frame/election-provider-multi-block/src/helpers.rs deleted file mode 100644 index 61c6f0b907ae3..0000000000000 --- a/substrate/frame/election-provider-multi-block/src/helpers.rs +++ /dev/null @@ -1,331 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2021 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. - -//! Some helper functions/macros for this crate. - -use crate::{ - types::{AllTargetPagesOf, AllVoterPagesOf, MaxWinnersPerPageOf, MinerVoterOf}, - SolutionTargetIndexOf, SolutionVoterIndexOf, -}; -use frame_election_provider_support::{PageIndex, VoteWeight}; -use frame_support::{traits::Get, BoundedVec}; -use sp_runtime::SaturatedConversion; -use sp_std::{cmp::Reverse, collections::btree_map::BTreeMap, vec, vec::Vec}; - -#[macro_export] -macro_rules! log { - ($level:tt, $pattern:expr $(, $values:expr)* $(,)?) => { - log::$level!( - target: $crate::LOG_TARGET, - concat!("[#{:?}]: 🎯🎯🎯 🗳 ", $pattern), >::block_number() $(, $values)* - ) - }; -} - -macro_rules! sublog { - ($level:tt, $sub_pallet:tt, $pattern:expr $(, $values:expr)* $(,)?) => { - #[cfg(not(feature = "std"))] - log!($level, $pattern $(, $values )*); - #[cfg(feature = "std")] - log::$level!( - target: format!("{}::{}", $crate::LOG_TARGET, $sub_pallet).as_ref(), - concat!("[#{:?}]: 🎯🎯🎯 🗳 ", $pattern), >::block_number() $(, $values )* - ) - }; -} - -use crate::unsigned::miner::Config as MinerConfig; - -/// Generate a btree-map cache of the voters and their indices within the provided `snapshot`. -/// -/// This does not care about pagination. `snapshot` might be a single page or the entire blob of -/// voters. -/// -/// This can be used to efficiently build index getter closures. -pub fn generate_voter_cache>( - snapshot: &BoundedVec, AnyBound>, -) -> BTreeMap { - let mut cache: BTreeMap = BTreeMap::new(); - snapshot.iter().enumerate().for_each(|(i, (x, _, _))| { - let _existed = cache.insert(x.clone(), i); - // if a duplicate exists, we only consider the last one. Defensive only, should never - // happen. - debug_assert!(_existed.is_none()); - }); - - cache -} - -pub fn generate_target_cache( - snapshot: &Vec, -) -> BTreeMap { - let mut cache: BTreeMap = BTreeMap::new(); - snapshot.iter().enumerate().for_each(|(i, x)| { - let _existed = cache.insert(x.clone(), i); - // if a duplicate exists, we only consider the last one. Defensive only, should never - // happen. - debug_assert!(_existed.is_none()); - }); - - cache -} - -pub fn generate_voter_staked_cache( - snapshot: &Vec<&MinerVoterOf>, -) -> BTreeMap { - let mut cache: BTreeMap = BTreeMap::new(); - snapshot.into_iter().enumerate().for_each(|(i, (x, _, _))| { - let _existed = cache.insert(x.clone(), i); - // if a duplicate exists, we only consider the last one. Defensive only, should never - // happen. - debug_assert!(_existed.is_none()); - }); - cache -} - -/// Generate an efficient closure of voters and the page in which they live in. -/// -/// The bucketing of voters into a page number is based on their position in the snapshot's page. -pub fn generate_voter_page_fn( - paged_snapshot: &AllVoterPagesOf, -) -> impl Fn(&T::AccountId) -> Option { - let mut cache: BTreeMap = BTreeMap::new(); - paged_snapshot - .iter() - .enumerate() - .map(|(page, whatever)| (page.saturated_into::(), whatever)) - .for_each(|(page, page_voters)| { - page_voters.iter().for_each(|(v, _, _)| { - let _existed = cache.insert(v.clone(), page); - // if a duplicate exists, we only consider the last one. Defensive only, should - // never happen. - debug_assert!(_existed.is_none()); - }); - }); - - move |who| cache.get(who).copied() -} - -pub fn generate_target_page_fn( - paged_snapshot: &AllTargetPagesOf, -) -> impl Fn(&T::AccountId) -> Option { - let mut cache: BTreeMap = BTreeMap::new(); - paged_snapshot - .iter() - .enumerate() - .map(|(page, whatever)| (page.saturated_into::(), whatever)) - .for_each(|(page, page_voters)| { - page_voters.iter().for_each(|t| { - let _existed = cache.insert(t.clone(), page); - // if a duplicate exists, we only consider the last one. Defensive only, should - // never happen. - debug_assert!(_existed.is_none()); - }); - }); - move |who| cache.get(who).copied() -} - -/// stake. -/// -/// The bucketing of voters into a page number is based on their relative stake in the assignments -/// set (the larger the stake, the higher the page). -pub fn generate_voter_page_stake_fn( - paged_snapshot: &AllVoterPagesOf, -) -> impl Fn(&T::AccountId) -> Option { - let mut cache: BTreeMap = BTreeMap::new(); - let mut sorted_by_weight: Vec<(VoteWeight, T::AccountId)> = vec![]; - - // sort voter stakes. - let _ = paged_snapshot.to_vec().iter().flatten().for_each(|voter| { - let pos = sorted_by_weight - .binary_search_by_key(&Reverse(&voter.1), |(weight, _)| Reverse(weight)) - .unwrap_or_else(|pos| pos); - sorted_by_weight.insert(pos, (voter.1, voter.0.clone())); - }); - - sorted_by_weight.iter().enumerate().for_each(|(idx, voter)| { - let page = idx - .saturating_div(MaxWinnersPerPageOf::::get() as usize) - .min(T::Pages::get() as usize); - let _existed = cache.insert(voter.1.clone(), page as PageIndex); - debug_assert!(_existed.is_none()); - }); - - move |who| cache.get(who).copied() -} - -/// Create a function that returns the index of a voter in the snapshot. -/// -/// The returning index type is the same as the one defined in `T::Solution::Voter`. -/// -/// ## Warning -/// -/// Note that this will represent the snapshot data from which the `cache` is generated. -pub fn voter_index_fn( - cache: &BTreeMap, -) -> impl Fn(&T::AccountId) -> Option> + '_ { - move |who| { - cache - .get(who) - .and_then(|i| >>::try_into(*i).ok()) - } -} - -/// Create a function that returns the index of a voter in the snapshot. -/// -/// Same as [`voter_index_fn`] but the returned function owns all its necessary data; nothing is -/// borrowed. -pub fn voter_index_fn_owned( - cache: BTreeMap, -) -> impl Fn(&T::AccountId) -> Option> { - move |who| { - cache - .get(who) - .and_then(|i| >>::try_into(*i).ok()) - } -} - -pub fn target_index_fn_owned( - cache: BTreeMap, -) -> impl Fn(&T::AccountId) -> Option> { - move |who| { - cache - .get(who) - .and_then(|i| >>::try_into(*i).ok()) - } -} - -/// Create a function that returns the index of a target in the snapshot. -/// -/// The returned index type is the same as the one defined in `T::Solution::Target`. -/// -/// Note: to the extent possible, the returned function should be cached and reused. Producing that -/// function requires a `O(n log n)` data transform. Each invocation of that function completes -/// in `O(log n)`. -pub fn target_index_fn( - snapshot: &Vec, -) -> impl Fn(&T::AccountId) -> Option> + '_ { - let cache: BTreeMap<_, _> = - snapshot.iter().enumerate().map(|(idx, account_id)| (account_id, idx)).collect(); - move |who| { - cache - .get(who) - .and_then(|i| >>::try_into(*i).ok()) - } -} - -/// Create a function that can map a voter index ([`SolutionVoterIndexOf`]) to the actual voter -/// account using a linearly indexible snapshot. -pub fn voter_at_fn( - snapshot: &Vec>, -) -> impl Fn(SolutionVoterIndexOf) -> Option + '_ { - move |i| { - as TryInto>::try_into(i) - .ok() - .and_then(|i| snapshot.get(i).map(|(x, _, _)| x).cloned()) - } -} - -/// Create a function that can map a target index ([`SolutionTargetIndexOf`]) to the actual target -/// account using a linearly indexible snapshot. -pub fn target_at_fn( - snapshot: &Vec, -) -> impl Fn(SolutionTargetIndexOf) -> Option + '_ { - move |i| { - as TryInto>::try_into(i) - .ok() - .and_then(|i| snapshot.get(i).cloned()) - } -} - -/// Same as [`voter_index_fn`], but the returning index is converted into usize, if possible. -/// -/// ## Warning -/// -/// Note that this will represent the snapshot data from which the `cache` is generated. -pub fn voter_index_fn_usize( - cache: &BTreeMap, -) -> impl Fn(&T::AccountId) -> Option + '_ { - move |who| cache.get(who).cloned() -} - -pub fn target_index_fn_usize( - cache: &BTreeMap, -) -> impl Fn(&T::AccountId) -> Option + '_ { - move |who| cache.get(who).cloned() -} - -/// Create a function to get the stake of a voter. -pub fn stake_of_fn<'a, T: MinerConfig, AnyBound: Get>( - snapshot: &'a BoundedVec, AnyBound>, - cache: &'a BTreeMap, -) -> impl Fn(&T::AccountId) -> VoteWeight + 'a { - move |who| { - if let Some(index) = cache.get(who) { - snapshot.get(*index).map(|(_, x, _)| x).cloned().unwrap_or_default() - } else { - 0 - } - } -} - -#[cfg(test)] -mod tests { - - use super::*; - use crate::mock::*; - - use sp_runtime::bounded_vec; - - type TargetsPerSnapshotPageOf = ::TargetSnapshotPerBlock; - - #[test] - fn paged_targets_indexing_works() { - ExtBuilder::default().build_and_execute(|| { - let all_target_pages: BoundedVec< - BoundedVec>, - ::Pages, - > = bounded_vec![ - bounded_vec![], // page 0 - bounded_vec![11, 61], // page 1 - bounded_vec![51, 101, 31, 41, 21, 81, 71, 91], // page 2 - ]; - - // flatten all targets. - let all_targets = all_target_pages.iter().cloned().flatten().collect::>(); - - // `targets_index_fn` get the snapshot page of the target. - let targets_index_fn = generate_target_page_fn::(&all_target_pages); - let all_targets_cache = generate_target_cache::(&all_targets); - let target_index = target_index_fn_usize::(&all_targets_cache); - - // check that the `targets_index_page` is correct. - for (page, targets) in all_target_pages.iter().enumerate() { - for t in targets { - assert_eq!(targets_index_fn(&t), Some(page as PageIndex)); - } - } - - // check that the `generate_target_cache` ad `target_index_fn_usize` return the correct - // btree cache/idx. - for (idx, target) in all_targets.iter().enumerate() { - assert_eq!(all_targets_cache.get(target), Some(idx).as_ref()); - assert_eq!(target_index(target), Some(idx)); - } - }) - } -} diff --git a/substrate/frame/election-provider-multi-block/src/lib.rs b/substrate/frame/election-provider-multi-block/src/lib.rs deleted file mode 100644 index 3308c3c261706..0000000000000 --- a/substrate/frame/election-provider-multi-block/src/lib.rs +++ /dev/null @@ -1,1193 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! # Multi-phase, multi-block election provider pallet -//! -//! This pallet manages the NPoS election across its different phases, with the ability to accept -//! both on-chain and off-chain solutions. The off-chain solutions may be submitted as a signed or -//! unsigned transaction. Crucially, it supports paginated, multi-block elections. The goal of -//! supporting paged elections is to scale the elections linearly with the number of blocks -//! allocated to the election. -//! -//! **PoV-friendly**: The work performed in a block by this pallet must be measurable and the time -//! and computation required per block must be deterministic with regards to the number of voters, -//! targets and any other configurations that are set apriori. These assumptions make this pallet -//! suitable to run on a parachain. -//! -//! ## Pallet organization and sub-pallets -//! -//! This pallet consists of a `core` pallet and multiple sub-pallets. Conceptually, the pallets have -//! a hierarquical relationship, where the `core` pallet sets and manages shared state between all -//! pallets. Its "child" pallets can not depend on the `core` pallet and iteract with it through -//! trait interfaces: -//! -//!```text -//! pallet-core -//! | -//! | -//! - pallet-verifier -//! | -//! - pallet-signed -//! | -//! - pallet-unsigned -//! ``` -//! -//! Each sub-pallet has a specific set of tasks and implement one or more interfaces to expose their -//! functionality to the core pallet: -//! - The [`verifier`] pallet provides an implementation of the [`verifier::Verifier`] trait, which -//! exposes the functionality to verify NPoS solutions in a multi-block context. In addition, it -//! implements [`verifier::AsyncVerifier`] which verifies multi-paged solution asynchronously. -//! - The [`signed`] pallet implements the signed phase, where off-chain entities commit to and -//! submit their election solutions. This pallet implements the -//! [`verifier::SolutionDataProvider`], which is used by the [`verifier`] pallet to fetch solution -//! data to perform the solution verification. -//! - The [`unsigned`] pallet implements the unsigned phase, where block authors can compute and -//! submit through inherent paged solutions. This pallet also implements the -//! [`verifier::SolutionDataProvider`] interface. -//! -//! ### Pallet ordering -//! -//! Due to the assumptions of when the `on_initialize` hook must be called by the executor for the -//! core pallet and each sub-pallets, it is crucial the the pallets are ordered correctly in the -//! construct runtime. The ordering must be the following: -//! -//! ```text -//! 1. pallet-core -//! 2. pallet-verifier -//! 3. pallet-signed -//! 4. pallet-unsigned -//! ``` -//! -//! ## Election Phases -//! -//! This pallet manages the election phases which signal to the other sub-pallets which actions to -//! take at a given block. The election phases are the following: -//! -//! -//! // TODO(gpestana): use a diagram instead of text diagram. -//! ```text -//! // ----------- ----------- -------------- ------------ -------- -//! // | | | | | | | -//! // Off Snapshot Signed SignedValidation Unsigned elect() Export -//! ``` -//! -//! Each phase duration depends on the estimate block number election, which can be fetched from -//! [`pallet::Config::DataProvider`]. -//! -//! TODO(gpestana): finish, add all info related to EPM-MB - -#![cfg_attr(not(feature = "std"), no_std)] - -use frame_election_provider_support::{ - bounds::ElectionBoundsBuilder, BoundedSupportsOf, ElectionDataProvider, ElectionProvider, - LockableElectionDataProvider, PageIndex, Weight, -}; -use frame_support::{ - defensive, ensure, - traits::{Defensive, DefensiveSaturating, Get}, - BoundedVec, -}; -use sp_runtime::traits::{One, Zero}; - -use frame_system::pallet_prelude::BlockNumberFor; - -#[macro_use] -pub mod helpers; - -pub mod signed; -pub mod types; -pub mod unsigned; -pub mod verifier; -pub mod weights; - -#[cfg(feature = "runtime-benchmarks")] -pub mod benchmarking; - -#[cfg(test)] -mod mock; - -pub use pallet::*; -pub use types::*; - -pub use crate::{unsigned::miner, verifier::Verifier, weights::WeightInfo}; - -/// Internal crate re-exports to use across benchmarking and tests. -#[cfg(any(test, feature = "runtime-benchmarks"))] -use crate::verifier::Pallet as PalletVerifier; - -/// Log target for this the core EPM-MB pallet. -const LOG_TARGET: &'static str = "runtime::multiblock-election"; - -/// Page configured for the election. -pub type PagesOf = ::Pages; - -/// Trait defining the benchmarking configs. -pub trait BenchmarkingConfig { - /// Range of voters registerd in the system. - const VOTERS: u32; - /// Range of targets registered in the system. - const TARGETS: u32; - /// Range of voters per snapshot page. - const VOTERS_PER_PAGE: [u32; 2]; - /// Range of targets per snapshot page. - const TARGETS_PER_PAGE: [u32; 2]; -} - -#[frame_support::pallet] -pub mod pallet { - - use super::*; - use frame_support::{ - pallet_prelude::{ValueQuery, *}, - sp_runtime::Saturating, - Twox64Concat, - }; - - #[pallet::config] - pub trait Config: frame_system::Config { - type RuntimeEvent: From> - + IsType<::RuntimeEvent> - + TryInto>; - - /// Duration of the signed phase; - /// - /// During the signed phase, staking miners may register their solutions and submit - /// paginated solutions. - #[pallet::constant] - type SignedPhase: Get>; - - /// Duration of the unsigned phase; - /// - /// During the unsigned phase, offchain workers of block producing validators compute and - /// submit paginated solutions. - #[pallet::constant] - type UnsignedPhase: Get>; - - /// Duration of the signed validation phase. - /// - /// During the signed validation phase, the async verifier verifies one or all the queued - /// solution submitions during the signed phase. Once one solution is accepted, this phase - /// terminates. - /// - /// The duration of this phase **SHOULD NOT** be less than `T::Pages` and there is no point - /// in it being more than the maximum number of pages per submission. - #[pallet::constant] - type SignedValidationPhase: Get>; - - /// The limit number of blocks that the `Phase::Export` will be open for. - /// - /// During the export phase, this pallet is open to return paginated, verified solution - /// pages if at least one solution has been verified and accepted in the current era. - /// - /// The export phase will terminate if it has been open for `T::ExportPhaseLimit` blocks or - /// the `EPM::call(0)` is called. - type ExportPhaseLimit: Get>; - - /// The number of blocks that the election should be ready before the election deadline. - #[pallet::constant] - type Lookhaead: Get>; - - /// The number of snapshot voters to fetch per block. - #[pallet::constant] - type VoterSnapshotPerBlock: Get; - - /// The number of snapshot targets to fetch per block. - #[pallet::constant] - type TargetSnapshotPerBlock: Get; - - /// Maximum number of supports (i.e. winners/validators/targets) that can be represented - /// in one page of a solution. - type MaxWinnersPerPage: Get; - - /// Maximum number of voters that can support a single target, across **ALL(()) the solution - /// pages. Thus, this can only be verified when processing the last solution page. - /// - /// This limit must be set so that the memory limits of the rest of the system are - /// respected. - type MaxBackersPerWinner: Get; - - /// The number of pages. - /// - /// A solution may contain at **MOST** this many pages. - #[pallet::constant] - type Pages: Get; - - /// Something that will provide the election data. - type DataProvider: LockableElectionDataProvider< - AccountId = Self::AccountId, - BlockNumber = BlockNumberFor, - >; - - // The miner configuration. - type MinerConfig: miner::Config< - AccountId = AccountIdOf, - Pages = Self::Pages, - MaxVotesPerVoter = ::MaxVotesPerVoter, - TargetSnapshotPerBlock = Self::TargetSnapshotPerBlock, - VoterSnapshotPerBlock = Self::VoterSnapshotPerBlock, - MaxWinnersPerPage = Self::MaxWinnersPerPage, - MaxBackersPerWinner = Self::MaxBackersPerWinner, - >; - - /// Something that implements a fallback election. - /// - /// This provider must run the election in one block, thus it has at most 1 page. - type Fallback: ElectionProvider< - AccountId = Self::AccountId, - BlockNumber = BlockNumberFor, - DataProvider = Self::DataProvider, - MaxWinnersPerPage = ::MaxWinnersPerPage, - MaxBackersPerWinner = ::MaxBackersPerWinner, - Pages = ConstU32<1>, - >; - - /// Something that implements an election solution verifier. - type Verifier: verifier::Verifier< - AccountId = Self::AccountId, - Solution = SolutionOf, - > + verifier::AsyncVerifier; - - /// Benchmarking configurations for this and sub-pallets. - type BenchmarkingConfig: BenchmarkingConfig; - - /// The weights for this pallet. - type WeightInfo: WeightInfo; - } - - // Expose miner configs over the metadata such that they can be re-implemented. - #[pallet::extra_constants] - impl Pallet { - #[pallet::constant_name(MinerMaxVotesPerVoter)] - fn max_votes_per_voter() -> u32 { - ::MaxVotesPerVoter::get() - } - - #[pallet::constant_name(MinerMaxBackersPerWinner)] - fn max_backers_per_winner() -> u32 { - ::MaxBackersPerWinner::get() - } - - #[pallet::constant_name(MinerMaxWinnersPerPage)] - fn max_winners_per_page() -> u32 { - ::MaxWinnersPerPage::get() - } - } - - /// Election failure strategy. - /// - /// This strategy defines the actions of this pallet once an election fails. - #[pallet::storage] - pub(crate) type ElectionFailure = - StorageValue<_, ElectionFailureStrategy, ValueQuery>; - - /// Current phase. - #[pallet::storage] - pub(crate) type CurrentPhase = StorageValue<_, Phase>, ValueQuery>; - - /// Current round. - #[pallet::storage] - pub(crate) type Round = StorageValue<_, u32, ValueQuery>; - - /// Target snapshot. - /// - /// Note: The target snapshot is single-paged. - #[pallet::storage] - pub(crate) type TargetSnapshot = StorageValue<_, TargetPageOf, OptionQuery>; - - /// Paginated voter snapshot. - #[pallet::storage] - pub(crate) type PagedVoterSnapshot = - StorageMap<_, Twox64Concat, PageIndex, VoterPageOf>; - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - /// There was a phase transition in a given round. - PhaseTransitioned { - from: Phase>, - to: Phase>, - round: u32, - }, - } - - #[pallet::error] - pub enum Error {} - - #[pallet::call] - impl Pallet {} - - #[pallet::hooks] - impl Hooks> for Pallet { - fn on_initialize(now: BlockNumberFor) -> Weight { - // ---------- ---------- ---------- ----------- ---------- -------- - // | | | | | | | - // Off Snapshot (Signed SigValid) Unsigned Export elect() - - let export_deadline = T::ExportPhaseLimit::get().saturating_add(T::Lookhaead::get()); - let unsigned_deadline = export_deadline.saturating_add(T::UnsignedPhase::get()); - let signed_validation_deadline = - unsigned_deadline.saturating_add(T::SignedValidationPhase::get()); - let signed_deadline = signed_validation_deadline.saturating_add(T::SignedPhase::get()); - let snapshot_deadline = signed_deadline - .saturating_add(T::Pages::get().into()) - .saturating_add(One::one()); - - let next_election = T::DataProvider::next_election_prediction(now) - .saturating_sub(T::Lookhaead::get()) - .max(now); - - let remaining_blocks = next_election - now; - let current_phase = Self::current_phase(); - - log!( - trace, - "now {:?} - current phase {:?} | \ - snapshot_deadline: {:?} (at #{:?}), signed_deadline: {:?} (at #{:?}), \ - signed_validation_deadline: {:?} (at #{:?}), unsigned_deadline: {:?} (at #{:?}) \ - export_deadline: {:?} (at #{:?}) - [next election at #{:?}, remaining: {:?}]", - now, - current_phase, - snapshot_deadline, - next_election.saturating_sub(snapshot_deadline), - signed_deadline, - next_election.saturating_sub(signed_deadline), - signed_validation_deadline, - next_election.saturating_sub(signed_validation_deadline), - unsigned_deadline, - next_election.saturating_sub(unsigned_deadline), - export_deadline, - next_election.saturating_sub(export_deadline), - next_election, - remaining_blocks, - ); - - match current_phase { - // start snapshot. - Phase::Off - // allocate one extra block for the (single-page) target snapshot. - if remaining_blocks <= snapshot_deadline && - remaining_blocks > signed_deadline => - Self::try_progress_snapshot(T::Pages::get() + 1), - - // continue snapshot. - Phase::Snapshot(x) if x > 0 => Self::try_progress_snapshot(x.saturating_sub(1)), - - // start unsigned phase if snapshot is ready and signed phase is disabled. - Phase::Snapshot(0) if T::SignedPhase::get().is_zero() => { - Self::phase_transition(Phase::Unsigned(now)); - T::WeightInfo::on_phase_transition() - }, - - // start signed phase. The `signed` sub-pallet will take further actions now. - Phase::Snapshot(0) - if remaining_blocks <= signed_deadline && - remaining_blocks > signed_validation_deadline => - Self::start_signed_phase(), - - // start signed validation. The `signed` sub-pallet will take further actions now. - Phase::Signed - if remaining_blocks <= signed_validation_deadline && - remaining_blocks > unsigned_deadline => - { - Self::phase_transition(Phase::SignedValidation(now)); - T::WeightInfo::on_phase_transition() - }, - - // start unsigned phase. The `unsigned` sub-pallet will take further actions now. - Phase::Signed | Phase::SignedValidation(_) | Phase::Snapshot(0) - if remaining_blocks <= unsigned_deadline && remaining_blocks > Zero::zero() => - { - Self::phase_transition(Phase::Unsigned(now)); - T::WeightInfo::on_phase_transition() - }, - - // start export phase. - Phase::Unsigned(_) if now == next_election.saturating_sub(export_deadline) => { - Self::phase_transition(Phase::Export(now)); - T::WeightInfo::on_phase_transition() - }, - - // election solution **MAY** be ready, start export phase to allow external pallets - // to request paged election solutions. - Phase::Export(started_at) => Self::do_export_phase(now, started_at), - - _ => T::WeightInfo::on_initialize_do_nothing(), - } - } - - fn integrity_test() { - // the signed validator phase must not be less than the number of pages of a - // submission. - assert!( - T::SignedValidationPhase::get() <= T::Pages::get().into(), - "signed validaton phase ({:?}) should not be less than the number of pages per submission ({:?})", - T::SignedValidationPhase::get(), - T::Pages::get(), - ); - } - } - - #[pallet::pallet] - pub struct Pallet(PhantomData); -} - -/// Wrapper struct for working with snapshots. -/// -/// It manages the following storage items: -/// -/// - [`PagedVoterSnapshot`]: Paginated map of voters. -/// - [`TargetSnapshot`]: Single page, bounded list of targets. -/// -/// To ensure correctness and data consistency, all the reads and writes to storage items related -/// to the snapshot and "wrapped" by this struct must be performed through the methods exposed by -/// the implementation of [`Snapshot`]. -pub(crate) struct Snapshot(sp_std::marker::PhantomData); -impl Snapshot { - /// Returns the targets snapshot. - /// - /// The target snapshot is single paged. - fn targets() -> Option> { - TargetSnapshot::::get() - } - - /// Sets a page of targets in the snapshot's storage. - /// - /// The target snapshot is single paged. - fn set_targets(targets: TargetPageOf) { - TargetSnapshot::::set(Some(targets)); - } - - /// Returns whether the target snapshot exists in storage. - fn targets_snapshot_exists() -> bool { - TargetSnapshot::::get().is_some() - } - - /// Returns the number of desired targets, as defined by [`T::DataProvider`]. - fn desired_targets() -> Option { - match T::DataProvider::desired_targets() { - Ok(desired) => Some(desired), - Err(err) => { - defensive!( - "error fetching the desired targets from the election data provider {}", - err - ); - None - }, - } - } - - /// Returns the voters of a specific `page` index of the current snapshot, if any. - fn voters(page: PageIndex) -> Option> { - PagedVoterSnapshot::::get(page) - } - - /// Sets a single page of voters in the snapshot's storage. - fn set_voters(page: PageIndex, voters: VoterPageOf) { - PagedVoterSnapshot::::insert(page, voters); - } - - /// Clears all data related to a snapshot. - /// - /// At the end of a round, all the snapshot related data must be cleared. Clearing the - /// snapshot data **MUST* only be performed only during `Phase::Off`. - fn kill() { - let _ = PagedVoterSnapshot::::clear(u32::MAX, None); - let _ = TargetSnapshot::::kill(); - - debug_assert_eq!(>::get(), Phase::Off); - } - - #[cfg(any(test, debug_assertions))] - #[allow(dead_code)] - pub(crate) fn ensure() -> Result<(), &'static str> { - let pages = T::Pages::get(); - ensure!(pages > 0, "number pages must be higer than 0."); - - // target snapshot exists (one page only); - ensure!(Self::targets().is_some(), "target snapshot does not exist."); - - // ensure that snapshot pages exist as expected. - for page in (crate::Pallet::::lsp()..=crate::Pallet::::msp()).rev() { - ensure!( - Self::voters(page).is_some(), - "at least one page of the snapshot does not exist" - ); - } - - Ok(()) - } -} - -impl Pallet { - /// Return the current election phase. - pub fn current_phase() -> Phase> { - >::get() - } - - /// Return the current election round. - pub fn current_round() -> u32 { - >::get() - } - - /// Phase transition helper. - pub(crate) fn phase_transition(to: Phase>) { - Self::deposit_event(Event::PhaseTransitioned { - from: >::get(), - to, - round: >::get(), - }); - >::put(to); - } - - /// Return the most significant page of the snapshot. - /// - /// Based on the contract with `ElectionDataProvider`, this is the first page to be requested - /// and filled. - pub fn msp() -> PageIndex { - T::Pages::get().checked_sub(1).defensive_unwrap_or_default() - } - - /// Return the least significant page of the snapshot. - /// - /// Based on the contract with `ElectionDataProvider`, this is the last page to be requested - /// and filled. - pub fn lsp() -> PageIndex { - Zero::zero() - } - - /// Creates and stores the target snapshot. - /// - /// Note: the target snapshot is single paged. - fn create_targets_snapshot() -> Result> { - let stored_count = Self::create_targets_snapshot_inner(T::TargetSnapshotPerBlock::get())?; - log!(trace, "created target snapshot with {} targets.", stored_count); - - Ok(stored_count) - } - - fn create_targets_snapshot_inner(targets_per_page: u32) -> Result> { - // set target count bound as the max number of targets per block. - let bounds = ElectionBoundsBuilder::default() - .targets_count(targets_per_page.into()) - .build() - .targets; - - let targets: BoundedVec<_, T::TargetSnapshotPerBlock> = - T::DataProvider::electable_targets(bounds, Zero::zero()) - .and_then(|t| { - t.try_into().map_err(|_| "too many targets returned by the data provider.") - }) - .map_err(|e| { - log!(error, "error fetching electable targets from data provider: {:?}", e); - ElectionError::::DataProvider - })?; - - let count = targets.len() as u32; - Snapshot::::set_targets(targets); - - Ok(count) - } - - /// Creates and store a single page of the voter snapshot. - fn create_voters_snapshot(remaining_pages: u32) -> Result> { - ensure!(remaining_pages < T::Pages::get(), ElectionError::::RequestedPageExceeded); - - let paged_voters_count = - Self::create_voters_snapshot_inner(remaining_pages, T::VoterSnapshotPerBlock::get())?; - log!(trace, "created voter snapshot with {} voters.", paged_voters_count); - - Ok(paged_voters_count) - } - - fn create_voters_snapshot_inner( - remaining_pages: u32, - voters_per_page: u32, - ) -> Result> { - // set voter count bound as the max number of voters per page. - let bounds = ElectionBoundsBuilder::default() - .voters_count(voters_per_page.into()) - .build() - .voters; - - let paged_voters: BoundedVec<_, T::VoterSnapshotPerBlock> = - T::DataProvider::electing_voters(bounds, remaining_pages) - .and_then(|v| { - v.try_into().map_err(|_| "too many voters returned by the data provider") - }) - .map_err(|_| ElectionError::::DataProvider)?; - - let count = paged_voters.len() as u32; - Snapshot::::set_voters(remaining_pages, paged_voters); - - Ok(count) - } - - /// Tries to progress the snapshot. - /// - /// The first call to this method will calculate and store the (single-paged) target snapshot. - /// The subsequent calls will fetch the voter pages. Thus, the caller must call this method - /// `T::Pages`..0 times. - fn try_progress_snapshot(remaining_pages: PageIndex) -> Weight { - let _ = ::set_lock(); - - debug_assert!( - CurrentPhase::::get().is_snapshot() || - !Snapshot::::targets_snapshot_exists() && - remaining_pages == T::Pages::get() + 1, - ); - - if !Snapshot::::targets_snapshot_exists() { - // first block for single target snapshot. - match Self::create_targets_snapshot() { - Ok(target_count) => { - log!(trace, "target snapshot created with {} targets", target_count); - Self::phase_transition(Phase::Snapshot(remaining_pages.saturating_sub(1))); - T::WeightInfo::create_targets_snapshot_paged(T::TargetSnapshotPerBlock::get()) - }, - Err(err) => { - log!(error, "error preparing targets snapshot: {:?}", err); - // TODO: T::WeightInfo::snapshot_error(); - Weight::default() - }, - } - } else { - // try progress voter snapshot. - match Self::create_voters_snapshot(remaining_pages) { - Ok(voter_count) => { - log!( - trace, - "voter snapshot progressed: page {} with {} voters", - remaining_pages, - voter_count, - ); - Self::phase_transition(Phase::Snapshot(remaining_pages)); - T::WeightInfo::create_voters_snapshot_paged(T::VoterSnapshotPerBlock::get()) - }, - Err(err) => { - log!(error, "error preparing voter snapshot: {:?}", err); - // TODO: T::WeightInfo::snapshot_error(); - Weight::default() - }, - } - } - } - - /// Start the signed phase. - /// We expect the snapshot to be ready by now. Thus the the data provider lock should be - /// released and transition to the signed phase. - pub(crate) fn start_signed_phase() -> Weight { - debug_assert!(Snapshot::::ensure().is_ok()); - - ::unlock(); - Self::phase_transition(Phase::Signed); - - T::WeightInfo::on_initialize_start_signed() - } - - /// Export phase. - /// - /// In practice, we just need to ensure the export phase does not remain open for too long. - /// During this phase, we expect the external entities to call [`ElectionProvider::elect`] for - /// all the solution pages. Once the least significant page is called, the phase should - /// transition to `Phase::Off`. Thus, if the export phase remains open for too long, it means - /// that the election failed. - pub(crate) fn do_export_phase(now: BlockNumberFor, started_at: BlockNumberFor) -> Weight { - debug_assert!(Pallet::::current_phase().is_export()); - - if now > started_at + T::ExportPhaseLimit::get() { - log!( - error, - "phase `Export` has been open for too long ({:?} blocks). election round failed.", - T::ExportPhaseLimit::get(), - ); - - match ElectionFailure::::get() { - ElectionFailureStrategy::Restart => Self::reset_round_restart(), - ElectionFailureStrategy::Emergency => Self::phase_transition(Phase::Emergency), - } - } - - T::WeightInfo::on_initialize_start_export() - } - - /// Performs all tasks required after a successful election: - /// - /// 1. Increment round. - /// 2. Change phase to [`Phase::Off`]. - /// 3. Clear all snapshot data. - /// 4. Resets verifier. - fn rotate_round() { - >::mutate(|r| r.defensive_saturating_accrue(1)); - Self::phase_transition(Phase::Off); - - Snapshot::::kill(); - ::kill(); - } - - /// Performs all tasks required after an unsuccessful election which should be self-healing - /// (i.e. the election should restart without entering in emergency phase). - /// - /// Note: the round should not restart as the previous election failed. - /// - /// 1. Change phase to [`Phase::Off`]. - /// 2. Clear all snapshot data. - /// 3. Resets verifier. - fn reset_round_restart() { - Self::phase_transition(Phase::Off); - - Snapshot::::kill(); - ::kill(); - } -} - -impl ElectionProvider for Pallet { - type AccountId = T::AccountId; - type BlockNumber = BlockNumberFor; - type Error = ElectionError; - type MaxWinnersPerPage = ::MaxWinnersPerPage; - type MaxBackersPerWinner = ::MaxBackersPerWinner; - type Pages = T::Pages; - type DataProvider = T::DataProvider; - - /// Important note: we do exect the caller of `elect` to call pages down to `lsp == 0`. - /// Otherwise the export phase will not explicitly finish which will result in a failed - /// election. - fn elect(remaining: PageIndex) -> Result, Self::Error> { - ensure!(Pallet::::current_phase().is_export(), ElectionError::ElectionNotReady); - - T::Verifier::get_queued_solution(remaining) - .ok_or(ElectionError::::SupportPageNotAvailable(remaining)) - .or_else(|err| { - log!( - error, - "elect(): (page {:?}) election provider failed due to {:?}, trying fallback.", - remaining, - err - ); - T::Fallback::elect(remaining).map_err(|fe| ElectionError::::Fallback(fe)) - }) - .map(|supports| { - if remaining.is_zero() { - log!(trace, "elect(): provided the last supports page, rotating round."); - Self::rotate_round(); - } else { - // Phase::Export is on while the election is calling all pages of `elect`. - if !Self::current_phase().is_export() { - let now = >::block_number(); - Self::phase_transition(Phase::Export(now)); - } - } - supports.into() - }) - .map_err(|err| { - log!(error, "elect(): fetching election page {} and fallback failed.", remaining); - - match ElectionFailure::::get() { - // force emergency phase for testing. - ElectionFailureStrategy::Restart => Self::reset_round_restart(), - ElectionFailureStrategy::Emergency => Self::phase_transition(Phase::Emergency), - } - err - }) - } - - fn ongoing() -> bool { - match CurrentPhase::::get() { - Phase::Off => false, - _ => true, - } - } -} - -#[cfg(test)] -mod phase_transition { - use super::*; - use crate::mock::*; - - use frame_support::assert_ok; - - #[test] - fn single_page() { - let (mut ext, _) = ExtBuilder::default() - .pages(1) - .signed_phase(3) - .validate_signed_phase(1) - .lookahead(0) - .build_offchainify(1); - - ext.execute_with(|| { - assert_eq!(System::block_number(), 0); - assert_eq!(Pages::get(), 1); - assert_eq!(>::get(), 0); - assert_eq!(>::get(), Phase::Off); - - let next_election = <::DataProvider as ElectionDataProvider>::next_election_prediction( - System::block_number() - ); - assert_eq!(next_election, 30); - - // representing the blocknumber when the phase transition happens. - let export_deadline = next_election - (ExportPhaseLimit::get() + Lookhaead::get()); - let expected_unsigned = export_deadline - UnsignedPhase::get(); - let expected_validate = expected_unsigned - SignedValidationPhase::get(); - let expected_signed = expected_validate - SignedPhase::get(); - let expected_snapshot = expected_signed - Pages::get() as u64; - - // tests transition phase boundaries. - roll_to(expected_snapshot); - assert_eq!(>::get(), Phase::Snapshot(Pages::get() - 1)); - - roll_to(expected_signed); - assert_eq!(>::get(), Phase::Signed); - - roll_to(expected_validate); - let start_validate = System::block_number(); - assert_eq!(>::get(), Phase::SignedValidation(start_validate)); - - roll_to(expected_unsigned); - let start_unsigned = System::block_number(); - assert_eq!(>::get(), Phase::Unsigned(start_unsigned)); - - // roll to export phase to call elect(). - roll_to_export(); - - // elect() should work. - assert_ok!(MultiPhase::elect(0)); - - // one page only -- election done, go to off phase. - assert_eq!(>::get(), Phase::Off); - }) - } - - #[test] - fn multi_page() { - let (mut ext, _) = ExtBuilder::default() - .pages(2) - .signed_phase(3) - .validate_signed_phase(1) - .lookahead(0) - .build_offchainify(1); - - ext.execute_with(|| { - assert_eq!(System::block_number(), 0); - assert_eq!(Pages::get(), 2); - assert_eq!(>::get(), 0); - assert_eq!(>::get(), Phase::Off); - - let next_election = <::DataProvider as ElectionDataProvider>::next_election_prediction( - System::block_number() - ); - assert_eq!(next_election, 30); - - // representing the blocknumber when the phase transition happens. - let export_deadline = next_election - (ExportPhaseLimit::get() + Lookhaead::get()); - let expected_unsigned = export_deadline - UnsignedPhase::get(); - let expected_validate = expected_unsigned - SignedValidationPhase::get(); - let expected_signed = expected_validate - SignedPhase::get(); - let expected_snapshot = expected_signed - Pages::get() as u64; - - // two blocks for snapshot. - roll_to(expected_snapshot); - assert_eq!(>::get(), Phase::Snapshot(Pages::get() - 1)); - - roll_to(expected_snapshot + 1); - assert_eq!(>::get(), Phase::Snapshot(0)); - - // ensure snapshot is sound by end of snapshot phase. - assert_ok!(Snapshot::::ensure()); - - roll_to(expected_signed); - assert_eq!(>::get(), Phase::Signed); - - roll_to(expected_signed + 1); - assert_eq!(>::get(), Phase::Signed); - - // two blocks for validate signed. - roll_to(expected_validate); - let start_validate = System::block_number(); - assert_eq!(>::get(), Phase::SignedValidation(start_validate)); - - // now in unsigned until elect() is called. - roll_to(expected_validate + 2); - let start_unsigned = System::block_number(); - assert_eq!(>::get(), Phase::Unsigned(start_unsigned - 1)); - - }) - } - - #[test] - fn emergency_phase_works() { - let (mut ext, _) = ExtBuilder::default().build_offchainify(1); - ext.execute_with(|| { - let next_election = <::DataProvider as ElectionDataProvider>::next_election_prediction( - System::block_number() - ); - - // if election fails, enters in emergency phase. - ElectionFailure::::set(ElectionFailureStrategy::Emergency); - - compute_snapshot_checked(); - roll_to(next_election); - - // election will fail due to inexistent solution. - assert!(MultiPhase::elect(Pallet::::msp()).is_err()); - // thus entering in emergency phase. - assert_eq!(>::get(), Phase::Emergency); - }) - } - - #[test] - fn restart_after_elect_fails_works() { - let (mut ext, _) = ExtBuilder::default().build_offchainify(1); - ext.execute_with(|| { - let next_election = <::DataProvider as ElectionDataProvider>::next_election_prediction( - System::block_number() - ); - - // if election fails, restart the election round. - ElectionFailure::::set(ElectionFailureStrategy::Restart); - - compute_snapshot_checked(); - roll_to(next_election); - - // election will fail due to inexistent solution. - assert!(MultiPhase::elect(Pallet::::msp()).is_err()); - // thus restarting from Off phase. - assert_eq!(>::get(), Phase::Off); - }) - } -} - -#[cfg(test)] -mod snapshot { - use super::*; - use crate::mock::*; - - use frame_support::{assert_noop, assert_ok}; - - #[test] - fn setters_getters_work() { - ExtBuilder::default().pages(2).build_and_execute(|| { - let t = BoundedVec::<_, _>::try_from(vec![]).unwrap(); - let v = BoundedVec::<_, _>::try_from(vec![]).unwrap(); - - assert!(Snapshot::::targets().is_none()); - assert!(Snapshot::::voters(0).is_none()); - assert!(Snapshot::::voters(1).is_none()); - - Snapshot::::set_targets(t.clone()); - assert!(Snapshot::::targets().is_some()); - - Snapshot::::set_voters(0, v.clone()); - Snapshot::::set_voters(1, v.clone()); - - assert!(Snapshot::::voters(0).is_some()); - assert!(Snapshot::::voters(1).is_some()); - - // ensure snapshot is sound. - assert_ok!(Snapshot::::ensure()); - - Snapshot::::kill(); - assert!(Snapshot::::targets().is_none()); - assert!(Snapshot::::voters(0).is_none()); - assert!(Snapshot::::voters(1).is_none()); - }) - } - - #[test] - fn targets_voters_snapshot_boundary_checks_works() { - ExtBuilder::default().build_and_execute(|| { - assert_eq!(Pages::get(), 3); - assert_eq!(MultiPhase::msp(), 2); - assert_eq!(MultiPhase::lsp(), 0); - - assert_ok!(MultiPhase::create_targets_snapshot()); - - assert_ok!(MultiPhase::create_voters_snapshot(2)); - assert_ok!(MultiPhase::create_voters_snapshot(1)); - assert_ok!(MultiPhase::create_voters_snapshot(0)); - - assert_noop!( - MultiPhase::create_voters_snapshot(3), - ElectionError::::RequestedPageExceeded - ); - assert_noop!( - MultiPhase::create_voters_snapshot(10), - ElectionError::::RequestedPageExceeded - ); - }) - } - - #[test] - fn create_targets_snapshot_works() { - ExtBuilder::default().build_and_execute(|| { - assert_eq!(MultiPhase::msp(), 2); - - let no_bounds = ElectionBoundsBuilder::default().build().targets; - let all_targets = - ::electable_targets(no_bounds, 0); - assert_eq!(all_targets.unwrap(), Targets::get()); - assert_eq!(Targets::get().len(), 8); - - // sets max targets per page to 2. - TargetSnapshotPerBlock::set(2); - - let result_and_count = MultiPhase::create_targets_snapshot(); - assert_eq!(result_and_count.unwrap(), 2); - assert_eq!(Snapshot::::targets().unwrap().to_vec(), vec![10, 20]); - - // sets max targets per page to 4. - TargetSnapshotPerBlock::set(4); - - let result_and_count = MultiPhase::create_targets_snapshot(); - assert_eq!(result_and_count.unwrap(), 4); - assert_eq!(Snapshot::::targets().unwrap().to_vec(), vec![10, 20, 30, 40]); - - Snapshot::::kill(); - - TargetSnapshotPerBlock::set(6); - - let result_and_count = MultiPhase::create_targets_snapshot(); - assert_eq!(result_and_count.unwrap(), 6); - assert_eq!(Snapshot::::targets().unwrap().to_vec(), vec![10, 20, 30, 40, 50, 60]); - - // reset storage. - Snapshot::::kill(); - }) - } - - #[test] - fn voters_snapshot_works() { - ExtBuilder::default().build_and_execute(|| { - assert_eq!(MultiPhase::msp(), 2); - - let no_bounds = ElectionBoundsBuilder::default().build().voters; - let all_voters = ::electing_voters(no_bounds, 0); - assert_eq!(all_voters.unwrap(), Voters::get()); - assert_eq!(Voters::get().len(), 16); - - // sets max voters per page to 7. - VoterSnapshotPerBlock::set(7); - - let voters_page = |page: PageIndex| { - Snapshot::::voters(page) - .unwrap() - .iter() - .map(|v| v.0) - .collect::>() - }; - - // page `msp`. - let result_and_count = MultiPhase::create_voters_snapshot(MultiPhase::msp()); - assert_eq!(result_and_count.unwrap(), 7); - assert_eq!(voters_page(MultiPhase::msp()), vec![1, 2, 3, 4, 5, 6, 7]); - - let result_and_count = MultiPhase::create_voters_snapshot(1); - assert_eq!(result_and_count.unwrap(), 7); - assert_eq!(voters_page(1), vec![8, 10, 20, 30, 40, 50, 60]); - - // page `lsp`. - let result_and_count = MultiPhase::create_voters_snapshot(MultiPhase::lsp()); - assert_eq!(result_and_count.unwrap(), 2); - assert_eq!(voters_page(MultiPhase::lsp()), vec![70, 80]); - }) - } - - #[test] - fn try_progress_snapshot_works() {} -} - -#[cfg(test)] -mod election_provider { - use super::*; - use crate::{mock::*, unsigned::miner::Miner}; - use frame_support::testing_prelude::*; - - #[test] - fn snapshot_to_supports_conversions_work() { - type VotersPerPage = ::VoterSnapshotPerBlock; - type TargetsPerPage = ::TargetSnapshotPerBlock; - type Pages = ::Pages; - - ExtBuilder::default() - .pages(2) - .snasphot_voters_page(4) - .snasphot_targets_page(4) - .desired_targets(2) - .build_and_execute(|| { - assert_eq!(MultiPhase::msp(), 1); - - let all_targets: BoundedVec = - bounded_vec![10, 20, 30, 40]; - - let all_voter_pages: BoundedVec< - BoundedVec, VotersPerPage>, - Pages, - > = bounded_vec![ - bounded_vec![ - (1, 100, bounded_vec![10, 20]), - (2, 20, bounded_vec![30]), - (3, 30, bounded_vec![10]), - (10, 10, bounded_vec![10]) - ], - bounded_vec![ - (20, 20, bounded_vec![20]), - (30, 30, bounded_vec![30]), - (40, 40, bounded_vec![40]) - ], - ]; - - Snapshot::::set_targets(all_targets.clone()); - Snapshot::::set_voters(0, all_voter_pages[0].clone()); - Snapshot::::set_voters(1, all_voter_pages[1].clone()); - - let desired_targets = Snapshot::::desired_targets().unwrap(); - let (results, _) = Miner::::mine_paged_solution_with_snapshot( - &all_voter_pages, - &all_targets, - Pages::get(), - current_round(), - desired_targets, - false, - ) - .unwrap(); - - let supports_page_zero = - PalletVerifier::::feasibility_check(results.solution_pages[0].clone(), 0) - .unwrap(); - let supports_page_one = - PalletVerifier::::feasibility_check(results.solution_pages[1].clone(), 1) - .unwrap(); - - use frame_election_provider_support::{BoundedSupports, TryIntoBoundedSupports}; - use sp_npos_elections::{Support, Supports}; - - let s0: Supports = vec![ - (10, Support { total: 90, voters: vec![(3, 30), (10, 10), (1, 50)] }), - (20, Support { total: 50, voters: vec![(1, 50)] }), - ]; - let bs0: BoundedSupports<_, _, _> = s0.try_into_bounded_supports().unwrap(); - - let s1: Supports = - vec![(20, Support { total: 20, voters: vec![(20, 20)] })]; - let bs1: BoundedSupports<_, _, _> = s1.try_into_bounded_supports().unwrap(); - - assert_eq!(supports_page_zero, bs0); - assert_eq!(supports_page_one, bs1); - }) - } -} diff --git a/substrate/frame/election-provider-multi-block/src/mock/mod.rs b/substrate/frame/election-provider-multi-block/src/mock/mod.rs deleted file mode 100644 index 3a564adaa515c..0000000000000 --- a/substrate/frame/election-provider-multi-block/src/mock/mod.rs +++ /dev/null @@ -1,624 +0,0 @@ -// 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. - -#![allow(unused)] - -mod staking; - -use frame_election_provider_support::{bounds::ElectionBounds, onchain, SequentialPhragmen}; -use sp_npos_elections::ElectionScore; -pub use staking::*; - -use crate::{ - self as epm, - signed::{self as signed_pallet, HoldReason}, - unsigned::{ - self as unsigned_pallet, - miner::{self, Miner, MinerError, OffchainWorkerMiner}, - }, - verifier::{self as verifier_pallet}, - Config, *, -}; -use frame_support::{ - derive_impl, pallet_prelude::*, parameter_types, traits::fungible::InspectHold, -}; -use parking_lot::RwLock; -use sp_runtime::{ - offchain::{ - testing::{PoolState, TestOffchainExt, TestTransactionPoolExt}, - OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, - }, - BuildStorage, Perbill, -}; -use std::sync::Arc; - -frame_support::construct_runtime!( - pub struct Runtime { - System: frame_system, - Balances: pallet_balances, - MultiPhase: epm, - VerifierPallet: verifier_pallet, - SignedPallet: signed_pallet, - UnsignedPallet: unsigned_pallet, - } -); - -pub type AccountId = u64; -pub type Balance = u128; -pub type BlockNumber = u64; -pub type VoterIndex = u32; -pub type TargetIndex = u16; -pub type T = Runtime; -pub type Block = frame_system::mocking::MockBlock; -pub(crate) type Solver = SequentialPhragmen; - -frame_election_provider_support::generate_solution_type!( - #[compact] - pub struct TestNposSolution::< - VoterIndex = VoterIndex, - TargetIndex = TargetIndex, - Accuracy = sp_runtime::PerU16, - MaxVoters = frame_support::traits::ConstU32::<2_000> - >(16) -); - -#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] -impl frame_system::Config for Runtime { - type Block = Block; - type AccountData = pallet_balances::AccountData; -} - -parameter_types! { - pub const ExistentialDeposit: Balance = 1; -} - -impl pallet_balances::Config for Runtime { - type Balance = Balance; - type RuntimeEvent = RuntimeEvent; - type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - type MaxLocks = (); - type MaxReserves = (); - type ReserveIdentifier = [u8; 8]; - type WeightInfo = (); - type FreezeIdentifier = (); - type MaxFreezes = (); - type DoneSlashHandler = (); - type RuntimeHoldReason = RuntimeHoldReason; - type RuntimeFreezeReason = (); -} - -parameter_types! { - pub static SignedPhase: BlockNumber = 3; - pub static UnsignedPhase: BlockNumber = 5; - pub static SignedValidationPhase: BlockNumber = Pages::get().into(); - pub static Lookhaead: BlockNumber = 0; - pub static VoterSnapshotPerBlock: VoterIndex = 5; - pub static TargetSnapshotPerBlock: TargetIndex = 8; - pub static Pages: PageIndex = 3; - pub static ExportPhaseLimit: BlockNumber = (Pages::get() * 2u32).into(); -} - -pub struct EPMBenchmarkingConfigs; -impl BenchmarkingConfig for EPMBenchmarkingConfigs { - const VOTERS: u32 = 100; - const TARGETS: u32 = 50; - const VOTERS_PER_PAGE: [u32; 2] = [1, 5]; - const TARGETS_PER_PAGE: [u32; 2] = [1, 8]; -} - -impl Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type SignedPhase = SignedPhase; - type UnsignedPhase = UnsignedPhase; - type SignedValidationPhase = SignedValidationPhase; - type Lookhaead = Lookhaead; - type VoterSnapshotPerBlock = VoterSnapshotPerBlock; - type TargetSnapshotPerBlock = TargetSnapshotPerBlock; - type MaxBackersPerWinner = MaxBackersPerWinner; - type MaxWinnersPerPage = MaxWinnersPerPage; - type Pages = Pages; - type ExportPhaseLimit = ExportPhaseLimit; - type DataProvider = MockStaking; - type MinerConfig = Self; - type Fallback = MockFallback; - type Verifier = VerifierPallet; - type BenchmarkingConfig = EPMBenchmarkingConfigs; - type WeightInfo = (); -} - -parameter_types! { - pub static SolutionImprovementThreshold: Perbill = Perbill::zero(); - pub static MaxWinnersPerPage: u32 = 100; - pub static MaxBackersPerWinner: u32 = 1000; -} - -impl crate::verifier::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type ForceOrigin = frame_system::EnsureRoot; - type SolutionImprovementThreshold = SolutionImprovementThreshold; - type SolutionDataProvider = SignedPallet; - type WeightInfo = (); -} - -parameter_types! { - pub static DepositBase: Balance = 10; - pub static DepositPerPage: Balance = 1; - pub static Reward: Balance = 10; - pub static MaxSubmissions: u32 = 5; -} - -impl crate::signed::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type EstimateCallFee = ConstU32<8>; - type OnSlash = (); // burn - type DepositBase = ConstDepositBase; - type DepositPerPage = DepositPerPage; - type Reward = Reward; - type MaxSubmissions = MaxSubmissions; - type RuntimeHoldReason = RuntimeHoldReason; - type WeightInfo = (); -} - -parameter_types! { - pub OffchainRepeatInterval: BlockNumber = 10; - pub MinerTxPriority: u64 = 0; - pub MinerSolutionMaxLength: u32 = 10; - pub MinerSolutionMaxWeight: Weight = Default::default(); -} - -impl crate::unsigned::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type OffchainRepeatInterval = OffchainRepeatInterval; - type MinerTxPriority = MinerTxPriority; - type MaxLength = MinerSolutionMaxLength; - type MaxWeight = MinerSolutionMaxWeight; - type WeightInfo = (); -} - -impl miner::Config for Runtime { - type AccountId = AccountId; - type Solution = TestNposSolution; - type Solver = Solver; - type Pages = Pages; - type MaxVotesPerVoter = MaxVotesPerVoter; - type MaxWinnersPerPage = MaxWinnersPerPage; - type MaxBackersPerWinner = MaxBackersPerWinner; - type VoterSnapshotPerBlock = VoterSnapshotPerBlock; - type TargetSnapshotPerBlock = TargetSnapshotPerBlock; - type MaxWeight = MinerSolutionMaxWeight; - type MaxLength = MinerSolutionMaxLength; -} - -pub type Extrinsic = sp_runtime::testing::TestXt; - -impl frame_system::offchain::CreateTransactionBase for Runtime -where - RuntimeCall: From, -{ - type RuntimeCall = RuntimeCall; - type Extrinsic = Extrinsic; -} - -impl frame_system::offchain::CreateInherent for Runtime -where - RuntimeCall: From, -{ - fn create_inherent(call: Self::RuntimeCall) -> Self::Extrinsic { - Extrinsic::new_bare(call) - } -} - -pub struct ConstDepositBase; -impl sp_runtime::traits::Convert for ConstDepositBase { - fn convert(_a: usize) -> Balance { - DepositBase::get() - } -} - -parameter_types! { - pub static OnChainElectionBounds: ElectionBounds = ElectionBoundsBuilder::default().build(); - pub static MaxVotesPerVoter: u32 = ::LIMIT as u32; - pub static FallbackEnabled: bool = true; -} - -impl onchain::Config for Runtime { - type System = Runtime; - type Solver = Solver; - type MaxWinnersPerPage = MaxWinnersPerPage; - type MaxBackersPerWinner = MaxBackersPerWinner; - type Bounds = OnChainElectionBounds; - type DataProvider = MockStaking; - type WeightInfo = (); -} - -pub struct MockFallback; -impl ElectionProvider for MockFallback { - type AccountId = AccountId; - type BlockNumber = BlockNumberFor; - type Error = &'static str; - type DataProvider = MockStaking; - type Pages = ConstU32<1>; - type MaxWinnersPerPage = MaxWinnersPerPage; - type MaxBackersPerWinner = MaxBackersPerWinner; - - fn elect(remaining: PageIndex) -> Result, Self::Error> { - if FallbackEnabled::get() { - onchain::OnChainExecution::::elect(remaining) - .map_err(|_| "fallback election failed") - } else { - Err("fallback election failed (forced in mock)") - } - } - - fn ongoing() -> bool { - false - } -} - -#[derive(Default)] -pub struct ExtBuilder { - with_verifier: bool, -} - -// TODO(gpestana): separate ext builder into separate builders for each pallet. -impl ExtBuilder { - pub(crate) fn pages(self, pages: u32) -> Self { - Pages::set(pages); - self - } - - pub(crate) fn snasphot_voters_page(self, voters: VoterIndex) -> Self { - VoterSnapshotPerBlock::set(voters); - self - } - - pub(crate) fn snasphot_targets_page(self, targets: TargetIndex) -> Self { - TargetSnapshotPerBlock::set(targets); - self - } - - pub(crate) fn signed_phase(self, blocks: BlockNumber) -> Self { - SignedPhase::set(blocks); - self - } - - pub(crate) fn validate_signed_phase(self, blocks: BlockNumber) -> Self { - SignedValidationPhase::set(blocks); - self - } - - pub(crate) fn unsigned_phase(self, blocks: BlockNumber) -> Self { - UnsignedPhase::set(blocks); - self - } - - pub(crate) fn lookahead(self, blocks: BlockNumber) -> Self { - Lookhaead::set(blocks); - self - } - - pub(crate) fn max_winners_per_page(self, max: u32) -> Self { - MaxWinnersPerPage::set(max); - self - } - - pub(crate) fn max_backers_per_winner(self, max: u32) -> Self { - MaxBackersPerWinner::set(max); - self - } - - pub(crate) fn desired_targets(self, desired: u32) -> Self { - DesiredTargets::set(desired); - self - } - - pub(crate) fn signed_max_submissions(self, max: u32) -> Self { - MaxSubmissions::set(max); - self - } - - pub(crate) fn solution_improvements_threshold(self, threshold: Perbill) -> Self { - SolutionImprovementThreshold::set(threshold); - self - } - - pub(crate) fn verifier() -> Self { - ExtBuilder { with_verifier: true } - } - - pub(crate) fn build(self) -> sp_io::TestExternalities { - sp_tracing::try_init_simple(); - - let mut storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); - let _ = pallet_balances::GenesisConfig:: { - balances: vec![ - (10, 100_000), - (20, 100_000), - (30, 100_000), - (40, 100_000), - (50, 100_000), - (60, 100_000), - (70, 100_000), - (80, 100_000), - (90, 100_000), - (91, 100), - (92, 100), - (93, 100), - (94, 100), - (95, 100), - (96, 100), - (97, 100), - (99, 100), - (999, 100), - (9999, 100), - ], - } - .assimilate_storage(&mut storage); - - if self.with_verifier { - // nothing special for now - } - - sp_io::TestExternalities::from(storage) - } - - pub fn build_offchainify( - self, - iters: u32, - ) -> (sp_io::TestExternalities, Arc>) { - let mut ext = self.build(); - let (offchain, offchain_state) = TestOffchainExt::new(); - let (pool, pool_state) = TestTransactionPoolExt::new(); - - let mut seed = [0_u8; 32]; - seed[0..4].copy_from_slice(&iters.to_le_bytes()); - offchain_state.write().seed = seed; - - ext.register_extension(OffchainDbExt::new(offchain.clone())); - ext.register_extension(OffchainWorkerExt::new(offchain)); - ext.register_extension(TransactionPoolExt::new(pool)); - - (ext, pool_state) - } - - pub(crate) fn build_and_execute(self, test: impl FnOnce() -> ()) { - let mut ext = self.build(); - ext.execute_with(test); - - #[cfg(feature = "try-runtime")] - ext.execute_with(|| { - //MultiPhase::do_try_state().unwrap(); - // etc.. - - let _ = VerifierPallet::do_try_state() - .map_err(|err| println!(" 🕵️‍♂️ Verifier `try_state` failure: {:?}", err)); - }); - } -} - -pub(crate) fn compute_snapshot_checked() { - let msp = crate::Pallet::::msp(); - - for page in (0..=Pages::get()).rev() { - CurrentPhase::::set(Phase::Snapshot(page)); - crate::Pallet::::try_progress_snapshot(page); - - assert!(Snapshot::::targets_snapshot_exists()); - - if page <= msp { - assert!(Snapshot::::voters(page).is_some()); - } - } -} - -pub(crate) fn mine_and_verify_all() -> Result< - Vec< - frame_election_provider_support::BoundedSupports< - AccountId, - MaxWinnersPerPage, - MaxBackersPerWinner, - >, - >, - &'static str, -> { - let msp = crate::Pallet::::msp(); - let mut paged_supports = vec![]; - - for page in (0..=msp).rev() { - let (_, score, solution) = - OffchainWorkerMiner::::mine(page).map_err(|e| "error mining")?; - - let supports = - ::verify_synchronous(solution, score, page) - .map_err(|_| "error verifying paged solution")?; - - paged_supports.push(supports); - } - - Ok(paged_supports) -} - -pub(crate) fn roll_to(n: BlockNumber) { - for bn in (System::block_number()) + 1..=n { - System::set_block_number(bn); - - MultiPhase::on_initialize(bn); - VerifierPallet::on_initialize(bn); - SignedPallet::on_initialize(bn); - UnsignedPallet::on_initialize(bn); - UnsignedPallet::offchain_worker(bn); - - // TODO: add try-checks for all pallets here too, as we progress the blocks. - log!( - trace, - "Block: {}, Phase: {:?}, Round: {:?}, Election at {:?}", - bn, - >::get(), - >::get(), - election_prediction() - ); - } -} - -// Fast forward until a given election phase. -pub fn roll_to_phase(phase: Phase) { - while MultiPhase::current_phase() != phase { - roll_to(System::block_number() + 1); - } -} - -pub fn roll_to_export() { - while !MultiPhase::current_phase().is_export() { - roll_to(System::block_number() + 1); - } -} - -pub fn roll_one_with_ocw(maybe_pool: Option>>) { - use sp_runtime::traits::Dispatchable; - let bn = System::block_number() + 1; - // if there's anything in the submission pool, submit it. - if let Some(ref pool) = maybe_pool { - pool.read() - .transactions - .clone() - .into_iter() - .map(|uxt| ::decode(&mut &*uxt).unwrap()) - .for_each(|xt| { - xt.function.dispatch(frame_system::RawOrigin::None.into()).unwrap(); - }); - pool.try_write().unwrap().transactions.clear(); - } - - roll_to(bn); -} - -pub fn roll_to_phase_with_ocw( - phase: Phase, - maybe_pool: Option>>, -) { - while MultiPhase::current_phase() != phase { - roll_one_with_ocw(maybe_pool.clone()); - } -} - -pub fn roll_to_with_ocw(n: BlockNumber, maybe_pool: Option>>) { - let now = System::block_number(); - for _i in now + 1..=n { - roll_one_with_ocw(maybe_pool.clone()); - } -} - -pub fn election_prediction() -> BlockNumber { - <::DataProvider as ElectionDataProvider>::next_election_prediction( - System::block_number(), - ) -} - -pub fn current_phase() -> Phase { - MultiPhase::current_phase() -} - -pub fn current_round() -> u32 { - Pallet::::current_round() -} - -pub fn call_elect() -> Result<(), crate::ElectionError> { - for p in (0..=Pallet::::msp()).rev() { - ::elect(p)?; - } - Ok(()) -} - -pub fn assert_snapshots() -> Result<(), &'static str> { - Snapshot::::ensure() -} - -pub fn clear_snapshot() { - let _ = crate::PagedVoterSnapshot::::clear(u32::MAX, None); - let _ = crate::TargetSnapshot::::kill(); -} - -/// Returns the free balance, and the total on-hold for the election submissions. -pub fn balances(who: AccountId) -> (Balance, Balance) { - ( - Balances::free_balance(who), - Balances::balance_on_hold(&HoldReason::ElectionSolutionSubmission.into(), &who), - ) -} - -pub fn mine_full(pages: PageIndex) -> Result, MinerError> { - let (targets, voters) = - OffchainWorkerMiner::::fetch_snapshots().map_err(|_| MinerError::DataProvider)?; - - let reduce = false; - let round = crate::Pallet::::current_round(); - let desired_targets = ::desired_targets() - .map_err(|_| MinerError::DataProvider)?; - - Miner::::mine_paged_solution_with_snapshot( - &targets, - &voters, - Pages::get(), - round, - desired_targets, - reduce, - ) - .map(|(s, _)| s) -} - -pub fn mine( - page: PageIndex, -) -> Result<(ElectionScore, SolutionOf<::MinerConfig>), ()> { - let (_, partial_score, partial_solution) = - OffchainWorkerMiner::::mine(page).map_err(|_| ())?; - - Ok((partial_score, partial_solution)) -} - -// Pallet events filters. - -pub(crate) fn unsigned_events() -> Vec> { - System::events() - .into_iter() - .map(|r| r.event) - .filter_map( - |e| if let RuntimeEvent::UnsignedPallet(inner) = e { Some(inner) } else { None }, - ) - .collect() -} - -pub(crate) fn signed_events() -> Vec> { - System::events() - .into_iter() - .map(|r| r.event) - .filter_map(|e| if let RuntimeEvent::SignedPallet(inner) = e { Some(inner) } else { None }) - .collect() -} - -// TODO fix or use macro. -pub(crate) fn filter_events( - types: Vec, -) -> Vec { - System::events() - .into_iter() - .map(|r| r.event) - .filter_map(|e| if types.contains(&e) { Some(e) } else { None }) - .collect() -} diff --git a/substrate/frame/election-provider-multi-block/src/mock/staking.rs b/substrate/frame/election-provider-multi-block/src/mock/staking.rs deleted file mode 100644 index 52fc297700420..0000000000000 --- a/substrate/frame/election-provider-multi-block/src/mock/staking.rs +++ /dev/null @@ -1,235 +0,0 @@ -// 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 sp_runtime::bounded_vec; - -use frame_election_provider_support::{ - bounds::CountBound, data_provider, DataProviderBounds, ElectionDataProvider, - LockableElectionDataProvider, PageIndex, VoterOf as VoterOfProvider, -}; - -use super::{AccountId, BlockNumber, MaxVotesPerVoter, T}; - -// alias for a voter of EPM-MB. -type VoterOf = frame_election_provider_support::VoterOf<::DataProvider>; - -frame_support::parameter_types! { - pub static Targets: Vec = vec![10, 20, 30, 40, 50, 60, 70, 80]; - pub static Voters: Vec> = vec![ - (1, 10, bounded_vec![10, 20]), - (2, 10, bounded_vec![30, 40]), - (3, 10, bounded_vec![40]), - (4, 10, bounded_vec![10, 20, 40]), - (5, 10, bounded_vec![10, 30, 40]), - (6, 10, bounded_vec![20, 30, 40]), - (7, 10, bounded_vec![20, 30]), - (8, 10, bounded_vec![10]), - (10, 10, bounded_vec![10]), - (20, 20, bounded_vec![20]), - (30, 30, bounded_vec![30]), - (40, 40, bounded_vec![40]), - (50, 50, bounded_vec![50]), - (60, 60, bounded_vec![60]), - (70, 70, bounded_vec![70]), - (80, 80, bounded_vec![80]), - ]; - pub static EpochLength: u64 = 30; - pub static DesiredTargets: u32 = 5; - - pub static LastIteratedTargetIndex: Option = None; - pub static LastIteratedVoterIndex: Option = None; - - pub static ElectionDataLock: Option<()> = None; // not locker. -} - -pub struct MockStaking; -impl ElectionDataProvider for MockStaking { - type AccountId = AccountId; - type BlockNumber = BlockNumber; - type MaxVotesPerVoter = MaxVotesPerVoter; - - fn electable_targets( - bounds: DataProviderBounds, - remaining: PageIndex, - ) -> data_provider::Result> { - let mut targets = Targets::get(); - - // drop previously processed targets. - if let Some(last_index) = LastIteratedTargetIndex::get() { - targets = targets.iter().skip(last_index).cloned().collect::>(); - } - - // take as many targets as requested. - if let Some(max_len) = bounds.count { - targets.truncate(max_len.0 as usize); - } - - assert!(!bounds.exhausted(None, CountBound(targets.len() as u32).into(),)); - - // update the last iterated target index accordingly. - if remaining > 0 { - if let Some(last) = targets.last().cloned() { - LastIteratedTargetIndex::set(Some( - Targets::get().iter().position(|v| v == &last).map(|i| i + 1).unwrap(), - )); - } else { - // no more targets to process, do nothing. - } - } else { - LastIteratedTargetIndex::set(None); - } - - Ok(targets) - } - - /// Note: electing voters bounds are only constrained by the count of voters. - fn electing_voters( - bounds: DataProviderBounds, - remaining: PageIndex, - ) -> data_provider::Result>> { - let mut voters = Voters::get(); - - // skip the already iterated voters in previous pages. - if let Some(index) = LastIteratedVoterIndex::get() { - voters = voters.iter().skip(index).cloned().collect::>(); - } - - // take as many voters as permitted by the bounds. - if let Some(max_len) = bounds.count { - voters.truncate(max_len.0 as usize); - } - - assert!(!bounds.exhausted(None, CountBound(voters.len() as u32).into())); - - // update the last iterater voter index accordingly. - if remaining > 0 { - if let Some(last) = voters.last().cloned() { - LastIteratedVoterIndex::set(Some( - Voters::get().iter().position(|v| v == &last).map(|i| i + 1).unwrap(), - )); - } else { - // no more voters to process, do nothing. - } - } else { - LastIteratedVoterIndex::set(None); - } - - Ok(voters) - } - - fn desired_targets() -> data_provider::Result { - Ok(DesiredTargets::get()) - } - - fn next_election_prediction(now: Self::BlockNumber) -> Self::BlockNumber { - now + EpochLength::get() - now % EpochLength::get() - } -} - -impl LockableElectionDataProvider for MockStaking { - fn set_lock() -> data_provider::Result<()> { - ElectionDataLock::get() - .ok_or("lock is already set") - .map(|_| ElectionDataLock::set(Some(()))) - } - - fn unlock() { - ElectionDataLock::set(None); - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::mock::{ExtBuilder, Pages}; - - #[test] - fn multi_page_targets() { - ExtBuilder::default().build_and_execute(|| { - // no bounds. - let targets = - ::electable_targets(Default::default(), 0); - assert_eq!(targets.unwrap().len(), 8); - assert_eq!(LastIteratedTargetIndex::get(), None); - - // 2 targets per page. - let bounds: DataProviderBounds = - DataProviderBounds { count: Some(2.into()), size: None }; - - let mut all_targets = vec![]; - for page in (0..(Pages::get())).rev() { - let mut targets = - ::electable_targets(bounds, page).unwrap(); - assert_eq!(targets.len(), bounds.count.unwrap().0 as usize); - - all_targets.append(&mut targets); - } - - assert_eq!(all_targets, vec![10, 20, 30, 40, 50, 60]); - assert_eq!(LastIteratedTargetIndex::get(), None); - }) - } - - #[test] - fn multi_page_voters() { - ExtBuilder::default().build_and_execute(|| { - // no bounds. - let voters = - ::electing_voters(Default::default(), 0); - assert_eq!(voters.unwrap().len(), 16); - assert_eq!(LastIteratedVoterIndex::get(), None); - - // 2 voters per page. - let bounds: DataProviderBounds = - DataProviderBounds { count: Some(2.into()), size: None }; - - let mut all_voters = vec![]; - for page in (0..(Pages::get())).rev() { - let mut voters = - ::electing_voters(bounds, page).unwrap(); - - assert_eq!(voters.len(), bounds.count.unwrap().0 as usize); - - all_voters.append(&mut voters); - } - - let mut expected_voters = Voters::get(); - expected_voters.truncate(6); - - assert_eq!(all_voters, expected_voters); - assert_eq!(LastIteratedVoterIndex::get(), None); - - // bound based on the *encoded size* of the voters, per page. - let bounds: DataProviderBounds = - DataProviderBounds { count: None, size: Some(100.into()) }; - - let mut all_voters = vec![]; - for page in (0..(Pages::get())).rev() { - let mut voters = - ::electing_voters(bounds, page).unwrap(); - - all_voters.append(&mut voters); - } - - let mut expected_voters = Voters::get(); - expected_voters.truncate(all_voters.len()); - - assert_eq!(all_voters, expected_voters); - assert_eq!(LastIteratedVoterIndex::get(), None); - }) - } -} diff --git a/substrate/frame/election-provider-multi-block/src/signed/benchmarking.rs b/substrate/frame/election-provider-multi-block/src/signed/benchmarking.rs deleted file mode 100644 index 9629acce2ed38..0000000000000 --- a/substrate/frame/election-provider-multi-block/src/signed/benchmarking.rs +++ /dev/null @@ -1,66 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! # Benchmarking for the Elections Multiblock Unsigned sub-pallet. - -use super::*; -use crate::{benchmarking::helpers, BenchmarkingConfig, ConfigCore, ConfigSigned, ConfigUnsigned}; -use frame_benchmarking::v2::*; - -#[benchmarks( - where T: ConfigCore + ConfigSigned + ConfigUnsigned, -)] -mod benchmarks { - use super::*; - - #[benchmark] - fn verify_page( - v: Linear< - { ::BenchmarkingConfig::VOTERS_PER_PAGE[0] }, - { ::BenchmarkingConfig::VOTERS_PER_PAGE[1] }, - >, - t: Linear< - { ::BenchmarkingConfig::TARGETS_PER_PAGE[0] }, - { ::BenchmarkingConfig::TARGETS_PER_PAGE[1] }, - >, - ) -> Result<(), BenchmarkError> { - helpers::setup_data_provider::( - ::BenchmarkingConfig::VOTERS, - ::BenchmarkingConfig::TARGETS, - ); - - if let Err(err) = helpers::setup_snapshot::(v, t) { - log!(error, "error setting up snapshot: {:?}.", err); - return Err(BenchmarkError::Stop("snapshot error")); - } - - #[block] - { - // TODO - let _ = 1 + 2; - } - - Ok(()) - } - - impl_benchmark_test_suite!( - PalletSigned, - crate::mock::ExtBuilder::default(), - crate::mock::Runtime, - exec_name = build_and_execute - ); -} diff --git a/substrate/frame/election-provider-multi-block/src/signed/mod.rs b/substrate/frame/election-provider-multi-block/src/signed/mod.rs deleted file mode 100644 index a13c2a105aedc..0000000000000 --- a/substrate/frame/election-provider-multi-block/src/signed/mod.rs +++ /dev/null @@ -1,856 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! # Signed sub-pallet -//! -//! The main goal of the signed sub-pallet is to manage a solution submissions from list of sorted -//! score commitments and correponding paged solutions during the [`crate::Phase::Signed`] and to -//! implement the [`SolutionDataProvider`] trait which exposes an interface for external entities to -//! fetch data related to signed submissions for the active round. -//! -//! ## Overview -//! -//! The core logic of this pallet is only active during [`Phase::Signed`]. During the signed phase, -//! accounts can register a solution for the current round and submit the solution's pages, one per -//! extrindic call. The main flow is the following: -//! -//! 1. [`Phase::Signed`] is enacted in the parent EPM pallet; -//! 2. Submitters call [`Call::register`] to register a solution with a given claimed score. This -//! pallet ensures that accepted submission registrations (encapsulated as -//! [`SubmissionMetadata`]) are kept sorted by claimed score in the [`SubmissionMetadata`] -//! storage map. This pallet accepts up to [`Config::MaxSubmissions`] active registrations per -//! round. -//! 3. Submitters that have successfully registered, may submit the solution pages through -//! [`Call::submit_page`], one page per call. -//! 4. Submitters may bail from a registered solution by calling [`Call::bail`]. Bailing from a -//! solution registration will result in a partial slash. -//! 5. This pallet implements the trait [`SolutionDataProvider`] which exposes methods for external -//! entities (e.g. verifier pallet) to query the data and metadata of the current best submitted -//! solution. -//! 6. Upon solution verification (performed by an external entity e.g. the verifier pallet), -//! [`SolutionDataProvider::report_result`] can be called to report the verification result of -//! the current best solution. Depending on the result, the corresponding submitter's deposit may -//! be fully slashed or the submitter may be rewarded with [`Config::Reward`]. -//! -//! Accounts may submit up to [`Config::MaxSubmissions`] score commitments per election round and -//! this pallet ensures that the scores are stored under the map `SortedScores` are sorted and keyed -//! by the correct round number. -//! -//! ## Reporting the verification result -//! -//! When the time to evaluate the signed submission comes, the solutions are checked from best to -//! worse. The [`SolutionDataProvider`] trait exposes the submission data and metadata to an -//! external entity that verifies the queued solutions until it accepts one solution (or none). The -//! verifier entity reports the result of the solution verification which may result in one of three -//! scenarios: -//! -//! 1. If the *best* committed score and page submissions are correct, the submitter is rewarded. -//! 2. Any queued score that was not evaluated, the held deposit is fully returned. -//! 3. Any invalid solution results in a 100% slash of the held deposit. -//! -//! ## Submission deposit -//! -//! Each submitter must hold a "base deposit" per submission that is calculated based on the number -//! of the number of submissions in the queue. In addition, for each solution page submitted there -//! is a fixed [`Config::PageDeposit`] deposit held. The held deposit may be returned or slashed at -//! by the end of the round, depending on the following: -//! -//! 1. If a submission is verified and accepted, the deposit is returned. -//! 2. If a submission is verified and not accepted, the whole deposit is slashed. -//! 3. If a submission is not verified, the deposit is returned. -//! 4. Bailing a registration will return the page deposit and burn the base balance. -//! -//! The deposit is burned when all the data from the submitter is cleared through the -//! [`Call::force_clear_submission`]. -//! -//! ## Submission reward -//! -//! Exposing [`SolutionDataProvider::report_result`] allows an external verifier to signal whether -//! the current best solution is correct or not. If the solution is correct, the submitter is -//! rewarded and the pallet can start clearing up the state of the current round. -//! -//! ## Storage management -//! -//! ### Storage mutations -//! -//! The [`Submissions`] wraps all the mutation and getters related to the sorted scores, metadata -//! and submissions storage types. All the mutations to those storage items *MUST* be performed -//! through [`Submissions`] to leverage the mutate checks and ensure the data consistency of the -//! submissions data. -//! -//! ### Clearing up the storage -//! -//! The [`SortedScores`] of the *active* submissions in a -//! given round. Each of the registered submissions may have one or more associated paged solution -//! stored in [`SubmissionsStorage`] and its corresponding [`SubmissionMetadata`]. -//! -//! This pallet never implicitly clears either the metadata or the paged submissions storage data. -//! The data is kept in storage until [`Call::force_clear_submission`] extrinsic is called. At that -//! time, the hold deposit may be slashed depending on the state of the `release_strategy` -//! associated with the metadata. - -#[cfg(feature = "runtime-benchmarks")] -pub mod benchmarking; - -#[cfg(test)] -mod tests; - -use crate::{ - signed::pallet::Submissions, - types::AccountIdOf, - verifier::{AsyncVerifier, SolutionDataProvider, VerificationResult}, - PageIndex, PagesOf, SolutionOf, -}; - -use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::{ - defensive, - traits::{ - fungible::{ - hold::Balanced as FnBalanced, Credit, Inspect as FnInspect, - InspectHold as FnInspectHold, Mutate as FnMutate, MutateHold as FnMutateHold, - }, - tokens::Precision, - Defensive, DefensiveSaturating, Get, - }, - RuntimeDebugNoBound, -}; -use scale_info::TypeInfo; -use sp_npos_elections::ElectionScore; -use sp_runtime::BoundedVec; -use sp_std::vec::Vec; - -// public re-exports. -pub use pallet::{ - Call, Config, Error, Event, HoldReason, Pallet, __substrate_call_check, - __substrate_event_check, tt_default_parts, tt_default_parts_v2, tt_error_token, -}; - -/// Alias for the pallet's balance type. -type BalanceOf = <::Currency as FnInspect>>::Balance; -/// Alias for the pallet's hold credit type. -pub type CreditOf = Credit, ::Currency>; - -/// Release strategy for currency held by this pallet. -#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound, PartialEq)] -pub(crate) enum ReleaseStrategy { - /// Releases all held deposit. - All, - /// Releases only the base deposit, - BaseDeposit, - /// Releases only the pages deposit. - PageDeposit, - /// Burn all held deposit. - BurnAll, -} - -impl Default for ReleaseStrategy { - fn default() -> Self { - Self::All - } -} - -/// Metadata of a registered submission. -#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Default, RuntimeDebugNoBound)] -#[cfg_attr(test, derive(frame_support::PartialEqNoBound, frame_support::EqNoBound))] -#[codec(mel_bound(T: Config))] -#[scale_info(skip_type_params(T))] -pub struct SubmissionMetadata { - /// The score that this submission is proposing. - claimed_score: ElectionScore, - /// A bit-wise bounded vec representing the submitted pages thus far. - pages: BoundedVec>, - /// The amount held for this submission. - deposit: BalanceOf, - /// Current release strategy for this metadata entry. - release_strategy: ReleaseStrategy, -} - -#[frame_support::pallet] -pub mod pallet { - use core::marker::PhantomData; - - use crate::verifier::{AsyncVerifier, Verifier}; - - use super::*; - use frame_support::{ - pallet_prelude::{ValueQuery, *}, - traits::{tokens::Fortitude, Defensive, EstimateCallFee, OnUnbalanced}, - Twox64Concat, - }; - use frame_system::{ - ensure_signed, - pallet_prelude::{BlockNumberFor, OriginFor}, - WeightInfo, - }; - use sp_npos_elections::ElectionScore; - use sp_runtime::traits::Convert; - - #[pallet::config] - #[pallet::disable_frame_system_supertrait_check] - pub trait Config: crate::Config { - /// The overarching event type. - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - - /// The currency type. - type Currency: FnMutateHold - + FnBalanced - + FnInspectHold - + FnMutate; - - /// Something that can predict the fee of a call. Used to sensibly distribute rewards. - type EstimateCallFee: EstimateCallFee, BalanceOf>; - - /// Handler for the unbalanced reduction that happens when submitters are slashed. - type OnSlash: OnUnbalanced>; - - /// Something that calculates the signed base deposit based on the size of the current - /// queued solution proposals. - type DepositBase: Convert>; - - /// Per-page deposit for a signed solution. - #[pallet::constant] - type DepositPerPage: Get>; - - /// Reward for an accepted solution. - #[pallet::constant] - type Reward: Get>; - - /// The maximum number of signed submissions per round. - #[pallet::constant] - type MaxSubmissions: Get; - - /// The pallet's hold reason. - type RuntimeHoldReason: From; - - type WeightInfo: WeightInfo; - } - - /// A sorted list of the current submissions scores corresponding to solution commitments - /// submitted in the signed phase, keyed by round. - /// - /// This pallet *MUST* ensure the bounded vec of scores is always sorted after mutation. - #[pallet::storage] - type SortedScores = StorageMap< - _, - Twox64Concat, - u32, - BoundedVec<(T::AccountId, ElectionScore), T::MaxSubmissions>, - ValueQuery, - >; - - /// A double-map from (`round`, `account_id`) to a submission metadata of a registered - /// solution commitment. - #[pallet::storage] - type SubmissionMetadataStorage = - StorageDoubleMap<_, Twox64Concat, u32, Twox64Concat, T::AccountId, SubmissionMetadata>; - - /// A triple-map from (round, account, page) to a submitted solution. - #[pallet::storage] - type SubmissionStorage = StorageNMap< - _, - ( - NMapKey, - NMapKey, - NMapKey, - ), - SolutionOf, - OptionQuery, - >; - - #[pallet::pallet] - pub struct Pallet(PhantomData); - - /// A reason for this pallet placing a hold on funds. - #[pallet::composite_enum] - pub enum HoldReason { - /// Deposit for registering an election solution. - ElectionSolutionSubmission, - } - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - /// A score commitment has been successfully registered. - Registered { round: u32, who: AccountIdOf, claimed_score: ElectionScore }, - /// A submission page was stored successfully. - PageStored { round: u32, who: AccountIdOf, page: PageIndex }, - /// Retracted a submission successfully. - Bailed { round: u32, who: AccountIdOf }, - /// A submission has been cleared by request. - SubmissionCleared { round: u32, submitter: AccountIdOf, reward: Option> }, - } - - #[pallet::error] - pub enum Error { - /// The election system is not expecting signed submissions. - NotAcceptingSubmissions, - /// Duplicate registering for a given round, - DuplicateRegister, - /// The submissions queue is full. Reject submission. - SubmissionsQueueFull, - /// Submission with a page index higher than the supported. - BadPageIndex, - /// A page submission was attempted for a submission that was not previously registered. - SubmissionNotRegistered, - /// A submission score is not high enough. - SubmissionScoreTooLow, - /// Bad timing for force clearing a stored submission. - CannotClear, - /// Error releasing held funds. - CannotReleaseFunds, - } - - /// Wrapper for signed submissions. - /// - /// It handle 3 storage items: - /// - /// 1. [`SortedScores`]: A flat, striclty sorted, vector with all the submission's scores. The - /// vector contains a tuple of `submitter_id` and `claimed_score`. - /// 2. [`SubmissionStorage`]: Paginated map with all submissions, keyed by round, submitter and - /// page index. - /// 3. [`SubmissionMetadataStorage`]: Double map with submissions metadata, keyed by submitter - /// ID and round. - /// - /// Invariants: - /// - [`SortedScores`] must be strictly sorted or empty. - /// - All registered scores in [`SortedScores`] must be higher than the minimum score. - /// - An entry in [`SortedScores`] for a given round must have an associated entry in - /// [`SubmissionMetadataStorage`]. - /// - For all registered submissions, there is a held deposit that matches that of the - /// submission metadata and the number of submitted pages. - pub(crate) struct Submissions(core::marker::PhantomData); - impl Submissions { - /// Generic mutation helper with checks. - /// - /// All the mutation functions must be done through this function. - fn mutate_checked R>(_round: u32, mutate: F) -> R { - let result = mutate(); - - #[cfg(debug_assertions)] - assert!(Self::sanity_check_round(crate::Pallet::::current_round()).is_ok()); - - result - } - - /// Try to register a submission commitment. - /// - /// The submission is not accepted if one of these invariants fails: - /// - The claimed score is not higher than the minimum expected score. - /// - The queue is full and the election score is strictly worse than all the current - /// queued solutions. - /// - /// A queued solution may be discarded if the queue is full and the new submission has a - /// better score. - /// - /// It must ensure that the metadata queue is sorted by election score. - fn try_register( - who: &T::AccountId, - round: u32, - metadata: SubmissionMetadata, - ) -> DispatchResult { - Self::mutate_checked(round, || Self::try_register_inner(who, round, metadata)) - } - - fn try_register_inner( - who: &T::AccountId, - round: u32, - metadata: SubmissionMetadata, - ) -> DispatchResult { - let mut scores = Submissions::::scores_for(round); - scores.iter().try_for_each(|(account, _)| -> DispatchResult { - ensure!(account != who, Error::::DuplicateRegister); - Ok(()) - })?; - - // most likely checked before, but double-checking. - debug_assert!(!SubmissionMetadataStorage::::contains_key(round, who)); - - // the submission score must be higher than the minimum trusted score. Note that since - // there is no queued solution yet, the check is only performed against the minimum - // score. - ensure!( - ::ensure_score_quality(metadata.claimed_score), - Error::::SubmissionScoreTooLow, - ); - - let pos = - match scores.binary_search_by_key(&metadata.claimed_score, |(_, score)| *score) { - // in the unlikely event that election scores already exists in the storage, we - // store the submissions next to one other. - Ok(pos) | Err(pos) => pos, - }; - - let submission = (who.clone(), metadata.claimed_score); - - match scores.force_insert_keep_right(pos, submission) { - // entry inserted without discarding. - Ok(None) => Ok(()), - // entry inserted but queue was full, clear the discarded submission. - Ok(Some((discarded, _s))) => { - let _ = SubmissionStorage::::clear_prefix( - (round, &discarded), - u32::max_value(), - None, - ); - // unreserve full deposit - let _ = T::Currency::release_all( - &HoldReason::ElectionSolutionSubmission.into(), - &who, - Precision::Exact, - ) - .defensive()?; - - Ok(()) - }, - Err(_) => Err(Error::::SubmissionsQueueFull), - }?; - - // hold deposit for this submission. - T::Currency::hold( - &HoldReason::ElectionSolutionSubmission.into(), - &who, - metadata.deposit, - )?; - - SortedScores::::insert(round, scores); - SubmissionMetadataStorage::::insert(round, who, metadata); - - Ok(()) - } - - /// Store a paged solution for `who` in a given `round`. - /// - /// If `maybe_solution` is None, it will delete the given page from the submission store. - /// Successive calls to this with the same page index will replace the existing page - /// submission. - pub(crate) fn try_mutate_page( - who: &T::AccountId, - round: u32, - page: PageIndex, - maybe_solution: Option>, - ) -> DispatchResult { - Self::mutate_checked(round, || { - Self::try_mutate_page_inner(who, round, page, maybe_solution) - }) - } - - fn try_mutate_page_inner( - who: &T::AccountId, - round: u32, - page: PageIndex, - maybe_solution: Option>, - ) -> DispatchResult { - ensure!(page < T::Pages::get(), Error::::BadPageIndex); - - ensure!(Self::metadata_for(round, &who).is_some(), Error::::SubmissionNotRegistered); - - let should_hold_extra = - SubmissionStorage::::mutate_exists((round, who, page), |maybe_old_solution| { - let exists = maybe_old_solution.is_some(); - *maybe_old_solution = maybe_solution; - - !exists - }); - - // the deposit per page is held IFF it is a new page being stored. - if should_hold_extra { - T::Currency::hold( - &HoldReason::ElectionSolutionSubmission.into(), - &who, - T::DepositPerPage::get(), - )?; - }; - - Ok(()) - } - - /// Set metadata for submitter. - pub(crate) fn set_metadata( - round: u32, - who: &T::AccountId, - metadata: SubmissionMetadata, - ) { - debug_assert!(SortedScores::::get(round).iter().any(|(account, _)| who == account)); - - Self::mutate_checked(round, || { - SubmissionMetadataStorage::::insert(round, who, metadata); - }); - } - - /// Clears the leader's score data, effectively disabling the submittion. - /// - /// Returns the submission metadata of the disabled. - pub(crate) fn take_leader_score( - round: u32, - ) -> Option<(T::AccountId, Option>)> { - Self::mutate_checked(round, || { - SortedScores::::mutate(round, |scores| scores.pop()).and_then( - |(submitter, _)| { - Some((submitter.clone(), Self::metadata_for(round, &submitter))) - }, - ) - }) - } - - /// Clear the registed metadata of a submission and its score and release the held deposit - /// based on the `release_strategy`. - /// - /// Clearing a submission only clears the metadata and stored score of a solution. The - /// paged submissions must be cleared by explicitly calling - /// [`Call::force_clear_submission`]. - /// - /// Note: the deposit can never be released completely or burned completely since - /// an account may have lingering held deposit from previous or subsequent rounds. Thus, the - /// amount to release and burn must always be calculated explicitly based on the round's - /// metadata and release strategy. - /// - /// The held deposit that is not released is burned as a penalty. - pub(crate) fn clear_submission_of( - who: &T::AccountId, - round: u32, - release_strategy: ReleaseStrategy, - ) -> DispatchResult { - let reason = HoldReason::ElectionSolutionSubmission; - - // calculates current base held deposit for this round, if any. - let base_deposit = if let Some(metadata) = Self::metadata_for(round, &who) { - metadata.deposit - } else { - return Err(Error::::SubmissionNotRegistered.into()); - }; - - // calculates current held page deposit for this round. - let page_deposit = T::DepositPerPage::get().defensive_saturating_mul( - Submissions::::page_count_submission_for(round, who).into(), - ); - - Self::mutate_checked(round, || { - SubmissionMetadataStorage::::remove(round, who); - SortedScores::::get(round).retain(|(submitter, _)| submitter != who); - }); - - let (burn, release) = match release_strategy { - ReleaseStrategy::All => - (Zero::zero(), base_deposit.defensive_saturating_add(page_deposit)), - ReleaseStrategy::BurnAll => - (base_deposit.defensive_saturating_add(page_deposit), Zero::zero()), - ReleaseStrategy::BaseDeposit => (page_deposit, base_deposit), - ReleaseStrategy::PageDeposit => (base_deposit, page_deposit), - }; - - T::Currency::burn_held(&reason.into(), who, burn, Precision::Exact, Fortitude::Force)?; - - T::Currency::release(&reason.into(), who, release, Precision::Exact)?; - - // clear the submission metadata for `who` in `round`. May be a noop. - Self::mutate_checked(round, || { - let _ = SubmissionMetadataStorage::::remove(round, who); - SortedScores::::get(round).retain(|(submitter, _)| submitter != who); - }); - - Ok(()) - } - - /// Returns the leader submitter for the current round and corresponding claimed score. - pub(crate) fn leader(round: u32) -> Option<(T::AccountId, ElectionScore)> { - Submissions::::scores_for(round).last().cloned() - } - - /// Returns the metadata of a submitter for a given account. - pub(crate) fn metadata_for( - round: u32, - who: &T::AccountId, - ) -> Option> { - SubmissionMetadataStorage::::get(round, who) - } - - /// Returns the scores for a given round. - pub(crate) fn scores_for( - round: u32, - ) -> BoundedVec<(T::AccountId, ElectionScore), T::MaxSubmissions> { - SortedScores::::get(round) - } - - /// Returns the submission of a submitter for a given round and page. - pub(crate) fn page_submission_for( - round: u32, - who: T::AccountId, - page: PageIndex, - ) -> Option> { - SubmissionStorage::::get((round, who, page)) - } - - pub(crate) fn page_count_submission_for(round: u32, who: &T::AccountId) -> u32 { - SubmissionStorage::::iter_key_prefix((round, who)).count() as u32 - } - - #[cfg(debug_assertions)] - fn sanity_check_round(_round: u32) -> Result<(), &'static str> { - // TODO - Ok(()) - } - } - - impl Pallet { - pub(crate) fn do_register( - who: &T::AccountId, - claimed_score: ElectionScore, - round: u32, - ) -> DispatchResult { - // base deposit depends on the number of submissions for the current `round`. - let deposit = T::DepositBase::convert( - SubmissionMetadataStorage::::iter_key_prefix(round).count(), - ); - - let pages: BoundedVec<_, T::Pages> = (0..T::Pages::get()) - .map(|_| false) - .collect::>() - .try_into() - .expect("bounded vec constructed from bound; qed."); - - let metadata = SubmissionMetadata { - pages, - claimed_score, - deposit, - // new submissions should receive back all held deposit. - release_strategy: ReleaseStrategy::All, - }; - - let _ = Submissions::::try_register(&who, round, metadata)?; - Ok(()) - } - } - - #[pallet::call] - impl Pallet { - /// Submit a score commitment for a solution in the current round. - /// - /// The scores must be kept sorted in the `SortedScores` storage map. - #[pallet::call_index(1)] - #[pallet::weight(Weight::default())] - pub fn register(origin: OriginFor, claimed_score: ElectionScore) -> DispatchResult { - let who = ensure_signed(origin)?; - - ensure!( - crate::Pallet::::current_phase().is_signed(), - Error::::NotAcceptingSubmissions - ); - - let round = crate::Pallet::::current_round(); - ensure!( - Submissions::::metadata_for(round, &who).is_none(), - Error::::DuplicateRegister - ); - - Self::do_register(&who, claimed_score, round)?; - - Self::deposit_event(Event::::Registered { round, who, claimed_score }); - Ok(()) - } - - /// Submit a page for a solution. - /// - /// To submit a solution page successfull, the submitter must have registered the - /// commitment before. - #[pallet::call_index(2)] - #[pallet::weight(Weight::default())] - pub fn submit_page( - origin: OriginFor, - page: PageIndex, - maybe_solution: Option>, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - ensure!( - crate::Pallet::::current_phase().is_signed(), - Error::::NotAcceptingSubmissions - ); - - let round = crate::Pallet::::current_round(); - Submissions::::try_mutate_page(&who, round, page, maybe_solution)?; - - Self::deposit_event(Event::::PageStored { - round: crate::Pallet::::current_round(), - who, - page, - }); - - Ok(()) - } - - /// Unregister a submission. - /// - /// This will fully remove the solution and corresponding metadata from storage and refund - /// the page submissions deposit only. - /// - /// Note: the base deposit will be burned to prevent the attack where rogue submitters - /// deprive honest submitters submitting a solution. - #[pallet::call_index(3)] - #[pallet::weight(Weight::default())] - pub fn bail(origin: OriginFor) -> DispatchResult { - let who = ensure_signed(origin)?; - - ensure!( - crate::Pallet::::current_phase().is_signed(), - Error::::NotAcceptingSubmissions - ); - - let round = crate::Pallet::::current_round(); - Submissions::::clear_submission_of(&who, round, ReleaseStrategy::PageDeposit)?; - - Self::deposit_event(Event::::Bailed { round, who }); - - Ok(()) - } - - /// Force clean submissions storage for a given (`sumitter`, `round`) tuple. - /// - /// This pallet expects that submitted pages for `round` may exist IFF a corresponding - /// metadata exists. - #[pallet::call_index(4)] - #[pallet::weight(Weight::default())] - pub fn force_clear_submission( - origin: OriginFor, - round: u32, - submitter: T::AccountId, - ) -> DispatchResultWithPostInfo { - let _who = ensure_signed(origin); - - // force clearing submissions may happen only during phase off. - ensure!(crate::Pallet::::current_phase().is_off(), Error::::CannotClear); - - if let Some(metadata) = Submissions::::metadata_for(round, &submitter) { - Submissions::::mutate_checked(round, || { - // clear submission metadata from submitter for `round`. - let _ = Submissions::::clear_submission_of( - &submitter, - round, - metadata.release_strategy, - ); - - // clear all pages from submitter in `round`. - let _ = SubmissionStorage::::clear_prefix( - (round, &submitter), - u32::max_value(), - None, - ); - }); - } else { - debug_assert!( - Submissions::::page_count_submission_for(round, &submitter).is_zero() - ); - - return Err(Error::::CannotClear.into()) - } - - Self::deposit_event(Event::::SubmissionCleared { - round: crate::Pallet::::current_round(), - submitter, - reward: None, - }); - - Ok(Pays::No.into()) - } - } - - #[pallet::hooks] - impl Hooks> for Pallet { - /// The `on_initialize` signals the [`AsyncVerifier`] whenever it should start or stop the - /// asynchronous verification of stored submissions. - /// - /// - Start async verification at the beginning of the [`crate::Phase::SignedValidation`]. - /// - Stops async verification at the beginning of the [`crate::Phase::Unsigned`]. - fn on_initialize(now: BlockNumberFor) -> Weight { - if crate::Pallet::::current_phase().is_signed_validation_open_at(Some(now)) { - sublog!(debug, "signed", "signed validation phase started, signaling the verifier to start async verifiacton."); - let _ = ::start().defensive(); - }; - - if crate::Pallet::::current_phase().is_unsigned_open_at(now) { - sublog!(debug, "signed", "signed validation phase ended, signaling the verifier to stop async verifiacton."); - ::stop(); - } - - Weight::default() - } - } -} - -impl SolutionDataProvider for Pallet { - type Solution = SolutionOf; - - /// Returns a paged solution of the *best* solution in the queue. - fn get_paged_solution(page: PageIndex) -> Option { - let round = crate::Pallet::::current_round(); - - Submissions::::leader(round).map(|(who, _score)| { - sublog!(info, "signed", "returning page {} of leader's {:?} solution", page, who); - Submissions::::page_submission_for(round, who, page).unwrap_or_default() - }) - } - - /// Returns the score of the *best* solution in the queueu. - fn get_score() -> Option { - let round = crate::Pallet::::current_round(); - Submissions::::leader(round).map(|(_who, score)| score) - } - - /// Called by an external entity to report a verification result of the current *best* - /// solution. - /// - /// If the verification is rejected, update the leader's metadata to be slashed (i.e. set - /// release strategy to [`ReleaseStrategy::BurnAll`] in the leader's metadata). If successful - /// (represented by the variant [``VerificationResult::Queued]), reward the submitter and - /// signal the verifier to stop the async election verification. - fn report_result(result: VerificationResult) { - let round = crate::Pallet::::current_round(); - - let (leader, mut metadata) = - if let Some((leader, maybe_metadata)) = Submissions::::take_leader_score(round) { - let metadata = match maybe_metadata { - Some(m) => m, - None => { - defensive!("unexpected: leader with inconsistent data (no metadata)."); - return; - }, - }; - (leader, metadata) - } else { - // TODO(gpestana): turn into defensive. - sublog!(error, "signed", "unexpected: leader called without active submissions."); - return - }; - - match result { - VerificationResult::Queued => { - // solution was accepted by the verifier, reward leader and stop async - // verification. - // TODO(gpestana): think better about the reward minting process -- should we keep - // a pot for rewards instead of minting it in staking? - let _ = T::Currency::mint_into(&leader, T::Reward::get()).defensive(); - let _ = ::stop(); - }, - VerificationResult::Rejected | VerificationResult::DataUnavailable => { - // updates metadata release strategy so that all the deposit is burned when the - // leader's data is cleared. - metadata.release_strategy = ReleaseStrategy::BurnAll; - Submissions::::set_metadata(round, &leader, metadata); - }, - } - } -} diff --git a/substrate/frame/election-provider-multi-block/src/signed/tests.rs b/substrate/frame/election-provider-multi-block/src/signed/tests.rs deleted file mode 100644 index 3346c84cc7dcf..0000000000000 --- a/substrate/frame/election-provider-multi-block/src/signed/tests.rs +++ /dev/null @@ -1,405 +0,0 @@ -// 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 super::*; -use crate::{mock::*, verifier::SolutionDataProvider, Phase, Verifier}; -use frame_support::{assert_noop, assert_ok, testing_prelude::*}; -use sp_npos_elections::ElectionScore; -use sp_runtime::traits::Convert; - -#[test] -fn clear_submission_of_works() { - ExtBuilder::default().build_and_execute(|| {}); -} - -mod calls { - use super::*; - use sp_core::bounded_vec; - - #[test] - fn register_works() { - ExtBuilder::default().build_and_execute(|| { - roll_to_phase(Phase::Signed); - assert_ok!(assert_snapshots()); - - assert_eq!(balances(99), (100, 0)); - let score = ElectionScore { minimal_stake: 100, ..Default::default() }; - - assert_ok!(SignedPallet::register(RuntimeOrigin::signed(99), score)); - assert_eq!(balances(99), (90, 10)); - - assert_eq!( - Submissions::::metadata_for(current_round(), &99).unwrap(), - SubmissionMetadata { - claimed_score: score, - deposit: 10, - pages: bounded_vec![false, false, false], - release_strategy: Default::default(), - } - ); - - assert_eq!( - signed_events(), - vec![Event::Registered { round: 0, who: 99, claimed_score: score }], - ); - - // duplicate submission for the same round fails. - assert_noop!( - SignedPallet::register(RuntimeOrigin::signed(99), score), - Error::::DuplicateRegister, - ); - - // if claimed score if below the minimum score, submission will fail. - ::set_minimum_score(ElectionScore { - minimal_stake: 20, - ..Default::default() - }); - - let low_score = ElectionScore { minimal_stake: 10, ..Default::default() }; - assert_noop!( - SignedPallet::register(RuntimeOrigin::signed(97), low_score), - Error::::SubmissionScoreTooLow, - ); - }) - } - - #[test] - fn register_sorted_works() { - ExtBuilder::default().signed_max_submissions(3).build_and_execute(|| { - // try register 5 submissions: - // - 3 are stored. - // - one submission is registered after queue is full while the score improves current - // submission in the queue; other submission is discarded. - // - one submission is registered after queue is full while the score does not improve - // the current submission in the queue; submission is discarded. - - roll_to_phase(Phase::Signed); - - let score = ElectionScore { minimal_stake: 40, ..Default::default() }; - assert_ok!(SignedPallet::register(RuntimeOrigin::signed(40), score)); - - let score = ElectionScore { minimal_stake: 30, ..Default::default() }; - assert_ok!(SignedPallet::register(RuntimeOrigin::signed(30), score)); - - let score = ElectionScore { minimal_stake: 20, ..Default::default() }; - assert_ok!(SignedPallet::register(RuntimeOrigin::signed(20), score)); - - // submission queue is full, next submissions will only be accepted if the submitted - // score improves the current lower score. - - // registration discarded. - let score = ElectionScore { minimal_stake: 10, ..Default::default() }; - assert_noop!( - SignedPallet::register(RuntimeOrigin::signed(10), score), - Error::::SubmissionsQueueFull - ); - - // higher score is successfully registered. - let higher_score = ElectionScore { minimal_stake: 50, ..Default::default() }; - assert_ok!(SignedPallet::register(RuntimeOrigin::signed(50), higher_score)); - - assert_eq!(Submissions::::leader(current_round()).unwrap(), (50, higher_score),); - - assert_eq!( - signed_events(), - vec![ - Event::Registered { - round: 0, - who: 40, - claimed_score: ElectionScore { - minimal_stake: 40, - sum_stake: 0, - sum_stake_squared: 0 - } - }, - Event::Registered { - round: 0, - who: 30, - claimed_score: ElectionScore { - minimal_stake: 30, - sum_stake: 0, - sum_stake_squared: 0 - } - }, - Event::Registered { - round: 0, - who: 20, - claimed_score: ElectionScore { - minimal_stake: 20, - sum_stake: 0, - sum_stake_squared: 0 - } - }, - Event::Registered { - round: 0, - who: 50, - claimed_score: ElectionScore { - minimal_stake: 50, - sum_stake: 0, - sum_stake_squared: 0 - } - }, - ], - ); - }) - } - - #[test] - fn submit_page_works() { - ExtBuilder::default().build_and_execute(|| { - // bad timing. - assert_noop!( - SignedPallet::submit_page(RuntimeOrigin::signed(40), 0, None), - Error::::NotAcceptingSubmissions - ); - - roll_to_phase(Phase::Signed); - - // submission not registered before. - assert_noop!( - SignedPallet::submit_page(RuntimeOrigin::signed(10), 0, None), - Error::::SubmissionNotRegistered - ); - - let score = ElectionScore { minimal_stake: 10, ..Default::default() }; - assert_ok!(SignedPallet::register(RuntimeOrigin::signed(10), score)); - - // 0 pages submitted so far. - assert_eq!(Submissions::::page_count_submission_for(current_round(), &10), 0); - - // now submission works since there is a registered commitment. - assert_ok!(SignedPallet::submit_page( - RuntimeOrigin::signed(10), - 0, - Some(Default::default()) - )); - - assert_eq!( - Submissions::::page_submission_for(current_round(), 10, 0), - Some(Default::default()), - ); - - // 1 page submitted so far. - assert_eq!(Submissions::::page_count_submission_for(current_round(), &10), 1); - - // tries to submit a page out of bounds. - assert_noop!( - SignedPallet::submit_page(RuntimeOrigin::signed(10), 10, Some(Default::default())), - Error::::BadPageIndex, - ); - - // 1 successful page submitted so far. - assert_eq!(Submissions::::page_count_submission_for(current_round(), &10), 1); - - assert_eq!( - signed_events(), - vec![ - Event::Registered { - round: 0, - who: 10, - claimed_score: ElectionScore { - minimal_stake: 10, - sum_stake: 0, - sum_stake_squared: 0 - } - }, - Event::PageStored { round: 0, who: 10, page: 0 } - ], - ); - }) - } - - #[test] - fn bail_works() { - ExtBuilder::default().build_and_execute(|| { - // TODO - }) - } - - #[test] - fn force_clear_submission_works() { - ExtBuilder::default().build_and_execute(|| { - // TODO - }) - } -} - -mod deposit { - use super::*; - - #[test] - fn register_submit_bail_deposit_works() { - ExtBuilder::default().build_and_execute(|| { - assert_eq!(::Pages::get(), 3); - - roll_to_phase(Phase::Signed); - assert_ok!(assert_snapshots()); - - // expected base deposit with 0 submissions in the queue. - let base_deposit = ::DepositBase::convert(0); - let page_deposit = ::DepositPerPage::get(); - assert!(base_deposit != 0 && page_deposit != 0 && base_deposit != page_deposit); - - // 99 has 100 free balance and 0 held balance for elections. - assert_eq!(balances(99), (100, 0)); - - assert_ok!(SignedPallet::register(RuntimeOrigin::signed(99), Default::default())); - - // free balance and held deposit updated as expected. - assert_eq!(balances(99), (100 - base_deposit, base_deposit)); - - // submit page 2. - assert_ok!(SignedPallet::submit_page( - RuntimeOrigin::signed(99), - 2, - Some(Default::default()) - )); - - // free balance and held deposit updated as expected. - assert_eq!( - balances(99), - (100 - base_deposit - page_deposit, base_deposit + page_deposit) - ); - - // submit remaining pages. - assert_ok!(SignedPallet::submit_page( - RuntimeOrigin::signed(99), - 1, - Some(Default::default()) - )); - assert_ok!(SignedPallet::submit_page( - RuntimeOrigin::signed(99), - 0, - Some(Default::default()) - )); - - // free balance and held deposit updated as expected (ie. base_deposit + Pages * - // page_deposit) - assert_eq!( - balances(99), - (100 - base_deposit - (3 * page_deposit), base_deposit + (3 * page_deposit)) - ); - - // now if 99 bails, all the deposits are released. - assert_ok!(SignedPallet::bail(RuntimeOrigin::signed(99))); - - // the base deposit was burned after bail and all the pages deposit were released. - assert_eq!(balances(99), (100 - base_deposit, 0)); - }) - } -} - -mod solution_data_provider { - use super::*; - - #[test] - fn higher_score_works() { - ExtBuilder::default().build_and_execute(|| { - roll_to_phase(Phase::Signed); - - assert_eq!(::get_score(), None); - - let higher_score = ElectionScore { minimal_stake: 40, ..Default::default() }; - assert_ok!(SignedPallet::register(RuntimeOrigin::signed(40), higher_score)); - - let score = ElectionScore { minimal_stake: 30, ..Default::default() }; - assert_ok!(SignedPallet::register(RuntimeOrigin::signed(30), score)); - - assert_eq!(::get_score(), Some(higher_score)); - }) - } - - #[test] - fn get_page_works() { - ExtBuilder::default().build_and_execute(|| { - roll_to_phase(Phase::Signed); - assert_eq!(::get_score(), None); - }) - } -} - -mod e2e { - use super::*; - - type MaxSubmissions = ::MaxSubmissions; - - mod simple_e2e_works { - use super::*; - - #[test] - fn submit_solution_happy_path_works() { - ExtBuilder::default().build_and_execute(|| { - roll_to_phase(Phase::Signed); - - let current_round = MultiPhase::current_round(); - assert!(Submissions::::metadata_for(current_round, &10).is_none()); - - let claimed_score = ElectionScore { minimal_stake: 100, ..Default::default() }; - - // register submission - assert_ok!(SignedPallet::register(RuntimeOrigin::signed(10), claimed_score,)); - - // metadata and claimed scores have been stored as expected. - assert_eq!( - Submissions::::metadata_for(current_round, &10), - Some(SubmissionMetadata { - claimed_score, - deposit: 10, - pages: bounded_vec![false, false, false], - release_strategy: Default::default(), - }) - ); - let expected_scores: BoundedVec<(AccountId, ElectionScore), MaxSubmissions> = - bounded_vec![(10, claimed_score)]; - assert_eq!(Submissions::::scores_for(current_round), expected_scores); - - // submit all pages of a noop solution; - let solution = TestNposSolution::default(); - for page in (0..=MultiPhase::msp()).into_iter().rev() { - assert_ok!(SignedPallet::submit_page( - RuntimeOrigin::signed(10), - page, - Some(solution.clone()) - )); - - assert_eq!( - Submissions::::page_submission_for(current_round, 10, page), - Some(solution.clone()) - ); - } - - assert_eq!( - signed_events(), - vec![ - Event::Registered { - round: 0, - who: 10, - claimed_score: ElectionScore { - minimal_stake: 100, - sum_stake: 0, - sum_stake_squared: 0 - } - }, - Event::PageStored { round: 0, who: 10, page: 2 }, - Event::PageStored { round: 0, who: 10, page: 1 }, - Event::PageStored { round: 0, who: 10, page: 0 }, - ] - ); - }) - } - } -} diff --git a/substrate/frame/election-provider-multi-block/src/types.rs b/substrate/frame/election-provider-multi-block/src/types.rs deleted file mode 100644 index 7f4c1d2cefdd2..0000000000000 --- a/substrate/frame/election-provider-multi-block/src/types.rs +++ /dev/null @@ -1,261 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! # Types for the multi-block election provider pallet and sub-pallets. - -use codec::{Decode, Encode, MaxEncodedLen}; -use scale_info::TypeInfo; - -use crate::{unsigned::miner::Config as MinerConfig, Verifier}; -use frame_election_provider_support::{ElectionProvider, NposSolution, PageIndex}; -use frame_support::{ - BoundedVec, CloneNoBound, DebugNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, - RuntimeDebugNoBound, -}; -use sp_npos_elections::ElectionScore; -use sp_runtime::SaturatedConversion; -use sp_std::{boxed::Box, vec::Vec}; - -/// The main account ID type. -pub type AccountIdOf = ::AccountId; - -/// Supports that are returned from a given [`Verifier`]. -pub type SupportsOf = frame_election_provider_support::BoundedSupports< - ::AccountId, - ::MaxWinnersPerPage, - ::MaxBackersPerWinner, ->; - -/// Supports that are returned from a given [`miner::Config`]. -pub type MinerSupportsOf = frame_election_provider_support::BoundedSupports< - ::AccountId, - ::MaxWinnersPerPage, - ::MaxBackersPerWinner, ->; - -/// The voter index. Derived from the solution of the Miner config. -pub type SolutionVoterIndexOf = <::Solution as NposSolution>::VoterIndex; -/// The target index. Derived from the solution of the Miner config. -pub type SolutionTargetIndexOf = <::Solution as NposSolution>::TargetIndex; - -/// The solution type used by this crate. -pub type SolutionOf = ::Solution; - -/// Alias for an error of a fallback election provider. -type FallbackErrorOf = <::Fallback as ElectionProvider>::Error; - -/// Alias for a voter, parameterized by this crate's config. -pub(crate) type VoterOf = - frame_election_provider_support::VoterOf<::DataProvider>; - -/// Same as [`VoterOf`], but parameterized by the `miner::Config`. -pub(crate) type MinerVoterOf = frame_election_provider_support::Voter< - ::AccountId, - ::MaxVotesPerVoter, ->; - -/// Alias for a page of voters, parameterized by this crate's config. -pub(crate) type VoterPageOf = - BoundedVec, ::VoterSnapshotPerBlock>; -/// Alias for a page of targets, parameterized by this crate's config. -pub(crate) type TargetPageOf = - BoundedVec, ::TargetSnapshotPerBlock>; - -/// Same as [`VoterPageOf`], but parameterized by [`miner::Config`]. -pub(crate) type VoterPageMinerOf = - BoundedVec, ::VoterSnapshotPerBlock>; -/// Same as [`TargetPageOf`], but parameterized by []`miner::Config`]. -pub(crate) type TargetPageMinerOf = - BoundedVec<::AccountId, ::TargetSnapshotPerBlock>; - -pub(crate) type MaxWinnersPerPageOf = ::MaxWinnersPerPage; - -/// Alias for all pages of voters, parameterized by the miner's Config. -pub(crate) type AllVoterPagesOf = BoundedVec, ::Pages>; -pub(crate) type AllTargetPagesOf = BoundedVec, ::Pages>; - -/// Edges from voters to nominated targets that are part of the winner set. -pub type AssignmentOf = - sp_npos_elections::Assignment<::AccountId, SolutionAccuracyOf>; - -// Accuracy of the election. -pub type SolutionAccuracyOf = <::Solution as NposSolution>::Accuracy; - -/// Encodes the length of a page of either a solution or a snapshot. -/// -/// This is stored automatically on-chain, and it contains the **size of the entire snapshot page**. -/// This is also used in dispatchables as weight witness data and should **only contain the size of -/// the presented solution page**, not the entire snapshot or page snaphsot. -#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, Debug, Default, TypeInfo)] -pub struct PageSize { - /// The length of voters. - #[codec(compact)] - pub voters: u32, - /// The length of targets. - #[codec(compact)] - pub targets: u32, -} - -/// Strategies for when the election fails. -#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, MaxEncodedLen, Debug, TypeInfo)] -pub enum ElectionFailureStrategy { - /// Enters in emergency phase when election fails. - Emergency, - /// Restarts the election phase without starting a new era. - Restart, -} - -impl Default for ElectionFailureStrategy { - fn default() -> Self { - ElectionFailureStrategy::Restart - } -} - -/// Current phase of an election. -#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, MaxEncodedLen, Debug, TypeInfo)] -pub enum Phase { - /// Election has halted -- nothing will happen. - Halted, - /// The election is off. - Off, - /// Signed phase is open. - Signed, - /// The signed validations phase - SignedValidation(Bn), - Unsigned(Bn), - /// Preparing the paged target and voter snapshots. - Snapshot(PageIndex), - /// Exporting the paged election result (i.e. most likely staking is requesting election - /// pages). It includes the block at which the export phase started. - Export(Bn), - /// Emergency phase, something went wrong and the election is halted. - Emergency, -} - -impl Default for Phase { - fn default() -> Self { - Phase::Off - } -} - -impl Phase { - pub(crate) fn is_off(&self) -> bool { - matches!(self, Phase::Off) - } - - pub(crate) fn is_signed(&self) -> bool { - matches!(self, Phase::Signed) - } - - pub(crate) fn is_snapshot(&self) -> bool { - matches!(self, Phase::Snapshot(_)) - } - - /// Returns whether the validation phase is ongoing. - pub(crate) fn is_signed_validation_open_at(&self, at: Option) -> bool { - match at { - Some(at) => matches!(self, Phase::SignedValidation(real) if *real == at), - None => matches!(self, Phase::SignedValidation(_)), - } - } - - pub(crate) fn is_unsigned_open_at(&self, at: Bn) -> bool { - matches!(self, Phase::Unsigned(real) if *real == at) - } - - pub(crate) fn is_unsigned(&self) -> bool { - matches!(self, Phase::Unsigned(_)) - } - - pub(crate) fn is_export(&self) -> bool { - matches!(self, Phase::Export(_)) - } -} - -#[derive(DebugNoBound, PartialEq)] -pub enum ElectionError { - /// Error returned by the election data provider. - DataProvider, - /// The data provider returned data that exceeded the boundaries defined in the contract with - /// the election provider. - DataProviderBoundariesExceeded, - /// The support `page_index` was not available at request. - SupportPageNotAvailable(PageIndex), - /// The requested page exceeds the number of election pages defined of the current election - /// config. - RequestedPageExceeded, - /// Election not ready yet. - ElectionNotReady, - /// The fallback election error'ed. - Fallback(FallbackErrorOf), -} - -/// A paged raw solution which contains a set of paginated solutions to be submitted. -/// -/// A raw solution has not been checked for correctness. -#[derive( - TypeInfo, - Encode, - Decode, - RuntimeDebugNoBound, - CloneNoBound, - EqNoBound, - PartialEqNoBound, - MaxEncodedLen, - DefaultNoBound, -)] -#[codec(mel_bound(T: MinerConfig))] -#[scale_info(skip_type_params(T))] -pub struct PagedRawSolution { - pub solution_pages: BoundedVec, T::Pages>, - pub score: ElectionScore, - pub round: u32, -} - -/// A helper trait to deal with the page index of partial solutions. -/// -/// This should only be called on the `Vec` or similar types. If the solution is *full*, -/// it returns a normal iterator that is just mapping the index (usize) to `PageIndex`. -/// -/// if the solution is partial, it shifts the indices sufficiently so that the most significant page -/// of the solution matches with the most significant page of the snapshot onchain. -pub trait Pagify { - fn pagify(&self, bound: PageIndex) -> Box + '_>; - fn into_pagify(self, bound: PageIndex) -> Box>; -} - -impl Pagify for Vec { - fn pagify(&self, desired_pages: PageIndex) -> Box + '_> { - Box::new( - self.into_iter() - .enumerate() - .map(|(p, s)| (p.saturated_into::(), s)) - .map(move |(p, s)| { - let desired_pages_usize = desired_pages as usize; - // TODO: this could be an error. - debug_assert!(self.len() <= desired_pages_usize); - let padding = desired_pages_usize.saturating_sub(self.len()); - let new_page = p.saturating_add(padding.saturated_into::()); - (new_page, s) - }), - ) - } - - fn into_pagify(self, _: PageIndex) -> Box> { - todo!() - } -} diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/benchmarking.rs b/substrate/frame/election-provider-multi-block/src/unsigned/benchmarking.rs deleted file mode 100644 index ba2d769a11bc0..0000000000000 --- a/substrate/frame/election-provider-multi-block/src/unsigned/benchmarking.rs +++ /dev/null @@ -1,88 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! # Benchmarking for the Elections Multiblock Unsigned sub-pallet. - -use super::*; -use crate::{ - benchmarking::helpers, signed::Config as ConfigSigned, unsigned::Config, BenchmarkingConfig, - Config as ConfigCore, ConfigVerifier, Pallet as PalletCore, Phase, -}; -use frame_system::RawOrigin; - -use frame_benchmarking::v2::*; - -#[benchmarks( - where T: Config + ConfigCore + ConfigSigned + ConfigVerifier, -)] -mod benchmarks { - use super::*; - - #[benchmark] - fn submit_page_unsigned( - v: Linear< - { ::BenchmarkingConfig::VOTERS_PER_PAGE[0] }, - { ::BenchmarkingConfig::VOTERS_PER_PAGE[1] }, - >, - t: Linear< - { ::BenchmarkingConfig::TARGETS_PER_PAGE[0] }, - { ::BenchmarkingConfig::TARGETS_PER_PAGE[1] }, - >, - ) -> Result<(), BenchmarkError> { - // configs necessary to proceed with the unsigned submission. - PalletCore::::phase_transition(Phase::Unsigned(0u32.into())); - - helpers::setup_data_provider::( - ::BenchmarkingConfig::VOTERS.max(v), - ::BenchmarkingConfig::TARGETS.max(t), - ); - - if let Err(err) = helpers::setup_snapshot::(v, t) { - log!(error, "error setting up snapshot: {:?}.", err); - return Err(BenchmarkError::Stop("snapshot error")); - } - - // the last page (0) will also perfom a full feasibility check for all the pages in the - // queue. For this benchmark, we want to ensure that we do not call `submit_page_unsigned` - // on the last page, to avoid this extra step. - assert!(T::Pages::get() >= 2); - - let (claimed_full_score, partial_score, paged_solution) = - OffchainWorkerMiner::::mine(PalletCore::::msp()).map_err(|err| { - log!(error, "mine error: {:?}", err); - BenchmarkError::Stop("miner error") - })?; - - #[extrinsic_call] - _( - RawOrigin::None, - PalletCore::::msp(), - paged_solution, - partial_score, - claimed_full_score, - ); - - Ok(()) - } - - impl_benchmark_test_suite!( - PalletUnsigned, - crate::mock::ExtBuilder::default(), - crate::mock::Runtime, - exec_name = build_and_execute - ); -} diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs b/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs deleted file mode 100644 index 1a1145b280336..0000000000000 --- a/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs +++ /dev/null @@ -1,811 +0,0 @@ -// 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. - -//! # NPoS miner - -use crate::{ - helpers, - types::{PageSize, Pagify}, - unsigned::{pallet::Config as UnsignedConfig, Call}, - verifier::FeasibilityError, - AssignmentOf, MinerSupportsOf, MinerVoterOf, Pallet as EPM, Snapshot, -}; - -use frame_election_provider_support::{ - ElectionDataProvider, IndexAssignmentOf, NposSolution, NposSolver, PageIndex, - TryIntoBoundedSupports, Weight, -}; -use frame_support::{ensure, traits::Get, BoundedVec}; -use scale_info::TypeInfo; -use sp_npos_elections::{ElectionResult, ElectionScore, ExtendedBalance, Support}; -use sp_runtime::{offchain::storage::StorageValueRef, SaturatedConversion}; -use sp_std::{prelude::ToOwned, vec, vec::Vec}; - -pub type TargetSnaphsotOf = - BoundedVec<::AccountId, ::TargetSnapshotPerBlock>; -pub type VoterSnapshotPagedOf = BoundedVec< - BoundedVec, ::VoterSnapshotPerBlock>, - ::Pages, ->; - -#[derive(Debug, Eq, PartialEq, Clone)] -pub enum MinerError { - /// An internal error in the NPoS elections crate. - NposElections(sp_npos_elections::Error), - /// Snapshot data was unavailable. - SnapshotUnAvailable(SnapshotType), - /// An error from the election solver. - Solver, - /// The solution generated from the miner is not feasible. - Feasibility(FeasibilityError), - InvalidPage, - SubmissionFailed, - NotEnoughTargets, - DataProvider, -} - -impl From for MinerError { - fn from(e: sp_npos_elections::Error) -> Self { - MinerError::NposElections(e) - } -} - -impl From for MinerError { - fn from(e: FeasibilityError) -> Self { - MinerError::Feasibility(e) - } -} - -impl From for MinerError { - fn from(typ: SnapshotType) -> Self { - MinerError::SnapshotUnAvailable(typ) - } -} - -/// The type of the snapshot. -/// -/// Used to express errors. -#[derive(Debug, Eq, PartialEq, Clone)] -pub enum SnapshotType { - /// Voters at the given page missing. - Voters(PageIndex), - /// Targets are missing. - Targets, - // Desired targets are missing. - DesiredTargets, -} - -/// Reports the trimming result of a mined solution -#[derive(Debug, Clone, PartialEq)] -pub struct TrimmingStatus { - weight: usize, - length: usize, -} - -impl Default for TrimmingStatus { - fn default() -> Self { - Self { weight: 0, length: 0 } - } -} - -use crate::PagedRawSolution; -use codec::{EncodeLike, MaxEncodedLen}; - -pub trait Config { - type AccountId: Ord + Clone + codec::Codec + core::fmt::Debug; - - type Solution: codec::Codec - + sp_std::fmt::Debug - + Default - + PartialEq - + Eq - + Clone - + Sized - + Ord - + NposSolution - + TypeInfo - + EncodeLike - + MaxEncodedLen; - - type Solver: NposSolver< - AccountId = Self::AccountId, - Accuracy = ::Accuracy, - >; - - type Pages: Get; - - type MaxVotesPerVoter: Get; - type MaxWinnersPerPage: Get; - type MaxBackersPerWinner: Get; - - type VoterSnapshotPerBlock: Get; - type TargetSnapshotPerBlock: Get; - - type MaxWeight: Get; - type MaxLength: Get; -} - -pub struct Miner(sp_std::marker::PhantomData); - -impl Miner { - pub fn mine_paged_solution_with_snapshot( - all_voter_pages: &BoundedVec< - BoundedVec, T::VoterSnapshotPerBlock>, - T::Pages, - >, - all_targets: &BoundedVec, - pages: PageIndex, - round: u32, - desired_targets: u32, - do_reduce: bool, - ) -> Result<(PagedRawSolution, TrimmingStatus), MinerError> { - // useless to proceed if the solution will not be feasible. - ensure!(all_targets.len() >= desired_targets as usize, MinerError::NotEnoughTargets); - - // flatten pages of voters and target snapshots. - let all_voters: Vec> = - all_voter_pages.iter().cloned().flatten().collect::>(); - - // these closures generate an efficient index mapping of each tvoter -> the snaphot - // that they are part of. this needs to be the same indexing fn in the verifier side to - // sync when reconstructing the assingments page from a solution. - //let binding_targets = all_targets.clone(); - let voters_page_fn = helpers::generate_voter_page_fn::(&all_voter_pages); - let targets_index_fn = helpers::target_index_fn::(&all_targets); - - // run the election with all voters and targets. - let ElectionResult { winners: _, assignments } = ::solve( - desired_targets as usize, - all_targets.clone().to_vec(), - all_voters.clone(), - ) - .map_err(|_| MinerError::Solver)?; - - if do_reduce { - // TODO(gpestana): reduce and trim. - } - // split assignments into `T::Pages pages. - let mut paged_assignments: BoundedVec>, T::Pages> = - BoundedVec::with_bounded_capacity(pages as usize); - - paged_assignments.bounded_resize(pages as usize, vec![]); - - // adds assignment to the correct page, based on the voter's snapshot page. - for assignment in assignments { - let page = voters_page_fn(&assignment.who).ok_or(MinerError::InvalidPage)?; - let assignment_page = - paged_assignments.get_mut(page as usize).ok_or(MinerError::InvalidPage)?; - assignment_page.push(assignment); - } - - // convert each page of assignments to a paged `T::Solution`. - let solution_pages: BoundedVec<::Solution, T::Pages> = paged_assignments - .clone() - .into_iter() - .enumerate() - .map(|(page_index, assignment_page)| { - let page: PageIndex = page_index.saturated_into(); - let voter_snapshot_page = all_voter_pages - .get(page as usize) - .ok_or(MinerError::SnapshotUnAvailable(SnapshotType::Voters(page)))?; - - let voters_index_fn = { - let cache = helpers::generate_voter_cache::(&voter_snapshot_page); - helpers::voter_index_fn_owned::(cache) - }; - - <::Solution>::from_assignment( - &assignment_page, - &voters_index_fn, - &targets_index_fn, - ) - .map_err(|e| MinerError::NposElections(e)) - }) - .collect::, _>>()? - .try_into() - .expect("paged_assignments is bound by `T::Pages. qed."); - - // TODO(gpestana): trim again? - let trimming_status = Default::default(); - - let mut paged_solution = - PagedRawSolution { solution_pages, score: Default::default(), round }; - - // everytthing's ready - calculate final solution score. - paged_solution.score = - Self::compute_score(all_voter_pages, all_targets, &paged_solution, desired_targets)?; - - Ok((paged_solution, trimming_status)) - } - - /// Take the given raw paged solution and compute its score. This will replicate what the chain - /// would do as closely as possible, and expects all the corresponding snapshot data to be - /// available. - fn compute_score( - voters: &VoterSnapshotPagedOf, - targets: &TargetSnaphsotOf, - paged_solution: &PagedRawSolution, - desired_targets: u32, - ) -> Result { - use sp_npos_elections::EvaluateSupport; - use sp_std::collections::btree_map::BTreeMap; - - let all_supports = - Self::feasibility_check(voters, targets, paged_solution, desired_targets)?; - let mut total_backings: BTreeMap = BTreeMap::new(); - all_supports.into_iter().map(|x| x.0).flatten().for_each(|(who, support)| { - let backing = total_backings.entry(who).or_default(); - *backing = backing.saturating_add(support.total); - }); - - let all_supports = total_backings - .into_iter() - .map(|(who, total)| (who, Support { total, ..Default::default() })) - .collect::>(); - - Ok((&all_supports).evaluate()) - } - - // Checks the feasibility of a paged solution and calculates the score associated with the - // page. - pub fn compute_partial_score( - voters: &VoterSnapshotPagedOf, - targets: &TargetSnaphsotOf, - solution: &::Solution, - desired_targets: u32, - page: PageIndex, - ) -> Result { - let supports = Self::feasibility_check_partial( - voters, - targets, - solution.clone(), - desired_targets, - page, - )?; - let score = sp_npos_elections::evaluate_support( - supports.clone().into_iter().map(|(_, backings)| backings), - ); - - Ok(score) - } - - /// Perform the feasibility check on all pages of a solution, one by one, and returns the - /// supports of the full solution. - pub fn feasibility_check( - voters: &VoterSnapshotPagedOf, - targets: &TargetSnaphsotOf, - paged_solution: &PagedRawSolution, - desired_targets: u32, - ) -> Result>, MinerError> { - // check every solution page for feasibility. - paged_solution - .solution_pages - .pagify(T::Pages::get()) - .map(|(page_index, page_solution)| { - Self::feasibility_check_partial( - voters, - targets, - page_solution.clone(), - desired_targets, - page_index as PageIndex, - ) - }) - .collect::, _>>() - .map_err(|err| MinerError::from(err)) - } - - /// Performs the feasibility check of a single page, returns the supports of the partial - /// feasibility check. - pub fn feasibility_check_partial( - voters: &VoterSnapshotPagedOf, - targets: &TargetSnaphsotOf, - partial_solution: ::Solution, - desired_targets: u32, - page: PageIndex, - ) -> Result, FeasibilityError> { - let voters_page: BoundedVec, ::VoterSnapshotPerBlock> = voters - .get(page as usize) - .ok_or(FeasibilityError::Incomplete) - .map(|v| v.to_owned())?; - - let voter_cache = helpers::generate_voter_cache::(&voters_page); - let voter_at = helpers::voter_at_fn::(&voters_page); - let target_at = helpers::target_at_fn::(targets); - let voter_index = helpers::voter_index_fn_usize::(&voter_cache); - - // Then convert solution -> assignment. This will fail if any of the indices are - // gibberish. - let assignments = partial_solution - .into_assignment(voter_at, target_at) - .map_err::(Into::into)?; - - // Ensure that assignments are all correct. - let _ = assignments - .iter() - .map(|ref assignment| { - // Check that assignment.who is actually a voter (defensive-only). NOTE: while - // using the index map from `voter_index` is better than a blind linear search, - // this *still* has room for optimization. Note that we had the index when we - // did `solution -> assignment` and we lost it. Ideal is to keep the index - // around. - - // Defensive-only: must exist in the snapshot. - let snapshot_index = - voter_index(&assignment.who).ok_or(FeasibilityError::InvalidVoter)?; - // Defensive-only: index comes from the snapshot, must exist. - let (_voter, _stake, targets) = - voters_page.get(snapshot_index).ok_or(FeasibilityError::InvalidVoter)?; - debug_assert!(*_voter == assignment.who); - - // Check that all of the targets are valid based on the snapshot. - if assignment.distribution.iter().any(|(t, _)| !targets.contains(t)) { - return Err(FeasibilityError::InvalidVote) - } - Ok(()) - }) - .collect::>()?; - - // ----- Start building support. First, we need one more closure. - let stake_of = helpers::stake_of_fn::(&voters_page, &voter_cache); - - // This might fail if the normalization fails. Very unlikely. See `integrity_test`. - let staked_assignments = - sp_npos_elections::assignment_ratio_to_staked_normalized(assignments, stake_of) - .map_err::(Into::into)?; - - let supports = sp_npos_elections::to_supports(&staked_assignments); - - // Check the maximum number of backers per winner. If this is a single-page solution, this - // is enough to check `MaxBackersPerWinner`. Else, this is just a heuristic, and needs to be - // checked again at the end (via `QueuedSolutionBackings`). - ensure!( - supports - .iter() - .all(|(_, s)| (s.voters.len() as u32) <= T::MaxBackersPerWinner::get()), - FeasibilityError::TooManyBackings - ); - - // supports per page must not be higher than the desired targets, otherwise final solution - // will also be higher than desired_targets. - ensure!((supports.len() as u32) <= desired_targets, FeasibilityError::WrongWinnerCount); - - // almost-defensive-only: `MaxBackersPerWinner` is already checked. A sane value of - // `MaxWinnersPerPage` should be more than any possible value of `desired_targets()`, which - // is ALSO checked, so this conversion can almost never fail. - let bounded_supports = supports - .try_into_bounded_supports() - .map_err(|_| FeasibilityError::WrongWinnerCount)?; - - Ok(bounded_supports) - } - - /// Greedily reduce the size of the solution to fit into the block w.r.t length. - /// - /// The length of the solution is largely a function of the number of voters. The number of - /// winners cannot be changed Thus, to reduce the solution size, we need to strip voters. - /// - /// Note that this solution is already computed, and winners are elected based on the merit of - /// the total stake in the system. Nevertheless, some of the voters may be removed here. - /// - /// Sometimes, removing a voter can cause a validator to also be implicitly removed, if - /// that voter was the only backer of that winner. In such cases, this solution is invalid, - /// which will be caught prior to submission. - /// - /// The score must be computed **after** this step. If this step reduces the score too much, - /// then the solution must be discarded. - pub fn trim_assignments_length( - max_allowed_length: u32, - assignments: &mut Vec>, - encoded_size_of: impl Fn( - &[IndexAssignmentOf], - ) -> Result, - ) -> Result { - // Perform a binary search for the max subset of which can fit into the allowed - // length. Having discovered that, we can truncate efficiently. - let max_allowed_length: usize = max_allowed_length.saturated_into(); - let mut high = assignments.len(); - let mut low = 0; - - // not much we can do if assignments are already empty. - if high == low { - return Ok(0) - } - - while high - low > 1 { - let test = (high + low) / 2; - if encoded_size_of(&assignments[..test])? <= max_allowed_length { - low = test; - } else { - high = test; - } - } - let maximum_allowed_voters = if low < assignments.len() && - encoded_size_of(&assignments[..low + 1])? <= max_allowed_length - { - low + 1 - } else { - low - }; - - // ensure our post-conditions are correct - //debug_assert!( - // encoded_size_of(&assignments[..maximum_allowed_voters]).unwrap() <= max_allowed_length - //); - debug_assert!(if maximum_allowed_voters < assignments.len() { - encoded_size_of(&assignments[..maximum_allowed_voters + 1]).unwrap() > - max_allowed_length - } else { - true - }); - - // NOTE: before this point, every access was immutable. - // after this point, we never error. - // check before edit. - - let remove = assignments.len().saturating_sub(maximum_allowed_voters); - assignments.truncate(maximum_allowed_voters); - - Ok(remove) - } - - /// Greedily reduce the size of the solution to fit into the block w.r.t. weight. - /// - /// The weight of the solution is foremost a function of the number of voters (i.e. - /// `assignments.len()`). Aside from this, the other components of the weight are invariant. The - /// number of winners shall not be changed (otherwise the solution is invalid) and the - /// `ElectionSize` is merely a representation of the total number of stakers. - /// - /// Thus, we reside to stripping away some voters from the `assignments`. - /// - /// Note that the solution is already computed, and the winners are elected based on the merit - /// of the entire stake in the system. Nonetheless, some of the voters will be removed further - /// down the line. - /// - /// Indeed, the score must be computed **after** this step. If this step reduces the score too - /// much or remove a winner, then the solution must be discarded **after** this step. - pub fn trim_assignments_weight( - desired_targets: u32, - size: PageSize, - max_weight: Weight, - assignments: &mut Vec>, - ) -> usize { - let maximum_allowed_voters = - Self::maximum_voter_for_weight(desired_targets, size, max_weight); - let removing: usize = - assignments.len().saturating_sub(maximum_allowed_voters.saturated_into()); - assignments.truncate(maximum_allowed_voters as usize); - - removing - } - - /// Find the maximum `len` that a solution can have in order to fit into the block weight. - /// - /// This only returns a value between zero and `size.nominators`. - pub fn maximum_voter_for_weight( - _desired_winners: u32, - size: PageSize, - max_weight: Weight, - ) -> u32 { - if size.voters < 1 { - return size.voters - } - - let max_voters = size.voters.max(1); - let mut voters = max_voters; - - // helper closures. - let weight_with = |_active_voters: u32| -> Weight { - Weight::zero() // TODO - }; - - let next_voters = |current_weight: Weight, voters: u32, step: u32| -> Result { - if current_weight.all_lt(max_weight) { - let next_voters = voters.checked_add(step); - match next_voters { - Some(voters) if voters < max_voters => Ok(voters), - _ => Err(()), - } - } else if current_weight.any_gt(max_weight) { - voters.checked_sub(step).ok_or(()) - } else { - // If any of the constituent weights is equal to the max weight, we're at max - Ok(voters) - } - }; - - // First binary-search the right amount of voters - let mut step = voters / 2; - let mut current_weight = weight_with(voters); - - while step > 0 { - match next_voters(current_weight, voters, step) { - // proceed with the binary search - Ok(next) if next != voters => { - voters = next; - }, - // we are out of bounds, break out of the loop. - Err(()) => break, - // we found the right value - early exit the function. - Ok(next) => return next, - } - step /= 2; - current_weight = weight_with(voters); - } - - // Time to finish. We might have reduced less than expected due to rounding error. Increase - // one last time if we have any room left, the reduce until we are sure we are below limit. - while voters < max_voters && weight_with(voters + 1).all_lt(max_weight) { - voters += 1; - } - while voters.checked_sub(1).is_some() && weight_with(voters).any_gt(max_weight) { - voters -= 1; - } - - let final_decision = voters.min(size.voters); - debug_assert!( - weight_with(final_decision).all_lte(max_weight), - "weight_with({}) <= {}", - final_decision, - max_weight, - ); - final_decision - } -} - -/// Errors associated with the off-chain worker miner. -#[derive( - frame_support::DebugNoBound, frame_support::EqNoBound, frame_support::PartialEqNoBound, -)] -pub enum OffchainMinerError { - Miner(MinerError), - PoolSubmissionFailed, - NotUnsignedPhase, - StorageError, - PageOutOfBounds, - Snapshots, -} - -impl From for OffchainMinerError { - fn from(e: MinerError) -> Self { - OffchainMinerError::Miner(e) - } -} - -/// A miner used in the context of the offchain worker for unsigned submissions. -pub(crate) struct OffchainWorkerMiner(sp_std::marker::PhantomData); - -impl OffchainWorkerMiner { - /// The off-chain storage lock to work with unsigned submissions. - pub(crate) const OFFCHAIN_LOCK: &'static [u8] = b"parity/multi-block-unsigned-election/lock"; - - /// The off-chain storage ID prefix for each of the solution's pages. Each page will be - /// prefixed by this ID, followed by the page index. The full page ID for a given index can be - /// generated by [`Self::page_cache_id`]. - pub(crate) const OFFCHAIN_CACHED_SOLUTION: &'static [u8] = - b"parity/multi-block-unsigned-election/solution"; - - /// The off-chain storage ID for the solution's full score. - pub(crate) const OFFCHAIN_CACHED_SCORE: &'static [u8] = - b"parity/multi-block-unsigned-election/score"; - - /// Mine a solution. - /// - /// Mines a new solution with [`crate::Pallet::Pages`] pages and computes the partial score - /// of the page with `page` index. - #[allow(dead_code)] - pub fn mine( - page: PageIndex, - ) -> Result< - (ElectionScore, ElectionScore, ::Solution), - OffchainMinerError, - > { - let reduce = true; - - let (all_voter_pages, all_targets) = Self::fetch_snapshots()?; - let round = crate::Pallet::::current_round(); - let desired_targets = - <::DataProvider as ElectionDataProvider>::desired_targets() - .map_err(|_| MinerError::DataProvider)?; - - let (solution, _trimming_status) = - Miner::::mine_paged_solution_with_snapshot( - &all_voter_pages, - &all_targets, - T::Pages::get(), - round, - desired_targets, - reduce, - )?; - - let partial_solution = solution - .solution_pages - .get(page as usize) - .ok_or(OffchainMinerError::PageOutOfBounds)?; - - let partial_score = Miner::::compute_partial_score( - &all_voter_pages, - &all_targets, - &partial_solution, - desired_targets, - page, - )?; - - Ok((solution.score, partial_score, partial_solution.clone())) - } - - pub(crate) fn fetch_snapshots() -> Result< - (VoterSnapshotPagedOf, TargetSnaphsotOf), - OffchainMinerError, - > { - // prepare range to fetch all pages of the target and voter snapshot. - let paged_range = 0..EPM::::msp() + 1; - - // fetch all pages of the voter snapshot and collect them in a bounded vec. - let all_voter_pages: BoundedVec<_, T::Pages> = paged_range - .map(|page| { - Snapshot::::voters(page) - .ok_or(MinerError::SnapshotUnAvailable(SnapshotType::Voters(page))) - }) - .collect::, _>>()? - .try_into() - .expect("range was constructed from the bounded vec bounds; qed."); - - // fetch all pages of the target snapshot and collect them in a bounded vec. - let all_targets = Snapshot::::targets() - .ok_or(MinerError::SnapshotUnAvailable(SnapshotType::Targets))?; - - Ok((all_voter_pages, all_targets)) - } - - /// Fetches from the local storage or mines a new solution. - /// - /// Calculates and returns the partial score of paged solution of the given `page` index. - pub fn fetch_or_mine( - page: PageIndex, - ) -> Result< - (ElectionScore, ElectionScore, ::Solution), - OffchainMinerError, - > { - let cache_id = Self::paged_cache_id(page)?; - let score_storage = StorageValueRef::persistent(&Self::OFFCHAIN_CACHED_SCORE); - let maybe_storage = StorageValueRef::persistent(&cache_id); - - let (full_score, paged_solution, partial_score) = - if let Ok(Some((solution_page, partial_score))) = - maybe_storage.get::<(::Solution, ElectionScore)>() - { - sublog!(debug, "unsigned::ocw-miner", "offchain restoring a solution from cache."); - - let full_score = score_storage - .get() - .map_err(|_| OffchainMinerError::StorageError)? - .ok_or(OffchainMinerError::StorageError)?; - - (full_score, solution_page, partial_score) - } else { - // no solution cached, compute it first. - sublog!(debug, "unsigned::ocw-miner", "offchain miner computing a new solution."); - - // fetch snapshots. - let (all_voter_pages, all_targets) = Self::fetch_snapshots()?; - let round = crate::Pallet::::current_round(); - let desired_targets = - <::DataProvider as ElectionDataProvider>::desired_targets() - .map_err(|_| MinerError::DataProvider)?; - - let reduce = false; // TODO - - let (solution, _trimming_status) = - Miner::::mine_paged_solution_with_snapshot( - &all_voter_pages, - &all_targets, - T::Pages::get(), - round, - desired_targets, - reduce, - )?; - - // caches the solution score. - score_storage - .mutate::<_, (), _>(|_| Ok(solution.score.clone())) - .map_err(|_| OffchainMinerError::StorageError)?; - - let mut solution_page = Default::default(); - let mut partial_score_r: ElectionScore = Default::default(); - - // caches each of the individual pages and their partial score under its own key. - for (idx, paged_solution) in solution.solution_pages.into_iter().enumerate() { - let partial_score = Miner::::compute_partial_score( - &all_voter_pages, - &all_targets, - &paged_solution, - desired_targets, - idx as u32, - )?; - - let cache_id = Self::paged_cache_id(idx as PageIndex)?; - let storage = StorageValueRef::persistent(&cache_id); - storage - .mutate::<_, (), _>(|_| Ok((paged_solution.clone(), partial_score))) - .map_err(|_| OffchainMinerError::StorageError)?; - - // save to return the requested paged solution and partial score. - if idx as PageIndex == page { - solution_page = paged_solution; - partial_score_r = partial_score; - } - } - (solution.score, solution_page, partial_score_r) - }; - - Ok((full_score, partial_score, paged_solution)) - } - - /// Clears all local storage items related to the unsigned off-chain miner. - pub(crate) fn clear_cache() { - let mut score_storage = StorageValueRef::persistent(&Self::OFFCHAIN_CACHED_SCORE); - score_storage.clear(); - - for idx in (0..::Pages::get()).into_iter() { - let cache_id = Self::paged_cache_id(idx as PageIndex) - .expect("page index was calculated based on the msp."); - let mut page_storage = StorageValueRef::persistent(&cache_id); - - page_storage.clear(); - } - - sublog!(debug, "unsigned", "offchain miner cache cleared."); - } - - /// Generate the page cache ID based on the `page` index and the - /// [`Self::OFFCHAIN_CACHED_SOLUTION`] prefix. - fn paged_cache_id(page: PageIndex) -> Result, OffchainMinerError> { - let mut id = Self::OFFCHAIN_CACHED_SOLUTION.to_vec(); - id.push(page.try_into().map_err(|_| OffchainMinerError::PageOutOfBounds)?); - Ok(id) - } - - /// Submits a paged solution through the [`Call::submit_page_unsigned`] callable as an - /// inherent. - pub(crate) fn submit_paged_call( - page: PageIndex, - solution: ::Solution, - partial_score: ElectionScore, - claimed_full_score: ElectionScore, - ) -> Result<(), OffchainMinerError> { - sublog!( - debug, - "unsigned::ocw-miner", - "miner submitting a solution as an unsigned transaction, page: {:?}", - page, - ); - - let call = Call::submit_page_unsigned { page, solution, partial_score, claimed_full_score }; - let xt = T::create_inherent(call.into()); - - frame_system::offchain::SubmitTransaction::>::submit_transaction(xt) - .map(|_| { - sublog!( - debug, - "unsigned::ocw-miner", - "miner submitted a solution as an unsigned transaction, page {:?}", - page - ); - }) - .map_err(|_| OffchainMinerError::PoolSubmissionFailed) - } -} diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs b/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs deleted file mode 100644 index 8d3b244b588a0..0000000000000 --- a/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs +++ /dev/null @@ -1,357 +0,0 @@ -// 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. - -//! # Unsigned sub-pallet -//! -//! The main goal of this sub-pallet is to manage the unsigned phase submissions by an off-chain -//! worker. It implements the `offchain_worker` hook which will compute and store -//! in the off-chain cache a paged solution and try to submit it if: -//! -//! - Current phase is [`crate::Phase::Unsigned`]; -//! - The score of the computed solution is better than the minimum score defined by the verifier -//! pallet and the current election score stored by the [`crate::signed::Pallet`]. -//! -//! During the unsigned phase, multiple block builders will collaborate to submit the full -//! solution, one page per block. -//! -//! ## Sync/Async off-chain worker -//! -//! The unsigned phase relies on a mix of sync and async checks to ensure that the paged unsigned -//! submissions (and final solution) are correct, namely: -//! -//! - Synchronous checks: each block builder will compute the *full* election solution. However, -//! only one page -//! is verified through the [Verifier::verify_synchronous] and submitted through the -//! [`Call::submit_page_unsigned`] callable as an inherent. -//! - Asynchronous checks: once all pages are submitted, the [`Call::submit_page_unsigned`] will -//! call [`verifier::AsyncVerifier::force_finalize_verification`] to ensure that the full solution -//! submitted by all the block builders is good. -//! -//! In sum, each submitted page is verified using the synchronous verification implemented by the -//! verifier pallet (i.e. [`verifier::Verifier::verify_synchronous`]). The pages are submitted by -//! order from [`crate::Pallet::msp`] down to [`crate::Pallet::lsp`]. After successfully submitting -//! the last page, the [`verifier::AsyncVerifier::force_finalize_verification`], which will perform -//! the last feasibility checks over the full stored solution. -//! -//! At each block of the unsigned phase, the block builder running the node with the off-chain -//! worker enabled will compute a solution based on the round's snapshot. The solution is pagified -//! and stored in the local cache. -//! -//! The off-chain miner will *always* compute a new solution regardless of whether there -//! is a queued solution for the current era. The solution will be added to the storage through the -//! inherent [`Call::submit_page_unsigned`] only if the computed (total) solution score is strictly -//! better than the current queued solution. - -pub mod miner; -pub mod weights; - -#[cfg(feature = "runtime-benchmarks")] -pub mod benchmarking; - -#[cfg(test)] -mod tests; - -use crate::{ - unsigned::{ - miner::{OffchainMinerError, OffchainWorkerMiner}, - weights::WeightInfo, - }, - verifier, Phase, SolutionOf, Verifier, -}; -use frame_election_provider_support::PageIndex; -use frame_support::{ - ensure, - pallet_prelude::{TransactionValidity, ValidTransaction}, - traits::Get, -}; -use frame_system::{ensure_none, offchain::CreateInherent, pallet_prelude::BlockNumberFor}; -use sp_npos_elections::ElectionScore; -use sp_runtime::SaturatedConversion; - -// public re-exports. -pub use pallet::{ - Call, Config, Event, Pallet, __substrate_call_check, __substrate_event_check, - __substrate_validate_unsigned_check, tt_default_parts, tt_default_parts_v2, tt_error_token, -}; - -#[frame_support::pallet] -pub(crate) mod pallet { - - use super::*; - use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::OriginFor; - - #[pallet::config] - #[pallet::disable_frame_system_supertrait_check] - pub trait Config: crate::Config + CreateInherent> { - /// The overarching event type. - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - - /// The off-chain worker interval between retrying to submit a solution. - type OffchainRepeatInterval: Get>; - - /// The priority of the unsigned tx submitted. - type MinerTxPriority: Get; - - /// Maximum length of the solution that the miner is allowed to generate. - /// - /// Solutions are trimmed to respect this. - type MaxLength: Get; - - /// Maximum weight of the solution that the miner is allowed to generate. - /// - /// Solutions are trimmed to respect this. - /// - /// The weight is computed using `solution_weight`. - type MaxWeight: Get; - - /// The weights for this pallet. - type WeightInfo: WeightInfo; - } - - #[pallet::pallet] - pub struct Pallet(PhantomData); - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - /// Unsigned solution submitted successfully. - UnsignedSolutionSubmitted { at: BlockNumberFor, page: PageIndex }, - } - - #[pallet::validate_unsigned] - impl ValidateUnsigned for Pallet { - type Call = Call; - - fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { - if let Call::submit_page_unsigned { page, partial_score, .. } = call { - ValidTransaction::with_tag_prefix("ElectionOffchainWorker") - // priority increases propotional to the `solution.minimal_stake`. - .priority( - T::MinerTxPriority::get() - .saturating_add(partial_score.minimal_stake.saturated_into()), - ) - // deduplicates unsigned solutions since each validator should calculate at most - // one paged solution per block. - .and_provides(page) - // transaction stays in the pool as long as the unsigned phase. - .longevity(T::UnsignedPhase::get().saturated_into::()) - .propagate(false) - .build() - } else { - sublog!(info, "unsigned", "validate_unsigned ERROR"); - InvalidTransaction::Call.into() - } - } - } - - #[pallet::call] - impl Pallet { - /// Submit a paged unsigned solution. - /// - /// The dispatch origin fo this call must be __none__. - /// - /// This submission is checked on the fly. Moreover, this unsigned solution is only - /// validated when submitted to the pool from the **local** node. Effectively, this means - /// that only active validators can submit this transaction when authoring a block (similar - /// to an inherent). - /// - /// To prevent any incorrect solution (and thus wasted time/weight), this transaction will - /// panic if the solution submitted by the validator is invalid in any way, effectively - /// putting their authoring reward at risk. - /// - /// No deposit or reward is associated with this submission. - #[pallet::call_index(1)] - #[pallet::weight(::WeightInfo::submit_page_unsigned( - ::MaxBackersPerWinner::get(), - ::MaxWinnersPerPage::get(), - ))] - pub fn submit_page_unsigned( - origin: OriginFor, - page: PageIndex, - solution: SolutionOf, - partial_score: ElectionScore, - claimed_full_score: ElectionScore, - ) -> DispatchResult { - ensure_none(origin)?; - let error_message = "Invalid unsigned submission must produce invalid block and \ - deprive validator from their authoring reward."; - - sublog!( - info, - "unsigned", - "submitting page {:?} with partial score {:?}", - page, - partial_score - ); - - // Check if score is an improvement, the current phase, page index and other paged - // solution metadata checks. - Self::pre_dispatch_checks(page, &claimed_full_score).expect(error_message); - - // The verifier will store the paged solution, if valid. - let _ = ::verify_synchronous( - solution, - partial_score, - page, - ) - .expect(error_message); - - // if all pages have been submitted, request an async verification finalization which - // will work on the queued paged solutions. - if ::next_missing_solution_page().is_none() { - ::force_finalize_verification( - claimed_full_score, - ) - .expect(error_message); - sublog!(info, "unsigned", "validate_unsigned last page verify OK"); - } else { - sublog!(info, "unsigned", "submit_page_unsigned: page {:?} submitted", page); - } - - Self::deposit_event(Event::UnsignedSolutionSubmitted { - at: >::block_number(), - page, - }); - - Ok(()) - } - } - - #[pallet::hooks] - impl Hooks> for Pallet { - fn on_initialize(_n: BlockNumberFor) -> Weight { - if crate::Pallet::::current_phase() == Phase::Off { - T::DbWeight::get().reads_writes(1, 1) - } else { - Default::default() - } - } - - /// The off-chain worker implementation - /// - /// The off-chain worker for this pallet will run IFF: - /// - /// - It can obtain the off-chain worker lock; - /// - The current block is part of the unsigned phase; - fn offchain_worker(now: BlockNumberFor) { - use sp_runtime::offchain::storage_lock::{BlockAndTime, StorageLock}; - - let mut lock = - StorageLock::>>::with_block_deadline( - miner::OffchainWorkerMiner::::OFFCHAIN_LOCK, - T::UnsignedPhase::get().saturated_into(), - ); - - if crate::Pallet::::current_phase().is_unsigned() { - match lock.try_lock() { - Ok(_guard) => { - sublog!(info, "unsigned", "obtained offchain lock at {:?}", now); - let _ = Self::do_sync_offchain_worker(now).map_err(|e| { - sublog!(debug, "unsigned", "offchain worker error."); - e - }); - }, - Err(deadline) => { - sublog!( - debug, - "unsigned", - "offchain worker lock not released, deadline is {:?}", - deadline - ); - }, - }; - } - } - - fn integrity_test() { - // TODO - } - - #[cfg(feature = "try-runtime")] - fn try_state(_n: BlockNumberFor) -> Result<(), sp_runtime::TryRuntimeError> { - todo!() - } - } -} - -impl Pallet { - /// Perform the off-chain worker workload. - /// - /// If the current block is part of the unsigned phase and there are missing solution pages: - /// - /// 1. Compute or restore a mined solution; - /// 2. Pagify the solution; - /// 3. Calculate the partial score for the page to submit; - /// 4. Verify if the *total* solution is strictly better than the current queued solution or - /// better than the minimum score, of no queued solution exists. - /// 5. Submits the paged solution as an inherent through the [`Call::submit_page_unsigned`] - /// callable. - pub fn do_sync_offchain_worker(_now: BlockNumberFor) -> Result<(), OffchainMinerError> { - let missing_solution_page = ::next_missing_solution_page(); - - match (crate::Pallet::::current_phase(), missing_solution_page) { - (Phase::Unsigned(_), Some(page)) => { - let (full_score, partial_score, partial_solution) = - OffchainWorkerMiner::::fetch_or_mine(page).map_err(|err| { - sublog!(error, "unsigned", "OCW mine error: {:?}", err); - err - })?; - - // submit page only if full score improves the current queued score. - if ::ensure_score_quality(full_score) { - OffchainWorkerMiner::::submit_paged_call( - page, - partial_solution, - partial_score, - full_score, - )?; - } else { - sublog!( - debug, - "unsigned", - "unsigned solution with score {:?} does not improve current queued solution; skip it.", - full_score - ); - } - }, - (Phase::Export(_), _) | (Phase::Unsigned(_), None) => { - // Unsigned phase is over or unsigned solution is no more required, clear the - // cache. - OffchainWorkerMiner::::clear_cache(); - }, - _ => (), // nothing to do here. - } - - Ok(()) - } - - /// Ihnerent pre-dispatch checks. - pub(crate) fn pre_dispatch_checks( - page: PageIndex, - claimed_full_score: &ElectionScore, - ) -> Result<(), ()> { - // timing and metadata checks. - ensure!(crate::Pallet::::current_phase().is_unsigned(), ()); - ensure!(page <= crate::Pallet::::msp(), ()); - - // full solution score check. - ensure!(::ensure_score_quality(*claimed_full_score), ()); - - Ok(()) - } -} diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/tests.rs b/substrate/frame/election-provider-multi-block/src/unsigned/tests.rs deleted file mode 100644 index 3464eb0be4536..0000000000000 --- a/substrate/frame/election-provider-multi-block/src/unsigned/tests.rs +++ /dev/null @@ -1,210 +0,0 @@ -// 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 super::*; -use crate::{mock::*, PagedVoterSnapshot, Phase, Snapshot, TargetSnapshot, Verifier}; - -use frame_election_provider_support::ElectionProvider; -use frame_support::assert_ok; - -mod calls { - use super::*; - - #[test] - fn unsigned_submission_works() { - let (mut ext, pool) = ExtBuilder::default().build_offchainify(0); - ext.execute_with(|| { - // election predicted at 30. - assert_eq!(election_prediction(), 30); - - // no solution available until the unsigned phase. - assert!(::queued_score().is_none()); - assert!(::get_queued_solution(2).is_none()); - - // progress through unsigned phase just before the election. - roll_to_with_ocw(29, Some(pool.clone())); - - // successful submission events for all 3 pages, as expected. - assert_eq!( - unsigned_events(), - [ - Event::UnsignedSolutionSubmitted { at: 19, page: 2 }, - Event::UnsignedSolutionSubmitted { at: 20, page: 1 }, - Event::UnsignedSolutionSubmitted { at: 21, page: 0 } - ] - ); - // now, solution exists. - assert!(::queued_score().is_some()); - assert!(::get_queued_solution(2).is_some()); - assert!(::get_queued_solution(1).is_some()); - assert!(::get_queued_solution(0).is_some()); - - // roll to election prediction bn. - roll_to_with_ocw(election_prediction(), Some(pool.clone())); - - // now in the export phase. - assert!(current_phase().is_export()); - - // thus, elect() works as expected. - assert!(call_elect().is_ok()); - - assert_eq!(current_phase(), Phase::Off); - }) - } - - #[test] - fn unsigned_submission_no_snapshot() { - let (mut ext, pool) = ExtBuilder::default().build_offchainify(1); - ext.execute_with(|| { - // election predicted at 30. - assert_eq!(election_prediction(), 30); - - roll_to_phase_with_ocw(Phase::Signed, Some(pool.clone())); - - // no solution available until the unsigned phase. - assert!(::queued_score().is_none()); - assert!(::get_queued_solution(2).is_none()); - - // but snapshot exists. - assert!(PagedVoterSnapshot::::get(crate::Pallet::::lsp()).is_some()); - assert!(TargetSnapshot::::get().is_some()); - // so let's clear it. - clear_snapshot(); - assert!(PagedVoterSnapshot::::get(crate::Pallet::::lsp()).is_none()); - assert!(TargetSnapshot::::get().is_none()); - - // progress through unsigned phase just before the election. - roll_to_with_ocw(29, Some(pool.clone())); - - // snapshot was not available, so unsigned submissions and thus no solution queued. - assert_eq!(unsigned_events().len(), 0); - // no solution available until the unsigned phase. - assert!(::queued_score().is_none()); - assert!(::get_queued_solution(2).is_none()); - - // call elect (which fails) to restart the phase. - assert!(call_elect().is_err()); - assert_eq!(current_phase(), Phase::Off); - - roll_to_phase_with_ocw(Phase::Signed, Some(pool.clone())); - - // snapshot exists now. - assert!(PagedVoterSnapshot::::get(crate::Pallet::::lsp()).is_some()); - assert!(TargetSnapshot::::get().is_some()); - - roll_to_with_ocw(election_prediction() - 1, Some(pool.clone())); - - // successful submission events for all 3 pages, as expected. - assert_eq!( - unsigned_events(), - [ - Event::UnsignedSolutionSubmitted { at: 49, page: 2 }, - Event::UnsignedSolutionSubmitted { at: 50, page: 1 }, - Event::UnsignedSolutionSubmitted { at: 51, page: 0 } - ] - ); - // now, solution exists. - assert!(::queued_score().is_some()); - assert!(::get_queued_solution(2).is_some()); - assert!(::get_queued_solution(1).is_some()); - assert!(::get_queued_solution(0).is_some()); - - // elect() works as expected. - assert_ok!(::elect(2)); - assert_ok!(::elect(1)); - assert_ok!(::elect(0)); - - assert_eq!(current_phase(), Phase::Off); - }) - } -} - -mod miner { - use super::*; - - #[test] - fn snapshot_idx_based_works() { - ExtBuilder::default().build_and_execute(|| { - roll_to_phase(Phase::Signed); - - let mut all_voter_pages = vec![]; - let mut all_target_pages = vec![]; - - for page in (0..Pages::get()).rev() { - all_voter_pages.push(Snapshot::::voters(page).unwrap()); - all_target_pages.push(Snapshot::::targets().unwrap()); - } - }) - } - - #[test] - fn desired_targets_bounds_works() { - ExtBuilder::default() - .max_winners_per_page(3) - .desired_targets(3) - .build_and_execute(|| { - // max winner per page == desired_targets, OK. - compute_snapshot_checked(); - assert_ok!(mine_and_verify_all()); - - // max winner per page > desired_targets, OK. - MaxWinnersPerPage::set(4); - compute_snapshot_checked(); - assert_ok!(mine_and_verify_all()); - - // max winner per page < desired_targets, fails. - MaxWinnersPerPage::set(2); - compute_snapshot_checked(); - assert!(mine_and_verify_all().is_err()); - }) - } - - #[test] - fn fetch_or_mine() { - let (mut ext, _) = ExtBuilder::default().build_offchainify(1); - - ext.execute_with(|| { - let msp = crate::Pallet::::msp(); - assert_eq!(msp, 2); - - // no snapshot available, calling mine_paged_solution should fail. - assert!(::queued_score().is_none()); - assert!(::get_queued_solution(msp).is_none()); - - assert!(OffchainWorkerMiner::::fetch_or_mine(0).is_err()); - compute_snapshot_checked(); - - let (full_score_2, partial_score_2, _) = - OffchainWorkerMiner::::fetch_or_mine(msp).unwrap(); - let (full_score_1, partial_score_1, _) = - OffchainWorkerMiner::::fetch_or_mine(msp - 1).unwrap(); - let (full_score_0, partial_score_0, _) = - OffchainWorkerMiner::::fetch_or_mine(0).unwrap(); - - assert!(full_score_2 == full_score_1 && full_score_2 == full_score_0); - assert!( - full_score_2.sum_stake == full_score_1.sum_stake && - full_score_2.sum_stake == full_score_0.sum_stake - ); - - assert_eq!( - partial_score_0.sum_stake + partial_score_1.sum_stake + partial_score_2.sum_stake, - full_score_0.sum_stake - ); - }) - } -} diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/weights.rs b/substrate/frame/election-provider-multi-block/src/unsigned/weights.rs deleted file mode 100644 index a593dfc8c7d9c..0000000000000 --- a/substrate/frame/election-provider-multi-block/src/unsigned/weights.rs +++ /dev/null @@ -1,76 +0,0 @@ -// 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. - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] -#![allow(missing_docs)] - -use frame_support::{traits::Get, weights::Weight}; -use core::marker::PhantomData; - -pub trait WeightInfo { - fn submit_page_unsigned(v: u32, t: u32) -> Weight; -} - -/// Weight functions for `pallet_epm_unsigned`. -pub struct SubstrateWeight(PhantomData); -impl WeightInfo for SubstrateWeight { - /// Storage: `ElectionProviderMultiBlock::CurrentPhase` (r:1 w:0) - /// Proof: `ElectionProviderMultiBlock::CurrentPhase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ElectionVerifierPallet::QueuedSolutionScore` (r:1 w:0) - /// Proof: `ElectionVerifierPallet::QueuedSolutionScore` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ElectionVerifierPallet::MinimumScore` (r:1 w:0) - /// Proof: `ElectionVerifierPallet::MinimumScore` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ElectionProviderMultiBlock::PagedTargetSnapshot` (r:1 w:0) - /// Proof: `ElectionProviderMultiBlock::PagedTargetSnapshot` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `ElectionProviderMultiBlock::PagedVoterSnapshot` (r:1 w:0) - /// Proof: `ElectionProviderMultiBlock::PagedVoterSnapshot` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Staking::ValidatorCount` (r:1 w:0) - /// Proof: `Staking::ValidatorCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `ElectionVerifierPallet::QueuedValidVariant` (r:1 w:0) - /// Proof: `ElectionVerifierPallet::QueuedValidVariant` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ElectionVerifierPallet::QueuedSolutionY` (r:0 w:1) - /// Proof: `ElectionVerifierPallet::QueuedSolutionY` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `ElectionVerifierPallet::LastStoredPage` (r:0 w:1) - /// Proof: `ElectionVerifierPallet::LastStoredPage` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ElectionVerifierPallet::QueuedSolutionBackings` (r:0 w:1) - /// Proof: `ElectionVerifierPallet::QueuedSolutionBackings` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `v` is `[32, 1024]`. - /// The range of component `t` is `[512, 2048]`. - fn submit_page_unsigned(v: u32, t: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `11869 + t * (10 ±0) + v * (71 ±0)` - // Estimated: `15334 + t * (10 ±0) + v * (71 ±0)` - // Minimum execution time: 1_382_000_000 picoseconds. - Weight::from_parts(3_157_322_580, 0) - .saturating_add(Weight::from_parts(0, 15334)) - // Standard Error: 80_316 - .saturating_add(Weight::from_parts(4_146_169, 0).saturating_mul(v.into())) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().writes(3)) - .saturating_add(Weight::from_parts(0, 10).saturating_mul(t.into())) - .saturating_add(Weight::from_parts(0, 71).saturating_mul(v.into())) - } -} - - -impl WeightInfo for () { - fn submit_page_unsigned(_v: u32, _t: u32) -> Weight { - Default::default() - } -} diff --git a/substrate/frame/election-provider-multi-block/src/verifier/benchmarking.rs b/substrate/frame/election-provider-multi-block/src/verifier/benchmarking.rs deleted file mode 100644 index 8ac6d05250916..0000000000000 --- a/substrate/frame/election-provider-multi-block/src/verifier/benchmarking.rs +++ /dev/null @@ -1,315 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! # Benchmarking for the Elections Multiblock Verifier sub-pallet. - -use super::*; -use crate::{ - benchmarking::helpers, - signed::pallet::Submissions, - unsigned::miner::OffchainWorkerMiner, - verifier::{AsyncVerifier, Status, Verifier}, - BenchmarkingConfig, ConfigCore, ConfigSigned, ConfigUnsigned, ConfigVerifier, PalletCore, - PalletVerifier, -}; -use frame_support::assert_ok; -use frame_system::RawOrigin; - -use frame_benchmarking::v2::*; - -#[benchmarks( - where T: ConfigCore + ConfigSigned + ConfigUnsigned + ConfigVerifier, -)] -mod benchmarks { - use super::*; - use frame_support::traits::Hooks; - - #[benchmark] - fn on_initialize_ongoing( - v: Linear< - { ::BenchmarkingConfig::VOTERS_PER_PAGE[0] }, - { ::BenchmarkingConfig::VOTERS_PER_PAGE[1] }, - >, - t: Linear< - { ::BenchmarkingConfig::TARGETS_PER_PAGE[0] }, - { ::BenchmarkingConfig::TARGETS_PER_PAGE[1] }, - >, - ) -> Result<(), BenchmarkError> { - helpers::setup_data_provider::( - ::BenchmarkingConfig::VOTERS.max(v), - ::BenchmarkingConfig::TARGETS.max(t), - ); - - if let Err(err) = helpers::setup_snapshot::(v, t) { - log!(error, "error setting up snapshot: {:?}.", err); - return Err(BenchmarkError::Stop("snapshot error")); - } - - let valid_solution = true; - let submitter = helpers::mine_and_submit::(Some(PalletCore::::msp()), valid_solution) - .map_err(|err| { - log!(error, "error mining and storing paged solutions, {:?}", err); - BenchmarkError::Stop("mine and store error") - })?; - - // page is ready for async verification. - assert!(Submissions::::get_page( - &submitter, - PalletCore::::current_round(), - PalletCore::::msp() - ) - .is_some()); - - // no backings for pages yet in storage. - assert!(PalletVerifier::::pages_backed() == 0); - - // set verifier status to pick first submitted page to verify. - as AsyncVerifier>::set_status( - Status::Ongoing(crate::Pallet::::msp()), - ); - - #[block] - { - PalletVerifier::::on_initialize(0u32.into()); - } - - // backings from submitted and verified page is in storage now - assert!(PalletVerifier::::pages_backed() == 1); - - Ok(()) - } - - #[benchmark] - fn on_initialize_ongoing_failed( - v: Linear< - { ::BenchmarkingConfig::VOTERS_PER_PAGE[0] }, - { ::BenchmarkingConfig::VOTERS_PER_PAGE[1] }, - >, - t: Linear< - { ::BenchmarkingConfig::TARGETS_PER_PAGE[0] }, - { ::BenchmarkingConfig::TARGETS_PER_PAGE[1] }, - >, - ) -> Result<(), BenchmarkError> { - helpers::setup_data_provider::( - ::BenchmarkingConfig::VOTERS.max(v), - ::BenchmarkingConfig::TARGETS.max(t), - ); - - if let Err(err) = helpers::setup_snapshot::(v, t) { - log!(error, "error setting up snapshot: {:?}.", err); - return Err(BenchmarkError::Stop("snapshot error")); - } - - let valid_solution = false; - let submitter = helpers::mine_and_submit::(Some(PalletCore::::msp()), valid_solution) - .map_err(|err| { - log!(error, "error mining and storing paged solutions, {:?}", err); - BenchmarkError::Stop("mine and store error") - })?; - - // page is ready for async verification. - assert!(Submissions::::get_page( - &submitter, - PalletCore::::current_round(), - PalletCore::::msp() - ) - .is_some()); - - // no backings for pages in storage. - assert!(PalletVerifier::::pages_backed() == 0); - - // set verifier status to pick first submitted page to verify. - as AsyncVerifier>::set_status( - Status::Ongoing(crate::Pallet::::msp()), - ); - - #[block] - { - PalletVerifier::::on_initialize(0u32.into()); - } - - // no backings for pages in storage due to failure. - assert!(PalletVerifier::::pages_backed() == 0); - - Ok(()) - } - - #[benchmark] - fn on_initialize_ongoing_finalize( - v: Linear< - { ::BenchmarkingConfig::VOTERS_PER_PAGE[0] }, - { ::BenchmarkingConfig::VOTERS_PER_PAGE[1] }, - >, - t: Linear< - { ::BenchmarkingConfig::TARGETS_PER_PAGE[0] }, - { ::BenchmarkingConfig::TARGETS_PER_PAGE[1] }, - >, - ) -> Result<(), BenchmarkError> { - helpers::setup_data_provider::( - ::BenchmarkingConfig::VOTERS.max(v), - ::BenchmarkingConfig::TARGETS.max(t), - ); - - if let Err(err) = helpers::setup_snapshot::(v, t) { - log!(error, "error setting up snapshot: {:?}.", err); - return Err(BenchmarkError::Stop("snapshot error")); - } - - // submit all pages with a valid solution. - let valid_solution = true; - let submitter = helpers::mine_and_submit::(None, valid_solution).map_err(|err| { - log!(error, "error mining and storing paged solutions, {:?}", err); - BenchmarkError::Stop("mine and store error") - })?; - - // all pages are ready for async verification. - for page in 0..T::Pages::get() { - assert!(Submissions::::get_page(&submitter, PalletCore::::current_round(), page) - .is_some()); - } - - // no backings for pages in storage. - assert!(PalletVerifier::::pages_backed() == 0); - // no queued score yet. - assert!( as Verifier>::queued_score().is_none()); - - // process all paged solutions but lsp. - for page in (1..T::Pages::get()).rev() { - as AsyncVerifier>::set_status(Status::Ongoing(page)); - Pallet::::on_initialize(0u32.into()); - } - - assert!(PalletVerifier::::pages_backed() as u32 == T::Pages::get().saturating_sub(1)); - - // set verifier status to pick last submitted page to verify. - as AsyncVerifier>::set_status(Status::Ongoing(PalletCore::::lsp())); - - #[block] - { - PalletVerifier::::on_initialize(0u32.into()); - } - - // OK, so score is queued. - assert!( as Verifier>::queued_score().is_some()); - - Ok(()) - } - - #[benchmark] - fn on_initialize_ongoing_finalize_failed( - v: Linear< - { ::BenchmarkingConfig::VOTERS_PER_PAGE[0] }, - { ::BenchmarkingConfig::VOTERS_PER_PAGE[1] }, - >, - t: Linear< - { ::BenchmarkingConfig::TARGETS_PER_PAGE[0] }, - { ::BenchmarkingConfig::TARGETS_PER_PAGE[1] }, - >, - ) -> Result<(), BenchmarkError> { - helpers::setup_data_provider::( - ::BenchmarkingConfig::VOTERS.max(v), - ::BenchmarkingConfig::TARGETS.max(t), - ); - - if let Err(err) = helpers::setup_snapshot::(v, t) { - log!(error, "error setting up snapshot: {:?}.", err); - return Err(BenchmarkError::Stop("snapshot error")); - } - - #[block] - { - let _ = 1 + 2; - } - - Ok(()) - } - - #[benchmark] - fn finalize_async_verification( - v: Linear< - { ::BenchmarkingConfig::VOTERS_PER_PAGE[0] }, - { ::BenchmarkingConfig::VOTERS_PER_PAGE[1] }, - >, - t: Linear< - { ::BenchmarkingConfig::TARGETS_PER_PAGE[0] }, - { ::BenchmarkingConfig::TARGETS_PER_PAGE[1] }, - >, - ) -> Result<(), BenchmarkError> { - helpers::setup_data_provider::( - ::BenchmarkingConfig::VOTERS.max(v), - ::BenchmarkingConfig::TARGETS.max(t), - ); - - if let Err(err) = helpers::setup_snapshot::(v, t) { - log!(error, "error setting up snapshot: {:?}.", err); - return Err(BenchmarkError::Stop("snapshot error")); - } - - #[block] - { - let _ = 1 + 2; - } - - Ok(()) - } - - #[benchmark] - fn verify_sync_paged( - v: Linear< - { ::BenchmarkingConfig::VOTERS_PER_PAGE[0] }, - { ::BenchmarkingConfig::VOTERS_PER_PAGE[1] }, - >, - t: Linear< - { ::BenchmarkingConfig::TARGETS_PER_PAGE[0] }, - { ::BenchmarkingConfig::TARGETS_PER_PAGE[1] }, - >, - ) -> Result<(), BenchmarkError> { - helpers::setup_data_provider::( - ::BenchmarkingConfig::VOTERS.max(v), - ::BenchmarkingConfig::TARGETS.max(t), - ); - - if let Err(err) = helpers::setup_snapshot::(v, t) { - log!(error, "error setting up snapshot: {:?}.", err); - return Err(BenchmarkError::Stop("snapshot error")); - } - - let (_claimed_full_score, partial_score, paged_solution) = - OffchainWorkerMiner::::mine(PalletCore::::msp()).map_err(|err| { - log!(error, "mine error: {:?}", err); - BenchmarkError::Stop("miner error") - })?; - - #[block] - { - assert_ok!(PalletVerifier::::do_verify_sync( - paged_solution, - partial_score, - PalletCore::::msp() - )); - } - - Ok(()) - } - - impl_benchmark_test_suite!( - PalletVerifier, - crate::mock::ExtBuilder::default(), - crate::mock::Runtime, - exec_name = build_and_execute - ); -} diff --git a/substrate/frame/election-provider-multi-block/src/verifier/impls.rs b/substrate/frame/election-provider-multi-block/src/verifier/impls.rs deleted file mode 100644 index c118debfd9074..0000000000000 --- a/substrate/frame/election-provider-multi-block/src/verifier/impls.rs +++ /dev/null @@ -1,680 +0,0 @@ -// ohis 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 super::*; -use crate::{unsigned::miner, verifier::weights::WeightInfo, MinerSupportsOf, SolutionOf}; -use pallet::*; - -use frame_election_provider_support::PageIndex; -use frame_support::{ - ensure, - pallet_prelude::Weight, - traits::{Defensive, TryCollect}, - BoundedVec, -}; -use sp_runtime::{traits::Zero, Perbill}; -use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; - -#[frame_support::pallet] -pub(crate) mod pallet { - use super::*; - use frame_support::pallet_prelude::{ValueQuery, *}; - use frame_system::pallet_prelude::*; - - #[pallet::config] - #[pallet::disable_frame_system_supertrait_check] - pub trait Config: crate::Config { - /// The overarching event type. - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - - /// Origin that can control this pallet. This must be a *trusted origin* since the - /// actions taken by this origin are not checked (e.g. `set_emergency_solution`). - type ForceOrigin: EnsureOrigin; - - /// Minimum improvement to a solution that defines a new solution as "better". - type SolutionImprovementThreshold: Get; - - /// Something that can provide the solution data to the verifier. - type SolutionDataProvider: crate::verifier::SolutionDataProvider< - Solution = SolutionOf, - >; - - /// The weight information of this pallet. - type WeightInfo: WeightInfo; - } - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - /// A verificaction failed at the given page. - VerificationFailed(PageIndex, FeasibilityError), - /// The final verifications of the `finalize_verification` failed. If this error happened, - /// all the single pages passed the feasibility checks. - FinalVerificationFailed(FeasibilityError), - /// The given page has been correctly verified, with the number of backers that are part of - /// the page. - Verified(PageIndex, u32), - /// A new solution with the given score has replaced the previous best solution, if any. - Queued(ElectionScore, Option), - /// The solution data was not available for a specific page. - SolutionDataUnavailable(PageIndex), - } - - /// A wrapper type of the storage items related to the queued solution. - /// - /// It manages the following storage types: - /// - /// - [`QueuedSolutionX`]: variant X of the queued solution. - /// - [`QueuedSolutionY`]: variant Y of the queued solution. - /// - [`QueuedValidVariant`]: pointer to which variant is the currently valid. - /// - [`QueuedSolutionScore`]: the soltution score of the current valid variant. - /// - [`QueuedSolutionBackings`]. - /// - /// Note that, as an async verification is progressing, the paged solution is kept in the - /// invalid variant storage. A solution is considered valid only when all the single page and - /// full solution checks have been perform based on the stored [`QueuedSolutionBackings`]. for - /// the corresponding in-verification solution. After the solution verification is successful, - /// the election score can be calculated and stored. - /// - /// ### Invariants - /// - /// - [`QueuedSolutionScore`] must be always the correct queued score of a variant corresponding - /// to the [`QueuedValidVariant`]. - /// - [`QueuedSolution`] must always be [`Config::SolutionImprovementThreshold`] better than - /// [`MininumScore`]. - /// - The [`QueuedSolutionBackings`] are always the backings corresponding to the *invalid* - /// variant. - pub struct QueuedSolution(sp_std::marker::PhantomData); - - impl QueuedSolution { - fn mutate_checked(mutate: impl FnOnce() -> R) -> R { - let r = mutate(); - #[cfg(debug_assertions)] - assert!(Self::sanity_check().is_ok()); - r - } - - /// Clear all relevant data of an invalid solution. - /// - /// This should be called when a solution being verified is deemed infeasible. - pub(crate) fn clear_invalid_and_backings() { - let _ = match Self::invalid() { - SolutionPointer::X => QueuedSolutionX::::clear(u32::MAX, None), - SolutionPointer::Y => QueuedSolutionY::::clear(u32::MAX, None), - }; - let _ = QueuedSolutionBackings::::clear(u32::MAX, None); - } - - /// Clear all relevant storage items. - pub(crate) fn kill() { - Self::mutate_checked(|| { - let _ = QueuedSolutionX::::clear(u32::MAX, None); - let _ = QueuedSolutionY::::clear(u32::MAX, None); - QueuedValidVariant::::kill(); - let _ = QueuedSolutionBackings::::clear(u32::MAX, None); - QueuedSolutionScore::::kill(); - }) - } - - /// Finalize a correct solution. - /// - /// It should be called at the end of the verification process of a valid solution to update - /// the queued solution score and flip the invalid variant. - pub(crate) fn finalize_solution(score: ElectionScore) { - sublog!( - info, - "verifier", - "finalizing verification of a correct solution, replacing old score {:?} with {:?}", - QueuedSolutionScore::::get(), - score - ); - - Self::mutate_checked(|| { - QueuedValidVariant::::mutate(|v| *v = v.other()); - QueuedSolutionScore::::put(score); - }) - } - - /// Write a single page of a valid solution into the `invalid` variant of the storage. - /// - /// It should be called only once the page has been verified to be 100% correct. - pub(crate) fn set_page(page: PageIndex, supports: MinerSupportsOf) { - Self::mutate_checked(|| { - let backings: BoundedVec<_, _> = supports - .iter() - .map(|(x, s)| (x.clone(), PartialBackings {total: s.total, backers: s.voters.len() as u32})) - .try_collect() - .expect("`SupportsOf` is bounded by as Verifier>::MaxWinnersPerPage which is ensured by an integrity test; qed."); - - QueuedSolutionBackings::::insert(page, backings); - - // update the last stored page. - RemainingUnsignedPages::::mutate(|remaining| { - remaining.retain(|p| *p != page); - sublog!(debug, "verifier", "updated remaining pages, current: {:?}", remaining); - }); - - // store the new page into the invalid variant storage type. - match Self::invalid() { - SolutionPointer::X => QueuedSolutionX::::insert(page, supports), - SolutionPointer::Y => QueuedSolutionY::::insert(page, supports), - } - }) - } - - /// Computes the score and the winner count of a stored variant solution. - pub(crate) fn compute_current_score() -> Result<(ElectionScore, u32), FeasibilityError> { - // ensures that all the pages are complete; - if QueuedSolutionBackings::::iter_keys().count() != T::Pages::get() as usize { - return Err(FeasibilityError::Incomplete) - } - - let mut supports: BTreeMap = Default::default(); - for (who, PartialBackings { backers, total }) in - QueuedSolutionBackings::::iter().map(|(_, backings)| backings).flatten() - { - let entry = supports.entry(who).or_default(); - entry.total = entry.total.saturating_add(total); - entry.backers = entry.backers.saturating_add(backers); - - if entry.backers > T::MaxBackersPerWinner::get() { - return Err(FeasibilityError::TooManyBackings) - } - } - - let winners_count = supports.len() as u32; - let score = sp_npos_elections::evaluate_support( - supports.into_iter().map(|(_, backings)| backings), - ); - - Ok((score, winners_count)) - } - - /// Returns the current queued score, if any. - pub(crate) fn queued_score() -> Option { - QueuedSolutionScore::::get() - } - - /// Returns the current *valid* paged queued solution, if any. - pub(crate) fn get_queued_solution( - page: PageIndex, - ) -> Option> { - match Self::valid() { - SolutionPointer::X => QueuedSolutionX::::get(page), - SolutionPointer::Y => QueuedSolutionY::::get(page), - } - } - - /// Returns the pointer for the valid solution storage. - pub(crate) fn valid() -> SolutionPointer { - QueuedValidVariant::::get() - } - - /// Returns the pointer for the invalid solution storage. - pub(crate) fn invalid() -> SolutionPointer { - Self::valid().other() - } - - #[allow(dead_code)] - pub(crate) fn sanity_check() -> Result<(), &'static str> { - // TODO(gpestana) - Ok(()) - } - } - - /// Supports of the solution of the variant X. - /// - /// A potential valid or invalid solution may be stored in this variant during the round. - #[pallet::storage] - pub type QueuedSolutionX = - StorageMap<_, Twox64Concat, PageIndex, MinerSupportsOf>; - - /// Supports of the solution of the variant Y. - /// - /// A potential valid or invalid solution may be stored in this variant during the round. - #[pallet::storage] - pub type QueuedSolutionY = - StorageMap<_, Twox64Concat, PageIndex, MinerSupportsOf>; - - /// The `(amount, count)` of backings, keyed by page. - /// - /// This is stored to facilitate the `MaxBackersPerWinner` check at the end of an async - /// verification. Once the solution is valid (i.e. verified), the solution backings are not - /// useful anymore and can be cleared. - #[pallet::storage] - pub(crate) type QueuedSolutionBackings = StorageMap< - _, - Twox64Concat, - PageIndex, - BoundedVec<(T::AccountId, PartialBackings), T::MaxWinnersPerPage>, - >; - - /// The score of the current valid solution. - #[pallet::storage] - type QueuedSolutionScore = StorageValue<_, ElectionScore>; - - /// Pointer for the storage variant (X or Y) that stores the current valid variant. - #[pallet::storage] - type QueuedValidVariant = StorageValue<_, SolutionPointer, ValueQuery>; - - /// The minimum score that each solution must have to be considered feasible. - #[pallet::storage] - pub(crate) type MinimumScore = StorageValue<_, ElectionScore>; - - /// Current status of the verification process. - #[pallet::storage] - pub(crate) type VerificationStatus = StorageValue<_, Status, ValueQuery>; - - // For unsigned page solutions only. - #[pallet::storage] - pub(crate) type RemainingUnsignedPages = - StorageValue<_, BoundedVec, ValueQuery>; - - #[pallet::pallet] - pub struct Pallet(PhantomData); - - #[pallet::hooks] - impl Hooks> for Pallet { - fn on_initialize(n: BlockNumberFor) -> Weight { - Self::do_on_initialize(n) - } - - fn integrity_test() { - // TODO(gpestana): add more integrity tests related to queued solution et al. - assert_eq!(T::MaxWinnersPerPage::get(), ::MaxWinnersPerPage::get()); - assert_eq!( - T::MaxBackersPerWinner::get(), - ::MaxBackersPerWinner::get() - ); - } - - #[cfg(feature = "try-runtime")] - fn try_state(_n: BlockNumberFor) -> Result<(), sp_runtime::TryRuntimeError> { - Self::do_try_state() - } - } -} - -impl Verifier for Pallet { - type AccountId = T::AccountId; - type Solution = SolutionOf; - type MaxWinnersPerPage = T::MaxWinnersPerPage; - type MaxBackersPerWinner = T::MaxBackersPerWinner; - - fn set_minimum_score(score: ElectionScore) { - MinimumScore::::put(score); - } - - fn queued_score() -> Option { - QueuedSolution::::queued_score() - } - - fn ensure_score_quality(claimed_score: ElectionScore) -> bool { - Self::ensure_score_quality(claimed_score).is_ok() - } - - fn get_queued_solution(page_index: PageIndex) -> Option> { - QueuedSolution::::get_queued_solution(page_index) - } - - fn next_missing_solution_page() -> Option { - let next_missing = RemainingUnsignedPages::::get().last().copied(); - sublog!(debug, "verifier", "next missing page: {:?}", next_missing); - - next_missing - } - - fn kill() { - QueuedSolution::::kill(); - >::put(Status::Nothing); - } - - fn verify_synchronous( - partial_solution: Self::Solution, - partial_score: ElectionScore, - page: PageIndex, - ) -> Result, FeasibilityError> { - let maybe_current_score = Self::queued_score(); - match Self::do_verify_sync(partial_solution, partial_score, page) { - Ok(supports) => { - sublog!( - trace, - "verifier", - "queued sync solution with score {:?} (page {:?})", - partial_score, - page - ); - Self::deposit_event(Event::::Verified(page, supports.len() as u32)); - Self::deposit_event(Event::::Queued(partial_score, maybe_current_score)); - Ok(supports) - }, - Err(err) => { - sublog!( - trace, - "verifier", - "sync verification failed with {:?} (page: {:?})", - err, - page - ); - Self::deposit_event(Event::::VerificationFailed(page, err.clone())); - Err(err) - }, - } - } - - fn feasibility_check( - solution: Self::Solution, - page: PageIndex, - ) -> Result, FeasibilityError> { - let targets = - crate::Snapshot::::targets().ok_or(FeasibilityError::SnapshotUnavailable)?; - - // prepare range to fetch all pages of the target and voter snapshot. - let paged_range = 0..crate::Pallet::::msp() + 1; - - // fetch all pages of the voter snapshot and collect them in a bounded vec. - let all_voter_pages: BoundedVec<_, T::Pages> = paged_range - .map(|page| { - crate::Snapshot::::voters(page).ok_or(FeasibilityError::SnapshotUnavailable) - }) - .collect::, _>>()? - .try_into() - .expect("range was constructed from the bounded vec bounds; qed."); - - let desired_targets = - crate::Snapshot::::desired_targets().ok_or(FeasibilityError::SnapshotUnavailable)?; - - miner::Miner::::feasibility_check_partial( - &all_voter_pages, - &targets, - solution, - desired_targets, - page, - ) - } -} - -impl AsyncVerifier for Pallet { - type SolutionDataProvider = T::SolutionDataProvider; - - fn force_finalize_verification(claimed_score: ElectionScore) -> Result<(), FeasibilityError> { - Self::finalize_async_verification(claimed_score) - } - - fn status() -> Status { - VerificationStatus::::get() - } - - fn start() -> Result<(), &'static str> { - if let Status::Nothing = Self::status() { - let claimed_score = Self::SolutionDataProvider::get_score().unwrap_or_default(); - - if Self::ensure_score_quality(claimed_score).is_err() { - Self::deposit_event(Event::::VerificationFailed( - crate::Pallet::::msp(), - FeasibilityError::ScoreTooLow, - )); - // report to the solution data provider that the page verification failed. - T::SolutionDataProvider::report_result(VerificationResult::Rejected); - // despite the verification failed, this was a successful `start` operation. - Ok(()) - } else { - VerificationStatus::::put(Status::Ongoing(crate::Pallet::::msp())); - Ok(()) - } - } else { - sublog!(warn, "verifier", "tries to start election while ongoing, ignored."); - Err("verification ongoing") - } - } - - fn stop() { - sublog!(warn, "verifier", "stop signal received. clearing everything."); - QueuedSolution::::clear_invalid_and_backings(); - - // if a verification is ongoing, signal the solution rejection to the solution data - // provider and reset the current status. - VerificationStatus::::mutate(|status| { - if matches!(status, Status::Ongoing(_)) { - T::SolutionDataProvider::report_result(VerificationResult::Rejected); - }; - *status = Status::Nothing; - }); - } - - // Sets current verifications status. - #[cfg(any(test, feature = "runtime-benchmarks"))] - fn set_status(status: Status) { - VerificationStatus::::put(status); - } -} - -impl Pallet { - fn do_on_initialize(_now: crate::BlockNumberFor) -> Weight { - let max_backers_winner = T::MaxBackersPerWinner::get(); - let max_winners_page = T::MaxWinnersPerPage::get(); - - match crate::Pallet::::current_phase() { - // reset remaining unsigned pages after snapshot is created. - crate::Phase::Snapshot(page) if page == crate::Pallet::::lsp() => { - RemainingUnsignedPages::::mutate(|remaining| { - *remaining = BoundedVec::truncate_from( - (crate::Pallet::::lsp()..crate::Pallet::::msp() + 1) - .collect::>(), - ); - }); - - sublog!( - debug, - "verifier", - "reset remaining unsgined pages to {:?}", - RemainingUnsignedPages::::get() - ); - }, - _ => (), - } - - if let Status::Ongoing(current_page) = >::get() { - let maybe_page_solution = - ::get_paged_solution(current_page); - - if maybe_page_solution.is_none() { - sublog!( - error, - "verifier", - "T::SolutionDataProvider failed to deliver page {} at {:?}.", - current_page, - crate::Pallet::::current_phase(), - ); - // reset election data and notify the `T::SolutionDataProvider`. - QueuedSolution::::clear_invalid_and_backings(); - VerificationStatus::::put(Status::Nothing); - T::SolutionDataProvider::report_result(VerificationResult::DataUnavailable); - - Self::deposit_event(Event::::SolutionDataUnavailable(current_page)); - - return ::WeightInfo::on_initialize_ongoing_failed( - max_backers_winner, - max_winners_page, - ); - } - - let page_solution = maybe_page_solution.expect("page solution checked to exist; qed."); - let maybe_supports = Self::feasibility_check(page_solution, current_page); - - // TODO: can refator out some of these code blocks to clean up the code. - let weight_consumed = match maybe_supports { - Ok(supports) => { - Self::deposit_event(Event::::Verified(current_page, supports.len() as u32)); - QueuedSolution::::set_page(current_page, supports); - - if current_page > crate::Pallet::::lsp() { - // election didn't finish, tick forward. - VerificationStatus::::put(Status::Ongoing( - current_page.saturating_sub(1), - )); - ::WeightInfo::on_initialize_ongoing( - max_backers_winner, - max_winners_page, - ) - } else { - // last page, finalize everything. At this point, the solution data - // provider should have a score ready for us. Otherwise, a default score - // will reset the whole election which is the desired behaviour. - let claimed_score = - T::SolutionDataProvider::get_score().defensive_unwrap_or_default(); - - // reset the election status. - VerificationStatus::::put(Status::Nothing); - - match Self::finalize_async_verification(claimed_score) { - Ok(_) => { - T::SolutionDataProvider::report_result(VerificationResult::Queued); - ::WeightInfo::on_initialize_ongoing_finalize( - max_backers_winner, - max_winners_page, - ) - }, - Err(_) => { - T::SolutionDataProvider::report_result( - VerificationResult::Rejected, - ); - // kill the solution in case of error. - QueuedSolution::::clear_invalid_and_backings(); - ::WeightInfo::on_initialize_ongoing_finalize_failed( - max_backers_winner, - max_winners_page, - ) - }, - } - } - }, - Err(err) => { - // the paged solution is invalid. - Self::deposit_event(Event::::VerificationFailed(current_page, err)); - VerificationStatus::::put(Status::Nothing); - QueuedSolution::::clear_invalid_and_backings(); - T::SolutionDataProvider::report_result(VerificationResult::Rejected); - - // TODO: may need to be a differnt another branch. - ::WeightInfo::on_initialize_ongoing_finalize_failed( - max_backers_winner, - max_winners_page, - ) - }, - }; - - weight_consumed - } else { - // nothing to do yet. - // TOOD(return weight reads=1) - Default::default() - } - } - - pub(crate) fn do_verify_sync( - partial_solution: SolutionOf, - partial_score: ElectionScore, - page: PageIndex, - ) -> Result, FeasibilityError> { - let _ = Self::ensure_score_quality(partial_score)?; - let supports = Self::feasibility_check(partial_solution.clone(), page)?; - - // TODO: implement fn evaluate on `BondedSupports`; remove extra clone. - let real_score = sp_npos_elections::evaluate_support( - supports.clone().into_iter().map(|(_, backings)| backings), - ); - ensure!(real_score == partial_score, FeasibilityError::InvalidScore); - - // queue valid solution of single page. - QueuedSolution::::set_page(page, supports.clone()); - - Ok(supports) - } - - pub(crate) fn finalize_async_verification( - claimed_score: ElectionScore, - ) -> Result<(), FeasibilityError> { - let outcome = QueuedSolution::::compute_current_score() - .and_then(|(final_score, winner_count)| { - let desired_targets = crate::Snapshot::::desired_targets().unwrap_or_default(); - - match (final_score == claimed_score, winner_count <= desired_targets) { - (true, true) => { - QueuedSolution::::finalize_solution(final_score); - Self::deposit_event(Event::::Queued( - final_score, - QueuedSolution::::queued_score(), - )); - - Ok(()) - }, - (false, true) => Err(FeasibilityError::InvalidScore), - (true, false) => Err(FeasibilityError::WrongWinnerCount), - (false, false) => Err(FeasibilityError::InvalidScore), - } - }) - .map_err(|err| { - sublog!(warn, "verifier", "finalizing the solution was invalid due to {:?}", err); - Self::deposit_event(Event::::VerificationFailed(Zero::zero(), err.clone())); - err - }); - - sublog!(debug, "verifier", "finalize verification outcome: {:?}", outcome); - outcome - } - - /// Checks if `score` improves the current queued score by `T::SolutionImprovementThreshold` and - /// that it is higher than `MinimumScore`. - pub fn ensure_score_quality(score: ElectionScore) -> Result<(), FeasibilityError> { - let is_improvement = ::queued_score().map_or(true, |best_score| { - score.strict_threshold_better(best_score, T::SolutionImprovementThreshold::get()) - }); - ensure!(is_improvement, FeasibilityError::ScoreTooLow); - - let is_greater_than_min_trusted = MinimumScore::::get() - .map_or(true, |min_score| score.strict_threshold_better(min_score, Perbill::zero())); - - ensure!(is_greater_than_min_trusted, FeasibilityError::ScoreTooLow); - Ok(()) - } - - /// Returns the number backings/pages verified and stored. - #[cfg(any(test, feature = "runtime-benchmarks"))] - #[allow(dead_code)] - pub(crate) fn pages_backed() -> usize { - QueuedSolutionBackings::::iter_keys().count() - } -} - -#[cfg(feature = "try-runtime")] -impl Pallet { - pub(crate) fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> { - Self::check_variants() - } - - /// Invariants: - /// - /// 1. The valid and invalid solution pointers are always different. - fn check_variants() -> Result<(), sp_runtime::TryRuntimeError> { - ensure!( - QueuedSolution::::valid() != QueuedSolution::::invalid(), - "valid and invalid solution pointers are the same" - ); - Ok(()) - } -} diff --git a/substrate/frame/election-provider-multi-block/src/verifier/mod.rs b/substrate/frame/election-provider-multi-block/src/verifier/mod.rs deleted file mode 100644 index 47f73bbed861b..0000000000000 --- a/substrate/frame/election-provider-multi-block/src/verifier/mod.rs +++ /dev/null @@ -1,291 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! # Verifier sub-pallet -//! -//! This pallet implements the NPoS solution verification logic. It supports both synchronous and -//! asynchronous verification of paged solutions. Moreover, it manages and ultimately stores -//! the best correct solution in a round, which can be requested by the election provider at the -//! time of the election. -//! -//! The paged solution data to be verified async is retrieved through the -//! [`Config::SolutionDataProvider`] implementor which most likely is the signed pallet. -//! -//! ## Feasibility check -//! -//! The goal of the feasibility of a solution is to ensure that a provided -//! [`crate::Config::Solution`] is correct based on the voter and target snapshots of a given round -//! kept by the parent pallet. The correctness of a solution is defined as: -//! -//! - The edges of a solution (voter -> targets) match the expected by the current snapshot. This -//! check can be performed at the page level. -//! - The total number of winners in the solution is sufficient. This check can only be performed -//! when the full paged solution is available;; -//! - The election score is higher than the expected minimum score. This check can only be performed -//! when the full paged solution is available;; -//! - All of the bounds of the election are respected, namely: -//! * [`Verifier::MaxBackersPerWinner`] - which set the total max of voters are backing a target, -//! per election. This check can only be performed when the full paged solution is available; -//! * [`Verifier::MaxWinnersPerPage`] - which ensure that a paged solution has a bound on the -//! number of targets. This check can be performed at the page level. -//! -//! Some checks can be performed at the page level (e.g. correct edges check) while others can only -//! be performed when the full solution is available. -//! -//! ## Sync and Async verification modes -//! -//! 1. Single-page, synchronous verification. This mode is used when a single page needs to be -//! verified on the fly, e.g. unsigned submission. -//! 2. Multi-page, asynchronous verification. This mode is used in the context of the multi-paged -//! signed solutions. -//! -//! The [`crate::verifier::Verifier`] and [`crate::verifier::AsyncVerifier`] traits define the -//! interface of each of the verification modes and this pallet implements both traits. -//! -//! ## Queued solution -//! -//! Once a solution has been succefully verified, it is stored in a queue. This pallet implements -//! the [`SolutionDataProvider`] trait which allows the parent pallet to request a correct -//! solution for the current round. - -mod impls; -pub mod weights; - -#[cfg(feature = "runtime-benchmarks")] -pub mod benchmarking; - -#[cfg(test)] -mod tests; - -use crate::{PageIndex, SupportsOf}; -use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::traits::Get; -use sp_npos_elections::{ElectionScore, ExtendedBalance}; -use sp_runtime::RuntimeDebug; - -// public re-exports. -pub use impls::pallet::{ - Call, Config, Event, Pallet, __substrate_call_check, __substrate_event_check, tt_default_parts, - tt_default_parts_v2, tt_error_token, -}; - -/// Errors related to the solution feasibility checks. -#[derive(Debug, Eq, PartialEq, codec::Encode, codec::Decode, scale_info::TypeInfo, Clone)] -pub enum FeasibilityError { - /// Election score is too low to be accepted. - ScoreTooLow, - /// Ongoing verification was not completed. - Incomplete, - /// Solution exceeds the number of backers per winner for at least one winner. - TooManyBackings, - /// Solution exceeds the number of winners. - WrongWinnerCount, - /// Snapshot is not available. - SnapshotUnavailable, - /// A voter is invalid. - InvalidVoter, - /// A vote is invalid. - InvalidVote, - /// Solution with an invalid score. - InvalidScore, - /// Internal election error. - #[codec(skip)] - NposElection(sp_npos_elections::Error), -} - -impl From for FeasibilityError { - fn from(err: sp_npos_elections::Error) -> Self { - FeasibilityError::NposElection(err) - } -} - -/// The status of this pallet. -/// -/// This pallet is either processing an async verification or doing nothing. A single page -/// verification can only be done while the pallet has status [`Status::Nothing`]. -#[derive(Encode, Decode, scale_info::TypeInfo, Clone, Copy, MaxEncodedLen, RuntimeDebug)] -pub enum Status { - /// A paged solution is ongoing and the next page to be verified is indicated in the inner - /// value. - Ongoing(PageIndex), - /// Nothing is happening. - Nothing, -} - -impl Default for Status { - fn default() -> Self { - Status::Nothing - } -} - -/// Pointer to the current valid solution of `QueuedSolution`. -#[derive(Encode, Decode, scale_info::TypeInfo, Clone, Copy, MaxEncodedLen, Debug, PartialEq)] -pub enum SolutionPointer { - /// Solution pointer variant `X`. - X, - /// Solution pointer variant `Y`. - Y, -} - -impl Default for SolutionPointer { - fn default() -> Self { - SolutionPointer::X - } -} - -impl SolutionPointer { - /// Returns the other variant of the current solution pointer in storage. - pub fn other(&self) -> SolutionPointer { - match *self { - SolutionPointer::X => SolutionPointer::Y, - SolutionPointer::Y => SolutionPointer::X, - } - } -} - -/// A type that represents a *partial* backing of a winner. It does not contain the -/// supports normally associated with a list of backings. -#[derive(Debug, Default, Encode, Decode, MaxEncodedLen, scale_info::TypeInfo)] -pub struct PartialBackings { - /// Total backing of a particular winner. - total: ExtendedBalance, - /// Number of backers. - backers: u32, -} - -impl sp_npos_elections::Backings for PartialBackings { - /// Returns the total backings of the winner. - fn total(&self) -> ExtendedBalance { - self.total - } -} - -/// The interface of something that can verify solutions for election in a multi-block context. -pub trait Verifier { - /// The account ID type. - type AccountId; - - /// The solution type; - type Solution; - - /// Maximum number of winners that a page supports. - /// - /// Note: This must always be greater or equal to `T::DataProvider::desired_targets()`. - type MaxWinnersPerPage: Get; - - /// Maximum number of backers that each winner can have. - type MaxBackersPerWinner: Get; - - /// Sets the minimum score that an election must have from now on. - fn set_minimum_score(score: ElectionScore); - - /// Fetches the current queued election score, if any. - /// - /// Returns `None` if not score is queued. - fn queued_score() -> Option; - - /// Check if a claimed score improves the current queued score or if it is higher than a - /// potential minimum score. - fn ensure_score_quality(claimed_score: ElectionScore) -> bool; - - /// Returns the next missing solution page. - fn next_missing_solution_page() -> Option; - - /// Clears all the storage items related to the verifier pallet. - fn kill(); - - /// Get a single page of the best verified solutions, if any. - fn get_queued_solution(page_index: PageIndex) -> Option>; - - /// Perform the feasibility check on a given single-page solution. - /// - /// This will perform: - /// 1. feasibility-check - /// 2. claimed score is correct and it is an improvements - /// 3. check if bounds are correct - /// 4. store the solution if all checks pass - fn verify_synchronous( - partial_solution: Self::Solution, - claimed_score: ElectionScore, - page: PageIndex, - ) -> Result, FeasibilityError>; - - /// Just perform a single-page feasibility-check, based on the standards of this pallet. - /// - /// No score check is part of this. - fn feasibility_check( - partial_solution: Self::Solution, - page: PageIndex, - ) -> Result, FeasibilityError>; -} - -/// Something that can verify a solution asynchronously. -pub trait AsyncVerifier: Verifier { - /// The data provider that can provide the candidate solution to verify. The result of the - /// verification is returned back to this entity. - type SolutionDataProvider: SolutionDataProvider; - - /// Forces finalizing the async verification. - fn force_finalize_verification(claimed_score: ElectionScore) -> Result<(), FeasibilityError>; - - /// Returns the status of the current verification. - fn status() -> Status; - - /// Start a verification process. - fn start() -> Result<(), &'static str>; // new error type? - - /// Stop the verification. - /// - /// An implementation must ensure that all related state and storage items are cleaned. - fn stop(); - - /// Sets current status. Only used for benchmarks and tests. - #[cfg(any(test, feature = "runtime-benchmarks"))] - fn set_status(status: Status); -} - -/// Encapsulates the result of the verification of a candidate solution. -#[derive(Clone, Copy, RuntimeDebug)] -#[cfg_attr(test, derive(PartialEq, Eq))] -pub enum VerificationResult { - /// Solution is valid and is queued. - Queued, - /// Solution is rejected, for whichever of the multiple reasons that it could be. - Rejected, - /// The data needed (solution pages or the score) was unavailable. This should rarely happen. - DataUnavailable, -} - -/// Something that provides paged solution data for the verifier. -/// -/// This can be implemented by [`crate::signed::Pallet`] where signed solutions are queued and -/// sorted based on the solution's score. -pub trait SolutionDataProvider { - // The solution type. - type Solution; - - /// Returns the `page`th page of the current best solution that the data provider has in store, - /// if it exists. Otherwise it returns `None`. - fn get_paged_solution(page: PageIndex) -> Option; - - /// Get the claimed score of the current best solution. - fn get_score() -> Option; - - /// Hook to report back the results of the verification of the current candidate solution that - /// is being exposed via [`Self::get_paged_solution`] and [`Self::get_score`]. - fn report_result(result: VerificationResult); -} diff --git a/substrate/frame/election-provider-multi-block/src/verifier/tests.rs b/substrate/frame/election-provider-multi-block/src/verifier/tests.rs deleted file mode 100644 index f0e3663883271..0000000000000 --- a/substrate/frame/election-provider-multi-block/src/verifier/tests.rs +++ /dev/null @@ -1,162 +0,0 @@ -// 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::{ - mock::*, - verifier::{impls::pallet::*, *}, - Phase, -}; -use frame_support::{assert_err, assert_noop, assert_ok}; -use sp_npos_elections::ElectionScore; -use sp_runtime::Perbill; - -#[test] -fn ensure_score_quality_works() { - ExtBuilder::default() - .solution_improvements_threshold(Perbill::from_percent(10)) - .build_and_execute(|| { - assert_eq!(MinimumScore::::get(), Default::default()); - assert!( as Verifier>::queued_score().is_none()); - - // if minimum score is not set and there's no queued score, any score has quality. - assert_ok!(Pallet::::ensure_score_quality(ElectionScore { - minimal_stake: 1, - sum_stake: 1, - sum_stake_squared: 1 - })); - - // if minimum score is set, the score being evaluated must be higher than the minimum - // score. - MinimumScore::::set( - ElectionScore { minimal_stake: 10, sum_stake: 20, sum_stake_squared: 300 }.into(), - ); - - // score is not higher than minimum score. - assert_err!( - Pallet::::ensure_score_quality(ElectionScore { - minimal_stake: 1, - sum_stake: 1, - sum_stake_squared: 1, - }), - FeasibilityError::ScoreTooLow - ); - - // if score improves the current one by the minimum solution improvement, we're gold. - assert_ok!(Pallet::::ensure_score_quality(ElectionScore { - minimal_stake: 11, - sum_stake: 22, - sum_stake_squared: 300 - })); - }) -} - -mod solution { - use super::*; - - #[test] - fn variant_flipping_works() { - ExtBuilder::default().build_and_execute(|| { - assert!(QueuedSolution::::valid() != QueuedSolution::::invalid()); - - let valid_before = QueuedSolution::::valid(); - let invalid_before = valid_before.other(); - - let mock_score = ElectionScore { minimal_stake: 10, ..Default::default() }; - - // queue solution and flip variant. - QueuedSolution::::finalize_solution(mock_score); - - // solution has been queued - assert_eq!(QueuedSolution::::queued_score().unwrap(), mock_score); - // variant has flipped. - assert_eq!(QueuedSolution::::valid(), invalid_before); - assert_eq!(QueuedSolution::::invalid(), valid_before); - }) - } -} - -mod feasibility_check { - use super::*; - - #[test] - fn winner_indices_page_in_bounds() { - ExtBuilder::default().pages(1).desired_targets(2).build_and_execute(|| { - roll_to_phase(Phase::Signed); - let mut solution = mine_full(1).unwrap(); - assert_eq!(crate::Snapshot::::targets().unwrap().len(), 8); - - // swap all votes from 3 to 4 to invalidate index 4. - solution.solution_pages[0] - .votes1 - .iter_mut() - .filter(|(_, t)| *t == TargetIndex::from(3u16)) - .for_each(|(_, t)| *t += 1); - - assert_noop!( - VerifierPallet::feasibility_check(solution.solution_pages[0].clone(), 0), - FeasibilityError::InvalidVote, - ); - }) - } -} - -mod sync_verifier { - use super::*; - - #[test] - fn sync_verifier_simple_works() { - ExtBuilder::default().build_and_execute(|| {}) - } - - #[test] - fn next_missing_solution_works() { - ExtBuilder::default().build_and_execute(|| { - let supports: SupportsOf> = Default::default(); - let msp = crate::Pallet::::msp(); - assert!(msp == ::Pages::get() - 1 && msp == 2); - - // run to snapshot phase to reset `RemainingUnsignedPages`. - roll_to_phase(Phase::Snapshot(crate::Pallet::::lsp())); - - // msp page is the next missing. - assert_eq!(::next_missing_solution_page(), Some(msp)); - - // X is the current valid solution, let's work with it. - assert_eq!(QueuedSolution::::valid(), SolutionPointer::X); - - // set msp and check the next missing page again. - QueuedSolution::::set_page(msp, supports.clone()); - assert_eq!(::next_missing_solution_page(), Some(msp - 1)); - - QueuedSolution::::set_page(msp - 1, supports.clone()); - assert_eq!(::next_missing_solution_page(), Some(0)); - - // set last page, missing page after is None as solution is complete. - QueuedSolution::::set_page(0, supports.clone()); - assert_eq!(::next_missing_solution_page(), None); - }) - } -} - -mod async_verifier { - use super::*; - - #[test] - fn async_verifier_simple_works() { - ExtBuilder::default().build_and_execute(|| {}) - } -} diff --git a/substrate/frame/election-provider-multi-block/src/verifier/weights.rs b/substrate/frame/election-provider-multi-block/src/verifier/weights.rs deleted file mode 100644 index ba9968ec1407b..0000000000000 --- a/substrate/frame/election-provider-multi-block/src/verifier/weights.rs +++ /dev/null @@ -1,240 +0,0 @@ -// 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. - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] -#![allow(missing_docs)] - -use frame_support::{traits::Get, weights::Weight}; -use core::marker::PhantomData; - -pub trait WeightInfo { - fn on_initialize_ongoing(v: u32, t: u32) -> Weight; - fn on_initialize_ongoing_failed(v: u32, t: u32) -> Weight; - fn on_initialize_ongoing_finalize(v: u32, t: u32) -> Weight; - fn on_initialize_ongoing_finalize_failed(v: u32, t: u32) -> Weight; - fn finalize_async_verification(v: u32, t: u32, ) -> Weight; - fn verify_sync_paged(v: u32, t: u32, ) -> Weight; -} - -/// Weight functions for `pallet_epm_verifier`. -pub struct SubstrateWeight(PhantomData); -impl WeightInfo for SubstrateWeight { - /// Storage: `ElectionVerifierPallet::VerificationStatus` (r:1 w:1) - /// Proof: `ElectionVerifierPallet::VerificationStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ElectionProviderMultiBlock::Round` (r:1 w:0) - /// Proof: `ElectionProviderMultiBlock::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ElectionSignedPallet::SortedScores` (r:1 w:0) - /// Proof: `ElectionSignedPallet::SortedScores` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `ElectionSignedPallet::SubmissionStorage` (r:1 w:0) - /// Proof: `ElectionSignedPallet::SubmissionStorage` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `ElectionProviderMultiBlock::PagedTargetSnapshot` (r:1 w:0) - /// Proof: `ElectionProviderMultiBlock::PagedTargetSnapshot` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `ElectionProviderMultiBlock::PagedVoterSnapshot` (r:1 w:0) - /// Proof: `ElectionProviderMultiBlock::PagedVoterSnapshot` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Staking::ValidatorCount` (r:1 w:0) - /// Proof: `Staking::ValidatorCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `ElectionVerifierPallet::QueuedValidVariant` (r:1 w:0) - /// Proof: `ElectionVerifierPallet::QueuedValidVariant` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ElectionVerifierPallet::QueuedSolutionY` (r:0 w:1) - /// Proof: `ElectionVerifierPallet::QueuedSolutionY` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `ElectionVerifierPallet::LastStoredPage` (r:0 w:1) - /// Proof: `ElectionVerifierPallet::LastStoredPage` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ElectionVerifierPallet::QueuedSolutionBackings` (r:0 w:1) - /// Proof: `ElectionVerifierPallet::QueuedSolutionBackings` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `v` is `[32, 1024]`. - /// The range of component `t` is `[512, 2048]`. - fn on_initialize_ongoing(v: u32, t: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `12992 + t * (26 ±0) + v * (80 ±0)` - // Estimated: `15414 + t * (27 ±1) + v * (80 ±2)` - // Minimum execution time: 2_036_000_000 picoseconds. - Weight::from_parts(2_036_000_000, 0) - .saturating_add(Weight::from_parts(0, 15414)) - // Standard Error: 3_307_370 - .saturating_add(Weight::from_parts(20_614_626, 0).saturating_mul(v.into())) - // Standard Error: 1_618_727 - .saturating_add(Weight::from_parts(1_324_037, 0).saturating_mul(t.into())) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(4)) - .saturating_add(Weight::from_parts(0, 27).saturating_mul(t.into())) - .saturating_add(Weight::from_parts(0, 80).saturating_mul(v.into())) - } - /// Storage: `ElectionVerifierPallet::VerificationStatus` (r:1 w:1) - /// Proof: `ElectionVerifierPallet::VerificationStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ElectionProviderMultiBlock::Round` (r:1 w:0) - /// Proof: `ElectionProviderMultiBlock::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ElectionSignedPallet::SortedScores` (r:1 w:1) - /// Proof: `ElectionSignedPallet::SortedScores` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `ElectionSignedPallet::SubmissionStorage` (r:1 w:1) - /// Proof: `ElectionSignedPallet::SubmissionStorage` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `ElectionProviderMultiBlock::PagedTargetSnapshot` (r:1 w:0) - /// Proof: `ElectionProviderMultiBlock::PagedTargetSnapshot` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `ElectionProviderMultiBlock::PagedVoterSnapshot` (r:1 w:0) - /// Proof: `ElectionProviderMultiBlock::PagedVoterSnapshot` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `ElectionVerifierPallet::QueuedValidVariant` (r:1 w:0) - /// Proof: `ElectionVerifierPallet::QueuedValidVariant` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ElectionSignedPallet::SubmissionMetadataStorage` (r:1 w:1) - /// Proof: `ElectionSignedPallet::SubmissionMetadataStorage` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `ElectionProviderMultiBlock::CurrentPhase` (r:1 w:0) - /// Proof: `ElectionProviderMultiBlock::CurrentPhase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// The range of component `v` is `[32, 1024]`. - /// The range of component `t` is `[512, 2048]`. - fn on_initialize_ongoing_failed(v: u32, _t: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0 + t * (4 ±0) + v * (112 ±0)` - // Estimated: `7604 + v * (108 ±2)` - // Minimum execution time: 1_034_000_000 picoseconds. - Weight::from_parts(1_576_541_397, 0) - .saturating_add(Weight::from_parts(0, 7604)) - // Standard Error: 296_982 - .saturating_add(Weight::from_parts(3_076_310, 0).saturating_mul(v.into())) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(4)) - .saturating_add(Weight::from_parts(0, 108).saturating_mul(v.into())) - } - /// Storage: `ElectionVerifierPallet::VerificationStatus` (r:1 w:1) - /// Proof: `ElectionVerifierPallet::VerificationStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ElectionProviderMultiBlock::Round` (r:1 w:0) - /// Proof: `ElectionProviderMultiBlock::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ElectionSignedPallet::SortedScores` (r:1 w:0) - /// Proof: `ElectionSignedPallet::SortedScores` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `ElectionSignedPallet::SubmissionStorage` (r:1 w:0) - /// Proof: `ElectionSignedPallet::SubmissionStorage` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `ElectionProviderMultiBlock::PagedTargetSnapshot` (r:1 w:0) - /// Proof: `ElectionProviderMultiBlock::PagedTargetSnapshot` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `ElectionProviderMultiBlock::PagedVoterSnapshot` (r:1 w:0) - /// Proof: `ElectionProviderMultiBlock::PagedVoterSnapshot` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Staking::ValidatorCount` (r:1 w:0) - /// Proof: `Staking::ValidatorCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `ElectionVerifierPallet::QueuedValidVariant` (r:1 w:1) - /// Proof: `ElectionVerifierPallet::QueuedValidVariant` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ElectionVerifierPallet::QueuedSolutionBackings` (r:3 w:1) - /// Proof: `ElectionVerifierPallet::QueuedSolutionBackings` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `ElectionVerifierPallet::QueuedSolutionScore` (r:1 w:1) - /// Proof: `ElectionVerifierPallet::QueuedSolutionScore` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ElectionVerifierPallet::QueuedSolutionY` (r:0 w:1) - /// Proof: `ElectionVerifierPallet::QueuedSolutionY` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `ElectionVerifierPallet::LastStoredPage` (r:0 w:1) - /// Proof: `ElectionVerifierPallet::LastStoredPage` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// The range of component `v` is `[32, 1024]`. - /// The range of component `t` is `[512, 2048]`. - fn on_initialize_ongoing_finalize(v: u32, t: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0 + t * (41 ±0) + v * (125 ±0)` - // Estimated: `79043 + t * (10 ±8) + v * (85 ±17)` - // Minimum execution time: 1_724_000_000 picoseconds. - Weight::from_parts(1_466_010_752, 0) - .saturating_add(Weight::from_parts(0, 79043)) - // Standard Error: 199_409 - .saturating_add(Weight::from_parts(3_322_580, 0).saturating_mul(v.into())) - // Standard Error: 128_785 - .saturating_add(Weight::from_parts(128_906, 0).saturating_mul(t.into())) - .saturating_add(T::DbWeight::get().reads(12)) - .saturating_add(T::DbWeight::get().writes(6)) - .saturating_add(Weight::from_parts(0, 10).saturating_mul(t.into())) - .saturating_add(Weight::from_parts(0, 85).saturating_mul(v.into())) - } - /// The range of component `v` is `[32, 1024]`. - /// The range of component `t` is `[512, 2048]`. - fn on_initialize_ongoing_finalize_failed(_v: u32, _t: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 3_000_000 picoseconds. - Weight::from_parts(4_659_677, 0) - .saturating_add(Weight::from_parts(0, 0)) - } - /// The range of component `v` is `[32, 1024]`. - /// The range of component `t` is `[512, 2048]`. - fn finalize_async_verification(v: u32, t: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 3_000_000 picoseconds. - Weight::from_parts(3_354_301, 0) - .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 2_197 - .saturating_add(Weight::from_parts(907, 0).saturating_mul(v.into())) - // Standard Error: 1_419 - .saturating_add(Weight::from_parts(65, 0).saturating_mul(t.into())) - } - /// Storage: `ElectionVerifierPallet::QueuedSolutionScore` (r:1 w:0) - /// Proof: `ElectionVerifierPallet::QueuedSolutionScore` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ElectionVerifierPallet::MinimumScore` (r:1 w:0) - /// Proof: `ElectionVerifierPallet::MinimumScore` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ElectionProviderMultiBlock::PagedTargetSnapshot` (r:1 w:0) - /// Proof: `ElectionProviderMultiBlock::PagedTargetSnapshot` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `ElectionProviderMultiBlock::PagedVoterSnapshot` (r:1 w:0) - /// Proof: `ElectionProviderMultiBlock::PagedVoterSnapshot` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Staking::ValidatorCount` (r:1 w:0) - /// Proof: `Staking::ValidatorCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `ElectionVerifierPallet::QueuedValidVariant` (r:1 w:0) - /// Proof: `ElectionVerifierPallet::QueuedValidVariant` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ElectionVerifierPallet::QueuedSolutionY` (r:0 w:1) - /// Proof: `ElectionVerifierPallet::QueuedSolutionY` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `ElectionVerifierPallet::LastStoredPage` (r:0 w:1) - /// Proof: `ElectionVerifierPallet::LastStoredPage` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ElectionVerifierPallet::QueuedSolutionBackings` (r:0 w:1) - /// Proof: `ElectionVerifierPallet::QueuedSolutionBackings` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `v` is `[32, 1024]`. - /// The range of component `t` is `[512, 2048]`. - fn verify_sync_paged(v: u32, t: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `15968 + t * (24 ±0) + v * (73 ±0)` - // Estimated: `18127 + t * (25 ±2) + v * (72 ±3)` - // Minimum execution time: 1_403_000_000 picoseconds. - Weight::from_parts(1_403_000_000, 0) - .saturating_add(Weight::from_parts(0, 18127)) - // Standard Error: 3_979_877 - .saturating_add(Weight::from_parts(24_084_766, 0).saturating_mul(v.into())) - // Standard Error: 1_947_873 - .saturating_add(Weight::from_parts(1_727_080, 0).saturating_mul(t.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - .saturating_add(Weight::from_parts(0, 25).saturating_mul(t.into())) - .saturating_add(Weight::from_parts(0, 72).saturating_mul(v.into())) - } -} - -impl WeightInfo for () { - fn on_initialize_ongoing(_v: u32, _t: u32) -> Weight { - Default::default() - } - - fn on_initialize_ongoing_failed(_v: u32, _t: u32) -> Weight { - Default::default() - } - - fn on_initialize_ongoing_finalize(_v: u32, _t: u32) -> Weight { - Default::default() - } - - fn on_initialize_ongoing_finalize_failed(_v: u32, _t: u32) -> Weight { - Default::default() - } - - fn finalize_async_verification(_v: u32, _t: u32, ) -> Weight { - Default::default() - } - - fn verify_sync_paged(_v: u32, _t: u32, ) -> Weight { - Default::default() - } -} - diff --git a/substrate/frame/election-provider-multi-block/src/weights.rs b/substrate/frame/election-provider-multi-block/src/weights.rs deleted file mode 100644 index f11e82e578b3f..0000000000000 --- a/substrate/frame/election-provider-multi-block/src/weights.rs +++ /dev/null @@ -1,179 +0,0 @@ -// 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. - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] -#![allow(missing_docs)] - -use frame_support::{traits::Get, weights::Weight}; -use core::marker::PhantomData; - -pub trait WeightInfo { - fn create_voters_snapshot_paged(t: u32) -> Weight; - fn create_targets_snapshot_paged(v: u32) -> Weight; - fn on_initialize_start_signed() -> Weight; - fn on_initialize_do_nothing() -> Weight; - fn on_phase_transition() -> Weight; - fn on_initialize_start_export() -> Weight; -} - -/// Weight functions for `pallet_epm_core`. -pub struct SubstrateWeight(PhantomData); -impl WeightInfo for SubstrateWeight { - /// Storage: `Staking::CounterForValidators` (r:1 w:0) - /// Proof: `Staking::CounterForValidators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Staking::TargetSnapshotStatus` (r:1 w:1) - /// Proof: `Staking::TargetSnapshotStatus` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) - /// Storage: `Staking::Validators` (r:2049 w:0) - /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) - /// Storage: `ElectionProviderMultiBlock::PagedTargetSnapshot` (r:0 w:1) - /// Proof: `ElectionProviderMultiBlock::PagedTargetSnapshot` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `t` is `[512, 2048]`. - fn create_targets_snapshot_paged(t: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `1041 + t * (46 ±0)` - // Estimated: `3510 + t * (2520 ±0)` - // Minimum execution time: 47_198_000_000 picoseconds. - Weight::from_parts(3_209_333_333, 0) - .saturating_add(Weight::from_parts(0, 3510)) - // Standard Error: 1_207_323 - .saturating_add(Weight::from_parts(86_960_937, 0).saturating_mul(t.into())) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(t.into()))) - .saturating_add(T::DbWeight::get().writes(2)) - .saturating_add(Weight::from_parts(0, 2520).saturating_mul(t.into())) - } - /// Storage: `VoterList::CounterForListNodes` (r:1 w:0) - /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Staking::VoterSnapshotStatus` (r:1 w:1) - /// Proof: `Staking::VoterSnapshotStatus` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) - /// Storage: `VoterList::ListBags` (r:200 w:0) - /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) - /// Storage: `VoterList::ListNodes` (r:1025 w:0) - /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) - /// Storage: `Staking::Bonded` (r:1024 w:0) - /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) - /// Storage: `Staking::Ledger` (r:1024 w:0) - /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Staking::Nominators` (r:1024 w:0) - /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) - /// Storage: `Staking::Validators` (r:1000 w:0) - /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) - /// Storage: `Staking::MinimumActiveStake` (r:0 w:1) - /// Proof: `Staking::MinimumActiveStake` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) - /// Storage: `ElectionProviderMultiBlock::PagedVoterSnapshot` (r:0 w:1) - /// Proof: `ElectionProviderMultiBlock::PagedVoterSnapshot` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `v` is `[32, 1024]`. - fn create_voters_snapshot_paged(v: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `73175 + v * (946 ±0)` - // Estimated: `512390 + v * (3566 ±0)` - // Minimum execution time: 13_398_000_000 picoseconds. - Weight::from_parts(4_906_354_838, 0) - .saturating_add(Weight::from_parts(0, 512390)) - // Standard Error: 534_281 - .saturating_add(Weight::from_parts(260_582_661, 0).saturating_mul(v.into())) - .saturating_add(T::DbWeight::get().reads(208)) - .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) - .saturating_add(T::DbWeight::get().writes(3)) - .saturating_add(Weight::from_parts(0, 3566).saturating_mul(v.into())) - } - /// Storage: `ElectionProviderMultiBlock::CurrentPhase` (r:1 w:1) - /// Proof: `ElectionProviderMultiBlock::CurrentPhase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ElectionProviderMultiBlock::Round` (r:1 w:0) - /// Proof: `ElectionProviderMultiBlock::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `Staking::ElectionDataLock` (r:0 w:1) - /// Proof: `Staking::ElectionDataLock` (`max_values`: Some(1), `max_size`: Some(0), added: 495, mode: `MaxEncodedLen`) - fn on_initialize_start_signed() -> Weight { - // Proof Size summary in bytes: - // Measured: `76` - // Estimated: `1561` - // Minimum execution time: 66_000_000 picoseconds. - Weight::from_parts(66_000_000, 0) - .saturating_add(Weight::from_parts(0, 1561)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: `ElectionProviderMultiBlock::CurrentPhase` (r:1 w:1) - /// Proof: `ElectionProviderMultiBlock::CurrentPhase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ElectionProviderMultiBlock::Round` (r:1 w:0) - /// Proof: `ElectionProviderMultiBlock::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn on_phase_transition() -> Weight { - // Proof Size summary in bytes: - // Measured: `76` - // Estimated: `1561` - // Minimum execution time: 62_000_000 picoseconds. - Weight::from_parts(62_000_000, 0) - .saturating_add(Weight::from_parts(0, 1561)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - fn on_initialize_start_export() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 3_000_000 picoseconds. - Weight::from_parts(3_000_000, 0) - .saturating_add(Weight::from_parts(0, 0)) - } - /// Storage: `Staking::CurrentEra` (r:1 w:0) - /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Staking::CurrentPlannedSession` (r:1 w:0) - /// Proof: `Staking::CurrentPlannedSession` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Staking::ErasStartSessionIndex` (r:1 w:0) - /// Proof: `Staking::ErasStartSessionIndex` (`max_values`: None, `max_size`: Some(16), added: 2491, mode: `MaxEncodedLen`) - /// Storage: `Staking::ForceEra` (r:1 w:0) - /// Proof: `Staking::ForceEra` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) - /// Storage: `ElectionProviderMultiBlock::CurrentPhase` (r:1 w:0) - /// Proof: `ElectionProviderMultiBlock::CurrentPhase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn on_initialize_do_nothing() -> Weight { - // Proof Size summary in bytes: - // Measured: `502` - // Estimated: `3481` - // Minimum execution time: 111_000_000 picoseconds. - Weight::from_parts(111_000_000, 0) - .saturating_add(Weight::from_parts(0, 3481)) - .saturating_add(T::DbWeight::get().reads(5)) - } -} - -impl WeightInfo for () { - fn create_voters_snapshot_paged(_v: u32) -> Weight { - Default::default() - } - - fn create_targets_snapshot_paged(_t: u32) -> Weight { - Default::default() - } - - fn on_initialize_start_signed() -> Weight { - Default::default() - } - - fn on_initialize_do_nothing() -> Weight { - Default::default() - } - - fn on_phase_transition() -> Weight { - Default::default() - } - - fn on_initialize_start_export() -> Weight { - Default::default() - } -} From 103465354869dbb626b77da376c85b5947ef7eac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Thu, 24 Oct 2024 11:39:30 +0200 Subject: [PATCH 021/169] nit fix in bags list remote tests --- prdoc/pr_6034.prdoc | 2 ++ substrate/frame/bags-list/remote-tests/src/snapshot.rs | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/prdoc/pr_6034.prdoc b/prdoc/pr_6034.prdoc index ba3d14326509f..e440c79bd3258 100644 --- a/prdoc/pr_6034.prdoc +++ b/prdoc/pr_6034.prdoc @@ -21,3 +21,5 @@ crates: bump: major - name: sp-staking bump: major +- name: pallet-bags-list-remote-tests + bump: patch diff --git a/substrate/frame/bags-list/remote-tests/src/snapshot.rs b/substrate/frame/bags-list/remote-tests/src/snapshot.rs index cb23fc6f5817d..0d9740addb54a 100644 --- a/substrate/frame/bags-list/remote-tests/src/snapshot.rs +++ b/substrate/frame/bags-list/remote-tests/src/snapshot.rs @@ -22,7 +22,10 @@ use frame_election_provider_support::{ }; use frame_support::traits::PalletInfoAccess; use remote_externalities::{Builder, Mode, OnlineConfig}; -use sp_runtime::{traits::Block as BlockT, DeserializeOwned}; +use sp_runtime::{ + traits::{Block as BlockT, Zero}, + DeserializeOwned, +}; /// Execute create a snapshot from pallet-staking. pub async fn execute(voter_limit: Option, currency_unit: u64, ws_url: String) @@ -70,8 +73,9 @@ where Some(v) => DataProviderBounds { count: Some(CountBound(v as u32)), size: None }, }; + // single page voter snapshot, thus page index == 0. let voters = - as ElectionDataProvider>::electing_voters(bounds) + as ElectionDataProvider>::electing_voters(bounds, Zero::zero()) .unwrap(); let mut voters_nominator_only = voters From 724896096a42eabf165e84ad5496e42046da8b90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Thu, 24 Oct 2024 11:43:47 +0200 Subject: [PATCH 022/169] nit fix prdoc --- prdoc/pr_6034.prdoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prdoc/pr_6034.prdoc b/prdoc/pr_6034.prdoc index e440c79bd3258..420fd5ca36742 100644 --- a/prdoc/pr_6034.prdoc +++ b/prdoc/pr_6034.prdoc @@ -21,5 +21,5 @@ crates: bump: major - name: sp-staking bump: major -- name: pallet-bags-list-remote-tests - bump: patch + - name: pallet-bags-list-remote-tests + bump: minor From a92d44a9fd112ca5611df2b2eab0c0c193a4e36b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Fri, 25 Oct 2024 15:33:46 +0200 Subject: [PATCH 023/169] Addresses PR reviews (removes all paged types/configs from epm and nits) --- substrate/frame/delegated-staking/src/mock.rs | 1 - .../election-provider-multi-phase/src/lib.rs | 70 +++++++++---------- .../election-provider-multi-phase/src/mock.rs | 13 ++-- .../src/signed.rs | 4 +- .../src/unsigned.rs | 15 ++-- .../election-provider-support/src/lib.rs | 6 +- substrate/frame/fast-unstake/src/mock.rs | 1 - substrate/frame/staking/src/pallet/mod.rs | 2 +- 8 files changed, 49 insertions(+), 63 deletions(-) diff --git a/substrate/frame/delegated-staking/src/mock.rs b/substrate/frame/delegated-staking/src/mock.rs index 396c8bf053dbb..a7363698e0747 100644 --- a/substrate/frame/delegated-staking/src/mock.rs +++ b/substrate/frame/delegated-staking/src/mock.rs @@ -111,7 +111,6 @@ impl pallet_staking::Config for Runtime { type GenesisElectionProvider = Self::ElectionProvider; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type TargetList = pallet_staking::UseValidatorsMap; - type MaxValidatorSet = ConstU32<100>; type EventListeners = (Pools, DelegatedStaking); } diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index 0c1c7f048474f..049bcbabce6a7 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -191,10 +191,15 @@ //! //! ## Multi-page election support //! -//! Even though the [`frame_election_provider_support::ElectionDataProvider`] and -//! [`frame_election_provider_support::ElectionProvider`] traits support multi-paged election, this -//! pallet only supports a single page election flow. Thus, [`Config::Pages`] must be always set to -//! one, which is asserted by the [`frame_support::traits::Hooks::integrity_test`]. +//! The [`frame_election_provider_support::ElectionDataProvider`] and +//! [`frame_election_provider_support::ElectionProvider`] traits used by this pallet support a +//! multi page election. +//! +//! However, this pallet is meant to be used only in the context of a single-page election and data +//! provider and all the relevant trait implementation ad configurations reflect that assumption. +//! +//! If external callers request the election of a page index higher than 0, the election will fail +//! with [`ElectionError::MultiPageNotSupported`]. //! //! ## Future Plans //! @@ -246,7 +251,6 @@ use frame_election_provider_support::{ InstantElectionProvider, NposSolution, PageIndex, TryIntoBoundedSupports, }; use frame_support::{ - defensive_assert, dispatch::DispatchClass, ensure, traits::{Currency, Get, OnUnbalanced, ReservableCurrency}, @@ -442,18 +446,18 @@ impl Default for RawSolution { DefaultNoBound, scale_info::TypeInfo, )] -#[scale_info(skip_type_params(AccountId, MaxWinnersPerPage, MaxBackersPerWinner))] -pub struct ReadySolution +#[scale_info(skip_type_params(AccountId, MaxWinners, MaxBackersPerWinner))] +pub struct ReadySolution where AccountId: IdentifierT, - MaxWinnersPerPage: Get, + MaxWinners: Get, MaxBackersPerWinner: Get, { /// The final supports of the solution. /// /// This is target-major vector, storing each winners, total backing, and each individual /// backer. - pub supports: BoundedSupports, + pub supports: BoundedSupports, /// The score of the solution. /// /// This is needed to potentially challenge the solution. @@ -504,6 +508,9 @@ pub enum ElectionError { DataProvider(&'static str), /// An error nested in the fallback. Fallback(FallbackErrorOf), + /// An error occurred when requesting an election result. The caller expects a mulit-paged + /// election, which this pallet does not support. + MultiPageNotSupported, /// No solution has been queued. NothingQueued, } @@ -624,7 +631,7 @@ pub mod pallet { type MinerConfig: crate::unsigned::MinerConfig< AccountId = Self::AccountId, MaxVotesPerVoter = ::MaxVotesPerVoter, - MaxWinnersPerPage = Self::MaxWinnersPerPage, + MaxWinners = Self::MaxWinners, MaxBackersPerWinner = Self::MaxBackersPerWinner, >; @@ -662,28 +669,22 @@ pub mod pallet { #[pallet::constant] type SignedDepositWeight: Get>; - /// Maximum number of winners that a page supports. + /// Maximum number of winners that an electio supports. /// /// Note: This must always be greater or equal to `T::DataProvider::desired_targets()`. - type MaxWinnersPerPage: Get; + type MaxWinners: Get; - /// Maximum number of voters that can support a single target, across ALL the solution - /// pages. Thus, this can only be verified when processing the last solution page. + /// Maximum number of voters that can support a winner target in an election solution. /// /// This limit must be set so that the memory limits of the rest of the system are /// respected. type MaxBackersPerWinner: Get; - /// Number of pages. - type Pages: Get; - /// Something that calculates the signed deposit base based on the signed submissions queue /// size. type SignedDepositBase: Convert>; /// The maximum number of electing voters and electable targets to put in the snapshot. - /// At the moment, snapshots are only over a single block, but once multi-block elections - /// are introduced they will take place over multiple blocks. type ElectionBounds: Get; /// Handler for the slashed deposits. @@ -704,7 +705,7 @@ pub mod pallet { BlockNumber = BlockNumberFor, DataProvider = Self::DataProvider, MaxBackersPerWinner = Self::MaxBackersPerWinner, - MaxWinnersPerPage = Self::MaxWinnersPerPage, + MaxWinnersPerPage = Self::MaxWinners, >; /// Configuration of the governance-only fallback. @@ -715,7 +716,7 @@ pub mod pallet { AccountId = Self::AccountId, BlockNumber = BlockNumberFor, DataProvider = Self::DataProvider, - MaxWinnersPerPage = Self::MaxWinnersPerPage, + MaxWinnersPerPage = Self::MaxWinners, MaxBackersPerWinner = Self::MaxBackersPerWinner, >; @@ -753,7 +754,7 @@ pub mod pallet { #[pallet::constant_name(MinerMaxWinners)] fn max_winners() -> u32 { - ::MaxWinnersPerPage::get() + ::MaxWinners::get() } } @@ -895,9 +896,6 @@ pub mod pallet { // `SignedMaxSubmissions` is a red flag that the developer does not understand how to // configure this pallet. assert!(T::SignedMaxSubmissions::get() >= T::SignedMaxRefunds::get()); - - // This pallet only supports single-page elections. - assert!(T::Pages::get() == 1u32); } #[cfg(feature = "try-runtime")] @@ -1001,7 +999,7 @@ pub mod pallet { T::ForceOrigin::ensure_origin(origin)?; ensure!(CurrentPhase::::get().is_emergency(), Error::::CallNotAllowed); - // bound supports with T::MaxWinnersPerPage. + // bound supports with T::MaxWinners. let supports: BoundedSupports<_, _, _> = supports.try_into_bounded_supports().map_err(|_| Error::::TooManyWinners)?; @@ -1283,7 +1281,7 @@ pub mod pallet { /// Always sorted by score. #[pallet::storage] pub type QueuedSolution = - StorageValue<_, ReadySolution>; + StorageValue<_, ReadySolution>; /// Snapshot data of the round. /// @@ -1416,7 +1414,7 @@ impl Pallet { /// /// Always sorted by score. pub fn queued_solution( - ) -> Option> { + ) -> Option> { QueuedSolution::::get() } @@ -1602,10 +1600,8 @@ impl Pallet { pub fn feasibility_check( raw_solution: RawSolution>, compute: ElectionCompute, - ) -> Result< - ReadySolution, - FeasibilityError, - > { + ) -> Result, FeasibilityError> + { let desired_targets = DesiredTargets::::get().ok_or(FeasibilityError::SnapshotUnavailable)?; @@ -1686,7 +1682,7 @@ impl Pallet { /// record the weight of the given `supports`. fn weigh_supports( - supports: &BoundedSupports, + supports: &BoundedSupports, ) { let active_voters = supports .iter() @@ -1783,14 +1779,14 @@ impl ElectionProvider for Pallet { type AccountId = T::AccountId; type BlockNumber = BlockNumberFor; type Error = ElectionError; - type MaxWinnersPerPage = T::MaxWinnersPerPage; + type MaxWinnersPerPage = T::MaxWinners; type MaxBackersPerWinner = T::MaxBackersPerWinner; - type Pages = T::Pages; + type Pages = sp_core::ConstU32<1>; type DataProvider = T::DataProvider; fn elect(page: PageIndex) -> Result, Self::Error> { - // this pallet **MUST** only by used in the single-block mode. - defensive_assert!(page.is_zero()); + // Note: this pallet **MUST** only by used in the single-block mode. + ensure!(page.is_zero(), ElectionError::::WrongPageIndex); match Self::do_elect() { Ok(bounded_supports) => { diff --git a/substrate/frame/election-provider-multi-phase/src/mock.rs b/substrate/frame/election-provider-multi-phase/src/mock.rs index 948f19b418528..4f2c38681d421 100644 --- a/substrate/frame/election-provider-multi-phase/src/mock.rs +++ b/substrate/frame/election-provider-multi-phase/src/mock.rs @@ -295,7 +295,7 @@ parameter_types! { pub static MaxElectableTargets: TargetIndex = TargetIndex::max_value(); #[derive(Debug)] - pub static MaxWinnersPerPage: u32 = 200; + pub static MaxWinners: u32 = 200; #[derive(Debug)] pub static MaxBackersPerWinner: u32 = 200; pub static Pages: u32 = 1; @@ -312,7 +312,7 @@ impl onchain::Config for OnChainSeqPhragmen { type Solver = SequentialPhragmen, Balancing>; type DataProvider = StakingMock; type WeightInfo = (); - type MaxWinnersPerPage = MaxWinnersPerPage; + type MaxWinnersPerPage = MaxWinners; type MaxBackersPerWinner = MaxBackersPerWinner; type Bounds = OnChainElectionsBounds; } @@ -322,9 +322,9 @@ impl ElectionProvider for MockFallback { type AccountId = AccountId; type BlockNumber = BlockNumber; type Error = &'static str; - type MaxWinnersPerPage = MaxWinnersPerPage; + type MaxWinnersPerPage = MaxWinners; type MaxBackersPerWinner = MaxBackersPerWinner; - type Pages = Pages; + type Pages = ConstU32<1>; type DataProvider = StakingMock; fn elect(_remaining: PageIndex) -> Result, Self::Error> { @@ -375,7 +375,7 @@ impl MinerConfig for Runtime { type MaxLength = MinerMaxLength; type MaxWeight = MinerMaxWeight; type MaxVotesPerVoter = ::MaxVotesPerVoter; - type MaxWinnersPerPage = MaxWinnersPerPage; + type MaxWinners = MaxWinners; type MaxBackersPerWinner = MaxBackersPerWinner; type Solution = TestNposSolution; @@ -418,8 +418,7 @@ impl crate::Config for Runtime { type GovernanceFallback = frame_election_provider_support::onchain::OnChainExecution; type ForceOrigin = frame_system::EnsureRoot; - type Pages = Pages; - type MaxWinnersPerPage = MaxWinnersPerPage; + type MaxWinners = MaxWinners; type MaxBackersPerWinner = MaxBackersPerWinner; type MinerConfig = Self; type Solver = SequentialPhragmen, Balancing>; diff --git a/substrate/frame/election-provider-multi-phase/src/signed.rs b/substrate/frame/election-provider-multi-phase/src/signed.rs index ef1538b1e4d39..7fe8f5920b14e 100644 --- a/substrate/frame/election-provider-multi-phase/src/signed.rs +++ b/substrate/frame/election-provider-multi-phase/src/signed.rs @@ -490,7 +490,7 @@ impl Pallet { /// /// Infallible pub fn finalize_signed_phase_accept_solution( - ready_solution: ReadySolution, + ready_solution: ReadySolution, who: &T::AccountId, deposit: BalanceOf, call_fee: BalanceOf, @@ -665,7 +665,7 @@ mod tests { ExtBuilder::default().build_and_execute(|| { // given desired_targets bigger than MaxWinners DesiredTargets::set(4); - MaxWinnersPerPage::set(3); + MaxWinners::set(3); // snapshot not created because data provider returned an unexpected number of // desired_targets diff --git a/substrate/frame/election-provider-multi-phase/src/unsigned.rs b/substrate/frame/election-provider-multi-phase/src/unsigned.rs index 1389a5babd9d1..1113c0bc8a96b 100644 --- a/substrate/frame/election-provider-multi-phase/src/unsigned.rs +++ b/substrate/frame/election-provider-multi-phase/src/unsigned.rs @@ -430,7 +430,7 @@ pub trait MinerConfig { /// The weight is computed using `solution_weight`. type MaxWeight: Get; /// The maximum number of winners that can be elected per page (and overall). - type MaxWinnersPerPage: Get; + type MaxWinners: Get; /// The maximum number of backers (edges) per winner in the last solution. type MaxBackersPerWinner: Get; /// Something that can compute the weight of a solution. @@ -751,10 +751,8 @@ impl Miner { snapshot: RoundSnapshot>, current_round: u32, minimum_untrusted_score: Option, - ) -> Result< - ReadySolution, - FeasibilityError, - > { + ) -> Result, FeasibilityError> + { let RawSolution { solution, score, round } = raw_solution; let RoundSnapshot { voters: snapshot_voters, targets: snapshot_targets } = snapshot; @@ -766,10 +764,7 @@ impl Miner { ensure!(winners.len() as u32 == desired_targets, FeasibilityError::WrongWinnerCount); // Fail early if targets requested by data provider exceed maximum winners supported. - ensure!( - desired_targets <= T::MaxWinnersPerPage::get(), - FeasibilityError::TooManyDesiredTargets - ); + ensure!(desired_targets <= T::MaxWinners::get(), FeasibilityError::TooManyDesiredTargets); // Ensure that the solution's score can pass absolute min-score. let submitted_score = raw_solution.score; @@ -826,7 +821,7 @@ impl Miner { let known_score = supports.evaluate(); ensure!(known_score == score, FeasibilityError::InvalidScore); - // Size of winners in miner solution is equal to `desired_targets` <= `MaxWinnersPerPage`. + // Size of winners in miner solution is equal to `desired_targets` <= `MaxWinners`. let supports = supports .try_into_bounded_supports() .defensive_map_err(|_| FeasibilityError::BoundedConversionFailed)?; diff --git a/substrate/frame/election-provider-support/src/lib.rs b/substrate/frame/election-provider-support/src/lib.rs index 56217c3d3052c..1358cbf1a166e 100644 --- a/substrate/frame/election-provider-support/src/lib.rs +++ b/substrate/frame/election-provider-support/src/lib.rs @@ -58,14 +58,13 @@ //! does not receive election data as an input. All value and type parameter must be provided by the //! [`ElectionDataProvider`] trait, even if the election happens immediately. //! -//! ## Multi-page election +//! ## Multi-page election support //! //! Both [`ElectionDataProvider`] and [`ElectionProvider`] traits are parameterized by page, //! supporting an election to be performed over multiple pages. This enables the //! [`ElectionDataProvider`] implementor to provide all the election data over multiple pages. //! Similarly [`ElectionProvider::elect`] is parameterized by page index. -//! -//! ## [`LockableElectionDataProvider`] for multi-page election +////! ## [`LockableElectionDataProvider`] for multi-page election //! //! The [`LockableElectionDataProvider`] trait exposes a way for election data providers to lock //! and unlock election data mutations. This is an useful trait to ensure that the results of @@ -330,7 +329,6 @@ pub trait ElectionDataProvider { /// Returns the possible targets for the election associated with page `page`, i.e. the targets /// that could become elected, thus "electable". /// - /// TODO(gpestana): remove self-weighing and return the weight. /// This should be implemented as a self-weighing function. The implementor should register its /// appropriate weight at the end of execution with the system pallet directly. fn electable_targets( diff --git a/substrate/frame/fast-unstake/src/mock.rs b/substrate/frame/fast-unstake/src/mock.rs index c0c23af51fb53..33f1369cb8fe2 100644 --- a/substrate/frame/fast-unstake/src/mock.rs +++ b/substrate/frame/fast-unstake/src/mock.rs @@ -118,7 +118,6 @@ impl pallet_staking::Config for Runtime { type GenesisElectionProvider = Self::ElectionProvider; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type TargetList = pallet_staking::UseValidatorsMap; - type MaxValidatorSet = ConstU32<100>; } parameter_types! { diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index f03b110c64db2..73f442723d585 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -234,7 +234,6 @@ pub mod pallet { /// The absolute maximum of next winner validators this pallet should return. #[pallet::constant] - #[pallet::no_default] type MaxValidatorSet: Get; /// Something that provides a best-effort sorted list of voters aka electing nominators, @@ -345,6 +344,7 @@ pub mod pallet { type NextNewSession = (); type MaxExposurePageSize = ConstU32<64>; type MaxUnlockingChunks = ConstU32<32>; + type MaxValidatorSet = ConstU32<100>; type MaxControllersInDeprecationBatch = ConstU32<100>; type EventListeners = (); type DisablingStrategy = crate::UpToLimitDisablingStrategy; From 3d1a3301fb2f5a11daac9a0c279f3f39fd518fd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Fri, 25 Oct 2024 17:16:31 +0200 Subject: [PATCH 024/169] improve memory efficiency of try into bounded supports; tests --- .../election-provider-multi-phase/src/lib.rs | 25 ++++++++++++++++++- .../election-provider-support/src/lib.rs | 24 ++++++++++++------ 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index 049bcbabce6a7..40fa163a0aa63 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -1786,7 +1786,7 @@ impl ElectionProvider for Pallet { fn elect(page: PageIndex) -> Result, Self::Error> { // Note: this pallet **MUST** only by used in the single-block mode. - ensure!(page.is_zero(), ElectionError::::WrongPageIndex); + ensure!(page.is_zero(), ElectionError::::MultiPageNotSupported); match Self::do_elect() { Ok(bounded_supports) => { @@ -2490,6 +2490,29 @@ mod tests { }) } + #[test] + fn try_elect_multi_page_fails() { + ExtBuilder::default().onchain_fallback(false).build_and_execute(|| { + roll_to_signed(); + assert!(Snapshot::::get().is_some()); + + // submit solution and assert it is queued and ready for elect to be called. + let (solution, _, _) = MultiPhase::mine_solution().unwrap(); + assert_ok!(MultiPhase::submit( + crate::mock::RuntimeOrigin::signed(99), + Box::new(solution), + )); + roll_to(30); + assert!(QueuedSolution::::get().is_some()); + + // single page elect call works as expected. + assert_ok!(MultiPhase::elect(SINGLE_PAGE)); + + // however, multi page calls will fail. + assert!(MultiPhase::elect(10).is_err()); + }) + } + #[test] fn fallback_strategy_works() { ExtBuilder::default().onchain_fallback(true).build_and_execute(|| { diff --git a/substrate/frame/election-provider-support/src/lib.rs b/substrate/frame/election-provider-support/src/lib.rs index 1358cbf1a166e..bfd29225fc6d6 100644 --- a/substrate/frame/election-provider-support/src/lib.rs +++ b/substrate/frame/election-provider-support/src/lib.rs @@ -871,13 +871,23 @@ impl, BInner: Get> TryIntoBoundedSupports for sp_npos_elections::Supports { fn try_into_bounded_supports(self) -> Result, ()> { - let inner_bounded_supports = self - .into_iter() - .map(|(a, s)| s.try_into().map(|s| (a, s))) - .collect::, _>>() - .map_err(|_| ())?; - let outer_bounded_supports: BoundedVec<_, BOuter> = - inner_bounded_supports.try_into().map_err(|_| ())?; + // optimization note: pre-allocate outer bounded vec. + let mut outer_bounded_supports = BoundedVec::< + (AccountId, BoundedSupport), + BOuter, + >::with_bounded_capacity( + self.len().min(BOuter::get() as usize) + ); + + // optimization note: avoid intermediate allocations. + self.into_iter() + .map(|(account, support)| (account, support.try_into().map_err(|_| ()))) + .try_for_each(|(account, maybe_bounded_supports)| { + outer_bounded_supports + .try_push((account, maybe_bounded_supports?)) + .map_err(|_| ()) + })?; + Ok(outer_bounded_supports.into()) } } From 1757c91631f31291f962b279366208dd312c043e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Mon, 28 Oct 2024 14:57:43 +0100 Subject: [PATCH 025/169] a few nits in the staking pallet; handles exposures better (do not leave exposure gaps in pages) --- substrate/frame/staking/src/lib.rs | 117 ++++++++---- substrate/frame/staking/src/mock.rs | 3 +- substrate/frame/staking/src/pallet/impls.rs | 197 +++++++------------- substrate/frame/staking/src/pallet/mod.rs | 88 ++++----- substrate/frame/staking/src/tests.rs | 114 +++++++---- substrate/primitives/staking/src/lib.rs | 52 +++++- 6 files changed, 319 insertions(+), 252 deletions(-) diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index bf8417d1ac74d..890007ec92ba8 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -352,10 +352,12 @@ macro_rules! log { /// config. pub type MaxWinnersOf = ::MaxValidatorSet; -/// Maximum number of exposures (validators) that each page of [`Config::ElectionProvider`] might -/// might return. -pub type MaxExposuresPerPageOf = - <::ElectionProvider as ElectionProvider>::MaxWinnersPerPage; +/// Maximum number of exposures that can fit into an exposure page, as defined by this pallet's +/// config. +/// TODO: needed? maybe use the type directly. +pub type MaxExposuresPerPageOf = ::MaxExposurePageSize; + +pub type MaxWinnersPerPageOf

=

::MaxWinnersPerPage; /// Maximum number of nominations per nominator. pub type MaxNominationsOf = @@ -787,6 +789,8 @@ impl EraInfo { } /// Store exposure for elected validators at start of an era. - pub fn set_exposure( + /// + /// If the exposure does not exist yet for the tuple (era, validator), it sets it. Otherwise, + /// it updates the existing record by ensuring *intermediate* exposure pages are filled up with + /// `T::MaxExposurePageSize` number of backiers per page. + pub fn upsert_exposure( era: EraIndex, validator: &T::AccountId, - exposure: Exposure>, + mut exposure: Exposure>, ) { let page_size = T::MaxExposurePageSize::get().defensive_max(1); - let nominator_count = exposure.others.len(); - // expected page count is the number of nominators divided by the page size, rounded up. - let expected_page_count = nominator_count - .defensive_saturating_add((page_size as usize).defensive_saturating_sub(1)) - .saturating_div(page_size as usize); - - let (exposure_metadata, exposure_pages) = exposure.into_pages(page_size); - defensive_assert!(exposure_pages.len() == expected_page_count, "unexpected page count"); - - // insert or update validator's overview. - let append_from = ErasStakersOverview::::mutate(era, &validator, |stored| { - if let Some(stored_overview) = stored { - let append_from = stored_overview.page_count; - *stored = Some(stored_overview.merge(exposure_metadata)); - append_from - } else { - *stored = Some(exposure_metadata); - Zero::zero() - } - }); - - exposure_pages.iter().enumerate().for_each(|(idx, paged_exposure)| { - let append_at = (append_from + idx as u32) as Page; - >::insert((era, &validator, append_at), &paged_exposure); - }); - } + if let Some(stored_overview) = ErasStakersOverview::::get(era, &validator) { + let exposures_append = exposure.split_others(stored_overview.last_page_empty_slots); + let last_page_idx = stored_overview.page_count.saturating_sub(1); + + let last_page_idx = ErasStakersOverview::::mutate(era, &validator, |stored| { + // new metadata is updated based on 3 different set of exposures: the + // current one, the exposure split to be "fitted" into the current last page and + // the exposure set that will be appended from the new page onwards. + let new_metadata = + stored.defensive_unwrap_or_default().update_with::( + [&exposures_append, &exposure] + .iter() + .fold(Default::default(), |total, expo| { + total.saturating_add(expo.total.saturating_sub(expo.own)) + }), + [&exposures_append, &exposure] + .iter() + .fold(Default::default(), |count, expo| { + count.saturating_add(expo.others.len() as u32) + }), + ); + *stored = new_metadata.into(); + + last_page_idx + }); - /// Store total exposure for all the elected validators in the era. - pub(crate) fn set_total_stake(era: EraIndex, total_stake: BalanceOf) { - >::insert(era, total_stake); + // fill up last page with exposures. + let mut last_page = + ErasStakersPaged::::get((era, validator, last_page_idx)).unwrap_or_default(); + + last_page.page_total = last_page + .page_total + .saturating_add(exposures_append.total) + .saturating_sub(exposures_append.own); + last_page.others.extend(exposures_append.others); + ErasStakersPaged::::insert((era, &validator, last_page_idx), last_page); + + // now handle the remainig exposures and append the exposure pages. The metadata update + // has been already handled above. + let (_, exposure_pages) = exposure.into_pages(page_size); + + exposure_pages.iter().enumerate().for_each(|(idx, paged_exposure)| { + let append_at = + (last_page_idx.saturating_add(1).saturating_add(idx as u32)) as Page; + >::insert((era, &validator, append_at), &paged_exposure); + }); + } else { + // expected page count is the number of nominators divided by the page size, rounded up. + let expected_page_count = exposure + .others + .len() + .defensive_saturating_add((page_size as usize).defensive_saturating_sub(1)) + .saturating_div(page_size as usize); + + // no exposures yet for this (era, validator) tuple, calculate paged exposure pages and + // metadata from a blank slate. + let (exposure_metadata, exposure_pages) = exposure.into_pages(page_size); + defensive_assert!(exposure_pages.len() == expected_page_count, "unexpected page count"); + + // insert metadata. + ErasStakersOverview::::insert(era, &validator, exposure_metadata); + + // insert or update validator's overview. + exposure_pages.iter().enumerate().for_each(|(idx, paged_exposure)| { + let append_at = idx as Page; + >::insert((era, &validator, append_at), &paged_exposure); + }); + }; } + /// Update the total exposure for all the elected validators in the era. pub(crate) fn add_total_stake(era: EraIndex, stake: BalanceOf) { >::mutate(era, |total_stake| { *total_stake += stake; diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index e9a761a59a81b..1734395fb6e86 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -20,7 +20,8 @@ use crate::{self as pallet_staking, *}; use frame_election_provider_support::{ bounds::{ElectionBounds, ElectionBoundsBuilder}, - onchain, BoundedSupports, SequentialPhragmen, Support, TryIntoBoundedSupports, VoteWeight, + onchain, BoundedSupports, ElectionProvider, SequentialPhragmen, Support, + TryIntoBoundedSupports, VoteWeight, }; use frame_support::{ assert_ok, derive_impl, ord_parameter_types, parameter_types, diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index ea6fd4915d00a..a566d7fd90acc 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -17,9 +17,6 @@ //! Implementations for the Staking FRAME Pallet. -// TODO: remove -#![allow(dead_code)] - use frame_election_provider_support::{ bounds::{CountBound, SizeBound}, data_provider, BoundedSupportsOf, DataProviderBounds, ElectionDataProvider, ElectionProvider, @@ -56,7 +53,7 @@ use sp_staking::{ use crate::{ asset, election_size_tracker::StaticTracker, log, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraInfo, EraPayout, Exposure, ExposureOf, Forcing, IndividualExposure, - LedgerIntegrityState, MaxExposuresPerPageOf, MaxNominationsOf, MaxWinnersOf, Nominations, + LedgerIntegrityState, MaxNominationsOf, MaxWinnersOf, MaxWinnersPerPageOf, Nominations, NominationsQuota, PositiveImbalanceOf, RewardDestination, SessionInterface, SnapshotStatus, StakingLedger, ValidatorPrefs, }; @@ -485,10 +482,6 @@ impl Pallet { Self::set_force_era(Forcing::NotForcing); } - //TODO: we may want to keep track of the past 1/N era's stashes - // reset electable stashes. - ElectableStashes::::kill(); - maybe_new_era_validators } else { // Set initial era. @@ -618,33 +611,12 @@ impl Pallet { /// * Bump the current era storage (which holds the latest planned era). /// * Store start session index for the new planned era. /// * Clean old era information. - /// * Store staking information for the new planned era /// - /// Returns the new validator set. - pub fn trigger_new_era( - start_session_index: SessionIndex, - exposures: BoundedVec< - (T::AccountId, Exposure>), - MaxExposuresPerPageOf, - >, - ) -> BoundedVec> { - // Increment or set current era. - let new_planned_era = CurrentEra::::mutate(|s| { - *s = Some(s.map(|s| s + 1).unwrap_or(0)); - s.unwrap() - }); - ErasStartSessionIndex::::insert(&new_planned_era, &start_session_index); - - // Clean old era information. - if let Some(old_era) = new_planned_era.checked_sub(T::HistoryDepth::get() + 1) { - Self::clear_era_information(old_era); - } - - // Set staking information for the new era. - Self::store_stakers_info(exposures, new_planned_era) - } - - pub fn trigger_new_era_paged(start_session_index: SessionIndex) { + /// Note: staking information for the new planned era has been processed and stored during the + /// `elect_paged(page_index)` calls. + /// + /// The new validator set for this era is stored under [`ElectableStashes`]. + pub fn trigger_new_era(start_session_index: SessionIndex) { // Increment or set current era. let new_planned_era = CurrentEra::::mutate(|s| { *s = Some(s.map(|s| s + 1).unwrap_or(0)); @@ -660,7 +632,9 @@ impl Pallet { /// Potentially plan a new era. /// - /// Get election result from `T::ElectionProvider`. + /// The election results are either fetched directly from an election provider if it is the + /// "genesis" election or from a cached set of winners. + /// /// In case election result has more than [`MinimumValidatorCount`] validator trigger a new era. /// /// In case a new era is planned, the new validator set is returned. @@ -669,23 +643,29 @@ impl Pallet { is_genesis: bool, ) -> Option>> { let validators: BoundedVec> = if is_genesis { - // genesis election only use the lsp of the election result. + // genesis election only uses one election result page. let result = ::elect(Zero::zero()).map_err(|e| { log!(warn, "genesis election provider failed due to {:?}", e); Self::deposit_event(Event::StakingElectionFailed); }); - let (_, planned_era) = ElectingStartedAt::::get().unwrap_or_default(); let exposures = Self::collect_exposures(result.ok().unwrap_or_default()); - Self::store_stakers_info_paged(exposures.clone(), planned_era); - exposures - .into_iter() + let validators = exposures + .iter() .map(|(validator, _)| validator) + .cloned() .try_collect() - .unwrap_or_default() + .unwrap_or_default(); + + // set stakers info for genesis era (0). + Self::store_stakers_info(exposures.into_inner(), Zero::zero()); + + validators } else { - ElectableStashes::::get() + // note: exposures have already been processed and stored for each of the election + // solution page at the time of `elect_paged(page_index)`. + ElectableStashes::::take() }; log!(info, "electable validators for session {:?}: {:?}", start_session_index, validators); @@ -717,114 +697,72 @@ impl Pallet { } Self::deposit_event(Event::StakersElected); - Self::trigger_new_era_paged(start_session_index); + Self::trigger_new_era(start_session_index); + Some(validators) } /// Paginated elect. /// - /// TODO: rust-docs + /// Fetches the election page with index `page` from the election provider. + /// + /// The results from the elect call shold be stored in the `ElectableStashes` storage. In + /// addition, it stores stakers' information for next planned era based on the paged solution + /// data returned. pub(crate) fn do_elect_paged(page: PageIndex) { let paged_result = match ::elect(page) { Ok(result) => result, Err(e) => { - log!(warn, "electiong provider page failed due to {:?} (page: {})", e, page); - // TODO: be resilient here, not all pages need to be submitted successfuly for an - // election to be OK, provided that the election score is good enough. + log!(warn, "election provider page failed due to {:?} (page: {})", e, page); Self::deposit_event(Event::StakingElectionFailed); return }, }; - let new_planned_era = CurrentEra::::get().unwrap_or_default().saturating_add(1); - let stashes = - Self::store_stakers_info_paged(Self::collect_exposures(paged_result), new_planned_era); + // preparing the next era. Note: we expect elect paged to be called *only* during a + // non-genesis era, thus current era should be set by now. + let planning_era = CurrentEra::::get().defensive_unwrap_or_default().saturating_add(1); + + let stashes = Self::store_stakers_info( + Self::collect_exposures(paged_result).into_inner(), + planning_era, + ); ElectableStashes::::mutate(|v| { - // TODO: be even more defensive and handle potential error? (should not happen if page - // bounds and T::MaxValidatorSet configs are in sync). + // TODO: dedup duplicate validator IDs, isntead of try_extend. let _ = (*v).try_extend(stashes.into_iter()).defensive(); }); } /// Process the output of a paged election. /// - /// Store staking information for the new planned era - pub fn store_stakers_info_paged( - exposures: BoundedVec< - (T::AccountId, Exposure>), - MaxExposuresPerPageOf, - >, - new_planned_era: EraIndex, - ) -> BoundedVec> { - // Populate elected stash, stakers, exposures, and the snapshot of validator prefs. - let mut total_stake: BalanceOf = Zero::zero(); - let mut elected_stashes = Vec::with_capacity(exposures.len()); - - exposures.into_iter().for_each(|(stash, exposure)| { - // build elected stash - elected_stashes.push(stash.clone()); - // accumulate total stake - total_stake = total_stake.saturating_add(exposure.total); - // store staker exposure for this era - EraInfo::::set_exposure(new_planned_era, &stash, exposure); - }); - - // TODO: correct?? - let elected_stashes: BoundedVec<_, MaxExposuresPerPageOf> = elected_stashes - .try_into() - .expect("elected_stashes.len() always equal to exposures.len(); qed"); - - EraInfo::::add_total_stake(new_planned_era, total_stake); - - // Collect the pref of all winners. - for stash in &elected_stashes { - let pref = Self::validators(stash); - >::insert(&new_planned_era, stash, pref); - } - - if new_planned_era > 0 { - log!( - info, - "updated validator set (current size {:?}) for era {:?}", - elected_stashes.len(), - new_planned_era, - ); - } - - elected_stashes - } - - /// Process the output of the election. - /// - /// Store staking information for the new planned era + /// Store staking information for the new planned era of a single election page. pub fn store_stakers_info( - exposures: BoundedVec< - (T::AccountId, Exposure>), - MaxExposuresPerPageOf, - >, + exposures: Vec<(T::AccountId, Exposure>)>, new_planned_era: EraIndex, - ) -> BoundedVec> { - // Populate elected stash, stakers, exposures, and the snapshot of validator prefs. - let mut total_stake: BalanceOf = Zero::zero(); - let mut elected_stashes = Vec::with_capacity(exposures.len()); + ) -> BoundedVec> { + // populate elected stash, stakers, exposures, and the snapshot of validator prefs. + let mut total_stake_page: BalanceOf = Zero::zero(); + let mut elected_stashes_page = Vec::with_capacity(exposures.len()); exposures.into_iter().for_each(|(stash, exposure)| { - // build elected stash - elected_stashes.push(stash.clone()); - // accumulate total stake - total_stake = total_stake.saturating_add(exposure.total); - // store staker exposure for this era - EraInfo::::set_exposure(new_planned_era, &stash, exposure); + // build elected stash. + elected_stashes_page.push(stash.clone()); + // accumulate total stake. + total_stake_page = total_stake_page.saturating_add(exposure.total); + // set or update staker exposure for this era. + EraInfo::::upsert_exposure(new_planned_era, &stash, exposure); }); - let elected_stashes: BoundedVec<_, MaxExposuresPerPageOf> = elected_stashes - .try_into() - .expect("elected_stashes.len() always equal to exposures.len(); qed"); + let elected_stashes: BoundedVec<_, MaxWinnersPerPageOf> = + elected_stashes_page + .try_into() + .expect("elected_stashes.len() always equal to exposures.len(); qed"); - EraInfo::::set_total_stake(new_planned_era, total_stake); + // adds to total stake in this era. + EraInfo::::add_total_stake(new_planned_era, total_stake_page); - // Collect the pref of all winners. + // collect or update the pref of all winners. for stash in &elected_stashes { let pref = Self::validators(stash); >::insert(&new_planned_era, stash, pref); @@ -833,7 +771,7 @@ impl Pallet { if new_planned_era > 0 { log!( info, - "new validator set of size {:?} has been processed for era {:?}", + "updated validator set with {:?} validators for era {:?}", elected_stashes.len(), new_planned_era, ); @@ -844,10 +782,15 @@ impl Pallet { /// Consume a set of [`BoundedSupports`] from [`sp_npos_elections`] and collect them into a /// [`Exposure`]. + /// + /// Returns vec of all the exposures of a validator in `paged_supports`, bounded by the number + /// of max winners per page returned by the election provider. fn collect_exposures( supports: BoundedSupportsOf, - ) -> BoundedVec<(T::AccountId, Exposure>), MaxExposuresPerPageOf> - { + ) -> BoundedVec< + (T::AccountId, Exposure>), + MaxWinnersPerPageOf, + > { let total_issuance = asset::total_issuance::(); let to_currency = |e: frame_election_provider_support::ExtendedBalance| { T::CurrencyToVote::to_currency(e, total_issuance) @@ -975,7 +918,7 @@ impl Pallet { stash: T::AccountId, exposure: Exposure>, ) { - EraInfo::::set_exposure(current_era, &stash, exposure); + EraInfo::::upsert_exposure(current_era, &stash, exposure); } #[cfg(feature = "runtime-benchmarks")] @@ -1341,7 +1284,6 @@ impl Pallet { } } -// TODO(gpestana): add unit tests. impl LockableElectionDataProvider for Pallet { fn set_lock() -> data_provider::Result<()> { match ElectionDataLock::::get() { @@ -2084,7 +2026,7 @@ impl StakingInterface for Pallet { .map(|(who, value)| IndividualExposure { who: who.clone(), value: *value }) .collect::>(); let exposure = Exposure { total: Default::default(), own: Default::default(), others }; - EraInfo::::set_exposure(*current_era, stash, exposure); + EraInfo::::upsert_exposure(*current_era, stash, exposure); } fn set_current_era(era: EraIndex) { @@ -2341,6 +2283,7 @@ impl Pallet { own: Zero::zero(), nominator_count: 0, page_count: 0, + last_page_empty_slots: Default::default(), }; ErasStakersPaged::::iter_prefix((era,)) @@ -2359,6 +2302,8 @@ impl Pallet { own: metadata.own, nominator_count: metadata.nominator_count + expo.others.len() as u32, page_count: metadata.page_count + 1, + last_page_empty_slots: (T::MaxExposurePageSize::get() + .saturating_sub(expo.others.len() as u32)), }, ); diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index 73f442723d585..13d0352423bc3 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -232,7 +232,10 @@ pub mod pallet { #[pallet::constant] type MaxExposurePageSize: Get; - /// The absolute maximum of next winner validators this pallet should return. + /// The absolute maximum of winner validators this pallet should return. + /// + /// As this pallet supports multi-block election, the set of winner validators *per + /// election* is bounded by this type. #[pallet::constant] type MaxValidatorSet: Get; @@ -742,8 +745,8 @@ pub mod pallet { /// Voter snapshot progress status. /// - /// If the status is `Ongoing`, it keeps track of the last voter account returned in the - /// snapshot. + /// If the status is `Ongoing`, it keeps a cursor of the last voter retrieved to proceed when + /// creating the next snapshot page. #[pallet::storage] pub(crate) type VoterSnapshotStatus = StorageValue<_, SnapshotStatus, ValueQuery>; @@ -756,25 +759,23 @@ pub mod pallet { pub(crate) type TargetSnapshotStatus = StorageValue<_, SnapshotStatus, ValueQuery>; - /// Keeps track of an ongoing multi-page election solution request and the block the first paged - /// was requested, if any. In addition, it also keeps track of the current era that is being - /// plannet. + /// Keeps track of an ongoing multi-page election solution request. + /// + /// Stores the block number of when the first election page was requested. `None` indicates + /// that the election results haven't started to be fetched. #[pallet::storage] - pub(crate) type ElectingStartedAt = - StorageValue<_, (BlockNumberFor, EraIndex), OptionQuery>; + pub(crate) type ElectingStartedAt = StorageValue<_, BlockNumberFor, OptionQuery>; - // TODO: - // * maybe use pallet-paged-list? (https://paritytech.github.io/polkadot-sdk/master/pallet_paged_list/index.html) + /// A bounded list of the "electable" stashes that resulted from a successful election. #[pallet::storage] pub(crate) type ElectableStashes = StorageValue<_, BoundedVec, ValueQuery>; - /// Lock for election data provider. + /// Lock state for election data mutations. /// - /// While the lock is set, the data to build a snapshot is frozen, i.e. the returned data from - /// `ElectionDataProvider` implementation will not change. + /// While the lock is set, there should be no mutations on the ledgers/staking data, ensuring + /// that the data provided to [`Config::ElectionDataProvider`] is stable during all pages. #[pallet::storage] - #[pallet::getter(fn election_data_lock)] pub(crate) type ElectionDataLock = StorageValue<_, (), OptionQuery>; #[pallet::genesis_config] @@ -840,6 +841,11 @@ pub mod pallet { ), _ => Ok(()), }); + assert!( + ValidatorCount::::get() <= + ::MaxWinnersPerPage::get() * + ::Pages::get() + ); } // all voters are reported to the `VoterList`. @@ -1017,51 +1023,41 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { /// Start fetching the election pages `Pages` blocks before the election prediction, so - /// that the `ElectableStashes` is ready with all the pages on time. + /// that the `ElectableStashes` has been populated with all validators from all pages at + /// the time of the election. fn on_initialize(now: BlockNumberFor) -> Weight { let pages: BlockNumberFor = <::ElectionProvider as ElectionProvider>::Pages::get().into(); - if let Some((started_at, planning_era)) = ElectingStartedAt::::get() { - let remaining_pages = + // election ongoing, fetch the next page. + if let Some(started_at) = ElectingStartedAt::::get() { + let next_page = pages.saturating_sub(One::one()).saturating_sub(now.saturating_sub(started_at)); - if remaining_pages == Zero::zero() { + // note: this pallet is expected to fetch all the solution pages starting from the + // most significant one through to the page 0. Fetching page zero is an indication + // that all the solution pages have been fetched. + if next_page == Zero::zero() { + crate::log!(trace, "elect(): finished fetching all paged solutions."); Self::do_elect_paged(Zero::zero()); - // last page, reset elect status and update era. - crate::log!(info, "elect(): finished fetching all paged solutions."); - CurrentEra::::set(Some(planning_era)); ElectingStartedAt::::kill(); } else { - crate::log!( - info, - "elect(): progressing with calling elect, remaining pages {:?}.", - remaining_pages - ); - Self::do_elect_paged(remaining_pages.saturated_into::()); + crate::log!(trace, "elect(): progressing, {:?} remaining pages.", next_page); + Self::do_elect_paged(next_page.saturated_into::()); } } else { + // election isn't ongoing yet, check if it should start. let next_election = ::next_election_prediction(now); if now == (next_election.saturating_sub(pages)) { - // start calling elect. crate::log!( - info, - "elect(): next election in {:?} pages, start fetching solution pages.", - pages, + trace, + "elect(): start fetching solution pages. expected pages: {}", + pages ); - Self::do_elect_paged(pages.saturated_into::().saturating_sub(1)); - // set `ElectingStartedAt` only in multi-paged election. - if pages > One::one() { - ElectingStartedAt::::set(Some(( - now, - CurrentEra::::get().unwrap_or_default().saturating_add(1), - ))); - } else { - crate::log!(info, "elect(): finished fetching the single paged solution."); - } + Self::do_elect_paged(pages.saturated_into::().saturating_sub(1)); } }; @@ -1099,7 +1095,15 @@ pub mod pallet { "As per documentation, slash defer duration ({}) should be less than bonding duration ({}).", T::SlashDeferDuration::get(), T::BondingDuration::get(), - ) + ); + + // TODO: needed and true? test it! + // The max exposure page size should not be larger than the max winners per page + // returned by the election provider. + assert!( + ::MaxExposurePageSize::get() <= + <::ElectionProvider as ElectionProvider>::MaxWinnersPerPage::get() + ); } #[cfg(feature = "try-runtime")] diff --git a/substrate/frame/staking/src/tests.rs b/substrate/frame/staking/src/tests.rs index a153f065ed19d..f66b4dcee597d 100644 --- a/substrate/frame/staking/src/tests.rs +++ b/substrate/frame/staking/src/tests.rs @@ -1314,6 +1314,7 @@ fn bond_extra_and_withdraw_unbonded_works() { legacy_claimed_rewards: bounded_vec![], } ); + assert_eq!( Staking::eras_stakers(active_era(), &11), Exposure { total: 1000, own: 1000, others: vec![] } @@ -1864,7 +1865,11 @@ fn reward_to_stake_works() { let _ = asset::set_stakeable_balance::(&20, 1000); // Bypass logic and change current exposure - EraInfo::::set_exposure(0, &21, Exposure { total: 69, own: 69, others: vec![] }); + EraInfo::::upsert_exposure( + 0, + &21, + Exposure { total: 69, own: 69, others: vec![] }, + ); >::insert( &20, StakingLedgerInspect { @@ -2334,7 +2339,7 @@ fn reward_validator_slashing_validator_does_not_overflow() { // Check reward ErasRewardPoints::::insert(0, reward); - EraInfo::::set_exposure(0, &11, exposure); + EraInfo::::upsert_exposure(0, &11, exposure); ErasValidatorReward::::insert(0, stake); assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 0, 0)); assert_eq!(asset::total_balance::(&11), stake * 2); @@ -2346,7 +2351,7 @@ fn reward_validator_slashing_validator_does_not_overflow() { // only slashes out of bonded stake are applied. without this line, it is 0. Staking::bond(RuntimeOrigin::signed(2), stake - 1, RewardDestination::Staked).unwrap(); // Override exposure of 11 - EraInfo::::set_exposure( + EraInfo::::upsert_exposure( 0, &11, Exposure { @@ -6932,7 +6937,7 @@ fn test_runtime_api_pending_rewards() { >::insert(0, validator_one, exposure.clone()); >::insert(0, validator_two, exposure.clone()); // add paged exposure for third validator - EraInfo::::set_exposure(0, &validator_three, exposure); + EraInfo::::upsert_exposure(0, &validator_three, exposure); // add some reward to be distributed ErasValidatorReward::::insert(0, 1000); @@ -8450,7 +8455,7 @@ pub mod multi_page_staking { } #[test] - fn collect_exposures_multi_page_elect_works() { + fn store_stakers_info_multi_page_elect_works() { ExtBuilder::default().exposures_page_size(2).build_and_execute(|| { assert_eq!(MaxExposurePageSize::get(), 2); @@ -8485,63 +8490,96 @@ pub mod multi_page_staking { let exposures_page_one = bounded_vec![(1, exposure_one), (2, exposure_two),]; let exposures_page_two = bounded_vec![(1, exposure_three),]; + // stores exposure page with exposures of validator 1 and 2, returns exposed validator + // account id. assert_eq!( - Pallet::::store_stakers_info_paged(exposures_page_one, current_era()) - .to_vec(), + Pallet::::store_stakers_info(exposures_page_one, current_era()).to_vec(), vec![1, 2] ); + // Stakers overview OK for validator 1 and 2. + assert_eq!( + ErasStakersOverview::::get(0, &1).unwrap(), + PagedExposureMetadata { + total: 1700, + own: 1000, + nominator_count: 3, + page_count: 2, + last_page_empty_slots: 1, + }, + ); assert_eq!( - Pallet::::store_stakers_info_paged(exposures_page_two, current_era()) - .to_vec(), + ErasStakersOverview::::get(0, &2).unwrap(), + PagedExposureMetadata { + total: 2000, + own: 1000, + nominator_count: 2, + page_count: 1, + last_page_empty_slots: 0, + }, + ); + + // stores exposure page with exposures of validator 1, returns exposed validator + // account id. + assert_eq!( + Pallet::::store_stakers_info(exposures_page_two, current_era()).to_vec(), vec![1] ); // Stakers overview OK for validator 1. assert_eq!( ErasStakersOverview::::get(0, &1).unwrap(), - PagedExposureMetadata { total: 2200, own: 1000, nominator_count: 5, page_count: 3 }, - ); - // Stakers overview OK for validator 2. - assert_eq!( - ErasStakersOverview::::get(0, &2).unwrap(), - PagedExposureMetadata { total: 2000, own: 1000, nominator_count: 2, page_count: 1 }, + PagedExposureMetadata { + total: 2200, + own: 1000, + nominator_count: 5, + page_count: 3, + last_page_empty_slots: 1, + }, ); // validator 1 has 3 paged exposures. + assert!( + ErasStakersPaged::::iter_prefix_values((0, &1)).count() as u32 == + EraInfo::::get_page_count(0, &1) && + EraInfo::::get_page_count(0, &1) == 3 + ); assert!(ErasStakersPaged::::get((0, &1, 0)).is_some()); assert!(ErasStakersPaged::::get((0, &1, 1)).is_some()); assert!(ErasStakersPaged::::get((0, &1, 2)).is_some()); assert!(ErasStakersPaged::::get((0, &1, 3)).is_none()); - assert_eq!(ErasStakersPaged::::iter_prefix_values((0, &1)).count(), 3); // validator 2 has 1 paged exposures. assert!(ErasStakersPaged::::get((0, &2, 0)).is_some()); assert!(ErasStakersPaged::::get((0, &2, 1)).is_none()); assert_eq!(ErasStakersPaged::::iter_prefix_values((0, &2)).count(), 1); - // exposures of validator 1. + // exposures of validator 1 are the expected: assert_eq!( - ErasStakersPaged::::iter_prefix_values((0, &1)).collect::>(), - vec![ - ExposurePage { - page_total: 100, - others: vec![IndividualExposure { who: 103, value: 100 }] - }, - ExposurePage { - page_total: 500, - others: vec![ - IndividualExposure { who: 110, value: 250 }, - IndividualExposure { who: 111, value: 250 } - ] - }, - ExposurePage { - page_total: 600, - others: vec![ - IndividualExposure { who: 101, value: 500 }, - IndividualExposure { who: 102, value: 100 } - ] - }, - ], + ErasStakersPaged::::get((0, &1, 0)).unwrap(), + ExposurePage { + page_total: 600, + others: vec![ + IndividualExposure { who: 101, value: 500 }, + IndividualExposure { who: 102, value: 100 } + ] + }, + ); + assert_eq!( + ErasStakersPaged::::get((0, &1, 1)).unwrap(), + ExposurePage { + page_total: 350, + others: vec![ + IndividualExposure { who: 103, value: 100 }, + IndividualExposure { who: 110, value: 250 } + ] + } + ); + assert_eq!( + ErasStakersPaged::::get((0, &1, 2)).unwrap(), + ExposurePage { + page_total: 250, + others: vec![IndividualExposure { who: 111, value: 250 }] + } ); // exposures of validator 2. diff --git a/substrate/primitives/staking/src/lib.rs b/substrate/primitives/staking/src/lib.rs index 191c1c3d8a312..0efb21070e20e 100644 --- a/substrate/primitives/staking/src/lib.rs +++ b/substrate/primitives/staking/src/lib.rs @@ -381,7 +381,26 @@ impl< Balance: HasCompact + AtLeast32BitUnsigned + Copy + codec::MaxEncodedLen, > Exposure { - /// Splits an `Exposure` into `PagedExposureMetadata` and multiple chunks of + /// Splits self into two where the returned partial `Exposure` has max of `n_others` individual + /// exposures while the remaining exposures are left in `self`. + pub fn split_others(&mut self, n_others: u32) -> Self { + let head_others: Vec<_> = + self.others.drain(..(n_others as usize).min(self.others.len())).collect(); + + let total_others_head: Balance = head_others + .iter() + .fold(Zero::zero(), |acc: Balance, o| acc.saturating_add(o.value)); + + self.total = self.total.saturating_sub(total_others_head); + + Self { + total: total_others_head.saturating_add(self.own), + own: self.own, + others: head_others, + } + } + + /// Converts an `Exposure` into `PagedExposureMetadata` and multiple chunks of /// `IndividualExposure` with each chunk having maximum of `page_size` elements. pub fn into_pages( self, @@ -390,6 +409,7 @@ impl< let individual_chunks = self.others.chunks(page_size as usize); let mut exposure_pages: Vec> = Vec::with_capacity(individual_chunks.len()); + let mut total_chunks_last_page = Default::default(); for chunk in individual_chunks { let mut page_total: Balance = Zero::zero(); @@ -403,6 +423,7 @@ impl< }) } + total_chunks_last_page = others.len() as u32; exposure_pages.push(ExposurePage { page_total, others }); } @@ -412,6 +433,7 @@ impl< own: self.own, nominator_count: self.others.len() as u32, page_count: exposure_pages.len() as Page, + last_page_empty_slots: page_size.saturating_sub(total_chunks_last_page), }, exposure_pages, ) @@ -477,6 +499,8 @@ pub struct PagedExposureMetadata { pub nominator_count: u32, /// Number of pages of nominators. pub page_count: Page, + /// Number of empty slots in the last page. + pub last_page_empty_slots: u32, } impl PagedExposureMetadata @@ -485,22 +509,30 @@ where + codec::MaxEncodedLen + sp_std::ops::Add + sp_std::ops::Sub + + sp_runtime::Saturating + PartialEq + Copy + sp_runtime::traits::Debug, { - /// Merge a paged exposure metadata page into self and return the result. - pub fn merge(self, other: Self) -> Self { - // TODO(gpestana): re-enable assert. - //debug_assert!(self.own == other.own); + /// Consomes self and returns the result of the metadata updated with `other_balances` and + /// of adding `other_num` nominators to the metadata. + /// + /// `Max` is a getter of the maximum number of nominators per page. + pub fn update_with>( + self, + others_balance: Balance, + others_num: u32, + ) -> Self { + let new_nominator_count = self.nominator_count.saturating_add(others_num); + let new_page_count = + new_nominator_count.saturating_add(Max::get()).saturating_div(Max::get()); Self { - total: self.total + other.total - self.own, + total: self.total.saturating_add(others_balance), own: self.own, - nominator_count: self.nominator_count + other.nominator_count, - // TODO(gpestana): merge the pages efficiently so that we make sure all the slots in the - // page are filled. - page_count: self.page_count + other.page_count, + nominator_count: new_nominator_count, + page_count: new_page_count, + last_page_empty_slots: Max::get().saturating_sub(new_nominator_count % Max::get()), } } } From 7022467bca3f75f8fa16ca19092eba0c2273f12f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Fri, 1 Nov 2024 12:49:15 +0100 Subject: [PATCH 026/169] updates electable stashes to allow for multiple adds across pages --- substrate/frame/staking/src/mock.rs | 9 ++++-- substrate/frame/staking/src/pallet/impls.rs | 35 ++++++++++++++++++--- substrate/frame/staking/src/pallet/mod.rs | 14 ++------- substrate/frame/staking/src/tests.rs | 30 ++++++++++++++++++ 4 files changed, 71 insertions(+), 17 deletions(-) diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index 1734395fb6e86..67540c25f7bdc 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -209,6 +209,7 @@ parameter_types! { pub static MaxValidatorSet: u32 = 100; pub static ElectionsBounds: ElectionBounds = ElectionBoundsBuilder::default().build(); pub static AbsoluteMaxNominations: u32 = 16; + pub static MaxWinnersPerPage: u32 = u32::MAX; } type VoterBagsListInstance = pallet_bags_list::Instance1; @@ -229,7 +230,7 @@ impl onchain::Config for OnChainSeqPhragmen { type WeightInfo = (); type Bounds = ElectionsBounds; type MaxBackersPerWinner = ConstU32<{ u32::MAX }>; - type MaxWinnersPerPage = ConstU32<{ u32::MAX }>; + type MaxWinnersPerPage = MaxWinnersPerPage; } pub struct MockReward {} @@ -431,6 +432,10 @@ impl ExtBuilder { self.balance_factor = factor; self } + pub fn max_validator_set(self, max: u32) -> Self { + MaxValidatorSet::set(max); + self + } pub fn try_state(self, enable: bool) -> Self { SkipTryStateCheck::set(!enable); self @@ -941,8 +946,8 @@ pub(crate) fn to_bounded_supports( supports: Vec<(AccountId, Support)>, ) -> BoundedSupports< AccountId, - <::ElectionProvider as ElectionProvider>::MaxBackersPerWinner, <::ElectionProvider as ElectionProvider>::MaxWinnersPerPage, + <::ElectionProvider as ElectionProvider>::MaxBackersPerWinner, > { supports.try_into_bounded_supports().unwrap() } diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index a566d7fd90acc..d889da7cbca24 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -666,6 +666,11 @@ impl Pallet { // note: exposures have already been processed and stored for each of the election // solution page at the time of `elect_paged(page_index)`. ElectableStashes::::take() + .into_inner() + .into_iter() + .collect::>() + .try_into() + .expect("same bounds, will fit; qed.") }; log!(info, "electable validators for session {:?}: {:?}", start_session_index, validators); @@ -728,10 +733,12 @@ impl Pallet { planning_era, ); - ElectableStashes::::mutate(|v| { - // TODO: dedup duplicate validator IDs, isntead of try_extend. - let _ = (*v).try_extend(stashes.into_iter()).defensive(); - }); + match Self::add_electables(stashes) { + Ok(_) => (), + Err(_) => { + defensive!("electable stashes exceeded limit, unexpected."); + }, + } } /// Process the output of a paged election. @@ -823,6 +830,26 @@ impl Pallet { .expect("we only map through support vector which cannot change the size; qed") } + /// Adds a new set of stashes to the electable stashes. + /// + /// Deduplicates stashes in place and returns an error if the bounds are exceeded. + pub(crate) fn add_electables( + stashes: BoundedVec>, + ) -> Result<(), ()> { + let mut storage_stashes = ElectableStashes::::get(); + for stash in stashes.into_iter() { + storage_stashes.try_insert(stash).map_err(|_| { + // add as many stashes as possible. + ElectableStashes::::set(storage_stashes.clone()); + () + })?; + } + + ElectableStashes::::set(storage_stashes); + + Ok(()) + } + /// Remove all associated data of a stash account from the staking system. /// /// Assumes storage is upgraded before calling. diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index 13d0352423bc3..54a6983573945 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -27,7 +27,7 @@ use frame_support::{ InspectLockableCurrency, LockableCurrency, OnUnbalanced, UnixTime, }, weights::Weight, - BoundedVec, + BoundedBTreeSet, BoundedVec, }; use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; use sp_runtime::{ @@ -769,7 +769,7 @@ pub mod pallet { /// A bounded list of the "electable" stashes that resulted from a successful election. #[pallet::storage] pub(crate) type ElectableStashes = - StorageValue<_, BoundedVec, ValueQuery>; + StorageValue<_, BoundedBTreeSet, ValueQuery>; /// Lock state for election data mutations. /// @@ -1061,7 +1061,7 @@ pub mod pallet { } }; - // TODO: benchmarls of fetching/ not fetching election page on_initialize. + // TODO: benchmark on_initialize // return the weight of the on_finalize. T::DbWeight::get().reads(1) @@ -1096,14 +1096,6 @@ pub mod pallet { T::SlashDeferDuration::get(), T::BondingDuration::get(), ); - - // TODO: needed and true? test it! - // The max exposure page size should not be larger than the max winners per page - // returned by the election provider. - assert!( - ::MaxExposurePageSize::get() <= - <::ElectionProvider as ElectionProvider>::MaxWinnersPerPage::get() - ); } #[cfg(feature = "try-runtime")] diff --git a/substrate/frame/staking/src/tests.rs b/substrate/frame/staking/src/tests.rs index f66b4dcee597d..7b7f420e70456 100644 --- a/substrate/frame/staking/src/tests.rs +++ b/substrate/frame/staking/src/tests.rs @@ -8374,6 +8374,36 @@ pub mod multi_page_staking { use super::*; use frame_election_provider_support::ElectionDataProvider; + #[test] + fn add_electable_stashes_work() { + ExtBuilder::default().max_validator_set(5).build_and_execute(|| { + assert_eq!(MaxValidatorSet::get(), 5); + assert!(ElectableStashes::::get().is_empty()); + + // adds stashes without duplicates, do not overflow bounds. + assert_ok!(Staking::add_electables(bounded_vec![1, 2, 3])); + assert_eq!( + ElectableStashes::::get().into_inner().into_iter().collect::>(), + vec![1, 2, 3] + ); + + // adds with duplicates which are deduplicated implicitly, no not overflow bounds. + assert_ok!(Staking::add_electables(bounded_vec![1, 2, 4])); + assert_eq!( + ElectableStashes::::get().into_inner().into_iter().collect::>(), + vec![1, 2, 3, 4] + ); + + // adds stashes so that bounds are overflown, fails and internal state changes so that + // all slots are filled. + assert!(Staking::add_electables(bounded_vec![6, 7, 8, 9, 10]).is_err()); + assert_eq!( + ElectableStashes::::get().into_inner().into_iter().collect::>(), + vec![1, 2, 3, 4, 6] + ); + }) + } + #[test] fn multi_page_target_snapshot_works() { ExtBuilder::default().nominate(true).build_and_execute(|| { From b49684e1acaa9765306239964ad876f64da4495b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Fri, 1 Nov 2024 13:30:25 +0100 Subject: [PATCH 027/169] Adds staking pallet rustdocs; nits --- substrate/frame/staking/src/lib.rs | 34 ++++++++++++++++++--- substrate/frame/staking/src/pallet/impls.rs | 19 +++++++----- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index 890007ec92ba8..1923dda09e8cc 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -143,6 +143,34 @@ //! The pallet implement the trait `SessionManager`. Which is the only API to query new validator //! set and allowing these validator set to be rewarded once their era is ended. //! +//! ## Multi-page election support +//! +//! > Unless explicitly stated on the contrary, one page is the equivalent of one block. "Pages" and +//! "blocks" are used interchangibly across the documentation. +//! +//! The pallet supports a multi-page election. In a multi-block election, some key parts of the +//! staking pallet progress over multi pages. Most notably: +//! 1. **Snapshot creation**: Both the voter and target snapshots are created over multi blocks. The +//! [`frame_election_provider_support::ElectionDataProvider`] trait supports that functionality +//! by parameterizing the electin voters and electable targets by the page index. +//! 2. **Election**: The election is multi-block, where a set of supports is fetched per page/block. +//! This pallet keeps track of the elected stashes and their exposures as the paged election is +//! called. The [`frame_election_provider_support::ElectionProvider`] trait supports this +//! functionaluty by parameterizing the elect call with the page index. +//! +//! ### Prepare an election ahead of time with `on_initialize` +//! +//! This pallet is expected to have a set of winners ready and their exposures collected and stored +//! at the time of a predicted election. In order to ensure that, it starts to fetch the paged +//! results of an election from the [`frame_election_provider_support::ElectionProvider`] `N` pages +//! ahead of the next election prediction. +//! +//! As the pages of winners are fetched, their exposures and era info are processed and stored so +//! that all the data is ready at the time of the next election. +//! +//! Even though this pallet supports mulit-page elections, it also can be used in a single page +//! context provided that the configs are set accordingly. +//! //! ## Interface //! //! ### Dispatchable Functions @@ -352,11 +380,7 @@ macro_rules! log { /// config. pub type MaxWinnersOf = ::MaxValidatorSet; -/// Maximum number of exposures that can fit into an exposure page, as defined by this pallet's -/// config. -/// TODO: needed? maybe use the type directly. -pub type MaxExposuresPerPageOf = ::MaxExposurePageSize; - +/// Alias for the maximum number of winners per page, as expected by the election provider. pub type MaxWinnersPerPageOf

=

::MaxWinnersPerPage; /// Maximum number of nominations per nominator. diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index d889da7cbca24..af8a7d12590dd 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -837,15 +837,14 @@ impl Pallet { stashes: BoundedVec>, ) -> Result<(), ()> { let mut storage_stashes = ElectableStashes::::get(); - for stash in stashes.into_iter() { - storage_stashes.try_insert(stash).map_err(|_| { - // add as many stashes as possible. - ElectableStashes::::set(storage_stashes.clone()); - () - })?; - } + for stash in stashes.into_iter() { + storage_stashes.try_insert(stash).map_err(|_| { + // add as many stashes as possible before returning err. + ElectableStashes::::set(storage_stashes.clone()); + })?; + } - ElectableStashes::::set(storage_stashes); + ElectableStashes::::set(storage_stashes); Ok(()) } @@ -1312,6 +1311,10 @@ impl Pallet { } impl LockableElectionDataProvider for Pallet { + // TODO: currently, setting the lock in the election data provider is a noop. Implement the + // logic that freezes and/or buffers the mutations to ledgers while the lock is set *before* + // the multi-page election is enabled. + // Tracking issue . fn set_lock() -> data_provider::Result<()> { match ElectionDataLock::::get() { Some(_) => Err("lock already set"), From 7851c6e7da7f3628969fd5a1db733fbbee5a9503 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 5 Nov 2024 15:00:39 +0100 Subject: [PATCH 028/169] Refactors the snapshot cursors of staking pallet and adds more tests --- substrate/frame/staking/src/lib.rs | 6 +- substrate/frame/staking/src/pallet/impls.rs | 128 +++++++------ substrate/frame/staking/src/tests.rs | 190 +++++++++++++------- 3 files changed, 204 insertions(+), 120 deletions(-) diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index 1923dda09e8cc..fa2a7564ebd14 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -148,8 +148,8 @@ //! > Unless explicitly stated on the contrary, one page is the equivalent of one block. "Pages" and //! "blocks" are used interchangibly across the documentation. //! -//! The pallet supports a multi-page election. In a multi-block election, some key parts of the -//! staking pallet progress over multi pages. Most notably: +//! The pallet supports a multi-page election. In a multi-page election, some key actions of the +//! staking pallet progress over multi pages/blocks. Most notably: //! 1. **Snapshot creation**: Both the voter and target snapshots are created over multi blocks. The //! [`frame_election_provider_support::ElectionDataProvider`] trait supports that functionality //! by parameterizing the electin voters and electable targets by the page index. @@ -482,7 +482,7 @@ pub struct UnlockChunk { /// Status of a paged snapshot progress. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub enum SnapshotStatus { - /// Paged snapshot is in progress, the `AccountId` was the last staker iterated. + /// Paged snapshot is in progress, the `AccountId` was the last staker iterated in the list. Ongoing(AccountId), /// All the stakers in the system have been consumed since the snapshot started. Consumed, diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index af8a7d12590dd..090c521ddefb2 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -952,18 +952,19 @@ impl Pallet { SlashRewardFraction::::put(fraction); } - /// Get all of the voters that are eligible for the npos election. + /// Get all the voters associated with `page` that are eligible for the npos election. /// - /// `maybe_max_len` can imposes a cap on the number of voters returned; + /// `maybe_max_len` can impose a cap on the number of voters returned per page. /// /// Sets `MinimumActiveStake` to the minimum active nominator stake in the returned set of /// nominators. /// + /// Note: in the context of the multi-page snapshot, we expect the *order* of `VoterList` and + /// `TargetList` not to change while the pages are being processed. This should be ensured by + /// the pallet, (e.g. using [`frame_election_provider_support::LockableElectionDataProvider`]). + /// /// This function is self-weighing as [`DispatchClass::Mandatory`]. - pub fn get_npos_voters( - bounds: DataProviderBounds, - remaining_pages: PageIndex, - ) -> Vec> { + pub fn get_npos_voters(bounds: DataProviderBounds, page: PageIndex) -> Vec> { let mut voters_size_tracker: StaticTracker = StaticTracker::default(); let final_predicted_len = { @@ -982,13 +983,13 @@ impl Pallet { let mut min_active_stake = u64::MAX; let mut sorted_voters = match VoterSnapshotStatus::::get() { - // snapshot continues, start from last iterated voter in the list. - SnapshotStatus::Ongoing(start_at) => - T::VoterList::iter_from(&start_at).unwrap_or_else(|_| T::TargetList::iter()), - // all the voters have been consumed, return an empty iterator. - SnapshotStatus::Consumed => Box::new(vec![].into_iter()), - // start the snapshot processing, start from the beginning. + // start the snapshot procssing from the beginning. SnapshotStatus::Waiting => T::VoterList::iter(), + // snapshot continues, start from the last iterated voter in the list. + SnapshotStatus::Ongoing(account_id) => T::VoterList::iter_from(&account_id) + .defensive_unwrap_or(Box::new(vec![].into_iter())), + // all voters have been consumed already, return an empty iterator. + SnapshotStatus::Consumed => Box::new(vec![].into_iter()), }; while all_voters.len() < final_predicted_len as usize && @@ -1063,20 +1064,30 @@ impl Pallet { } } - match (remaining_pages, VoterSnapshotStatus::::get()) { - // last page requested, reset. - (0, _) => VoterSnapshotStatus::::set(SnapshotStatus::Waiting), - // all voters have been consumed, do nothing. - (_, SnapshotStatus::Consumed) => {}, - (_, SnapshotStatus::Waiting) | (_, SnapshotStatus::Ongoing(_)) => { - if let Some(last) = all_voters.last().map(|(x, _, _)| x).cloned() { - VoterSnapshotStatus::::set(SnapshotStatus::Ongoing(last)); - } else { - // no more to consume, next pages will be empty. - VoterSnapshotStatus::::set(SnapshotStatus::Consumed); - } - }, - }; + // update the voter snapshot status. + VoterSnapshotStatus::::mutate(|status| { + match (page, status.clone()) { + // last page, reset status for next round. + (0, _) => *status = SnapshotStatus::Waiting, + + (_, SnapshotStatus::Waiting) | (_, SnapshotStatus::Ongoing(_)) => { + let maybe_last = all_voters.last().map(|(x, _, _)| x).cloned(); + + if let Some(ref last) = maybe_last { + if maybe_last == T::VoterList::iter().last() { + // all voters in the voter list have been consumed. + *status = SnapshotStatus::Consumed; + } else { + *status = SnapshotStatus::Ongoing(last.clone()); + } + } else { + debug_assert!(*status == SnapshotStatus::Consumed); + } + }, + // do nothing. + (_, SnapshotStatus::Consumed) => (), + } + }); // all_voters should have not re-allocated. debug_assert!(all_voters.capacity() == final_predicted_len as usize); @@ -1099,13 +1110,14 @@ impl Pallet { all_voters } - /// Get the targets for an upcoming npos election. + /// Get all the targets associated with `page` that are eligible for the npos election. + /// + /// Note: in the context of the multi-page snapshot, we expect the *order* of `VoterList` and + /// `TargetList` not to change while the pages are being processed. This should be ensured by + /// the pallet, (e.g. using [`frame_election_provider_support::LockableElectionDataProvider`]). /// /// This function is self-weighing as [`DispatchClass::Mandatory`]. - pub fn get_npos_targets( - bounds: DataProviderBounds, - remaining_pages: PageIndex, - ) -> Vec { + pub fn get_npos_targets(bounds: DataProviderBounds, page: PageIndex) -> Vec { let mut targets_size_tracker: StaticTracker = StaticTracker::default(); let final_predicted_len = { @@ -1115,15 +1127,16 @@ impl Pallet { let mut all_targets = Vec::::with_capacity(final_predicted_len as usize); let mut targets_seen = 0; + let mut targets_taken = 0u32; let mut targets_iter = match TargetSnapshotStatus::::get() { + // start the snapshot processing, start from the beginning. + SnapshotStatus::Waiting => T::TargetList::iter(), // snapshot continues, start from last iterated target in the list. SnapshotStatus::Ongoing(start_at) => - T::TargetList::iter_from(&start_at).unwrap_or_else(|_| T::TargetList::iter()), + T::TargetList::iter_from(&start_at).unwrap_or(Box::new(vec![].into_iter())), // all the targets have been consumed, return an empty iterator. SnapshotStatus::Consumed => Box::new(vec![].into_iter()), - // start the snapshot processing, start from the beginning. - SnapshotStatus::Waiting => T::TargetList::iter(), }; while all_targets.len() < final_predicted_len as usize && @@ -1147,23 +1160,34 @@ impl Pallet { if Validators::::contains_key(&target) { all_targets.push(target); + targets_taken.saturating_inc(); } } - match (remaining_pages, TargetSnapshotStatus::::get()) { - // last page requested, reset. - (0, _) => TargetSnapshotStatus::::set(SnapshotStatus::Waiting), - // all targets have been consumed, do nothing. - (_, SnapshotStatus::Consumed) => {}, - (_, SnapshotStatus::Waiting) | (_, SnapshotStatus::Ongoing(_)) => { - if let Some(last) = all_targets.last().cloned() { - TargetSnapshotStatus::::set(SnapshotStatus::Ongoing(last)); - } else { - // no more to consume, next pages will be empty. - TargetSnapshotStatus::::set(SnapshotStatus::Consumed); - } - }, - }; + // update the target snapshot status. + TargetSnapshotStatus::::mutate(|status| { + match (page, status.clone()) { + // last page, reset status for next round. + (0, _) => *status = SnapshotStatus::Waiting, + + (_, SnapshotStatus::Waiting) | (_, SnapshotStatus::Ongoing(_)) => { + let maybe_last = all_targets.last().map(|x| x).cloned(); + + if let Some(ref last) = maybe_last { + if maybe_last == T::TargetList::iter().last() { + // all targets in the target list have been consumed. + *status = SnapshotStatus::Consumed; + } else { + *status = SnapshotStatus::Ongoing(last.clone()); + } + } else { + debug_assert!(*status == SnapshotStatus::Consumed); + } + }, + // do nothing. + (_, SnapshotStatus::Consumed) => (), + } + }); Self::register_weight(T::WeightInfo::get_npos_targets(all_targets.len() as u32)); log!(info, "generated {} npos targets", all_targets.len()); @@ -1342,9 +1366,9 @@ impl ElectionDataProvider for Pallet { fn electing_voters( bounds: DataProviderBounds, - remaining_pages: PageIndex, + page: PageIndex, ) -> data_provider::Result>> { - let voters = Self::get_npos_voters(bounds, remaining_pages); + let voters = Self::get_npos_voters(bounds, page); debug_assert!(!bounds.exhausted( SizeBound(voters.encoded_size() as u32).into(), @@ -1356,9 +1380,9 @@ impl ElectionDataProvider for Pallet { fn electable_targets( bounds: DataProviderBounds, - remaining: PageIndex, + page: PageIndex, ) -> data_provider::Result> { - let targets = Self::get_npos_targets(bounds, remaining); + let targets = Self::get_npos_targets(bounds, page); // We can't handle this case yet -- return an error. WIP to improve handling this case in // . if bounds.exhausted(None, CountBound(targets.len() as u32).into()) { diff --git a/substrate/frame/staking/src/tests.rs b/substrate/frame/staking/src/tests.rs index 7b7f420e70456..572c1734d5033 100644 --- a/substrate/frame/staking/src/tests.rs +++ b/substrate/frame/staking/src/tests.rs @@ -8406,82 +8406,142 @@ pub mod multi_page_staking { #[test] fn multi_page_target_snapshot_works() { - ExtBuilder::default().nominate(true).build_and_execute(|| { - let bounds = ElectionBoundsBuilder::default().targets_count(2.into()).build().targets; + ExtBuilder::default() + .nominate(true) + .set_status(41, StakerStatus::Validator) + .set_status(51, StakerStatus::Validator) + .set_status(101, StakerStatus::Idle) + .build_and_execute(|| { + // all registered validators. + assert_eq!( + ::TargetList::iter().collect::>(), + vec![51, 31, 41, 21, 11] + ); - // fetch from page 3 to 0. - assert_eq!( - ::electable_targets(bounds, 3).unwrap(), - vec![31, 21] - ); - assert_eq!( - ::electable_targets(bounds, 2).unwrap(), - vec![11] - ); - // all targets consumed now, thus remaining calls are empty vecs. - assert!(::electable_targets(bounds, 1) - .unwrap() - .is_empty()); - assert_eq!(TargetSnapshotStatus::::get(), SnapshotStatus::Consumed); - - assert!(::electable_targets(bounds, 0) - .unwrap() - .is_empty()); - - // once we reach page 0, the status reset. - assert_eq!(TargetSnapshotStatus::::get(), SnapshotStatus::Waiting); - // and requesting a nsew snapshot can restart - assert_eq!( - ::electable_targets(bounds, 1).unwrap(), - vec![31, 21] - ); - }) + // 2 targets per page. + let bounds = + ElectionBoundsBuilder::default().targets_count(2.into()).build().targets; + + let mut all_targets = vec![]; + + let targets_page_3 = + ::electable_targets(bounds, 3).unwrap(); + all_targets.extend(targets_page_3.clone()); + + // result is expected: 2 most significant targets in the target list. + assert_eq!(targets_page_3, vec![51, 31]); + + // target snapshot status updated as expecteed: snapshot is ongoing, last target ID + // returned was 31 as there has been 2 targets comndumed already. + assert_eq!(TargetSnapshotStatus::::get(), SnapshotStatus::Ongoing(31)); + + let targets_page_2 = + ::electable_targets(bounds, 2).unwrap(); + all_targets.extend(targets_page_2.clone()); + + assert_eq!(targets_page_2, vec![41, 21]); + // 4 targets consumed since the beginning. + assert_eq!(TargetSnapshotStatus::::get(), SnapshotStatus::Ongoing(21)); + + let targets_page_1 = + ::electable_targets(bounds, 1).unwrap(); + all_targets.extend(targets_page_1.clone()); + + // did not fullfill the bounds because there were not enough targets in the list. + assert_eq!(targets_page_1, vec![11]); + + // all targets have been consumed in the list. + assert_eq!(TargetSnapshotStatus::::get(), SnapshotStatus::Consumed); + + // all targets in the list have been consumed, it should return an empty set of + // targets. + assert!(::electable_targets(bounds, 0) + .unwrap() + .is_empty()); + + // all pages have been requested, thus in waiting status, prepared for + // electable targets requests for a new snapshot. + assert_eq!(TargetSnapshotStatus::::get(), SnapshotStatus::Waiting); + + // now request 1 page with bounds where all registerd targets fit. u32::MAX + // emulates a no bounds request. + let bounds = + ElectionBoundsBuilder::default().targets_count(u32::MAX.into()).build().targets; + + let single_page_targets = + ::electable_targets(bounds, 0).unwrap(); + + // complete set of paged targets is the same as single page, no bounds set of + // targets. + assert_eq!(all_targets, single_page_targets); + }) } #[test] fn multi_page_voter_snapshot_works() { - ExtBuilder::default().nominate(true).build_and_execute(|| { - let bounds = ElectionBoundsBuilder::default().voters_count(3.into()).build().voters; + ExtBuilder::default() + .nominate(true) + .set_status(51, StakerStatus::Validator) + .set_status(41, StakerStatus::Nominator(vec![51])) + .set_status(101, StakerStatus::Validator) + .build_and_execute(|| { + let bounds = ElectionBoundsBuilder::default().voters_count(3.into()).build().voters; - // fetch from page 3 to 0. - assert_eq!( - ::electing_voters(bounds, 3) + assert_eq!( + ::VoterList::iter().collect::>(), + vec![11, 21, 31, 41, 51, 101], + ); + + let mut all_voters = vec![]; + + let voters_page_3 = ::electing_voters(bounds, 3) .unwrap() - .iter() - .map(|(x, _, _)| *x) - .collect::>(), - vec![11, 21, 31] - ); - assert_eq!( - ::electing_voters(bounds, 2) + .into_iter() + .map(|(a, _, _)| a) + .collect::>(); + all_voters.extend(voters_page_3.clone()); + + assert_eq!(voters_page_3, vec![11, 21, 31]); + + let voters_page_2 = ::electing_voters(bounds, 2) .unwrap() - .iter() - .map(|(x, _, _)| *x) - .collect::>(), - vec![101] - ); - // all voters consumed now, thus remaining calls are empty vecs. - assert!(::electing_voters(bounds, 1) - .unwrap() - .is_empty()); - assert_eq!(VoterSnapshotStatus::::get(), SnapshotStatus::Consumed); + .into_iter() + .map(|(a, _, _)| a) + .collect::>(); + all_voters.extend(voters_page_2.clone()); - assert!(::electing_voters(bounds, 0) - .unwrap() - .is_empty()); + assert_eq!(voters_page_2, vec![41, 51, 101]); - // once we reach page 0, the status reset. - assert_eq!(VoterSnapshotStatus::::get(), SnapshotStatus::Waiting); - // and requesting a nsew snapshot can restart - assert_eq!( - ::electing_voters(bounds, 1) + // all voters in the list have been consumed. + assert_eq!(VoterSnapshotStatus::::get(), SnapshotStatus::Consumed); + + // thus page 1 and 0 are empty. + assert!(::electing_voters(bounds, 1) .unwrap() - .iter() - .map(|(x, _, _)| *x) - .collect::>(), - vec![11, 21, 31] - ); - }) + .is_empty()); + assert!(::electing_voters(bounds, 0) + .unwrap() + .is_empty()); + + // last page has been requested, reset the snapshot status to waiting. + assert_eq!(VoterSnapshotStatus::::get(), SnapshotStatus::Waiting); + + // now request 1 page with bounds where all registerd voters fit. u32::MAX + // emulates a no bounds request. + let bounds = + ElectionBoundsBuilder::default().voters_count(u32::MAX.into()).build().targets; + + let single_page_voters = + ::electing_voters(bounds, 0) + .unwrap() + .into_iter() + .map(|(a, _, _)| a) + .collect::>(); + + // complete set of paged voters is the same as single page, no bounds set of + // voters. + assert_eq!(all_voters, single_page_voters); + }) } #[test] From 3ff9914932d1c079dd9b6903bd0dbea38190dc70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Thu, 7 Nov 2024 12:25:46 +0100 Subject: [PATCH 029/169] Adds e2e tests to staking pallet --- substrate/frame/staking/src/lib.rs | 2 + substrate/frame/staking/src/mock.rs | 90 ++- substrate/frame/staking/src/pallet/impls.rs | 7 + substrate/frame/staking/src/pallet/mod.rs | 6 +- substrate/frame/staking/src/tests.rs | 317 --------- .../frame/staking/src/tests_paged_election.rs | 620 ++++++++++++++++++ 6 files changed, 713 insertions(+), 329 deletions(-) create mode 100644 substrate/frame/staking/src/tests_paged_election.rs diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index fa2a7564ebd14..74ced178ad6c6 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -322,6 +322,8 @@ pub mod testing_utils; pub(crate) mod mock; #[cfg(test)] mod tests; +#[cfg(test)] +mod tests_paged_election; pub mod asset; pub mod election_size_tracker; diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index 67540c25f7bdc..7ca5f4b6453c7 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -20,8 +20,8 @@ use crate::{self as pallet_staking, *}; use frame_election_provider_support::{ bounds::{ElectionBounds, ElectionBoundsBuilder}, - onchain, BoundedSupports, ElectionProvider, SequentialPhragmen, Support, - TryIntoBoundedSupports, VoteWeight, + onchain, BoundedSupports, BoundedSupportsOf, ElectionProvider, PageIndex, SequentialPhragmen, + Support, TryIntoBoundedSupports, VoteWeight, }; use frame_support::{ assert_ok, derive_impl, ord_parameter_types, parameter_types, @@ -36,7 +36,7 @@ use sp_io; use sp_runtime::{curve::PiecewiseLinear, testing::UintAuthorityId, traits::Zero, BuildStorage}; use sp_staking::{ offence::{OffenceDetails, OnOffenceHandler}, - OnStakingUpdate, + OnStakingUpdate, StakingInterface, }; pub(crate) const INIT_TIMESTAMP: u64 = 30_000; @@ -209,7 +209,7 @@ parameter_types! { pub static MaxValidatorSet: u32 = 100; pub static ElectionsBounds: ElectionBounds = ElectionBoundsBuilder::default().build(); pub static AbsoluteMaxNominations: u32 = 16; - pub static MaxWinnersPerPage: u32 = u32::MAX; + pub static MaxWinnersPerPage: u32 = 10_000; } type VoterBagsListInstance = pallet_bags_list::Instance1; @@ -222,6 +222,67 @@ impl pallet_bags_list::Config for Test { type Score = VoteWeight; } +// multi-page types and controller. +parameter_types! { + // default is single page EP. + pub static Pages: PageIndex = 1; + pub static MaxBackersPerWinner: u32 = 10_000; +} + +// An election provider wrapper that allows testing with single and multi page modes. +pub struct SingleOrMultipageElectionProvider(core::marker::PhantomData); +impl< + // single page EP. + SP: ElectionProvider< + AccountId = AccountId, + MaxWinnersPerPage = MaxWinnersPerPage, + MaxBackersPerWinner = ConstU32<{ u32::MAX }>, + Error = onchain::Error, + >, + > ElectionProvider for SingleOrMultipageElectionProvider +{ + type AccountId = AccountId; + type BlockNumber = BlockNumber; + type MaxWinnersPerPage = MaxWinnersPerPage; + type MaxBackersPerWinner = ConstU32<{ u32::MAX }>; + type Pages = Pages; + type DataProvider = Staking; + type Error = onchain::Error; + + fn elect(page: PageIndex) -> Result, Self::Error> { + if Pages::get() == 1 { + SP::elect(page) + } else { + // will take first `MaxWinnersPerPage` in the validator set as winners. in this mock + // impl, we return a random nominator exposure per winner/page. + let supports: Vec<(AccountId, Support)> = Validators::::iter_keys() + .filter(|x| Staking::status(x) == Ok(StakerStatus::Validator)) + .take(Self::MaxWinnersPerPage::get() as usize) + .map(|v| { + ( + v, + Support { + total: (100 + page).into(), + voters: vec![(page as AccountId, (100 + page).into())], + }, + ) + }) + .collect::>(); + + Ok(to_bounded_supports(supports)) + } + } + fn msp() -> PageIndex { + SP::msp() + } + fn lsp() -> PageIndex { + SP::lsp() + } + fn ongoing() -> bool { + SP::ongoing() + } +} + pub struct OnChainSeqPhragmen; impl onchain::Config for OnChainSeqPhragmen { type System = Test; @@ -279,8 +340,9 @@ impl crate::pallet::pallet::Config for Test { type NextNewSession = Session; type MaxExposurePageSize = MaxExposurePageSize; type MaxValidatorSet = MaxValidatorSet; - type ElectionProvider = onchain::OnChainExecution; - type GenesisElectionProvider = Self::ElectionProvider; + type ElectionProvider = + SingleOrMultipageElectionProvider>; + type GenesisElectionProvider = onchain::OnChainExecution; // NOTE: consider a macro and use `UseNominatorsAndValidatorsMap` as well. type VoterList = VoterBagsList; type TargetList = UseValidatorsMap; @@ -432,8 +494,12 @@ impl ExtBuilder { self.balance_factor = factor; self } - pub fn max_validator_set(self, max: u32) -> Self { - MaxValidatorSet::set(max); + pub fn multi_page_election_provider(self, pages: PageIndex) -> Self { + Pages::set(pages); + self + } + pub fn max_winners_per_page(self, max: u32) -> Self { + MaxWinnersPerPage::set(max); self } pub fn try_state(self, enable: bool) -> Self { @@ -474,6 +540,7 @@ impl ExtBuilder { (71, self.balance_factor * 2000), (80, self.balance_factor), (81, self.balance_factor * 2000), + (91, self.balance_factor * 2000), // This allows us to have a total_payout different from 0. (999, 1_000_000_000_000), ], @@ -727,6 +794,13 @@ pub(crate) fn validator_controllers() -> Vec { .collect() } +pub(crate) fn era_exposures(era: u32) -> Vec<(AccountId, Exposure)> { + validator_controllers() + .into_iter() + .map(|v| (v, Staking::eras_stakers(era, &v))) + .collect::>() +} + pub(crate) fn on_offence_in_era( offenders: &[OffenceDetails< AccountId, diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index 090c521ddefb2..d062537e2fe18 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -75,6 +75,11 @@ use sp_runtime::TryRuntimeError; const NPOS_MAX_ITERATIONS_COEFFICIENT: u32 = 2; impl Pallet { + /// Fetches the number of pages configured by the election provider. + pub fn election_pages() -> u32 { + <::ElectionProvider as ElectionProvider>::Pages::get() + } + /// Fetches the ledger associated with a controller or stash account, if any. pub fn ledger(account: StakingAccount) -> Result, Error> { StakingLedger::::get(account) @@ -628,6 +633,8 @@ impl Pallet { if let Some(old_era) = new_planned_era.checked_sub(T::HistoryDepth::get() + 1) { Self::clear_era_information(old_era); } + // Including electing targets of previous era. + ElectingStartedAt::::kill(); } /// Potentially plan a new era. diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index 54a6983573945..0d32370e172a7 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -1026,8 +1026,7 @@ pub mod pallet { /// that the `ElectableStashes` has been populated with all validators from all pages at /// the time of the election. fn on_initialize(now: BlockNumberFor) -> Weight { - let pages: BlockNumberFor = - <::ElectionProvider as ElectionProvider>::Pages::get().into(); + let pages: BlockNumberFor = Self::election_pages().into(); // election ongoing, fetch the next page. if let Some(started_at) = ElectingStartedAt::::get() { @@ -1040,8 +1039,6 @@ pub mod pallet { if next_page == Zero::zero() { crate::log!(trace, "elect(): finished fetching all paged solutions."); Self::do_elect_paged(Zero::zero()); - - ElectingStartedAt::::kill(); } else { crate::log!(trace, "elect(): progressing, {:?} remaining pages.", next_page); Self::do_elect_paged(next_page.saturated_into::()); @@ -1057,6 +1054,7 @@ pub mod pallet { pages ); + ElectingStartedAt::::set(Some(now)); Self::do_elect_paged(pages.saturated_into::().saturating_sub(1)); } }; diff --git a/substrate/frame/staking/src/tests.rs b/substrate/frame/staking/src/tests.rs index 572c1734d5033..3398161f42aeb 100644 --- a/substrate/frame/staking/src/tests.rs +++ b/substrate/frame/staking/src/tests.rs @@ -8369,320 +8369,3 @@ mod byzantine_threshold_disabling_strategy { }); } } - -pub mod multi_page_staking { - use super::*; - use frame_election_provider_support::ElectionDataProvider; - - #[test] - fn add_electable_stashes_work() { - ExtBuilder::default().max_validator_set(5).build_and_execute(|| { - assert_eq!(MaxValidatorSet::get(), 5); - assert!(ElectableStashes::::get().is_empty()); - - // adds stashes without duplicates, do not overflow bounds. - assert_ok!(Staking::add_electables(bounded_vec![1, 2, 3])); - assert_eq!( - ElectableStashes::::get().into_inner().into_iter().collect::>(), - vec![1, 2, 3] - ); - - // adds with duplicates which are deduplicated implicitly, no not overflow bounds. - assert_ok!(Staking::add_electables(bounded_vec![1, 2, 4])); - assert_eq!( - ElectableStashes::::get().into_inner().into_iter().collect::>(), - vec![1, 2, 3, 4] - ); - - // adds stashes so that bounds are overflown, fails and internal state changes so that - // all slots are filled. - assert!(Staking::add_electables(bounded_vec![6, 7, 8, 9, 10]).is_err()); - assert_eq!( - ElectableStashes::::get().into_inner().into_iter().collect::>(), - vec![1, 2, 3, 4, 6] - ); - }) - } - - #[test] - fn multi_page_target_snapshot_works() { - ExtBuilder::default() - .nominate(true) - .set_status(41, StakerStatus::Validator) - .set_status(51, StakerStatus::Validator) - .set_status(101, StakerStatus::Idle) - .build_and_execute(|| { - // all registered validators. - assert_eq!( - ::TargetList::iter().collect::>(), - vec![51, 31, 41, 21, 11] - ); - - // 2 targets per page. - let bounds = - ElectionBoundsBuilder::default().targets_count(2.into()).build().targets; - - let mut all_targets = vec![]; - - let targets_page_3 = - ::electable_targets(bounds, 3).unwrap(); - all_targets.extend(targets_page_3.clone()); - - // result is expected: 2 most significant targets in the target list. - assert_eq!(targets_page_3, vec![51, 31]); - - // target snapshot status updated as expecteed: snapshot is ongoing, last target ID - // returned was 31 as there has been 2 targets comndumed already. - assert_eq!(TargetSnapshotStatus::::get(), SnapshotStatus::Ongoing(31)); - - let targets_page_2 = - ::electable_targets(bounds, 2).unwrap(); - all_targets.extend(targets_page_2.clone()); - - assert_eq!(targets_page_2, vec![41, 21]); - // 4 targets consumed since the beginning. - assert_eq!(TargetSnapshotStatus::::get(), SnapshotStatus::Ongoing(21)); - - let targets_page_1 = - ::electable_targets(bounds, 1).unwrap(); - all_targets.extend(targets_page_1.clone()); - - // did not fullfill the bounds because there were not enough targets in the list. - assert_eq!(targets_page_1, vec![11]); - - // all targets have been consumed in the list. - assert_eq!(TargetSnapshotStatus::::get(), SnapshotStatus::Consumed); - - // all targets in the list have been consumed, it should return an empty set of - // targets. - assert!(::electable_targets(bounds, 0) - .unwrap() - .is_empty()); - - // all pages have been requested, thus in waiting status, prepared for - // electable targets requests for a new snapshot. - assert_eq!(TargetSnapshotStatus::::get(), SnapshotStatus::Waiting); - - // now request 1 page with bounds where all registerd targets fit. u32::MAX - // emulates a no bounds request. - let bounds = - ElectionBoundsBuilder::default().targets_count(u32::MAX.into()).build().targets; - - let single_page_targets = - ::electable_targets(bounds, 0).unwrap(); - - // complete set of paged targets is the same as single page, no bounds set of - // targets. - assert_eq!(all_targets, single_page_targets); - }) - } - - #[test] - fn multi_page_voter_snapshot_works() { - ExtBuilder::default() - .nominate(true) - .set_status(51, StakerStatus::Validator) - .set_status(41, StakerStatus::Nominator(vec![51])) - .set_status(101, StakerStatus::Validator) - .build_and_execute(|| { - let bounds = ElectionBoundsBuilder::default().voters_count(3.into()).build().voters; - - assert_eq!( - ::VoterList::iter().collect::>(), - vec![11, 21, 31, 41, 51, 101], - ); - - let mut all_voters = vec![]; - - let voters_page_3 = ::electing_voters(bounds, 3) - .unwrap() - .into_iter() - .map(|(a, _, _)| a) - .collect::>(); - all_voters.extend(voters_page_3.clone()); - - assert_eq!(voters_page_3, vec![11, 21, 31]); - - let voters_page_2 = ::electing_voters(bounds, 2) - .unwrap() - .into_iter() - .map(|(a, _, _)| a) - .collect::>(); - all_voters.extend(voters_page_2.clone()); - - assert_eq!(voters_page_2, vec![41, 51, 101]); - - // all voters in the list have been consumed. - assert_eq!(VoterSnapshotStatus::::get(), SnapshotStatus::Consumed); - - // thus page 1 and 0 are empty. - assert!(::electing_voters(bounds, 1) - .unwrap() - .is_empty()); - assert!(::electing_voters(bounds, 0) - .unwrap() - .is_empty()); - - // last page has been requested, reset the snapshot status to waiting. - assert_eq!(VoterSnapshotStatus::::get(), SnapshotStatus::Waiting); - - // now request 1 page with bounds where all registerd voters fit. u32::MAX - // emulates a no bounds request. - let bounds = - ElectionBoundsBuilder::default().voters_count(u32::MAX.into()).build().targets; - - let single_page_voters = - ::electing_voters(bounds, 0) - .unwrap() - .into_iter() - .map(|(a, _, _)| a) - .collect::>(); - - // complete set of paged voters is the same as single page, no bounds set of - // voters. - assert_eq!(all_voters, single_page_voters); - }) - } - - #[test] - fn store_stakers_info_multi_page_elect_works() { - ExtBuilder::default().exposures_page_size(2).build_and_execute(|| { - assert_eq!(MaxExposurePageSize::get(), 2); - - let exposure_one = Exposure { - total: 1000 + 700, - own: 1000, - others: vec![ - IndividualExposure { who: 101, value: 500 }, - IndividualExposure { who: 102, value: 100 }, - IndividualExposure { who: 103, value: 100 }, - ], - }; - - let exposure_two = Exposure { - total: 1000 + 1000, - own: 1000, - others: vec![ - IndividualExposure { who: 104, value: 500 }, - IndividualExposure { who: 105, value: 500 }, - ], - }; - - let exposure_three = Exposure { - total: 1000 + 500, - own: 1000, - others: vec![ - IndividualExposure { who: 110, value: 250 }, - IndividualExposure { who: 111, value: 250 }, - ], - }; - - let exposures_page_one = bounded_vec![(1, exposure_one), (2, exposure_two),]; - let exposures_page_two = bounded_vec![(1, exposure_three),]; - - // stores exposure page with exposures of validator 1 and 2, returns exposed validator - // account id. - assert_eq!( - Pallet::::store_stakers_info(exposures_page_one, current_era()).to_vec(), - vec![1, 2] - ); - // Stakers overview OK for validator 1 and 2. - assert_eq!( - ErasStakersOverview::::get(0, &1).unwrap(), - PagedExposureMetadata { - total: 1700, - own: 1000, - nominator_count: 3, - page_count: 2, - last_page_empty_slots: 1, - }, - ); - assert_eq!( - ErasStakersOverview::::get(0, &2).unwrap(), - PagedExposureMetadata { - total: 2000, - own: 1000, - nominator_count: 2, - page_count: 1, - last_page_empty_slots: 0, - }, - ); - - // stores exposure page with exposures of validator 1, returns exposed validator - // account id. - assert_eq!( - Pallet::::store_stakers_info(exposures_page_two, current_era()).to_vec(), - vec![1] - ); - - // Stakers overview OK for validator 1. - assert_eq!( - ErasStakersOverview::::get(0, &1).unwrap(), - PagedExposureMetadata { - total: 2200, - own: 1000, - nominator_count: 5, - page_count: 3, - last_page_empty_slots: 1, - }, - ); - - // validator 1 has 3 paged exposures. - assert!( - ErasStakersPaged::::iter_prefix_values((0, &1)).count() as u32 == - EraInfo::::get_page_count(0, &1) && - EraInfo::::get_page_count(0, &1) == 3 - ); - assert!(ErasStakersPaged::::get((0, &1, 0)).is_some()); - assert!(ErasStakersPaged::::get((0, &1, 1)).is_some()); - assert!(ErasStakersPaged::::get((0, &1, 2)).is_some()); - assert!(ErasStakersPaged::::get((0, &1, 3)).is_none()); - - // validator 2 has 1 paged exposures. - assert!(ErasStakersPaged::::get((0, &2, 0)).is_some()); - assert!(ErasStakersPaged::::get((0, &2, 1)).is_none()); - assert_eq!(ErasStakersPaged::::iter_prefix_values((0, &2)).count(), 1); - - // exposures of validator 1 are the expected: - assert_eq!( - ErasStakersPaged::::get((0, &1, 0)).unwrap(), - ExposurePage { - page_total: 600, - others: vec![ - IndividualExposure { who: 101, value: 500 }, - IndividualExposure { who: 102, value: 100 } - ] - }, - ); - assert_eq!( - ErasStakersPaged::::get((0, &1, 1)).unwrap(), - ExposurePage { - page_total: 350, - others: vec![ - IndividualExposure { who: 103, value: 100 }, - IndividualExposure { who: 110, value: 250 } - ] - } - ); - assert_eq!( - ErasStakersPaged::::get((0, &1, 2)).unwrap(), - ExposurePage { - page_total: 250, - others: vec![IndividualExposure { who: 111, value: 250 }] - } - ); - - // exposures of validator 2. - assert_eq!( - ErasStakersPaged::::iter_prefix_values((0, &2)).collect::>(), - vec![ExposurePage { - page_total: 1000, - others: vec![ - IndividualExposure { who: 104, value: 500 }, - IndividualExposure { who: 105, value: 500 } - ] - }], - ); - }) - } -} diff --git a/substrate/frame/staking/src/tests_paged_election.rs b/substrate/frame/staking/src/tests_paged_election.rs new file mode 100644 index 0000000000000..064aabdd5d964 --- /dev/null +++ b/substrate/frame/staking/src/tests_paged_election.rs @@ -0,0 +1,620 @@ +// 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::{mock::*, *}; +use frame_support::{assert_ok, testing_prelude::*}; +use substrate_test_utils::assert_eq_uvec; + +use frame_election_provider_support::{ + bounds::ElectionBoundsBuilder, ElectionDataProvider, SortedListProvider, +}; +use sp_staking::StakingInterface; + +#[test] +fn add_electable_stashes_work() { + ExtBuilder::default().build_and_execute(|| { + MaxValidatorSet::set(5); + assert_eq!(MaxValidatorSet::get(), 5); + assert!(ElectableStashes::::get().is_empty()); + + // adds stashes without duplicates, do not overflow bounds. + assert_ok!(Staking::add_electables(bounded_vec![1, 2, 3])); + assert_eq!( + ElectableStashes::::get().into_inner().into_iter().collect::>(), + vec![1, 2, 3] + ); + + // adds with duplicates which are deduplicated implicitly, no not overflow bounds. + assert_ok!(Staking::add_electables(bounded_vec![1, 2, 4])); + assert_eq!( + ElectableStashes::::get().into_inner().into_iter().collect::>(), + vec![1, 2, 3, 4] + ); + + // adds stashes so that bounds are overflown, fails and internal state changes so that + // all slots are filled. + assert!(Staking::add_electables(bounded_vec![6, 7, 8, 9, 10]).is_err()); + assert_eq!( + ElectableStashes::::get().into_inner().into_iter().collect::>(), + vec![1, 2, 3, 4, 6] + ); + }) +} + +mod paged_on_initialize { + use super::*; + + #[test] + fn single_page_election_works() { + ExtBuilder::default() + // set desired targets to 3. + .validator_count(3) + .build_and_execute(|| { + // single page election provider. + assert_eq!( + <::ElectionProvider as ElectionProvider>::Pages::get(), + 1 + ); + + let next_election = ::next_election_prediction( + System::block_number(), + ); + + // single page. + let pages: BlockNumber = Staking::election_pages().into(); + assert_eq!(pages, 1); + + // genesis validators. + assert_eq!(current_era(), 0); + assert_eq_uvec!(Session::validators(), vec![11, 21, 31]); + + // force unstake of 31 to ensure the election results of the next era are + // different than genesis. + assert_ok!(Staking::force_unstake(RuntimeOrigin::root(), 31, 0)); + + let expected_elected = Validators::::iter_keys() + .filter(|x| Staking::status(x) == Ok(StakerStatus::Validator)) + .collect::>(); + // use all registered validators as potential targets. + ValidatorCount::::set(expected_elected.len() as u32); + assert_eq!(expected_elected.len(), 2); + + // 1. election prep hasn't started yet, election cursor and electable stashes are + // not + // set yet. + run_to_block(next_election - pages - 1); + assert_eq!(ElectingStartedAt::::get(), None); + assert!(ElectableStashes::::get().is_empty()); + + // 2. starts preparing election at the (election_prediction - n_pages) block. + run_to_block(next_election - pages); + + // electing started at cursor is set once the election starts to be prepared. + assert_eq!(ElectingStartedAt::::get(), Some(next_election - pages)); + // now the electable stashes have been fetched and stored. + assert_eq_uvec!( + ElectableStashes::::get().into_iter().collect::>(), + expected_elected + ); + + // era is still 0. + assert_eq!(current_era(), 0); + + // 3. progress to election block, which matches with era rotation. + run_to_block(next_election); + assert_eq!(current_era(), 1); + // clears out election metadata for era. + assert!(ElectingStartedAt::::get().is_none()); + assert!(ElectableStashes::::get().into_iter().collect::>().is_empty()); + + // era progresseed and electable stashes have been served to session pallet. + assert_eq_uvec!(Session::validators(), vec![11, 21, 31]); + + // 4. in the next era, the validator set does not include 31 anymore which was + // unstaked. + start_active_era(2); + assert_eq_uvec!(Session::validators(), vec![11, 21]); + }) + } + + #[test] + fn single_page_election_era_transition_exposures_work() { + ExtBuilder::default() + // set desired targets to 3. + .validator_count(3) + .build_and_execute(|| { + // single page election provider. + assert_eq!( + <::ElectionProvider as ElectionProvider>::Pages::get(), + 1 + ); + + assert_eq!(current_era(), 0); + + // 3 sessions per era. + assert_eq!(SessionsPerEra::get(), 3); + + // genesis validators and exposures. + assert_eq!(current_era(), 0); + assert_eq_uvec!(validator_controllers(), vec![11, 21, 31]); + assert_eq!( + era_exposures(current_era()), + vec![ + ( + 11, + Exposure { + total: 1125, + own: 1000, + others: vec![IndividualExposure { who: 101, value: 125 }] + } + ), + ( + 21, + Exposure { + total: 1375, + own: 1000, + others: vec![IndividualExposure { who: 101, value: 375 }] + } + ), + (31, Exposure { total: 500, own: 500, others: vec![] }) + ] + ); + + start_session(1); + assert_eq!(current_era(), 0); + // election haven't started yet. + assert_eq!(ElectingStartedAt::::get(), None); + assert!(ElectableStashes::::get().is_empty()); + + // progress to era rotation session. + start_session(SessionsPerEra::get()); + assert_eq!(current_era(), 1); + assert_eq_uvec!(Session::validators(), vec![11, 21, 31]); + assert_eq!( + era_exposures(current_era()), + vec![ + ( + 11, + Exposure { + total: 1125, + own: 1000, + others: vec![IndividualExposure { who: 101, value: 125 }] + } + ), + ( + 21, + Exposure { + total: 1375, + own: 1000, + others: vec![IndividualExposure { who: 101, value: 375 }] + } + ), + (31, Exposure { total: 500, own: 500, others: vec![] }) + ] + ); + + // force unstake validator 31 for next era. + assert_ok!(Staking::force_unstake(RuntimeOrigin::root(), 31, 0)); + + // progress session and rotate era. + start_session(SessionsPerEra::get() * 2); + assert_eq!(current_era(), 2); + assert_eq_uvec!(Session::validators(), vec![11, 21]); + + assert_eq!( + era_exposures(current_era()), + vec![ + ( + 11, + Exposure { + total: 1125, + own: 1000, + others: vec![IndividualExposure { who: 101, value: 125 }] + } + ), + ( + 21, + Exposure { + total: 1375, + own: 1000, + others: vec![IndividualExposure { who: 101, value: 375 }] + } + ), + ] + ); + }) + } + + #[test] + fn multi_page_election_works() { + ExtBuilder::default() + .add_staker(61, 61, 10, StakerStatus::Validator) + .add_staker(71, 71, 10, StakerStatus::Validator) + .add_staker(81, 81, 10, StakerStatus::Validator) + .add_staker(91, 91, 10, StakerStatus::Validator) + .multi_page_election_provider(3) + .max_winners_per_page(5) + .build_and_execute(|| { + // election provider has 3 pages. + let pages: BlockNumber = + <::ElectionProvider as ElectionProvider>::Pages::get().into(); + assert_eq!(pages, 3); + // 5 max winners per page. + let max_winners_page = <::ElectionProvider as ElectionProvider>::MaxWinnersPerPage::get(); + assert_eq!(max_winners_page, 5); + + // genesis era. + assert_eq!(current_era(), 0); + + // confirm the genesis validators. + assert_eq!(Session::validators(), vec![11, 21]); + + let next_election = ::next_election_prediction( + System::block_number(), + ); + assert_eq!(next_election, 10); + + let expected_elected = Validators::::iter_keys() + .filter(|x| Staking::status(x) == Ok(StakerStatus::Validator)) + // mock multi page election provider takes first `max_winners_page` + // validators as winners. + .take(max_winners_page as usize) + .collect::>(); + // adjust desired targets to number of winners per page. + ValidatorCount::::set(expected_elected.len() as u32); + assert_eq!(expected_elected.len(), 5); + + // 1. election prep hasn't started yet, election cursor and electable stashes are not + // set yet. + run_to_block(next_election - pages - 1); + assert_eq!(ElectingStartedAt::::get(), None); + assert!(ElectableStashes::::get().is_empty()); + + // 2. starts preparing election at the (election_prediction - n_pages) block. + // fetches msp (i.e. 2). + run_to_block(next_election - pages); + + // electing started at cursor is set once the election starts to be prepared. + assert_eq!(ElectingStartedAt::::get(), Some(next_election - pages)); + // now the electable stashes started to be fetched and stored. + assert_eq_uvec!( + ElectableStashes::::get().into_iter().collect::>(), + expected_elected + ); + + // 3. progress one block to fetch page 1. + run_to_block(System::block_number() + 1); + // the electable stashes remain the same. + assert_eq_uvec!( + ElectableStashes::::get().into_iter().collect::>(), + expected_elected + ); + // election cursor reamins unchanged during intermediate pages. + assert_eq!(ElectingStartedAt::::get(), Some(next_election - pages)); + + // 4. progress one block to fetch lsp (i.e. 0). + run_to_block(System::block_number() + 1); + // the electable stashes remain the same. + assert_eq_uvec!( + ElectableStashes::::get().into_iter().collect::>(), + expected_elected + ); + // upon fetchin page 0, the electing started at will remain in storage until the + // era rotates. + assert_eq!(current_era(), 0); + assert_eq!(ElectingStartedAt::::get(), Some(next_election - pages)); + + // 5. rotate era. + start_active_era(current_era() + 1); + // the new era validators are the expected elected stashes. + assert_eq_uvec!(Session::validators(), expected_elected); + // and all the metadata has been cleared up and ready for the next election. + assert!(ElectingStartedAt::::get().is_none()); + assert!(ElectableStashes::::get().is_empty()); + }) + } +} + +mod paged_snapshot { + use super::*; + + #[test] + fn target_snapshot_works() { + ExtBuilder::default() + .nominate(true) + .set_status(41, StakerStatus::Validator) + .set_status(51, StakerStatus::Validator) + .set_status(101, StakerStatus::Idle) + .build_and_execute(|| { + // all registered validators. + assert_eq!( + ::TargetList::iter().collect::>(), + vec![51, 31, 41, 21, 11] + ); + + // 2 targets per page. + let bounds = + ElectionBoundsBuilder::default().targets_count(2.into()).build().targets; + + let mut all_targets = vec![]; + + let targets_page_3 = + ::electable_targets(bounds, 3).unwrap(); + all_targets.extend(targets_page_3.clone()); + + // result is expected: 2 most significant targets in the target list. + assert_eq!(targets_page_3, vec![51, 31]); + + // target snapshot status updated as expecteed: snapshot is ongoing, last target ID + // returned was 31 as there has been 2 targets comndumed already. + assert_eq!(TargetSnapshotStatus::::get(), SnapshotStatus::Ongoing(31)); + + let targets_page_2 = + ::electable_targets(bounds, 2).unwrap(); + all_targets.extend(targets_page_2.clone()); + + assert_eq!(targets_page_2, vec![41, 21]); + // 4 targets consumed since the beginning. + assert_eq!(TargetSnapshotStatus::::get(), SnapshotStatus::Ongoing(21)); + + let targets_page_1 = + ::electable_targets(bounds, 1).unwrap(); + all_targets.extend(targets_page_1.clone()); + + // did not fullfill the bounds because there were not enough targets in the list. + assert_eq!(targets_page_1, vec![11]); + + // all targets have been consumed in the list. + assert_eq!(TargetSnapshotStatus::::get(), SnapshotStatus::Consumed); + + // all targets in the list have been consumed, it should return an empty set of + // targets. + assert!(::electable_targets(bounds, 0) + .unwrap() + .is_empty()); + + // all pages have been requested, thus in waiting status, prepared for + // electable targets requests for a new snapshot. + assert_eq!(TargetSnapshotStatus::::get(), SnapshotStatus::Waiting); + + // now request 1 page with bounds where all registerd targets fit. u32::MAX + // emulates a no bounds request. + let bounds = + ElectionBoundsBuilder::default().targets_count(u32::MAX.into()).build().targets; + + let single_page_targets = + ::electable_targets(bounds, 0).unwrap(); + + // complete set of paged targets is the same as single page, no bounds set of + // targets. + assert_eq!(all_targets, single_page_targets); + }) + } + + #[test] + fn voter_snapshot_works() { + ExtBuilder::default() + .nominate(true) + .set_status(51, StakerStatus::Validator) + .set_status(41, StakerStatus::Nominator(vec![51])) + .set_status(101, StakerStatus::Validator) + .build_and_execute(|| { + let bounds = ElectionBoundsBuilder::default().voters_count(3.into()).build().voters; + + assert_eq!( + ::VoterList::iter().collect::>(), + vec![11, 21, 31, 41, 51, 101], + ); + + let mut all_voters = vec![]; + + let voters_page_3 = ::electing_voters(bounds, 3) + .unwrap() + .into_iter() + .map(|(a, _, _)| a) + .collect::>(); + all_voters.extend(voters_page_3.clone()); + + assert_eq!(voters_page_3, vec![11, 21, 31]); + + let voters_page_2 = ::electing_voters(bounds, 2) + .unwrap() + .into_iter() + .map(|(a, _, _)| a) + .collect::>(); + all_voters.extend(voters_page_2.clone()); + + assert_eq!(voters_page_2, vec![41, 51, 101]); + + // all voters in the list have been consumed. + assert_eq!(VoterSnapshotStatus::::get(), SnapshotStatus::Consumed); + + // thus page 1 and 0 are empty. + assert!(::electing_voters(bounds, 1) + .unwrap() + .is_empty()); + assert!(::electing_voters(bounds, 0) + .unwrap() + .is_empty()); + + // last page has been requested, reset the snapshot status to waiting. + assert_eq!(VoterSnapshotStatus::::get(), SnapshotStatus::Waiting); + + // now request 1 page with bounds where all registerd voters fit. u32::MAX + // emulates a no bounds request. + let bounds = + ElectionBoundsBuilder::default().voters_count(u32::MAX.into()).build().targets; + + let single_page_voters = + ::electing_voters(bounds, 0) + .unwrap() + .into_iter() + .map(|(a, _, _)| a) + .collect::>(); + + // complete set of paged voters is the same as single page, no bounds set of + // voters. + assert_eq!(all_voters, single_page_voters); + }) + } +} + +mod paged_exposures { + use super::*; + + #[test] + fn store_stakers_info_elect_works() { + ExtBuilder::default().exposures_page_size(2).build_and_execute(|| { + assert_eq!(MaxExposurePageSize::get(), 2); + + let exposure_one = Exposure { + total: 1000 + 700, + own: 1000, + others: vec![ + IndividualExposure { who: 101, value: 500 }, + IndividualExposure { who: 102, value: 100 }, + IndividualExposure { who: 103, value: 100 }, + ], + }; + + let exposure_two = Exposure { + total: 1000 + 1000, + own: 1000, + others: vec![ + IndividualExposure { who: 104, value: 500 }, + IndividualExposure { who: 105, value: 500 }, + ], + }; + + let exposure_three = Exposure { + total: 1000 + 500, + own: 1000, + others: vec![ + IndividualExposure { who: 110, value: 250 }, + IndividualExposure { who: 111, value: 250 }, + ], + }; + + let exposures_page_one = bounded_vec![(1, exposure_one), (2, exposure_two),]; + let exposures_page_two = bounded_vec![(1, exposure_three),]; + + // stores exposure page with exposures of validator 1 and 2, returns exposed validator + // account id. + assert_eq!( + Pallet::::store_stakers_info(exposures_page_one, current_era()).to_vec(), + vec![1, 2] + ); + // Stakers overview OK for validator 1 and 2. + assert_eq!( + ErasStakersOverview::::get(0, &1).unwrap(), + PagedExposureMetadata { + total: 1700, + own: 1000, + nominator_count: 3, + page_count: 2, + last_page_empty_slots: 1, + }, + ); + assert_eq!( + ErasStakersOverview::::get(0, &2).unwrap(), + PagedExposureMetadata { + total: 2000, + own: 1000, + nominator_count: 2, + page_count: 1, + last_page_empty_slots: 0, + }, + ); + + // stores exposure page with exposures of validator 1, returns exposed validator + // account id. + assert_eq!( + Pallet::::store_stakers_info(exposures_page_two, current_era()).to_vec(), + vec![1] + ); + + // Stakers overview OK for validator 1. + assert_eq!( + ErasStakersOverview::::get(0, &1).unwrap(), + PagedExposureMetadata { + total: 2200, + own: 1000, + nominator_count: 5, + page_count: 3, + last_page_empty_slots: 1, + }, + ); + + // validator 1 has 3 paged exposures. + assert!( + ErasStakersPaged::::iter_prefix_values((0, &1)).count() as u32 == + EraInfo::::get_page_count(0, &1) && + EraInfo::::get_page_count(0, &1) == 3 + ); + assert!(ErasStakersPaged::::get((0, &1, 0)).is_some()); + assert!(ErasStakersPaged::::get((0, &1, 1)).is_some()); + assert!(ErasStakersPaged::::get((0, &1, 2)).is_some()); + assert!(ErasStakersPaged::::get((0, &1, 3)).is_none()); + + // validator 2 has 1 paged exposures. + assert!(ErasStakersPaged::::get((0, &2, 0)).is_some()); + assert!(ErasStakersPaged::::get((0, &2, 1)).is_none()); + assert_eq!(ErasStakersPaged::::iter_prefix_values((0, &2)).count(), 1); + + // exposures of validator 1 are the expected: + assert_eq!( + ErasStakersPaged::::get((0, &1, 0)).unwrap(), + ExposurePage { + page_total: 600, + others: vec![ + IndividualExposure { who: 101, value: 500 }, + IndividualExposure { who: 102, value: 100 } + ] + }, + ); + assert_eq!( + ErasStakersPaged::::get((0, &1, 1)).unwrap(), + ExposurePage { + page_total: 350, + others: vec![ + IndividualExposure { who: 103, value: 100 }, + IndividualExposure { who: 110, value: 250 } + ] + } + ); + assert_eq!( + ErasStakersPaged::::get((0, &1, 2)).unwrap(), + ExposurePage { + page_total: 250, + others: vec![IndividualExposure { who: 111, value: 250 }] + } + ); + + // exposures of validator 2. + assert_eq!( + ErasStakersPaged::::iter_prefix_values((0, &2)).collect::>(), + vec![ExposurePage { + page_total: 1000, + others: vec![ + IndividualExposure { who: 104, value: 500 }, + IndividualExposure { who: 105, value: 500 } + ] + }], + ); + }) + } +} From bc7b20018444092d80004b73f74915e8fc30c4e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Thu, 7 Nov 2024 13:55:22 +0100 Subject: [PATCH 030/169] adds exposure collection checks to multi page election tests --- .../frame/staking/src/tests_paged_election.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/substrate/frame/staking/src/tests_paged_election.rs b/substrate/frame/staking/src/tests_paged_election.rs index 064aabdd5d964..08e2910493677 100644 --- a/substrate/frame/staking/src/tests_paged_election.rs +++ b/substrate/frame/staking/src/tests_paged_election.rs @@ -295,6 +295,13 @@ mod paged_on_initialize { ElectableStashes::::get().into_iter().collect::>(), expected_elected ); + // exposures have been collected for all validators in the page. + // note that the mock election provider adds one exposures per winner for + // each page. + for s in expected_elected.iter() { + // 1 page fetched, 1 `other` exposure collected per electable stash. + assert_eq!(Staking::eras_stakers(current_era() + 1, s).others.len(), 1); + } // 3. progress one block to fetch page 1. run_to_block(System::block_number() + 1); @@ -305,6 +312,11 @@ mod paged_on_initialize { ); // election cursor reamins unchanged during intermediate pages. assert_eq!(ElectingStartedAt::::get(), Some(next_election - pages)); + // exposures have been collected for all validators in the page. + for s in expected_elected.iter() { + // 2 pages fetched, 2 `other` exposures collected per electable stash. + assert_eq!(Staking::eras_stakers(current_era() + 1, s).others.len(), 2); + } // 4. progress one block to fetch lsp (i.e. 0). run_to_block(System::block_number() + 1); @@ -313,7 +325,12 @@ mod paged_on_initialize { ElectableStashes::::get().into_iter().collect::>(), expected_elected ); - // upon fetchin page 0, the electing started at will remain in storage until the + // exposures have been collected for all validators in the page. + for s in expected_elected.iter() { + // 3 pages fetched, 3 `other` exposures collected per electable stash. + assert_eq!(Staking::eras_stakers(current_era() + 1, s).others.len(), 3); + } + // upon fetching page 0, the electing started at will remain in storage until the // era rotates. assert_eq!(current_era(), 0); assert_eq!(ElectingStartedAt::::get(), Some(next_election - pages)); From 2e71ab2d6b35e6f214970499e0eed1dddd9248e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Fri, 8 Nov 2024 12:00:11 +0100 Subject: [PATCH 031/169] EPM-MB: Supports only single page target snapshot (#6414) Removes the unnecessary complexity of paged target snapshot since it is not going be be used in AHM in the short/mid term. --- substrate/frame/staking/src/lib.rs | 10 ++- substrate/frame/staking/src/pallet/impls.rs | 52 +++--------- substrate/frame/staking/src/pallet/mod.rs | 8 -- .../frame/staking/src/tests_paged_election.rs | 79 ++++++++----------- 4 files changed, 49 insertions(+), 100 deletions(-) diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index 74ced178ad6c6..63d427cf1b9c8 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -150,14 +150,20 @@ //! //! The pallet supports a multi-page election. In a multi-page election, some key actions of the //! staking pallet progress over multi pages/blocks. Most notably: -//! 1. **Snapshot creation**: Both the voter and target snapshots are created over multi blocks. The +//! 1. **Snapshot creation**: The voter snapshot *may be* created over multi blocks. The //! [`frame_election_provider_support::ElectionDataProvider`] trait supports that functionality -//! by parameterizing the electin voters and electable targets by the page index. +//! by parameterizing the electing voters by the page index. Even though the target snapshot +//! could be paged, this pallet implements a single-page target snapshot only. //! 2. **Election**: The election is multi-block, where a set of supports is fetched per page/block. //! This pallet keeps track of the elected stashes and their exposures as the paged election is //! called. The [`frame_election_provider_support::ElectionProvider`] trait supports this //! functionaluty by parameterizing the elect call with the page index. //! +//! Note: [`frame_election_provider_support::ElectionDataProvider`] trait supports mulit-paged +//! target snaphsot. However, this pallet only supports and implements a single-page snapshot. +//! Calling [`ElectionDataProvider::electable_targets`] with a different index than 0 is redundant +//! and the single page idx 0 of targets be returned. +//! //! ### Prepare an election ahead of time with `on_initialize` //! //! This pallet is expected to have a set of winners ready and their exposures collected and stored diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index d062537e2fe18..896e94ff01405 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -1117,14 +1117,12 @@ impl Pallet { all_voters } - /// Get all the targets associated with `page` that are eligible for the npos election. + /// Get all the targets associated are eligible for the npos election. /// - /// Note: in the context of the multi-page snapshot, we expect the *order* of `VoterList` and - /// `TargetList` not to change while the pages are being processed. This should be ensured by - /// the pallet, (e.g. using [`frame_election_provider_support::LockableElectionDataProvider`]). + /// The target snaphot is *always* single paged. /// /// This function is self-weighing as [`DispatchClass::Mandatory`]. - pub fn get_npos_targets(bounds: DataProviderBounds, page: PageIndex) -> Vec { + pub fn get_npos_targets(bounds: DataProviderBounds) -> Vec { let mut targets_size_tracker: StaticTracker = StaticTracker::default(); let final_predicted_len = { @@ -1134,18 +1132,8 @@ impl Pallet { let mut all_targets = Vec::::with_capacity(final_predicted_len as usize); let mut targets_seen = 0; - let mut targets_taken = 0u32; - - let mut targets_iter = match TargetSnapshotStatus::::get() { - // start the snapshot processing, start from the beginning. - SnapshotStatus::Waiting => T::TargetList::iter(), - // snapshot continues, start from last iterated target in the list. - SnapshotStatus::Ongoing(start_at) => - T::TargetList::iter_from(&start_at).unwrap_or(Box::new(vec![].into_iter())), - // all the targets have been consumed, return an empty iterator. - SnapshotStatus::Consumed => Box::new(vec![].into_iter()), - }; + let mut targets_iter = T::TargetList::iter(); while all_targets.len() < final_predicted_len as usize && targets_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * final_predicted_len as u32) { @@ -1167,35 +1155,9 @@ impl Pallet { if Validators::::contains_key(&target) { all_targets.push(target); - targets_taken.saturating_inc(); } } - // update the target snapshot status. - TargetSnapshotStatus::::mutate(|status| { - match (page, status.clone()) { - // last page, reset status for next round. - (0, _) => *status = SnapshotStatus::Waiting, - - (_, SnapshotStatus::Waiting) | (_, SnapshotStatus::Ongoing(_)) => { - let maybe_last = all_targets.last().map(|x| x).cloned(); - - if let Some(ref last) = maybe_last { - if maybe_last == T::TargetList::iter().last() { - // all targets in the target list have been consumed. - *status = SnapshotStatus::Consumed; - } else { - *status = SnapshotStatus::Ongoing(last.clone()); - } - } else { - debug_assert!(*status == SnapshotStatus::Consumed); - } - }, - // do nothing. - (_, SnapshotStatus::Consumed) => (), - } - }); - Self::register_weight(T::WeightInfo::get_npos_targets(all_targets.len() as u32)); log!(info, "generated {} npos targets", all_targets.len()); @@ -1389,7 +1351,11 @@ impl ElectionDataProvider for Pallet { bounds: DataProviderBounds, page: PageIndex, ) -> data_provider::Result> { - let targets = Self::get_npos_targets(bounds, page); + if page > 0 { + log!(warn, "multi-page target snapshot not supported, returning page 0."); + } + + let targets = Self::get_npos_targets(bounds); // We can't handle this case yet -- return an error. WIP to improve handling this case in // . if bounds.exhausted(None, CountBound(targets.len() as u32).into()) { diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index 0d32370e172a7..4e360dd9f41c0 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -751,14 +751,6 @@ pub mod pallet { pub(crate) type VoterSnapshotStatus = StorageValue<_, SnapshotStatus, ValueQuery>; - /// Target snapshot progress status. - /// - /// If the status is `Ongoing`, it keeps track of the last target account returned in the - /// snapshot. - #[pallet::storage] - pub(crate) type TargetSnapshotStatus = - StorageValue<_, SnapshotStatus, ValueQuery>; - /// Keeps track of an ongoing multi-page election solution request. /// /// Stores the block number of when the first election page was requested. `None` indicates diff --git a/substrate/frame/staking/src/tests_paged_election.rs b/substrate/frame/staking/src/tests_paged_election.rs index 08e2910493677..995d88c95ccee 100644 --- a/substrate/frame/staking/src/tests_paged_election.rs +++ b/substrate/frame/staking/src/tests_paged_election.rs @@ -358,58 +358,21 @@ mod paged_snapshot { .set_status(101, StakerStatus::Idle) .build_and_execute(|| { // all registered validators. - assert_eq!( + let all_targets = vec![51, 31, 41, 21, 11]; + assert_eq_uvec!( ::TargetList::iter().collect::>(), - vec![51, 31, 41, 21, 11] + all_targets, ); - // 2 targets per page. + // 3 targets per page. let bounds = - ElectionBoundsBuilder::default().targets_count(2.into()).build().targets; - - let mut all_targets = vec![]; - - let targets_page_3 = - ::electable_targets(bounds, 3).unwrap(); - all_targets.extend(targets_page_3.clone()); - - // result is expected: 2 most significant targets in the target list. - assert_eq!(targets_page_3, vec![51, 31]); - - // target snapshot status updated as expecteed: snapshot is ongoing, last target ID - // returned was 31 as there has been 2 targets comndumed already. - assert_eq!(TargetSnapshotStatus::::get(), SnapshotStatus::Ongoing(31)); + ElectionBoundsBuilder::default().targets_count(3.into()).build().targets; - let targets_page_2 = - ::electable_targets(bounds, 2).unwrap(); - all_targets.extend(targets_page_2.clone()); - - assert_eq!(targets_page_2, vec![41, 21]); - // 4 targets consumed since the beginning. - assert_eq!(TargetSnapshotStatus::::get(), SnapshotStatus::Ongoing(21)); - - let targets_page_1 = - ::electable_targets(bounds, 1).unwrap(); - all_targets.extend(targets_page_1.clone()); - - // did not fullfill the bounds because there were not enough targets in the list. - assert_eq!(targets_page_1, vec![11]); - - // all targets have been consumed in the list. - assert_eq!(TargetSnapshotStatus::::get(), SnapshotStatus::Consumed); - - // all targets in the list have been consumed, it should return an empty set of - // targets. - assert!(::electable_targets(bounds, 0) - .unwrap() - .is_empty()); - - // all pages have been requested, thus in waiting status, prepared for - // electable targets requests for a new snapshot. - assert_eq!(TargetSnapshotStatus::::get(), SnapshotStatus::Waiting); + let targets = + ::electable_targets(bounds, 0).unwrap(); + assert_eq_uvec!(targets, all_targets.iter().take(3).cloned().collect::>()); - // now request 1 page with bounds where all registerd targets fit. u32::MAX - // emulates a no bounds request. + // emulates a no bounds target snapshot request. let bounds = ElectionBoundsBuilder::default().targets_count(u32::MAX.into()).build().targets; @@ -418,10 +381,32 @@ mod paged_snapshot { // complete set of paged targets is the same as single page, no bounds set of // targets. - assert_eq!(all_targets, single_page_targets); + assert_eq_uvec!(all_targets, single_page_targets); }) } + #[test] + fn target_snaposhot_multi_page_redundant() { + ExtBuilder::default().build_and_execute(|| { + let all_targets = vec![31, 21, 11]; + assert_eq_uvec!(::TargetList::iter().collect::>(), all_targets,); + + // no bounds. + let bounds = + ElectionBoundsBuilder::default().targets_count(u32::MAX.into()).build().targets; + + // target snapshot supports only single-page, thus it is redundant what's the page index + // requested. + let snapshot = Staking::electable_targets(bounds, 0).unwrap(); + assert!( + snapshot == all_targets && + snapshot == Staking::electable_targets(bounds, 1).unwrap() && + snapshot == Staking::electable_targets(bounds, 2).unwrap() && + snapshot == Staking::electable_targets(bounds, u32::MAX).unwrap(), + ); + }) + } + #[test] fn voter_snapshot_works() { ExtBuilder::default() From e9854fcec3fdb2607cd74c202ce12c2609d8bb56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Fri, 8 Nov 2024 18:04:35 +0100 Subject: [PATCH 032/169] Adds try-state checks for election prep metadata and tests --- substrate/frame/staking/src/lib.rs | 2 +- substrate/frame/staking/src/mock.rs | 2 +- substrate/frame/staking/src/pallet/impls.rs | 83 ++++++++++++++++- .../frame/staking/src/tests_paged_election.rs | 88 +++++++++++++++++-- 4 files changed, 164 insertions(+), 11 deletions(-) diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index 63d427cf1b9c8..0ec53a3552938 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -1360,7 +1360,7 @@ impl EraInfo { // insert metadata. ErasStakersOverview::::insert(era, &validator, exposure_metadata); - // insert or update validator's overview. + // insert validator's overview. exposure_pages.iter().enumerate().for_each(|(idx, paged_exposure)| { let append_at = idx as Page; >::insert((era, &validator, append_at), &paged_exposure); diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index 7ca5f4b6453c7..112d1ff148d5e 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -254,7 +254,7 @@ impl< SP::elect(page) } else { // will take first `MaxWinnersPerPage` in the validator set as winners. in this mock - // impl, we return a random nominator exposure per winner/page. + // impl, we return an arbitratily but deterministic nominator exposure per winner/page. let supports: Vec<(AccountId, Support)> = Validators::::iter_keys() .filter(|x| Staking::status(x) == Ok(StakerStatus::Validator)) .take(Self::MaxWinnersPerPage::get() as usize) diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index 896e94ff01405..d00f3cc662ed4 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -80,6 +80,12 @@ impl Pallet { <::ElectionProvider as ElectionProvider>::Pages::get() } + /// Clears up all election preparation metadata in storage. + pub(crate) fn clear_election_metadata() { + ElectingStartedAt::::kill(); + ElectableStashes::::kill(); + } + /// Fetches the ledger associated with a controller or stash account, if any. pub fn ledger(account: StakingAccount) -> Result, Error> { StakingLedger::::get(account) @@ -633,8 +639,8 @@ impl Pallet { if let Some(old_era) = new_planned_era.checked_sub(T::HistoryDepth::get() + 1) { Self::clear_era_information(old_era); } - // Including electing targets of previous era. - ElectingStartedAt::::kill(); + // Including election prep metadata. + Self::clear_election_metadata(); } /// Potentially plan a new era. @@ -704,6 +710,9 @@ impl Pallet { _ => {}, } + // election failed, clear election prep metadata. + Self::clear_election_metadata(); + Self::deposit_event(Event::StakingElectionFailed); return None } @@ -726,12 +735,15 @@ impl Pallet { Ok(result) => result, Err(e) => { log!(warn, "election provider page failed due to {:?} (page: {})", e, page); + // election failed, clear election prep metadata. + Self::clear_election_metadata(); + Self::deposit_event(Event::StakingElectionFailed); return }, }; - // preparing the next era. Note: we expect elect paged to be called *only* during a + // preparing the next era. Note: we expect `do_elect_paged` to be called *only* during a // non-genesis era, thus current era should be set by now. let planning_era = CurrentEra::::get().defensive_unwrap_or_default().saturating_add(1); @@ -2112,13 +2124,14 @@ impl sp_staking::StakingUnchecked for Pallet { #[cfg(any(test, feature = "try-runtime"))] impl Pallet { - pub(crate) fn do_try_state(_: BlockNumberFor) -> Result<(), TryRuntimeError> { + pub(crate) fn do_try_state(now: BlockNumberFor) -> Result<(), TryRuntimeError> { ensure!( T::VoterList::iter() .all(|x| >::contains_key(&x) || >::contains_key(&x)), "VoterList contains non-staker" ); + Self::ensure_snapshot_metadata_state(now)?; Self::check_ledgers()?; Self::check_bonded_consistency()?; Self::check_payees()?; @@ -2129,6 +2142,68 @@ impl Pallet { Self::ensure_disabled_validators_sorted() } + /// Invariants: + /// TODO + pub fn ensure_snapshot_metadata_state(now: BlockNumberFor) -> Result<(), TryRuntimeError> { + let pages: BlockNumberFor = Self::election_pages().into(); + let next_election = ::next_election_prediction(now); + let expect_election_start_at = next_election.saturating_sub(pages); + + let election_prep_started = now >= expect_election_start_at; + + // check election metadata, electable targets and era exposures if election should have + // already started. + match election_prep_started { + // election prep should have been started. + true => + if let Some(started_at) = ElectingStartedAt::::get() { + ensure!( + started_at == expect_election_start_at, + "unexpected electing_started_at block number in storage." + ); + ensure!( + !ElectableStashes::::get().is_empty(), + "election should have been started and the electable stashes non empty." + ); + + // all the current electable stashes exposures should have been collected and + // stored for the next era, and their total exposure suhould be > 0. + for s in ElectableStashes::::get().iter() { + ensure!( + EraInfo::::get_paged_exposure( + Self::current_era().unwrap_or_default().saturating_add(1), + s, + 0 + ) + .defensive_proof("electable stash exposure does not exist, unexpected.") + .unwrap() + .exposure_metadata + .total != Zero::zero(), + "no exposures collected for an electable stash." + ); + } + } else { + return Err( + "election prep should have started already, no election metadata in storage." + .into(), + ); + }, + // election prep should have not been started. + false => { + ensure!( + ElectableStashes::::get().is_empty(), + "unexpected electable stashes in storage while election prep hasn't started." + ); + ensure!( + ElectingStartedAt::::get().is_none(), + "unexpected election metadata while election prep hasn't started.", + ); + }, + } + + Ok(()) + } + /// Invariants: /// * A controller should not be associated with more than one ledger. /// * A bonded (stash, controller) pair should have only one associated ledger. I.e. if the diff --git a/substrate/frame/staking/src/tests_paged_election.rs b/substrate/frame/staking/src/tests_paged_election.rs index 995d88c95ccee..78c26b2ed24d5 100644 --- a/substrate/frame/staking/src/tests_paged_election.rs +++ b/substrate/frame/staking/src/tests_paged_election.rs @@ -16,7 +16,7 @@ // limitations under the License. use crate::{mock::*, *}; -use frame_support::{assert_ok, testing_prelude::*}; +use frame_support::{assert_ok, testing_prelude::*, BoundedBTreeSet}; use substrate_test_utils::assert_eq_uvec; use frame_election_provider_support::{ @@ -52,6 +52,9 @@ fn add_electable_stashes_work() { ElectableStashes::::get().into_inner().into_iter().collect::>(), vec![1, 2, 3, 4, 6] ); + + // skip final try state checks. + SkipTryStateCheck::set(true); }) } @@ -100,8 +103,12 @@ mod paged_on_initialize { assert_eq!(ElectingStartedAt::::get(), None); assert!(ElectableStashes::::get().is_empty()); + // try-state sanity check. + assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); + // 2. starts preparing election at the (election_prediction - n_pages) block. run_to_block(next_election - pages); + assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); // electing started at cursor is set once the election starts to be prepared. assert_eq!(ElectingStartedAt::::get(), Some(next_election - pages)); @@ -116,6 +123,7 @@ mod paged_on_initialize { // 3. progress to election block, which matches with era rotation. run_to_block(next_election); + assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); assert_eq!(current_era(), 1); // clears out election metadata for era. assert!(ElectingStartedAt::::get().is_none()); @@ -174,7 +182,11 @@ mod paged_on_initialize { ] ); + // try-state sanity check. + assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); + start_session(1); + assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); assert_eq!(current_era(), 0); // election haven't started yet. assert_eq!(ElectingStartedAt::::get(), None); @@ -182,6 +194,7 @@ mod paged_on_initialize { // progress to era rotation session. start_session(SessionsPerEra::get()); + assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); assert_eq!(current_era(), 1); assert_eq_uvec!(Session::validators(), vec![11, 21, 31]); assert_eq!( @@ -212,6 +225,7 @@ mod paged_on_initialize { // progress session and rotate era. start_session(SessionsPerEra::get() * 2); + assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); assert_eq!(current_era(), 2); assert_eq_uvec!(Session::validators(), vec![11, 21]); @@ -242,10 +256,10 @@ mod paged_on_initialize { #[test] fn multi_page_election_works() { ExtBuilder::default() - .add_staker(61, 61, 10, StakerStatus::Validator) - .add_staker(71, 71, 10, StakerStatus::Validator) - .add_staker(81, 81, 10, StakerStatus::Validator) - .add_staker(91, 91, 10, StakerStatus::Validator) + .add_staker(61, 61, 1000, StakerStatus::Validator) + .add_staker(71, 71, 1000, StakerStatus::Validator) + .add_staker(81, 81, 1000, StakerStatus::Validator) + .add_staker(91, 91, 1000, StakerStatus::Validator) .multi_page_election_provider(3) .max_winners_per_page(5) .build_and_execute(|| { @@ -278,15 +292,20 @@ mod paged_on_initialize { ValidatorCount::::set(expected_elected.len() as u32); assert_eq!(expected_elected.len(), 5); + // try-state sanity check. + assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); + // 1. election prep hasn't started yet, election cursor and electable stashes are not // set yet. run_to_block(next_election - pages - 1); + assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); assert_eq!(ElectingStartedAt::::get(), None); assert!(ElectableStashes::::get().is_empty()); // 2. starts preparing election at the (election_prediction - n_pages) block. // fetches msp (i.e. 2). run_to_block(next_election - pages); + assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); // electing started at cursor is set once the election starts to be prepared. assert_eq!(ElectingStartedAt::::get(), Some(next_election - pages)); @@ -305,6 +324,7 @@ mod paged_on_initialize { // 3. progress one block to fetch page 1. run_to_block(System::block_number() + 1); + assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); // the electable stashes remain the same. assert_eq_uvec!( ElectableStashes::::get().into_iter().collect::>(), @@ -320,6 +340,7 @@ mod paged_on_initialize { // 4. progress one block to fetch lsp (i.e. 0). run_to_block(System::block_number() + 1); + assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); // the electable stashes remain the same. assert_eq_uvec!( ElectableStashes::::get().into_iter().collect::>(), @@ -336,6 +357,7 @@ mod paged_on_initialize { assert_eq!(ElectingStartedAt::::get(), Some(next_election - pages)); // 5. rotate era. + assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); start_active_era(current_era() + 1); // the new era validators are the expected elected stashes. assert_eq_uvec!(Session::validators(), expected_elected); @@ -344,6 +366,62 @@ mod paged_on_initialize { assert!(ElectableStashes::::get().is_empty()); }) } + + #[test] + fn try_state_failure_works() { + ExtBuilder::default().build_and_execute(|| { + let pages: BlockNumber = + <::ElectionProvider as ElectionProvider>::Pages::get().into(); + let next_election = + ::next_election_prediction(System::block_number()); + + let mut invalid_stashes = BoundedBTreeSet::new(); + + run_to_block(next_election - pages - 1); + + // election hasn't started yet, no electable stashes expected in storage. + assert_ok!(invalid_stashes.try_insert(42)); + ElectableStashes::::set(invalid_stashes); + assert_err!( + Staking::ensure_snapshot_metadata_state(System::block_number()), + "unexpected electable stashes in storage while election prep hasn't started." + ); + Staking::clear_election_metadata(); + + // election hasn't started yet, no electable stashes expected in storage. + ElectingStartedAt::::set(Some(42)); + assert_err!( + Staking::ensure_snapshot_metadata_state(System::block_number()), + "unexpected election metadata while election prep hasn't started." + ); + Staking::clear_election_metadata(); + + run_to_block(next_election - pages); + + // election prep started, metadata, electable stashes and exposures are expected to + // exist. + let _ = ErasStakersOverview::::clear(u32::MAX, None); + let _ = ErasStakersPaged::::clear(u32::MAX, None); + assert_err!( + Staking::ensure_snapshot_metadata_state(System::block_number()), + "no exposures collected for an electable stash." + ); + + ElectingStartedAt::::kill(); + assert_err!( + Staking::ensure_snapshot_metadata_state(System::block_number()), + "election prep should have started already, no election metadata in storage." + ); + ElectingStartedAt::::set(Some(424242)); + assert_err!( + Staking::ensure_snapshot_metadata_state(System::block_number()), + "unexpected electing_started_at block number in storage." + ); + + // skip final try state checks. + SkipTryStateCheck::set(true); + }) + } } mod paged_snapshot { From 9f4b6a337c31847e6f8e1c326c8892cb162b5377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Mon, 11 Nov 2024 23:27:21 +0100 Subject: [PATCH 033/169] more tests --- substrate/frame/staking/src/mock.rs | 2 +- substrate/frame/staking/src/pallet/impls.rs | 8 ++- .../frame/staking/src/tests_paged_election.rs | 69 +++++++++++++++++++ 3 files changed, 77 insertions(+), 2 deletions(-) diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index 112d1ff148d5e..e822dfaf017a2 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -263,7 +263,7 @@ impl< v, Support { total: (100 + page).into(), - voters: vec![(page as AccountId, (100 + page).into())], + voters: vec![((page + 1) as AccountId, (100 + page).into())], }, ) }) diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index d00f3cc662ed4..f1d6ce420f4ba 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -2143,7 +2143,13 @@ impl Pallet { } /// Invariants: - /// TODO + /// * If the election preparation has started (i.e. `now` >= `expected_election - n_pages`): + /// * The election preparation metadata should be set (`ElectingStartedAt`); + /// * The electable stashes should not be empty; + /// * The exposures for the current electable stashes should have been collected; + /// * If the election preparation has not started yet: + /// * The election preparation metadata is empty; + /// * The electable stashes for this era is empty; pub fn ensure_snapshot_metadata_state(now: BlockNumberFor) -> Result<(), TryRuntimeError> { let pages: BlockNumberFor = Self::election_pages().into(); let next_election = ::next_election_prediction(now); diff --git a/substrate/frame/staking/src/tests_paged_election.rs b/substrate/frame/staking/src/tests_paged_election.rs index 78c26b2ed24d5..26946887fb5ed 100644 --- a/substrate/frame/staking/src/tests_paged_election.rs +++ b/substrate/frame/staking/src/tests_paged_election.rs @@ -367,6 +367,75 @@ mod paged_on_initialize { }) } + #[test] + fn multi_page_election_with_mulit_page_exposures_rewards_work() { + ExtBuilder::default() + .add_staker(61, 61, 1000, StakerStatus::Validator) + .add_staker(71, 71, 1000, StakerStatus::Validator) + .add_staker(1, 1, 5, StakerStatus::Nominator(vec![21, 31, 71])) + .add_staker(2, 2, 5, StakerStatus::Nominator(vec![21, 31, 71])) + .add_staker(3, 3, 5, StakerStatus::Nominator(vec![21, 31, 71])) + .multi_page_election_provider(3) + .max_winners_per_page(3) + .exposures_page_size(2) + .build_and_execute(|| { + // election provider has 3 pages. + let pages: BlockNumber = + <::ElectionProvider as ElectionProvider>::Pages::get().into(); + assert_eq!(pages, 3); + // 3 max winners per page. + let max_winners_page = <::ElectionProvider as ElectionProvider>::MaxWinnersPerPage::get(); + assert_eq!(max_winners_page, 3); + + // setup validator payee prefs and 10% commission. + for s in vec![21, 31, 71] { + Payee::::insert(s, RewardDestination::Account(s)); + let prefs = ValidatorPrefs { commission: Perbill::from_percent(10), ..Default::default() }; + Validators::::insert(s, prefs.clone()); + } + + let init_balance_all = vec![21, 31, 71, 1, 2, 3].iter().fold(0, |mut acc, s| { + acc += asset::total_balance::(&s); + acc + }); + + // progress era. + assert_eq!(current_era(), 0); + start_active_era(1); + assert_eq!(current_era(), 1); + assert_eq!(Session::validators(), vec![21, 31, 71]); + + // distribute reward, + Pallet::::reward_by_ids(vec![(21, 50)]); + Pallet::::reward_by_ids(vec![(31, 50)]); + Pallet::::reward_by_ids(vec![(71, 50)]); + + let total_payout = current_total_payout_for_duration(reward_time_per_era()); + + start_active_era(2); + + // all the validators exposed in era 1 have two pages of exposures, since exposure + // page size is 2. + assert_eq!(MaxExposurePageSize::get(), 2); + assert_eq!(EraInfo::::get_page_count(1, &21), 2); + assert_eq!(EraInfo::::get_page_count(1, &31), 2); + assert_eq!(EraInfo::::get_page_count(1, &71), 2); + + make_all_reward_payment(1); + + let balance_all = vec![21, 31, 71, 1, 2, 3].iter().fold(0, |mut acc, s| { + acc += asset::total_balance::(&s); + acc + }); + + assert_eq_error_rate!( + total_payout, + balance_all - init_balance_all, + 4 + ); + }) + } + #[test] fn try_state_failure_works() { ExtBuilder::default().build_and_execute(|| { From fb31d1b9d35968c52fca8f8320b7f92354f19221 Mon Sep 17 00:00:00 2001 From: command-bot <> Date: Tue, 12 Nov 2024 08:44:18 +0000 Subject: [PATCH 034/169] ".git/.scripts/commands/fmt/fmt.sh" --- .../frame/election-provider-multi-phase/src/benchmarking.rs | 3 ++- substrate/frame/staking/src/pallet/mod.rs | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/substrate/frame/election-provider-multi-phase/src/benchmarking.rs b/substrate/frame/election-provider-multi-phase/src/benchmarking.rs index 408cbb476e0d5..a2289195fd662 100644 --- a/substrate/frame/election-provider-multi-phase/src/benchmarking.rs +++ b/substrate/frame/election-provider-multi-phase/src/benchmarking.rs @@ -289,7 +289,8 @@ mod benchmarks { // We don't directly need the data-provider to be populated, but it is just easy to use it. set_up_data_provider::(v, t); // default bounds are unbounded. - let targets = T::DataProvider::electable_targets(DataProviderBounds::default(), Zero::zero())?; + let targets = + T::DataProvider::electable_targets(DataProviderBounds::default(), Zero::zero())?; let voters = T::DataProvider::electing_voters(DataProviderBounds::default(), Zero::zero())?; let desired_targets = T::DataProvider::desired_targets()?; diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index 69d9a5a337954..b98279f3c80e0 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -911,7 +911,9 @@ pub mod pallet { /// The election failed. No new era is planned. StakingElectionFailed, /// An account has stopped participating as either a validator or nominator. - Chilled { stash: T::AccountId }, + Chilled { + stash: T::AccountId, + }, /// A Page of stakers rewards are getting paid. `next` is `None` if all pages are claimed. PayoutStarted { era_index: EraIndex, From 01e09c6c7842a32c9676be40628250f605f5e665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 12 Nov 2024 09:58:28 +0100 Subject: [PATCH 035/169] Update substrate/frame/election-provider-multi-phase/src/lib.rs Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com> --- substrate/frame/election-provider-multi-phase/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index 40fa163a0aa63..614e56514ef4e 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -669,7 +669,7 @@ pub mod pallet { #[pallet::constant] type SignedDepositWeight: Get>; - /// Maximum number of winners that an electio supports. + /// Maximum number of winners that an election supports. /// /// Note: This must always be greater or equal to `T::DataProvider::desired_targets()`. type MaxWinners: Get; From b4c900c96c54bca832e95b812928e1b2fc6e2151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 12 Nov 2024 09:59:20 +0100 Subject: [PATCH 036/169] Update substrate/frame/election-provider-multi-phase/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- substrate/frame/election-provider-multi-phase/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index 614e56514ef4e..c5d42361470a6 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -1786,7 +1786,7 @@ impl ElectionProvider for Pallet { fn elect(page: PageIndex) -> Result, Self::Error> { // Note: this pallet **MUST** only by used in the single-block mode. - ensure!(page.is_zero(), ElectionError::::MultiPageNotSupported); + ensure!(page == SINGLE_PAGE, ElectionError::::MultiPageNotSupported); match Self::do_elect() { Ok(bounded_supports) => { From 628ae6777a97714c0a3b9cffbca0400af3a0f914 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 12 Nov 2024 10:01:00 +0100 Subject: [PATCH 037/169] Update prdoc/pr_6034.prdoc Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com> --- prdoc/pr_6034.prdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prdoc/pr_6034.prdoc b/prdoc/pr_6034.prdoc index 420fd5ca36742..d26f285f4afd4 100644 --- a/prdoc/pr_6034.prdoc +++ b/prdoc/pr_6034.prdoc @@ -3,7 +3,7 @@ title: Adds multi-block election types and refactors current single logic to sup doc: - audience: Runtime Dev description: | - This PR adds election types and structs required to run a multi-block election. In additoin, + This PR adds election types and structs required to run a multi-block election. In addition, it EPM, staking pallet and all dependent pallets and logic to use the multi-block types. crates: From d05a002fb841aa4b4252634fdab4d363d8d5dee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 12 Nov 2024 10:02:23 +0100 Subject: [PATCH 038/169] Update substrate/frame/staking/src/lib.rs Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com> --- substrate/frame/staking/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index 0ec53a3552938..9cdccca439f1d 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -157,7 +157,7 @@ //! 2. **Election**: The election is multi-block, where a set of supports is fetched per page/block. //! This pallet keeps track of the elected stashes and their exposures as the paged election is //! called. The [`frame_election_provider_support::ElectionProvider`] trait supports this -//! functionaluty by parameterizing the elect call with the page index. +//! functionality by parameterizing the elect call with the page index. //! //! Note: [`frame_election_provider_support::ElectionDataProvider`] trait supports mulit-paged //! target snaphsot. However, this pallet only supports and implements a single-page snapshot. From 07796d935f328b2085d3cfcf9c67db851abadfcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 12 Nov 2024 10:02:38 +0100 Subject: [PATCH 039/169] Update substrate/frame/staking/src/lib.rs Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com> --- substrate/frame/staking/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index 9cdccca439f1d..437cd1071add0 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -1290,7 +1290,7 @@ impl EraInfo { /// /// If the exposure does not exist yet for the tuple (era, validator), it sets it. Otherwise, /// it updates the existing record by ensuring *intermediate* exposure pages are filled up with - /// `T::MaxExposurePageSize` number of backiers per page. + /// `T::MaxExposurePageSize` number of backers per page. pub fn upsert_exposure( era: EraIndex, validator: &T::AccountId, From 23b9c212bd00437da9a18af639c84aff32c8d854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 12 Nov 2024 10:03:36 +0100 Subject: [PATCH 040/169] Update substrate/frame/election-provider-multi-phase/src/lib.rs Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com> --- substrate/frame/election-provider-multi-phase/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index c5d42361470a6..5e2e754f2bf22 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -192,8 +192,8 @@ //! ## Multi-page election support //! //! The [`frame_election_provider_support::ElectionDataProvider`] and -//! [`frame_election_provider_support::ElectionProvider`] traits used by this pallet support a -//! multi page election. +//! [`frame_election_provider_support::ElectionProvider`] traits used by this pallet can support a +//! multi-page election. //! //! However, this pallet is meant to be used only in the context of a single-page election and data //! provider and all the relevant trait implementation ad configurations reflect that assumption. From 2570c230aba427b14ce277c6fdfd41ccef623868 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 12 Nov 2024 10:04:31 +0100 Subject: [PATCH 041/169] Update substrate/frame/election-provider-multi-phase/src/lib.rs Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com> --- substrate/frame/election-provider-multi-phase/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index 5e2e754f2bf22..1a1dbff2cde61 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -195,7 +195,7 @@ //! [`frame_election_provider_support::ElectionProvider`] traits used by this pallet can support a //! multi-page election. //! -//! However, this pallet is meant to be used only in the context of a single-page election and data +//! However, this pallet only supports single-page election and data //! provider and all the relevant trait implementation ad configurations reflect that assumption. //! //! If external callers request the election of a page index higher than 0, the election will fail From 808e2ba98eebb0377abca510ff0b07bfef746751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 12 Nov 2024 10:10:20 +0100 Subject: [PATCH 042/169] Update substrate/frame/election-provider-multi-phase/src/lib.rs Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com> --- substrate/frame/election-provider-multi-phase/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index 1a1dbff2cde61..db087bae23154 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -674,7 +674,7 @@ pub mod pallet { /// Note: This must always be greater or equal to `T::DataProvider::desired_targets()`. type MaxWinners: Get; - /// Maximum number of voters that can support a winner target in an election solution. + /// Maximum number of voters that can support a winner in an election solution. /// /// This limit must be set so that the memory limits of the rest of the system are /// respected. From 2092d2c261e41ae7178bc84e91864f4ea51f32d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 12 Nov 2024 10:11:27 +0100 Subject: [PATCH 043/169] Update substrate/frame/election-provider-multi-phase/src/unsigned.rs Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com> --- substrate/frame/election-provider-multi-phase/src/unsigned.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/election-provider-multi-phase/src/unsigned.rs b/substrate/frame/election-provider-multi-phase/src/unsigned.rs index 1113c0bc8a96b..c6c29ff0af5a2 100644 --- a/substrate/frame/election-provider-multi-phase/src/unsigned.rs +++ b/substrate/frame/election-provider-multi-phase/src/unsigned.rs @@ -431,7 +431,7 @@ pub trait MinerConfig { type MaxWeight: Get; /// The maximum number of winners that can be elected per page (and overall). type MaxWinners: Get; - /// The maximum number of backers (edges) per winner in the last solution. + /// The maximum number of backers per winner in the last solution. type MaxBackersPerWinner: Get; /// Something that can compute the weight of a solution. /// From f2fe96041b1bb5b949b746624504b8f8380a8576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 12 Nov 2024 10:12:39 +0100 Subject: [PATCH 044/169] Update substrate/frame/election-provider-support/src/onchain.rs Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com> --- substrate/frame/election-provider-support/src/onchain.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/election-provider-support/src/onchain.rs b/substrate/frame/election-provider-support/src/onchain.rs index 95f74ba298277..c8c985b2a4401 100644 --- a/substrate/frame/election-provider-support/src/onchain.rs +++ b/substrate/frame/election-provider-support/src/onchain.rs @@ -43,7 +43,7 @@ pub enum Error { /// `MaxWinners`. TooManyWinners, /// Single page election called with multi-page configs. - SinglePageExpected, + UnsupportedPageIndex, } impl From for Error { From bca81c29e3ffc33416aacd39a1dc07e37fae00b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 12 Nov 2024 10:13:55 +0100 Subject: [PATCH 045/169] Update substrate/frame/election-provider-support/src/lib.rs Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com> --- substrate/frame/election-provider-support/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/election-provider-support/src/lib.rs b/substrate/frame/election-provider-support/src/lib.rs index bfd29225fc6d6..72899972d96d6 100644 --- a/substrate/frame/election-provider-support/src/lib.rs +++ b/substrate/frame/election-provider-support/src/lib.rs @@ -326,7 +326,7 @@ pub trait ElectionDataProvider { /// Maximum number of votes per voter that this data provider is providing. type MaxVotesPerVoter: Get; - /// Returns the possible targets for the election associated with page `page`, i.e. the targets + /// Returns the possible targets for the election associated with the provided `page`, i.e. the targets /// that could become elected, thus "electable". /// /// This should be implemented as a self-weighing function. The implementor should register its From df729a0898f3f69204868123ed0c117d1ca93458 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 12 Nov 2024 10:39:16 +0100 Subject: [PATCH 046/169] addresses PR review comments --- .../election-provider-support/src/onchain.rs | 4 ++-- substrate/frame/staking/src/pallet/impls.rs | 20 ++++++++----------- .../frame/staking/src/tests_paged_election.rs | 16 +++++++++++++-- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/substrate/frame/election-provider-support/src/onchain.rs b/substrate/frame/election-provider-support/src/onchain.rs index c8c985b2a4401..be59450ce148b 100644 --- a/substrate/frame/election-provider-support/src/onchain.rs +++ b/substrate/frame/election-provider-support/src/onchain.rs @@ -42,7 +42,7 @@ pub enum Error { /// Configurational error caused by `desired_targets` requested by data provider exceeding /// `MaxWinners`. TooManyWinners, - /// Single page election called with multi-page configs. + /// Election page index not supported. UnsupportedPageIndex, } @@ -181,7 +181,7 @@ impl ElectionProvider for OnChainExecution { fn elect(page: PageIndex) -> Result, Self::Error> { if page > 0 { - return Err(Error::SinglePageExpected) + return Err(Error::UnsupportedPageIndex) } let election_bounds = ElectionBoundsBuilder::from(T::Bounds::get()).build(); diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index d926f33084a85..5993b3dc4a978 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -625,9 +625,6 @@ impl Pallet { /// * Store start session index for the new planned era. /// * Clean old era information. /// - /// Note: staking information for the new planned era has been processed and stored during the - /// `elect_paged(page_index)` calls. - /// /// The new validator set for this era is stored under [`ElectableStashes`]. pub fn trigger_new_era(start_session_index: SessionIndex) { // Increment or set current era. @@ -857,15 +854,14 @@ impl Pallet { pub(crate) fn add_electables( stashes: BoundedVec>, ) -> Result<(), ()> { - let mut storage_stashes = ElectableStashes::::get(); - for stash in stashes.into_iter() { - storage_stashes.try_insert(stash).map_err(|_| { - // add as many stashes as possible before returning err. - ElectableStashes::::set(storage_stashes.clone()); - })?; - } - - ElectableStashes::::set(storage_stashes); + ElectableStashes::::mutate(|electable| { + for stash in stashes.into_iter() { + if let Err(_) = (*electable).try_insert(stash) { + return Err(()) + } + } + Ok(()) + })?; Ok(()) } diff --git a/substrate/frame/staking/src/tests_paged_election.rs b/substrate/frame/staking/src/tests_paged_election.rs index 26946887fb5ed..edb40bc9bb99b 100644 --- a/substrate/frame/staking/src/tests_paged_election.rs +++ b/substrate/frame/staking/src/tests_paged_election.rs @@ -45,12 +45,24 @@ fn add_electable_stashes_work() { vec![1, 2, 3, 4] ); + // skip final try state checks. + SkipTryStateCheck::set(true); + }) +} + +#[test] +fn add_electable_stashes_overflow_works() { + ExtBuilder::default().build_and_execute(|| { + MaxValidatorSet::set(5); + assert_eq!(MaxValidatorSet::get(), 5); + assert!(ElectableStashes::::get().is_empty()); + // adds stashes so that bounds are overflown, fails and internal state changes so that // all slots are filled. - assert!(Staking::add_electables(bounded_vec![6, 7, 8, 9, 10]).is_err()); + assert!(Staking::add_electables(bounded_vec![1, 2, 3, 4, 5, 6]).is_err()); assert_eq!( ElectableStashes::::get().into_inner().into_iter().collect::>(), - vec![1, 2, 3, 4, 6] + vec![1, 2, 3, 4, 5] ); // skip final try state checks. From 51329d7fc4c954469ecf9fb739835134c9c6f4b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 12 Nov 2024 12:39:25 +0100 Subject: [PATCH 047/169] ensures only collected electable stashes have exposures collectede --- .../election-provider-support/src/lib.rs | 12 +- substrate/frame/staking/src/pallet/impls.rs | 62 ++++++--- .../frame/staking/src/tests_paged_election.rs | 129 ++++++++++++------ 3 files changed, 140 insertions(+), 63 deletions(-) diff --git a/substrate/frame/election-provider-support/src/lib.rs b/substrate/frame/election-provider-support/src/lib.rs index 72899972d96d6..766469c9c425b 100644 --- a/substrate/frame/election-provider-support/src/lib.rs +++ b/substrate/frame/election-provider-support/src/lib.rs @@ -326,8 +326,8 @@ pub trait ElectionDataProvider { /// Maximum number of votes per voter that this data provider is providing. type MaxVotesPerVoter: Get; - /// Returns the possible targets for the election associated with the provided `page`, i.e. the targets - /// that could become elected, thus "electable". + /// Returns the possible targets for the election associated with the provided `page`, i.e. the + /// targets that could become elected, thus "electable". /// /// This should be implemented as a self-weighing function. The implementor should register its /// appropriate weight at the end of execution with the system pallet directly. @@ -803,6 +803,14 @@ pub struct BoundedSupports, BInner: Get>( pub BoundedVec<(AccountId, BoundedSupport), BOuter>, ); +impl, BInner: Get> sp_std::ops::DerefMut + for BoundedSupports +{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + impl, BInner: Get> Debug for BoundedSupports { diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index 5993b3dc4a978..46a8d6c67ede0 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -729,6 +729,10 @@ impl Pallet { /// The results from the elect call shold be stored in the `ElectableStashes` storage. In /// addition, it stores stakers' information for next planned era based on the paged solution /// data returned. + /// + /// If any new election winner does not fit in the electable stashes storage, it truncates the + /// result of the election. We ensure that only the winners that are part of the electable + /// stashes have exposures collected for the next era. pub(crate) fn do_elect_paged(page: PageIndex) { let paged_result = match ::elect(page) { Ok(result) => result, @@ -742,19 +746,41 @@ impl Pallet { }, }; + if let Err(_) = Self::do_elect_paged_inner(paged_result) { + defensive!("electable stashes exceeded limit, unexpected but election proceeds."); + }; + } + + pub(crate) fn do_elect_paged_inner( + mut supports: BoundedSupportsOf, + ) -> Result<(), ()> { // preparing the next era. Note: we expect `do_elect_paged` to be called *only* during a // non-genesis era, thus current era should be set by now. let planning_era = CurrentEra::::get().defensive_unwrap_or_default().saturating_add(1); - let stashes = Self::store_stakers_info( - Self::collect_exposures(paged_result).into_inner(), - planning_era, - ); + match Self::add_electables(supports.iter().map(|(s, _)| s.clone())) { + Ok(_) => { + let _ = Self::store_stakers_info( + Self::collect_exposures(supports).into_inner(), + planning_era, + ); + Ok(()) + }, + Err(not_included) => { + log!( + warn, + "not all winners fit within the electable stashes, excluding tail: {:?}.", + not_included + ); + + // filter out exposures of stashes that do not fit in electable stashes. + supports.retain(|(s, _)| !not_included.contains(s)); - match Self::add_electables(stashes) { - Ok(_) => (), - Err(_) => { - defensive!("electable stashes exceeded limit, unexpected."); + let _ = Self::store_stakers_info( + Self::collect_exposures(supports).into_inner(), + planning_era, + ); + Err(()) }, } } @@ -850,20 +876,22 @@ impl Pallet { /// Adds a new set of stashes to the electable stashes. /// - /// Deduplicates stashes in place and returns an error if the bounds are exceeded. + /// Deduplicates stashes in place and returns an error if the bounds are exceeded. In case of + /// error, it returns the stashes that were not added to the storage. pub(crate) fn add_electables( - stashes: BoundedVec>, - ) -> Result<(), ()> { + mut stashes_iter: impl Iterator, + ) -> Result<(), Vec> { ElectableStashes::::mutate(|electable| { - for stash in stashes.into_iter() { - if let Err(_) = (*electable).try_insert(stash) { - return Err(()) + while let Some(stash) = stashes_iter.next() { + if let Err(_) = (*electable).try_insert(stash.clone()) { + let mut not_included = stashes_iter.collect::>(); + not_included.push(stash); + + return Err(not_included); } } Ok(()) - })?; - - Ok(()) + }) } /// Remove all associated data of a stash account from the staking system. diff --git a/substrate/frame/staking/src/tests_paged_election.rs b/substrate/frame/staking/src/tests_paged_election.rs index edb40bc9bb99b..46180bf922f9d 100644 --- a/substrate/frame/staking/src/tests_paged_election.rs +++ b/substrate/frame/staking/src/tests_paged_election.rs @@ -20,54 +20,95 @@ use frame_support::{assert_ok, testing_prelude::*, BoundedBTreeSet}; use substrate_test_utils::assert_eq_uvec; use frame_election_provider_support::{ - bounds::ElectionBoundsBuilder, ElectionDataProvider, SortedListProvider, + bounds::ElectionBoundsBuilder, ElectionDataProvider, SortedListProvider, Support, }; use sp_staking::StakingInterface; -#[test] -fn add_electable_stashes_work() { - ExtBuilder::default().build_and_execute(|| { - MaxValidatorSet::set(5); - assert_eq!(MaxValidatorSet::get(), 5); - assert!(ElectableStashes::::get().is_empty()); - - // adds stashes without duplicates, do not overflow bounds. - assert_ok!(Staking::add_electables(bounded_vec![1, 2, 3])); - assert_eq!( - ElectableStashes::::get().into_inner().into_iter().collect::>(), - vec![1, 2, 3] - ); - - // adds with duplicates which are deduplicated implicitly, no not overflow bounds. - assert_ok!(Staking::add_electables(bounded_vec![1, 2, 4])); - assert_eq!( - ElectableStashes::::get().into_inner().into_iter().collect::>(), - vec![1, 2, 3, 4] - ); - - // skip final try state checks. - SkipTryStateCheck::set(true); - }) -} +mod electable_stashes { + use super::*; + + #[test] + fn add_electable_stashes_work() { + ExtBuilder::default().build_and_execute(|| { + MaxValidatorSet::set(5); + assert_eq!(MaxValidatorSet::get(), 5); + assert!(ElectableStashes::::get().is_empty()); + + // adds stashes without duplicates, do not overflow bounds. + assert_ok!(Staking::add_electables(vec![1u64, 2, 3].into_iter())); + assert_eq!( + ElectableStashes::::get().into_inner().into_iter().collect::>(), + vec![1, 2, 3] + ); + + // adds with duplicates which are deduplicated implicitly, no not overflow bounds. + assert_ok!(Staking::add_electables(vec![1u64, 2, 4].into_iter())); + assert_eq!( + ElectableStashes::::get().into_inner().into_iter().collect::>(), + vec![1, 2, 3, 4] + ); + + // skip final try state checks. + SkipTryStateCheck::set(true); + }) + } + + #[test] + fn add_electable_stashes_overflow_works() { + ExtBuilder::default().build_and_execute(|| { + MaxValidatorSet::set(5); + assert_eq!(MaxValidatorSet::get(), 5); + assert!(ElectableStashes::::get().is_empty()); -#[test] -fn add_electable_stashes_overflow_works() { - ExtBuilder::default().build_and_execute(|| { - MaxValidatorSet::set(5); - assert_eq!(MaxValidatorSet::get(), 5); - assert!(ElectableStashes::::get().is_empty()); - - // adds stashes so that bounds are overflown, fails and internal state changes so that - // all slots are filled. - assert!(Staking::add_electables(bounded_vec![1, 2, 3, 4, 5, 6]).is_err()); - assert_eq!( - ElectableStashes::::get().into_inner().into_iter().collect::>(), - vec![1, 2, 3, 4, 5] - ); - - // skip final try state checks. - SkipTryStateCheck::set(true); - }) + // adds stashes so that bounds are overflown, fails and internal state changes so that + // all slots are filled. + assert!(Staking::add_electables(vec![1u64, 2, 3, 4, 5, 6].into_iter()).is_err()); + assert_eq!( + ElectableStashes::::get().into_inner().into_iter().collect::>(), + vec![1, 2, 3, 4, 5] + ); + + SkipTryStateCheck::set(true); + }) + } + + #[test] + fn overflow_electable_stashes_no_exposures_work() { + // ensures exposures are stored only for the electable stashes that fit within the + // electable stashes bounds in case of overflow. + ExtBuilder::default().build_and_execute(|| { + MaxValidatorSet::set(2); + assert_eq!(MaxValidatorSet::get(), 2); + assert!(ElectableStashes::::get().is_empty()); + + // current era is 0, preparing 1. + assert_eq!(current_era(), 0); + + let supports = to_bounded_supports(vec![ + (1, Support { total: 100, voters: vec![(10, 1_000)] }), + (2, Support { total: 200, voters: vec![(20, 2_000)] }), + (3, Support { total: 300, voters: vec![(30, 3_000)] }), + (4, Support { total: 400, voters: vec![(40, 4_000)] }), + ]); + + // error due to bounds. + assert!(Staking::do_elect_paged_inner(supports).is_err()); + + // electable stashes have been collected to the max bounds despite the error. + assert_eq!(ElectableStashes::::get().into_iter().collect::>(), vec![1, 2]); + + let exposure_exists = + |acc, era| EraInfo::::get_full_exposure(era, &acc).total != 0; + + // exposures were only collected for electable stashes in bounds (1 and 2). + assert!(exposure_exists(1, 1)); + assert!(exposure_exists(2, 1)); + assert!(!exposure_exists(3, 1)); + assert!(!exposure_exists(4, 1)); + + SkipTryStateCheck::set(true); + }) + } } mod paged_on_initialize { From 64d3ab3ab2102407a50cfba743add566a220c493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 12 Nov 2024 12:43:11 +0100 Subject: [PATCH 048/169] comment nits --- substrate/frame/staking/src/pallet/impls.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index 46a8d6c67ede0..d4c136751f2ff 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -751,6 +751,10 @@ impl Pallet { }; } + /// Inner implementation of [`Self::do_elect_paged`]. + /// + /// Returns an error if adding election winners to the electable stashes storage fails due to + /// exceeded bounds. pub(crate) fn do_elect_paged_inner( mut supports: BoundedSupportsOf, ) -> Result<(), ()> { @@ -773,7 +777,8 @@ impl Pallet { not_included ); - // filter out exposures of stashes that do not fit in electable stashes. + // filter out supports of stashes that do not fit within the electable stashes + // storage bounds to prevent collecting their exposures. supports.retain(|(s, _)| !not_included.contains(s)); let _ = Self::store_stakers_info( From 2082723b4fa32b89cbd6061bcb4a28fa7352f5d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 12 Nov 2024 14:54:21 +0100 Subject: [PATCH 049/169] adds type alias to ensure bounds are in order --- .../election-provider-multi-phase/src/lib.rs | 21 ++++++++++--------- .../src/signed.rs | 4 ++-- .../src/unsigned.rs | 10 ++++----- substrate/frame/staking/src/lib.rs | 16 ++------------ substrate/frame/staking/src/mock.rs | 2 +- 5 files changed, 21 insertions(+), 32 deletions(-) diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index db087bae23154..fb6e785eb65bc 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -308,6 +308,12 @@ pub type SolutionTargetIndexOf = as NposSolution>::TargetIndex /// The accuracy of the election, when submitted from offchain. Derived from [`SolutionOf`]. pub type SolutionAccuracyOf = ::MinerConfig> as NposSolution>::Accuracy; +/// A ready solution parameterized with this pallet's miner config. +pub type ReadySolutionOf = ReadySolution< + ::AccountId, + ::MaxWinners, + ::MaxBackersPerWinner, +>; /// The fallback election type. pub type FallbackErrorOf = <::Fallback as ElectionProvider>::Error; @@ -1000,7 +1006,7 @@ pub mod pallet { ensure!(CurrentPhase::::get().is_emergency(), Error::::CallNotAllowed); // bound supports with T::MaxWinners. - let supports: BoundedSupports<_, _, _> = + let supports: BoundedSupportsOf> = supports.try_into_bounded_supports().map_err(|_| Error::::TooManyWinners)?; // Note: we don't `rotate_round` at this point; the next call to @@ -1280,8 +1286,7 @@ pub mod pallet { /// /// Always sorted by score. #[pallet::storage] - pub type QueuedSolution = - StorageValue<_, ReadySolution>; + pub type QueuedSolution = StorageValue<_, ReadySolutionOf>; /// Snapshot data of the round. /// @@ -1413,8 +1418,7 @@ impl Pallet { /// Current best solution, signed or unsigned, queued to be returned upon `elect`. /// /// Always sorted by score. - pub fn queued_solution( - ) -> Option> { + pub fn queued_solution() -> Option> { QueuedSolution::::get() } @@ -1600,8 +1604,7 @@ impl Pallet { pub fn feasibility_check( raw_solution: RawSolution>, compute: ElectionCompute, - ) -> Result, FeasibilityError> - { + ) -> Result, FeasibilityError> { let desired_targets = DesiredTargets::::get().ok_or(FeasibilityError::SnapshotUnavailable)?; @@ -1681,9 +1684,7 @@ impl Pallet { } /// record the weight of the given `supports`. - fn weigh_supports( - supports: &BoundedSupports, - ) { + fn weigh_supports(supports: &BoundedSupportsOf) { let active_voters = supports .iter() .map(|(_, x)| x) diff --git a/substrate/frame/election-provider-multi-phase/src/signed.rs b/substrate/frame/election-provider-multi-phase/src/signed.rs index 7fe8f5920b14e..5b8b22e6119b7 100644 --- a/substrate/frame/election-provider-multi-phase/src/signed.rs +++ b/substrate/frame/election-provider-multi-phase/src/signed.rs @@ -21,7 +21,7 @@ use core::marker::PhantomData; use crate::{ unsigned::MinerConfig, Config, ElectionCompute, Pallet, QueuedSolution, RawSolution, - ReadySolution, SignedSubmissionIndices, SignedSubmissionNextIndex, SignedSubmissionsMap, + ReadySolutionOf, SignedSubmissionIndices, SignedSubmissionNextIndex, SignedSubmissionsMap, SnapshotMetadata, SolutionOf, SolutionOrSnapshotSize, Weight, WeightInfo, }; use alloc::{ @@ -490,7 +490,7 @@ impl Pallet { /// /// Infallible pub fn finalize_signed_phase_accept_solution( - ready_solution: ReadySolution, + ready_solution: ReadySolutionOf, who: &T::AccountId, deposit: BalanceOf, call_fee: BalanceOf, diff --git a/substrate/frame/election-provider-multi-phase/src/unsigned.rs b/substrate/frame/election-provider-multi-phase/src/unsigned.rs index c6c29ff0af5a2..00b41bbb25580 100644 --- a/substrate/frame/election-provider-multi-phase/src/unsigned.rs +++ b/substrate/frame/election-provider-multi-phase/src/unsigned.rs @@ -19,8 +19,8 @@ use crate::{ helpers, Call, Config, CurrentPhase, DesiredTargets, ElectionCompute, Error, FeasibilityError, - Pallet, QueuedSolution, RawSolution, ReadySolution, Round, RoundSnapshot, Snapshot, - SolutionAccuracyOf, SolutionOf, SolutionOrSnapshotSize, Weight, + Pallet, QueuedSolution, RawSolution, ReadySolution, ReadySolutionOf, Round, RoundSnapshot, + Snapshot, SolutionAccuracyOf, SolutionOf, SolutionOrSnapshotSize, Weight, }; use alloc::{boxed::Box, vec::Vec}; use codec::Encode; @@ -429,7 +429,8 @@ pub trait MinerConfig { /// /// The weight is computed using `solution_weight`. type MaxWeight: Get; - /// The maximum number of winners that can be elected per page (and overall). + /// The maximum number of winners that can be elected in the single page supported by this + /// pallet. type MaxWinners: Get; /// The maximum number of backers per winner in the last solution. type MaxBackersPerWinner: Get; @@ -751,8 +752,7 @@ impl Miner { snapshot: RoundSnapshot>, current_round: u32, minimum_untrusted_score: Option, - ) -> Result, FeasibilityError> - { + ) -> Result, FeasibilityError> { let RawSolution { solution, score, round } = raw_solution; let RoundSnapshot { voters: snapshot_voters, targets: snapshot_targets } = snapshot; diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index 437cd1071add0..c5a61f0981495 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -422,13 +422,6 @@ pub struct ActiveEraInfo { pub start: Option, } -/// Pointer to the last iterated indices for targets and voters used when generating the snapshot. -#[derive(Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] -pub(crate) struct LastIteratedStakers { - voter: AccountId, - target: AccountId, -} - /// Reward points of an era. Used to split era total payout between validators. /// /// This points will be used to reward validators and their respective nominators. @@ -488,22 +481,17 @@ pub struct UnlockChunk { } /// Status of a paged snapshot progress. -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen, Default)] pub enum SnapshotStatus { /// Paged snapshot is in progress, the `AccountId` was the last staker iterated in the list. Ongoing(AccountId), /// All the stakers in the system have been consumed since the snapshot started. Consumed, /// Waiting for a new snapshot to be requested. + #[default] Waiting, } -impl Default for SnapshotStatus { - fn default() -> Self { - Self::Waiting - } -} - /// The ledger of a (bonded) stash. /// /// Note: All the reads and mutations to the [`Ledger`], [`Bonded`] and [`Payee`] storage items diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index e822dfaf017a2..cd68244c8cc24 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -209,7 +209,7 @@ parameter_types! { pub static MaxValidatorSet: u32 = 100; pub static ElectionsBounds: ElectionBounds = ElectionBoundsBuilder::default().build(); pub static AbsoluteMaxNominations: u32 = 16; - pub static MaxWinnersPerPage: u32 = 10_000; + pub static MaxWinnersPerPage: u32 = 100; } type VoterBagsListInstance = pallet_bags_list::Instance1; From d720e5a65766cdf69d5337f12e2734a7669d40ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 12 Nov 2024 15:19:25 +0100 Subject: [PATCH 050/169] integrity test --- substrate/frame/staking/src/pallet/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index b98279f3c80e0..5e825666d9733 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -1088,6 +1088,14 @@ pub mod pallet { T::SlashDeferDuration::get(), T::BondingDuration::get(), ); + + // the max validator set bound must be the same of lower that the EP's max winner's per + // page, to ensure that the max validator set does not overflow when the retuned + // election page is full. + assert!( + ::MaxWinnersPerPage::get() <= + T::MaxValidatorSet::get() + ); } #[cfg(feature = "try-runtime")] From a88361a567c06e20820cb8ddefdab1f49a88c215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 12 Nov 2024 15:20:46 +0100 Subject: [PATCH 051/169] Update substrate/frame/election-provider-multi-phase/src/lib.rs Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com> --- substrate/frame/election-provider-multi-phase/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index fb6e785eb65bc..08fb10e2071ed 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -682,8 +682,7 @@ pub mod pallet { /// Maximum number of voters that can support a winner in an election solution. /// - /// This limit must be set so that the memory limits of the rest of the system are - /// respected. + /// This is needed to ensure election computation is bounded. type MaxBackersPerWinner: Get; /// Something that calculates the signed deposit base based on the signed submissions queue From ea5aa9ed89a8ec55b9cafca74f0dcc91040b328a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 12 Nov 2024 15:21:19 +0100 Subject: [PATCH 052/169] Update substrate/frame/election-provider-multi-phase/src/lib.rs Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com> --- substrate/frame/election-provider-multi-phase/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index 08fb10e2071ed..46e54681807d9 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -1785,7 +1785,7 @@ impl ElectionProvider for Pallet { type DataProvider = T::DataProvider; fn elect(page: PageIndex) -> Result, Self::Error> { - // Note: this pallet **MUST** only by used in the single-block mode. + // Note: this pallet **MUST** only by used in the single-page mode. ensure!(page == SINGLE_PAGE, ElectionError::::MultiPageNotSupported); match Self::do_elect() { From d140b1feb61c25959cd0a7d48951ae1f495cd4af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 12 Nov 2024 16:06:15 +0100 Subject: [PATCH 053/169] removes the lockable election provider and other nits --- .../election-provider-multi-phase/src/lib.rs | 3 ++- .../election-provider-multi-phase/src/mock.rs | 1 - .../election-provider-support/src/lib.rs | 19 -------------- substrate/frame/staking/src/pallet/impls.rs | 26 ++----------------- substrate/frame/staking/src/pallet/mod.rs | 7 ----- 5 files changed, 4 insertions(+), 52 deletions(-) diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index fb6e785eb65bc..cc32b230f08a4 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -534,6 +534,7 @@ where (Miner(x), Miner(y)) if x == y => true, (DataProvider(x), DataProvider(y)) if x == y => true, (Fallback(x), Fallback(y)) if x == y => true, + (MultiPageNotSupported, MultiPageNotSupported) => true, _ => false, } } @@ -2510,7 +2511,7 @@ mod tests { assert_ok!(MultiPhase::elect(SINGLE_PAGE)); // however, multi page calls will fail. - assert!(MultiPhase::elect(10).is_err()); + assert_noop!(MultiPhase::elect(SINGLE_PAGE + 1), ElectionError::MultiPageNotSupported); }) } diff --git a/substrate/frame/election-provider-multi-phase/src/mock.rs b/substrate/frame/election-provider-multi-phase/src/mock.rs index 4f2c38681d421..4add202ebc045 100644 --- a/substrate/frame/election-provider-multi-phase/src/mock.rs +++ b/substrate/frame/election-provider-multi-phase/src/mock.rs @@ -298,7 +298,6 @@ parameter_types! { pub static MaxWinners: u32 = 200; #[derive(Debug)] pub static MaxBackersPerWinner: u32 = 200; - pub static Pages: u32 = 1; // `ElectionBounds` and `OnChainElectionsBounds` are defined separately to set them independently in the tests. pub static ElectionsBounds: ElectionBounds = ElectionBoundsBuilder::default().build(); pub static OnChainElectionsBounds: ElectionBounds = ElectionBoundsBuilder::default().build(); diff --git a/substrate/frame/election-provider-support/src/lib.rs b/substrate/frame/election-provider-support/src/lib.rs index 766469c9c425b..d1990e4b52fdf 100644 --- a/substrate/frame/election-provider-support/src/lib.rs +++ b/substrate/frame/election-provider-support/src/lib.rs @@ -64,12 +64,6 @@ //! supporting an election to be performed over multiple pages. This enables the //! [`ElectionDataProvider`] implementor to provide all the election data over multiple pages. //! Similarly [`ElectionProvider::elect`] is parameterized by page index. -////! ## [`LockableElectionDataProvider`] for multi-page election -//! -//! The [`LockableElectionDataProvider`] trait exposes a way for election data providers to lock -//! and unlock election data mutations. This is an useful trait to ensure that the results of -//! calling [`ElectionDataProvider::electing_voters`] and -//! [`ElectionDataProvider::electable_targets`] remain consistent over multiple pages. //! //! ## Election Data //! @@ -407,19 +401,6 @@ pub trait ElectionDataProvider { fn set_desired_targets(_count: u32) {} } -/// An [`ElectionDataProvider`] that exposes for an external entity to request lock/unlock on -/// updates in the election data. -/// -/// This functionality is useful when requesting multi-pages of election data, so that the data -/// provided across pages (and potentially across blocks) is consistent. -pub trait LockableElectionDataProvider: ElectionDataProvider { - /// Lock mutations in the election data provider. - fn set_lock() -> data_provider::Result<()>; - - /// Unlocks mutations in the election data provider. - fn unlock(); -} - /// Something that can compute the result of an election and pass it back to the caller in a paged /// way. pub trait ElectionProvider { diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index d4c136751f2ff..46207da2ce091 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -20,8 +20,7 @@ use frame_election_provider_support::{ bounds::{CountBound, SizeBound}, data_provider, BoundedSupportsOf, DataProviderBounds, ElectionDataProvider, ElectionProvider, - LockableElectionDataProvider, PageIndex, ScoreProvider, SortedListProvider, VoteWeight, - VoterOf, + PageIndex, ScoreProvider, SortedListProvider, VoteWeight, VoterOf, }; use frame_support::{ defensive, @@ -1010,8 +1009,7 @@ impl Pallet { /// nominators. /// /// Note: in the context of the multi-page snapshot, we expect the *order* of `VoterList` and - /// `TargetList` not to change while the pages are being processed. This should be ensured by - /// the pallet, (e.g. using [`frame_election_provider_support::LockableElectionDataProvider`]). + /// `TargetList` not to change while the pages are being processed. /// /// This function is self-weighing as [`DispatchClass::Mandatory`]. pub fn get_npos_voters(bounds: DataProviderBounds, page: PageIndex) -> Vec> { @@ -1346,26 +1344,6 @@ impl Pallet { } } -impl LockableElectionDataProvider for Pallet { - // TODO: currently, setting the lock in the election data provider is a noop. Implement the - // logic that freezes and/or buffers the mutations to ledgers while the lock is set *before* - // the multi-page election is enabled. - // Tracking issue . - fn set_lock() -> data_provider::Result<()> { - match ElectionDataLock::::get() { - Some(_) => Err("lock already set"), - None => { - ElectionDataLock::::set(Some(())); - Ok(()) - }, - } - } - - fn unlock() { - ElectionDataLock::::set(None); - } -} - impl ElectionDataProvider for Pallet { type AccountId = T::AccountId; type BlockNumber = BlockNumberFor; diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index 5e825666d9733..0720d2391d130 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -763,13 +763,6 @@ pub mod pallet { pub(crate) type ElectableStashes = StorageValue<_, BoundedBTreeSet, ValueQuery>; - /// Lock state for election data mutations. - /// - /// While the lock is set, there should be no mutations on the ledgers/staking data, ensuring - /// that the data provided to [`Config::ElectionDataProvider`] is stable during all pages. - #[pallet::storage] - pub(crate) type ElectionDataLock = StorageValue<_, (), OptionQuery>; - #[pallet::genesis_config] #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig { From 1a2ddfcee1ce2b3f4512586035601998297449a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Wed, 13 Nov 2024 17:39:17 +0100 Subject: [PATCH 054/169] removes unecessary last_page_empty_slots --- substrate/frame/staking/src/lib.rs | 17 +++++++------ substrate/frame/staking/src/pallet/impls.rs | 3 --- .../frame/staking/src/tests_paged_election.rs | 24 +++---------------- substrate/primitives/staking/src/lib.rs | 7 ------ 4 files changed, 11 insertions(+), 40 deletions(-) diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index c5a61f0981495..764575e94e359 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -809,8 +809,6 @@ impl EraInfo { let page_size = T::MaxExposurePageSize::get().defensive_max(1); if let Some(stored_overview) = ErasStakersOverview::::get(era, &validator) { - let exposures_append = exposure.split_others(stored_overview.last_page_empty_slots); let last_page_idx = stored_overview.page_count.saturating_sub(1); - let last_page_idx = ErasStakersOverview::::mutate(era, &validator, |stored| { + let mut last_page = + ErasStakersPaged::::get((era, validator, last_page_idx)).unwrap_or_default(); + let last_page_empty_slots = + T::MaxExposurePageSize::get().saturating_sub(last_page.others.len() as u32); + + let exposures_append = exposure.split_others(last_page_empty_slots); + + ErasStakersOverview::::mutate(era, &validator, |stored| { // new metadata is updated based on 3 different set of exposures: the // current one, the exposure split to be "fitted" into the current last page and // the exposure set that will be appended from the new page onwards. @@ -1308,14 +1312,9 @@ impl EraInfo { }), ); *stored = new_metadata.into(); - - last_page_idx }); // fill up last page with exposures. - let mut last_page = - ErasStakersPaged::::get((era, validator, last_page_idx)).unwrap_or_default(); - last_page.page_total = last_page .page_total .saturating_add(exposures_append.total) diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index 46207da2ce091..a14ece52f83b7 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -2408,7 +2408,6 @@ impl Pallet { own: Zero::zero(), nominator_count: 0, page_count: 0, - last_page_empty_slots: Default::default(), }; ErasStakersPaged::::iter_prefix((era,)) @@ -2427,8 +2426,6 @@ impl Pallet { own: metadata.own, nominator_count: metadata.nominator_count + expo.others.len() as u32, page_count: metadata.page_count + 1, - last_page_empty_slots: (T::MaxExposurePageSize::get() - .saturating_sub(expo.others.len() as u32)), }, ); diff --git a/substrate/frame/staking/src/tests_paged_election.rs b/substrate/frame/staking/src/tests_paged_election.rs index 46180bf922f9d..2610b93916cfd 100644 --- a/substrate/frame/staking/src/tests_paged_election.rs +++ b/substrate/frame/staking/src/tests_paged_election.rs @@ -723,23 +723,11 @@ mod paged_exposures { // Stakers overview OK for validator 1 and 2. assert_eq!( ErasStakersOverview::::get(0, &1).unwrap(), - PagedExposureMetadata { - total: 1700, - own: 1000, - nominator_count: 3, - page_count: 2, - last_page_empty_slots: 1, - }, + PagedExposureMetadata { total: 1700, own: 1000, nominator_count: 3, page_count: 2 }, ); assert_eq!( ErasStakersOverview::::get(0, &2).unwrap(), - PagedExposureMetadata { - total: 2000, - own: 1000, - nominator_count: 2, - page_count: 1, - last_page_empty_slots: 0, - }, + PagedExposureMetadata { total: 2000, own: 1000, nominator_count: 2, page_count: 1 }, ); // stores exposure page with exposures of validator 1, returns exposed validator @@ -752,13 +740,7 @@ mod paged_exposures { // Stakers overview OK for validator 1. assert_eq!( ErasStakersOverview::::get(0, &1).unwrap(), - PagedExposureMetadata { - total: 2200, - own: 1000, - nominator_count: 5, - page_count: 3, - last_page_empty_slots: 1, - }, + PagedExposureMetadata { total: 2200, own: 1000, nominator_count: 5, page_count: 3 }, ); // validator 1 has 3 paged exposures. diff --git a/substrate/primitives/staking/src/lib.rs b/substrate/primitives/staking/src/lib.rs index 0efb21070e20e..4885f242c5c46 100644 --- a/substrate/primitives/staking/src/lib.rs +++ b/substrate/primitives/staking/src/lib.rs @@ -409,7 +409,6 @@ impl< let individual_chunks = self.others.chunks(page_size as usize); let mut exposure_pages: Vec> = Vec::with_capacity(individual_chunks.len()); - let mut total_chunks_last_page = Default::default(); for chunk in individual_chunks { let mut page_total: Balance = Zero::zero(); @@ -422,8 +421,6 @@ impl< value: individual.value, }) } - - total_chunks_last_page = others.len() as u32; exposure_pages.push(ExposurePage { page_total, others }); } @@ -433,7 +430,6 @@ impl< own: self.own, nominator_count: self.others.len() as u32, page_count: exposure_pages.len() as Page, - last_page_empty_slots: page_size.saturating_sub(total_chunks_last_page), }, exposure_pages, ) @@ -499,8 +495,6 @@ pub struct PagedExposureMetadata { pub nominator_count: u32, /// Number of pages of nominators. pub page_count: Page, - /// Number of empty slots in the last page. - pub last_page_empty_slots: u32, } impl PagedExposureMetadata @@ -532,7 +526,6 @@ where own: self.own, nominator_count: new_nominator_count, page_count: new_page_count, - last_page_empty_slots: Max::get().saturating_sub(new_nominator_count % Max::get()), } } } From 21170560c3a3283547dcc8a5c03ca134080e6cd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Thu, 14 Nov 2024 10:36:15 +0100 Subject: [PATCH 055/169] docs and tests for exposures.split_others --- substrate/frame/staking/src/lib.rs | 6 ++- substrate/primitives/staking/src/lib.rs | 57 ++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index 764575e94e359..e87439515afa1 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -1276,7 +1276,8 @@ impl EraInfo { /// /// If the exposure does not exist yet for the tuple (era, validator), it sets it. Otherwise, /// it updates the existing record by ensuring *intermediate* exposure pages are filled up with - /// `T::MaxExposurePageSize` number of backers per page. + /// `T::MaxExposurePageSize` number of backers per page and the remaining exposures are addded + /// to new exposure pages. pub fn upsert_exposure( era: EraIndex, validator: &T::AccountId, @@ -1292,6 +1293,9 @@ impl EraInfo { let last_page_empty_slots = T::MaxExposurePageSize::get().saturating_sub(last_page.others.len() as u32); + // splits the exposure so that `exposures_append` will fit witin the last exposure + // page, up to the max exposure page size. The remaining individual exposures in + // `exposure` will be added to new pages. let exposures_append = exposure.split_others(last_page_empty_slots); ErasStakersOverview::::mutate(era, &validator, |stored| { diff --git a/substrate/primitives/staking/src/lib.rs b/substrate/primitives/staking/src/lib.rs index 4885f242c5c46..9e9fde3a6db00 100644 --- a/substrate/primitives/staking/src/lib.rs +++ b/substrate/primitives/staking/src/lib.rs @@ -381,8 +381,10 @@ impl< Balance: HasCompact + AtLeast32BitUnsigned + Copy + codec::MaxEncodedLen, > Exposure { - /// Splits self into two where the returned partial `Exposure` has max of `n_others` individual - /// exposures while the remaining exposures are left in `self`. + /// Splits self into two instances of exposures. + /// + /// `n_others` individual exposures are consumed from self and returned as part of the new + /// exposure. pub fn split_others(&mut self, n_others: u32) -> Self { let head_others: Vec<_> = self.others.drain(..(n_others as usize).min(self.others.len())).collect(); @@ -716,4 +718,55 @@ mod tests { let exposure_page: ExposurePage = empty_exposures.into(); assert_eq!(exposure_page, ExposurePage { page_total: 0, others: vec![] }); } + + #[test] + fn exposure_split_others_works() { + let exposure = Exposure { + total: 100, + own: 20, + others: vec![ + IndividualExposure { who: 1, value: 20u32 }, + IndividualExposure { who: 2, value: 20 }, + IndividualExposure { who: 3, value: 20 }, + IndividualExposure { who: 4, value: 20 }, + ], + }; + + let mut exposure_0 = exposure.clone(); + // split others with with 0 `n_others` is a noop and returns an exposure with own only. + let split_exposure = exposure_0.split_others(0); + assert_eq!(exposure_0, exposure); + assert_eq!(split_exposure, Exposure { total: 20, own: 20, others: vec![] }); + + let mut exposure_1 = exposure.clone(); + // split individual exposures so that the returned exposure has 1 individual exposure. + let split_exposure = exposure_1.split_others(1); + assert_eq!(exposure_1.own, 20); + assert_eq!(exposure_1.total, 20 + 3 * 20); + assert_eq!(exposure_1.others.len(), 3); + + assert_eq!(split_exposure.own, 20); + assert_eq!(split_exposure.total, 20 + 1 * 20); + assert_eq!(split_exposure.others.len(), 1); + + let mut exposure_3 = exposure.clone(); + // split individual exposures so that the returned exposure has 3 individual exposures, + // which are consumed from the original exposure. + let split_exposure = exposure_3.split_others(3); + assert_eq!(exposure_3.own, 20); + assert_eq!(exposure_3.total, 20 + 1 * 20); + assert_eq!(exposure_3.others.len(), 1); + + assert_eq!(split_exposure.own, 20); + assert_eq!(split_exposure.total, 20 + 3 * 20); + assert_eq!(split_exposure.others.len(), 3); + + let mut exposure_100 = exposure.clone(); + // split others with with more `n_others` than the number of others in the exposure returns + // consumes all the individual exposures of the original Exposure and returns them in the + // new exposure. + let split_exposure = exposure_100.split_others(100); + assert_eq!(split_exposure, exposure); + assert_eq!(exposure_100, Exposure { total: 20, own: 20, others: vec![] }); + } } From 83c7288d71fb8540148b829b8b4cee82b3b2c31d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Thu, 14 Nov 2024 11:14:30 +0100 Subject: [PATCH 056/169] nits and removes sp-std dependency from staking primitives --- Cargo.lock | 1 - substrate/primitives/staking/Cargo.toml | 2 -- substrate/primitives/staking/src/lib.rs | 26 +++++++++++++------------ 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 771cffeebe5af..8408adf93675c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27272,7 +27272,6 @@ dependencies = [ "serde", "sp-core 28.0.0", "sp-runtime 31.0.1", - "sp-std 14.0.0", ] [[package]] diff --git a/substrate/primitives/staking/Cargo.toml b/substrate/primitives/staking/Cargo.toml index 80220cd1f6fcc..35e7e4f604136 100644 --- a/substrate/primitives/staking/Cargo.toml +++ b/substrate/primitives/staking/Cargo.toml @@ -23,7 +23,6 @@ impl-trait-for-tuples = { workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } -sp-std = { workspace = true } [features] default = ["std"] @@ -33,6 +32,5 @@ std = [ "serde/std", "sp-core/std", "sp-runtime/std", - "sp-std/std", ] runtime-benchmarks = ["sp-runtime/runtime-benchmarks"] diff --git a/substrate/primitives/staking/src/lib.rs b/substrate/primitives/staking/src/lib.rs index 9e9fde3a6db00..b53d75f512a57 100644 --- a/substrate/primitives/staking/src/lib.rs +++ b/substrate/primitives/staking/src/lib.rs @@ -25,7 +25,7 @@ extern crate alloc; use crate::currency_to_vote::CurrencyToVote; use alloc::{collections::btree_map::BTreeMap, vec, vec::Vec}; use codec::{Decode, Encode, FullCodec, HasCompact, MaxEncodedLen}; -use core::ops::Sub; +use core::ops::{Add, AddAssign, Sub, SubAssign}; use scale_info::TypeInfo; use sp_runtime::{ traits::{AtLeast32BitUnsigned, Zero}, @@ -357,8 +357,6 @@ pub struct IndividualExposure { /// A snapshot of the stake backing a single validator in the system. #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] -#[codec(mel_bound(T: Config))] -#[scale_info(skip_type_params(T))] pub struct Exposure { /// The total balance backing this validator. #[codec(compact)] @@ -385,6 +383,9 @@ impl< /// /// `n_others` individual exposures are consumed from self and returned as part of the new /// exposure. + /// + /// Since this method splits `others` of a single exposure, `total.own` will be the same for + /// both `self` and the returned exposure. pub fn split_others(&mut self, n_others: u32) -> Self { let head_others: Vec<_> = self.others.drain(..(n_others as usize).min(self.others.len())).collect(); @@ -455,8 +456,8 @@ impl Default for ExposurePage { } /// Returns an exposure page from a set of individual exposures. -impl - From>> for ExposurePage +impl From>> + for ExposurePage { fn from(exposures: Vec>) -> Self { exposures.into_iter().fold(ExposurePage::default(), |mut page, e| { @@ -503,8 +504,8 @@ impl PagedExposureMetadata where Balance: HasCompact + codec::MaxEncodedLen - + sp_std::ops::Add - + sp_std::ops::Sub + + Add + + Sub + sp_runtime::Saturating + PartialEq + Copy @@ -733,7 +734,8 @@ mod tests { }; let mut exposure_0 = exposure.clone(); - // split others with with 0 `n_others` is a noop and returns an exposure with own only. + // split others with with 0 `n_others` is a noop and returns an empty exposure (with `own` + // only). let split_exposure = exposure_0.split_others(0); assert_eq!(exposure_0, exposure); assert_eq!(split_exposure, Exposure { total: 20, own: 20, others: vec![] }); @@ -761,12 +763,12 @@ mod tests { assert_eq!(split_exposure.total, 20 + 3 * 20); assert_eq!(split_exposure.others.len(), 3); - let mut exposure_100 = exposure.clone(); - // split others with with more `n_others` than the number of others in the exposure returns + let mut exposure_max = exposure.clone(); + // split others with with more `n_others` than the number of others in the exposure // consumes all the individual exposures of the original Exposure and returns them in the // new exposure. - let split_exposure = exposure_100.split_others(100); + let split_exposure = exposure_max.split_others(u32::MAX); assert_eq!(split_exposure, exposure); - assert_eq!(exposure_100, Exposure { total: 20, own: 20, others: vec![] }); + assert_eq!(exposure_max, Exposure { total: 20, own: 20, others: vec![] }); } } From 6705e0ffae777890bd927f2e78362486588cd005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Thu, 14 Nov 2024 11:15:02 +0100 Subject: [PATCH 057/169] Update substrate/frame/election-provider-multi-phase/src/lib.rs Co-authored-by: Guillaume Thiolliere --- substrate/frame/election-provider-multi-phase/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index 4081900d4d472..b0fad1bedeac6 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -196,7 +196,7 @@ //! multi-page election. //! //! However, this pallet only supports single-page election and data -//! provider and all the relevant trait implementation ad configurations reflect that assumption. +//! provider and all the relevant trait implementation and configurations reflect that assumption. //! //! If external callers request the election of a page index higher than 0, the election will fail //! with [`ElectionError::MultiPageNotSupported`]. From 4e13e4f682c5b97e28bfd3a7c57cdf0125f11c96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Thu, 14 Nov 2024 11:15:17 +0100 Subject: [PATCH 058/169] Update prdoc/pr_6034.prdoc Co-authored-by: Guillaume Thiolliere --- prdoc/pr_6034.prdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prdoc/pr_6034.prdoc b/prdoc/pr_6034.prdoc index d26f285f4afd4..e6ecd8aae5c8c 100644 --- a/prdoc/pr_6034.prdoc +++ b/prdoc/pr_6034.prdoc @@ -4,7 +4,7 @@ doc: - audience: Runtime Dev description: | This PR adds election types and structs required to run a multi-block election. In addition, - it EPM, staking pallet and all dependent pallets and logic to use the multi-block types. + it modifies EPM, staking pallet and all dependent pallets and logic to use the multi-block types. crates: - name: frame-election-provider-support From 825984b98db9550ac6075b6206f0777ce59aedb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Thu, 14 Nov 2024 11:25:19 +0100 Subject: [PATCH 059/169] fixes epm test case --- .../frame/election-provider-multi-phase/src/lib.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index b0fad1bedeac6..bf511ece1c52c 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -2493,7 +2493,7 @@ mod tests { #[test] fn try_elect_multi_page_fails() { - ExtBuilder::default().onchain_fallback(false).build_and_execute(|| { + let prepare_election = || { roll_to_signed(); assert!(Snapshot::::get().is_some()); @@ -2505,11 +2505,17 @@ mod tests { )); roll_to(30); assert!(QueuedSolution::::get().is_some()); + }; + ExtBuilder::default().onchain_fallback(false).build_and_execute(|| { + prepare_election(); // single page elect call works as expected. assert_ok!(MultiPhase::elect(SINGLE_PAGE)); + }); - // however, multi page calls will fail. + ExtBuilder::default().onchain_fallback(false).build_and_execute(|| { + prepare_election(); + // multi page calls will fail with multipage not supported error. assert_noop!(MultiPhase::elect(SINGLE_PAGE + 1), ElectionError::MultiPageNotSupported); }) } From 93d6bda464692ad55a5604c3cde354a7b46933da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Thu, 14 Nov 2024 11:45:23 +0100 Subject: [PATCH 060/169] removes TryIntoBoundedSupports trait and impl coversion directly in SupportedBounds --- .../election-provider-multi-phase/src/lib.rs | 6 ++--- .../src/unsigned.rs | 6 ++--- .../election-provider-support/src/lib.rs | 23 ++++++++----------- .../election-provider-support/src/onchain.rs | 11 ++++----- substrate/frame/staking/src/mock.rs | 4 ++-- .../primitives/npos-elections/src/lib.rs | 2 ++ 6 files changed, 24 insertions(+), 28 deletions(-) diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index bf511ece1c52c..8676be438300f 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -248,7 +248,7 @@ use codec::{Decode, Encode}; use frame_election_provider_support::{ bounds::{CountBound, ElectionBounds, ElectionBoundsBuilder, SizeBound}, BoundedSupports, BoundedSupportsOf, DataProviderBounds, ElectionDataProvider, ElectionProvider, - InstantElectionProvider, NposSolution, PageIndex, TryIntoBoundedSupports, + InstantElectionProvider, NposSolution, PageIndex, }; use frame_support::{ dispatch::DispatchClass, @@ -1007,7 +1007,7 @@ pub mod pallet { // bound supports with T::MaxWinners. let supports: BoundedSupportsOf> = - supports.try_into_bounded_supports().map_err(|_| Error::::TooManyWinners)?; + supports.try_into().map_err(|_| Error::::TooManyWinners)?; // Note: we don't `rotate_round` at this point; the next call to // `ElectionProvider::elect` will succeed and take care of that. @@ -2534,7 +2534,7 @@ mod tests { (30, Support { total: 40, voters: vec![(2, 5), (4, 5), (30, 30)] }), (40, Support { total: 60, voters: vec![(2, 5), (3, 10), (4, 5), (40, 40)] }), ] - .try_into_bounded_supports() + .try_into() .unwrap(); assert_eq!(supports, expected_supports); diff --git a/substrate/frame/election-provider-multi-phase/src/unsigned.rs b/substrate/frame/election-provider-multi-phase/src/unsigned.rs index 00b41bbb25580..90b12343aaeb2 100644 --- a/substrate/frame/election-provider-multi-phase/src/unsigned.rs +++ b/substrate/frame/election-provider-multi-phase/src/unsigned.rs @@ -24,9 +24,7 @@ use crate::{ }; use alloc::{boxed::Box, vec::Vec}; use codec::Encode; -use frame_election_provider_support::{ - NposSolution, NposSolver, PerThing128, TryIntoBoundedSupports, VoteWeight, -}; +use frame_election_provider_support::{NposSolution, NposSolver, PerThing128, VoteWeight}; use frame_support::{ dispatch::DispatchResult, ensure, @@ -823,7 +821,7 @@ impl Miner { // Size of winners in miner solution is equal to `desired_targets` <= `MaxWinners`. let supports = supports - .try_into_bounded_supports() + .try_into() .defensive_map_err(|_| FeasibilityError::BoundedConversionFailed)?; Ok(ReadySolution { supports, compute, score }) diff --git a/substrate/frame/election-provider-support/src/lib.rs b/substrate/frame/election-provider-support/src/lib.rs index d1990e4b52fdf..49bd533cc8c3c 100644 --- a/substrate/frame/election-provider-support/src/lib.rs +++ b/substrate/frame/election-provider-support/src/lib.rs @@ -849,33 +849,30 @@ impl, BInner: Get> IntoIterator } } -/// An extension trait to convert from [`sp_npos_elections::Supports`] into -/// [`BoundedSupports`]. -pub trait TryIntoBoundedSupports, BInner: Get> { - /// Perform the conversion. - fn try_into_bounded_supports(self) -> Result, ()>; -} - -impl, BInner: Get> - TryIntoBoundedSupports for sp_npos_elections::Supports +impl, BInner: Get> TryFrom> + for BoundedSupports { - fn try_into_bounded_supports(self) -> Result, ()> { + type Error = crate::Error; + + fn try_from(supports: sp_npos_elections::Supports) -> Result { // optimization note: pre-allocate outer bounded vec. let mut outer_bounded_supports = BoundedVec::< (AccountId, BoundedSupport), BOuter, >::with_bounded_capacity( - self.len().min(BOuter::get() as usize) + supports.len().min(BOuter::get() as usize) ); // optimization note: avoid intermediate allocations. - self.into_iter() + supports + .into_iter() .map(|(account, support)| (account, support.try_into().map_err(|_| ()))) .try_for_each(|(account, maybe_bounded_supports)| { outer_bounded_supports .try_push((account, maybe_bounded_supports?)) .map_err(|_| ()) - })?; + }) + .map_err(|_| crate::Error::BoundsExceeded)?; Ok(outer_bounded_supports.into()) } diff --git a/substrate/frame/election-provider-support/src/onchain.rs b/substrate/frame/election-provider-support/src/onchain.rs index be59450ce148b..379dccee2ce69 100644 --- a/substrate/frame/election-provider-support/src/onchain.rs +++ b/substrate/frame/election-provider-support/src/onchain.rs @@ -22,7 +22,7 @@ use crate::{ bounds::{DataProviderBounds, ElectionBounds, ElectionBoundsBuilder}, BoundedSupportsOf, Debug, ElectionDataProvider, ElectionProvider, InstantElectionProvider, - NposSolver, PageIndex, TryIntoBoundedSupports, WeightInfo, Zero, + NposSolver, PageIndex, WeightInfo, Zero, }; use alloc::collections::btree_map::BTreeMap; use core::marker::PhantomData; @@ -147,9 +147,8 @@ impl OnChainExecution { // defensive: Since npos solver returns a result always bounded by `desired_targets`, this // is never expected to happen as long as npos solver does what is expected for it to do. - let supports: BoundedSupportsOf = to_supports(&staked) - .try_into_bounded_supports() - .map_err(|_| Error::TooManyWinners)?; + let supports: BoundedSupportsOf = + to_supports(&staked).try_into().map_err(|_| Error::TooManyWinners)?; Ok(supports) } @@ -321,7 +320,7 @@ mod tests { ), (30, Support { total: 35, voters: vec![(2, 20), (3, 15)] }), ] - .try_into_bounded_supports() + .try_into() .unwrap(); assert_eq!( @@ -357,7 +356,7 @@ mod tests { ), (30, Support { total: 35, voters: vec![(2, 20), (3, 15)] }) ] - .try_into_bounded_supports() + .try_into() .unwrap() ); }) diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index 12a4181387851..79491ad5434c3 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -21,7 +21,7 @@ use crate::{self as pallet_staking, *}; use frame_election_provider_support::{ bounds::{ElectionBounds, ElectionBoundsBuilder}, onchain, BoundedSupports, BoundedSupportsOf, ElectionProvider, PageIndex, SequentialPhragmen, - Support, TryIntoBoundedSupports, VoteWeight, + Support, VoteWeight, }; use frame_support::{ assert_ok, derive_impl, ord_parameter_types, parameter_types, @@ -1023,5 +1023,5 @@ pub(crate) fn to_bounded_supports( <::ElectionProvider as ElectionProvider>::MaxWinnersPerPage, <::ElectionProvider as ElectionProvider>::MaxBackersPerWinner, > { - supports.try_into_bounded_supports().unwrap() + supports.try_into().unwrap() } diff --git a/substrate/primitives/npos-elections/src/lib.rs b/substrate/primitives/npos-elections/src/lib.rs index 9a9d33aa8e20d..96af46e30f63f 100644 --- a/substrate/primitives/npos-elections/src/lib.rs +++ b/substrate/primitives/npos-elections/src/lib.rs @@ -127,6 +127,8 @@ pub enum Error { InvalidSupportEdge, /// The number of voters is bigger than the `MaxVoters` bound. TooManyVoters, + /// Some bounds were exceeded when converting election types. + BoundsExceeded, } /// A type which is used in the API of this crate as a numeric weight of a vote, most often the From 69b38e59d98769ed93a0da26570cc714fa66240e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Thu, 14 Nov 2024 16:37:22 +0100 Subject: [PATCH 061/169] Adds max backers per winner bounds check in the phragmen implementation (#6482) --- .../election-provider-multi-phase/src/mock.rs | 16 +++- .../src/unsigned.rs | 95 ++++++++++++++++++- .../election-provider-support/src/lib.rs | 20 +++- .../election-provider-support/src/onchain.rs | 5 +- .../primitives/npos-elections/src/lib.rs | 11 ++- .../primitives/npos-elections/src/mock.rs | 2 + .../primitives/npos-elections/src/phragmen.rs | 28 +++++- .../primitives/npos-elections/src/pjr.rs | 6 ++ .../primitives/npos-elections/src/tests.rs | 80 +++++++++++++--- 9 files changed, 232 insertions(+), 31 deletions(-) diff --git a/substrate/frame/election-provider-multi-phase/src/mock.rs b/substrate/frame/election-provider-multi-phase/src/mock.rs index 4add202ebc045..100abc01c2d9d 100644 --- a/substrate/frame/election-provider-multi-phase/src/mock.rs +++ b/substrate/frame/election-provider-multi-phase/src/mock.rs @@ -153,7 +153,8 @@ pub fn trim_helpers() -> TrimHelpers { let desired_targets = crate::DesiredTargets::::get().unwrap(); let ElectionResult::<_, SolutionAccuracyOf> { mut assignments, .. } = - seq_phragmen(desired_targets as usize, targets.clone(), voters.clone(), None).unwrap(); + seq_phragmen(desired_targets as usize, targets.clone(), voters.clone(), None, None) + .unwrap(); // sort by decreasing order of stake assignments.sort_by_key(|assignment| { @@ -180,7 +181,8 @@ pub fn raw_solution() -> RawSolution> { let desired_targets = crate::DesiredTargets::::get().unwrap(); let ElectionResult::<_, SolutionAccuracyOf> { winners: _, assignments } = - seq_phragmen(desired_targets as usize, targets.clone(), voters.clone(), None).unwrap(); + seq_phragmen(desired_targets as usize, targets.clone(), voters.clone(), None, None) + .unwrap(); // closures let cache = helpers::generate_voter_cache::(&voters); @@ -308,7 +310,8 @@ parameter_types! { pub struct OnChainSeqPhragmen; impl onchain::Config for OnChainSeqPhragmen { type System = Runtime; - type Solver = SequentialPhragmen, Balancing>; + type Solver = + SequentialPhragmen, MaxBackersPerWinner, Balancing>; type DataProvider = StakingMock; type WeightInfo = (); type MaxWinnersPerPage = MaxWinners; @@ -420,7 +423,8 @@ impl crate::Config for Runtime { type MaxWinners = MaxWinners; type MaxBackersPerWinner = MaxBackersPerWinner; type MinerConfig = Self; - type Solver = SequentialPhragmen, Balancing>; + type Solver = + SequentialPhragmen, MaxBackersPerWinner, Balancing>; type ElectionBounds = ElectionsBounds; } @@ -607,6 +611,10 @@ impl ExtBuilder { ::set(weight); self } + pub fn max_backers_per_winner(self, max: u32) -> Self { + ::set(max); + self + } pub fn build(self) -> sp_io::TestExternalities { sp_tracing::try_init_simple(); let mut storage = diff --git a/substrate/frame/election-provider-multi-phase/src/unsigned.rs b/substrate/frame/election-provider-multi-phase/src/unsigned.rs index 90b12343aaeb2..aa2b8f265bfbf 100644 --- a/substrate/frame/election-provider-multi-phase/src/unsigned.rs +++ b/substrate/frame/election-provider-multi-phase/src/unsigned.rs @@ -430,7 +430,7 @@ pub trait MinerConfig { /// The maximum number of winners that can be elected in the single page supported by this /// pallet. type MaxWinners: Get; - /// The maximum number of backers per winner in the last solution. + /// The maximum number of backers per winner in a solution. type MaxBackersPerWinner: Get; /// Something that can compute the weight of a solution. /// @@ -1865,6 +1865,99 @@ mod tests { }) } + #[test] + fn mine_solution_always_respects_max_backers_per_winner() { + use crate::mock::MaxBackersPerWinner; + use frame_election_provider_support::BoundedSupport; + + let targets = vec![10, 20, 30, 40]; + let voters = vec![ + (1, 10, bounded_vec![10, 20, 30]), + (2, 10, bounded_vec![10, 20, 30]), + (3, 10, bounded_vec![10, 20, 30]), + (4, 10, bounded_vec![10, 20, 30]), + (5, 10, bounded_vec![10, 20, 40]), + ]; + let snapshot = RoundSnapshot { voters: voters.clone(), targets: targets.clone() }; + let (round, desired_targets) = (1, 3); + + let expected_score_unbounded = + ElectionScore { minimal_stake: 12, sum_stake: 50, sum_stake_squared: 874 }; + let expected_score_bounded = + ElectionScore { minimal_stake: 2, sum_stake: 10, sum_stake_squared: 44 }; + + // solution without max_backers_per_winner set will be higher than the score when bounds + // are set, confirming the trimming when using the same snapshot state. + assert!(expected_score_unbounded > expected_score_bounded); + + // election with unbounded max backers per winnner. + ExtBuilder::default().max_backers_per_winner(u32::MAX).build_and_execute(|| { + assert_eq!(MaxBackersPerWinner::get(), u32::MAX); + + let solution = Miner::::mine_solution_with_snapshot::< + ::Solver, + >(voters.clone(), targets.clone(), desired_targets) + .unwrap() + .0; + + let ready_solution = Miner::::feasibility_check( + RawSolution { solution, score: expected_score_unbounded, round }, + Default::default(), + desired_targets, + snapshot.clone(), + round, + Default::default(), + ) + .unwrap(); + + assert_eq!( + ready_solution.supports.into_iter().collect::>(), + vec![ + ( + 10, + BoundedSupport { total: 21, voters: bounded_vec![(1, 10), (4, 8), (5, 3)] } + ), + (20, BoundedSupport { total: 17, voters: bounded_vec![(2, 10), (5, 7)] }), + (30, BoundedSupport { total: 12, voters: bounded_vec![(3, 10), (4, 2)] }), + ] + ); + }); + + // election with max 1 backer per winnner. + ExtBuilder::default().max_backers_per_winner(1).build_and_execute(|| { + assert_eq!(MaxBackersPerWinner::get(), 1); + + let solution = Miner::::mine_solution_with_snapshot::< + ::Solver, + >(voters, targets, desired_targets) + .unwrap() + .0; + + let ready_solution = Miner::::feasibility_check( + RawSolution { solution, score: expected_score_bounded, round }, + Default::default(), + desired_targets, + snapshot, + round, + Default::default(), + ) + .unwrap(); + + for (_, supports) in ready_solution.supports.iter() { + assert!((supports.voters.len() as u32) <= MaxBackersPerWinner::get()); + } + + assert_eq!( + ready_solution.supports.into_iter().collect::>(), + vec![ + (10, BoundedSupport { total: 6, voters: bounded_vec![(1, 6)] }), + (20, BoundedSupport { total: 2, voters: bounded_vec![(1, 2)] }), + (30, BoundedSupport { total: 2, voters: bounded_vec![(1, 2)] }), + ] + ); + }); + } + #[test] fn trim_assignments_length_does_not_modify_when_short_enough() { ExtBuilder::default().build_and_execute(|| { diff --git a/substrate/frame/election-provider-support/src/lib.rs b/substrate/frame/election-provider-support/src/lib.rs index 49bd533cc8c3c..86bc085ca0a73 100644 --- a/substrate/frame/election-provider-support/src/lib.rs +++ b/substrate/frame/election-provider-support/src/lib.rs @@ -670,12 +670,16 @@ pub trait NposSolver { /// A wrapper for [`sp_npos_elections::seq_phragmen`] that implements [`NposSolver`]. See the /// documentation of [`sp_npos_elections::seq_phragmen`] for more info. -pub struct SequentialPhragmen( - core::marker::PhantomData<(AccountId, Accuracy, Balancing)>, +pub struct SequentialPhragmen( + core::marker::PhantomData<(AccountId, Accuracy, MaxBackersPerWinner, Balancing)>, ); -impl>> - NposSolver for SequentialPhragmen +impl< + AccountId: IdentifierT, + Accuracy: PerThing128, + MaxBackersPerWinner: Get>, + Balancing: Get>, + > NposSolver for SequentialPhragmen { type AccountId = AccountId; type Accuracy = Accuracy; @@ -685,7 +689,13 @@ impl, voters: Vec<(Self::AccountId, VoteWeight, impl IntoIterator)>, ) -> Result, Self::Error> { - sp_npos_elections::seq_phragmen(winners, targets, voters, Balancing::get()) + sp_npos_elections::seq_phragmen( + winners, + targets, + voters, + MaxBackersPerWinner::get(), + Balancing::get(), + ) } fn weight(voters: u32, targets: u32, vote_degree: u32) -> Weight { diff --git a/substrate/frame/election-provider-support/src/onchain.rs b/substrate/frame/election-provider-support/src/onchain.rs index 379dccee2ce69..5e4f9b54984c7 100644 --- a/substrate/frame/election-provider-support/src/onchain.rs +++ b/substrate/frame/election-provider-support/src/onchain.rs @@ -145,8 +145,9 @@ impl OnChainExecution { DispatchClass::Mandatory, ); - // defensive: Since npos solver returns a result always bounded by `desired_targets`, this - // is never expected to happen as long as npos solver does what is expected for it to do. + // defensive: Since npos solver returns a result always bounded by `desired_targets`, and + // ensures the maximum backers per winner, this is never expected to happen as long as npos + // solver does what is expected for it to do. let supports: BoundedSupportsOf = to_supports(&staked).try_into().map_err(|_| Error::TooManyWinners)?; diff --git a/substrate/primitives/npos-elections/src/lib.rs b/substrate/primitives/npos-elections/src/lib.rs index 96af46e30f63f..cfd8bc1d6656e 100644 --- a/substrate/primitives/npos-elections/src/lib.rs +++ b/substrate/primitives/npos-elections/src/lib.rs @@ -247,6 +247,9 @@ pub struct Candidate { elected: bool, /// The round index at which this candidate was elected. round: usize, + /// A list of included backers for this candidate. This can be used to control the bounds of + /// maximum backers per candidate. + bounded_backers: Vec, } impl Candidate { @@ -269,6 +272,8 @@ pub struct Edge { candidate: CandidatePtr, /// The weight (i.e. stake given to `who`) of this edge. weight: ExtendedBalance, + /// Skips this edge. + skip: bool, } #[cfg(test)] @@ -276,14 +281,14 @@ impl Edge { fn new(candidate: Candidate, weight: ExtendedBalance) -> Self { let who = candidate.who.clone(); let candidate = Rc::new(RefCell::new(candidate)); - Self { weight, who, candidate, load: Default::default() } + Self { weight, who, candidate, load: Default::default(), skip: false } } } #[cfg(feature = "std")] impl core::fmt::Debug for Edge { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "Edge({:?}, weight = {:?})", self.who, self.weight) + write!(f, "Edge({:?}, weight = {:?}, skip = {})", self.who, self.weight, self.skip) } } @@ -556,6 +561,7 @@ pub fn setup_inputs( backed_stake: Default::default(), elected: Default::default(), round: Default::default(), + bounded_backers: Default::default(), } .to_ptr() }) @@ -580,6 +586,7 @@ pub fn setup_inputs( candidate: Rc::clone(&candidates[*idx]), load: Default::default(), weight: Default::default(), + skip: false, }); } // else {} would be wrong votes. We don't really care about it. } diff --git a/substrate/primitives/npos-elections/src/mock.rs b/substrate/primitives/npos-elections/src/mock.rs index 91757404145f3..a94803367fb40 100644 --- a/substrate/primitives/npos-elections/src/mock.rs +++ b/substrate/primitives/npos-elections/src/mock.rs @@ -311,6 +311,7 @@ pub(crate) fn run_and_compare( voters: Vec<(AccountId, Vec)>, stake_of: FS, to_elect: usize, + max_backers_candidate: Option, ) where Output: PerThing128, FS: Fn(&AccountId) -> VoteWeight, @@ -323,6 +324,7 @@ pub(crate) fn run_and_compare( .iter() .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), + max_backers_candidate, None, ) .unwrap(); diff --git a/substrate/primitives/npos-elections/src/phragmen.rs b/substrate/primitives/npos-elections/src/phragmen.rs index f331152e722a2..c6c2246244ae1 100644 --- a/substrate/primitives/npos-elections/src/phragmen.rs +++ b/substrate/primitives/npos-elections/src/phragmen.rs @@ -71,11 +71,13 @@ pub fn seq_phragmen( to_elect: usize, candidates: Vec, voters: Vec<(AccountId, VoteWeight, impl IntoIterator)>, + max_backers_per_candidate: Option, balancing: Option, ) -> Result, crate::Error> { let (candidates, voters) = setup_inputs(candidates, voters); - let (candidates, mut voters) = seq_phragmen_core::(to_elect, candidates, voters)?; + let (candidates, mut voters) = + seq_phragmen_core::(to_elect, candidates, voters, max_backers_per_candidate)?; if let Some(ref config) = balancing { // NOTE: might create zero-edges, but we will strip them again when we convert voter into @@ -118,6 +120,7 @@ pub fn seq_phragmen_core( to_elect: usize, candidates: Vec>, mut voters: Vec>, + max_backers_per_candidate: Option, ) -> Result<(Vec>, Vec>), crate::Error> { // we have already checked that we have more candidates than minimum_candidate_count. let to_elect = to_elect.min(candidates.len()); @@ -138,10 +141,21 @@ pub fn seq_phragmen_core( } } - // loop 2: increment score - for voter in &voters { - for edge in &voter.edges { + // loop 2: increment score and the included backers of a candidate. + for voter in &mut voters { + for edge in &mut voter.edges { let mut candidate = edge.candidate.borrow_mut(); + + if (candidate.bounded_backers.len() as u32) >= + max_backers_per_candidate.unwrap_or(Bounded::max_value()) && + !candidate.bounded_backers.contains(&voter.who) + { + // if the candidate has reached max backers and the voter is not part of the + // bounded backers, taint the edge with skip and continue. + edge.skip = true; + continue + } + if !candidate.elected && !candidate.approval_stake.is_zero() { let temp_n = multiply_by_rational_with_rounding( voter.load.n(), @@ -153,6 +167,7 @@ pub fn seq_phragmen_core( let temp_d = voter.load.d(); let temp = Rational128::from(temp_n, temp_d); candidate.score = candidate.score.lazy_saturating_add(temp); + candidate.bounded_backers.push(voter.who.clone()); } } } @@ -183,6 +198,11 @@ pub fn seq_phragmen_core( // update backing stake of candidates and voters for voter in &mut voters { for edge in &mut voter.edges { + if edge.skip { + // skip this edge as its candidate has already reached max backers. + continue + } + if edge.candidate.borrow().elected { // update internal state. edge.weight = multiply_by_rational_with_rounding( diff --git a/substrate/primitives/npos-elections/src/pjr.rs b/substrate/primitives/npos-elections/src/pjr.rs index 6e3775199a219..a807aa740754d 100644 --- a/substrate/primitives/npos-elections/src/pjr.rs +++ b/substrate/primitives/npos-elections/src/pjr.rs @@ -294,6 +294,8 @@ fn prepare_pjr_input( score: Default::default(), approval_stake: Default::default(), round: Default::default(), + // TODO: check if we need to pass the bounds here. + bounded_backers: supports.iter().map(|(a, _)| a).cloned().collect(), } .to_ptr() }) @@ -324,6 +326,7 @@ fn prepare_pjr_input( candidate: Rc::clone(&candidates[*idx]), weight, load: Default::default(), + skip: false, }); } } @@ -402,6 +405,7 @@ mod tests { score: Default::default(), approval_stake: Default::default(), round: Default::default(), + bounded_backers: Default::default(), } }) .collect::>(); @@ -412,6 +416,7 @@ mod tests { weight: c.backed_stake, candidate: c.to_ptr(), load: Default::default(), + skip: false, }) .collect::>(); voter.edges = edges; @@ -454,6 +459,7 @@ mod tests { approval_stake: Default::default(), backed_stake: Default::default(), round: Default::default(), + bounded_backers: Default::default(), } .to_ptr(); let score = pre_score(unelected, &vec![v1, v2, v3], 15); diff --git a/substrate/primitives/npos-elections/src/tests.rs b/substrate/primitives/npos-elections/src/tests.rs index 72ae9a0222be1..416168efcde1d 100644 --- a/substrate/primitives/npos-elections/src/tests.rs +++ b/substrate/primitives/npos-elections/src/tests.rs @@ -101,7 +101,7 @@ fn phragmen_core_poc_works() { let voters = vec![(10, 10, vec![1, 2]), (20, 20, vec![1, 3]), (30, 30, vec![2, 3])]; let (candidates, voters) = setup_inputs(candidates, voters); - let (candidates, voters) = seq_phragmen_core(2, candidates, voters).unwrap(); + let (candidates, voters) = seq_phragmen_core(2, candidates, voters, None).unwrap(); assert_eq!( voters @@ -141,7 +141,7 @@ fn balancing_core_works() { ]; let (candidates, voters) = setup_inputs(candidates, voters); - let (candidates, mut voters) = seq_phragmen_core(4, candidates, voters).unwrap(); + let (candidates, mut voters) = seq_phragmen_core(4, candidates, voters, None).unwrap(); let config = BalancingConfig { iterations: 4, tolerance: 0 }; let iters = balancing::balance::(&mut voters, &config); @@ -236,6 +236,7 @@ fn phragmen_poc_works() { .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), None, + None, ) .unwrap(); @@ -277,6 +278,50 @@ fn phragmen_poc_works() { ); } +#[test] +fn phragmen_poc_works_with_max_backers_per_candidate() { + let candidates = vec![1, 2, 3]; + let voters = vec![(10, vec![1, 2]), (20, vec![1, 2, 3]), (30, vec![1, 2, 3])]; + let stake_of = create_stake_of(&[(10, 10), (20, 20), (30, 30)]); + + let run_election = |max_backers: Option| { + let ElectionResult::<_, Perbill> { winners: _, assignments } = seq_phragmen( + 3, + candidates.clone(), + voters + .iter() + .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) + .collect::>(), + max_backers, + None, + ) + .unwrap(); + + let staked = assignment_ratio_to_staked(assignments, &stake_of); + to_support_map::(&staked) + }; + + let with_unbounded_backers = run_election(None); + + assert_eq!(with_unbounded_backers.get(&1).unwrap().voters.len(), 3); + assert_eq!(with_unbounded_backers.get(&2).unwrap().voters.len(), 3); + assert_eq!(with_unbounded_backers.get(&3).unwrap().voters.len(), 2); + + // max 2 backers per candidate. + let with_bounded_backers = run_election(Some(2)); + + assert_eq!(with_bounded_backers.get(&1).unwrap().voters.len(), 2); + assert_eq!(with_bounded_backers.get(&2).unwrap().voters.len(), 2); + assert_eq!(with_bounded_backers.get(&3).unwrap().voters.len(), 2); + + // max 1 backers per candidate. + let with_bounded_backers = run_election(Some(1)); + + assert_eq!(with_bounded_backers.get(&1).unwrap().voters.len(), 1); + assert_eq!(with_bounded_backers.get(&2).unwrap().voters.len(), 1); + assert_eq!(with_bounded_backers.get(&3).unwrap().voters.len(), 1); +} + #[test] fn phragmen_poc_works_with_balancing() { let candidates = vec![1, 2, 3]; @@ -291,6 +336,7 @@ fn phragmen_poc_works_with_balancing() { .iter() .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), + None, Some(config), ) .unwrap(); @@ -340,10 +386,10 @@ fn phragmen_poc_2_works() { let stake_of = create_stake_of(&[(10, 1000), (20, 1000), (30, 1000), (40, 1000), (2, 500), (4, 500)]); - run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2); - run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2); - run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2); - run_and_compare::(candidates, voters, &stake_of, 2); + run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2, None); + run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2, None); + run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2, None); + run_and_compare::(candidates, voters, &stake_of, 2, None); } #[test] @@ -352,10 +398,10 @@ fn phragmen_poc_3_works() { let voters = vec![(2, vec![10, 20, 30]), (4, vec![10, 20, 40])]; let stake_of = create_stake_of(&[(10, 1000), (20, 1000), (30, 1000), (2, 50), (4, 1000)]); - run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2); - run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2); - run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2); - run_and_compare::(candidates, voters, &stake_of, 2); + run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2, None); + run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2, None); + run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2, None); + run_and_compare::(candidates, voters, &stake_of, 2, None); } #[test] @@ -379,6 +425,7 @@ fn phragmen_accuracy_on_large_scale_only_candidates() { .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), None, + None, ) .unwrap(); @@ -410,6 +457,7 @@ fn phragmen_accuracy_on_large_scale_voters_and_candidates() { .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), None, + None, ) .unwrap(); @@ -442,6 +490,7 @@ fn phragmen_accuracy_on_small_scale_self_vote() { .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), None, + None, ) .unwrap(); @@ -472,6 +521,7 @@ fn phragmen_accuracy_on_small_scale_no_self_vote() { .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), None, + None, ) .unwrap(); @@ -508,6 +558,7 @@ fn phragmen_large_scale_test() { .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), None, + None, ) .unwrap(); @@ -535,6 +586,7 @@ fn phragmen_large_scale_test_2() { .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), None, + None, ) .unwrap(); @@ -587,7 +639,7 @@ fn phragmen_linear_equalize() { (130, 1000), ]); - run_and_compare::(candidates, voters, &stake_of, 2); + run_and_compare::(candidates, voters, &stake_of, 2, None); } #[test] @@ -604,6 +656,7 @@ fn elect_has_no_entry_barrier() { .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), None, + None, ) .unwrap(); @@ -625,6 +678,7 @@ fn phragmen_self_votes_should_be_kept() { .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), None, + None, ) .unwrap(); @@ -664,7 +718,7 @@ fn duplicate_target_is_ignored() { let voters = vec![(10, 100, vec![1, 1, 2, 3]), (20, 100, vec![2, 3]), (30, 50, vec![1, 1, 2])]; let ElectionResult::<_, Perbill> { winners, assignments } = - seq_phragmen(2, candidates, voters, None).unwrap(); + seq_phragmen(2, candidates, voters, None, None).unwrap(); assert_eq!(winners, vec![(2, 140), (3, 110)]); assert_eq!( @@ -682,7 +736,7 @@ fn duplicate_target_is_ignored_when_winner() { let voters = vec![(10, 100, vec![1, 1, 2, 3]), (20, 100, vec![1, 2])]; let ElectionResult::<_, Perbill> { winners, assignments } = - seq_phragmen(2, candidates, voters, None).unwrap(); + seq_phragmen(2, candidates, voters, None, None).unwrap(); assert_eq!(winners, vec![(1, 100), (2, 100)]); assert_eq!( From e43e8859d140ad937e134981bb259d627889baf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 3 Dec 2024 23:04:56 +0100 Subject: [PATCH 062/169] addresses PR comments --- .../frame/election-provider-multi-phase/src/lib.rs | 1 + substrate/frame/staking/src/pallet/impls.rs | 13 ++++++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index 8676be438300f..6733bd15b357b 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -679,6 +679,7 @@ pub mod pallet { /// Maximum number of winners that an election supports. /// /// Note: This must always be greater or equal to `T::DataProvider::desired_targets()`. + #[pallet::constant] type MaxWinners: Get; /// Maximum number of voters that can support a winner in an election solution. diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index 785c688453dd4..fb30e3f497d1e 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -707,18 +707,17 @@ impl Pallet { }, _ => {}, } - // election failed, clear election prep metadata. Self::clear_election_metadata(); - Self::deposit_event(Event::StakingElectionFailed); - return None - } - Self::deposit_event(Event::StakersElected); - Self::trigger_new_era(start_session_index); + None + } else { + Self::deposit_event(Event::StakersElected); + Self::trigger_new_era(start_session_index); - Some(validators) + Some(validators) + } } /// Paginated elect. From 676db53d98580947e3f78f9d955669988d36ca80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 3 Dec 2024 23:24:25 +0100 Subject: [PATCH 063/169] Revert "Adds max backers per winner bounds check in the phragmen implementation (#6482)" This reverts commit 69b38e59d98769ed93a0da26570cc714fa66240e. --- .../election-provider-multi-phase/src/mock.rs | 16 +--- .../src/unsigned.rs | 95 +------------------ .../election-provider-support/src/lib.rs | 20 +--- .../election-provider-support/src/onchain.rs | 5 +- .../primitives/npos-elections/src/lib.rs | 11 +-- .../primitives/npos-elections/src/mock.rs | 2 - .../primitives/npos-elections/src/phragmen.rs | 28 +----- .../primitives/npos-elections/src/pjr.rs | 6 -- .../primitives/npos-elections/src/tests.rs | 80 +++------------- 9 files changed, 31 insertions(+), 232 deletions(-) diff --git a/substrate/frame/election-provider-multi-phase/src/mock.rs b/substrate/frame/election-provider-multi-phase/src/mock.rs index 100abc01c2d9d..4add202ebc045 100644 --- a/substrate/frame/election-provider-multi-phase/src/mock.rs +++ b/substrate/frame/election-provider-multi-phase/src/mock.rs @@ -153,8 +153,7 @@ pub fn trim_helpers() -> TrimHelpers { let desired_targets = crate::DesiredTargets::::get().unwrap(); let ElectionResult::<_, SolutionAccuracyOf> { mut assignments, .. } = - seq_phragmen(desired_targets as usize, targets.clone(), voters.clone(), None, None) - .unwrap(); + seq_phragmen(desired_targets as usize, targets.clone(), voters.clone(), None).unwrap(); // sort by decreasing order of stake assignments.sort_by_key(|assignment| { @@ -181,8 +180,7 @@ pub fn raw_solution() -> RawSolution> { let desired_targets = crate::DesiredTargets::::get().unwrap(); let ElectionResult::<_, SolutionAccuracyOf> { winners: _, assignments } = - seq_phragmen(desired_targets as usize, targets.clone(), voters.clone(), None, None) - .unwrap(); + seq_phragmen(desired_targets as usize, targets.clone(), voters.clone(), None).unwrap(); // closures let cache = helpers::generate_voter_cache::(&voters); @@ -310,8 +308,7 @@ parameter_types! { pub struct OnChainSeqPhragmen; impl onchain::Config for OnChainSeqPhragmen { type System = Runtime; - type Solver = - SequentialPhragmen, MaxBackersPerWinner, Balancing>; + type Solver = SequentialPhragmen, Balancing>; type DataProvider = StakingMock; type WeightInfo = (); type MaxWinnersPerPage = MaxWinners; @@ -423,8 +420,7 @@ impl crate::Config for Runtime { type MaxWinners = MaxWinners; type MaxBackersPerWinner = MaxBackersPerWinner; type MinerConfig = Self; - type Solver = - SequentialPhragmen, MaxBackersPerWinner, Balancing>; + type Solver = SequentialPhragmen, Balancing>; type ElectionBounds = ElectionsBounds; } @@ -611,10 +607,6 @@ impl ExtBuilder { ::set(weight); self } - pub fn max_backers_per_winner(self, max: u32) -> Self { - ::set(max); - self - } pub fn build(self) -> sp_io::TestExternalities { sp_tracing::try_init_simple(); let mut storage = diff --git a/substrate/frame/election-provider-multi-phase/src/unsigned.rs b/substrate/frame/election-provider-multi-phase/src/unsigned.rs index aa2b8f265bfbf..90b12343aaeb2 100644 --- a/substrate/frame/election-provider-multi-phase/src/unsigned.rs +++ b/substrate/frame/election-provider-multi-phase/src/unsigned.rs @@ -430,7 +430,7 @@ pub trait MinerConfig { /// The maximum number of winners that can be elected in the single page supported by this /// pallet. type MaxWinners: Get; - /// The maximum number of backers per winner in a solution. + /// The maximum number of backers per winner in the last solution. type MaxBackersPerWinner: Get; /// Something that can compute the weight of a solution. /// @@ -1865,99 +1865,6 @@ mod tests { }) } - #[test] - fn mine_solution_always_respects_max_backers_per_winner() { - use crate::mock::MaxBackersPerWinner; - use frame_election_provider_support::BoundedSupport; - - let targets = vec![10, 20, 30, 40]; - let voters = vec![ - (1, 10, bounded_vec![10, 20, 30]), - (2, 10, bounded_vec![10, 20, 30]), - (3, 10, bounded_vec![10, 20, 30]), - (4, 10, bounded_vec![10, 20, 30]), - (5, 10, bounded_vec![10, 20, 40]), - ]; - let snapshot = RoundSnapshot { voters: voters.clone(), targets: targets.clone() }; - let (round, desired_targets) = (1, 3); - - let expected_score_unbounded = - ElectionScore { minimal_stake: 12, sum_stake: 50, sum_stake_squared: 874 }; - let expected_score_bounded = - ElectionScore { minimal_stake: 2, sum_stake: 10, sum_stake_squared: 44 }; - - // solution without max_backers_per_winner set will be higher than the score when bounds - // are set, confirming the trimming when using the same snapshot state. - assert!(expected_score_unbounded > expected_score_bounded); - - // election with unbounded max backers per winnner. - ExtBuilder::default().max_backers_per_winner(u32::MAX).build_and_execute(|| { - assert_eq!(MaxBackersPerWinner::get(), u32::MAX); - - let solution = Miner::::mine_solution_with_snapshot::< - ::Solver, - >(voters.clone(), targets.clone(), desired_targets) - .unwrap() - .0; - - let ready_solution = Miner::::feasibility_check( - RawSolution { solution, score: expected_score_unbounded, round }, - Default::default(), - desired_targets, - snapshot.clone(), - round, - Default::default(), - ) - .unwrap(); - - assert_eq!( - ready_solution.supports.into_iter().collect::>(), - vec![ - ( - 10, - BoundedSupport { total: 21, voters: bounded_vec![(1, 10), (4, 8), (5, 3)] } - ), - (20, BoundedSupport { total: 17, voters: bounded_vec![(2, 10), (5, 7)] }), - (30, BoundedSupport { total: 12, voters: bounded_vec![(3, 10), (4, 2)] }), - ] - ); - }); - - // election with max 1 backer per winnner. - ExtBuilder::default().max_backers_per_winner(1).build_and_execute(|| { - assert_eq!(MaxBackersPerWinner::get(), 1); - - let solution = Miner::::mine_solution_with_snapshot::< - ::Solver, - >(voters, targets, desired_targets) - .unwrap() - .0; - - let ready_solution = Miner::::feasibility_check( - RawSolution { solution, score: expected_score_bounded, round }, - Default::default(), - desired_targets, - snapshot, - round, - Default::default(), - ) - .unwrap(); - - for (_, supports) in ready_solution.supports.iter() { - assert!((supports.voters.len() as u32) <= MaxBackersPerWinner::get()); - } - - assert_eq!( - ready_solution.supports.into_iter().collect::>(), - vec![ - (10, BoundedSupport { total: 6, voters: bounded_vec![(1, 6)] }), - (20, BoundedSupport { total: 2, voters: bounded_vec![(1, 2)] }), - (30, BoundedSupport { total: 2, voters: bounded_vec![(1, 2)] }), - ] - ); - }); - } - #[test] fn trim_assignments_length_does_not_modify_when_short_enough() { ExtBuilder::default().build_and_execute(|| { diff --git a/substrate/frame/election-provider-support/src/lib.rs b/substrate/frame/election-provider-support/src/lib.rs index 86bc085ca0a73..49bd533cc8c3c 100644 --- a/substrate/frame/election-provider-support/src/lib.rs +++ b/substrate/frame/election-provider-support/src/lib.rs @@ -670,16 +670,12 @@ pub trait NposSolver { /// A wrapper for [`sp_npos_elections::seq_phragmen`] that implements [`NposSolver`]. See the /// documentation of [`sp_npos_elections::seq_phragmen`] for more info. -pub struct SequentialPhragmen( - core::marker::PhantomData<(AccountId, Accuracy, MaxBackersPerWinner, Balancing)>, +pub struct SequentialPhragmen( + core::marker::PhantomData<(AccountId, Accuracy, Balancing)>, ); -impl< - AccountId: IdentifierT, - Accuracy: PerThing128, - MaxBackersPerWinner: Get>, - Balancing: Get>, - > NposSolver for SequentialPhragmen +impl>> + NposSolver for SequentialPhragmen { type AccountId = AccountId; type Accuracy = Accuracy; @@ -689,13 +685,7 @@ impl< targets: Vec, voters: Vec<(Self::AccountId, VoteWeight, impl IntoIterator)>, ) -> Result, Self::Error> { - sp_npos_elections::seq_phragmen( - winners, - targets, - voters, - MaxBackersPerWinner::get(), - Balancing::get(), - ) + sp_npos_elections::seq_phragmen(winners, targets, voters, Balancing::get()) } fn weight(voters: u32, targets: u32, vote_degree: u32) -> Weight { diff --git a/substrate/frame/election-provider-support/src/onchain.rs b/substrate/frame/election-provider-support/src/onchain.rs index 5e4f9b54984c7..379dccee2ce69 100644 --- a/substrate/frame/election-provider-support/src/onchain.rs +++ b/substrate/frame/election-provider-support/src/onchain.rs @@ -145,9 +145,8 @@ impl OnChainExecution { DispatchClass::Mandatory, ); - // defensive: Since npos solver returns a result always bounded by `desired_targets`, and - // ensures the maximum backers per winner, this is never expected to happen as long as npos - // solver does what is expected for it to do. + // defensive: Since npos solver returns a result always bounded by `desired_targets`, this + // is never expected to happen as long as npos solver does what is expected for it to do. let supports: BoundedSupportsOf = to_supports(&staked).try_into().map_err(|_| Error::TooManyWinners)?; diff --git a/substrate/primitives/npos-elections/src/lib.rs b/substrate/primitives/npos-elections/src/lib.rs index cfd8bc1d6656e..96af46e30f63f 100644 --- a/substrate/primitives/npos-elections/src/lib.rs +++ b/substrate/primitives/npos-elections/src/lib.rs @@ -247,9 +247,6 @@ pub struct Candidate { elected: bool, /// The round index at which this candidate was elected. round: usize, - /// A list of included backers for this candidate. This can be used to control the bounds of - /// maximum backers per candidate. - bounded_backers: Vec, } impl Candidate { @@ -272,8 +269,6 @@ pub struct Edge { candidate: CandidatePtr, /// The weight (i.e. stake given to `who`) of this edge. weight: ExtendedBalance, - /// Skips this edge. - skip: bool, } #[cfg(test)] @@ -281,14 +276,14 @@ impl Edge { fn new(candidate: Candidate, weight: ExtendedBalance) -> Self { let who = candidate.who.clone(); let candidate = Rc::new(RefCell::new(candidate)); - Self { weight, who, candidate, load: Default::default(), skip: false } + Self { weight, who, candidate, load: Default::default() } } } #[cfg(feature = "std")] impl core::fmt::Debug for Edge { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "Edge({:?}, weight = {:?}, skip = {})", self.who, self.weight, self.skip) + write!(f, "Edge({:?}, weight = {:?})", self.who, self.weight) } } @@ -561,7 +556,6 @@ pub fn setup_inputs( backed_stake: Default::default(), elected: Default::default(), round: Default::default(), - bounded_backers: Default::default(), } .to_ptr() }) @@ -586,7 +580,6 @@ pub fn setup_inputs( candidate: Rc::clone(&candidates[*idx]), load: Default::default(), weight: Default::default(), - skip: false, }); } // else {} would be wrong votes. We don't really care about it. } diff --git a/substrate/primitives/npos-elections/src/mock.rs b/substrate/primitives/npos-elections/src/mock.rs index a94803367fb40..91757404145f3 100644 --- a/substrate/primitives/npos-elections/src/mock.rs +++ b/substrate/primitives/npos-elections/src/mock.rs @@ -311,7 +311,6 @@ pub(crate) fn run_and_compare( voters: Vec<(AccountId, Vec)>, stake_of: FS, to_elect: usize, - max_backers_candidate: Option, ) where Output: PerThing128, FS: Fn(&AccountId) -> VoteWeight, @@ -324,7 +323,6 @@ pub(crate) fn run_and_compare( .iter() .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), - max_backers_candidate, None, ) .unwrap(); diff --git a/substrate/primitives/npos-elections/src/phragmen.rs b/substrate/primitives/npos-elections/src/phragmen.rs index c6c2246244ae1..f331152e722a2 100644 --- a/substrate/primitives/npos-elections/src/phragmen.rs +++ b/substrate/primitives/npos-elections/src/phragmen.rs @@ -71,13 +71,11 @@ pub fn seq_phragmen( to_elect: usize, candidates: Vec, voters: Vec<(AccountId, VoteWeight, impl IntoIterator)>, - max_backers_per_candidate: Option, balancing: Option, ) -> Result, crate::Error> { let (candidates, voters) = setup_inputs(candidates, voters); - let (candidates, mut voters) = - seq_phragmen_core::(to_elect, candidates, voters, max_backers_per_candidate)?; + let (candidates, mut voters) = seq_phragmen_core::(to_elect, candidates, voters)?; if let Some(ref config) = balancing { // NOTE: might create zero-edges, but we will strip them again when we convert voter into @@ -120,7 +118,6 @@ pub fn seq_phragmen_core( to_elect: usize, candidates: Vec>, mut voters: Vec>, - max_backers_per_candidate: Option, ) -> Result<(Vec>, Vec>), crate::Error> { // we have already checked that we have more candidates than minimum_candidate_count. let to_elect = to_elect.min(candidates.len()); @@ -141,21 +138,10 @@ pub fn seq_phragmen_core( } } - // loop 2: increment score and the included backers of a candidate. - for voter in &mut voters { - for edge in &mut voter.edges { + // loop 2: increment score + for voter in &voters { + for edge in &voter.edges { let mut candidate = edge.candidate.borrow_mut(); - - if (candidate.bounded_backers.len() as u32) >= - max_backers_per_candidate.unwrap_or(Bounded::max_value()) && - !candidate.bounded_backers.contains(&voter.who) - { - // if the candidate has reached max backers and the voter is not part of the - // bounded backers, taint the edge with skip and continue. - edge.skip = true; - continue - } - if !candidate.elected && !candidate.approval_stake.is_zero() { let temp_n = multiply_by_rational_with_rounding( voter.load.n(), @@ -167,7 +153,6 @@ pub fn seq_phragmen_core( let temp_d = voter.load.d(); let temp = Rational128::from(temp_n, temp_d); candidate.score = candidate.score.lazy_saturating_add(temp); - candidate.bounded_backers.push(voter.who.clone()); } } } @@ -198,11 +183,6 @@ pub fn seq_phragmen_core( // update backing stake of candidates and voters for voter in &mut voters { for edge in &mut voter.edges { - if edge.skip { - // skip this edge as its candidate has already reached max backers. - continue - } - if edge.candidate.borrow().elected { // update internal state. edge.weight = multiply_by_rational_with_rounding( diff --git a/substrate/primitives/npos-elections/src/pjr.rs b/substrate/primitives/npos-elections/src/pjr.rs index a807aa740754d..6e3775199a219 100644 --- a/substrate/primitives/npos-elections/src/pjr.rs +++ b/substrate/primitives/npos-elections/src/pjr.rs @@ -294,8 +294,6 @@ fn prepare_pjr_input( score: Default::default(), approval_stake: Default::default(), round: Default::default(), - // TODO: check if we need to pass the bounds here. - bounded_backers: supports.iter().map(|(a, _)| a).cloned().collect(), } .to_ptr() }) @@ -326,7 +324,6 @@ fn prepare_pjr_input( candidate: Rc::clone(&candidates[*idx]), weight, load: Default::default(), - skip: false, }); } } @@ -405,7 +402,6 @@ mod tests { score: Default::default(), approval_stake: Default::default(), round: Default::default(), - bounded_backers: Default::default(), } }) .collect::>(); @@ -416,7 +412,6 @@ mod tests { weight: c.backed_stake, candidate: c.to_ptr(), load: Default::default(), - skip: false, }) .collect::>(); voter.edges = edges; @@ -459,7 +454,6 @@ mod tests { approval_stake: Default::default(), backed_stake: Default::default(), round: Default::default(), - bounded_backers: Default::default(), } .to_ptr(); let score = pre_score(unelected, &vec![v1, v2, v3], 15); diff --git a/substrate/primitives/npos-elections/src/tests.rs b/substrate/primitives/npos-elections/src/tests.rs index 416168efcde1d..72ae9a0222be1 100644 --- a/substrate/primitives/npos-elections/src/tests.rs +++ b/substrate/primitives/npos-elections/src/tests.rs @@ -101,7 +101,7 @@ fn phragmen_core_poc_works() { let voters = vec![(10, 10, vec![1, 2]), (20, 20, vec![1, 3]), (30, 30, vec![2, 3])]; let (candidates, voters) = setup_inputs(candidates, voters); - let (candidates, voters) = seq_phragmen_core(2, candidates, voters, None).unwrap(); + let (candidates, voters) = seq_phragmen_core(2, candidates, voters).unwrap(); assert_eq!( voters @@ -141,7 +141,7 @@ fn balancing_core_works() { ]; let (candidates, voters) = setup_inputs(candidates, voters); - let (candidates, mut voters) = seq_phragmen_core(4, candidates, voters, None).unwrap(); + let (candidates, mut voters) = seq_phragmen_core(4, candidates, voters).unwrap(); let config = BalancingConfig { iterations: 4, tolerance: 0 }; let iters = balancing::balance::(&mut voters, &config); @@ -236,7 +236,6 @@ fn phragmen_poc_works() { .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), None, - None, ) .unwrap(); @@ -278,50 +277,6 @@ fn phragmen_poc_works() { ); } -#[test] -fn phragmen_poc_works_with_max_backers_per_candidate() { - let candidates = vec![1, 2, 3]; - let voters = vec![(10, vec![1, 2]), (20, vec![1, 2, 3]), (30, vec![1, 2, 3])]; - let stake_of = create_stake_of(&[(10, 10), (20, 20), (30, 30)]); - - let run_election = |max_backers: Option| { - let ElectionResult::<_, Perbill> { winners: _, assignments } = seq_phragmen( - 3, - candidates.clone(), - voters - .iter() - .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) - .collect::>(), - max_backers, - None, - ) - .unwrap(); - - let staked = assignment_ratio_to_staked(assignments, &stake_of); - to_support_map::(&staked) - }; - - let with_unbounded_backers = run_election(None); - - assert_eq!(with_unbounded_backers.get(&1).unwrap().voters.len(), 3); - assert_eq!(with_unbounded_backers.get(&2).unwrap().voters.len(), 3); - assert_eq!(with_unbounded_backers.get(&3).unwrap().voters.len(), 2); - - // max 2 backers per candidate. - let with_bounded_backers = run_election(Some(2)); - - assert_eq!(with_bounded_backers.get(&1).unwrap().voters.len(), 2); - assert_eq!(with_bounded_backers.get(&2).unwrap().voters.len(), 2); - assert_eq!(with_bounded_backers.get(&3).unwrap().voters.len(), 2); - - // max 1 backers per candidate. - let with_bounded_backers = run_election(Some(1)); - - assert_eq!(with_bounded_backers.get(&1).unwrap().voters.len(), 1); - assert_eq!(with_bounded_backers.get(&2).unwrap().voters.len(), 1); - assert_eq!(with_bounded_backers.get(&3).unwrap().voters.len(), 1); -} - #[test] fn phragmen_poc_works_with_balancing() { let candidates = vec![1, 2, 3]; @@ -336,7 +291,6 @@ fn phragmen_poc_works_with_balancing() { .iter() .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), - None, Some(config), ) .unwrap(); @@ -386,10 +340,10 @@ fn phragmen_poc_2_works() { let stake_of = create_stake_of(&[(10, 1000), (20, 1000), (30, 1000), (40, 1000), (2, 500), (4, 500)]); - run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2, None); - run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2, None); - run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2, None); - run_and_compare::(candidates, voters, &stake_of, 2, None); + run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2); + run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2); + run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2); + run_and_compare::(candidates, voters, &stake_of, 2); } #[test] @@ -398,10 +352,10 @@ fn phragmen_poc_3_works() { let voters = vec![(2, vec![10, 20, 30]), (4, vec![10, 20, 40])]; let stake_of = create_stake_of(&[(10, 1000), (20, 1000), (30, 1000), (2, 50), (4, 1000)]); - run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2, None); - run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2, None); - run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2, None); - run_and_compare::(candidates, voters, &stake_of, 2, None); + run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2); + run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2); + run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2); + run_and_compare::(candidates, voters, &stake_of, 2); } #[test] @@ -425,7 +379,6 @@ fn phragmen_accuracy_on_large_scale_only_candidates() { .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), None, - None, ) .unwrap(); @@ -457,7 +410,6 @@ fn phragmen_accuracy_on_large_scale_voters_and_candidates() { .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), None, - None, ) .unwrap(); @@ -490,7 +442,6 @@ fn phragmen_accuracy_on_small_scale_self_vote() { .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), None, - None, ) .unwrap(); @@ -521,7 +472,6 @@ fn phragmen_accuracy_on_small_scale_no_self_vote() { .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), None, - None, ) .unwrap(); @@ -558,7 +508,6 @@ fn phragmen_large_scale_test() { .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), None, - None, ) .unwrap(); @@ -586,7 +535,6 @@ fn phragmen_large_scale_test_2() { .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), None, - None, ) .unwrap(); @@ -639,7 +587,7 @@ fn phragmen_linear_equalize() { (130, 1000), ]); - run_and_compare::(candidates, voters, &stake_of, 2, None); + run_and_compare::(candidates, voters, &stake_of, 2); } #[test] @@ -656,7 +604,6 @@ fn elect_has_no_entry_barrier() { .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), None, - None, ) .unwrap(); @@ -678,7 +625,6 @@ fn phragmen_self_votes_should_be_kept() { .map(|(ref v, ref vs)| (*v, stake_of(v), vs.clone())) .collect::>(), None, - None, ) .unwrap(); @@ -718,7 +664,7 @@ fn duplicate_target_is_ignored() { let voters = vec![(10, 100, vec![1, 1, 2, 3]), (20, 100, vec![2, 3]), (30, 50, vec![1, 1, 2])]; let ElectionResult::<_, Perbill> { winners, assignments } = - seq_phragmen(2, candidates, voters, None, None).unwrap(); + seq_phragmen(2, candidates, voters, None).unwrap(); assert_eq!(winners, vec![(2, 140), (3, 110)]); assert_eq!( @@ -736,7 +682,7 @@ fn duplicate_target_is_ignored_when_winner() { let voters = vec![(10, 100, vec![1, 1, 2, 3]), (20, 100, vec![1, 2])]; let ElectionResult::<_, Perbill> { winners, assignments } = - seq_phragmen(2, candidates, voters, None, None).unwrap(); + seq_phragmen(2, candidates, voters, None).unwrap(); assert_eq!(winners, vec![(1, 100), (2, 100)]); assert_eq!( From fe8b9ae9604607effb6b4536d53406429ecbe716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 3 Dec 2024 23:26:36 +0100 Subject: [PATCH 064/169] adds max backers per winner trimming test --- .../election-provider-multi-phase/src/mock.rs | 4 + .../src/unsigned.rs | 93 +++++++++++++++++++ 2 files changed, 97 insertions(+) diff --git a/substrate/frame/election-provider-multi-phase/src/mock.rs b/substrate/frame/election-provider-multi-phase/src/mock.rs index 4add202ebc045..20fe4016375c8 100644 --- a/substrate/frame/election-provider-multi-phase/src/mock.rs +++ b/substrate/frame/election-provider-multi-phase/src/mock.rs @@ -607,6 +607,10 @@ impl ExtBuilder { ::set(weight); self } + pub fn max_backers_per_winner(self, max: u32) -> Self { + MaxBackersPerWinner::set(max); + self + } pub fn build(self) -> sp_io::TestExternalities { sp_tracing::try_init_simple(); let mut storage = diff --git a/substrate/frame/election-provider-multi-phase/src/unsigned.rs b/substrate/frame/election-provider-multi-phase/src/unsigned.rs index 90b12343aaeb2..2e0767b28d950 100644 --- a/substrate/frame/election-provider-multi-phase/src/unsigned.rs +++ b/substrate/frame/election-provider-multi-phase/src/unsigned.rs @@ -1865,6 +1865,99 @@ mod tests { }) } + #[test] + fn mine_solution_always_respects_max_backers_per_winner() { + use crate::mock::MaxBackersPerWinner; + use frame_election_provider_support::BoundedSupport; + + let targets = vec![10, 20, 30, 40]; + let voters = vec![ + (1, 10, bounded_vec![10, 20, 30]), + (2, 10, bounded_vec![10, 20, 30]), + (3, 10, bounded_vec![10, 20, 30]), + (4, 10, bounded_vec![10, 20, 30]), + (5, 10, bounded_vec![10, 20, 40]), + ]; + let snapshot = RoundSnapshot { voters: voters.clone(), targets: targets.clone() }; + let (round, desired_targets) = (1, 3); + + let expected_score_unbounded = + ElectionScore { minimal_stake: 12, sum_stake: 50, sum_stake_squared: 874 }; + let expected_score_bounded = + ElectionScore { minimal_stake: 2, sum_stake: 10, sum_stake_squared: 44 }; + + // solution without max_backers_per_winner set will be higher than the score when bounds + // are set, confirming the trimming when using the same snapshot state. + assert!(expected_score_unbounded > expected_score_bounded); + + // election with unbounded max backers per winnner. + ExtBuilder::default().max_backers_per_winner(u32::MAX).build_and_execute(|| { + assert_eq!(MaxBackersPerWinner::get(), u32::MAX); + + let solution = Miner::::mine_solution_with_snapshot::< + ::Solver, + >(voters.clone(), targets.clone(), desired_targets) + .unwrap() + .0; + + let ready_solution = Miner::::feasibility_check( + RawSolution { solution, score: expected_score_unbounded, round }, + Default::default(), + desired_targets, + snapshot.clone(), + round, + Default::default(), + ) + .unwrap(); + + assert_eq!( + ready_solution.supports.into_iter().collect::>(), + vec![ + ( + 10, + BoundedSupport { total: 21, voters: bounded_vec![(1, 10), (4, 8), (5, 3)] } + ), + (20, BoundedSupport { total: 17, voters: bounded_vec![(2, 10), (5, 7)] }), + (30, BoundedSupport { total: 12, voters: bounded_vec![(3, 10), (4, 2)] }), + ] + ); + }); + + // election with max 1 backer per winnner. + ExtBuilder::default().max_backers_per_winner(1).build_and_execute(|| { + assert_eq!(MaxBackersPerWinner::get(), 1); + + let solution = Miner::::mine_solution_with_snapshot::< + ::Solver, + >(voters, targets, desired_targets) + .unwrap() + .0; + + let ready_solution = Miner::::feasibility_check( + RawSolution { solution, score: expected_score_bounded, round }, + Default::default(), + desired_targets, + snapshot, + round, + Default::default(), + ) + .unwrap(); + + for (_, supports) in ready_solution.supports.iter() { + assert!((supports.voters.len() as u32) <= MaxBackersPerWinner::get()); + } + + assert_eq!( + ready_solution.supports.into_iter().collect::>(), + vec![ + (10, BoundedSupport { total: 6, voters: bounded_vec![(1, 6)] }), + (20, BoundedSupport { total: 2, voters: bounded_vec![(1, 2)] }), + (30, BoundedSupport { total: 2, voters: bounded_vec![(1, 2)] }), + ] + ); + }); + } + #[test] fn trim_assignments_length_does_not_modify_when_short_enough() { ExtBuilder::default().build_and_execute(|| { From 84eea26a8edafcbbb22d7d0abdac6aa33ca2a875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Thu, 5 Dec 2024 13:13:50 +0100 Subject: [PATCH 065/169] Ensures max backers per winner bounds are met in Staking miner (#6771) Ensures max backers per winner bounds are met in Staking miner. This PR replaces https://github.com/paritytech/polkadot-sdk/pull/6482 (already reverted in the base branch) and moves the trimming logic when max backer per winner exceed configured bounds to the miner. --- .../src/unsigned.rs | 208 ++++++++++++++++-- 1 file changed, 189 insertions(+), 19 deletions(-) diff --git a/substrate/frame/election-provider-multi-phase/src/unsigned.rs b/substrate/frame/election-provider-multi-phase/src/unsigned.rs index 2e0767b28d950..6c2d55cac3dea 100644 --- a/substrate/frame/election-provider-multi-phase/src/unsigned.rs +++ b/substrate/frame/election-provider-multi-phase/src/unsigned.rs @@ -112,16 +112,20 @@ impl From for MinerError { } } -/// Reports the trimming result of a mined solution +/// Reports the trimming result of a mined solution. #[derive(Debug, Clone)] pub struct TrimmingStatus { + /// Number of voters trimmed due to the solution weight limits. weight: usize, + /// Number of voters trimmed due to the solution length limits. length: usize, + /// Number of edges (voter -> target) trimmed due to the max backers per winner bound. + edges: usize, } impl TrimmingStatus { pub fn is_trimmed(&self) -> bool { - self.weight > 0 || self.length > 0 + self.weight > 0 || self.length > 0 || self.edges > 0 } pub fn trimmed_weight(&self) -> usize { @@ -131,6 +135,10 @@ impl TrimmingStatus { pub fn trimmed_length(&self) -> usize { self.length } + + pub fn trimmed_edges(&self) -> usize { + self.edges + } } /// Save a given call into OCW storage. @@ -493,7 +501,11 @@ impl Miner { let ElectionResult { assignments, winners: _ } = election_result; - // Reduce (requires round-trip to staked form) + // keeps track of how many edges were trimmed out. + let mut edges_trimmed = 0; + + // Reduce (requires round-trip to staked form) and ensures the max backer per winner bound + // requirements are met. let sorted_assignments = { // convert to staked and reduce. let mut staked = assignment_ratio_to_staked_normalized(assignments, &stake_of)?; @@ -520,6 +532,57 @@ impl Miner { }, ); + // ensures that the max backers per winner bounds are respected given the supports + // generated from the assignments. We achieve that by removing edges (voter -> + // target) in the assignments with lower stake until the total number of backers per + // winner fits within the expected bounded supports. This should be performed *after* + // applying reduce over the assignments to avoid over-trimming. + // + // a potential trimming does not affect the desired targets of the solution as the + // targets have *too many* edges by definition if trimmed. + let max_backers_per_winner = T::MaxBackersPerWinner::get().saturated_into::(); + + let _ = sp_npos_elections::to_supports(&staked) + .iter_mut() + .filter(|(_, support)| support.voters.len() > max_backers_per_winner) + .for_each(|(target, ref mut support)| { + // first sort by support stake, lowest at the tail. + support.voters.sort_by(|a, b| b.1.cmp(&a.1)); + + // filter out lowest stake edge in this support. + // optimization note: collects edge voters to remove from assignments into a + // btree set to optimize the search in the next loop. + let filtered: std::collections::BTreeSet<_> = support + .voters + .split_off(max_backers_per_winner) + .into_iter() + .map(|(who, stake)| { + // update total support of the target where the edge will be removed. + support.total -= stake; + who + }) + .collect(); + + // remove lowest stake edges calculated above from assignments. + staked.iter_mut().for_each(|assignment| { + if filtered.contains(&assignment.who) { + assignment.distribution.retain(|(t, _)| t != target); + } + }); + + edges_trimmed += filtered.len(); + }); + + debug_assert!({ + // at this point we expect the supports generated from the assignments to fit within + // the expected bounded supports. + let expected_ok: Result< + crate::BoundedSupports<_, T::MaxWinners, T::MaxBackersPerWinner>, + _, + > = sp_npos_elections::to_supports(&staked).try_into(); + expected_ok.is_ok() + }); + // convert back. assignment_staked_to_ratio_normalized(staked)? }; @@ -552,7 +615,8 @@ impl Miner { // re-calc score. let score = solution.clone().score(stake_of, voter_at, target_at)?; - let is_trimmed = TrimmingStatus { weight: weight_trimmed, length: length_trimmed }; + let is_trimmed = + TrimmingStatus { weight: weight_trimmed, length: length_trimmed, edges: edges_trimmed }; Ok((solution, score, size, is_trimmed)) } @@ -817,9 +881,12 @@ impl Miner { // Finally, check that the claimed score was indeed correct. let known_score = supports.evaluate(); + ensure!(known_score == score, FeasibilityError::InvalidScore); - // Size of winners in miner solution is equal to `desired_targets` <= `MaxWinners`. + // Size of winners in miner solution is equal to `desired_targets` <= `MaxWinners`. In + // addition, the miner should have ensured that the MaxBackerPerWinner bound in respected, + // thus this conversion should not fail. let supports = supports .try_into() .defensive_map_err(|_| FeasibilityError::BoundedConversionFailed)?; @@ -1884,7 +1951,7 @@ mod tests { let expected_score_unbounded = ElectionScore { minimal_stake: 12, sum_stake: 50, sum_stake_squared: 874 }; let expected_score_bounded = - ElectionScore { minimal_stake: 2, sum_stake: 10, sum_stake_squared: 44 }; + ElectionScore { minimal_stake: 10, sum_stake: 30, sum_stake_squared: 300 }; // solution without max_backers_per_winner set will be higher than the score when bounds // are set, confirming the trimming when using the same snapshot state. @@ -1894,11 +1961,13 @@ mod tests { ExtBuilder::default().max_backers_per_winner(u32::MAX).build_and_execute(|| { assert_eq!(MaxBackersPerWinner::get(), u32::MAX); - let solution = Miner::::mine_solution_with_snapshot::< - ::Solver, - >(voters.clone(), targets.clone(), desired_targets) - .unwrap() - .0; + let (solution, _, _, trimming_status) = + Miner::::mine_solution_with_snapshot::<::Solver>( + voters.clone(), + targets.clone(), + desired_targets, + ) + .unwrap(); let ready_solution = Miner::::feasibility_check( RawSolution { solution, score: expected_score_unbounded, round }, @@ -1921,17 +1990,22 @@ mod tests { (30, BoundedSupport { total: 12, voters: bounded_vec![(3, 10), (4, 2)] }), ] ); + + // no trimmed edges. + assert_eq!(trimming_status.trimmed_edges(), 0); }); // election with max 1 backer per winnner. ExtBuilder::default().max_backers_per_winner(1).build_and_execute(|| { assert_eq!(MaxBackersPerWinner::get(), 1); - let solution = Miner::::mine_solution_with_snapshot::< - ::Solver, - >(voters, targets, desired_targets) - .unwrap() - .0; + let (solution, _, _, trimming_status) = + Miner::::mine_solution_with_snapshot::<::Solver>( + voters, + targets, + desired_targets, + ) + .unwrap(); let ready_solution = Miner::::feasibility_check( RawSolution { solution, score: expected_score_bounded, round }, @@ -1950,14 +2024,110 @@ mod tests { assert_eq!( ready_solution.supports.into_iter().collect::>(), vec![ - (10, BoundedSupport { total: 6, voters: bounded_vec![(1, 6)] }), - (20, BoundedSupport { total: 2, voters: bounded_vec![(1, 2)] }), - (30, BoundedSupport { total: 2, voters: bounded_vec![(1, 2)] }), + (10, BoundedSupport { total: 10, voters: bounded_vec![(1, 10)] }), + (20, BoundedSupport { total: 10, voters: bounded_vec![(2, 10)] }), + (30, BoundedSupport { total: 10, voters: bounded_vec![(3, 10)] }), ] ); + + // four trimmed edges. + assert_eq!(trimming_status.trimmed_edges(), 4); }); } + #[test] + fn max_backers_edges_trims_lowest_stake() { + use crate::mock::MaxBackersPerWinner; + + ExtBuilder::default().build_and_execute(|| { + let targets = vec![10, 20, 30, 40]; + + let voters = vec![ + (1, 100, bounded_vec![10, 20]), + (2, 200, bounded_vec![10, 20, 30]), + (3, 300, bounded_vec![10, 30]), + (4, 400, bounded_vec![10, 30]), + (5, 500, bounded_vec![10, 20, 30]), + (6, 600, bounded_vec![10, 20, 30, 40]), + ]; + let snapshot = RoundSnapshot { voters: voters.clone(), targets: targets.clone() }; + let (round, desired_targets) = (1, 4); + + let max_backers_bound = u32::MAX; + let trim_backers_bound = 2; + + // election with unbounded max backers per winnner. + MaxBackersPerWinner::set(max_backers_bound); + let (solution, score, _, trimming_status) = + Miner::::mine_solution_with_snapshot::<::Solver>( + voters.clone(), + targets.clone(), + desired_targets, + ) + .unwrap(); + + assert_eq!(trimming_status.trimmed_edges(), 0); + + let ready_solution = Miner::::feasibility_check( + RawSolution { solution, score, round }, + Default::default(), + desired_targets, + snapshot.clone(), + round, + Default::default(), + ) + .unwrap(); + + let full_supports = ready_solution.supports.into_iter().collect::>(); + + // gather the expected trimmed supports (lowest stake from supports with more backers + // than expected when MaxBackersPerWinner is 2) from the full, unbounded supports. + let expected_trimmed_supports = full_supports + .into_iter() + .filter(|(_, s)| s.voters.len() as u32 > trim_backers_bound) + .map(|(t, s)| (t, s.voters.into_iter().min_by(|a, b| a.1.cmp(&b.1)).unwrap())) + .collect::>(); + + // election with bounded 2 max backers per winnner. + MaxBackersPerWinner::set(trim_backers_bound); + let (solution, score, _, trimming_status) = + Miner::::mine_solution_with_snapshot::<::Solver>( + voters.clone(), + targets.clone(), + desired_targets, + ) + .unwrap(); + + assert_eq!(trimming_status.trimmed_edges(), 2); + + let ready_solution = Miner::::feasibility_check( + RawSolution { solution, score, round }, + Default::default(), + desired_targets, + snapshot.clone(), + round, + Default::default(), + ) + .unwrap(); + + let trimmed_supports = ready_solution.supports.into_iter().collect::>(); + + // gather all trimmed_supports edges from the trimmed solution. + let mut trimmed_supports_edges_full = vec![]; + for (t, s) in trimmed_supports { + for v in s.voters { + trimmed_supports_edges_full.push((t, v)); + } + } + + // expected trimmed supports set should be disjoint to the trimmed_supports full set of + // edges. + for edge in trimmed_supports_edges_full { + assert!(!expected_trimmed_supports.contains(&edge)); + } + }) + } + #[test] fn trim_assignments_length_does_not_modify_when_short_enough() { ExtBuilder::default().build_and_execute(|| { From 478857933b031e5d703a8c9a1ed6047918ef4d90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Mon, 9 Dec 2024 13:09:03 +0100 Subject: [PATCH 066/169] fixes overflowing tests --- substrate/frame/staking/src/pallet/mod.rs | 12 ++++++-- substrate/frame/staking/src/tests.rs | 36 +++++++++++++++++------ 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index 31d25be0bd6bc..0966c0a42d669 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -917,11 +917,17 @@ pub mod pallet { mode: Forcing, }, /// Report of a controller batch deprecation. - ControllerBatchDeprecated { failures: u32 }, + ControllerBatchDeprecated { + failures: u32, + }, /// Validator has been disabled. - ValidatorDisabled { stash: T::AccountId }, + ValidatorDisabled { + stash: T::AccountId, + }, /// Validator has been re-enabled. - ValidatorReenabled { stash: T::AccountId }, + ValidatorReenabled { + stash: T::AccountId, + }, } #[pallet::error] diff --git a/substrate/frame/staking/src/tests.rs b/substrate/frame/staking/src/tests.rs index c319c1c9a148d..82a5ac2f4e843 100644 --- a/substrate/frame/staking/src/tests.rs +++ b/substrate/frame/staking/src/tests.rs @@ -2321,7 +2321,7 @@ fn phragmen_should_not_overflow() { #[test] fn reward_validator_slashing_validator_does_not_overflow() { - ExtBuilder::default().build_and_execute(|| { + ExtBuilder::default().nominate(false).build_and_execute(|| { let stake = u64::MAX as Balance * 2; let reward_slash = u64::MAX as Balance * 2; @@ -2339,7 +2339,19 @@ fn reward_validator_slashing_validator_does_not_overflow() { // Check reward ErasRewardPoints::::insert(0, reward); - EraInfo::::upsert_exposure(0, &11, exposure); + + // force exposure metadata to account for the overflowing `stake`. + ErasStakersOverview::::insert( + current_era(), + 11, + PagedExposureMetadata { total: stake, own: stake, nominator_count: 0, page_count: 0 }, + ); + + // we want to slash only self-stake, confirm that no others exposed. + let full_exposure_after = EraInfo::::get_full_exposure(current_era(), &11); + assert_eq!(full_exposure_after.total, stake); + assert_eq!(full_exposure_after.others, vec![]); + ErasValidatorReward::::insert(0, stake); assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 0, 0)); assert_eq!(asset::total_balance::(&11), stake * 2); @@ -2350,13 +2362,19 @@ fn reward_validator_slashing_validator_does_not_overflow() { // only slashes out of bonded stake are applied. without this line, it is 0. Staking::bond(RuntimeOrigin::signed(2), stake - 1, RewardDestination::Staked).unwrap(); - // Override exposure of 11 - EraInfo::::upsert_exposure( - 0, - &11, - Exposure { - total: stake, - own: 1, + + // Override metadata and exposures of 11 so that it exposes minmal self stake and `stake` - + // 1 from nominator 2. + ErasStakersOverview::::insert( + current_era(), + 11, + PagedExposureMetadata { total: stake, own: 1, nominator_count: 1, page_count: 1 }, + ); + + ErasStakersPaged::::insert( + (current_era(), &11, 0), + ExposurePage { + page_total: stake - 1, others: vec![IndividualExposure { who: 2, value: stake - 1 }], }, ); From b792a6d8290cb363b5111b41dc55f4b06662e094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Wed, 11 Dec 2024 12:55:29 +0000 Subject: [PATCH 067/169] adds benchs and nits --- substrate/bin/node/runtime/src/lib.rs | 9 ++- .../src/unsigned.rs | 2 +- .../election-provider-support/src/lib.rs | 1 - .../frame/session/benchmarking/src/inner.rs | 2 + substrate/frame/staking/src/benchmarking.rs | 78 +++++++++++++++++-- substrate/frame/staking/src/pallet/impls.rs | 12 ++- substrate/frame/staking/src/pallet/mod.rs | 25 ++---- substrate/frame/staking/src/tests.rs | 1 - .../frame/staking/src/tests_paged_election.rs | 22 +++++- substrate/frame/staking/src/weights.rs | 27 ++++++- 10 files changed, 146 insertions(+), 33 deletions(-) diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index faffcd23fbcf9..deafb01a19666 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -732,6 +732,7 @@ impl pallet_staking::Config for Runtime { type EraPayout = pallet_staking::ConvertCurve; type NextNewSession = Session; type MaxExposurePageSize = ConstU32<256>; + type MaxValidatorSet = MaxActiveValidators; type ElectionProvider = ElectionProviderMultiPhase; type GenesisElectionProvider = onchain::OnChainExecution; type VoterList = VoterList; @@ -805,6 +806,8 @@ parameter_types! { // The maximum winners that can be elected by the Election pallet which is equivalent to the // maximum active validators the staking pallet can have. pub MaxActiveValidators: u32 = 1000; + // Unbounded number of backers per winner in the election solution. + pub MaxBackersPerWinner: u32 = u32::MAX; } /// The numbers configured here could always be more than the the maximum limits of staking pallet @@ -855,8 +858,10 @@ impl onchain::Config for OnChainSeqPhragmen { >; type DataProvider = ::DataProvider; type WeightInfo = frame_election_provider_support::weights::SubstrateWeight; - type MaxWinners = ::MaxWinners; type Bounds = ElectionBoundsOnChain; + type MaxBackersPerWinner = + ::MaxBackersPerWinner; + type MaxWinnersPerPage = MaxActiveValidators; } impl pallet_election_provider_multi_phase::MinerConfig for Runtime { @@ -867,6 +872,7 @@ impl pallet_election_provider_multi_phase::MinerConfig for Runtime { type MaxVotesPerVoter = <::DataProvider as ElectionDataProvider>::MaxVotesPerVoter; type MaxWinners = MaxActiveValidators; + type MaxBackersPerWinner = MaxBackersPerWinner; // The unsigned submissions have to respect the weight of the submit_unsigned call, thus their // weight estimate function is wired to this call's weight. @@ -905,6 +911,7 @@ impl pallet_election_provider_multi_phase::Config for Runtime { type Solver = SequentialPhragmen, OffchainRandomBalancing>; type ForceOrigin = EnsureRootOrHalfCouncil; type MaxWinners = MaxActiveValidators; + type MaxBackersPerWinner = MaxBackersPerWinner; type ElectionBounds = ElectionBoundsMultiPhase; type BenchmarkingConfig = ElectionProviderBenchmarkConfig; type WeightInfo = pallet_election_provider_multi_phase::weights::SubstrateWeight; diff --git a/substrate/frame/election-provider-multi-phase/src/unsigned.rs b/substrate/frame/election-provider-multi-phase/src/unsigned.rs index 6c2d55cac3dea..1a1245dbfd435 100644 --- a/substrate/frame/election-provider-multi-phase/src/unsigned.rs +++ b/substrate/frame/election-provider-multi-phase/src/unsigned.rs @@ -552,7 +552,7 @@ impl Miner { // filter out lowest stake edge in this support. // optimization note: collects edge voters to remove from assignments into a // btree set to optimize the search in the next loop. - let filtered: std::collections::BTreeSet<_> = support + let filtered: alloc::collections::BTreeSet<_> = support .voters .split_off(max_backers_per_winner) .into_iter() diff --git a/substrate/frame/election-provider-support/src/lib.rs b/substrate/frame/election-provider-support/src/lib.rs index 49bd533cc8c3c..8b2edf4452a87 100644 --- a/substrate/frame/election-provider-support/src/lib.rs +++ b/substrate/frame/election-provider-support/src/lib.rs @@ -442,7 +442,6 @@ pub trait ElectionProvider { /// /// The result is returned in a target major format, namely as vector of supports. /// - /// TODO(gpestana): remove self-weighing? /// This should be implemented as a self-weighing function. The implementor should register its /// appropriate weight at the end of execution with the system pallet directly. fn elect(page: PageIndex) -> Result, Self::Error>; diff --git a/substrate/frame/session/benchmarking/src/inner.rs b/substrate/frame/session/benchmarking/src/inner.rs index 9789b6bb593d0..4c35f10789e9e 100644 --- a/substrate/frame/session/benchmarking/src/inner.rs +++ b/substrate/frame/session/benchmarking/src/inner.rs @@ -58,6 +58,7 @@ mod benchmarks { false, true, RewardDestination::Staked, + pallet_staking::CurrentEra::::get().unwrap(), )?; let v_controller = pallet_staking::Pallet::::bonded(&v_stash).ok_or("not stash")?; @@ -82,6 +83,7 @@ mod benchmarks { false, true, RewardDestination::Staked, + pallet_staking::CurrentEra::::get().unwrap(), )?; let v_controller = pallet_staking::Pallet::::bonded(&v_stash).ok_or("not stash")?; let keys = T::Keys::decode(&mut TrailingZeroInput::zeroes()).unwrap(); diff --git a/substrate/frame/staking/src/benchmarking.rs b/substrate/frame/staking/src/benchmarking.rs index 954f1bd02b47d..606c93e3fc00b 100644 --- a/substrate/frame/staking/src/benchmarking.rs +++ b/substrate/frame/staking/src/benchmarking.rs @@ -74,6 +74,7 @@ pub fn create_validator_with_nominators( dead_controller: bool, unique_controller: bool, destination: RewardDestination, + era: u32, ) -> Result<(T::AccountId, Vec<(T::AccountId, T::AccountId)>), &'static str> { // Clean up any existing state. clear_validators_and_nominators::(); @@ -129,14 +130,13 @@ pub fn create_validator_with_nominators( individual: points_individual.into_iter().collect(), }; - let current_era = CurrentEra::::get().unwrap(); - ErasRewardPoints::::insert(current_era, reward); + ErasRewardPoints::::insert(era, reward); // Create reward pool let total_payout = asset::existential_deposit::() .saturating_mul(upper_bound.into()) .saturating_mul(1000u32.into()); - >::insert(current_era, total_payout); + >::insert(era, total_payout); Ok((v_stash, nominators)) } @@ -224,6 +224,67 @@ const USER_SEED: u32 = 999666; mod benchmarks { use super::*; + #[benchmark] + fn on_initialize_noop() { + assert!(ElectableStashes::::get().is_empty()); + assert_eq!(ElectingStartedAt::::get(), None); + + #[block] + { + Pallet::::on_initialize(1_u32.into()); + } + + assert!(ElectableStashes::::get().is_empty()); + assert_eq!(ElectingStartedAt::::get(), None); + } + + #[benchmark] + fn do_elect_paged(v: Linear<1, { T::MaxValidatorSet::get() }>) -> Result<(), BenchmarkError> { + assert!(ElectableStashes::::get().is_empty()); + + create_validators_with_nominators_for_era::( + v, + 100, + MaxNominationsOf::::get() as usize, + false, + None, + )?; + + #[block] + { + Pallet::::do_elect_paged(0u32); + } + + assert!(!ElectableStashes::::get().is_empty()); + + Ok(()) + } + + #[benchmark] + fn clear_election_metadata( + v: Linear<1, { T::MaxValidatorSet::get() }>, + ) -> Result<(), BenchmarkError> { + use frame_support::BoundedBTreeSet; + + let mut stashes: BoundedBTreeSet = BoundedBTreeSet::new(); + for u in (0..v).into_iter() { + frame_support::assert_ok!(stashes.try_insert(account("stash", u, SEED))); + } + + ElectableStashes::::set(stashes); + ElectingStartedAt::::set(Some(10u32.into())); + + #[block] + { + Pallet::::clear_election_metadata() + } + + assert!(ElectingStartedAt::::get().is_none()); + assert!(ElectableStashes::::get().is_empty()); + + Ok(()) + } + #[benchmark] fn bond() { let stash = create_funded_user::("stash", USER_SEED, 100); @@ -696,15 +757,20 @@ mod benchmarks { fn payout_stakers_alive_staked( n: Linear<0, { T::MaxExposurePageSize::get() as u32 }>, ) -> Result<(), BenchmarkError> { + // reset genesis era 0 so that triggering the new genesis era works as expected. + CurrentEra::::set(Some(0)); + let current_era = CurrentEra::::get().unwrap(); + Staking::::clear_era_information(current_era); + let (validator, nominators) = create_validator_with_nominators::( n, T::MaxExposurePageSize::get() as u32, false, true, RewardDestination::Staked, + current_era, )?; - let current_era = CurrentEra::::get().unwrap(); // set the commission for this particular era as well. >::insert( current_era, @@ -989,7 +1055,7 @@ mod benchmarks { #[block] { // default bounds are unbounded. - targets = >::get_npos_targets(DataProviderBounds::default(), SINGLE_PAGE); + targets = >::get_npos_targets(DataProviderBounds::default()); } assert_eq!(targets.len() as u32, v); @@ -1185,6 +1251,7 @@ mod tests { false, false, RewardDestination::Staked, + CurrentEra::::get().unwrap(), ) .unwrap(); @@ -1217,6 +1284,7 @@ mod tests { false, false, RewardDestination::Staked, + CurrentEra::::get().unwrap(), ) .unwrap(); diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index fb30e3f497d1e..5244af5081285 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -271,6 +271,7 @@ impl Pallet { })?; let history_depth = T::HistoryDepth::get(); + ensure!( era <= current_era && era >= current_era.saturating_sub(history_depth), Error::::InvalidEraToReward @@ -731,22 +732,24 @@ impl Pallet { /// If any new election winner does not fit in the electable stashes storage, it truncates the /// result of the election. We ensure that only the winners that are part of the electable /// stashes have exposures collected for the next era. - pub(crate) fn do_elect_paged(page: PageIndex) { + pub(crate) fn do_elect_paged(page: PageIndex) -> Weight { let paged_result = match ::elect(page) { Ok(result) => result, Err(e) => { log!(warn, "election provider page failed due to {:?} (page: {})", e, page); // election failed, clear election prep metadata. Self::clear_election_metadata(); - Self::deposit_event(Event::StakingElectionFailed); - return + + return T::WeightInfo::clear_election_metadata(); }, }; if let Err(_) = Self::do_elect_paged_inner(paged_result) { defensive!("electable stashes exceeded limit, unexpected but election proceeds."); }; + + T::WeightInfo::do_elect_paged(T::MaxValidatorSet::get()) } /// Inner implementation of [`Self::do_elect_paged`]. @@ -805,6 +808,7 @@ impl Pallet { // accumulate total stake. total_stake_page = total_stake_page.saturating_add(exposure.total); // set or update staker exposure for this era. + EraInfo::::upsert_exposure(new_planned_era, &stash, exposure); }); @@ -839,7 +843,7 @@ impl Pallet { /// /// Returns vec of all the exposures of a validator in `paged_supports`, bounded by the number /// of max winners per page returned by the election provider. - fn collect_exposures( + pub(crate) fn collect_exposures( supports: BoundedSupportsOf, ) -> BoundedVec< (T::AccountId, Exposure>), diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index 0966c0a42d669..8785b7e59c1fc 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -1010,20 +1010,11 @@ pub mod pallet { let pages: BlockNumberFor = Self::election_pages().into(); // election ongoing, fetch the next page. - if let Some(started_at) = ElectingStartedAt::::get() { + let inner_weight = if let Some(started_at) = ElectingStartedAt::::get() { let next_page = pages.saturating_sub(One::one()).saturating_sub(now.saturating_sub(started_at)); - // note: this pallet is expected to fetch all the solution pages starting from the - // most significant one through to the page 0. Fetching page zero is an indication - // that all the solution pages have been fetched. - if next_page == Zero::zero() { - crate::log!(trace, "elect(): finished fetching all paged solutions."); - Self::do_elect_paged(Zero::zero()); - } else { - crate::log!(trace, "elect(): progressing, {:?} remaining pages.", next_page); - Self::do_elect_paged(next_page.saturated_into::()); - } + Self::do_elect_paged(next_page.saturated_into::()) } else { // election isn't ongoing yet, check if it should start. let next_election = ::next_election_prediction(now); @@ -1031,19 +1022,18 @@ pub mod pallet { if now == (next_election.saturating_sub(pages)) { crate::log!( trace, - "elect(): start fetching solution pages. expected pages: {}", + "elect(): start fetching solution pages. expected pages: {:?}", pages ); ElectingStartedAt::::set(Some(now)); - Self::do_elect_paged(pages.saturated_into::().saturating_sub(1)); + Self::do_elect_paged(pages.saturated_into::().saturating_sub(1)) + } else { + Weight::default() } }; - // TODO: benchmark on_initialize - - // return the weight of the on_finalize. - T::DbWeight::get().reads(1) + T::WeightInfo::on_initialize_noop().saturating_add(inner_weight) } fn on_finalize(_n: BlockNumberFor) { @@ -1878,6 +1868,7 @@ pub mod pallet { era: EraIndex, ) -> DispatchResultWithPostInfo { ensure_signed(origin)?; + Self::do_payout_stakers(validator_stash, era) } diff --git a/substrate/frame/staking/src/tests.rs b/substrate/frame/staking/src/tests.rs index 82a5ac2f4e843..218fa682735ec 100644 --- a/substrate/frame/staking/src/tests.rs +++ b/substrate/frame/staking/src/tests.rs @@ -2331,7 +2331,6 @@ fn reward_validator_slashing_validator_does_not_overflow() { // Set staker let _ = asset::set_stakeable_balance::(&11, stake); - let exposure = Exposure:: { total: stake, own: stake, others: vec![] }; let reward = EraRewardPoints:: { total: 1, individual: vec![(11, 1)].into_iter().collect(), diff --git a/substrate/frame/staking/src/tests_paged_election.rs b/substrate/frame/staking/src/tests_paged_election.rs index 2610b93916cfd..99652fa0f1e46 100644 --- a/substrate/frame/staking/src/tests_paged_election.rs +++ b/substrate/frame/staking/src/tests_paged_election.rs @@ -150,8 +150,7 @@ mod paged_on_initialize { assert_eq!(expected_elected.len(), 2); // 1. election prep hasn't started yet, election cursor and electable stashes are - // not - // set yet. + // not set yet. run_to_block(next_election - pages - 1); assert_eq!(ElectingStartedAt::::get(), None); assert!(ElectableStashes::::get().is_empty()); @@ -678,6 +677,25 @@ mod paged_snapshot { mod paged_exposures { use super::*; + #[test] + fn genesis_collect_exposures_works() { + ExtBuilder::default().multi_page_election_provider(3).build_and_execute(|| { + // first, clean up all the era data and metadata to mimic a genesis election next. + Staking::clear_era_information(current_era()); + + // genesis election is single paged. + let genesis_result = <::GenesisElectionProvider>::elect(0u32).unwrap(); + let expected_exposures = Staking::collect_exposures(genesis_result.clone()); + + Staking::try_trigger_new_era(0u32, true); + + // expected exposures are stored for the expected genesis validators. + for exposure in expected_exposures { + assert_eq!(EraInfo::::get_full_exposure(0, &exposure.0), exposure.1); + } + }) + } + #[test] fn store_stakers_info_elect_works() { ExtBuilder::default().exposures_page_size(2).build_and_execute(|| { diff --git a/substrate/frame/staking/src/weights.rs b/substrate/frame/staking/src/weights.rs index 56f561679cfc7..f0b9da081c1e6 100644 --- a/substrate/frame/staking/src/weights.rs +++ b/substrate/frame/staking/src/weights.rs @@ -51,6 +51,9 @@ use core::marker::PhantomData; /// Weight functions needed for `pallet_staking`. pub trait WeightInfo { + fn on_initialize_noop() -> Weight; + fn do_elect_paged(v: u32,) -> Weight; + fn clear_election_metadata() -> Weight; fn bond() -> Weight; fn bond_extra() -> Weight; fn unbond() -> Weight; @@ -88,6 +91,17 @@ pub trait WeightInfo { /// Weights for `pallet_staking` using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { + // TODO: run CI bench bot + fn on_initialize_noop() -> Weight { + RocksDbWeight::get().reads(1) + } + fn do_elect_paged(_v: u32,) -> Weight { + RocksDbWeight::get().reads(1) + } + fn clear_election_metadata() -> Weight { + RocksDbWeight::get().reads(1) + } + /// Storage: `Staking::Bonded` (r:1 w:1) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) @@ -838,6 +852,17 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests. impl WeightInfo for () { + // TODO: run CI bench bot + fn on_initialize_noop() -> Weight { + RocksDbWeight::get().reads(1) + } + fn do_elect_paged(_v: u32,) -> Weight { + RocksDbWeight::get().reads(1) + } + fn clear_election_metadata() -> Weight { + RocksDbWeight::get().reads(1) + } + /// Storage: `Staking::Bonded` (r:1 w:1) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) @@ -1584,4 +1609,4 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } -} \ No newline at end of file +} From 5ce51eaa86964ed90789d7f2bd6208c0860df835 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Wed, 11 Dec 2024 14:32:22 +0000 Subject: [PATCH 068/169] pr review refactors and improvements --- substrate/frame/staking/src/lib.rs | 14 +- substrate/frame/staking/src/pallet/impls.rs | 130 ++++++++---------- .../frame/staking/src/tests_paged_election.rs | 2 +- 3 files changed, 74 insertions(+), 72 deletions(-) diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index 94b8e62577921..ff3621c394abf 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -384,7 +384,17 @@ macro_rules! log { }; } -/// Alias fo the maximum number of winners (aka. active validators), as defined in by this pallet's +/// Alias for a bounded set of exposures behind a validator, parameterized by this pallet's +/// election provider. +pub type BoundedExposuresOf = BoundedVec< + ( + ::AccountId, + Exposure<::AccountId, BalanceOf>, + ), + MaxWinnersPerPageOf<::ElectionProvider>, +>; + +/// Alias for the maximum number of winners (aka. active validators), as defined in by this pallet's /// config. pub type MaxWinnersOf = ::MaxValidatorSet; @@ -1336,7 +1346,7 @@ impl EraInfo { last_page.others.extend(exposures_append.others); ErasStakersPaged::::insert((era, &validator, last_page_idx), last_page); - // now handle the remainig exposures and append the exposure pages. The metadata update + // now handle the remaining exposures and append the exposure pages. The metadata update // has been already handled above. let (_, exposure_pages) = exposure.into_pages(page_size); diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index 5244af5081285..808e36aa039ba 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -51,10 +51,10 @@ use sp_staking::{ use crate::{ asset, election_size_tracker::StaticTracker, log, slashing, weights::WeightInfo, ActiveEraInfo, - BalanceOf, EraInfo, EraPayout, Exposure, ExposureOf, Forcing, IndividualExposure, - LedgerIntegrityState, MaxNominationsOf, MaxWinnersOf, MaxWinnersPerPageOf, Nominations, - NominationsQuota, PositiveImbalanceOf, RewardDestination, SessionInterface, SnapshotStatus, - StakingLedger, ValidatorPrefs, + BalanceOf, BoundedExposuresOf, EraInfo, EraPayout, Exposure, ExposureOf, Forcing, + IndividualExposure, LedgerIntegrityState, MaxNominationsOf, MaxWinnersOf, MaxWinnersPerPageOf, + Nominations, NominationsQuota, PositiveImbalanceOf, RewardDestination, SessionInterface, + SnapshotStatus, StakingLedger, ValidatorPrefs, }; use alloc::{boxed::Box, vec, vec::Vec}; @@ -671,7 +671,7 @@ impl Pallet { .unwrap_or_default(); // set stakers info for genesis era (0). - Self::store_stakers_info(exposures.into_inner(), Zero::zero()); + Self::store_stakers_info(exposures, Zero::zero()); validators } else { @@ -725,7 +725,7 @@ impl Pallet { /// /// Fetches the election page with index `page` from the election provider. /// - /// The results from the elect call shold be stored in the `ElectableStashes` storage. In + /// The results from the elect call should be stored in the `ElectableStashes` storage. In /// addition, it stores stakers' information for next planned era based on the paged solution /// data returned. /// @@ -765,10 +765,7 @@ impl Pallet { match Self::add_electables(supports.iter().map(|(s, _)| s.clone())) { Ok(_) => { - let _ = Self::store_stakers_info( - Self::collect_exposures(supports).into_inner(), - planning_era, - ); + let _ = Self::store_stakers_info(Self::collect_exposures(supports), planning_era); Ok(()) }, Err(not_included) => { @@ -782,10 +779,7 @@ impl Pallet { // storage bounds to prevent collecting their exposures. supports.retain(|(s, _)| !not_included.contains(s)); - let _ = Self::store_stakers_info( - Self::collect_exposures(supports).into_inner(), - planning_era, - ); + let _ = Self::store_stakers_info(Self::collect_exposures(supports), planning_era); Err(()) }, } @@ -795,7 +789,7 @@ impl Pallet { /// /// Store staking information for the new planned era of a single election page. pub fn store_stakers_info( - exposures: Vec<(T::AccountId, Exposure>)>, + exposures: BoundedExposuresOf, new_planned_era: EraIndex, ) -> BoundedVec> { // populate elected stash, stakers, exposures, and the snapshot of validator prefs. @@ -815,7 +809,7 @@ impl Pallet { let elected_stashes: BoundedVec<_, MaxWinnersPerPageOf> = elected_stashes_page .try_into() - .expect("elected_stashes.len() always equal to exposures.len(); qed"); + .expect("both typs are bounded by MaxWinnersPerPageOf; qed"); // adds to total stake in this era. EraInfo::::add_total_stake(new_planned_era, total_stake_page); @@ -845,10 +839,7 @@ impl Pallet { /// of max winners per page returned by the election provider. pub(crate) fn collect_exposures( supports: BoundedSupportsOf, - ) -> BoundedVec< - (T::AccountId, Exposure>), - MaxWinnersPerPageOf, - > { + ) -> BoundedExposuresOf { let total_issuance = asset::total_issuance::(); let to_currency = |e: frame_election_provider_support::ExtendedBalance| { T::CurrencyToVote::to_currency(e, total_issuance) @@ -1131,8 +1122,6 @@ impl Pallet { } else { *status = SnapshotStatus::Ongoing(last.clone()); } - } else { - debug_assert!(*status == SnapshotStatus::Consumed); } }, // do nothing. @@ -2174,54 +2163,57 @@ impl Pallet { let election_prep_started = now >= expect_election_start_at; - // check election metadata, electable targets and era exposures if election should have - // already started. - match election_prep_started { - // election prep should have been started. - true => - if let Some(started_at) = ElectingStartedAt::::get() { - ensure!( - started_at == expect_election_start_at, - "unexpected electing_started_at block number in storage." - ); - ensure!( - !ElectableStashes::::get().is_empty(), - "election should have been started and the electable stashes non empty." - ); + if !election_prep_started { + // election prep should have not been started yet, no metadata in storage. + ensure!( + ElectableStashes::::get().is_empty(), + "unexpected electable stashes in storage while election prep hasn't started." + ); + ensure!( + ElectingStartedAt::::get().is_none(), + "unexpected election metadata while election prep hasn't started.", + ); - // all the current electable stashes exposures should have been collected and - // stored for the next era, and their total exposure suhould be > 0. - for s in ElectableStashes::::get().iter() { - ensure!( - EraInfo::::get_paged_exposure( - Self::current_era().unwrap_or_default().saturating_add(1), - s, - 0 - ) - .defensive_proof("electable stash exposure does not exist, unexpected.") - .unwrap() - .exposure_metadata - .total != Zero::zero(), - "no exposures collected for an electable stash." - ); - } - } else { - return Err( - "election prep should have started already, no election metadata in storage." - .into(), - ); - }, - // election prep should have not been started. - false => { - ensure!( - ElectableStashes::::get().is_empty(), - "unexpected electable stashes in storage while election prep hasn't started." - ); - ensure!( - ElectingStartedAt::::get().is_none(), - "unexpected election metadata while election prep hasn't started.", - ); - }, + return Ok(()) + } + + // from now on, we expect the election to have started. check election metadata, electable + // targets and era exposures. + let maybe_electing_started = ElectingStartedAt::::get(); + + if maybe_electing_started.is_none() { + return Err( + "election prep should have started already, but no election metadata in storage." + .into(), + ); + } + + let started_at = maybe_electing_started.unwrap(); + + ensure!( + started_at == expect_election_start_at, + "unexpected electing_started_at block number in storage." + ); + ensure!( + !ElectableStashes::::get().is_empty(), + "election should have been started and the electable stashes non empty." + ); + + // all the current electable stashes exposures should have been collected and + // stored for the next era, and their total exposure should be > 0. + for s in ElectableStashes::::get().iter() { + ensure!( + EraInfo::::get_paged_exposure( + Self::current_era().unwrap_or_default().saturating_add(1), + s, + 0 + ) + .defensive_proof("electable stash exposure does not exist, unexpected.") + .unwrap() + .exposure_metadata + .total != Zero::zero(), + "no exposures collected for an electable stash." + ); } Ok(()) diff --git a/substrate/frame/staking/src/tests_paged_election.rs b/substrate/frame/staking/src/tests_paged_election.rs index 99652fa0f1e46..4d85a1bd11fe8 100644 --- a/substrate/frame/staking/src/tests_paged_election.rs +++ b/substrate/frame/staking/src/tests_paged_election.rs @@ -531,7 +531,7 @@ mod paged_on_initialize { ElectingStartedAt::::kill(); assert_err!( Staking::ensure_snapshot_metadata_state(System::block_number()), - "election prep should have started already, no election metadata in storage." + "election prep should have started already, but no election metadata in storage." ); ElectingStartedAt::::set(Some(424242)); assert_err!( From 39e5c3d676b36d81934eb1adb7008fcf88cec560 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Wed, 11 Dec 2024 16:02:44 +0000 Subject: [PATCH 069/169] improves add_electables and tests --- substrate/frame/staking/src/pallet/impls.rs | 26 ++++++++++--------- .../frame/staking/src/tests_paged_election.rs | 23 ++++++++-------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index 808e36aa039ba..26cc1b3da8641 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -745,8 +745,12 @@ impl Pallet { }, }; - if let Err(_) = Self::do_elect_paged_inner(paged_result) { - defensive!("electable stashes exceeded limit, unexpected but election proceeds."); + if let Err(not_included) = Self::do_elect_paged_inner(paged_result) { + defensive!( + "electable stashes exceeded limit, unexpected but election proceeds.\ + {} stashes from election result discarded", + not_included.len() + ); }; T::WeightInfo::do_elect_paged(T::MaxValidatorSet::get()) @@ -755,10 +759,11 @@ impl Pallet { /// Inner implementation of [`Self::do_elect_paged`]. /// /// Returns an error if adding election winners to the electable stashes storage fails due to - /// exceeded bounds. + /// exceeded bounds. In case of error, it returns the stashes that were not included in the + /// electable stashes storage due to bounds contraints. pub(crate) fn do_elect_paged_inner( mut supports: BoundedSupportsOf, - ) -> Result<(), ()> { + ) -> Result<(), Vec> { // preparing the next era. Note: we expect `do_elect_paged` to be called *only* during a // non-genesis era, thus current era should be set by now. let planning_era = CurrentEra::::get().defensive_unwrap_or_default().saturating_add(1); @@ -780,7 +785,7 @@ impl Pallet { supports.retain(|(s, _)| !not_included.contains(s)); let _ = Self::store_stakers_info(Self::collect_exposures(supports), planning_era); - Err(()) + Err(not_included) }, } } @@ -877,15 +882,12 @@ impl Pallet { /// Deduplicates stashes in place and returns an error if the bounds are exceeded. In case of /// error, it returns the stashes that were not added to the storage. pub(crate) fn add_electables( - mut stashes_iter: impl Iterator, + stashes_iter: impl Iterator + Clone, ) -> Result<(), Vec> { ElectableStashes::::mutate(|electable| { - while let Some(stash) = stashes_iter.next() { - if let Err(_) = (*electable).try_insert(stash.clone()) { - let mut not_included = stashes_iter.collect::>(); - not_included.push(stash); - - return Err(not_included); + for stash in stashes_iter.clone() { + if electable.try_insert(stash.clone()).is_err() { + return Err(stashes_iter.skip_while(|s| *s != stash).collect::>()); } } Ok(()) diff --git a/substrate/frame/staking/src/tests_paged_election.rs b/substrate/frame/staking/src/tests_paged_election.rs index 4d85a1bd11fe8..38da0e94acbf4 100644 --- a/substrate/frame/staking/src/tests_paged_election.rs +++ b/substrate/frame/staking/src/tests_paged_election.rs @@ -29,7 +29,7 @@ mod electable_stashes { #[test] fn add_electable_stashes_work() { - ExtBuilder::default().build_and_execute(|| { + ExtBuilder::default().try_state(false).build_and_execute(|| { MaxValidatorSet::set(5); assert_eq!(MaxValidatorSet::get(), 5); assert!(ElectableStashes::::get().is_empty()); @@ -47,28 +47,28 @@ mod electable_stashes { ElectableStashes::::get().into_inner().into_iter().collect::>(), vec![1, 2, 3, 4] ); - - // skip final try state checks. - SkipTryStateCheck::set(true); }) } #[test] fn add_electable_stashes_overflow_works() { - ExtBuilder::default().build_and_execute(|| { + ExtBuilder::default().try_state(false).build_and_execute(|| { MaxValidatorSet::set(5); assert_eq!(MaxValidatorSet::get(), 5); assert!(ElectableStashes::::get().is_empty()); // adds stashes so that bounds are overflown, fails and internal state changes so that // all slots are filled. - assert!(Staking::add_electables(vec![1u64, 2, 3, 4, 5, 6].into_iter()).is_err()); + let expected_not_included = vec![6, 7, 8]; + assert_eq!( + Staking::add_electables(vec![1u64, 2, 3, 4, 5, 6, 7, 8].into_iter()), + Err(expected_not_included) + ); + // the included were added to the electable stashes, despite the error. assert_eq!( ElectableStashes::::get().into_inner().into_iter().collect::>(), vec![1, 2, 3, 4, 5] ); - - SkipTryStateCheck::set(true); }) } @@ -76,7 +76,7 @@ mod electable_stashes { fn overflow_electable_stashes_no_exposures_work() { // ensures exposures are stored only for the electable stashes that fit within the // electable stashes bounds in case of overflow. - ExtBuilder::default().build_and_execute(|| { + ExtBuilder::default().try_state(false).build_and_execute(|| { MaxValidatorSet::set(2); assert_eq!(MaxValidatorSet::get(), 2); assert!(ElectableStashes::::get().is_empty()); @@ -92,7 +92,8 @@ mod electable_stashes { ]); // error due to bounds. - assert!(Staking::do_elect_paged_inner(supports).is_err()); + let expected_not_included = vec![3u64, 4]; + assert_eq!(Staking::do_elect_paged_inner(supports), Err(expected_not_included)); // electable stashes have been collected to the max bounds despite the error. assert_eq!(ElectableStashes::::get().into_iter().collect::>(), vec![1, 2]); @@ -105,8 +106,6 @@ mod electable_stashes { assert!(exposure_exists(2, 1)); assert!(!exposure_exists(3, 1)); assert!(!exposure_exists(4, 1)); - - SkipTryStateCheck::set(true); }) } } From 65cf87f912c26ac66eb66e6386041e6c07360b63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Wed, 11 Dec 2024 16:27:22 +0000 Subject: [PATCH 070/169] improves add_electables --- substrate/frame/staking/src/pallet/impls.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index 26cc1b3da8641..04fda942cac1c 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -885,12 +885,19 @@ impl Pallet { stashes_iter: impl Iterator + Clone, ) -> Result<(), Vec> { ElectableStashes::::mutate(|electable| { - for stash in stashes_iter.clone() { + let mut not_included = vec![]; + + for stash in stashes_iter { if electable.try_insert(stash.clone()).is_err() { - return Err(stashes_iter.skip_while(|s| *s != stash).collect::>()); + not_included.push(stash); } } - Ok(()) + + if not_included.len() != 0 { + Err(not_included) + } else { + Ok(()) + } }) } From 7d33c5c306dd78af2e4bdfe3cc2d101a5f1e0beb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Thu, 12 Dec 2024 11:09:18 +0000 Subject: [PATCH 071/169] remove unecessary cloning --- substrate/frame/staking/src/pallet/impls.rs | 37 ++++++++----------- .../frame/staking/src/tests_paged_election.rs | 9 +++-- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index 04fda942cac1c..f898b4544dc39 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -749,7 +749,7 @@ impl Pallet { defensive!( "electable stashes exceeded limit, unexpected but election proceeds.\ {} stashes from election result discarded", - not_included.len() + not_included ); }; @@ -763,7 +763,7 @@ impl Pallet { /// electable stashes storage due to bounds contraints. pub(crate) fn do_elect_paged_inner( mut supports: BoundedSupportsOf, - ) -> Result<(), Vec> { + ) -> Result<(), usize> { // preparing the next era. Note: we expect `do_elect_paged` to be called *only* during a // non-genesis era, thus current era should be set by now. let planning_era = CurrentEra::::get().defensive_unwrap_or_default().saturating_add(1); @@ -773,18 +773,20 @@ impl Pallet { let _ = Self::store_stakers_info(Self::collect_exposures(supports), planning_era); Ok(()) }, - Err(not_included) => { + Err(not_included_idx) => { + let not_included = supports.len().saturating_sub(not_included_idx); + log!( warn, - "not all winners fit within the electable stashes, excluding tail: {:?}.", - not_included + "not all winners fit within the electable stashes, excluding {:?} accounts from solution.", + not_included, ); // filter out supports of stashes that do not fit within the electable stashes // storage bounds to prevent collecting their exposures. - supports.retain(|(s, _)| !not_included.contains(s)); - + supports.truncate(not_included_idx); let _ = Self::store_stakers_info(Self::collect_exposures(supports), planning_era); + Err(not_included) }, } @@ -880,24 +882,17 @@ impl Pallet { /// Adds a new set of stashes to the electable stashes. /// /// Deduplicates stashes in place and returns an error if the bounds are exceeded. In case of - /// error, it returns the stashes that were not added to the storage. + /// error, it returns the iter index of the element that failed to add. pub(crate) fn add_electables( - stashes_iter: impl Iterator + Clone, - ) -> Result<(), Vec> { + stashes_iter: impl Iterator, + ) -> Result<(), usize> { ElectableStashes::::mutate(|electable| { - let mut not_included = vec![]; - - for stash in stashes_iter { - if electable.try_insert(stash.clone()).is_err() { - not_included.push(stash); + for (idx, stash) in stashes_iter.enumerate() { + if electable.try_insert(stash).is_err() { + return Err(idx); } } - - if not_included.len() != 0 { - Err(not_included) - } else { - Ok(()) - } + Ok(()) }) } diff --git a/substrate/frame/staking/src/tests_paged_election.rs b/substrate/frame/staking/src/tests_paged_election.rs index 38da0e94acbf4..bd91f6ca766bf 100644 --- a/substrate/frame/staking/src/tests_paged_election.rs +++ b/substrate/frame/staking/src/tests_paged_election.rs @@ -58,11 +58,12 @@ mod electable_stashes { assert!(ElectableStashes::::get().is_empty()); // adds stashes so that bounds are overflown, fails and internal state changes so that - // all slots are filled. - let expected_not_included = vec![6, 7, 8]; + // all slots are filled. error will return the idx of the first account that was not + // included. + let expected_idx_not_included = 5; // stash 6. assert_eq!( Staking::add_electables(vec![1u64, 2, 3, 4, 5, 6, 7, 8].into_iter()), - Err(expected_not_included) + Err(expected_idx_not_included) ); // the included were added to the electable stashes, despite the error. assert_eq!( @@ -92,7 +93,7 @@ mod electable_stashes { ]); // error due to bounds. - let expected_not_included = vec![3u64, 4]; + let expected_not_included = 2; assert_eq!(Staking::do_elect_paged_inner(supports), Err(expected_not_included)); // electable stashes have been collected to the max bounds despite the error. From d7817e0f993c4cecd8aaa5af614d0f8363f996fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 17 Dec 2024 18:41:17 +0000 Subject: [PATCH 072/169] migrations --- substrate/frame/staking/src/migrations.rs | 55 +++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/substrate/frame/staking/src/migrations.rs b/substrate/frame/staking/src/migrations.rs index 9dfa93c70b325..1f92dbb738f53 100644 --- a/substrate/frame/staking/src/migrations.rs +++ b/substrate/frame/staking/src/migrations.rs @@ -60,6 +60,61 @@ impl Default for ObsoleteReleases { #[storage_alias] type StorageVersion = StorageValue, ObsoleteReleases, ValueQuery>; +/// Migrates to multi-page election support. +/// +/// Important note: this migration should be released with the election provider configured by this +/// pallet supporting up to 1 page. Thus, +/// * `VoterSnapshotStatus` does not need migration, as it will always be `Status::Waiting` when +/// the number of election pages is 1. +/// * `ElectableStashes` must be populated iif there are collected exposures for a future era (i.e. +/// exposures have been collected but `fn try_trigger_new_era` was not called). +pub mod v17 { + use super::*; + + pub struct VersionedMigrateV16ToV17(core::marker::PhantomData); + impl OnRuntimeUpgrade for VersionedMigrateV16ToV17 { + fn on_runtime_upgrade() -> Weight { + // Populates the `ElectableStashes` with the exposures of the next planning era if it + // is initialized (i.e. if the there are exposures collected for the next planning + // era). + + // note: we expect the migration to be released with a single page config. + debug_assert!(Pallet::::election_pages() == 1); + + let next_era = CurrentEra::::get().defensive_unwrap_or_default().saturating_add(1); + let prepared_exposures = ErasStakersOverview::::iter() + .filter(|(era_idx, _, _)| *era_idx == next_era) + .map(|(_, v, _)| v) + .collect::>(); + let migrated_stashes = prepared_exposures.len() as u32; + + let result = Pallet::::add_electables(prepared_exposures.into_iter()); + debug_assert!(result.is_ok()); + + T::DbWeight::get().reads_writes( + // 1x read per history depth and current era read. + (T::HistoryDepth::get() + 1u32).into(), + // 1x write per exposure migrated. + migrated_stashes.into(), + ) + } + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), TryRuntimeError> { + frame_support::ensure!(Pallet::::on_chain_storage_version() >= 17, "v17 not applied"); + Ok(()) + } + + pub type MigrateV16ToV17 = VersionedMigration< + 16, + 17, + VersionedMigrateV16ToV17, + Pallet, + ::DbWeight, + >; +} + /// Migrating `DisabledValidators` from `Vec` to `Vec<(u32, OffenceSeverity)>` to track offense /// severity for re-enabling purposes. pub mod v16 { From de4f1da22de15e2a0fbf1f966df9e9cd12faed41 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 15 Jan 2025 11:23:50 +0000 Subject: [PATCH 073/169] fix --- substrate/frame/staking/src/tests_paged_election.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/substrate/frame/staking/src/tests_paged_election.rs b/substrate/frame/staking/src/tests_paged_election.rs index ef9d0eb866434..12dcfcfe6751d 100644 --- a/substrate/frame/staking/src/tests_paged_election.rs +++ b/substrate/frame/staking/src/tests_paged_election.rs @@ -685,6 +685,7 @@ mod paged_snapshot { assert_eq!(all_voters, single_page_voters); }) } +} mod paged_exposures { use super::*; From 06d33089722c625be236db4c16ed79d8a401fafa Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 15 Jan 2025 11:36:52 +0000 Subject: [PATCH 074/169] fmt --- polkadot/runtime/westend/src/lib.rs | 3 ++- substrate/frame/staking/src/migrations.rs | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index ee1d2ce811bfe..88f722e75a9a3 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -1150,7 +1150,8 @@ impl InstanceFilter for ProxyType { matches!( c, RuntimeCall::Staking(..) | - RuntimeCall::Session(..) | RuntimeCall::Utility(..) | + RuntimeCall::Session(..) | + RuntimeCall::Utility(..) | RuntimeCall::FastUnstake(..) | RuntimeCall::VoterList(..) | RuntimeCall::NominationPools(..) diff --git a/substrate/frame/staking/src/migrations.rs b/substrate/frame/staking/src/migrations.rs index 1f92dbb738f53..b9219b4acb80e 100644 --- a/substrate/frame/staking/src/migrations.rs +++ b/substrate/frame/staking/src/migrations.rs @@ -62,6 +62,8 @@ type StorageVersion = StorageValue, ObsoleteReleases, Value /// Migrates to multi-page election support. /// +/// See: https://github.com/paritytech/polkadot-sdk/pull/6034 +/// /// Important note: this migration should be released with the election provider configured by this /// pallet supporting up to 1 page. Thus, /// * `VoterSnapshotStatus` does not need migration, as it will always be `Status::Waiting` when From 5426e880588c53695f8a63249114ab5478ec64a8 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:38:28 +0000 Subject: [PATCH 075/169] Update substrate/frame/election-provider-multi-phase/src/lib.rs Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com> --- substrate/frame/election-provider-multi-phase/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index a2e46ecca3c7f..ae1d3edb794b2 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -514,7 +514,7 @@ pub enum ElectionError { DataProvider(&'static str), /// An error nested in the fallback. Fallback(FallbackErrorOf), - /// An error occurred when requesting an election result. The caller expects a mulit-paged + /// An error occurred when requesting an election result. The caller expects a multi-paged /// election, which this pallet does not support. MultiPageNotSupported, /// No solution has been queued. From c228dc4a9750f349d54b2b489f66340be3908f70 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 15 Jan 2025 12:48:11 +0000 Subject: [PATCH 076/169] fix lock file --- Cargo.lock | 5971 +++++++++++++++++++++++++--------------------------- 1 file changed, 2814 insertions(+), 3157 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 95d06bf6cfce4..a19da6f71ad54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,7 +27,7 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ - "gimli 0.28.1", + "gimli 0.28.0", ] [[package]] @@ -36,12 +36,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - [[package]] name = "adler32" version = "1.2.0" @@ -60,9 +54,9 @@ dependencies = [ [[package]] name = "aes" -version = "0.8.4" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" dependencies = [ "cfg-if", "cipher 0.4.4", @@ -80,7 +74,7 @@ dependencies = [ "cipher 0.4.4", "ctr", "ghash", - "subtle 2.6.1", + "subtle 2.5.0", ] [[package]] @@ -118,56 +112,56 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" dependencies = [ "memchr", ] [[package]] name = "allocator-api2" -version = "0.2.21" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" [[package]] name = "alloy-core" -version = "0.8.18" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0713007d14d88a6edb8e248cddab783b698dbb954a28b8eee4bab21cfb7e578" +checksum = "c618bd382f0bc2ac26a7e4bfae01c9b015ca8f21b37ca40059ae35a7e62b3dc6" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", - "alloy-primitives 0.8.18", + "alloy-primitives 0.8.15", "alloy-rlp", - "alloy-sol-types 0.8.18", + "alloy-sol-types 0.8.15", ] [[package]] name = "alloy-dyn-abi" -version = "0.8.18" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44e3b98c37b3218924cd1d2a8570666b89662be54e5b182643855f783ea68b33" +checksum = "41056bde53ae10ffbbf11618efbe1e0290859e5eab0fe9ef82ebdb62f12a866f" dependencies = [ "alloy-json-abi", - "alloy-primitives 0.8.18", + "alloy-primitives 0.8.15", "alloy-sol-type-parser", - "alloy-sol-types 0.8.18", + "alloy-sol-types 0.8.15", "const-hex", "itoa", "serde", "serde_json", - "winnow 0.6.24", + "winnow 0.6.18", ] [[package]] name = "alloy-json-abi" -version = "0.8.18" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "731ea743b3d843bc657e120fb1d1e9cc94f5dab8107e35a82125a63e6420a102" +checksum = "c357da577dfb56998d01f574d81ad7a1958d248740a7981b205d69d65a7da404" dependencies = [ - "alloy-primitives 0.8.18", + "alloy-primitives 0.8.15", "alloy-sol-type-parser", "serde", "serde_json", @@ -183,7 +177,7 @@ dependencies = [ "bytes", "cfg-if", "const-hex", - "derive_more 0.99.18", + "derive_more 0.99.17", "hex-literal", "itoa", "proptest", @@ -195,9 +189,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.8.18" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "788bb18e8f61d5d9340b52143f27771daf7e1dccbaf2741621d2493f9debf52e" +checksum = "6259a506ab13e1d658796c31e6e39d2e2ee89243bcc505ddc613b35732e0a430" dependencies = [ "alloy-rlp", "bytes", @@ -206,6 +200,7 @@ dependencies = [ "derive_more 1.0.0", "foldhash", "hashbrown 0.15.2", + "hex-literal", "indexmap 2.7.0", "itoa", "k256", @@ -214,7 +209,7 @@ dependencies = [ "proptest", "rand", "ruint", - "rustc-hash 2.1.0", + "rustc-hash 2.0.0", "serde", "sha3 0.10.8", "tiny-keccak", @@ -222,12 +217,13 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.10" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f542548a609dca89fcd72b3b9f355928cf844d4363c5eed9c5273a3dd225e097" +checksum = "cc0fac0fc16baf1f63f78b47c3d24718f3619b0714076f6a02957d808d52cbef" dependencies = [ - "arrayvec 0.7.6", + "arrayvec 0.7.4", "bytes", + "smol_str", ] [[package]] @@ -240,68 +236,68 @@ dependencies = [ "dunce", "heck 0.4.1", "proc-macro-error", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", "syn-solidity 0.4.2", "tiny-keccak", ] [[package]] name = "alloy-sol-macro" -version = "0.8.18" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a07b74d48661ab2e4b50bb5950d74dbff5e61dd8ed03bb822281b706d54ebacb" +checksum = "d9d64f851d95619233f74b310f12bcf16e0cbc27ee3762b6115c14a84809280a" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", "proc-macro-error2", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "alloy-sol-macro-expander" -version = "0.8.18" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19cc9c7f20b90f9be1a8f71a3d8e283a43745137b0837b1a1cb13159d37cad72" +checksum = "6bf7ed1574b699f48bf17caab4e6e54c6d12bc3c006ab33d58b1e227c1c3559f" dependencies = [ "alloy-sol-macro-input", "const-hex", "heck 0.5.0", "indexmap 2.7.0", "proc-macro-error2", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", - "syn-solidity 0.8.18", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", + "syn-solidity 0.8.15", "tiny-keccak", ] [[package]] name = "alloy-sol-macro-input" -version = "0.8.18" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713b7e6dfe1cb2f55c80fb05fd22ed085a1b4e48217611365ed0ae598a74c6ac" +checksum = "8c02997ccef5f34f9c099277d4145f183b422938ed5322dc57a089fe9b9ad9ee" dependencies = [ "const-hex", "dunce", "heck 0.5.0", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", - "syn-solidity 0.8.18", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", + "syn-solidity 0.8.15", ] [[package]] name = "alloy-sol-type-parser" -version = "0.8.18" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1eda2711ab2e1fb517fc6e2ffa9728c9a232e296d16810810e6957b781a1b8bc" +checksum = "ce13ff37285b0870d0a0746992a4ae48efaf34b766ae4c2640fa15e5305f8e73" dependencies = [ "serde", - "winnow 0.6.24", + "winnow 0.6.18", ] [[package]] @@ -318,13 +314,13 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "0.8.18" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b478bc9c0c4737a04cd976accde4df7eba0bdc0d90ad6ff43d58bc93cf79c1" +checksum = "1174cafd6c6d810711b4e00383037bdb458efc4fe3dbafafa16567e0320c54d8" dependencies = [ "alloy-json-abi", - "alloy-primitives 0.8.18", - "alloy-sol-macro 0.8.18", + "alloy-primitives 0.8.15", + "alloy-sol-macro 0.8.15", "const-hex", "serde", ] @@ -367,59 +363,57 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", - "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" dependencies = [ "anstyle", - "once_cell", - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] name = "anyhow" -version = "1.0.95" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "approx" @@ -439,16 +433,16 @@ dependencies = [ "include_dir", "itertools 0.10.5", "proc-macro-error", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "arbitrary" -version = "1.4.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" dependencies = [ "derive_arbitrary", ] @@ -459,7 +453,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb00293ba84f51ce3bd026bd0de55899c4e68f0a39a5728cebae3a73ffdc0a4f" dependencies = [ - "ark-ec 0.4.2", + "ark-ec", "ark-ff 0.4.2", "ark-std 0.4.0", ] @@ -471,7 +465,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20c7021f180a0cbea0380eba97c2af3c57074cdaffe0eef7e840e1c9f2841e55" dependencies = [ "ark-bls12-377", - "ark-ec 0.4.2", + "ark-ec", "ark-models-ext", "ark-std 0.4.0", ] @@ -482,7 +476,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" dependencies = [ - "ark-ec 0.4.2", + "ark-ec", "ark-ff 0.4.2", "ark-serialize 0.4.2", "ark-std 0.4.0", @@ -495,7 +489,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1dc4b3d08f19e8ec06e949712f95b8361e43f1391d94f65e4234df03480631c" dependencies = [ "ark-bls12-381", - "ark-ec 0.4.2", + "ark-ec", "ark-ff 0.4.2", "ark-models-ext", "ark-serialize 0.4.2", @@ -509,7 +503,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e0605daf0cc5aa2034b78d008aaf159f56901d92a52ee4f6ecdfdac4f426700" dependencies = [ "ark-bls12-377", - "ark-ec 0.4.2", + "ark-ec", "ark-ff 0.4.2", "ark-std 0.4.0", ] @@ -521,7 +515,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccee5fba47266f460067588ee1bf070a9c760bf2050c1c509982c5719aadb4f2" dependencies = [ "ark-bw6-761", - "ark-ec 0.4.2", + "ark-ec", "ark-ff 0.4.2", "ark-models-ext", "ark-std 0.4.0", @@ -534,7 +528,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" dependencies = [ "ark-ff 0.4.2", - "ark-poly 0.4.2", + "ark-poly", "ark-serialize 0.4.2", "ark-std 0.4.0", "derivative", @@ -545,27 +539,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "ark-ec" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" -dependencies = [ - "ahash 0.8.11", - "ark-ff 0.5.0", - "ark-poly 0.5.0", - "ark-serialize 0.5.0", - "ark-std 0.5.0", - "educe", - "fnv", - "hashbrown 0.15.2", - "itertools 0.13.0", - "num-bigint", - "num-integer", - "num-traits", - "zeroize", -] - [[package]] name = "ark-ed-on-bls12-377" version = "0.4.0" @@ -573,7 +546,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b10d901b9ac4b38f9c32beacedfadcdd64e46f8d7f8e88c1ae1060022cf6f6c6" dependencies = [ "ark-bls12-377", - "ark-ec 0.4.2", + "ark-ec", "ark-ff 0.4.2", "ark-std 0.4.0", ] @@ -584,7 +557,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524a4fb7540df2e1a8c2e67a83ba1d1e6c3947f4f9342cc2359fc2e789ad731d" dependencies = [ - "ark-ec 0.4.2", + "ark-ec", "ark-ed-on-bls12-377", "ark-ff 0.4.2", "ark-models-ext", @@ -598,7 +571,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9cde0f2aa063a2a5c28d39b47761aa102bda7c13c84fc118a61b87c7b2f785c" dependencies = [ "ark-bls12-381", - "ark-ec 0.4.2", + "ark-ec", "ark-ff 0.4.2", "ark-std 0.4.0", ] @@ -609,7 +582,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d15185f1acb49a07ff8cbe5f11a1adc5a93b19e211e325d826ae98e98e124346" dependencies = [ - "ark-ec 0.4.2", + "ark-ec", "ark-ed-on-bls12-381-bandersnatch", "ark-ff 0.4.2", "ark-models-ext", @@ -650,27 +623,7 @@ dependencies = [ "num-bigint", "num-traits", "paste", - "rustc_version 0.4.1", - "zeroize", -] - -[[package]] -name = "ark-ff" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" -dependencies = [ - "ark-ff-asm 0.5.0", - "ark-ff-macros 0.5.0", - "ark-serialize 0.5.0", - "ark-std 0.5.0", - "arrayvec 0.7.6", - "digest 0.10.7", - "educe", - "itertools 0.13.0", - "num-bigint", - "num-traits", - "paste", + "rustc_version 0.4.0", "zeroize", ] @@ -680,7 +633,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" dependencies = [ - "quote 1.0.38", + "quote 1.0.37", "syn 1.0.109", ] @@ -690,20 +643,10 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" dependencies = [ - "quote 1.0.38", + "quote 1.0.37", "syn 1.0.109", ] -[[package]] -name = "ark-ff-asm" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" -dependencies = [ - "quote 1.0.38", - "syn 2.0.96", -] - [[package]] name = "ark-ff-macros" version = "0.3.0" @@ -712,7 +655,7 @@ checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" dependencies = [ "num-bigint", "num-traits", - "quote 1.0.38", + "quote 1.0.37", "syn 1.0.109", ] @@ -724,31 +667,18 @@ checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" dependencies = [ "num-bigint", "num-traits", - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "syn 1.0.109", ] -[[package]] -name = "ark-ff-macros" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" -dependencies = [ - "num-bigint", - "num-traits", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", -] - [[package]] name = "ark-models-ext" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e9eab5d4b5ff2f228b763d38442adc9b084b0a465409b059fac5c2308835ec2" dependencies = [ - "ark-ec 0.4.2", + "ark-ec", "ark-ff 0.4.2", "ark-serialize 0.4.2", "ark-std 0.4.0", @@ -769,18 +699,17 @@ dependencies = [ ] [[package]] -name = "ark-poly" -version = "0.5.0" +name = "ark-scale" +version = "0.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" +checksum = "51bd73bb6ddb72630987d37fa963e99196896c0d0ea81b7c894567e74a2f83af" dependencies = [ - "ahash 0.8.11", - "ark-ff 0.5.0", - "ark-serialize 0.5.0", - "ark-std 0.5.0", - "educe", - "fnv", - "hashbrown 0.15.2", + "ark-ec", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "parity-scale-codec", + "scale-info", ] [[package]] @@ -789,7 +718,7 @@ version = "0.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f69c00b3b529be29528a6f2fd5fa7b1790f8bed81b9cdca17e326538545a179" dependencies = [ - "ark-ec 0.4.2", + "ark-ec", "ark-ff 0.4.2", "ark-serialize 0.4.2", "ark-std 0.4.0", @@ -802,7 +731,7 @@ name = "ark-secret-scalar" version = "0.0.2" source = "git+https://github.com/w3f/ring-vrf?rev=0fef826#0fef8266d851932ad25d6b41bc4b34d834d1e11d" dependencies = [ - "ark-ec 0.4.2", + "ark-ec", "ark-ff 0.4.2", "ark-serialize 0.4.2", "ark-std 0.4.0", @@ -828,47 +757,23 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ - "ark-serialize-derive 0.4.2", + "ark-serialize-derive", "ark-std 0.4.0", "digest 0.10.7", "num-bigint", ] -[[package]] -name = "ark-serialize" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" -dependencies = [ - "ark-serialize-derive 0.5.0", - "ark-std 0.5.0", - "arrayvec 0.7.6", - "digest 0.10.7", - "num-bigint", -] - [[package]] name = "ark-serialize-derive" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "syn 1.0.109", ] -[[package]] -name = "ark-serialize-derive" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", -] - [[package]] name = "ark-std" version = "0.3.0" @@ -890,16 +795,6 @@ dependencies = [ "rayon", ] -[[package]] -name = "ark-std" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" -dependencies = [ - "num-traits", - "rand", -] - [[package]] name = "ark-transcript" version = "0.0.2" @@ -915,15 +810,15 @@ dependencies = [ [[package]] name = "array-bytes" -version = "6.2.3" +version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5dde061bd34119e902bbb2d9b90c5692635cf59fb91d582c2b68043f1b8293" +checksum = "6f840fb7195bcfc5e17ea40c26e5ce6d5b9ce5d584466e17703209657e459ae0" [[package]] name = "arrayref" -version = "0.3.9" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" [[package]] name = "arrayvec" @@ -942,15 +837,15 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "arrayvec" -version = "0.7.6" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "asn1-rs" -version = "0.6.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" +checksum = "22ad1373757efa0f70ec53939aabc7152e1591cb485208052993070ac8d2429d" dependencies = [ "asn1-rs-derive", "asn1-rs-impl", @@ -958,19 +853,19 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror 1.0.69", + "thiserror", "time", ] [[package]] name = "asn1-rs-derive" -version = "0.5.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" +checksum = "7378575ff571966e99a744addeff0bff98b8ada0dedf1956d59e634db95eaac1" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", "synstructure 0.13.1", ] @@ -980,22 +875,21 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "assert_cmd" -version = "2.0.16" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d" +checksum = "ed72493ac66d5804837f480ab3766c72bdfab91a65e565fc54fa9e42db0073a8" dependencies = [ "anstyle", "bstr", "doc-comment", - "libc", - "predicates 3.1.3", + "predicates 3.0.3", "predicates-core", "predicates-tree", "wait-timeout", @@ -1316,23 +1210,23 @@ dependencies = [ "cumulus-pallet-parachain-system 0.17.1", "cumulus-pallet-xcmp-queue 0.17.0", "cumulus-primitives-core 0.16.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "pallet-assets 40.0.0", "pallet-balances 39.0.0", "pallet-collator-selection 19.0.0", "pallet-session 38.0.0", "pallet-timestamp 37.0.0", - "pallet-xcm 17.0.1", - "pallet-xcm-bridge-hub-router 0.15.3", + "pallet-xcm 17.0.0", + "pallet-xcm-bridge-hub-router 0.15.1", "parachains-common 18.0.0", "parachains-runtimes-test-utils 17.0.0", "parity-scale-codec", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "staging-parachain-info 0.17.0", "staging-xcm 14.2.0", - "staging-xcm-builder 17.0.3", + "staging-xcm-builder 17.0.1", "staging-xcm-executor 17.0.0", "substrate-wasm-builder 24.0.1", ] @@ -1361,24 +1255,24 @@ dependencies = [ [[package]] name = "assets-common" -version = "0.18.3" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c540587f89a03003946b14decef4fcadb083edc4e62f968de245b82e5402e923" +checksum = "4556e56f9206b129c3f96249cd907b76e8d7ad5265fe368c228c708789a451a3" dependencies = [ "cumulus-primitives-core 0.16.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "impl-trait-for-tuples", "log", "pallet-asset-conversion 20.0.0", "pallet-assets 40.0.0", - "pallet-xcm 17.0.1", + "pallet-xcm 17.0.0", "parachains-common 18.0.0", "parity-scale-codec", "scale-info", "sp-api 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "staging-xcm 14.2.0", - "staging-xcm-builder 17.0.3", + "staging-xcm-builder 17.0.1", "staging-xcm-executor 17.0.0", "substrate-wasm-builder 24.0.1", ] @@ -1389,7 +1283,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" dependencies = [ - "quote 1.0.38", + "quote 1.0.37", "syn 1.0.109", ] @@ -1406,11 +1300,12 @@ dependencies = [ [[package]] name = "async-channel" -version = "2.3.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +checksum = "9f2776ead772134d55b62dd45e59a79e21612d85d0af729b8b7d3967d601a62a" dependencies = [ "concurrent-queue", + "event-listener 5.3.1", "event-listener-strategy", "futures-core", "pin-project-lite", @@ -1418,14 +1313,15 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.13.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" dependencies = [ + "async-lock 2.8.0", "async-task", "concurrent-queue", - "fastrand 2.3.0", - "futures-lite 2.6.0", + "fastrand 1.9.0", + "futures-lite 1.13.0", "slab", ] @@ -1449,21 +1345,21 @@ checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" dependencies = [ "async-lock 3.4.0", "blocking", - "futures-lite 2.6.0", + "futures-lite 2.3.0", ] [[package]] name = "async-global-executor" -version = "2.4.1" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" dependencies = [ - "async-channel 2.3.1", + "async-channel 1.9.0", "async-executor", - "async-io 2.4.0", - "async-lock 3.4.0", + "async-io 1.13.0", + "async-lock 2.8.0", "blocking", - "futures-lite 2.6.0", + "futures-lite 1.13.0", "once_cell", ] @@ -1481,29 +1377,29 @@ dependencies = [ "log", "parking", "polling 2.8.0", - "rustix 0.37.28", + "rustix 0.37.23", "slab", - "socket2 0.4.10", + "socket2 0.4.9", "waker-fn", ] [[package]] name = "async-io" -version = "2.4.0" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" +checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964" dependencies = [ "async-lock 3.4.0", "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.6.0", + "futures-lite 2.3.0", "parking", - "polling 3.7.4", - "rustix 0.38.43", + "polling 3.4.0", + "rustix 0.38.42", "slab", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1521,18 +1417,19 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ - "event-listener 5.4.0", + "event-listener 5.3.1", "event-listener-strategy", "pin-project-lite", ] [[package]] name = "async-net" -version = "1.8.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0434b1ed18ce1cf5769b8ac540e33f01fa9471058b5e89da9e06f3c882a8c12f" +checksum = "4051e67316bc7eff608fe723df5d32ed639946adcd69e07df41fd42a7b411f1f" dependencies = [ "async-io 1.13.0", + "autocfg", "blocking", "futures-lite 1.13.0", ] @@ -1543,25 +1440,26 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" dependencies = [ - "async-io 2.4.0", + "async-io 2.3.3", "blocking", - "futures-lite 2.6.0", + "futures-lite 2.3.0", ] [[package]] name = "async-process" -version = "1.8.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" +checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" dependencies = [ "async-io 1.13.0", "async-lock 2.8.0", - "async-signal", + "autocfg", "blocking", "cfg-if", - "event-listener 3.1.0", + "event-listener 2.5.3", "futures-lite 1.13.0", - "rustix 0.38.43", + "rustix 0.37.23", + "signal-hook", "windows-sys 0.48.0", ] @@ -1571,54 +1469,54 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" dependencies = [ - "async-channel 2.3.1", - "async-io 2.4.0", + "async-channel 2.3.0", + "async-io 2.3.3", "async-lock 3.4.0", "async-signal", "async-task", "blocking", "cfg-if", - "event-listener 5.4.0", - "futures-lite 2.6.0", - "rustix 0.38.43", + "event-listener 5.3.1", + "futures-lite 2.3.0", + "rustix 0.38.42", "tracing", ] [[package]] name = "async-signal" -version = "0.2.10" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" +checksum = "dfb3634b73397aa844481f814fad23bbf07fdb0eabec10f2eb95e58944b1ec32" dependencies = [ - "async-io 2.4.0", + "async-io 2.3.3", "async-lock 3.4.0", "atomic-waker", "cfg-if", "futures-core", "futures-io", - "rustix 0.38.43", + "rustix 0.38.42", "signal-hook-registry", "slab", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] name = "async-std" -version = "1.13.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" dependencies = [ "async-attributes", "async-channel 1.9.0", "async-global-executor", - "async-io 2.4.0", - "async-lock 3.4.0", + "async-io 1.13.0", + "async-lock 2.8.0", "crossbeam-utils", "futures-channel", "futures-core", "futures-io", - "futures-lite 2.6.0", - "gloo-timers 0.3.0", + "futures-lite 1.13.0", + "gloo-timers", "kv-log-macro", "log", "memchr", @@ -1631,9 +1529,9 @@ dependencies = [ [[package]] name = "async-stream" -version = "0.3.6" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" dependencies = [ "async-stream-impl", "futures-core", @@ -1642,13 +1540,13 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.6" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -1659,13 +1557,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.85" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -1711,9 +1609,9 @@ checksum = "a8ab6b55fe97976e46f91ddbed8d147d966475dc29b2032757ba47e02376fbc3" [[package]] name = "atomic-waker" -version = "1.1.2" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" [[package]] name = "attohttpc" @@ -1721,7 +1619,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d9a9bf8b79a749ee0b911b91b671cc2b6c670bdbc7e3dfd537576ddc94bb2a2" dependencies = [ - "http 0.2.12", + "http 0.2.9", "log", "url", ] @@ -1739,20 +1637,21 @@ dependencies = [ [[package]] name = "auto_impl" -version = "1.2.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12882f59de5360c748c4cbf569a042d5fb0eb515f7bea9c1f470b47f6ffbd73" +checksum = "fee3da8ef1276b0bee5dd1c7258010d8fffd31801447323115a25560e1327b89" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro-error", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 1.0.109", ] [[package]] name = "autocfg" -version = "1.4.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backoff" @@ -1775,7 +1674,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide 0.7.4", + "miniz_oxide", "object 0.32.2", "rustc-demangle", ] @@ -1786,7 +1685,7 @@ version = "0.0.4" source = "git+https://github.com/w3f/ring-vrf?rev=0fef826#0fef8266d851932ad25d6b41bc4b34d834d1e11d" dependencies = [ "ark-bls12-381", - "ark-ec 0.4.2", + "ark-ec", "ark-ed-on-bls12-381-bandersnatch", "ark-ff 0.4.2", "ark-serialize 0.4.2", @@ -1843,6 +1742,15 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "basic-toml" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2db21524cad41c5591204d22d75e1970a2d1f71060214ca931dc7d5afe2c14e5" +dependencies = [ + "serde", +] + [[package]] name = "binary-merkle-tree" version = "13.0.0" @@ -1888,12 +1796,12 @@ dependencies = [ "lazycell", "peeking_take_while", "prettyplease", - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.96", + "syn 2.0.87", ] [[package]] @@ -1908,7 +1816,7 @@ dependencies = [ "rand_core 0.6.4", "ripemd", "sha2 0.10.8", - "subtle 2.6.1", + "subtle 2.5.0", "zeroize", ] @@ -1957,7 +1865,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" dependencies = [ "bitcoin-internals", - "hex-conservative 0.1.2", + "hex-conservative 0.1.1", ] [[package]] @@ -1978,9 +1886,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.8.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" dependencies = [ "serde", ] @@ -2047,8 +1955,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" dependencies = [ "arrayref", - "arrayvec 0.7.6", - "constant_time_eq 0.3.1", + "arrayvec 0.7.4", + "constant_time_eq 0.3.0", ] [[package]] @@ -2064,26 +1972,26 @@ dependencies = [ [[package]] name = "blake2s_simd" -version = "1.0.2" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94230421e395b9920d23df13ea5d77a20e1725331f90fbbf6df6040b33f756ae" +checksum = "6637f448b9e61dfadbdcbae9a885fadee1f3eaffb1f8d3c1965d3ade8bdfd44f" dependencies = [ "arrayref", - "arrayvec 0.7.6", - "constant_time_eq 0.3.1", + "arrayvec 0.7.4", + "constant_time_eq 0.2.6", ] [[package]] name = "blake3" -version = "1.5.5" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8ee0c1824c4dea5b5f81736aff91bae041d2c07ee1192bec91054e10e3e601e" +checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" dependencies = [ "arrayref", - "arrayvec 0.7.6", + "arrayvec 0.7.4", "cc", "cfg-if", - "constant_time_eq 0.3.1", + "constant_time_eq 0.3.0", ] [[package]] @@ -2113,15 +2021,17 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "blocking" -version = "1.6.1" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" dependencies = [ - "async-channel 2.3.1", + "async-channel 1.9.0", + "async-lock 2.8.0", "async-task", - "futures-io", - "futures-lite 2.6.0", - "piper", + "atomic-waker", + "fastrand 1.9.0", + "futures-lite 1.13.0", + "log", ] [[package]] @@ -2143,7 +2053,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68534a48cbf63a4b1323c433cf21238c9ec23711e0df13b08c33e5c2082663ce" dependencies = [ - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -2284,13 +2194,13 @@ checksum = "890df97cea17ee61ff982466bb9e90cb6b1462adb45380999019388d05e4b92d" dependencies = [ "bp-runtime 0.18.0", "finality-grandpa", - "frame-support 38.2.0", + "frame-support 38.0.0", "parity-scale-codec", "scale-info", "serde", "sp-consensus-grandpa 21.0.0", "sp-core 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2331,7 +2241,7 @@ checksum = "7efabf94339950b914ba87249497f1a0e35a73849934d164fecae4b275928cf6" dependencies = [ "bp-header-chain 0.18.1", "bp-runtime 0.18.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "parity-scale-codec", "scale-info", "serde", @@ -2365,12 +2275,12 @@ dependencies = [ "bp-header-chain 0.18.1", "bp-polkadot-core 0.18.0", "bp-runtime 0.18.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "impl-trait-for-tuples", "parity-scale-codec", "scale-info", "sp-core 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2395,7 +2305,7 @@ dependencies = [ "bp-header-chain 0.18.1", "bp-polkadot-core 0.18.0", "bp-runtime 0.18.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "sp-api 34.0.0", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2442,14 +2352,14 @@ checksum = "345cf472bac11ef79d403e4846a666b7d22a13cd16d9c85b62cd6b5e16c4a042" dependencies = [ "bp-messages 0.18.0", "bp-runtime 0.18.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "parity-util-mem", "scale-info", "serde", "sp-core 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2482,12 +2392,12 @@ dependencies = [ "bp-messages 0.18.0", "bp-parachains 0.18.0", "bp-runtime 0.18.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "pallet-utility 38.0.0", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2532,7 +2442,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "746d9464f912b278f8a5e2400f10541f95da7fc6c7d688a2788b9a46296146ee" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "hash-db", "impl-trait-for-tuples", @@ -2543,7 +2453,7 @@ dependencies = [ "serde", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-state-machine 0.43.0", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-trie 37.0.0", @@ -2585,7 +2495,7 @@ dependencies = [ "sp-application-crypto 38.0.0", "sp-consensus-grandpa 21.0.0", "sp-core 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-trie 37.0.0", ] @@ -2620,13 +2530,13 @@ dependencies = [ [[package]] name = "bp-xcm-bridge-hub" -version = "0.4.2" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0873c54562b3d492541cbc8a7974c6854a5157d07880a2a71f8ba888a69e17e9" +checksum = "6909117ca87cb93703742939d5f0c4c93e9646d9cda22262e9709d68c929999b" dependencies = [ "bp-messages 0.18.0", "bp-runtime 0.18.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "parity-scale-codec", "scale-info", "serde", @@ -2656,7 +2566,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-core 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "staging-xcm 14.2.0", ] @@ -2683,13 +2593,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c31b53c53d627e2da38f8910807944bf3121e154b5c0ac9e122995af9dfb13ed" dependencies = [ "cumulus-primitives-core 0.16.0", - "frame-support 38.2.0", - "pallet-message-queue 41.0.2", + "frame-support 38.0.0", + "pallet-message-queue 41.0.1", "parity-scale-codec", "scale-info", "snowbridge-core 0.10.0", "sp-core 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "staging-xcm 14.2.0", ] @@ -2902,11 +2812,11 @@ dependencies = [ "bp-relayers 0.18.0", "bp-runtime 0.18.0", "bp-test-utils 0.18.0", - "bp-xcm-bridge-hub 0.4.2", - "bridge-runtime-common 0.18.2", + "bp-xcm-bridge-hub 0.4.0", + "bridge-runtime-common 0.18.0", "cumulus-pallet-parachain-system 0.17.1", "cumulus-pallet-xcmp-queue 0.17.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "impl-trait-for-tuples", "log", @@ -2914,21 +2824,21 @@ dependencies = [ "pallet-bridge-grandpa 0.18.0", "pallet-bridge-messages 0.18.0", "pallet-bridge-parachains 0.18.0", - "pallet-bridge-relayers 0.18.2", + "pallet-bridge-relayers 0.18.0", "pallet-timestamp 37.0.0", "pallet-utility 38.0.0", - "pallet-xcm 17.0.1", - "pallet-xcm-bridge-hub 0.13.2", + "pallet-xcm 17.0.0", + "pallet-xcm-bridge-hub 0.13.0", "parachains-common 18.0.0", "parachains-runtimes-test-utils 17.0.0", "parity-scale-codec", "sp-core 34.0.0", "sp-io 38.0.0", "sp-keyring 39.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-tracing 17.0.1", "staging-xcm 14.2.0", - "staging-xcm-builder 17.0.3", + "staging-xcm-builder 17.0.1", "staging-xcm-executor 17.0.0", ] @@ -3122,9 +3032,9 @@ dependencies = [ [[package]] name = "bridge-runtime-common" -version = "0.18.2" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86cf718057e18ce3e5f2c8e3fc318c38ad71d47ada91dc4b841c0f69c214ef04" +checksum = "c639aa22de6e904156a3e8b0e6b9e6af790cb27a1299688cc07997e1ffe5b648" dependencies = [ "bp-header-chain 0.18.1", "bp-messages 0.18.0", @@ -3132,20 +3042,20 @@ dependencies = [ "bp-polkadot-core 0.18.0", "bp-relayers 0.18.0", "bp-runtime 0.18.0", - "bp-xcm-bridge-hub 0.4.2", - "frame-support 38.2.0", + "bp-xcm-bridge-hub 0.4.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-bridge-grandpa 0.18.0", "pallet-bridge-messages 0.18.0", "pallet-bridge-parachains 0.18.0", - "pallet-bridge-relayers 0.18.2", - "pallet-transaction-payment 38.0.2", + "pallet-bridge-relayers 0.18.0", + "pallet-transaction-payment 38.0.0", "pallet-utility 38.0.0", "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-trie 37.0.0", "staging-xcm 14.2.0", @@ -3164,12 +3074,12 @@ dependencies = [ [[package]] name = "bstr" -version = "1.11.3" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" +checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" dependencies = [ "memchr", - "regex-automata 0.4.9", + "regex-automata 0.3.6", "serde", ] @@ -3184,9 +3094,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "byte-slice-cast" @@ -3202,9 +3112,9 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" [[package]] name = "bytemuck" -version = "1.21.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" +checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" [[package]] name = "byteorder" @@ -3214,9 +3124,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.9.0" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" dependencies = [ "serde", ] @@ -3257,24 +3167,24 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "846501f4575cd66766a40bb7ab6d8e960adc7eb49f753c8232bd8e0e09cf6ca2" dependencies = [ - "quote 1.0.38", + "quote 1.0.37", "syn 1.0.109", ] [[package]] name = "camino" -version = "1.1.9" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" dependencies = [ "serde", ] [[package]] name = "cargo-platform" -version = "0.1.9" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +checksum = "2cfa25e60aea747ec7e1124f238816749faa93759c6ff5b31f1ccdda137f4479" dependencies = [ "serde", ] @@ -3287,10 +3197,10 @@ checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" dependencies = [ "camino", "cargo-platform", - "semver 1.0.24", + "semver 1.0.18", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -3307,9 +3217,9 @@ checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" [[package]] name = "cc" -version = "1.2.9" +version = "1.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8293772165d9345bdaaa39b45b2109591e63fe5e6fbc23c6ff930a048aa310b" +checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" dependencies = [ "jobserver", "libc", @@ -3333,9 +3243,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.15.8" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +checksum = "03915af431787e6ffdcc74c645077518c6b6e01f80b761e0fbbfa288536311b3" dependencies = [ "smallvec", ] @@ -3421,9 +3331,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.39" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", @@ -3431,14 +3341,14 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] name = "ciborium" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" dependencies = [ "ciborium-io", "ciborium-ll", @@ -3447,15 +3357,15 @@ dependencies = [ [[package]] name = "ciborium-io" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" +checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" [[package]] name = "ciborium-ll" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" dependencies = [ "ciborium-io", "half", @@ -3529,9 +3439,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.8.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" dependencies = [ "glob", "libc", @@ -3548,69 +3458,108 @@ dependencies = [ "atty", "bitflags 1.3.2", "strsim 0.8.0", - "textwrap", - "unicode-width 0.1.14", + "textwrap 0.11.0", + "unicode-width", "vec_map", ] [[package]] name = "clap" -version = "4.5.26" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "atty", + "bitflags 1.3.2", + "clap_derive 3.2.25", + "clap_lex 0.2.4", + "indexmap 1.9.3", + "once_cell", + "strsim 0.10.0", + "termcolor", + "textwrap 0.16.0", +] + +[[package]] +name = "clap" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" +checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" dependencies = [ "clap_builder", - "clap_derive", + "clap_derive 4.5.13", ] [[package]] name = "clap-num" -version = "1.1.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e063d263364859dc54fb064cedb7c122740cd4733644b14b176c097f51e8ab7" +checksum = "488557e97528174edaa2ee268b23a809e0c598213a4bbcb4f34575a46fda147e" dependencies = [ "num-traits", ] [[package]] name = "clap_builder" -version = "4.5.26" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" +checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" dependencies = [ "anstream", "anstyle", - "clap_lex", + "clap_lex 0.7.0", "strsim 0.11.1", "terminal_size", ] [[package]] name = "clap_complete" -version = "4.5.42" +version = "4.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa3c596da3cf0983427b0df0dba359df9182c13bd5b519b585a482b0c351f4e8" +dependencies = [ + "clap 4.5.13", +] + +[[package]] +name = "clap_derive" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33a7e468e750fa4b6be660e8b5651ad47372e8fb114030b594c2d75d48c5ffd0" +checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" dependencies = [ - "clap 4.5.26", + "heck 0.4.1", + "proc-macro-error", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 1.0.109", ] [[package]] name = "clap_derive" -version = "4.5.24" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ "heck 0.5.0", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "cmd_lib" @@ -3619,7 +3568,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "371c15a3c178d0117091bd84414545309ca979555b1aad573ef591ad58818d41" dependencies = [ "cmd_lib_macros", - "env_logger 0.10.2", + "env_logger 0.10.1", "faccess", "lazy_static", "log", @@ -3633,19 +3582,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb844bd05be34d91eb67101329aeba9d3337094c04fd8507d821db7ebb488eaf" dependencies = [ "proc-macro-error2", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "coarsetime" -version = "0.1.35" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4252bf230cb600c19826a575b31c8c9c84c6f11acfab6dfcad2e941b10b6f8e2" +checksum = "a90d114103adbc625300f346d4d09dfb4ab1c4a8df6868435dd903392ecf4354" dependencies = [ "libc", - "wasix", + "once_cell", + "wasi", "wasm-bindgen", ] @@ -3656,7 +3606,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" dependencies = [ "termcolor", - "unicode-width 0.1.14", + "unicode-width", ] [[package]] @@ -3795,46 +3745,47 @@ dependencies = [ [[package]] name = "color-print" -version = "0.3.7" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3aa954171903797d5623e047d9ab69d91b493657917bdfb8c2c80ecaf9cdb6f4" +checksum = "f2a5e6504ed8648554968650feecea00557a3476bc040d0ffc33080e66b646d0" dependencies = [ "color-print-proc-macro", ] [[package]] name = "color-print-proc-macro" -version = "0.3.7" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692186b5ebe54007e45a59aea47ece9eb4108e141326c304cdc91699a7118a22" +checksum = "d51beaa537d73d2d1ff34ee70bc095f170420ab2ec5d687ecd3ec2b0d092514b" dependencies = [ "nom", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 1.0.109", ] [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "colored" -version = "2.2.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" dependencies = [ + "is-terminal", "lazy_static", - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] name = "combine" -version = "4.6.7" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" dependencies = [ "bytes", "memchr", @@ -3842,13 +3793,13 @@ dependencies = [ [[package]] name = "comfy-table" -version = "7.1.3" +version = "7.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24f165e7b643266ea80cb858aed492ad9280e3e05ce24d4a99d7d7b889b6a4d9" +checksum = "7c64043d6c7b7a4c58e39e7efccfdea7b93d885a795d0c054a69dbbf4dd52686" dependencies = [ - "strum 0.26.3", - "strum_macros 0.26.4", - "unicode-width 0.2.0", + "strum 0.25.0", + "strum_macros 0.25.3", + "unicode-width", ] [[package]] @@ -3856,9 +3807,9 @@ name = "common" version = "0.1.0" source = "git+https://github.com/w3f/ring-proof?rev=665f5f5#665f5f51af5734c7b6d90b985dd6861d4c5b4752" dependencies = [ - "ark-ec 0.4.2", + "ark-ec", "ark-ff 0.4.2", - "ark-poly 0.4.2", + "ark-poly", "ark-serialize 0.4.2", "ark-std 0.4.0", "fflonk", @@ -3875,9 +3826,9 @@ checksum = "2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101" [[package]] name = "comparable" -version = "0.5.5" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8606f9aa5b5a2df738584b139c79413d0c1545ed0ffd16e76e0944d1de7388c0" +checksum = "eb513ee8037bf08c5270ecefa48da249f4c58e57a71ccfce0a5b0877d2a20eb2" dependencies = [ "comparable_derive", "comparable_helper", @@ -3887,25 +3838,25 @@ dependencies = [ [[package]] name = "comparable_derive" -version = "0.5.5" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f36ea7383b9a2a9ae0a4e225d8a9c1c3aeadde78c59cdc35bad5c02b4dad01" +checksum = "a54b9c40054eb8999c5d1d36fdc90e4e5f7ff0d1d9621706f360b3cbc8beb828" dependencies = [ "convert_case 0.4.0", - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "syn 1.0.109", ] [[package]] name = "comparable_helper" -version = "0.5.5" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71c9b60259084f32c14d32476f3a299b4997e3c186e1473bd972ff8a8c83d1b4" +checksum = "fb5437e327e861081c91270becff184859f706e3e50f5301a9d4dc8eb50752c3" dependencies = [ "convert_case 0.6.0", - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "syn 1.0.109", ] @@ -3920,15 +3871,25 @@ dependencies = [ [[package]] name = "console" -version = "0.15.10" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" dependencies = [ "encode_unicode", + "lazy_static", "libc", - "once_cell", - "unicode-width 0.2.0", - "windows-sys 0.59.0", + "unicode-width", + "windows-sys 0.52.0", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", ] [[package]] @@ -3946,27 +3907,29 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.6" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" [[package]] name = "const-random" -version = "0.1.18" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +checksum = "368a7a772ead6ce7e1de82bfb04c485f3db8ec744f72925af5735e29a22cc18e" dependencies = [ "const-random-macro", + "proc-macro-hack", ] [[package]] name = "const-random-macro" -version = "0.1.16" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +checksum = "9d7d6ab3c3a2282db210df5f02c4dab6e0a7057af0fb7ebd4070f30fe05c0ddb" dependencies = [ "getrandom", "once_cell", + "proc-macro-hack", "tiny-keccak", ] @@ -3978,15 +3941,21 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "constant_time_eq" -version = "0.3.1" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a53c0a4d288377e7415b53dcfc3c04da5cdc2cc95c8d5ac178b58f0b861ad6" + +[[package]] +name = "constant_time_eq" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" [[package]] name = "constcat" -version = "0.3.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7e35aee659887cbfb97aaf227ac12cad1a9d7c71e55ff3376839ed4e282d08" +checksum = "f272d0c4cf831b4fa80ee529c7707f76585986e910e1fbce1d7921970bc1a241" [[package]] name = "contracts-rococo-runtime" @@ -4083,21 +4052,11 @@ dependencies = [ "libc", ] -[[package]] -name = "core-foundation" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" -version = "0.8.7" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "core2" @@ -4320,9 +4279,9 @@ dependencies = [ [[package]] name = "cpp_demangle" -version = "0.4.4" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96e58d342ad113c2b878f16d5d034c03be492ae460cdbc02b7f0f2284d310c7d" +checksum = "7e8227005286ec39567949b33df9896bcadfa6051bccca2488129f108ca23119" dependencies = [ "cfg-if", ] @@ -4339,9 +4298,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ "libc", ] @@ -4440,7 +4399,7 @@ dependencies = [ "itertools 0.10.5", "log", "smallvec", - "wasmparser 0.102.0", + "wasmparser", "wasmtime-types", ] @@ -4461,9 +4420,9 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] @@ -4477,7 +4436,7 @@ dependencies = [ "anes", "cast", "ciborium", - "clap 4.5.26", + "clap 4.5.13", "criterion-plot", "futures", "is-terminal", @@ -4508,37 +4467,42 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.6" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" dependencies = [ + "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.18" +version = "0.9.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ + "autocfg", + "cfg-if", "crossbeam-utils", + "memoffset 0.9.0", + "scopeguard", ] [[package]] name = "crossbeam-queue" -version = "0.3.12" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.21" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" @@ -4548,13 +4512,13 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.5.5" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" dependencies = [ "generic-array 0.14.7", "rand_core 0.6.4", - "subtle 2.6.1", + "subtle 2.5.0", "zeroize", ] @@ -4586,7 +4550,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ "generic-array 0.14.7", - "subtle 2.6.1", + "subtle 2.5.0", ] [[package]] @@ -4600,7 +4564,7 @@ dependencies = [ "generic-array 0.14.7", "poly1305", "salsa20", - "subtle 2.6.1", + "subtle 2.5.0", "zeroize", ] @@ -4617,7 +4581,7 @@ dependencies = [ name = "cumulus-client-cli" version = "0.7.0" dependencies = [ - "clap 4.5.26", + "clap 4.5.13", "parity-scale-codec", "sc-chain-spec", "sc-cli", @@ -4749,7 +4713,7 @@ dependencies = [ "sp-inherents 26.0.0", "sp-runtime 31.0.1", "sp-state-machine 0.35.0", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -4933,7 +4897,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cbe2735fc7cf2b6521eab00cb1a1ab025abc1575cc36887b36dc8c5cb1c9434" dependencies = [ "cumulus-pallet-parachain-system 0.17.1", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "pallet-aura 37.0.0", "pallet-timestamp 37.0.0", @@ -4941,7 +4905,7 @@ dependencies = [ "scale-info", "sp-application-crypto 38.0.0", "sp-consensus-aura 0.40.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -4970,13 +4934,13 @@ checksum = "97263a8e758d201ebe81db7cea7b278b4fb869c11442f77acef70138ac1a252f" dependencies = [ "cumulus-primitives-core 0.16.0", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "staging-xcm 14.2.0", ] @@ -5040,11 +5004,11 @@ dependencies = [ "cumulus-primitives-proof-size-hostfunction 0.10.0", "environmental", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "impl-trait-for-tuples", "log", - "pallet-message-queue 41.0.2", + "pallet-message-queue 41.0.1", "parity-scale-codec", "polkadot-parachain-primitives 14.0.0", "polkadot-runtime-common 17.0.0", @@ -5054,13 +5018,13 @@ dependencies = [ "sp-externalities 0.29.0", "sp-inherents 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-state-machine 0.43.0", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-trie 37.0.0", "sp-version 37.0.0", "staging-xcm 14.2.0", - "staging-xcm-builder 17.0.3", + "staging-xcm-builder 17.0.1", "trie-db", ] @@ -5068,10 +5032,10 @@ dependencies = [ name = "cumulus-pallet-parachain-system-proc-macro" version = "0.6.0" dependencies = [ - "proc-macro-crate 3.2.0", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro-crate 3.1.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -5080,10 +5044,10 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "befbaf3a1ce23ac8476481484fef5f4d500cbd15b4dad6380ce1d28134b0c1f7" dependencies = [ - "proc-macro-crate 3.2.0", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro-crate 3.1.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -5105,11 +5069,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18168570689417abfb514ac8812fca7e6429764d01942750e395d7d8ce0716ef" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "pallet-session 38.0.0", "parity-scale-codec", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -5133,13 +5097,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42c74548c8cab75da6f2479a953f044b582cfce98479862344a24df7bbd215" dependencies = [ "cumulus-pallet-parachain-system 0.17.1", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "pallet-sudo 38.0.0", "parity-scale-codec", "polkadot-primitives 16.0.0", "scale-info", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -5182,12 +5146,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e49231f6cd8274438b078305dc8ce44c54c0d3f4a28e902589bcbaa53d954608" dependencies = [ "cumulus-primitives-core 0.16.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "staging-xcm 14.2.0", ] @@ -5227,19 +5191,19 @@ dependencies = [ "bp-xcm-bridge-hub-router 0.14.1", "cumulus-primitives-core 0.16.0", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", - "pallet-message-queue 41.0.2", + "pallet-message-queue 41.0.1", "parity-scale-codec", "polkadot-runtime-common 17.0.0", "polkadot-runtime-parachains 17.0.1", "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "staging-xcm 14.2.0", - "staging-xcm-builder 17.0.3", + "staging-xcm-builder 17.0.1", "staging-xcm-executor 17.0.0", ] @@ -5265,11 +5229,11 @@ checksum = "f47128f797359951723e2d106a80e592d007bb7446c299958cdbafb1489ddbf0" dependencies = [ "cumulus-pallet-xcm 0.17.0", "cumulus-primitives-core 0.16.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "staging-xcm 14.2.0", ] @@ -5278,7 +5242,7 @@ name = "cumulus-pov-validator" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.5.26", + "clap 4.5.13", "parity-scale-codec", "polkadot-node-primitives", "polkadot-parachain-primitives 6.0.0", @@ -5288,7 +5252,7 @@ dependencies = [ "sp-io 30.0.0", "sp-maybe-compressed-blob 11.0.0", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.18", ] [[package]] @@ -5310,7 +5274,7 @@ dependencies = [ "polkadot-primitives 15.0.0", "sp-api 34.0.0", "sp-consensus-aura 0.40.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -5340,7 +5304,7 @@ dependencies = [ "polkadot-primitives 16.0.0", "scale-info", "sp-api 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-trie 37.0.0", "staging-xcm 14.2.0", ] @@ -5424,12 +5388,12 @@ dependencies = [ "cumulus-primitives-core 0.16.0", "cumulus-primitives-proof-size-hostfunction 0.10.0", "docify", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -5475,14 +5439,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bdcf4d46dd93f1e6d5dd6d379133566a44042ba6476d04bdcbdb4981c622ae4" dependencies = [ "cumulus-primitives-core 0.16.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "log", "pallet-asset-conversion 20.0.0", "parity-scale-codec", "polkadot-runtime-common 17.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "staging-xcm 14.2.0", - "staging-xcm-builder 17.0.3", + "staging-xcm-builder 17.0.1", "staging-xcm-executor 17.0.0", ] @@ -5529,7 +5493,7 @@ dependencies = [ "sp-blockchain", "sp-state-machine 0.35.0", "sp-version 29.0.0", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -5600,7 +5564,7 @@ dependencies = [ "sp-storage 19.0.0", "sp-version 29.0.0", "substrate-prometheus-endpoint", - "thiserror 1.0.69", + "thiserror", "tokio", "tokio-util", "tracing", @@ -5665,7 +5629,7 @@ dependencies = [ "cumulus-primitives-core 0.16.0", "parity-scale-codec", "polkadot-primitives 16.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-state-machine 0.43.0", "sp-trie 37.0.0", ] @@ -5718,7 +5682,7 @@ name = "cumulus-test-service" version = "0.1.0" dependencies = [ "async-trait", - "clap 4.5.26", + "clap 4.5.13", "criterion", "cumulus-client-cli", "cumulus-client-collator", @@ -5797,24 +5761,24 @@ dependencies = [ [[package]] name = "curl" -version = "0.4.47" +version = "0.4.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9fb4d13a1be2b58f14d60adba57c9834b78c62fd86c3e76a148f732686e9265" +checksum = "1e2161dd6eba090ff1594084e95fd67aeccf04382ffea77999ea94ed42ec67b6" dependencies = [ "curl-sys", "libc", "openssl-probe", "openssl-sys", "schannel", - "socket2 0.5.8", + "socket2 0.5.7", "windows-sys 0.52.0", ] [[package]] name = "curl-sys" -version = "0.4.78+curl-8.11.0" +version = "0.4.72+curl-8.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eec768341c5c7789611ae51cf6c459099f22e64a5d5d0ce4892434e33821eaf" +checksum = "29cbdc8314c447d11e8fd156dcdd031d9e02a7a976163e396b548c03153bc9ea" dependencies = [ "cc", "libc", @@ -5835,7 +5799,7 @@ dependencies = [ "byteorder", "digest 0.9.0", "rand_core 0.5.1", - "subtle 2.6.1", + "subtle 2.5.0", "zeroize", ] @@ -5850,20 +5814,20 @@ dependencies = [ "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", - "rustc_version 0.4.1", - "subtle 2.6.1", + "rustc_version 0.4.0", + "subtle 2.5.0", "zeroize", ] [[package]] name = "curve25519-dalek-derive" -version = "0.1.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -5881,61 +5845,46 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.136" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad7c7515609502d316ab9a24f67dc045132d93bfd3f00713389e90d9898bf30d" +checksum = "28403c86fc49e3401fdf45499ba37fad6493d9329449d6449d7f0e10f4654d28" dependencies = [ "cc", - "cxxbridge-cmd", "cxxbridge-flags", "cxxbridge-macro", - "foldhash", "link-cplusplus", ] [[package]] name = "cxx-build" -version = "1.0.136" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bfd16fca6fd420aebbd80d643c201ee4692114a0de208b790b9cd02ceae65fb" +checksum = "78da94fef01786dc3e0c76eafcd187abcaa9972c78e05ff4041e24fdf059c285" dependencies = [ "cc", "codespan-reporting", - "proc-macro2 1.0.93", - "quote 1.0.38", + "once_cell", + "proc-macro2 1.0.86", + "quote 1.0.37", "scratch", - "syn 2.0.96", -] - -[[package]] -name = "cxxbridge-cmd" -version = "1.0.136" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c33fd49f5d956a1b7ee5f7a9768d58580c6752838d92e39d0d56439efdedc35" -dependencies = [ - "clap 4.5.26", - "codespan-reporting", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "syn 2.0.87", ] [[package]] name = "cxxbridge-flags" -version = "1.0.136" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0f1077278fac36299cce8446effd19fe93a95eedb10d39265f3bf67b3036c9" +checksum = "e2a6f5e1dfb4b34292ad4ea1facbfdaa1824705b231610087b00b17008641809" [[package]] name = "cxxbridge-macro" -version = "1.0.136" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da7e4d6e74af6b79031d264b2f13c3ea70af1978083741c41ffce9308f1f24f" +checksum = "50c49547d73ba8dcfd4ad7325d64c6d5391ff4224d498fc39a6f3f49825a530d" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "rustversion", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -5956,10 +5905,10 @@ checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "strsim 0.11.1", - "syn 2.0.96", + "syn 2.0.87", ] [[package]] @@ -5969,34 +5918,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", - "quote 1.0.38", - "syn 2.0.96", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "dashmap" -version = "5.5.3" +version = "5.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +checksum = "edd72493923899c6f10c641bdbdeddc7183d6396641d99c1a0d1597f37f92e28" dependencies = [ "cfg-if", "hashbrown 0.14.5", "lock_api", "once_cell", - "parking_lot_core 0.9.10", + "parking_lot_core 0.9.8", ] [[package]] name = "data-encoding" -version = "2.7.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "data-encoding-macro" -version = "0.1.16" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b16d9d0d88a5273d830dac8b78ceb217ffc9b1d5404e5597a3542515329405b" +checksum = "c904b33cc60130e1aeea4956ab803d08a3f4a0ca82d64ed757afac3891f2bb99" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -6004,12 +5953,12 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.14" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1145d32e826a7748b69ee8fc62d3e6355ff7f1051df53141e7048162fc90481b" +checksum = "8fdf3fce3ce863539ec1d7fd1b6dcc3c645663376b43ed376bbf887733e4f772" dependencies = [ "data-encoding", - "syn 2.0.96", + "syn 1.0.109", ] [[package]] @@ -6023,9 +5972,9 @@ dependencies = [ [[package]] name = "der" -version = "0.7.9" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" dependencies = [ "const-oid", "pem-rfc7468", @@ -6061,8 +6010,8 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "syn 1.0.109", ] @@ -6072,9 +6021,9 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -6083,33 +6032,33 @@ version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "derive_arbitrary" -version = "1.4.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "derive_more" -version = "0.99.18" +version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ "convert_case 0.4.0", - "proc-macro2 1.0.93", - "quote 1.0.38", - "rustc_version 0.4.1", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "rustc_version 0.4.0", + "syn 1.0.109", ] [[package]] @@ -6127,10 +6076,10 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", - "unicode-xid 0.2.6", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", + "unicode-xid 0.2.4", ] [[package]] @@ -6172,7 +6121,7 @@ dependencies = [ "block-buffer 0.10.4", "const-oid", "crypto-common", - "subtle 2.6.1", + "subtle 2.5.0", ] [[package]] @@ -6228,46 +6177,44 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "dissimilar" -version = "1.0.9" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59f8e79d1fbf76bdfbde321e902714bf6c49df88a7dda6fc682fc2979226962d" +checksum = "86e3bdc80eee6e16b2b6b0f87fbc98c04bee3455e35174c0de1a125d0688c632" [[package]] name = "dleq_vrf" version = "0.0.2" source = "git+https://github.com/w3f/ring-vrf?rev=0fef826#0fef8266d851932ad25d6b41bc4b34d834d1e11d" dependencies = [ - "ark-ec 0.4.2", + "ark-ec", "ark-ff 0.4.2", - "ark-scale", + "ark-scale 0.0.12", "ark-secret-scalar", "ark-serialize 0.4.2", "ark-std 0.4.0", "ark-transcript", - "arrayvec 0.7.6", + "arrayvec 0.7.4", "zeroize", ] [[package]] name = "dlmalloc" -version = "0.2.7" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b5e0d321d61de16390ed273b647ce51605b575916d3c25e6ddf27a1e140035" +checksum = "203540e710bfadb90e5e29930baf5d10270cec1f43ab34f46f78b147b2de715a" dependencies = [ - "cfg-if", "libc", - "windows-sys 0.59.0", ] [[package]] @@ -6294,10 +6241,10 @@ dependencies = [ "common-path", "derive-syn-parse", "once_cell", - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "regex", - "syn 2.0.96", + "syn 2.0.87", "termcolor", "toml 0.8.19", "walkdir", @@ -6326,9 +6273,9 @@ checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" [[package]] name = "downcast-rs" -version = "1.2.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "dtoa" @@ -6338,9 +6285,9 @@ checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" [[package]] name = "dunce" -version = "1.0.5" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" [[package]] name = "dyn-clonable" @@ -6358,22 +6305,22 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "558e40ea573c374cf53507fd240b7ee2f5477df7cfebdb97323ec61c719399c5" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "syn 1.0.109", ] [[package]] name = "dyn-clone" -version = "1.0.17" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" [[package]] name = "ecdsa" -version = "0.16.9" +version = "0.16.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" dependencies = [ "der", "digest 0.10.7", @@ -6386,9 +6333,9 @@ dependencies = [ [[package]] name = "ed25519" -version = "2.2.3" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d" dependencies = [ "pkcs8", "signature", @@ -6405,7 +6352,7 @@ dependencies = [ "rand_core 0.6.4", "serde", "sha2 0.10.8", - "subtle 2.6.1", + "subtle 2.5.0", "zeroize", ] @@ -6438,18 +6385,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "educe" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" -dependencies = [ - "enum-ordinalize", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", -] - [[package]] name = "either" version = "1.13.0" @@ -6475,7 +6410,7 @@ dependencies = [ "rand_core 0.6.4", "sec1", "serdect", - "subtle 2.6.1", + "subtle 2.5.0", "zeroize", ] @@ -6516,87 +6451,67 @@ dependencies = [ [[package]] name = "encode_unicode" -version = "1.0.0" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.35" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ "cfg-if", ] [[package]] name = "enum-as-inner" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" -dependencies = [ - "heck 0.5.0", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", -] - -[[package]] -name = "enum-ordinalize" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" -dependencies = [ - "enum-ordinalize-derive", -] - -[[package]] -name = "enum-ordinalize-derive" -version = "4.3.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" +checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "heck 0.4.1", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "enumflags2" -version = "0.7.10" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" +checksum = "c041f5090df68b32bcd905365fd51769c8b9d553fe87fde0b683534f10c01bd2" dependencies = [ "enumflags2_derive", ] [[package]] name = "enumflags2_derive" -version = "0.7.10" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" +checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "enumn" -version = "0.1.14" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" +checksum = "6fd000fd6988e73bbe993ea3db9b1aa64906ab88766d654973924340c8cddb42" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "env_filter" -version = "0.1.3" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" dependencies = [ "log", "regex", @@ -6614,9 +6529,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.10.2" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" dependencies = [ "humantime", "is-terminal", @@ -6627,9 +6542,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.6" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" dependencies = [ "anstream", "anstyle", @@ -6667,12 +6582,11 @@ dependencies = [ [[package]] name = "erased-serde" -version = "0.4.5" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" +checksum = "2b73807008a3c7f171cc40312f37d95ef0396e048b5848d775f54b1a4dd4a0d3" dependencies = [ "serde", - "typeid", ] [[package]] @@ -6719,7 +6633,7 @@ dependencies = [ "serde", "serde_json", "sha3 0.10.8", - "thiserror 1.0.69", + "thiserror", "uint 0.9.5", ] @@ -6813,20 +6727,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener" -version = "5.4.0" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" dependencies = [ "concurrent-queue", "parking", @@ -6835,11 +6738,11 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ - "event-listener 5.4.0", + "event-listener 5.3.1", "pin-project-lite", ] @@ -6862,16 +6765,16 @@ dependencies = [ "file-guard", "fs-err", "prettyplease", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "eyre" -version = "0.6.12" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" dependencies = [ "indenter", "once_cell", @@ -6921,18 +6824,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" dependencies = [ - "arrayvec 0.7.6", - "auto_impl", - "bytes", -] - -[[package]] -name = "fastrlp" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" -dependencies = [ - "arrayvec 0.7.6", + "arrayvec 0.7.4", "auto_impl", "bytes", ] @@ -6944,7 +6836,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec6f82451ff7f0568c6181287189126d492b5654e30a788add08027b6363d019" dependencies = [ "fatality-proc-macro", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -6955,10 +6847,10 @@ checksum = "eb42427514b063d97ce21d5199f36c0c307d981434a6be32582bc79fe5bd2303" dependencies = [ "expander", "indexmap 2.7.0", - "proc-macro-crate 3.2.0", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro-crate 3.1.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -6968,7 +6860,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e182f7dbc2ef73d9ef67351c5fbbea084729c48362d3ce9dd44c28e32e277fe5" dependencies = [ "libc", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -6994,27 +6886,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ "rand_core 0.6.4", - "subtle 2.6.1", + "subtle 2.5.0", ] [[package]] name = "fflonk" -version = "0.1.1" -source = "git+https://github.com/w3f/fflonk#eda051ea3b80042e844a3ebd17c2f60536e6ee3f" +version = "0.1.0" +source = "git+https://github.com/w3f/fflonk#1e854f35e9a65d08b11a86291405cdc95baa0a35" dependencies = [ - "ark-ec 0.5.0", - "ark-ff 0.5.0", - "ark-poly 0.5.0", - "ark-serialize 0.5.0", - "ark-std 0.5.0", + "ark-ec", + "ark-ff 0.4.2", + "ark-poly", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "merlin", ] [[package]] name = "fiat-crypto" -version = "0.2.9" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7" [[package]] name = "file-guard" @@ -7032,20 +6924,20 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84f2e425d9790201ba4af4630191feac6dcc98765b118d4d18e91d23c2353866" dependencies = [ - "env_logger 0.10.2", + "env_logger 0.10.1", "log", ] [[package]] name = "filetime" -version = "0.2.25" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" dependencies = [ "cfg-if", "libc", - "libredox", - "windows-sys 0.59.0", + "redox_syscall 0.3.5", + "windows-sys 0.48.0", ] [[package]] @@ -7122,12 +7014,12 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.35" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" dependencies = [ "crc32fast", - "miniz_oxide 0.8.3", + "miniz_oxide", ] [[package]] @@ -7158,9 +7050,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" [[package]] name = "foreign-types" @@ -7200,7 +7092,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9" dependencies = [ "nonempty", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -7254,7 +7146,7 @@ version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a01bdd47c2d541b38bd892da647d1e972c9d85b4ecd7094ad64f7600175da54d" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-support-procedural 30.0.4", "frame-system 38.0.0", "linregress", @@ -7267,7 +7159,7 @@ dependencies = [ "sp-application-crypto 38.0.0", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-runtime-interface 28.0.0", "sp-storage 21.0.0", "static_assertions", @@ -7280,7 +7172,7 @@ dependencies = [ "Inflector", "array-bytes", "chrono", - "clap 4.5.26", + "clap 4.5.13", "comfy-table", "cumulus-client-parachain-inherent", "cumulus-primitives-proof-size-hostfunction 0.2.0", @@ -7333,7 +7225,7 @@ dependencies = [ "substrate-test-runtime", "subxt", "subxt-signer", - "thiserror 1.0.69", + "thiserror", "thousands", "westend-runtime", ] @@ -7358,12 +7250,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ffde6f573a63eeb1ccb7d2667c5741a11ce93bc30f33712e5326b9d8a811c29" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -7387,12 +7279,12 @@ dependencies = [ "frame-election-provider-support 28.0.0", "frame-support 28.0.0", "parity-scale-codec", - "proc-macro-crate 3.2.0", - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro-crate 3.1.0", + "proc-macro2 1.0.86", + "quote 1.0.37", "scale-info", "sp-arithmetic 23.0.0", - "syn 2.0.96", + "syn 2.0.87", "trybuild", ] @@ -7402,10 +7294,10 @@ version = "14.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8156f209055d352994ecd49e19658c6b469d7c6de923bd79868957d0dcfb6f71" dependencies = [ - "proc-macro-crate 3.2.0", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro-crate 3.1.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -7433,21 +7325,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c36f5116192c63d39f1b4556fa30ac7db5a6a52575fa241b045f7dfa82ecc2be" dependencies = [ "frame-election-provider-solution-type 14.0.1", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "scale-info", "sp-arithmetic 26.0.0", "sp-core 34.0.0", "sp-npos-elections 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] name = "frame-election-solution-type-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 4.5.26", + "clap 4.5.13", "frame-election-provider-solution-type 13.0.0", "frame-election-provider-support 28.0.0", "frame-support 28.0.0", @@ -7489,7 +7381,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c365bf3879de25bbee28e9584096955a02fbe8d7e7624e10675800317f1cee5b" dependencies = [ "aquamarine", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "frame-try-runtime 0.44.0", "log", @@ -7497,7 +7389,7 @@ dependencies = [ "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-tracing 17.0.1", ] @@ -7567,12 +7459,12 @@ checksum = "56ac71dbd97039c49fdd69f416a4dd5d8da3652fdcafc3738b45772ad79eb4ec" dependencies = [ "array-bytes", "docify", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -7580,7 +7472,7 @@ name = "frame-omni-bencher" version = "0.1.0" dependencies = [ "assert_cmd", - "clap 4.5.26", + "clap 4.5.13", "cumulus-primitives-proof-size-hostfunction 0.2.0", "cumulus-test-runtime", "frame-benchmarking-cli", @@ -7592,7 +7484,7 @@ dependencies = [ "sp-statement-store 10.0.0", "sp-tracing 16.0.0", "tempfile", - "tracing-subscriber", + "tracing-subscriber 0.3.18", ] [[package]] @@ -7667,9 +7559,9 @@ dependencies = [ [[package]] name = "frame-support" -version = "38.2.0" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7dd8b9f161a8289e3b9fe6c1068519358dbff2270d38097a923d3d1b4459dca" +checksum = "1e44af69fa61bc5005ffe0339e198957e77f0f255704a9bee720da18a733e3dc" dependencies = [ "aquamarine", "array-bytes", @@ -7697,7 +7589,7 @@ dependencies = [ "sp-inherents 34.0.0", "sp-io 38.0.0", "sp-metadata-ir 0.7.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-staking 36.0.0", "sp-state-machine 0.43.0", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -7724,8 +7616,8 @@ dependencies = [ "parity-scale-codec", "pretty_assertions", "proc-macro-warning", - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "regex", "scale-info", "sp-core 28.0.0", @@ -7734,7 +7626,7 @@ dependencies = [ "sp-metadata-ir 0.6.0", "sp-runtime 31.0.1", "static_assertions", - "syn 2.0.96", + "syn 2.0.87", ] [[package]] @@ -7747,14 +7639,14 @@ dependencies = [ "cfg-expr", "derive-syn-parse", "expander", - "frame-support-procedural-tools 13.0.1", + "frame-support-procedural-tools 13.0.0", "itertools 0.11.0", "macro_magic", "proc-macro-warning", - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 2.0.96", + "syn 2.0.87", ] [[package]] @@ -7762,32 +7654,32 @@ name = "frame-support-procedural-tools" version = "10.0.0" dependencies = [ "frame-support-procedural-tools-derive 11.0.0", - "proc-macro-crate 3.2.0", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro-crate 3.1.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "frame-support-procedural-tools" -version = "13.0.1" +version = "13.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81a088fd6fda5f53ff0c17fc7551ce8bd0ead14ba742228443c8196296a7369b" +checksum = "bead15a320be1764cdd50458c4cfacb23e0cee65f64f500f8e34136a94c7eeca" dependencies = [ "frame-support-procedural-tools-derive 12.0.0", - "proc-macro-crate 3.2.0", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro-crate 3.1.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "frame-support-procedural-tools-derive" version = "11.0.0" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -7796,9 +7688,9 @@ version = "12.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed971c6435503a099bdac99fe4c5bea08981709e5b5a0a8535a1856f48561191" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -7891,14 +7783,14 @@ checksum = "e3c7fa02f8c305496d2ae52edaecdb9d165f11afa965e05686d7d7dd1ce93611" dependencies = [ "cfg-if", "docify", - "frame-support 38.2.0", + "frame-support 38.0.0", "log", "parity-scale-codec", "scale-info", "serde", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-version 37.0.0", "sp-weights 31.0.0", @@ -7927,12 +7819,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9693b2a736beb076e673520e1e8dee4fc128b8d35b020ef3e8a4b1b5ad63d9f2" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "scale-info", "sp-core 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -7971,20 +7863,17 @@ version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83c811a5a1f5429c7fb5ebbf6cf9502d8f9b673fd395c12cf46c44a30a7daf0e" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "parity-scale-codec", "sp-api 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] name = "fs-err" -version = "2.11.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" -dependencies = [ - "autocfg", -] +checksum = "0845fa252299212f0389d64ba26f34fa32cfe41588355f21ed507c59a0f64541" [[package]] name = "fs2" @@ -8002,7 +7891,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29f9df8a11882c4e3335eb2d18a0137c505d9ca927470b0cac9c6f0ae07d28f7" dependencies = [ - "rustix 0.38.43", + "rustix 0.38.42", "windows-sys 0.48.0", ] @@ -8105,9 +7994,9 @@ dependencies = [ [[package]] name = "futures-lite" -version = "2.6.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ "fastrand 2.3.0", "futures-core", @@ -8122,9 +8011,9 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -8134,7 +8023,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb" dependencies = [ "futures-io", - "rustls 0.23.21", + "rustls 0.23.18", "rustls-pki-types", ] @@ -8156,7 +8045,7 @@ version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" dependencies = [ - "gloo-timers 0.2.6", + "gloo-timers", "send_wrapper", ] @@ -8232,15 +8121,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi", - "wasm-bindgen", ] [[package]] @@ -8255,11 +8142,11 @@ dependencies = [ [[package]] name = "ghash" -version = "0.5.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" dependencies = [ - "opaque-debug 0.3.1", + "opaque-debug 0.3.0", "polyval", ] @@ -8276,9 +8163,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" dependencies = [ "fallible-iterator 0.3.0", "stable_deref_trait", @@ -8296,9 +8183,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "glob-match" @@ -8316,12 +8203,12 @@ dependencies = [ "futures-core", "futures-sink", "gloo-utils", - "http 1.2.0", + "http 1.1.0", "js-sys", "pin-project", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -8339,18 +8226,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "gloo-utils" version = "0.2.0" @@ -8411,9 +8286,9 @@ dependencies = [ [[package]] name = "governor" -version = "0.6.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a7f542ee6b35af73b06abc0dad1c1bae89964e4e253bc4b587b91c9637867b" +checksum = "821239e5672ff23e2a7060901fa622950bbd80b649cdaadd78d1c1767ed14eb4" dependencies = [ "cfg-if", "dashmap", @@ -8422,11 +8297,9 @@ dependencies = [ "no-std-compat", "nonzero_ext", "parking_lot 0.12.3", - "portable-atomic", "quanta", "rand", "smallvec", - "spinning_top", ] [[package]] @@ -8437,7 +8310,7 @@ checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", "rand_core 0.6.4", - "subtle 2.6.1", + "subtle 2.5.0", ] [[package]] @@ -8451,7 +8324,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http 0.2.12", + "http 0.2.9", "indexmap 2.7.0", "slab", "tokio", @@ -8461,16 +8334,16 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.7" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.2.0", + "http 1.1.0", "indexmap 2.7.0", "slab", "tokio", @@ -8480,26 +8353,22 @@ dependencies = [ [[package]] name = "half" -version = "2.4.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" -dependencies = [ - "cfg-if", - "crunchy", -] +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "handlebars" -version = "5.1.2" +version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d08485b96a0e6393e9e4d1b8d48cf74ad6c063cd905eb33f42c1ce3f0377539b" +checksum = "ab283476b99e66691dee3f1640fea91487a8d81f50fb5ecc75538f8f8879a1e4" dependencies = [ "log", "pest", "pest_derive", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -8552,8 +8421,6 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ - "allocator-api2", - "equivalent", "foldhash", "serde", ] @@ -8569,11 +8436,11 @@ dependencies = [ [[package]] name = "hashlink" -version = "0.10.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" dependencies = [ - "hashbrown 0.15.2", + "hashbrown 0.14.5", ] [[package]] @@ -8612,12 +8479,6 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" -[[package]] -name = "hermit-abi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" - [[package]] name = "hex" version = "0.4.3" @@ -8629,9 +8490,9 @@ dependencies = [ [[package]] name = "hex-conservative" -version = "0.1.2" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" +checksum = "30ed443af458ccb6d81c1e7e661545f94d3176752fb1df2f543b902a1e0f51e2" [[package]] name = "hex-conservative" @@ -8639,7 +8500,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" dependencies = [ - "arrayvec 0.7.6", + "arrayvec 0.7.4", ] [[package]] @@ -8650,9 +8511,9 @@ checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" [[package]] name = "hickory-proto" -version = "0.24.2" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447afdcdb8afb9d0a852af6dc65d9b285ce720ed7a59e42a8bf2e931c67bc1b5" +checksum = "07698b8420e2f0d6447a436ba999ec85d8fbf2a398bbd737b82cac4a2e96e512" dependencies = [ "async-trait", "cfg-if", @@ -8661,12 +8522,12 @@ dependencies = [ "futures-channel", "futures-io", "futures-util", - "idna", + "idna 0.4.0", "ipnet", "once_cell", "rand", - "socket2 0.5.8", - "thiserror 1.0.69", + "socket2 0.5.7", + "thiserror", "tinyvec", "tokio", "tracing", @@ -8689,7 +8550,7 @@ dependencies = [ "rand", "resolv-conf", "smallvec", - "thiserror 1.0.69", + "thiserror", "tokio", "tracing", ] @@ -8735,23 +8596,23 @@ dependencies = [ [[package]] name = "home" -version = "0.5.11" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] name = "honggfuzz" -version = "0.5.56" +version = "0.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c76b6234c13c9ea73946d1379d33186151148e0da231506b964b44f3d023505" +checksum = "848e9c511092e0daa0a35a63e8e6e475a3e8f870741448b9f6028d69b142f18e" dependencies = [ "arbitrary", "lazy_static", - "memmap2 0.9.5", - "rustc_version 0.4.1", + "memmap2 0.5.10", + "rustc_version 0.4.0", ] [[package]] @@ -8767,9 +8628,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.12" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", @@ -8778,9 +8639,9 @@ dependencies = [ [[package]] name = "http" -version = "1.2.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", @@ -8789,23 +8650,23 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.6" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", - "http 0.2.12", + "http 0.2.9", "pin-project-lite", ] [[package]] name = "http-body" -version = "1.0.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" dependencies = [ "bytes", - "http 1.2.0", + "http 1.1.0", ] [[package]] @@ -8816,8 +8677,8 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", - "http 1.2.0", - "http-body 1.0.1", + "http 1.1.0", + "http-body 1.0.0", "pin-project-lite", ] @@ -8829,9 +8690,9 @@ checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" [[package]] name = "httparse" -version = "1.9.5" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" @@ -8847,22 +8708,22 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.32" +version = "0.14.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", + "http 0.2.9", + "http-body 0.4.5", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.8", + "socket2 0.5.7", "tokio", "tower-service", "tracing", @@ -8871,16 +8732,16 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.2" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.7", - "http 1.2.0", - "http-body 1.0.1", + "h2 0.4.5", + "http 1.1.0", + "http-body 1.0.0", "httparse", "httpdate", "itoa", @@ -8897,10 +8758,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", - "http 0.2.12", - "hyper 0.14.32", + "http 0.2.9", + "hyper 0.14.29", "log", - "rustls 0.21.12", + "rustls 0.21.7", "rustls-native-certs 0.6.3", "tokio", "tokio-rustls 0.24.1", @@ -8908,22 +8769,22 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.5" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", - "http 1.2.0", - "hyper 1.5.2", + "http 1.1.0", + "hyper 1.3.1", "hyper-util", "log", - "rustls 0.23.21", - "rustls-native-certs 0.8.1", + "rustls 0.23.18", + "rustls-native-certs 0.8.0", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.1", + "tokio-rustls 0.26.0", "tower-service", - "webpki-roots 0.26.7", + "webpki-roots 0.26.3", ] [[package]] @@ -8932,7 +8793,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.32", + "hyper 0.14.29", "pin-project-lite", "tokio", "tokio-io-timeout", @@ -8945,7 +8806,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper 0.14.32", + "hyper 0.14.29", "native-tls", "tokio", "tokio-native-tls", @@ -8953,35 +8814,36 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.2.0", - "http-body 1.0.1", - "hyper 1.5.2", + "http 1.1.0", + "http-body 1.0.0", + "hyper 1.3.1", "pin-project-lite", - "socket2 0.5.8", + "socket2 0.5.7", "tokio", + "tower", "tower-service", "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core 0.52.0", + "windows 0.48.0", ] [[package]] @@ -8994,181 +8856,58 @@ dependencies = [ ] [[package]] -name = "icu_collections" -version = "1.5.0" +name = "ident_case" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] -name = "icu_locid" -version = "1.5.0" +name = "idna" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", + "unicode-bidi", + "unicode-normalization", ] [[package]] -name = "icu_locid_transform" -version = "1.5.0" +name = "idna" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", + "unicode-bidi", + "unicode-normalization", ] [[package]] -name = "icu_locid_transform_data" -version = "1.5.0" +name = "if-addrs" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" +checksum = "cabb0019d51a643781ff15c9c8a3e5dedc365c47211270f4e8f82812fedd8f0a" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] [[package]] -name = "icu_normalizer" -version = "1.5.0" +name = "if-watch" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "d6b0422c86d7ce0e97169cc42e04ae643caf278874a7a3c87b8150a220dc7e1e" dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" - -[[package]] -name = "icu_properties" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" - -[[package]] -name = "icu_provider" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "if-addrs" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cabb0019d51a643781ff15c9c8a3e5dedc365c47211270f4e8f82812fedd8f0a" -dependencies = [ - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "if-watch" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf9d64cfcf380606e64f9a0bcf493616b65331199f984151a6fa11a7b3cde38" -dependencies = [ - "async-io 2.4.0", - "core-foundation 0.9.4", - "fnv", - "futures", - "if-addrs", - "ipnet", - "log", - "netlink-packet-core", - "netlink-packet-route", - "netlink-proto", - "netlink-sys", - "rtnetlink", - "system-configuration 0.6.1", - "tokio", - "windows 0.53.0", + "async-io 2.3.3", + "core-foundation", + "fnv", + "futures", + "if-addrs", + "ipnet", + "log", + "rtnetlink", + "system-configuration", + "tokio", + "windows 0.51.1", ] [[package]] @@ -9181,8 +8920,8 @@ dependencies = [ "attohttpc", "bytes", "futures", - "http 0.2.12", - "hyper 0.14.32", + "http 0.2.9", + "hyper 0.14.29", "log", "rand", "tokio", @@ -9268,32 +9007,32 @@ dependencies = [ [[package]] name = "impl-trait-for-tuples" -version = "0.2.3" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 1.0.109", ] [[package]] name = "include_dir" -version = "0.7.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" +checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e" dependencies = [ "include_dir_macros", ] [[package]] name = "include_dir_macros" -version = "0.7.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", ] [[package]] @@ -9332,15 +9071,15 @@ checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" [[package]] name = "indicatif" -version = "0.17.9" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf675b85ed934d3c67b5c5469701eec7db22689d0a2139d856e0925fa28b281" +checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" dependencies = [ "console", + "instant", "number_prefix", "portable-atomic", - "unicode-width 0.2.0", - "web-time", + "unicode-width", ] [[package]] @@ -9393,7 +9132,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2 0.5.8", + "socket2 0.5.7", "widestring", "windows-sys 0.48.0", "winreg", @@ -9405,7 +9144,7 @@ version = "0.21.3" source = "git+https://github.com/chevdor/subwasm?rev=v0.21.3#aa8acb6fdfb34144ac51ab95618a9b37fa251295" dependencies = [ "ipfs-unixfs", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -9424,36 +9163,30 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.10.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" [[package]] name = "is-terminal" -version = "0.4.13" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "hermit-abi 0.4.0", - "libc", - "windows-sys 0.52.0", + "hermit-abi 0.3.9", + "rustix 0.38.42", + "windows-sys 0.48.0", ] [[package]] name = "is_executable" -version = "1.0.4" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a1b5bad6f9072935961dfbf1cced2f3d129963d091b6f69f007fe04e758ae2" +checksum = "fa9acdc6d67b75e626ad644734e8bc6df893d9cd2a834129065d3dd6158ea9c8" dependencies = [ "winapi", ] -[[package]] -name = "is_terminal_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - [[package]] name = "isahc" version = "1.7.2" @@ -9468,7 +9201,7 @@ dependencies = [ "encoding_rs", "event-listener 2.5.3", "futures-lite 1.13.0", - "http 0.2.12", + "http 0.2.9", "log", "mime", "once_cell", @@ -9519,9 +9252,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "jemalloc_pprof" @@ -9550,7 +9283,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror 1.0.69", + "thiserror", "walkdir", ] @@ -9571,14 +9304,19 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ - "once_cell", "wasm-bindgen", ] +[[package]] +name = "json" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" + [[package]] name = "json-patch" version = "1.4.0" @@ -9587,7 +9325,7 @@ checksum = "ec9ad60d674508f3ca8f380a928cfe7b096bc729c4e2dbfe3852bc45da3ab30b" dependencies = [ "serde", "serde_json", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -9600,7 +9338,7 @@ dependencies = [ "pest_derive", "regex", "serde_json", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -9642,16 +9380,16 @@ dependencies = [ "futures-channel", "futures-util", "gloo-net", - "http 1.2.0", + "http 1.1.0", "jsonrpsee-core", "pin-project", - "rustls 0.23.21", + "rustls 0.23.18", "rustls-pki-types", "rustls-platform-verifier", - "soketto 0.8.1", - "thiserror 1.0.69", + "soketto 0.8.0", + "thiserror", "tokio", - "tokio-rustls 0.26.1", + "tokio-rustls 0.26.0", "tokio-util", "tracing", "url", @@ -9667,17 +9405,17 @@ dependencies = [ "bytes", "futures-timer", "futures-util", - "http 1.2.0", - "http-body 1.0.1", + "http 1.1.0", + "http-body 1.0.0", "http-body-util", "jsonrpsee-types", "parking_lot 0.12.3", "pin-project", "rand", - "rustc-hash 2.1.0", + "rustc-hash 2.0.0", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror", "tokio", "tokio-stream", "tracing", @@ -9692,19 +9430,19 @@ checksum = "b3638bc4617f96675973253b3a45006933bde93c2fd8a6170b33c777cc389e5b" dependencies = [ "async-trait", "base64 0.22.1", - "http-body 1.0.1", - "hyper 1.5.2", - "hyper-rustls 0.27.5", + "http-body 1.0.0", + "hyper 1.3.1", + "hyper-rustls 0.27.3", "hyper-util", "jsonrpsee-core", "jsonrpsee-types", - "rustls 0.23.21", + "rustls 0.23.18", "rustls-platform-verifier", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror", "tokio", - "tower 0.4.13", + "tower", "tracing", "url", ] @@ -9716,10 +9454,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06c01ae0007548e73412c08e2285ffe5d723195bf268bce67b1b77c3bb2a14d" dependencies = [ "heck 0.5.0", - "proc-macro-crate 3.2.0", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro-crate 3.1.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -9729,10 +9467,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82ad8ddc14be1d4290cd68046e7d1d37acd408efed6d3ca08aefcc3ad6da069c" dependencies = [ "futures-util", - "http 1.2.0", - "http-body 1.0.1", + "http 1.1.0", + "http-body 1.0.0", "http-body-util", - "hyper 1.5.2", + "hyper 1.3.1", "hyper-util", "jsonrpsee-core", "jsonrpsee-types", @@ -9740,12 +9478,12 @@ dependencies = [ "route-recognizer", "serde", "serde_json", - "soketto 0.8.1", - "thiserror 1.0.69", + "soketto 0.8.0", + "thiserror", "tokio", "tokio-stream", "tokio-util", - "tower 0.4.13", + "tower", "tracing", ] @@ -9755,10 +9493,10 @@ version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a178c60086f24cc35bb82f57c651d0d25d99c4742b4d335de04e97fa1f08a8a1" dependencies = [ - "http 1.2.0", + "http 1.1.0", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -9778,7 +9516,7 @@ version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fe322e0896d0955a3ebdd5bf813571c53fea29edd713bc315b76620b327e86d" dependencies = [ - "http 1.2.0", + "http 1.1.0", "jsonrpsee-client-transport", "jsonrpsee-core", "jsonrpsee-types", @@ -9815,9 +9553,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.5" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" dependencies = [ "cpufeatures", ] @@ -9901,9 +9639,9 @@ dependencies = [ "either", "futures", "home", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.32", + "http 0.2.9", + "http-body 0.4.5", + "hyper 0.14.29", "hyper-rustls 0.24.2", "hyper-timeout", "jsonpath-rust", @@ -9912,17 +9650,17 @@ dependencies = [ "pem 3.0.4", "pin-project", "rand", - "rustls 0.21.12", - "rustls-pemfile 1.0.4", + "rustls 0.21.7", + "rustls-pemfile 1.0.3", "secrecy 0.8.0", "serde", "serde_json", "serde_yaml", - "thiserror 1.0.69", + "thiserror", "tokio", "tokio-tungstenite", "tokio-util", - "tower 0.4.13", + "tower", "tower-http 0.4.4", "tracing", ] @@ -9935,13 +9673,13 @@ checksum = "b5bba93d054786eba7994d03ce522f368ef7d48c88a1826faa28478d85fb63ae" dependencies = [ "chrono", "form_urlencoded", - "http 0.2.12", + "http 0.2.9", "json-patch", "k8s-openapi", "once_cell", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -9964,7 +9702,7 @@ dependencies = [ "serde", "serde_json", "smallvec", - "thiserror 1.0.69", + "thiserror", "tokio", "tokio-util", "tracing", @@ -10023,13 +9761,13 @@ dependencies = [ [[package]] name = "landlock" -version = "0.3.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9baa9eeb6e315942429397e617a190f4fdc696ef1ee0342939d641029cbb4ea7" +checksum = "1530c5b973eeed4ac216af7e24baf5737645a6272e361f1fb95710678b67d9cc" dependencies = [ "enumflags2", "libc", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -10081,35 +9819,36 @@ dependencies = [ [[package]] name = "libfuzzer-sys" -version = "0.4.8" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b9569d2f74e257076d8c6bfa73fb505b46b851e51ddaecc825944aa3bed17fa" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" dependencies = [ "arbitrary", "cc", + "once_cell", ] [[package]] name = "libloading" -version = "0.8.6" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "winapi", ] [[package]] name = "libm" -version = "0.2.11" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libnghttp2-sys" -version = "0.1.11+1.64.0" +version = "0.1.9+1.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b6c24e48a7167cffa7119da39d577fa482e66c688a4aac016bee862e1a713c4" +checksum = "b57e858af2798e167e709b9d969325b6d8e9d50232fcbc494d7d54f976854a64" dependencies = [ "cc", "libc", @@ -10144,10 +9883,10 @@ dependencies = [ "libp2p-upnp", "libp2p-websocket", "libp2p-yamux", - "multiaddr 0.18.2", + "multiaddr 0.18.1", "pin-project", "rw-stream-sink", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -10185,8 +9924,8 @@ dependencies = [ "futures", "futures-timer", "libp2p-identity", - "multiaddr 0.18.2", - "multihash 0.19.3", + "multiaddr 0.18.1", + "multihash 0.19.1", "multistream-select", "once_cell", "parking_lot 0.12.3", @@ -10195,7 +9934,7 @@ dependencies = [ "rand", "rw-stream-sink", "smallvec", - "thiserror 1.0.69", + "thiserror", "tracing", "unsigned-varint 0.8.0", "void", @@ -10232,29 +9971,29 @@ dependencies = [ "libp2p-core", "libp2p-identity", "libp2p-swarm", - "lru 0.12.5", + "lru 0.12.3", "quick-protobuf 0.8.1", "quick-protobuf-codec", "smallvec", - "thiserror 1.0.69", + "thiserror", "tracing", "void", ] [[package]] name = "libp2p-identity" -version = "0.2.10" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "257b5621d159b32282eac446bed6670c39c7dc68a200a992d8f056afa0066f6d" +checksum = "55cca1eb2bc1fd29f099f3daaab7effd01e1a54b7c577d0ed082521034d912e8" dependencies = [ "bs58", "ed25519-dalek", "hkdf", - "multihash 0.19.3", + "multihash 0.19.1", "quick-protobuf 0.8.1", "rand", "sha2 0.10.8", - "thiserror 1.0.69", + "thiserror", "tracing", "zeroize", ] @@ -10265,7 +10004,7 @@ version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced237d0bd84bbebb7c2cad4c073160dacb4fe40534963c32ed6d4c6bb7702a3" dependencies = [ - "arrayvec 0.7.6", + "arrayvec 0.7.4", "asynchronous-codec 0.7.0", "bytes", "either", @@ -10281,7 +10020,7 @@ dependencies = [ "rand", "sha2 0.10.8", "smallvec", - "thiserror 1.0.69", + "thiserror", "tracing", "uint 0.9.5", "void", @@ -10303,7 +10042,7 @@ dependencies = [ "libp2p-swarm", "rand", "smallvec", - "socket2 0.5.8", + "socket2 0.5.7", "tokio", "tracing", "void", @@ -10339,15 +10078,15 @@ dependencies = [ "futures", "libp2p-core", "libp2p-identity", - "multiaddr 0.18.2", - "multihash 0.19.3", + "multiaddr 0.18.1", + "multihash 0.19.1", "once_cell", "quick-protobuf 0.8.1", "rand", "sha2 0.10.8", "snow", "static_assertions", - "thiserror 1.0.69", + "thiserror", "tracing", "x25519-dalek", "zeroize", @@ -10388,9 +10127,9 @@ dependencies = [ "quinn", "rand", "ring 0.17.8", - "rustls 0.23.21", - "socket2 0.5.8", - "thiserror 1.0.69", + "rustls 0.23.18", + "socket2 0.5.7", + "thiserror", "tokio", "tracing", ] @@ -10428,7 +10167,7 @@ dependencies = [ "libp2p-core", "libp2p-identity", "libp2p-swarm-derive", - "lru 0.12.5", + "lru 0.12.3", "multistream-select", "once_cell", "rand", @@ -10446,9 +10185,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "206e0aa0ebe004d778d79fb0966aa0de996c19894e2c0605ba2f8524dd4443d8" dependencies = [ "heck 0.5.0", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -10463,7 +10202,7 @@ dependencies = [ "libc", "libp2p-core", "libp2p-identity", - "socket2 0.5.8", + "socket2 0.5.7", "tokio", "tracing", ] @@ -10480,9 +10219,9 @@ dependencies = [ "libp2p-identity", "rcgen 0.11.3", "ring 0.17.8", - "rustls 0.23.21", - "rustls-webpki 0.101.7", - "thiserror 1.0.69", + "rustls 0.23.18", + "rustls-webpki 0.101.4", + "thiserror", "x509-parser", "yasna", ] @@ -10517,11 +10256,11 @@ dependencies = [ "parking_lot 0.12.3", "pin-project-lite", "rw-stream-sink", - "soketto 0.8.1", - "thiserror 1.0.69", + "soketto 0.8.0", + "thiserror", "tracing", "url", - "webpki-roots 0.25.4", + "webpki-roots 0.25.2", ] [[package]] @@ -10533,21 +10272,10 @@ dependencies = [ "either", "futures", "libp2p-core", - "thiserror 1.0.69", + "thiserror", "tracing", "yamux 0.12.1", - "yamux 0.13.4", -] - -[[package]] -name = "libredox" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags 2.8.0", - "libc", - "redox_syscall 0.5.8", + "yamux 0.13.3", ] [[package]] @@ -10592,7 +10320,7 @@ checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" dependencies = [ "crunchy", "digest 0.9.0", - "subtle 2.6.1", + "subtle 2.5.0", ] [[package]] @@ -10626,9 +10354,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.21" +version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9b68e50e6e0b26f672573834882eb57759f6db9b3be2ea3c35c91188bb4eaa" +checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" dependencies = [ "cc", "libc", @@ -10653,18 +10381,18 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linked_hash_set" -version = "0.1.5" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae85b5be22d9843c80e5fc80e9b64c8a3b1f98f867c709956eca3efff4e92e2" +checksum = "47186c6da4d81ca383c7c47c1bfc80f4b95f4720514d860a5407aaf4233f9588" dependencies = [ "linked-hash-map", ] [[package]] name = "linregress" -version = "0.5.4" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9eda9dcf4f2a99787827661f312ac3219292549c2ee992bf9a6248ffb066bf7" +checksum = "4de0b5f52a9f84544d268f5fabb71b38962d6aa3c6600b8bcd27d44ccf9c9c45" dependencies = [ "nalgebra", ] @@ -10683,9 +10411,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.15" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lioness" @@ -10717,12 +10445,6 @@ dependencies = [ "paste", ] -[[package]] -name = "litemap" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" - [[package]] name = "litep2p" version = "0.8.4" @@ -10740,7 +10462,7 @@ dependencies = [ "hickory-resolver", "indexmap 2.7.0", "libc", - "mockall 0.13.1", + "mockall 0.13.0", "multiaddr 0.17.1", "multihash 0.17.0", "network-interface", @@ -10758,9 +10480,9 @@ dependencies = [ "simple-dns", "smallvec", "snow", - "socket2 0.5.8", + "socket2 0.5.7", "static_assertions", - "thiserror 1.0.69", + "thiserror", "tokio", "tokio-stream", "tokio-tungstenite", @@ -10783,9 +10505,9 @@ checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", @@ -10793,9 +10515,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.25" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" dependencies = [ "serde", "value-bag", @@ -10812,17 +10534,17 @@ dependencies = [ [[package]] name = "lru" -version = "0.11.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a83fb7698b3643a0e34f9ae6f2e8f0178c0fd42f8b59d493aa271ff3a5bf21" +checksum = "eedb2bdbad7e0634f83989bf596f497b070130daaa398ab22d84c39e266deec5" [[package]] name = "lru" -version = "0.12.5" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" dependencies = [ - "hashbrown 0.15.2", + "hashbrown 0.14.5", ] [[package]] @@ -10836,18 +10558,19 @@ dependencies = [ [[package]] name = "lz4" -version = "1.28.1" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20b523e860d03443e98350ceaac5e71c6ba89aea7d960769ec3ce37f4de5af4" +checksum = "7e9e2dd86df36ce760a60f6ff6ad526f7ba1f14ba0356f8254fb6905e6494df1" dependencies = [ + "libc", "lz4-sys", ] [[package]] name = "lz4-sys" -version = "1.11.1+lz4-1.10.0" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" +checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900" dependencies = [ "cc", "libc", @@ -10862,6 +10585,15 @@ dependencies = [ "libc", ] +[[package]] +name = "mach2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +dependencies = [ + "libc", +] + [[package]] name = "macro_magic" version = "0.5.1" @@ -10870,8 +10602,8 @@ checksum = "cc33f9f0351468d26fbc53d9ce00a096c8522ecb42f19b50f34f2c422f76d21d" dependencies = [ "macro_magic_core", "macro_magic_macros", - "quote 1.0.38", - "syn 2.0.96", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -10883,9 +10615,9 @@ dependencies = [ "const-random", "derive-syn-parse", "macro_magic_core_macros", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -10894,9 +10626,9 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -10906,8 +10638,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73ea28ee64b88876bf45277ed9a5817c1817df061a74f2b988971a12570e5869" dependencies = [ "macro_magic_core", - "quote 1.0.38", - "syn 2.0.96", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -10935,6 +10667,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" +[[package]] +name = "matchers" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "matchers" version = "0.1.0" @@ -10946,9 +10687,9 @@ dependencies = [ [[package]] name = "matrixmultiply" -version = "0.3.9" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" +checksum = "090126dc04f95dc0d1c1c91f61bdd474b3930ca064c1edc8a849da2c6cbe1e77" dependencies = [ "autocfg", "rawpointer", @@ -10972,11 +10713,11 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memfd" -version = "0.6.4" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" +checksum = "ffc89ccdc6e10d6907450f753537ebc5c5d3460d2e4e62ea74bd571db62c0f9e" dependencies = [ - "rustix 0.38.43", + "rustix 0.37.23", ] [[package]] @@ -10990,9 +10731,9 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.9.5" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +checksum = "45fd3a57831bf88bc63f8cebc0cf956116276e97fef3966103e96416209f7c92" dependencies = [ "libc", ] @@ -11006,6 +10747,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + [[package]] name = "memory-db" version = "0.32.0" @@ -11065,16 +10815,6 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "minicov" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" -dependencies = [ - "cc", - "walkdir", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -11085,7 +10825,7 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" name = "minimal-template-node" version = "0.0.0" dependencies = [ - "clap 4.5.26", + "clap 4.5.13", "docify", "futures", "futures-timer", @@ -11108,28 +10848,20 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.7.4" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", ] -[[package]] -name = "miniz_oxide" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" -dependencies = [ - "adler2", -] - [[package]] name = "mio" -version = "1.0.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ + "hermit-abi 0.3.9", "libc", "wasi", "windows-sys 0.52.0", @@ -11142,7 +10874,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daa3eb39495d8e2e2947a1d862852c90cc6a4a8845f8b41c8829cb9fcc047f4a" dependencies = [ "arrayref", - "arrayvec 0.7.6", + "arrayvec 0.7.4", "bitflags 1.3.2", "blake2 0.10.6", "c2-chacha", @@ -11155,8 +10887,8 @@ dependencies = [ "rand", "rand_chacha", "rand_distr", - "subtle 2.6.1", - "thiserror 1.0.69", + "subtle 2.5.0", + "thiserror", "zeroize", ] @@ -11215,15 +10947,15 @@ dependencies = [ [[package]] name = "mockall" -version = "0.13.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39a6bfcc6c8c7eed5ee98b9c3e33adc726054389233e201c95dab2d41a3839d2" +checksum = "d4c28b3fb6d753d28c20e826cd46ee611fda1cf3cde03a443a974043247c065a" dependencies = [ "cfg-if", "downcast", "fragile", - "mockall_derive 0.13.1", - "predicates 3.1.3", + "mockall_derive 0.13.0", + "predicates 3.0.3", "predicates-tree", ] @@ -11234,21 +10966,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ce75669015c4f47b289fd4d4f56e894e4c96003ffdf3ac51313126f94c6cbb" dependencies = [ "cfg-if", - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "syn 1.0.109", ] [[package]] name = "mockall_derive" -version = "0.13.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ca3004c2efe9011bd4e461bd8256445052b9615405b4f7ea43fc8ca5c20898" +checksum = "341014e7f530314e9a1fdbc7400b244efea7122662c96bfa248c31da5bfb2020" dependencies = [ "cfg-if", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -11278,20 +11010,20 @@ dependencies = [ [[package]] name = "multiaddr" -version = "0.18.2" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe6351f60b488e04c1d21bc69e56b89cb3f5e8f5d22557d6e8031bdfd79b6961" +checksum = "8b852bc02a2da5feed68cd14fa50d0774b92790a5bdbfa932a813926c8472070" dependencies = [ "arrayref", "byteorder", "data-encoding", "libp2p-identity", "multibase 0.9.1", - "multihash 0.19.3", + "multihash 0.19.1", "percent-encoding", "serde", "static_assertions", - "unsigned-varint 0.8.0", + "unsigned-varint 0.7.2", "url", ] @@ -11339,7 +11071,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835d6ff01d610179fbce3de1694d007e500bf33a7f29689838941d6bf783ae40" dependencies = [ "blake2b_simd 1.0.2", - "blake2s_simd 1.0.2", + "blake2s_simd 1.0.1", "blake3", "core2", "digest 0.10.7", @@ -11356,7 +11088,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfd8a792c1694c6da4f68db0a9d707c72bd260994da179e6030a5dcee00bb815" dependencies = [ "blake2b_simd 1.0.2", - "blake2s_simd 1.0.2", + "blake2s_simd 1.0.1", "blake3", "core2", "digest 0.10.7", @@ -11368,33 +11100,33 @@ dependencies = [ [[package]] name = "multihash" -version = "0.19.3" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" +checksum = "076d548d76a0e2a0d4ab471d0b1c36c577786dfc4471242035d97a12a735c492" dependencies = [ "core2", - "unsigned-varint 0.8.0", + "unsigned-varint 0.7.2", ] [[package]] name = "multihash-derive" -version = "0.8.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6d4752e6230d8ef7adf7bd5d8c4b1f6561c1014c5ba9a37445ccefe18aa1db" +checksum = "fc076939022111618a5026d3be019fd8b366e76314538ff9a1b59ffbcbf98bcd" dependencies = [ - "proc-macro-crate 1.1.3", + "proc-macro-crate 1.3.1", "proc-macro-error", - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "syn 1.0.109", "synstructure 0.12.6", ] [[package]] name = "multimap" -version = "0.10.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "multistream-select" @@ -11412,12 +11144,13 @@ dependencies = [ [[package]] name = "nalgebra" -version = "0.33.2" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26aecdf64b707efd1310e3544d709c5c0ac61c13756046aaaba41be5c4f66a3b" +checksum = "307ed9b18cc2423f29e83f84fd23a8e73628727990181f18641a8b5dc2ab1caa" dependencies = [ "approx", "matrixmultiply", + "nalgebra-macros", "num-complex", "num-rational", "num-traits", @@ -11425,12 +11158,24 @@ dependencies = [ "typenum", ] +[[package]] +name = "nalgebra-macros" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91761aed67d03ad966ef783ae962ef9bbaca728d2dd7ceb7939ec110fffad998" +dependencies = [ + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 1.0.109", +] + [[package]] name = "names" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bddcd3bf5144b6392de80e04c347cd7fab2508f6df16a85fc496ecd5cec39bc" dependencies = [ + "clap 3.2.25", "rand", ] @@ -11452,27 +11197,28 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework 2.11.1", + "security-framework", "security-framework-sys", "tempfile", ] [[package]] name = "netlink-packet-core" -version = "0.7.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" +checksum = "345b8ab5bd4e71a2986663e88c56856699d060e78e152e6e9d7966fcd5491297" dependencies = [ "anyhow", "byteorder", + "libc", "netlink-packet-utils", ] [[package]] name = "netlink-packet-route" -version = "0.17.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" +checksum = "d9ea4302b9759a7a88242299225ea3688e63c85ea136371bb6cf94fd674efaab" dependencies = [ "anyhow", "bitflags 1.3.2", @@ -11491,29 +11237,29 @@ dependencies = [ "anyhow", "byteorder", "paste", - "thiserror 1.0.69", + "thiserror", ] [[package]] name = "netlink-proto" -version = "0.11.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b33524dc0968bfad349684447bfce6db937a9ac3332a1fe60c0c5a5ce63f21" +checksum = "65b4b14489ab424703c092062176d52ba55485a89c076b4f9db05092b7223aa6" dependencies = [ "bytes", "futures", "log", "netlink-packet-core", "netlink-sys", - "thiserror 1.0.69", + "thiserror", "tokio", ] [[package]] name = "netlink-sys" -version = "0.8.7" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16c903aa70590cb93691bf97a767c8d1d6122d2cc9070433deb3bbf36ce8bd23" +checksum = "6471bf08e7ac0135876a9581bf3217ef0333c191c128d34878079f42ee150411" dependencies = [ "bytes", "futures", @@ -11524,21 +11270,21 @@ dependencies = [ [[package]] name = "network-interface" -version = "1.1.4" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a43439bf756eed340bdf8feba761e2d50c7d47175d87545cd5cbe4a137c4d1" +checksum = "ae72fd9dbd7f55dda80c00d66acc3b2130436fcba9ea89118fc508eaae48dfb0" dependencies = [ "cc", "libc", - "thiserror 1.0.69", + "thiserror", "winapi", ] [[package]] name = "nix" -version = "0.26.4" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" dependencies = [ "bitflags 1.3.2", "cfg-if", @@ -11547,11 +11293,11 @@ dependencies = [ [[package]] name = "nix" -version = "0.27.1" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ - "bitflags 2.8.0", + "bitflags 1.3.2", "cfg-if", "libc", ] @@ -11562,7 +11308,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.6.0", "cfg-if", "cfg_aliases 0.2.1", "libc", @@ -11586,8 +11332,8 @@ version = "0.9.0-dev" dependencies = [ "array-bytes", "async-trait", - "clap 4.5.26", - "derive_more 0.99.18", + "clap 4.5.13", + "derive_more 0.99.17", "fs_extra", "futures", "hash-db", @@ -11662,7 +11408,7 @@ dependencies = [ name = "node-runtime-generate-bags" version = "3.0.0" dependencies = [ - "clap 4.5.26", + "clap 4.5.13", "generate-bags", "kitchensink-runtime", ] @@ -11671,7 +11417,7 @@ dependencies = [ name = "node-template-release" version = "3.0.0" dependencies = [ - "clap 4.5.26", + "clap 4.5.13", "flate2", "fs_extra", "glob", @@ -11782,9 +11528,9 @@ dependencies = [ [[package]] name = "num" -version = "0.4.3" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" dependencies = [ "num-bigint", "num-complex", @@ -11796,10 +11542,11 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.6" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ + "autocfg", "num-integer", "num-traits", ] @@ -11823,9 +11570,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.6" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" dependencies = [ "num-traits", ] @@ -11842,9 +11589,9 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -11853,24 +11600,25 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" dependencies = [ - "arrayvec 0.7.6", + "arrayvec 0.7.4", "itoa", ] [[package]] name = "num-integer" -version = "0.1.46" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ + "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.45" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" dependencies = [ "autocfg", "num-integer", @@ -11879,10 +11627,11 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ + "autocfg", "num-bigint", "num-integer", "num-traits", @@ -11946,33 +11695,33 @@ dependencies = [ [[package]] name = "object" -version = "0.36.7" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" dependencies = [ "memchr", ] [[package]] name = "oid-registry" -version = "0.7.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" +checksum = "1c958dd45046245b9c3c2547369bb634eb461670b2e7e0de552905801a648d1d" dependencies = [ "asn1-rs", ] [[package]] name = "once_cell" -version = "1.20.2" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oorandom" -version = "11.1.4" +version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "opaque-debug" @@ -11982,17 +11731,17 @@ checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" [[package]] name = "opaque-debug" -version = "0.3.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.68" +version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.6.0", "cfg-if", "foreign-types", "libc", @@ -12007,9 +11756,9 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -12020,9 +11769,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.104" +version = "0.9.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", @@ -12049,7 +11798,7 @@ dependencies = [ "orchestra-proc-macro", "pin-project", "prioritized-metered-channel", - "thiserror 1.0.69", + "thiserror", "tracing", ] @@ -12063,9 +11812,9 @@ dependencies = [ "indexmap 2.7.0", "itertools 0.11.0", "petgraph", - "proc-macro-crate 3.2.0", - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro-crate 3.1.0", + "proc-macro2 1.0.86", + "quote 1.0.37", "syn 1.0.109", ] @@ -12088,6 +11837,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "os_str_bytes" +version = "6.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" + [[package]] name = "overload" version = "0.1.1" @@ -12127,7 +11882,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59378a648a0aa279a4b10650366c3389cd0a1239b1876f74bfecd268eecb086b" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-collective 38.0.0", @@ -12137,7 +11892,7 @@ dependencies = [ "sp-core 34.0.0", "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -12167,7 +11922,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33f0078659ae95efe6a1bf138ab5250bc41ab98f22ff3651d0208684f08ae797" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", @@ -12176,7 +11931,7 @@ dependencies = [ "sp-arithmetic 26.0.0", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -12206,7 +11961,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3edbeda834bcd6660f311d4eead3dabdf6d385b7308ac75b0fae941a960e6c3a" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-asset-conversion 20.0.0", @@ -12215,7 +11970,7 @@ dependencies = [ "sp-arithmetic 26.0.0", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -12243,13 +11998,13 @@ version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ab66c4c22ac0f20e620a954ce7ba050118d6d8011e2d02df599309502064e98" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "pallet-asset-conversion 20.0.0", - "pallet-transaction-payment 38.0.2", + "pallet-transaction-payment 38.0.0", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -12274,12 +12029,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71b2149aa741bc39466bbcc92d9d0ab6e9adcf39d2790443a735ad573b3191e7" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "scale-info", "sp-core 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -12310,15 +12065,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "406a486466d15acc48c99420191f96f1af018f3381fde829c467aba489030f18" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", - "pallet-transaction-payment 38.0.2", + "pallet-transaction-payment 38.0.0", "parity-scale-codec", "scale-info", "serde", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -12345,14 +12100,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f45f4eb6027fc34c4650e0ed6a7e57ed3335cc364be74b4531f714237676bcee" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "impl-trait-for-tuples", "log", "parity-scale-codec", "scale-info", "sp-core 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -12379,13 +12134,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "127adc2250b89416b940850ce2175dab10a9297b503b1fcb05dc555bd9bd3207" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-assets 40.0.0", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -12404,13 +12159,13 @@ version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15906a685adeabe6027e49c814a34066222dd6136187a8a79c213d0d739b6634" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -12436,7 +12191,7 @@ version = "37.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b31da6e794d655d1f9c4da6557a57399538d75905a7862a2ed3f7e5fb711d7e4" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-timestamp 37.0.0", @@ -12444,7 +12199,7 @@ dependencies = [ "scale-info", "sp-application-crypto 38.0.0", "sp-consensus-aura 0.40.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -12469,14 +12224,14 @@ version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffb0208f0538d58dcb78ce1ff5e6e8641c5f37b23b20b05587e51da30ab13541" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "pallet-session 38.0.0", "parity-scale-codec", "scale-info", "sp-application-crypto 38.0.0", "sp-authority-discovery 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -12499,12 +12254,12 @@ version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625d47577cabbe1318ccec5d612e2379002d1b6af1ab6edcef3243c66ec246df" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "impl-trait-for-tuples", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -12541,7 +12296,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee096c0def13832475b340d00121025e0225de29604d44bc6dfcaa294c995b4" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-authorship 38.0.0", @@ -12553,7 +12308,7 @@ dependencies = [ "sp-consensus-babe 0.40.0", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-session 36.0.0", "sp-staking 36.0.0", ] @@ -12588,7 +12343,7 @@ dependencies = [ "docify", "frame-benchmarking 38.0.0", "frame-election-provider-support 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-balances 39.0.0", @@ -12596,7 +12351,7 @@ dependencies = [ "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-tracing 17.0.1", ] @@ -12653,12 +12408,12 @@ checksum = "5c6945b078919acb14d126490e4b0973a688568b30142476ca69c6df2bed27ad" dependencies = [ "docify", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -12694,7 +12449,7 @@ version = "39.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "014d177a3aba19ac144fc6b2b5eb94930b9874734b91fd014902b6706288bb5f" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-authorship 38.0.0", @@ -12703,7 +12458,7 @@ dependencies = [ "scale-info", "serde", "sp-consensus-beefy 22.1.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-session 36.0.0", "sp-staking 36.0.0", ] @@ -12742,7 +12497,7 @@ dependencies = [ "array-bytes", "binary-merkle-tree 15.0.1", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-beefy 39.0.0", @@ -12755,7 +12510,7 @@ dependencies = [ "sp-consensus-beefy 22.1.0", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-state-machine 0.43.0", ] @@ -12783,7 +12538,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1163f9cd8bbc47ec0c6900a3ca67689d8d7b40bedfa6aa22b1b3c6027b1090e" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-treasury 37.0.0", @@ -12791,7 +12546,7 @@ dependencies = [ "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -12848,13 +12603,13 @@ dependencies = [ "bp-runtime 0.18.0", "bp-test-utils 0.18.0", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", "scale-info", "sp-consensus-grandpa 21.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -12891,12 +12646,12 @@ dependencies = [ "bp-messages 0.18.0", "bp-runtime 0.18.0", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-trie 37.0.0", ] @@ -12934,13 +12689,13 @@ dependencies = [ "bp-polkadot-core 0.18.0", "bp-runtime 0.18.0", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-bridge-grandpa 0.18.0", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -12976,26 +12731,26 @@ dependencies = [ [[package]] name = "pallet-bridge-relayers" -version = "0.18.2" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fe3be7077b7ddee7178b1b12e9171435da73778d093788e10b1bdfad1e10962" +checksum = "2faead05455a965a0a0ec69ffa779933479b599e40bda809c0aa1efa72a39281" dependencies = [ "bp-header-chain 0.18.1", "bp-messages 0.18.0", "bp-relayers 0.18.0", "bp-runtime 0.18.0", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-bridge-grandpa 0.18.0", "pallet-bridge-messages 0.18.0", "pallet-bridge-parachains 0.18.0", - "pallet-transaction-payment 38.0.2", + "pallet-transaction-payment 38.0.0", "parity-scale-codec", "scale-info", "sp-arithmetic 26.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -13021,13 +12776,13 @@ dependencies = [ [[package]] name = "pallet-broker" -version = "0.17.2" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "018b477d7d464c451b1d09a4ce9e792c3c65b15fd764b23da38ff9980e786065" +checksum = "3043c90106d88cb93fcf0d9b6d19418f11f44cc2b11873414aec3b46044a24ea" dependencies = [ "bitvec", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", @@ -13035,7 +12790,7 @@ dependencies = [ "sp-api 34.0.0", "sp-arithmetic 26.0.0", "sp-core 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -13063,7 +12818,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7f3bc38ae6584b5f57e4de3e49e5184bfc0f20692829530ae1465ffe04e09e7" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-bounties 37.0.0", @@ -13072,7 +12827,7 @@ dependencies = [ "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -13106,7 +12861,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "658798d70c9054165169f6a6a96cfa9d6a5e7d24a524bc19825bf17fcbc5cc5a" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-authorship 38.0.0", @@ -13115,7 +12870,7 @@ dependencies = [ "parity-scale-codec", "rand", "scale-info", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-staking 36.0.0", ] @@ -13143,14 +12898,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e149f1aefd444c9a1da6ec5a94bc8a7671d7a33078f85dd19ae5b06e3438e60" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -13174,12 +12929,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38a6a5cbe781d9c711be74855ba32ef138f3779d6c54240c08e6d1b4bbba4d1d" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "scale-info", "sp-core 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -13235,13 +12990,13 @@ dependencies = [ "bitflags 1.3.2", "environmental", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "impl-trait-for-tuples", "log", "pallet-balances 39.0.0", - "pallet-contracts-proc-macro 23.0.2", - "pallet-contracts-uapi 12.0.1", + "pallet-contracts-proc-macro 23.0.1", + "pallet-contracts-uapi 12.0.0", "parity-scale-codec", "paste", "rand", @@ -13251,10 +13006,10 @@ dependencies = [ "sp-api 34.0.0", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "staging-xcm 14.2.0", - "staging-xcm-builder 17.0.3", + "staging-xcm-builder 17.0.1", "wasm-instrument", "wasmi 0.32.3", ] @@ -13315,19 +13070,19 @@ version = "14.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "309666537ed001c61a99f59fa7b98680f4a6e4e361ed3bc64f7b0237da3e3e06" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "pallet-assets 40.0.0", "pallet-balances 39.0.0", "pallet-contracts 38.0.0", - "pallet-contracts-proc-macro 23.0.2", - "pallet-contracts-uapi 12.0.1", + "pallet-contracts-proc-macro 23.0.1", + "pallet-contracts-uapi 12.0.0", "pallet-insecure-randomness-collective-flip 26.0.0", - "pallet-message-queue 41.0.2", + "pallet-message-queue 41.0.1", "pallet-proxy 38.0.0", "pallet-timestamp 37.0.0", "pallet-utility 38.0.0", - "pallet-xcm 17.0.1", + "pallet-xcm 17.0.0", "parity-scale-codec", "polkadot-parachain-primitives 14.0.0", "polkadot-primitives 16.0.0", @@ -13337,10 +13092,10 @@ dependencies = [ "sp-core 34.0.0", "sp-io 38.0.0", "sp-keystore 0.40.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-tracing 17.0.1", "staging-xcm 14.2.0", - "staging-xcm-builder 17.0.3", + "staging-xcm-builder 17.0.1", "staging-xcm-executor 17.0.0", "xcm-simulator 17.0.0", ] @@ -13349,20 +13104,20 @@ dependencies = [ name = "pallet-contracts-proc-macro" version = "18.0.0" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "pallet-contracts-proc-macro" -version = "23.0.2" +version = "23.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3170e2f4a3d95f2ace274b703a72630294f0a27c687a4adbad9590e2b3e5fe82" +checksum = "94226cbd48516b7c310eb5dae8d50798c1ce73a7421dc0977c55b7fc2237a283" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -13377,13 +13132,14 @@ dependencies = [ [[package]] name = "pallet-contracts-uapi" -version = "12.0.1" +version = "12.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3e13d72cda1a30083a1c080acc56fc5f286d09c89d9d91e8e4942a230c58c8" +checksum = "16f74b000590c33fadea48585d3ae3f4b7867e99f0a524c444d5779f36b9a1b6" dependencies = [ "bitflags 1.3.2", "parity-scale-codec", "paste", + "polkavm-derive 0.9.1", "scale-info", ] @@ -13413,13 +13169,13 @@ checksum = "999c242491b74395b8c5409ef644e782fe426d87ae36ad92240ffbf21ff0a76e" dependencies = [ "assert_matches", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "scale-info", "serde", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -13441,21 +13197,21 @@ dependencies = [ [[package]] name = "pallet-core-fellowship" -version = "22.2.0" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93052dd8d5910e1b939441541cec416e629b2c0ab92680124c2e5a137e12c285" +checksum = "d063b41df454bd128d6fefd5800af8a71ac383c9dd6f20096832537efc110a8a" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", - "pallet-ranked-collective 38.2.0", + "pallet-ranked-collective 38.0.0", "parity-scale-codec", "scale-info", "sp-arithmetic 26.0.0", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -13500,13 +13256,13 @@ version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117f003a97f980514c6db25a50c22aaec2a9ccb5664b3cb32f52fb990e0b0c12" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-staking 36.0.0", ] @@ -13536,7 +13292,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6d1dc655f50b7c65bb2fb14086608ba11af02ef2936546f7a67db980ec1f133" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", @@ -13544,7 +13300,7 @@ dependencies = [ "serde", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -13568,14 +13324,14 @@ version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae1d8050c09c5e003d502c1addc7fdfbde21a854bd57787e94447078032710c8" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-balances 39.0.0", "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -13636,7 +13392,7 @@ checksum = "62f9ad5ae0c13ba3727183dadf1825b6b7b0b0598ed5c366f8697e13fd540f7d" dependencies = [ "frame-benchmarking 38.0.0", "frame-election-provider-support 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-election-provider-support-benchmarking 37.0.0", @@ -13647,7 +13403,7 @@ dependencies = [ "sp-core 34.0.0", "sp-io 38.0.0", "sp-npos-elections 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "strum 0.26.3", ] @@ -13674,7 +13430,7 @@ dependencies = [ "frame-system 38.0.0", "parity-scale-codec", "sp-npos-elections 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -13704,7 +13460,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "705c66d6c231340c6d085a0df0319a6ce42a150f248171e88e389ab1e3ce20f5" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", @@ -13712,7 +13468,7 @@ dependencies = [ "sp-core 34.0.0", "sp-io 38.0.0", "sp-npos-elections 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-staking 36.0.0", ] @@ -13902,13 +13658,13 @@ dependencies = [ "docify", "frame-benchmarking 38.0.0", "frame-election-provider-support 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-staking 36.0.0", ] @@ -13938,7 +13694,7 @@ checksum = "a1c79ab340890f6ab088a638c350ac1173a1b2a79c18004787523032025582b4" dependencies = [ "blake2 0.10.6", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", @@ -13946,7 +13702,7 @@ dependencies = [ "sp-core 34.0.0", "sp-inherents 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -13985,7 +13741,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d3a570a4aac3173ea46b600408183ca2bcfdaadc077f802f11e6055963e2449" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-authorship 38.0.0", @@ -13996,7 +13752,7 @@ dependencies = [ "sp-consensus-grandpa 21.0.0", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-session 36.0.0", "sp-staking 36.0.0", ] @@ -14027,13 +13783,13 @@ checksum = "e3a4288548de9a755e39fcb82ffb9024b6bb1ba0f582464a44423038dd7a892e" dependencies = [ "enumflags2", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -14062,7 +13818,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6fd95270cf029d16cb40fe6bd9f8ab9c78cd966666dccbca4d8bfec35c5bba5" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-authorship 38.0.0", @@ -14071,7 +13827,7 @@ dependencies = [ "sp-application-crypto 38.0.0", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-staking 36.0.0", ] @@ -14098,14 +13854,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5e4b97de630427a39d50c01c9e81ab8f029a00e56321823958b39b438f7b940" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", "sp-keyring 39.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -14128,12 +13884,12 @@ version = "26.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dce7ad80675d78bd38a7a66ecbbf2d218dd32955e97f8e301d0afe6c87b0f251" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "safe-mix", "scale-info", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -14159,11 +13915,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae0920ee53cf7b0665cfb6d275759ae0537dc3850ec78da5f118d814c99d3562" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -14188,14 +13944,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1868b5dca4bbfd1f4a222cbb80735a5197020712a71577b496bbb7e19aaa5394" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -14223,13 +13979,13 @@ dependencies = [ [[package]] name = "pallet-message-queue" -version = "41.0.2" +version = "41.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "983f7d1be18e9a089a3e23670918f5085705b4403acd3fdde31878d57b76a1a8" +checksum = "0faa48b29bf5a178580c164ef00de87319a37da7547a9cd6472dfd160092811a" dependencies = [ "environmental", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", @@ -14237,7 +13993,7 @@ dependencies = [ "sp-arithmetic 26.0.0", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-weights 31.0.0", ] @@ -14273,14 +14029,14 @@ checksum = "9b417fc975636bce94e7c6d707e42d0706d67dfa513e72f5946918e1044beef1" dependencies = [ "docify", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "impl-trait-for-tuples", "log", "parity-scale-codec", "scale-info", "sp-core 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -14312,7 +14068,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf3fa2b7f759a47f698a403ab40c54bc8935e2969387947224cbdb4e2bc8a28a" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", @@ -14322,7 +14078,7 @@ dependencies = [ "sp-arithmetic 26.0.0", "sp-io 38.0.0", "sp-mixnet 0.12.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -14351,7 +14107,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6932dfb85f77a57c2d1fdc28a7b3a59ffe23efd8d5bb02dc3039d91347e4a3b" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", @@ -14359,7 +14115,7 @@ dependencies = [ "sp-core 34.0.0", "sp-io 38.0.0", "sp-mmr-primitives 34.1.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -14380,13 +14136,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e5099c9a4442efcc1568d88ca1d22d624e81ab96358f99f616c67fbd82532d2" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -14414,14 +14170,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168792cf95a32fa3baf9b874efec82a45124da0a79cee1ae3c98a823e6841959" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-assets 40.0.0", "pallet-nfts 32.0.0", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -14450,14 +14206,14 @@ checksum = "59e2aad461a0849d7f0471576eeb1fe3151795bcf2ec9e15eca5cca5b9d743b2" dependencies = [ "enumflags2", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -14503,13 +14259,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ac349e119880b7df1a7c4c36d919b33a498d0e9548af3c237365c654ae0c73d" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "scale-info", "sp-arithmetic 26.0.0", "sp-core 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -14528,14 +14284,14 @@ version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec3133be9e767b8feafbb26edd805824faa59956da008d2dc7fcf4b4720e56" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -14557,11 +14313,11 @@ dependencies = [ [[package]] name = "pallet-nomination-pools" -version = "35.0.2" +version = "35.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d04f050ab02af6cbe058e101abb8706be7f8ea7958e5bf1d4cd8caa6b66c71" +checksum = "c42906923f9f2b65b22f1211136b57c6878296ba6f6228a075c4442cc1fc1659" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-balances 39.0.0", @@ -14569,7 +14325,7 @@ dependencies = [ "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-staking 36.0.0", "sp-tracing 17.0.1", ] @@ -14606,15 +14362,15 @@ checksum = "38d2eaca0349bcda923343226b8b64d25a80b67e0a1ebaaa5b0ab1e1b3b225bc" dependencies = [ "frame-benchmarking 38.0.0", "frame-election-provider-support 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "pallet-bags-list 37.0.0", "pallet-delegated-staking 5.0.0", - "pallet-nomination-pools 35.0.2", + "pallet-nomination-pools 35.0.0", "pallet-staking 38.0.0", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-runtime-interface 28.0.0", "sp-staking 36.0.0", ] @@ -14645,11 +14401,11 @@ dependencies = [ [[package]] name = "pallet-nomination-pools-runtime-api" -version = "33.0.2" +version = "33.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03eea431eba0658ca763a078bd849e0622c37c85eddd011b8e886460b50c0827" +checksum = "7a9e1cb89cc2e6df06ce274a7fc814e5e688aad04c43902a10191fa3d2a56a96" dependencies = [ - "pallet-nomination-pools 35.0.2", + "pallet-nomination-pools 35.0.0", "parity-scale-codec", "sp-api 34.0.0", ] @@ -14724,14 +14480,14 @@ version = "37.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c4379cf853465696c1c5c03e7e8ce80aeaca0a6139d698abe9ecb3223fd732a" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-balances 39.0.0", "parity-scale-codec", "scale-info", "serde", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-staking 36.0.0", ] @@ -14769,7 +14525,7 @@ checksum = "69aa1b24cdffc3fa8c89cdea32c83f1bf9c1c82a87fa00e57ae4be8e85f5e24f" dependencies = [ "frame-benchmarking 38.0.0", "frame-election-provider-support 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-babe 38.0.0", @@ -14781,7 +14537,7 @@ dependencies = [ "pallet-staking 38.0.0", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-staking 36.0.0", ] @@ -14809,14 +14565,14 @@ checksum = "c8e099fb116068836b17ca4232dc52f762b69dc8cd4e33f509372d958de278b0" dependencies = [ "docify", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", "sp-metadata-ir 0.7.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -14866,14 +14622,14 @@ checksum = "b9aba424d55e17b2a2bec766a41586eab878137704d4803c04bebd6a4743db7b" dependencies = [ "docify", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "paste", "scale-info", "serde", "sp-core 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -14899,14 +14655,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "407828bc48c6193ac076fdf909b2fadcaaecd65f42b0b0a04afe22fe8e563834" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -14927,12 +14683,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d39df395f0dbcf07dafe842916adea3266a87ce36ed87b5132184b6bcd746393" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -14954,12 +14710,12 @@ dependencies = [ [[package]] name = "pallet-ranked-collective" -version = "38.2.0" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15a640e732164203eb5298823cc8c29cfc563763c43c9114e76153b3166b8b9d" +checksum = "c2b38708feaed202debf1ac6beffaa5e20c99a9825c5ca0991753c2d4eaaf3ac" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "impl-trait-for-tuples", "log", @@ -14968,7 +14724,7 @@ dependencies = [ "sp-arithmetic 26.0.0", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -14993,12 +14749,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "406a116aa6d05f88f3c10d79ff89cf577323680a48abd8e5550efb47317e67fa" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -15029,7 +14785,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3008c20531d1730c9b457ae77ecf0e3c9b07aaf8c4f5d798d61ef6f0b9e2d4b" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", @@ -15037,7 +14793,7 @@ dependencies = [ "serde", "sp-arithmetic 26.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -15062,14 +14818,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e8cae0e20888065ec73dda417325c6ecabf797f4002329484b59c25ecc34d4" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "scale-info", "serde", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -15078,7 +14834,7 @@ version = "0.1.0" dependencies = [ "array-bytes", "assert_matches", - "derive_more 0.99.18", + "derive_more 0.99.17", "environmental", "ethereum-types 0.15.1", "frame-benchmarking 28.0.0", @@ -15126,13 +14882,13 @@ dependencies = [ "bitflags 1.3.2", "environmental", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "impl-trait-for-tuples", "log", "pallet-balances 39.0.0", "pallet-revive-fixtures 0.2.0", - "pallet-revive-proc-macro 0.1.2", + "pallet-revive-proc-macro 0.1.1", "pallet-revive-uapi 0.1.1", "parity-scale-codec", "paste", @@ -15142,10 +14898,10 @@ dependencies = [ "sp-api 34.0.0", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "staging-xcm 14.2.0", - "staging-xcm-builder 17.0.3", + "staging-xcm-builder 17.0.1", ] [[package]] @@ -15153,8 +14909,8 @@ name = "pallet-revive-eth-rpc" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.5.26", - "env_logger 0.11.6", + "clap 4.5.13", + "env_logger 0.11.3", "ethabi", "futures", "hex", @@ -15178,7 +14934,7 @@ dependencies = [ "substrate-prometheus-endpoint", "subxt", "subxt-signer", - "thiserror 1.0.69", + "thiserror", "tokio", ] @@ -15203,7 +14959,7 @@ dependencies = [ "frame-system 38.0.0", "parity-wasm", "polkavm-linker 0.10.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "tempfile", "toml 0.8.19", ] @@ -15245,18 +15001,18 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60e74591d44dbd78db02c8593f5caa75bd61bcc4d63999302150223fb969ae37" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "pallet-assets 40.0.0", "pallet-balances 39.0.0", - "pallet-message-queue 41.0.2", + "pallet-message-queue 41.0.1", "pallet-proxy 38.0.0", "pallet-revive 0.2.0", - "pallet-revive-proc-macro 0.1.2", + "pallet-revive-proc-macro 0.1.1", "pallet-revive-uapi 0.1.1", "pallet-timestamp 37.0.0", "pallet-utility 38.0.0", - "pallet-xcm 17.0.1", + "pallet-xcm 17.0.0", "parity-scale-codec", "polkadot-parachain-primitives 14.0.0", "polkadot-primitives 16.0.0", @@ -15266,10 +15022,10 @@ dependencies = [ "sp-core 34.0.0", "sp-io 38.0.0", "sp-keystore 0.40.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-tracing 17.0.1", "staging-xcm 14.2.0", - "staging-xcm-builder 17.0.3", + "staging-xcm-builder 17.0.1", "staging-xcm-executor 17.0.0", "xcm-simulator 17.0.0", ] @@ -15278,20 +15034,20 @@ dependencies = [ name = "pallet-revive-proc-macro" version = "0.1.0" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "pallet-revive-proc-macro" -version = "0.1.2" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8aee42afa416be6324cf6650c137da9742f27dc7be3c7ed39ad9748baf3b9ae" +checksum = "0cc16d1f7cee6a1ee6e8cd710e16230d59fb4935316c1704cf770e4d2335f8d4" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -15345,13 +15101,13 @@ version = "35.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b35774b830928daaeeca7196cead7c56eeed952a6616ad6dc5ec068d8c85c81a" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "pallet-session 38.0.0", "pallet-staking 38.0.0", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-staking 36.0.0", ] @@ -15374,13 +15130,13 @@ version = "14.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be95e7c320ac1d381715364cd721e67ab3152ab727f8e4defd3a92e41ebbc880" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -15410,7 +15166,7 @@ checksum = "6d3e67dd4644c168cedbf257ac3dd2527aad81acf4a0d413112197094e549f76" dependencies = [ "docify", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "pallet-balances 39.0.0", "pallet-proxy 38.0.0", @@ -15418,7 +15174,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-arithmetic 26.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -15434,21 +15190,21 @@ dependencies = [ [[package]] name = "pallet-salary" -version = "23.2.0" +version = "23.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3af2d92b1fef1c379c0692113b505c108c186e09c25c72b38e879b6e0f172ebe" +checksum = "0544a71dba06a9a29da0778ba8cb37728c3b9a8377ac9737c4b1bc48c618bc2f" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", - "pallet-ranked-collective 38.2.0", + "pallet-ranked-collective 38.0.0", "parity-scale-codec", "scale-info", "sp-arithmetic 26.0.0", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -15496,13 +15252,13 @@ checksum = "26899a331e7ab5f7d5966cbf203e1cf5bd99cd110356d7ddcaa7597087cdc0b5" dependencies = [ "docify", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-weights 31.0.0", ] @@ -15526,12 +15282,12 @@ version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f84b48bb4702712c902f43931c4077d3a1cb6773c8d8c290d4a6251f6bc2a5c" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -15560,7 +15316,7 @@ version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8474b62b6b7622f891e83d922a589e2ad5be5471f5ca47d45831a797dba0b3f4" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "impl-trait-for-tuples", "log", @@ -15569,7 +15325,7 @@ dependencies = [ "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-session 36.0.0", "sp-staking 36.0.0", "sp-state-machine 0.43.0", @@ -15605,13 +15361,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8aadce7df0fee981721983795919642648b846dab5ab9096f82c2cea781007d0" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "pallet-session 38.0.0", "pallet-staking 38.0.0", "parity-scale-codec", "rand", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-session 36.0.0", ] @@ -15632,11 +15388,11 @@ version = "13.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8c2cb0dae13d2c2d2e76373f337d408468f571459df1900cbd7458f21cf6c01" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -15666,7 +15422,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1dc69fea8a8de343e71691f009d5fece6ae302ed82b7bb357882b2ea6454143" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", @@ -15674,7 +15430,7 @@ dependencies = [ "scale-info", "sp-arithmetic 26.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -15714,7 +15470,7 @@ checksum = "c870d123f4f053b56af808a4beae1ffc4309a696e829796c26837936c926db3b" dependencies = [ "frame-benchmarking 38.0.0", "frame-election-provider-support 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-authorship 38.0.0", @@ -15724,7 +15480,7 @@ dependencies = [ "serde", "sp-application-crypto 38.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-staking 36.0.0", ] @@ -15732,11 +15488,11 @@ dependencies = [ name = "pallet-staking-reward-curve" version = "11.0.0" dependencies = [ - "proc-macro-crate 3.2.0", - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro-crate 3.1.0", + "proc-macro2 1.0.86", + "quote 1.0.37", "sp-runtime 31.0.1", - "syn 2.0.96", + "syn 2.0.87", ] [[package]] @@ -15808,14 +15564,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138c15b4200b9dc4c3e031def6a865a235cdc76ff91ee96fba19ca1787c9dda6" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -15841,7 +15597,7 @@ version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e03e147efa900e75cd106337f36da3d7dcd185bd9e5f5c3df474c08c3c37d16" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", @@ -15849,7 +15605,7 @@ dependencies = [ "sp-api 34.0.0", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-statement-store 18.0.0", ] @@ -15876,12 +15632,12 @@ checksum = "1574fe2aed3d52db4a389b77b53d8c9758257b121e3e7bbe24c4904e11681e0e" dependencies = [ "docify", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -15925,14 +15681,14 @@ checksum = "a9ba9b71bbfd33ae672f23ba7efaeed2755fdac37b8f946cb7474fc37841b7e1" dependencies = [ "docify", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", "scale-info", "sp-inherents 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-storage 21.0.0", "sp-timestamp 34.0.0", ] @@ -15963,7 +15719,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa1d4371a70c309ba11624933f8f5262fe4edad0149c556361d31f26190da936" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-treasury 37.0.0", @@ -15972,7 +15728,7 @@ dependencies = [ "serde", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -15994,18 +15750,18 @@ dependencies = [ [[package]] name = "pallet-transaction-payment" -version = "38.0.2" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cdb86580c72b58145f9cddba21a0c1814742ca56abc9caac3c1ac72f6bde649" +checksum = "47b1aa3498107a30237f941b0f02180db3b79012c3488878ff01a4ac3e8ee04e" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "scale-info", "serde", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -16040,10 +15796,10 @@ version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49fdf5ab71e9dbcadcf7139736b6ea6bac8ec4a83985d46cbd130e1eec770e41" dependencies = [ - "pallet-transaction-payment 38.0.2", + "pallet-transaction-payment 38.0.0", "parity-scale-codec", "sp-api 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-weights 31.0.0", ] @@ -16074,7 +15830,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8c337a972a6a796c0a0acc6c03b5e02901c43ad721ce79eb87b45717d75c93b" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-balances 39.0.0", @@ -16083,7 +15839,7 @@ dependencies = [ "serde", "sp-inherents 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-transaction-storage-proof 34.0.0", ] @@ -16115,7 +15871,7 @@ checksum = "98bfdd3bb9b58fb010bcd419ff5bf940817a8e404cdbf7886a53ac730f5dda2b" dependencies = [ "docify", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "impl-trait-for-tuples", "pallet-balances 39.0.0", @@ -16123,7 +15879,7 @@ dependencies = [ "scale-info", "serde", "sp-core 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -16152,14 +15908,14 @@ checksum = "cee153f5be5efc84ebd53aa581e5361cde17dc3669ef80d8ad327f4041d89ebe" dependencies = [ "docify", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "pallet-balances 39.0.0", "pallet-proxy 38.0.0", "pallet-utility 38.0.0", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -16185,12 +15941,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2b13cdaedf2d5bd913a5f6e637cb52b5973d8ed4b8d45e56d921bc4d627006f" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -16218,13 +15974,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fdcade6efc0b66fc7fc4138964802c02d0ffb7380d894e26b9dd5073727d2b3" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "scale-info", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -16269,12 +16025,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "807df2ef13ab6bf940879352c3013bfa00b670458b4c125c2f60e5753f68e3d5" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -16301,12 +16057,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ef17df925290865cf37096dd0cb76f787df11805bba01b1d0ca3e106d06280b" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "scale-info", "sp-api 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -16336,13 +16092,13 @@ dependencies = [ [[package]] name = "pallet-xcm" -version = "17.0.1" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989676964dbda5f5275650fbdcd3894fe7fac626d113abf89d572b4952adcc36" +checksum = "0b1760b6589e53f4ad82216c72c0e38fcb4df149c37224ab3301dc240c85d1d4" dependencies = [ "bounded-collections", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-balances 39.0.0", @@ -16351,12 +16107,11 @@ dependencies = [ "serde", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "staging-xcm 14.2.0", - "staging-xcm-builder 17.0.3", + "staging-xcm-builder 17.0.1", "staging-xcm-executor 17.0.0", - "tracing", - "xcm-runtime-apis 0.4.2", + "xcm-runtime-apis 0.4.0", ] [[package]] @@ -16389,15 +16144,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2da423463933b42f4a4c74175f9e9295a439de26719579b894ce533926665e4a" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "staging-xcm 14.2.0", - "staging-xcm-builder 17.0.3", + "staging-xcm-builder 17.0.1", "staging-xcm-executor 17.0.0", ] @@ -16430,24 +16185,24 @@ dependencies = [ [[package]] name = "pallet-xcm-bridge-hub" -version = "0.13.2" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f336403f9e9bf22a0e1fdb90aa5093c52599c9a0639591fbcc1e979b58862d1b" +checksum = "d5f9670065b7cba92771060a4a3925b6650ff67611443ccfccd5aa356f7d5aac" dependencies = [ "bp-messages 0.18.0", "bp-runtime 0.18.0", - "bp-xcm-bridge-hub 0.4.2", - "frame-support 38.2.0", + "bp-xcm-bridge-hub 0.4.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-bridge-messages 0.18.0", "parity-scale-codec", "scale-info", "sp-core 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "staging-xcm 14.2.0", - "staging-xcm-builder 17.0.3", + "staging-xcm-builder 17.0.1", "staging-xcm-executor 17.0.0", ] @@ -16472,29 +16227,29 @@ dependencies = [ [[package]] name = "pallet-xcm-bridge-hub-router" -version = "0.15.3" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabf1fdcf451ac79995f11cb9b6a0761924c57bb79442c2d91b3bbefe4dfa081" +checksum = "f3b5347c826b721098ef39afb0d750e621c77538044fc1e865af1a8747824fdf" dependencies = [ "bp-xcm-bridge-hub-router 0.14.1", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", "scale-info", "sp-core 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "staging-xcm 14.2.0", - "staging-xcm-builder 17.0.3", + "staging-xcm-builder 17.0.1", ] [[package]] name = "parachain-template-node" version = "0.0.0" dependencies = [ - "clap 4.5.26", + "clap 4.5.13", "color-print", "docify", "futures", @@ -16563,7 +16318,7 @@ checksum = "c9460a69f409be27c62161d8b4d36ffc32735d09a4f9097f9c789db0cca7196c" dependencies = [ "cumulus-primitives-core 0.16.0", "cumulus-primitives-utility 0.17.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-asset-tx-payment 38.0.0", @@ -16571,15 +16326,15 @@ dependencies = [ "pallet-authorship 38.0.0", "pallet-balances 39.0.0", "pallet-collator-selection 19.0.0", - "pallet-message-queue 41.0.2", - "pallet-xcm 17.0.1", + "pallet-message-queue 41.0.1", + "pallet-xcm 17.0.0", "parity-scale-codec", "polkadot-primitives 16.0.0", "scale-info", "sp-consensus-aura 0.40.0", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "staging-parachain-info 0.17.0", "staging-xcm 14.2.0", "staging-xcm-executor 17.0.0", @@ -16644,19 +16399,19 @@ dependencies = [ "cumulus-primitives-core 0.16.0", "cumulus-primitives-parachain-inherent 0.16.0", "cumulus-test-relay-sproof-builder 0.16.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "pallet-balances 39.0.0", "pallet-collator-selection 19.0.0", "pallet-session 38.0.0", "pallet-timestamp 37.0.0", - "pallet-xcm 17.0.1", + "pallet-xcm 17.0.0", "parity-scale-codec", "polkadot-parachain-primitives 14.0.0", "sp-consensus-aura 0.40.0", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-tracing 17.0.1", "staging-parachain-info 0.17.0", "staging-xcm 14.2.0", @@ -16685,9 +16440,9 @@ checksum = "16b56e3a2420138bdb970f84dfb9c774aea80fa0e7371549eedec0d80c209c67" [[package]] name = "parity-db" -version = "0.4.13" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "592a28a24b09c9dc20ac8afaa6839abc417c720afe42c12e1e4a9d6aa2508d2e" +checksum = "59e9ab494af9e6e813c72170f0d3c1de1500990d62c97cc05cc7576f91aa402f" dependencies = [ "blake2 0.10.6", "crc32fast", @@ -16701,7 +16456,6 @@ dependencies = [ "rand", "siphasher 0.3.11", "snap", - "winapi", ] [[package]] @@ -16710,7 +16464,7 @@ version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" dependencies = [ - "arrayvec 0.7.6", + "arrayvec 0.7.4", "bitvec", "byte-slice-cast", "bytes", @@ -16725,9 +16479,9 @@ version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" dependencies = [ - "proc-macro-crate 3.2.0", - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro-crate 3.1.0", + "proc-macro2 1.0.86", + "quote 1.0.37", "syn 1.0.109", ] @@ -16755,7 +16509,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f557c32c6d268a07c921471619c0295f5efad3a0e76d4f97a05c091a51d110b2" dependencies = [ - "proc-macro2 1.0.93", + "proc-macro2 1.0.86", "syn 1.0.109", "synstructure 0.12.6", ] @@ -16790,7 +16544,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", - "parking_lot_core 0.9.10", + "parking_lot_core 0.9.8", ] [[package]] @@ -16809,15 +16563,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.8", + "redox_syscall 0.3.5", "smallvec", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -16834,7 +16588,7 @@ checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", "rand_core 0.6.4", - "subtle 2.6.1", + "subtle 2.5.0", ] [[package]] @@ -17178,20 +16932,19 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.15" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" +checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a" dependencies = [ - "memchr", - "thiserror 2.0.11", + "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.15" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" +checksum = "666d00490d4ac815001da55838c500eafb0320019bbaa44444137c48b443a853" dependencies = [ "pest", "pest_generator", @@ -17199,22 +16952,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.15" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" +checksum = "68ca01446f50dbda87c1786af8770d535423fa8a53aec03b8f4e3d7eb10e0929" dependencies = [ "pest", "pest_meta", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "pest_meta" -version = "2.7.15" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" +checksum = "56af0a30af74d0445c0bf6d9d051c979b516a1a5af790d251daee76005420a48" dependencies = [ "once_cell", "pest", @@ -17223,9 +16976,9 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.6.5" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", "indexmap 2.7.0", @@ -17233,29 +16986,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.8" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" +checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.8" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" +checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -17263,17 +17016,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "piper" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" -dependencies = [ - "atomic-waker", - "fastrand 2.3.0", - "futures-io", -] - [[package]] name = "pkcs1" version = "0.7.5" @@ -17297,21 +17039,21 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "platforms" -version = "3.5.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d43467300237085a4f9e864b937cf0bc012cef7740be12be1a48b10d2c8a3701" +checksum = "0e4c7666f2019727f9e8e14bf14456e99c707d780922869f1ba473eee101fa49" [[package]] name = "plotters" -version = "0.3.7" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" dependencies = [ "num-traits", "plotters-backend", @@ -17322,15 +17064,15 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.7" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" [[package]] name = "plotters-svg" -version = "0.3.7" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" dependencies = [ "plotters-backend", ] @@ -17417,7 +17159,7 @@ name = "polkadot-availability-distribution" version = "7.0.0" dependencies = [ "assert_matches", - "derive_more 0.99.18", + "derive_more 0.99.17", "fatality", "futures", "futures-timer", @@ -17439,7 +17181,7 @@ dependencies = [ "sp-keyring 31.0.0", "sp-keystore 0.34.0", "sp-tracing 16.0.0", - "thiserror 1.0.69", + "thiserror", "tracing-gum", ] @@ -17471,7 +17213,7 @@ dependencies = [ "sp-core 28.0.0", "sp-keyring 31.0.0", "sp-tracing 16.0.0", - "thiserror 1.0.69", + "thiserror", "tokio", "tracing-gum", ] @@ -17491,7 +17233,7 @@ name = "polkadot-cli" version = "7.0.0" dependencies = [ "cfg-if", - "clap 4.5.26", + "clap 4.5.13", "frame-benchmarking-cli", "futures", "log", @@ -17512,7 +17254,7 @@ dependencies = [ "sp-maybe-compressed-blob 11.0.0", "sp-runtime 31.0.1", "substrate-build-script-utils", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -17541,7 +17283,7 @@ dependencies = [ "sp-keystore 0.34.0", "sp-runtime 31.0.1", "sp-tracing 16.0.0", - "thiserror 1.0.69", + "thiserror", "tokio-util", "tracing-gum", ] @@ -17565,7 +17307,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-core 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -17575,7 +17317,7 @@ dependencies = [ "assert_matches", "async-channel 1.9.0", "async-trait", - "derive_more 0.99.18", + "derive_more 0.99.17", "fatality", "futures", "futures-timer", @@ -17596,7 +17338,7 @@ dependencies = [ "sp-keyring 31.0.0", "sp-keystore 0.34.0", "sp-tracing 16.0.0", - "thiserror 1.0.69", + "thiserror", "tracing-gum", ] @@ -17612,7 +17354,7 @@ dependencies = [ "reed-solomon-novelpoly", "sp-core 28.0.0", "sp-trie 29.0.0", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -17670,7 +17412,7 @@ dependencies = [ "sp-consensus", "sp-core 28.0.0", "sp-keyring 31.0.0", - "thiserror 1.0.69", + "thiserror", "tracing-gum", ] @@ -17693,7 +17435,7 @@ dependencies = [ "sp-core 28.0.0", "sp-keyring 31.0.0", "sp-maybe-compressed-blob 11.0.0", - "thiserror 1.0.69", + "thiserror", "tracing-gum", ] @@ -17704,7 +17446,7 @@ dependencies = [ "assert_matches", "async-trait", "bitvec", - "derive_more 0.99.18", + "derive_more 0.99.17", "futures", "futures-timer", "itertools 0.11.0", @@ -17737,7 +17479,7 @@ dependencies = [ "sp-keystore 0.34.0", "sp-runtime 31.0.1", "sp-tracing 16.0.0", - "thiserror 1.0.69", + "thiserror", "tracing-gum", ] @@ -17779,7 +17521,7 @@ dependencies = [ "sp-keystore 0.34.0", "sp-runtime 31.0.1", "sp-tracing 16.0.0", - "thiserror 1.0.69", + "thiserror", "tracing-gum", ] @@ -17808,7 +17550,7 @@ dependencies = [ "sp-core 28.0.0", "sp-keyring 31.0.0", "sp-tracing 16.0.0", - "thiserror 1.0.69", + "thiserror", "tracing-gum", ] @@ -17837,7 +17579,7 @@ dependencies = [ "sp-keyring 31.0.0", "sp-keystore 0.34.0", "sp-tracing 16.0.0", - "thiserror 1.0.69", + "thiserror", "tracing-gum", ] @@ -17852,7 +17594,7 @@ dependencies = [ "polkadot-primitives 7.0.0", "polkadot-primitives-test-helpers", "sp-keystore 0.34.0", - "thiserror 1.0.69", + "thiserror", "tracing-gum", "wasm-timer", ] @@ -17922,7 +17664,7 @@ dependencies = [ "polkadot-node-subsystem-util", "polkadot-primitives 7.0.0", "sp-core 28.0.0", - "thiserror 1.0.69", + "thiserror", "tracing-gum", ] @@ -17950,7 +17692,7 @@ dependencies = [ "sp-keyring 31.0.0", "sp-keystore 0.34.0", "sp-tracing 16.0.0", - "thiserror 1.0.69", + "thiserror", "tracing-gum", ] @@ -17966,7 +17708,7 @@ dependencies = [ "polkadot-primitives 7.0.0", "sp-blockchain", "sp-inherents 26.0.0", - "thiserror 1.0.69", + "thiserror", "tracing-gum", ] @@ -17986,7 +17728,7 @@ dependencies = [ "rstest", "sp-core 28.0.0", "sp-tracing 16.0.0", - "thiserror 1.0.69", + "thiserror", "tracing-gum", ] @@ -18008,7 +17750,7 @@ dependencies = [ "schnellru", "sp-application-crypto 30.0.0", "sp-keystore 0.34.0", - "thiserror 1.0.69", + "thiserror", "tracing-gum", ] @@ -18053,7 +17795,7 @@ dependencies = [ "tempfile", "test-parachain-adder", "test-parachain-halt", - "thiserror 1.0.69", + "thiserror", "tokio", "tracing-gum", ] @@ -18077,7 +17819,7 @@ dependencies = [ "sp-keyring 31.0.0", "sp-keystore 0.34.0", "sp-runtime 31.0.1", - "thiserror 1.0.69", + "thiserror", "tracing-gum", ] @@ -18104,7 +17846,7 @@ dependencies = [ "sp-io 30.0.0", "sp-tracing 16.0.0", "tempfile", - "thiserror 1.0.69", + "thiserror", "tracing-gum", ] @@ -18179,7 +17921,7 @@ dependencies = [ "futures", "futures-timer", "http-body-util", - "hyper 1.5.2", + "hyper 1.3.1", "hyper-util", "log", "parity-scale-codec", @@ -18205,7 +17947,7 @@ dependencies = [ "async-channel 1.9.0", "async-trait", "bitvec", - "derive_more 0.99.18", + "derive_more 0.99.17", "fatality", "futures", "hex", @@ -18219,7 +17961,7 @@ dependencies = [ "sc-network-types", "sp-runtime 31.0.1", "strum 0.26.3", - "thiserror 1.0.69", + "thiserror", "tracing-gum", ] @@ -18245,7 +17987,7 @@ dependencies = [ "sp-keystore 0.34.0", "sp-maybe-compressed-blob 11.0.0", "sp-runtime 31.0.1", - "thiserror 1.0.69", + "thiserror", "zstd 0.12.4", ] @@ -18284,7 +18026,7 @@ version = "7.0.0" dependencies = [ "async-trait", "bitvec", - "derive_more 0.99.18", + "derive_more 0.99.17", "fatality", "futures", "orchestra", @@ -18303,7 +18045,7 @@ dependencies = [ "sp-consensus-babe 0.32.0", "sp-runtime 31.0.1", "substrate-prometheus-endpoint", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -18312,7 +18054,7 @@ version = "7.0.0" dependencies = [ "assert_matches", "async-trait", - "derive_more 0.99.18", + "derive_more 0.99.17", "fatality", "futures", "futures-channel", @@ -18343,7 +18085,7 @@ dependencies = [ "sp-core 28.0.0", "sp-keystore 0.34.0", "tempfile", - "thiserror 1.0.69", + "thiserror", "tracing-gum", ] @@ -18362,7 +18104,7 @@ version = "0.1.0" dependencies = [ "assert_cmd", "async-trait", - "clap 4.5.26", + "clap 4.5.13", "color-print", "cumulus-client-cli", "cumulus-client-collator", @@ -18503,7 +18245,7 @@ name = "polkadot-parachain-primitives" version = "6.0.0" dependencies = [ "bounded-collections", - "derive_more 0.99.18", + "derive_more 0.99.17", "parity-scale-codec", "polkadot-core-primitives 7.0.0", "scale-info", @@ -18520,13 +18262,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52b5648a2e8ce1f9a0f8c41c38def670cefd91932cd793468e1a5b0b0b4e4af1" dependencies = [ "bounded-collections", - "derive_more 0.99.18", + "derive_more 0.99.17", "parity-scale-codec", "polkadot-core-primitives 15.0.0", "scale-info", "serde", "sp-core 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-weights 31.0.0", ] @@ -18555,7 +18297,7 @@ dependencies = [ "sp-runtime 31.0.1", "sp-staking 26.0.0", "sp-std 14.0.0", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -18581,7 +18323,7 @@ dependencies = [ "sp-inherents 34.0.0", "sp-io 38.0.0", "sp-keystore 0.40.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-staking 34.0.0", ] @@ -18608,7 +18350,7 @@ dependencies = [ "sp-inherents 34.0.0", "sp-io 38.0.0", "sp-keystore 0.40.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-staking 36.0.0", ] @@ -18722,7 +18464,7 @@ dependencies = [ "bitvec", "frame-benchmarking 38.0.0", "frame-election-provider-support 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "impl-trait-for-tuples", "libsecp256k1", @@ -18730,7 +18472,7 @@ dependencies = [ "pallet-asset-rate 17.0.0", "pallet-authorship 38.0.0", "pallet-balances 39.0.0", - "pallet-broker 0.17.2", + "pallet-broker 0.17.0", "pallet-election-provider-multi-phase 37.0.0", "pallet-fast-unstake 37.0.0", "pallet-identity 38.0.0", @@ -18738,7 +18480,7 @@ dependencies = [ "pallet-staking 38.0.0", "pallet-staking-reward-fn 22.0.0", "pallet-timestamp 37.0.0", - "pallet-transaction-payment 38.0.2", + "pallet-transaction-payment 38.0.0", "pallet-treasury 37.0.0", "pallet-vesting 38.0.0", "parity-scale-codec", @@ -18754,11 +18496,11 @@ dependencies = [ "sp-inherents 34.0.0", "sp-io 38.0.0", "sp-npos-elections 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-session 36.0.0", "sp-staking 36.0.0", "staging-xcm 14.2.0", - "staging-xcm-builder 17.0.3", + "staging-xcm-builder 17.0.1", "staging-xcm-executor 17.0.0", "static_assertions", ] @@ -18794,7 +18536,7 @@ dependencies = [ "assert_matches", "bitflags 1.3.2", "bitvec", - "derive_more 0.99.18", + "derive_more 0.99.17", "frame-benchmarking 28.0.0", "frame-support 28.0.0", "frame-support-test", @@ -18855,9 +18597,9 @@ checksum = "bd58e3a17e5df678f5737b018cbfec603af2c93bec56bbb9f8fb8b2b017b54b1" dependencies = [ "bitflags 1.3.2", "bitvec", - "derive_more 0.99.18", + "derive_more 0.99.17", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "impl-trait-for-tuples", "log", @@ -18865,8 +18607,8 @@ dependencies = [ "pallet-authorship 38.0.0", "pallet-babe 38.0.0", "pallet-balances 39.0.0", - "pallet-broker 0.17.2", - "pallet-message-queue 41.0.2", + "pallet-broker 0.17.0", + "pallet-message-queue 41.0.1", "pallet-mmr 38.0.0", "pallet-session 38.0.0", "pallet-staking 38.0.0", @@ -18888,7 +18630,7 @@ dependencies = [ "sp-inherents 34.0.0", "sp-io 38.0.0", "sp-keystore 0.40.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-session 36.0.0", "sp-staking 36.0.0", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -19286,7 +19028,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb819108697967452fa6d8d96ab4c0d48cbaa423b3156499dcb24f1cf95d6775" dependencies = [ "asset-test-utils 18.0.0", - "assets-common 0.18.3", + "assets-common 0.18.0", "binary-merkle-tree 15.0.1", "bp-header-chain 0.18.1", "bp-messages 0.18.0", @@ -19296,11 +19038,11 @@ dependencies = [ "bp-relayers 0.18.0", "bp-runtime 0.18.0", "bp-test-utils 0.18.0", - "bp-xcm-bridge-hub 0.4.2", + "bp-xcm-bridge-hub 0.4.0", "bp-xcm-bridge-hub-router 0.14.1", "bridge-hub-common 0.10.0", "bridge-hub-test-utils 0.18.0", - "bridge-runtime-common 0.18.2", + "bridge-runtime-common 0.18.0", "cumulus-pallet-aura-ext 0.17.0", "cumulus-pallet-dmp-queue 0.17.0", "cumulus-pallet-parachain-system 0.17.1", @@ -19323,7 +19065,7 @@ dependencies = [ "frame-election-provider-support 38.0.0", "frame-executive 38.0.0", "frame-metadata-hash-extension 0.6.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-support-procedural 30.0.4", "frame-system 38.0.0", "frame-system-benchmarking 38.0.0", @@ -19350,8 +19092,8 @@ dependencies = [ "pallet-bridge-grandpa 0.18.0", "pallet-bridge-messages 0.18.0", "pallet-bridge-parachains 0.18.0", - "pallet-bridge-relayers 0.18.2", - "pallet-broker 0.17.2", + "pallet-bridge-relayers 0.18.0", + "pallet-broker 0.17.0", "pallet-child-bounties 37.0.0", "pallet-collator-selection 19.0.0", "pallet-collective 38.0.0", @@ -19359,7 +19101,7 @@ dependencies = [ "pallet-contracts 38.0.0", "pallet-contracts-mock-network 14.0.0", "pallet-conviction-voting 38.0.0", - "pallet-core-fellowship 22.2.0", + "pallet-core-fellowship 22.0.0", "pallet-delegated-staking 5.0.0", "pallet-democracy 38.0.0", "pallet-dev-mode 20.0.0", @@ -19375,7 +19117,7 @@ dependencies = [ "pallet-insecure-randomness-collective-flip 26.0.0", "pallet-lottery 38.0.0", "pallet-membership 38.0.0", - "pallet-message-queue 41.0.2", + "pallet-message-queue 41.0.1", "pallet-migrations 8.0.0", "pallet-mixnet 0.14.0", "pallet-mmr 38.0.0", @@ -19385,16 +19127,16 @@ dependencies = [ "pallet-nfts-runtime-api 24.0.0", "pallet-nis 38.0.0", "pallet-node-authorization 38.0.0", - "pallet-nomination-pools 35.0.2", + "pallet-nomination-pools 35.0.0", "pallet-nomination-pools-benchmarking 36.0.0", - "pallet-nomination-pools-runtime-api 33.0.2", + "pallet-nomination-pools-runtime-api 33.0.0", "pallet-offences 37.0.0", "pallet-offences-benchmarking 38.0.0", "pallet-paged-list 0.16.0", "pallet-parameters 0.9.0", "pallet-preimage 38.0.0", "pallet-proxy 38.0.0", - "pallet-ranked-collective 38.2.0", + "pallet-ranked-collective 38.0.0", "pallet-recovery 38.0.0", "pallet-referenda 38.0.0", "pallet-remark 38.0.0", @@ -19404,7 +19146,7 @@ dependencies = [ "pallet-root-offences 35.0.0", "pallet-root-testing 14.0.0", "pallet-safe-mode 19.0.0", - "pallet-salary 23.2.0", + "pallet-salary 23.0.0", "pallet-scheduler 39.0.0", "pallet-scored-pool 38.0.0", "pallet-session 38.0.0", @@ -19419,7 +19161,7 @@ dependencies = [ "pallet-sudo 38.0.0", "pallet-timestamp 37.0.0", "pallet-tips 37.0.0", - "pallet-transaction-payment 38.0.2", + "pallet-transaction-payment 38.0.0", "pallet-transaction-payment-rpc-runtime-api 38.0.0", "pallet-transaction-storage 37.0.0", "pallet-treasury 37.0.0", @@ -19428,10 +19170,10 @@ dependencies = [ "pallet-utility 38.0.0", "pallet-vesting 38.0.0", "pallet-whitelist 37.0.0", - "pallet-xcm 17.0.1", + "pallet-xcm 17.0.0", "pallet-xcm-benchmarks 17.0.0", - "pallet-xcm-bridge-hub 0.13.2", - "pallet-xcm-bridge-hub-router 0.15.3", + "pallet-xcm-bridge-hub 0.13.0", + "pallet-xcm-bridge-hub-router 0.15.1", "parachains-common 18.0.0", "parachains-runtimes-test-utils 17.0.0", "polkadot-core-primitives 15.0.0", @@ -19486,7 +19228,7 @@ dependencies = [ "sp-mmr-primitives 34.1.0", "sp-npos-elections 34.0.0", "sp-offchain 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-runtime-interface 28.0.0", "sp-session 36.0.0", "sp-staking 36.0.0", @@ -19504,11 +19246,11 @@ dependencies = [ "sp-weights 31.0.0", "staging-parachain-info 0.17.0", "staging-xcm 14.2.0", - "staging-xcm-builder 17.0.3", + "staging-xcm-builder 17.0.1", "staging-xcm-executor 17.0.0", "substrate-bip39 0.6.0", "testnet-parachains-constants 10.0.0", - "xcm-runtime-apis 0.4.2", + "xcm-runtime-apis 0.4.0", ] [[package]] @@ -19685,7 +19427,7 @@ dependencies = [ "docify", "frame-benchmarking 38.0.0", "frame-executive 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "frame-system-benchmarking 38.0.0", "frame-system-rpc-runtime-api 34.0.0", @@ -19702,7 +19444,7 @@ dependencies = [ "sp-inherents 34.0.0", "sp-io 38.0.0", "sp-offchain 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-session 36.0.0", "sp-storage 21.0.0", "sp-transaction-pool 34.0.0", @@ -19818,7 +19560,7 @@ dependencies = [ "staging-xcm 7.0.0", "substrate-prometheus-endpoint", "tempfile", - "thiserror 1.0.69", + "thiserror", "tracing-gum", "westend-runtime", "westend-runtime-constants 7.0.0", @@ -19829,7 +19571,7 @@ dependencies = [ name = "polkadot-statement-distribution" version = "7.0.0" dependencies = [ - "arrayvec 0.7.6", + "arrayvec 0.7.4", "assert_matches", "async-channel 1.9.0", "bitvec", @@ -19857,7 +19599,7 @@ dependencies = [ "sp-keystore 0.34.0", "sp-staking 26.0.0", "sp-tracing 16.0.0", - "thiserror 1.0.69", + "thiserror", "tracing-gum", ] @@ -19879,7 +19621,7 @@ dependencies = [ "async-trait", "bincode", "bitvec", - "clap 4.5.26", + "clap 4.5.13", "clap-num", "color-eyre", "colored", @@ -19981,7 +19723,7 @@ version = "1.0.0" dependencies = [ "assert_matches", "async-trait", - "clap 4.5.26", + "clap 4.5.13", "color-eyre", "futures", "futures-timer", @@ -20123,7 +19865,7 @@ dependencies = [ name = "polkadot-voter-bags" version = "7.0.0" dependencies = [ - "clap 4.5.26", + "clap 4.5.13", "generate-bags", "sp-io 30.0.0", "westend-runtime", @@ -20134,7 +19876,7 @@ name = "polkadot-zombienet-sdk-tests" version = "0.1.0" dependencies = [ "anyhow", - "env_logger 0.11.6", + "env_logger 0.11.3", "log", "parity-scale-codec", "polkadot-primitives 7.0.0", @@ -20277,9 +20019,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c4fdfc49717fb9a196e74a5d28e0bc764eb394a2c803eb11133a31ac996c60c" dependencies = [ "polkavm-common 0.9.0", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -20289,21 +20031,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7855353a5a783dd5d09e3b915474bddf66575f5a3cf45dec8d1c5e051ba320dc" dependencies = [ "polkavm-common 0.10.0", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "polkavm-derive-impl" -version = "0.18.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f2116a92e6e96220a398930f4c8a6cda1264206f3e2034fc9982bfd93f261f7" +checksum = "12d2840cc62a0550156b1676fed8392271ddf2fab4a00661db56231424674624" dependencies = [ "polkavm-common 0.18.0", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -20313,7 +20055,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ "polkavm-derive-impl 0.9.0", - "syn 2.0.96", + "syn 2.0.87", ] [[package]] @@ -20323,7 +20065,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9324fe036de37c17829af233b46ef6b5562d4a0c09bb7fdb9f8378856dee30cf" dependencies = [ "polkavm-derive-impl 0.10.0", - "syn 2.0.96", + "syn 2.0.87", ] [[package]] @@ -20332,8 +20074,8 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c16669ddc7433e34c1007d31080b80901e3e8e523cb9d4b441c3910cf9294b" dependencies = [ - "polkavm-derive-impl 0.18.1", - "syn 2.0.96", + "polkavm-derive-impl 0.18.0", + "syn 2.0.87", ] [[package]] @@ -20342,7 +20084,7 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c7be503e60cf56c0eb785f90aaba4b583b36bff00e93997d93fef97f9553c39" dependencies = [ - "gimli 0.28.1", + "gimli 0.28.0", "hashbrown 0.14.5", "log", "object 0.32.2", @@ -20357,10 +20099,10 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d704edfe7bdcc876784f19436d53d515b65eb07bc9a0fae77085d552c2dbbb5" dependencies = [ - "gimli 0.28.1", + "gimli 0.28.0", "hashbrown 0.14.5", "log", - "object 0.36.7", + "object 0.36.1", "polkavm-common 0.10.0", "regalloc2 0.9.3", "rustc-demangle", @@ -20376,7 +20118,7 @@ dependencies = [ "gimli 0.31.1", "hashbrown 0.14.5", "log", - "object 0.36.7", + "object 0.36.1", "polkavm-common 0.18.0", "regalloc2 0.9.3", "rustc-demangle", @@ -20418,17 +20160,16 @@ dependencies = [ [[package]] name = "polling" -version = "3.7.4" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" +checksum = "30054e72317ab98eddd8561db0f6524df3367636884b7b21b703e4b280a84a14" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi 0.4.0", "pin-project-lite", - "rustix 0.38.43", + "rustix 0.38.42", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -20438,27 +20179,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ "cpufeatures", - "opaque-debug 0.3.1", + "opaque-debug 0.3.0", "universal-hash", ] [[package]] name = "polyval" -version = "0.6.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" dependencies = [ "cfg-if", "cpufeatures", - "opaque-debug 0.3.1", + "opaque-debug 0.3.0", "universal-hash", ] [[package]] name = "portable-atomic" -version = "1.10.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" +checksum = "f32154ba0af3a075eefa1eda8bb414ee928f62303a54ea85b8d6638ff1a6ee9e" [[package]] name = "portpicker" @@ -20476,23 +20217,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] -name = "pprof2" -version = "0.13.1" +name = "pprof" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8961ed0a916b512e565f8070eb0dfa05773dd140160b45ac9a5ad339b557adeb" +checksum = "978385d59daf9269189d052ca8a84c1acfd0715c0599a5d5188d4acc078ca46a" dependencies = [ "backtrace", "cfg-if", "findshlibs", "libc", "log", - "nix 0.27.1", + "nix 0.26.4", "once_cell", "parking_lot 0.12.3", "smallvec", "symbolic-demangle", "tempfile", - "thiserror 2.0.11", + "thiserror", ] [[package]] @@ -20510,12 +20251,9 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" -dependencies = [ - "zerocopy", -] +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "predicates" @@ -20533,26 +20271,27 @@ dependencies = [ [[package]] name = "predicates" -version = "3.1.3" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" +checksum = "09963355b9f467184c04017ced4a2ba2d75cbcb4e7462690d388233253d4b1a9" dependencies = [ "anstyle", "difflib", + "itertools 0.10.5", "predicates-core", ] [[package]] name = "predicates-core" -version = "1.0.9" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" +checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" [[package]] name = "predicates-tree" -version = "1.0.12" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" +checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" dependencies = [ "predicates-core", "termtree", @@ -20560,9 +20299,9 @@ dependencies = [ [[package]] name = "pretty_assertions" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" dependencies = [ "diff", "yansi", @@ -20570,12 +20309,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.29" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" +checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62" dependencies = [ - "proc-macro2 1.0.93", - "syn 2.0.96", + "proc-macro2 1.0.86", + "syn 2.0.87", ] [[package]] @@ -20616,31 +20355,31 @@ checksum = "a172e6cc603231f2cf004232eabcecccc0da53ba576ab286ef7baa0cfc7927ad" dependencies = [ "coarsetime", "crossbeam-queue", - "derive_more 0.99.18", + "derive_more 0.99.17", "futures", "futures-timer", "nanorand", - "thiserror 1.0.69", + "thiserror", "tracing", ] [[package]] name = "proc-macro-crate" -version = "1.1.3" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ - "thiserror 1.0.69", - "toml 0.5.11", + "once_cell", + "toml_edit 0.19.15", ] [[package]] name = "proc-macro-crate" -version = "3.2.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ - "toml_edit 0.22.22", + "toml_edit 0.21.0", ] [[package]] @@ -20650,8 +20389,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "syn 1.0.109", "version_check", ] @@ -20662,8 +20401,8 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "version_check", ] @@ -20673,8 +20412,8 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", ] [[package]] @@ -20684,20 +20423,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" dependencies = [ "proc-macro-error-attr2", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + [[package]] name = "proc-macro-warning" -version = "1.0.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "834da187cfe638ae8abb0203f0b33e5ccdb02a28e7199f2f47b3e2754f50edca" +checksum = "9b698b0b09d40e9b7c1a47b132d66a8b54bcd20583d9b6d06e4535e383b4405c" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -20711,9 +20456,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -20724,13 +20469,13 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "731e0d9356b0c25f16f33b5be79b1c57b562f141ebfcdb0ad8ac2c13a24293b4" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.6.0", "chrono", "flate2", "hex", "lazy_static", "procfs-core", - "rustix 0.38.43", + "rustix 0.38.42", ] [[package]] @@ -20739,23 +20484,23 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d3554923a69f4ce04c4a754260c338f505ce22642d3830e049a399fc2059a29" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.6.0", "chrono", "hex", ] [[package]] name = "prometheus" -version = "0.13.4" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d33c28a30771f7f96db69893f78b857f7450d7e0237e9c8fc6427a81bae7ed1" +checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c" dependencies = [ "cfg-if", "fnv", "lazy_static", "memchr", "parking_lot 0.12.3", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -20776,32 +20521,32 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "prometheus-parse" -version = "0.2.5" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "811031bea65e5a401fb2e1f37d802cca6601e204ac463809a3189352d13b78a5" +checksum = "0c2aa5feb83bf4b2c8919eaf563f51dbab41183de73ba2353c0e03cd7b6bd892" dependencies = [ "chrono", - "itertools 0.12.1", + "itertools 0.10.5", "once_cell", "regex", ] [[package]] name = "proptest" -version = "1.5.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" +checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.8.0", + "bitflags 2.6.0", "lazy_static", "num-traits", "rand", @@ -20835,20 +20580,21 @@ dependencies = [ [[package]] name = "prost" -version = "0.13.4" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c0fef6c4230e4ccf618a35c59d7ede15dea37de8427500f50aff708806e42ec" +checksum = "3b2ecbe40f08db5c006b5764a2645f7f3f141ce756412ac9e1dd6087e6d32995" dependencies = [ "bytes", - "prost-derive 0.13.4", + "prost-derive 0.13.2", ] [[package]] name = "prost-build" -version = "0.13.4" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f3e5beed80eb580c68e2c600937ac2c4eedabdfd5ef1e5b7ea4f3fba84497b" +checksum = "f8650aabb6c35b860610e9cff5dc1af886c9e25073b7b1712a68972af4281302" dependencies = [ + "bytes", "heck 0.5.0", "itertools 0.13.0", "log", @@ -20856,10 +20602,10 @@ dependencies = [ "once_cell", "petgraph", "prettyplease", - "prost 0.13.4", + "prost 0.13.2", "prost-types", "regex", - "syn 2.0.96", + "syn 2.0.87", "tempfile", ] @@ -20871,8 +20617,8 @@ checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", "itertools 0.10.5", - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "syn 1.0.109", ] @@ -20884,80 +20630,81 @@ checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", "itertools 0.12.1", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "prost-derive" -version = "0.13.4" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3" +checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" dependencies = [ "anyhow", "itertools 0.13.0", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "prost-types" -version = "0.13.4" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2f1e56baa61e93533aebc21af4d2134b70f66275e0fcdf3cbe43d77ff7e8fc" +checksum = "60caa6738c7369b940c3d49246a8d1749323674c65cb13010134f5c9bad5b519" dependencies = [ - "prost 0.13.4", + "prost 0.13.2", ] [[package]] name = "psm" -version = "0.1.24" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810" +checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" dependencies = [ "cc", ] [[package]] name = "pyroscope" -version = "0.5.8" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3a5f63b0d2727095db59045e6a0ef3259b28b90d481ae88f0e3d866d0234ce8" +checksum = "ac8a53ce01af1087eaeee6ce7c4fbf50ea4040ab1825c0115c4bafa039644ba9" dependencies = [ + "json", "libc", "libflate", "log", "names", "prost 0.11.9", - "reqwest 0.12.12", - "serde_json", - "thiserror 1.0.69", + "reqwest 0.11.27", + "thiserror", "url", "winapi", ] [[package]] name = "pyroscope_pprofrs" -version = "0.2.8" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614a25777053da6bdca9d84a67892490b5a57590248dbdee3d7bf0716252af70" +checksum = "43f010b2a981a7f8449a650f25f309e520b5206ea2d89512dcb146aaa5518ff4" dependencies = [ "log", - "pprof2", + "pprof", "pyroscope", - "thiserror 1.0.69", + "thiserror", ] [[package]] name = "quanta" -version = "0.12.5" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bd1fe6824cea6538803de3ff1bc0cf3949024db3d43c9643024bfb33a807c0e" +checksum = "a17e662a7a8291a865152364c20c7abc5e60486ab2001e8ec10b24862de0b9ab" dependencies = [ "crossbeam-utils", "libc", + "mach2", "once_cell", "raw-cpuid", "wasi", @@ -20998,7 +20745,7 @@ dependencies = [ "asynchronous-codec 0.7.0", "bytes", "quick-protobuf 0.8.1", - "thiserror 1.0.69", + "thiserror", "unsigned-varint 0.8.0", ] @@ -21026,55 +20773,51 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.6" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" +checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" dependencies = [ "bytes", "futures-io", "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.0", - "rustls 0.23.21", - "socket2 0.5.8", - "thiserror 2.0.11", + "rustc-hash 2.0.0", + "rustls 0.23.18", + "socket2 0.5.7", + "thiserror", "tokio", "tracing", ] [[package]] name = "quinn-proto" -version = "0.11.9" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" dependencies = [ "bytes", - "getrandom", "rand", "ring 0.17.8", - "rustc-hash 2.1.0", - "rustls 0.23.21", - "rustls-pki-types", + "rustc-hash 2.0.0", + "rustls 0.23.18", "slab", - "thiserror 2.0.11", + "thiserror", "tinyvec", "tracing", - "web-time", ] [[package]] name = "quinn-udp" -version = "0.5.9" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904" +checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" dependencies = [ - "cfg_aliases 0.2.1", "libc", "once_cell", - "socket2 0.5.8", + "socket2 0.5.7", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -21088,11 +20831,11 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.38" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ - "proc-macro2 1.0.93", + "proc-macro2 1.0.86", ] [[package]] @@ -21168,11 +20911,11 @@ dependencies = [ [[package]] name = "raw-cpuid" -version = "11.3.0" +version = "10.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6928fa44c097620b706542d428957635951bade7143269085389d42c8a4927e" +checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" dependencies = [ - "bitflags 2.8.0", + "bitflags 1.3.2", ] [[package]] @@ -21254,24 +20997,33 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.6.0", ] [[package]] name = "redox_users" -version = "0.4.6" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom", - "libredox", - "thiserror 1.0.69", + "redox_syscall 0.2.16", + "thiserror", ] [[package]] @@ -21280,10 +21032,10 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87413ebb313323d431e85d0afc5a68222aaed972843537cbfe5f061cf1b4bcab" dependencies = [ - "derive_more 0.99.18", + "derive_more 0.99.17", "fs-err", "static_init", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -21301,9 +21053,9 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -21339,7 +21091,7 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", + "regex-automata 0.4.8", "regex-syntax 0.8.5", ] @@ -21354,9 +21106,15 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" + +[[package]] +name = "regex-automata" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -21377,9 +21135,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "relative-path" -version = "1.9.3" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" +checksum = "e898588f33fdd5b9420719948f9f2a32c922a246964576f71ba7f24f80610fbc" [[package]] name = "relay-substrate-client" @@ -21417,7 +21175,7 @@ dependencies = [ "sp-trie 29.0.0", "sp-version 29.0.0", "staging-xcm 7.0.0", - "thiserror 1.0.69", + "thiserror", "tokio", ] @@ -21442,7 +21200,7 @@ dependencies = [ "sp-tracing 16.0.0", "substrate-prometheus-endpoint", "sysinfo", - "thiserror 1.0.69", + "thiserror", "time", "tokio", ] @@ -21451,7 +21209,7 @@ dependencies = [ name = "remote-ext-tests-bags-list" version = "1.0.0" dependencies = [ - "clap 4.5.26", + "clap 4.5.13", "frame-system 28.0.0", "log", "pallet-bags-list-remote-tests", @@ -21474,9 +21232,10 @@ dependencies = [ "futures-core", "futures-util", "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.32", + "http 0.2.9", + "http-body 0.4.5", + "hyper 0.14.29", + "hyper-rustls 0.24.2", "hyper-tls", "ipnet", "js-sys", @@ -21486,38 +21245,41 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile 1.0.4", + "rustls 0.21.7", + "rustls-pemfile 1.0.3", "serde", "serde_json", "serde_urlencoded", "sync_wrapper 0.1.2", - "system-configuration 0.5.1", + "system-configuration", "tokio", "tokio-native-tls", + "tokio-rustls 0.24.1", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots 0.25.2", "winreg", ] [[package]] name = "reqwest" -version = "0.12.12" +version = "0.12.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" dependencies = [ "base64 0.22.1", "bytes", "futures-channel", "futures-core", "futures-util", - "http 1.2.0", - "http-body 1.0.1", + "http 1.1.0", + "http-body 1.0.0", "http-body-util", - "hyper 1.5.2", - "hyper-rustls 0.27.5", + "hyper 1.3.1", + "hyper-rustls 0.27.3", "hyper-util", "ipnet", "js-sys", @@ -21527,22 +21289,21 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.21", - "rustls-pemfile 2.2.0", + "rustls 0.23.18", + "rustls-pemfile 2.0.0", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.2", + "sync_wrapper 1.0.1", "tokio", - "tokio-rustls 0.26.1", - "tower 0.5.2", + "tokio-rustls 0.26.0", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.26.7", + "webpki-roots 0.26.3", "windows-registry", ] @@ -21563,7 +21324,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ "hmac 0.12.1", - "subtle 2.6.1", + "subtle 2.5.0", ] [[package]] @@ -21571,12 +21332,12 @@ name = "ring" version = "0.1.0" source = "git+https://github.com/w3f/ring-proof?rev=665f5f5#665f5f51af5734c7b6d90b985dd6861d4c5b4752" dependencies = [ - "ark-ec 0.4.2", + "ark-ec", "ark-ff 0.4.2", - "ark-poly 0.4.2", + "ark-poly", "ark-serialize 0.4.2", "ark-std 0.4.0", - "arrayvec 0.7.6", + "arrayvec 0.7.4", "blake2 0.10.6", "common", "fflonk", @@ -21854,15 +21615,15 @@ version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1ec6683a2e52fe3be2eaf942a80619abd99eb36e973c5ab4489a2f3b100db5c" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "polkadot-primitives 16.0.0", "polkadot-runtime-common 17.0.0", "smallvec", "sp-core 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-weights 31.0.0", "staging-xcm 14.2.0", - "staging-xcm-builder 17.0.3", + "staging-xcm-builder 17.0.1", ] [[package]] @@ -21900,20 +21661,20 @@ checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" [[package]] name = "rpassword" -version = "7.3.1" +version = "7.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f" +checksum = "6678cf63ab3491898c0d021b493c94c9b221d91295294a2a5746eacbe5928322" dependencies = [ "libc", "rtoolbox", - "windows-sys 0.48.0", + "winapi", ] [[package]] name = "rsa" -version = "0.9.7" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" +checksum = "af6c4b23d99685a1408194da11270ef8e9809aff951cc70ec9b17350b087e474" dependencies = [ "const-oid", "digest 0.10.7", @@ -21925,7 +21686,7 @@ dependencies = [ "rand_core 0.6.4", "signature", "spki", - "subtle 2.6.1", + "subtle 2.5.0", "zeroize", ] @@ -21938,7 +21699,7 @@ dependencies = [ "futures", "futures-timer", "rstest_macros", - "rustc_version 0.4.1", + "rustc_version 0.4.0", ] [[package]] @@ -21949,57 +21710,52 @@ checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" dependencies = [ "cfg-if", "glob", - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "regex", "relative-path", - "rustc_version 0.4.1", - "syn 2.0.96", + "rustc_version 0.4.0", + "syn 2.0.87", "unicode-ident", ] [[package]] name = "rtnetlink" -version = "0.13.1" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a552eb82d19f38c3beed3f786bd23aa434ceb9ac43ab44419ca6d67a7e186c0" +checksum = "322c53fd76a18698f1c27381d58091de3a043d356aa5bd0d510608b565f469a0" dependencies = [ "futures", "log", - "netlink-packet-core", "netlink-packet-route", - "netlink-packet-utils", "netlink-proto", - "netlink-sys", - "nix 0.26.4", - "thiserror 1.0.69", + "nix 0.24.3", + "thiserror", "tokio", ] [[package]] name = "rtoolbox" -version = "0.0.2" +version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e" +checksum = "034e22c514f5c0cb8a10ff341b9b048b5ceb21591f31c8f44c43b960f9b3524a" dependencies = [ "libc", - "windows-sys 0.48.0", + "winapi", ] [[package]] name = "ruint" -version = "1.12.4" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5ef8fb1dd8de3870cb8400d51b4c2023854bbafd5431a3ac7e7317243e22d2f" +checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286" dependencies = [ "alloy-rlp", "ark-ff 0.3.0", "ark-ff 0.4.2", "bytes", - "fastrlp 0.3.1", - "fastrlp 0.4.0", + "fastrlp", "num-bigint", - "num-integer", "num-traits", "parity-scale-codec", "primitive-types 0.12.2", @@ -22020,9 +21776,9 @@ checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustc-hash" @@ -22032,9 +21788,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.1.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" [[package]] name = "rustc-hex" @@ -22062,11 +21818,11 @@ dependencies = [ [[package]] name = "rustc_version" -version = "0.4.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.24", + "semver 1.0.18", ] [[package]] @@ -22080,9 +21836,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.17" +version = "0.36.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "305efbd14fde4139eb501df5f136994bb520b033fa9fbdce287507dc23b8c7ed" +checksum = "c37f1bd5ef1b5422177b7646cba67430579cfe2ace80f284fee876bca52ad941" dependencies = [ "bitflags 1.3.2", "errno", @@ -22094,9 +21850,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.28" +version = "0.37.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" dependencies = [ "bitflags 1.3.2", "errno", @@ -22108,14 +21864,14 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.43" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.6.0", "errno", "libc", - "linux-raw-sys 0.4.15", + "linux-raw-sys 0.4.14", "windows-sys 0.59.0", ] @@ -22132,13 +21888,13 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.12" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" dependencies = [ "log", - "ring 0.17.8", - "rustls-webpki 0.101.7", + "ring 0.16.20", + "rustls-webpki 0.101.4", "sct", ] @@ -22152,22 +21908,22 @@ dependencies = [ "ring 0.17.8", "rustls-pki-types", "rustls-webpki 0.102.8", - "subtle 2.6.1", + "subtle 2.5.0", "zeroize", ] [[package]] name = "rustls" -version = "0.23.21" +version = "0.23.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8" +checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f" dependencies = [ "log", "once_cell", "ring 0.17.8", "rustls-pki-types", "rustls-webpki 0.102.8", - "subtle 2.6.1", + "subtle 2.5.0", "zeroize", ] @@ -22178,98 +21934,97 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", - "rustls-pemfile 1.0.4", + "rustls-pemfile 1.0.3", "schannel", - "security-framework 2.11.1", + "security-framework", ] [[package]] name = "rustls-native-certs" -version = "0.7.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" dependencies = [ "openssl-probe", - "rustls-pemfile 2.2.0", + "rustls-pemfile 2.0.0", "rustls-pki-types", "schannel", - "security-framework 2.11.1", + "security-framework", ] [[package]] name = "rustls-native-certs" -version = "0.8.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" dependencies = [ "openssl-probe", + "rustls-pemfile 2.0.0", "rustls-pki-types", "schannel", - "security-framework 3.2.0", + "security-framework", ] [[package]] name = "rustls-pemfile" -version = "1.0.4" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ "base64 0.21.7", ] [[package]] name = "rustls-pemfile" -version = "2.2.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +checksum = "35e4980fa29e4c4b212ffb3db068a564cbf560e51d3944b7c88bd8bf5bec64f4" dependencies = [ + "base64 0.21.7", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.10.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" -dependencies = [ - "web-time", -] +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" [[package]] name = "rustls-platform-verifier" -version = "0.3.4" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afbb878bdfdf63a336a5e63561b1835e7a8c91524f51621db870169eac84b490" +checksum = "b5f0d26fa1ce3c790f9590868f0109289a044acb954525f933e2aa3b871c157d" dependencies = [ - "core-foundation 0.9.4", + "core-foundation", "core-foundation-sys", "jni", "log", "once_cell", - "rustls 0.23.21", - "rustls-native-certs 0.7.3", + "rustls 0.23.18", + "rustls-native-certs 0.7.0", "rustls-platform-verifier-android", "rustls-webpki 0.102.8", - "security-framework 2.11.1", + "security-framework", "security-framework-sys", - "webpki-roots 0.26.7", + "webpki-roots 0.26.3", "winapi", ] [[package]] name = "rustls-platform-verifier-android" -version = "0.1.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" +checksum = "84e217e7fdc8466b5b35d30f8c0a30febd29173df4a3a0c2115d306b9c4117ad" [[package]] name = "rustls-webpki" -version = "0.101.7" +version = "0.101.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d" dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", + "ring 0.16.20", + "untrusted 0.7.1", ] [[package]] @@ -22285,9 +22040,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.19" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "rusty-fork" @@ -22319,7 +22074,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5174a470eeb535a721ae9fdd6e291c2411a906b96592182d05217591d5c5cf7b" dependencies = [ "byteorder", - "derive_more 0.99.18", + "derive_more 0.99.17", ] [[package]] @@ -22335,9 +22090,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "safe-mix" @@ -22350,9 +22105,9 @@ dependencies = [ [[package]] name = "safe_arch" -version = "0.7.4" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +checksum = "f398075ce1e6a179b46f51bd88d0598b92b00d3551f1a2d4ac49e771b56ac354" dependencies = [ "bytemuck", ] @@ -22382,7 +22137,7 @@ dependencies = [ "log", "sp-core 28.0.0", "sp-wasm-interface 20.0.0", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -22394,7 +22149,7 @@ dependencies = [ "log", "sp-core 33.0.1", "sp-wasm-interface 21.0.1", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -22406,7 +22161,7 @@ dependencies = [ "log", "sp-core 34.0.0", "sp-wasm-interface 21.0.1", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -22419,7 +22174,7 @@ dependencies = [ "ip_network", "linked_hash_set", "log", - "multihash 0.19.3", + "multihash 0.19.1", "parity-scale-codec", "prost 0.12.6", "prost-build", @@ -22437,7 +22192,7 @@ dependencies = [ "sp-tracing 16.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime-client", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -22486,10 +22241,10 @@ name = "sc-chain-spec" version = "28.0.0" dependencies = [ "array-bytes", - "clap 4.5.26", + "clap 4.5.13", "docify", "log", - "memmap2 0.9.5", + "memmap2 0.9.3", "parity-scale-codec", "regex", "sc-chain-spec-derive", @@ -22517,10 +22272,10 @@ dependencies = [ name = "sc-chain-spec-derive" version = "11.0.0" dependencies = [ - "proc-macro-crate 3.2.0", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro-crate 3.1.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -22529,7 +22284,7 @@ version = "0.36.0" dependencies = [ "array-bytes", "chrono", - "clap 4.5.26", + "clap 4.5.13", "fdlimit", "futures", "futures-timer", @@ -22563,7 +22318,7 @@ dependencies = [ "sp-tracing 16.0.0", "sp-version 29.0.0", "tempfile", - "thiserror 1.0.69", + "thiserror", "tokio", ] @@ -22593,7 +22348,7 @@ dependencies = [ "sp-trie 29.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -22650,7 +22405,7 @@ dependencies = [ "sp-state-machine 0.35.0", "sp-test-primitives", "substrate-prometheus-endpoint", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -22687,7 +22442,7 @@ dependencies = [ "substrate-prometheus-endpoint", "substrate-test-runtime-client", "tempfile", - "thiserror 1.0.69", + "thiserror", "tokio", ] @@ -22729,7 +22484,7 @@ dependencies = [ "sp-tracing 16.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime-client", - "thiserror 1.0.69", + "thiserror", "tokio", ] @@ -22757,7 +22512,7 @@ dependencies = [ "sp-keystore 0.34.0", "sp-runtime 31.0.1", "substrate-test-runtime-client", - "thiserror 1.0.69", + "thiserror", "tokio", ] @@ -22800,7 +22555,7 @@ dependencies = [ "substrate-prometheus-endpoint", "substrate-test-runtime-client", "tempfile", - "thiserror 1.0.69", + "thiserror", "tokio", "wasm-timer", ] @@ -22823,7 +22578,7 @@ dependencies = [ "sp-core 28.0.0", "sp-runtime 31.0.1", "substrate-test-runtime-client", - "thiserror 1.0.69", + "thiserror", "tokio", ] @@ -22885,7 +22640,7 @@ dependencies = [ "sp-tracing 16.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime-client", - "thiserror 1.0.69", + "thiserror", "tokio", ] @@ -22909,7 +22664,7 @@ dependencies = [ "sp-keyring 31.0.0", "sp-runtime 31.0.1", "substrate-test-runtime-client", - "thiserror 1.0.69", + "thiserror", "tokio", ] @@ -22947,7 +22702,7 @@ dependencies = [ "substrate-prometheus-endpoint", "substrate-test-runtime-client", "substrate-test-runtime-transaction-pool", - "thiserror 1.0.69", + "thiserror", "tokio", ] @@ -22972,7 +22727,7 @@ dependencies = [ "sp-inherents 26.0.0", "sp-runtime 31.0.1", "substrate-prometheus-endpoint", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -23033,7 +22788,7 @@ dependencies = [ "substrate-test-runtime", "tempfile", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.18", "wat", ] @@ -23053,7 +22808,7 @@ dependencies = [ "sp-core 33.0.1", "sp-externalities 0.28.0", "sp-io 36.0.0", - "sp-panic-handler 13.0.1", + "sp-panic-handler 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-runtime-interface 27.0.0", "sp-trie 35.0.0", "sp-version 35.0.0", @@ -23077,7 +22832,7 @@ dependencies = [ "sp-core 34.0.0", "sp-externalities 0.29.0", "sp-io 38.0.0", - "sp-panic-handler 13.0.1", + "sp-panic-handler 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-runtime-interface 28.0.0", "sp-trie 37.0.0", "sp-version 37.0.0", @@ -23093,7 +22848,7 @@ dependencies = [ "sc-allocator 23.0.0", "sp-maybe-compressed-blob 11.0.0", "sp-wasm-interface 20.0.0", - "thiserror 1.0.69", + "thiserror", "wasm-instrument", ] @@ -23107,7 +22862,7 @@ dependencies = [ "sc-allocator 28.0.0", "sp-maybe-compressed-blob 11.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-wasm-interface 21.0.1", - "thiserror 1.0.69", + "thiserror", "wasm-instrument", ] @@ -23121,7 +22876,7 @@ dependencies = [ "sc-allocator 29.0.0", "sp-maybe-compressed-blob 11.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-wasm-interface 21.0.1", - "thiserror 1.0.69", + "thiserror", "wasm-instrument", ] @@ -23171,7 +22926,7 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", "paste", - "rustix 0.36.17", + "rustix 0.36.15", "sc-allocator 23.0.0", "sc-executor-common 0.29.0", "sc-runtime-test", @@ -23194,7 +22949,7 @@ dependencies = [ "libc", "log", "parking_lot 0.12.3", - "rustix 0.36.17", + "rustix 0.36.15", "sc-allocator 28.0.0", "sc-executor-common 0.34.0", "sp-runtime-interface 27.0.0", @@ -23213,7 +22968,7 @@ dependencies = [ "libc", "log", "parking_lot 0.12.3", - "rustix 0.36.17", + "rustix 0.36.15", "sc-allocator 29.0.0", "sc-executor-common 0.35.0", "sp-runtime-interface 28.0.0", @@ -23248,7 +23003,7 @@ dependencies = [ "sp-core 28.0.0", "sp-keystore 0.34.0", "tempfile", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -23256,14 +23011,14 @@ name = "sc-mixnet" version = "0.4.0" dependencies = [ "array-bytes", - "arrayvec 0.7.6", + "arrayvec 0.7.4", "blake2 0.10.6", "bytes", "futures", "futures-timer", "log", "mixnet", - "multiaddr 0.18.2", + "multiaddr 0.18.1", "parity-scale-codec", "parking_lot 0.12.3", "sc-client-api", @@ -23276,7 +23031,7 @@ dependencies = [ "sp-keystore 0.34.0", "sp-mixnet 0.4.0", "sp-runtime 31.0.1", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -23334,7 +23089,7 @@ dependencies = [ "substrate-test-runtime", "substrate-test-runtime-client", "tempfile", - "thiserror 1.0.69", + "thiserror", "tokio", "tokio-stream", "tokio-test", @@ -23403,7 +23158,7 @@ dependencies = [ "sp-blockchain", "sp-core 28.0.0", "sp-runtime 31.0.1", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -23460,7 +23215,7 @@ dependencies = [ "sp-tracing 16.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime-client", - "thiserror 1.0.69", + "thiserror", "tokio", "tokio-stream", ] @@ -23525,11 +23280,11 @@ dependencies = [ "libp2p-kad", "litep2p", "log", - "multiaddr 0.18.2", - "multihash 0.19.3", + "multiaddr 0.18.1", + "multihash 0.19.1", "quickcheck", "rand", - "thiserror 1.0.69", + "thiserror", "zeroize", ] @@ -23544,8 +23299,8 @@ dependencies = [ "futures", "futures-timer", "http-body-util", - "hyper 1.5.2", - "hyper-rustls 0.27.5", + "hyper 1.3.1", + "hyper-rustls 0.27.3", "hyper-util", "log", "num_cpus", @@ -23553,7 +23308,7 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", "rand", - "rustls 0.23.21", + "rustls 0.23.18", "sc-block-builder", "sc-client-api", "sc-client-db", @@ -23641,7 +23396,7 @@ dependencies = [ "sp-rpc", "sp-runtime 31.0.1", "sp-version 29.0.0", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -23652,9 +23407,9 @@ dependencies = [ "forwarded-header-value", "futures", "governor", - "http 1.2.0", + "http 1.1.0", "http-body-util", - "hyper 1.5.2", + "hyper 1.3.1", "ip_network", "jsonrpsee", "log", @@ -23663,7 +23418,7 @@ dependencies = [ "serde_json", "substrate-prometheus-endpoint", "tokio", - "tower 0.4.13", + "tower", "tower-http 0.5.2", ] @@ -23707,7 +23462,7 @@ dependencies = [ "substrate-test-runtime", "substrate-test-runtime-client", "substrate-test-runtime-transaction-pool", - "thiserror 1.0.69", + "thiserror", "tokio", "tokio-stream", ] @@ -23739,7 +23494,7 @@ dependencies = [ "sp-version 29.0.0", "sp-wasm-interface 20.0.0", "subxt", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -23801,7 +23556,7 @@ dependencies = [ "substrate-test-runtime", "substrate-test-runtime-client", "tempfile", - "thiserror 1.0.69", + "thiserror", "tokio", "tracing", "tracing-futures", @@ -23877,11 +23632,11 @@ dependencies = [ name = "sc-storage-monitor" version = "0.16.0" dependencies = [ - "clap 4.5.26", + "clap 4.5.13", "fs4", "log", "sp-core 28.0.0", - "thiserror 1.0.69", + "thiserror", "tokio", ] @@ -23900,14 +23655,14 @@ dependencies = [ "serde_json", "sp-blockchain", "sp-runtime 31.0.1", - "thiserror 1.0.69", + "thiserror", ] [[package]] name = "sc-sysinfo" version = "27.0.0" dependencies = [ - "derive_more 0.99.18", + "derive_more 0.99.17", "futures", "libc", "log", @@ -23938,7 +23693,7 @@ dependencies = [ "sc-utils", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror", "wasm-timer", ] @@ -23965,20 +23720,20 @@ dependencies = [ "sp-rpc", "sp-runtime 31.0.1", "sp-tracing 16.0.0", - "thiserror 1.0.69", + "thiserror", "tracing", - "tracing-log", - "tracing-subscriber", + "tracing-log 0.2.0", + "tracing-subscriber 0.3.18", ] [[package]] name = "sc-tracing-proc-macro" version = "11.0.0" dependencies = [ - "proc-macro-crate 3.2.0", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro-crate 3.1.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -24014,7 +23769,7 @@ dependencies = [ "substrate-test-runtime", "substrate-test-runtime-client", "substrate-test-runtime-transaction-pool", - "thiserror 1.0.69", + "thiserror", "tokio", "tokio-stream", ] @@ -24032,7 +23787,7 @@ dependencies = [ "sp-blockchain", "sp-core 28.0.0", "sp-runtime 31.0.1", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -24067,7 +23822,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e98f3262c250d90e700bb802eb704e1f841e03331c2eb815e46516c4edbf5b27" dependencies = [ - "derive_more 0.99.18", + "derive_more 0.99.17", "parity-scale-codec", "scale-bits", "scale-type-resolver", @@ -24096,9 +23851,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ed9401effa946b493f9f84dc03714cca98119b230497df6f3df6b84a2b03648" dependencies = [ "darling", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -24123,10 +23878,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "102fbc6236de6c53906c0b262f12c7aa69c2bdc604862c12728f5f4d370bc137" dependencies = [ "darling", - "proc-macro-crate 3.2.0", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro-crate 3.1.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -24149,10 +23904,10 @@ version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6630024bf739e2179b91fb424b28898baf819414262c5d376677dbff1fe7ebf" dependencies = [ - "proc-macro-crate 3.2.0", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro-crate 3.1.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -24171,11 +23926,11 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc4c70c7fea2eef1740f0081d3fe385d8bee1eef11e9272d3bec7dc8e5438e0" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "scale-info", - "syn 2.0.96", - "thiserror 1.0.69", + "syn 2.0.87", + "thiserror", ] [[package]] @@ -24200,18 +23955,18 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.27" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] name = "schemars" -version = "0.8.21" +version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +checksum = "763f8cd0d4c71ed8389c90cb8100cba87e763bd01a8e614d4f0af97bcd50a161" dependencies = [ "dyn-clone", "schemars_derive", @@ -24221,21 +23976,21 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.21" +version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +checksum = "ec0f696e21e10fa546b7ffb1c9672c6de8fbc7a81acf59524386d8639bf12737" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "serde_derive_internals", - "syn 2.0.96", + "syn 1.0.109", ] [[package]] name = "schnellru" -version = "0.2.4" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "356285bbf17bea63d9e52e96bd18f039672ac92b55b8cb997d6162a2a37d1649" +checksum = "c9a8ef13a93c54d20580de1e5c413e624e53121d42fc7e2c11d10ef7f8b02367" dependencies = [ "ahash 0.8.11", "cfg-if", @@ -24249,7 +24004,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "844b7645371e6ecdf61ff246ba1958c29e802881a749ae3fb1993675d210d28d" dependencies = [ "arrayref", - "arrayvec 0.7.6", + "arrayvec 0.7.4", "curve25519-dalek-ng", "merlin", "rand_core 0.6.4", @@ -24266,17 +24021,23 @@ checksum = "8de18f6d8ba0aad7045f5feae07ec29899c1112584a38509a84ad7b04451eaa0" dependencies = [ "aead", "arrayref", - "arrayvec 0.7.6", + "arrayvec 0.7.4", "curve25519-dalek 4.1.3", "getrandom_or_panic", "merlin", "rand_core 0.6.4", "serde_bytes", "sha2 0.10.8", - "subtle 2.6.1", + "subtle 2.5.0", "zeroize", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -24303,12 +24064,12 @@ dependencies = [ [[package]] name = "sct" -version = "0.7.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", + "ring 0.16.20", + "untrusted 0.7.1", ] [[package]] @@ -24322,7 +24083,7 @@ dependencies = [ "generic-array 0.14.7", "pkcs8", "serdect", - "subtle 2.6.1", + "subtle 2.5.0", "zeroize", ] @@ -24394,36 +24155,23 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.11.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 2.8.0", - "core-foundation 0.9.4", + "bitflags 2.6.0", + "core-foundation", "core-foundation-sys", "libc", "num-bigint", "security-framework-sys", ] -[[package]] -name = "security-framework" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" -dependencies = [ - "bitflags 2.8.0", - "core-foundation 0.10.0", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - [[package]] name = "security-framework-sys" -version = "2.14.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" dependencies = [ "core-foundation-sys", "libc", @@ -24453,14 +24201,14 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" dependencies = [ - "semver-parser 0.10.3", + "semver-parser 0.10.2", ] [[package]] name = "semver" -version = "1.0.24" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" dependencies = [ "serde", ] @@ -24473,9 +24221,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "semver-parser" -version = "0.10.3" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" dependencies = [ "pest", ] @@ -24494,9 +24242,9 @@ checksum = "f97841a747eef040fcd2e7b3b9a220a7205926e60488e673d9e4926d27772ce5" [[package]] name = "serde" -version = "1.0.217" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" dependencies = [ "serde_derive", ] @@ -24522,33 +24270,33 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.15" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "serde_derive_internals" -version = "0.29.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 1.0.109", ] [[package]] @@ -24562,9 +24310,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.135" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "indexmap 2.7.0", "itoa", @@ -24575,9 +24323,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.8" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" dependencies = [ "serde", ] @@ -24627,7 +24375,7 @@ dependencies = [ "cfg-if", "cpufeatures", "digest 0.9.0", - "opaque-debug 0.3.1", + "opaque-debug 0.3.0", ] [[package]] @@ -24651,7 +24399,7 @@ dependencies = [ "cfg-if", "cpufeatures", "digest 0.9.0", - "opaque-debug 0.3.1", + "opaque-debug 0.3.0", ] [[package]] @@ -24674,7 +24422,7 @@ dependencies = [ "block-buffer 0.9.0", "digest 0.9.0", "keccak", - "opaque-debug 0.3.1", + "opaque-debug 0.3.0", ] [[package]] @@ -24699,9 +24447,9 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.7" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" dependencies = [ "lazy_static", ] @@ -24712,20 +24460,30 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] [[package]] name = "signature" -version = "2.2.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" dependencies = [ "digest 0.10.7", "rand_core 0.6.4", @@ -24733,9 +24491,9 @@ dependencies = [ [[package]] name = "simba" -version = "0.9.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3a386a501cd104797982c15ae17aafe8b9261315b5d07e3ec803f2ea26be0fa" +checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae" dependencies = [ "approx", "num-complex", @@ -24750,7 +24508,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c80e565e7dcc4f1ef247e2f395550d4cf7d777746d5988e7e4e3156b71077fc" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.6.0", ] [[package]] @@ -24810,14 +24568,14 @@ dependencies = [ "enumn", "parity-scale-codec", "paste", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] name = "slotmap" -version = "1.0.7" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" dependencies = [ "version_check", ] @@ -24853,8 +24611,8 @@ dependencies = [ "async-fs 1.6.0", "async-io 1.13.0", "async-lock 2.8.0", - "async-net 1.8.0", - "async-process 1.8.1", + "async-net 1.7.0", + "async-process 1.7.0", "blocking", "futures-lite 1.13.0", ] @@ -24865,15 +24623,24 @@ version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f" dependencies = [ - "async-channel 2.3.1", + "async-channel 2.3.0", "async-executor", "async-fs 2.1.2", - "async-io 2.4.0", + "async-io 2.3.3", "async-lock 3.4.0", "async-net 2.0.0", "async-process 2.3.0", "blocking", - "futures-lite 2.6.0", + "futures-lite 2.3.0", +] + +[[package]] +name = "smol_str" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74212e6bbe9a4352329b2f68ba3130c15a3f26fe88ff22dbdc6cdd58fa85e99c" +dependencies = [ + "serde", ] [[package]] @@ -24882,7 +24649,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0bb30cf57b7b5f6109ce17c3164445e2d6f270af2cb48f6e4d31c2967c9a9f5" dependencies = [ - "arrayvec 0.7.6", + "arrayvec 0.7.4", "async-lock 2.8.0", "atomic-take", "base64 0.21.7", @@ -24891,7 +24658,7 @@ dependencies = [ "bs58", "chacha20", "crossbeam-queue", - "derive_more 0.99.18", + "derive_more 0.99.17", "ed25519-zebra 4.0.3", "either", "event-listener 2.5.3", @@ -24936,7 +24703,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "966e72d77a3b2171bb7461d0cb91f43670c63558c62d7cf42809cae6c8b6b818" dependencies = [ - "arrayvec 0.7.6", + "arrayvec 0.7.4", "async-lock 3.4.0", "atomic-take", "base64 0.22.1", @@ -24945,12 +24712,12 @@ dependencies = [ "bs58", "chacha20", "crossbeam-queue", - "derive_more 0.99.18", + "derive_more 0.99.17", "ed25519-zebra 4.0.3", "either", - "event-listener 5.4.0", + "event-listener 5.3.1", "fnv", - "futures-lite 2.6.0", + "futures-lite 2.3.0", "futures-util", "hashbrown 0.14.5", "hex", @@ -24977,7 +24744,7 @@ dependencies = [ "siphasher 1.0.1", "slab", "smallvec", - "soketto 0.8.1", + "soketto 0.8.0", "twox-hash", "wasmi 0.32.3", "x25519-dalek", @@ -24994,7 +24761,7 @@ dependencies = [ "async-lock 2.8.0", "base64 0.21.7", "blake2-rfc", - "derive_more 0.99.18", + "derive_more 0.99.17", "either", "event-listener 2.5.3", "fnv", @@ -25005,7 +24772,7 @@ dependencies = [ "hex", "itertools 0.11.0", "log", - "lru 0.11.1", + "lru 0.11.0", "no-std-net", "parking_lot 0.12.3", "pin-project", @@ -25026,23 +24793,23 @@ version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a33b06891f687909632ce6a4e3fd7677b24df930365af3d0bcb078310129f3f" dependencies = [ - "async-channel 2.3.1", + "async-channel 2.3.0", "async-lock 3.4.0", "base64 0.22.1", "blake2-rfc", "bs58", - "derive_more 0.99.18", + "derive_more 0.99.17", "either", - "event-listener 5.4.0", + "event-listener 5.3.1", "fnv", "futures-channel", - "futures-lite 2.6.0", + "futures-lite 2.3.0", "futures-util", "hashbrown 0.14.5", "hex", "itertools 0.13.0", "log", - "lru 0.12.5", + "lru 0.12.3", "parking_lot 0.12.3", "pin-project", "rand", @@ -25058,9 +24825,9 @@ dependencies = [ [[package]] name = "snap" -version = "1.1.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" +checksum = "5e9f0ab6ef7eb7353d9119c170a436d1bf248eea575ac42d19d12f4e34130831" [[package]] name = "snow" @@ -25074,9 +24841,9 @@ dependencies = [ "curve25519-dalek 4.1.3", "rand_core 0.6.4", "ring 0.17.8", - "rustc_version 0.4.1", + "rustc_version 0.4.0", "sha2 0.10.8", - "subtle 2.6.1", + "subtle 2.5.0", ] [[package]] @@ -25118,7 +24885,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10bd720997e558beb556d354238fa90781deb38241cf31c1b6368738ef21c279" dependencies = [ "byte-slice-cast", - "frame-support 38.2.0", + "frame-support 38.0.0", "hex", "parity-scale-codec", "rlp 0.5.2", @@ -25128,7 +24895,7 @@ dependencies = [ "snowbridge-milagro-bls", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "ssz_rs", "ssz_rs_derive", @@ -25165,7 +24932,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6be61e4db95d1e253a1d5e722953b2d2f6605e5f9761f0a919e5d3fbdbff9da9" dependencies = [ "ethabi-decode 1.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "hex-literal", "parity-scale-codec", @@ -25176,10 +24943,10 @@ dependencies = [ "sp-arithmetic 26.0.0", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "staging-xcm 14.2.0", - "staging-xcm-builder 17.0.3", + "staging-xcm-builder 17.0.1", ] [[package]] @@ -25221,7 +24988,7 @@ dependencies = [ "serde", "serde-big-array", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -25264,7 +25031,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-core 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -25285,7 +25052,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38d27b8d9cb8022637a5ce4f52692520fa75874f393e04ef5cd75bd8795087f6" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "parity-scale-codec", "snowbridge-core 0.10.0", "snowbridge-outbound-queue-merkle-tree 0.9.1", @@ -25327,7 +25094,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d53d32d8470c643f9f8c1f508e1e34263f76297e4c9150e10e8f2e0b63992e1" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-timestamp 37.0.0", @@ -25340,7 +25107,7 @@ dependencies = [ "snowbridge-pallet-ethereum-client-fixtures 0.18.0", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "static_assertions", ] @@ -25406,7 +25173,7 @@ dependencies = [ "alloy-primitives 0.4.2", "alloy-sol-types 0.4.2", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "pallet-balances 39.0.0", @@ -25419,7 +25186,7 @@ dependencies = [ "snowbridge-router-primitives 0.16.0", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "staging-xcm 14.2.0", "staging-xcm-executor 17.0.0", @@ -25481,7 +25248,7 @@ dependencies = [ "bridge-hub-common 0.10.0", "ethabi-decode 1.0.0", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "scale-info", @@ -25491,7 +25258,7 @@ dependencies = [ "sp-arithmetic 26.0.0", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -25528,7 +25295,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "674db59b3c8013382e5c07243ad9439b64d81d2e8b3c4f08d752b55aa5de697e" dependencies = [ "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "log", "parity-scale-codec", @@ -25536,7 +25303,7 @@ dependencies = [ "snowbridge-core 0.10.0", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "staging-xcm 14.2.0", "staging-xcm-executor 17.0.0", @@ -25566,7 +25333,7 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "025f1e6805753821b1db539369f1fb183fd59fd5df7023f7633a4c0cfd3e62f9" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "hex-literal", "log", "parity-scale-codec", @@ -25574,7 +25341,7 @@ dependencies = [ "snowbridge-core 0.10.0", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "staging-xcm 14.2.0", "staging-xcm-executor 17.0.0", @@ -25601,14 +25368,14 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6093f0e73d6cfdd2eea8712155d1d75b5063fc9b1d854d2665b097b4bb29570d" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "log", "parity-scale-codec", "snowbridge-core 0.10.0", "sp-arithmetic 26.0.0", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "staging-xcm 14.2.0", - "staging-xcm-builder 17.0.3", + "staging-xcm-builder 17.0.1", "staging-xcm-executor 17.0.0", ] @@ -25649,15 +25416,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "893480d6cde2489051c65efb5d27fa87efe047b3b61216d8e27bb2f0509b7faf" dependencies = [ "cumulus-pallet-parachain-system 0.17.1", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "pallet-balances 39.0.0", "pallet-collator-selection 19.0.0", - "pallet-message-queue 41.0.2", + "pallet-message-queue 41.0.1", "pallet-session 38.0.0", "pallet-timestamp 37.0.0", "pallet-utility 38.0.0", - "pallet-xcm 17.0.1", + "pallet-xcm 17.0.0", "parachains-runtimes-test-utils 17.0.0", "parity-scale-codec", "snowbridge-core 0.10.0", @@ -25668,7 +25435,7 @@ dependencies = [ "sp-core 34.0.0", "sp-io 38.0.0", "sp-keyring 39.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "staging-parachain-info 0.17.0", "staging-xcm 14.2.0", "staging-xcm-executor 17.0.0", @@ -25700,9 +25467,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.10" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", @@ -25710,9 +25477,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.8" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -25735,14 +25502,14 @@ dependencies = [ [[package]] name = "soketto" -version = "0.8.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e859df029d160cb88608f5d7df7fb4753fd20fdfb4de5644f3d8b8440841721" +checksum = "37468c595637c10857701c990f93a40ce0e357cedb0953d1c26c8d8027f9bb53" dependencies = [ "base64 0.22.1", "bytes", "futures", - "http 1.2.0", + "http 1.1.0", "httparse", "log", "rand", @@ -25753,7 +25520,7 @@ dependencies = [ name = "solochain-template-node" version = "0.0.0" dependencies = [ - "clap 4.5.26", + "clap 4.5.13", "frame-benchmarking-cli", "frame-metadata-hash-extension 0.1.0", "frame-system 28.0.0", @@ -25851,7 +25618,7 @@ dependencies = [ "sp-test-primitives", "sp-trie 29.0.0", "sp-version 29.0.0", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -25874,7 +25641,7 @@ dependencies = [ "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-trie 35.0.0", "sp-version 35.0.0", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -25892,12 +25659,12 @@ dependencies = [ "sp-core 34.0.0", "sp-externalities 0.29.0", "sp-metadata-ir 0.7.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-runtime-interface 28.0.0", "sp-state-machine 0.43.0", "sp-trie 37.0.0", "sp-version 37.0.0", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -25908,10 +25675,10 @@ dependencies = [ "assert_matches", "blake2 0.10.6", "expander", - "proc-macro-crate 3.2.0", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro-crate 3.1.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -25923,10 +25690,10 @@ dependencies = [ "Inflector", "blake2 0.10.6", "expander", - "proc-macro-crate 3.2.0", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro-crate 3.1.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -25938,10 +25705,10 @@ dependencies = [ "Inflector", "blake2 0.10.6", "expander", - "proc-macro-crate 3.2.0", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro-crate 3.1.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -26081,7 +25848,7 @@ version = "0.4.2" source = "git+https://github.com/paritytech/arkworks-substrate#caa2eed74beb885dd07c7db5f916f2281dad818f" dependencies = [ "ark-bls12-381-ext", - "sp-crypto-ec-utils 0.10.0 (git+https://github.com/paritytech/polkadot-sdk)", + "sp-crypto-ec-utils 0.4.1", ] [[package]] @@ -26090,7 +25857,7 @@ version = "0.4.2" source = "git+https://github.com/paritytech/arkworks-substrate#caa2eed74beb885dd07c7db5f916f2281dad818f" dependencies = [ "ark-ed-on-bls12-381-bandersnatch-ext", - "sp-crypto-ec-utils 0.10.0 (git+https://github.com/paritytech/polkadot-sdk)", + "sp-crypto-ec-utils 0.4.1", ] [[package]] @@ -26114,7 +25881,7 @@ dependencies = [ "scale-info", "sp-api 34.0.0", "sp-application-crypto 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -26134,7 +25901,7 @@ checksum = "74738809461e3d4bd707b5b94e0e0c064a623a74a6a8fe5c98514417a02858dd" dependencies = [ "sp-api 34.0.0", "sp-inherents 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -26151,7 +25918,7 @@ dependencies = [ "sp-database", "sp-runtime 31.0.1", "sp-state-machine 0.35.0", - "thiserror 1.0.69", + "thiserror", "tracing", ] @@ -26167,7 +25934,7 @@ dependencies = [ "sp-runtime 31.0.1", "sp-state-machine 0.35.0", "sp-test-primitives", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -26198,7 +25965,7 @@ dependencies = [ "sp-application-crypto 38.0.0", "sp-consensus-slots 0.40.1", "sp-inherents 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-timestamp 34.0.0", ] @@ -26234,7 +26001,7 @@ dependencies = [ "sp-consensus-slots 0.40.1", "sp-core 34.0.0", "sp-inherents 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-timestamp 34.0.0", ] @@ -26276,7 +26043,7 @@ dependencies = [ "sp-io 38.0.0", "sp-keystore 0.40.0", "sp-mmr-primitives 34.1.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-weights 31.0.0", "strum 0.26.3", ] @@ -26312,7 +26079,7 @@ dependencies = [ "sp-application-crypto 38.0.0", "sp-core 34.0.0", "sp-keystore 0.40.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -26334,7 +26101,7 @@ dependencies = [ "parity-scale-codec", "sp-api 34.0.0", "sp-core 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -26416,7 +26183,7 @@ dependencies = [ "sp-storage 19.0.0", "ss58-registry", "substrate-bip39 0.4.7", - "thiserror 1.0.69", + "thiserror", "tracing", "w3f-bls", "zeroize", @@ -26463,7 +26230,7 @@ dependencies = [ "sp-storage 21.0.0", "ss58-registry", "substrate-bip39 0.6.0", - "thiserror 1.0.69", + "thiserror", "tracing", "w3f-bls", "zeroize", @@ -26510,7 +26277,7 @@ dependencies = [ "sp-storage 21.0.0", "ss58-registry", "substrate-bip39 0.6.0", - "thiserror 1.0.69", + "thiserror", "tracing", "w3f-bls", "zeroize", @@ -26557,7 +26324,7 @@ dependencies = [ "sp-storage 21.0.0", "ss58-registry", "substrate-bip39 0.6.0", - "thiserror 1.0.69", + "thiserror", "tracing", "w3f-bls", "zeroize", @@ -26597,7 +26364,8 @@ dependencies = [ [[package]] name = "sp-crypto-ec-utils" -version = "0.10.0" +version = "0.4.1" +source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" dependencies = [ "ark-bls12-377", "ark-bls12-377-ext", @@ -26605,19 +26373,19 @@ dependencies = [ "ark-bls12-381-ext", "ark-bw6-761", "ark-bw6-761-ext", - "ark-ec 0.4.2", + "ark-ec", "ark-ed-on-bls12-377", "ark-ed-on-bls12-377-ext", "ark-ed-on-bls12-381-bandersnatch", "ark-ed-on-bls12-381-bandersnatch-ext", - "ark-scale", - "sp-runtime-interface 24.0.0", + "ark-scale 0.0.11", + "sp-runtime-interface 17.0.0", + "sp-std 8.0.0", ] [[package]] name = "sp-crypto-ec-utils" version = "0.10.0" -source = "git+https://github.com/paritytech/polkadot-sdk#f798111afc15f464a772cd7ed37910cc6208b713" dependencies = [ "ark-bls12-377", "ark-bls12-377-ext", @@ -26625,13 +26393,13 @@ dependencies = [ "ark-bls12-381-ext", "ark-bw6-761", "ark-bw6-761-ext", - "ark-ec 0.4.2", + "ark-ec", "ark-ed-on-bls12-377", "ark-ed-on-bls12-377-ext", "ark-ed-on-bls12-381-bandersnatch", "ark-ed-on-bls12-381-bandersnatch-ext", - "ark-scale", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk)", + "ark-scale 0.0.12", + "sp-runtime-interface 24.0.0", ] [[package]] @@ -26646,12 +26414,12 @@ dependencies = [ "ark-bls12-381-ext", "ark-bw6-761", "ark-bw6-761-ext", - "ark-ec 0.4.2", + "ark-ec", "ark-ed-on-bls12-377", "ark-ed-on-bls12-377-ext", "ark-ed-on-bls12-381-bandersnatch", "ark-ed-on-bls12-381-bandersnatch-ext", - "ark-scale", + "ark-scale 0.0.12", "sp-runtime-interface 28.0.0", ] @@ -26687,9 +26455,9 @@ dependencies = [ name = "sp-crypto-hashing-proc-macro" version = "0.1.0" dependencies = [ - "quote 1.0.38", + "quote 1.0.37", "sp-crypto-hashing 0.1.0", - "syn 2.0.96", + "syn 2.0.87", ] [[package]] @@ -26698,9 +26466,9 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b85d0f1f1e44bd8617eb2a48203ee854981229e3e79e6f468c7175d5fd37489b" dependencies = [ - "quote 1.0.38", + "quote 1.0.37", "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 2.0.96", + "syn 2.0.87", ] [[package]] @@ -26713,51 +26481,52 @@ dependencies = [ [[package]] name = "sp-debug-derive" -version = "14.0.0" +version = "8.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "sp-debug-derive" version = "14.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d09fa0a5f7299fb81ee25ae3853d26200f7a348148aed6de76be905c007dbe" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "sp-debug-derive" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#f798111afc15f464a772cd7ed37910cc6208b713" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d09fa0a5f7299fb81ee25ae3853d26200f7a348148aed6de76be905c007dbe" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "sp-externalities" -version = "0.25.0" +version = "0.19.0" +source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" dependencies = [ "environmental", "parity-scale-codec", - "sp-storage 19.0.0", + "sp-std 8.0.0", + "sp-storage 13.0.0", ] [[package]] name = "sp-externalities" version = "0.25.0" -source = "git+https://github.com/paritytech/polkadot-sdk#f798111afc15f464a772cd7ed37910cc6208b713" dependencies = [ "environmental", "parity-scale-codec", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk)", + "sp-storage 19.0.0", ] [[package]] @@ -26803,7 +26572,7 @@ dependencies = [ "scale-info", "serde_json", "sp-api 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -26816,7 +26585,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-runtime 31.0.1", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -26829,8 +26598,8 @@ dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.5", - "thiserror 1.0.69", + "sp-runtime 39.0.2", + "thiserror", ] [[package]] @@ -26955,7 +26724,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c0e20624277f578b27f44ecfbe2ebc2e908488511ee2c900c5281599f700ab3" dependencies = [ "sp-core 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "strum 0.26.3", ] @@ -27011,7 +26780,7 @@ dependencies = [ name = "sp-maybe-compressed-blob" version = "11.0.0" dependencies = [ - "thiserror 1.0.69", + "thiserror", "zstd 0.12.4", ] @@ -27021,7 +26790,7 @@ version = "11.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0c768c11afbe698a090386876911da4236af199cd38a5866748df4d8628aeff" dependencies = [ - "thiserror 1.0.69", + "thiserror", "zstd 0.12.4", ] @@ -27081,7 +26850,7 @@ dependencies = [ "sp-core 28.0.0", "sp-debug-derive 14.0.0", "sp-runtime 31.0.1", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -27098,8 +26867,8 @@ dependencies = [ "sp-api 34.0.0", "sp-core 34.0.0", "sp-debug-derive 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sp-runtime 39.0.5", - "thiserror 1.0.69", + "sp-runtime 39.0.2", + "thiserror", ] [[package]] @@ -27127,14 +26896,14 @@ dependencies = [ "serde", "sp-arithmetic 26.0.0", "sp-core 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] name = "sp-npos-elections-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 4.5.26", + "clap 4.5.13", "honggfuzz", "rand", "sp-npos-elections 26.0.0", @@ -27158,7 +26927,7 @@ checksum = "2d9de237d72ecffd07f90826eef18360208b16d8de939d54e61591fac0fcbf99" dependencies = [ "sp-api 34.0.0", "sp-core 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -27171,11 +26940,12 @@ dependencies = [ [[package]] name = "sp-panic-handler" -version = "13.0.1" +version = "13.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81478b3740b357fa0ea10fcdc1ee02ebae7734e50f80342c4743476d9f78eeea" +checksum = "d8f5a17a0a11de029a8b811cb6e8b32ce7e02183cc04a3e965c383246798c416" dependencies = [ "backtrace", + "lazy_static", "regex", ] @@ -27276,9 +27046,9 @@ dependencies = [ [[package]] name = "sp-runtime" -version = "39.0.5" +version = "39.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e00503b83cf48fffe48746b91b9b832d6785d4e2eeb0941558371eac6baac6" +checksum = "658f23be7c79a85581029676a73265c107c5469157e3444c8c640fdbaa8bfed0" dependencies = [ "docify", "either", @@ -27301,6 +27071,24 @@ dependencies = [ "tracing", ] +[[package]] +name = "sp-runtime-interface" +version = "17.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" +dependencies = [ + "bytes", + "impl-trait-for-tuples", + "parity-scale-codec", + "primitive-types 0.12.2", + "sp-externalities 0.19.0", + "sp-runtime-interface-proc-macro 11.0.0", + "sp-std 8.0.0", + "sp-storage 13.0.0", + "sp-tracing 10.0.0", + "sp-wasm-interface 14.0.0", + "static_assertions", +] + [[package]] name = "sp-runtime-interface" version = "24.0.0" @@ -27325,25 +27113,6 @@ dependencies = [ "trybuild", ] -[[package]] -name = "sp-runtime-interface" -version = "24.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#f798111afc15f464a772cd7ed37910cc6208b713" -dependencies = [ - "bytes", - "impl-trait-for-tuples", - "parity-scale-codec", - "polkavm-derive 0.18.0", - "primitive-types 0.13.1", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk)", - "sp-runtime-interface-proc-macro 17.0.0 (git+https://github.com/paritytech/polkadot-sdk)", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk)", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk)", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk)", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk)", - "static_assertions", -] - [[package]] name = "sp-runtime-interface" version = "27.0.0" @@ -27386,27 +27155,26 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" -version = "17.0.0" +version = "11.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" dependencies = [ "Inflector", - "expander", - "proc-macro-crate 3.2.0", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro-crate 1.3.1", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "sp-runtime-interface-proc-macro" version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#f798111afc15f464a772cd7ed37910cc6208b713" dependencies = [ "Inflector", "expander", - "proc-macro-crate 3.2.0", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro-crate 3.1.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -27417,10 +27185,10 @@ checksum = "0195f32c628fee3ce1dfbbf2e7e52a30ea85f3589da9fe62a8b816d70fc06294" dependencies = [ "Inflector", "expander", - "proc-macro-crate 3.2.0", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro-crate 3.1.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -27484,7 +27252,7 @@ dependencies = [ "sp-api 34.0.0", "sp-core 34.0.0", "sp-keystore 0.40.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-staking 36.0.0", ] @@ -27511,7 +27279,7 @@ dependencies = [ "scale-info", "serde", "sp-core 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -27525,7 +27293,7 @@ dependencies = [ "scale-info", "serde", "sp-core 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -27547,7 +27315,7 @@ dependencies = [ "sp-panic-handler 13.0.0", "sp-runtime 31.0.1", "sp-trie 29.0.0", - "thiserror 1.0.69", + "thiserror", "tracing", "trie-db", ] @@ -27566,9 +27334,9 @@ dependencies = [ "smallvec", "sp-core 32.0.0", "sp-externalities 0.28.0", - "sp-panic-handler 13.0.1", + "sp-panic-handler 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-trie 34.0.0", - "thiserror 1.0.69", + "thiserror", "tracing", "trie-db", ] @@ -27587,9 +27355,9 @@ dependencies = [ "smallvec", "sp-core 33.0.1", "sp-externalities 0.28.0", - "sp-panic-handler 13.0.1", + "sp-panic-handler 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-trie 35.0.0", - "thiserror 1.0.69", + "thiserror", "tracing", "trie-db", ] @@ -27608,9 +27376,9 @@ dependencies = [ "smallvec", "sp-core 34.0.0", "sp-externalities 0.29.0", - "sp-panic-handler 13.0.1", + "sp-panic-handler 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-trie 37.0.0", - "thiserror 1.0.69", + "thiserror", "tracing", "trie-db", ] @@ -27634,7 +27402,7 @@ dependencies = [ "sp-externalities 0.25.0", "sp-runtime 31.0.1", "sp-runtime-interface 24.0.0", - "thiserror 1.0.69", + "thiserror", "x25519-dalek", ] @@ -27657,48 +27425,49 @@ dependencies = [ "sp-core 34.0.0", "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-externalities 0.29.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-runtime-interface 28.0.0", - "thiserror 1.0.69", + "thiserror", "x25519-dalek", ] [[package]] name = "sp-std" -version = "14.0.0" +version = "8.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" [[package]] name = "sp-std" version = "14.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f8ee986414b0a9ad741776762f4083cd3a5128449b982a3919c4df36874834" [[package]] name = "sp-std" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#f798111afc15f464a772cd7ed37910cc6208b713" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f8ee986414b0a9ad741776762f4083cd3a5128449b982a3919c4df36874834" [[package]] name = "sp-storage" -version = "19.0.0" +version = "13.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" dependencies = [ - "impl-serde 0.5.0", + "impl-serde 0.4.0", "parity-scale-codec", "ref-cast", "serde", - "sp-debug-derive 14.0.0", + "sp-debug-derive 8.0.0", + "sp-std 8.0.0", ] [[package]] name = "sp-storage" version = "19.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#f798111afc15f464a772cd7ed37910cc6208b713" dependencies = [ "impl-serde 0.5.0", "parity-scale-codec", "ref-cast", "serde", - "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk)", + "sp-debug-derive 14.0.0", ] [[package]] @@ -27734,7 +27503,7 @@ dependencies = [ "parity-scale-codec", "sp-inherents 26.0.0", "sp-runtime 31.0.1", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -27746,29 +27515,30 @@ dependencies = [ "async-trait", "parity-scale-codec", "sp-inherents 34.0.0", - "sp-runtime 39.0.5", - "thiserror 1.0.69", + "sp-runtime 39.0.2", + "thiserror", ] [[package]] name = "sp-tracing" -version = "16.0.0" +version = "10.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" dependencies = [ "parity-scale-codec", + "sp-std 8.0.0", "tracing", "tracing-core", - "tracing-subscriber", + "tracing-subscriber 0.2.25", ] [[package]] name = "sp-tracing" version = "16.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#f798111afc15f464a772cd7ed37910cc6208b713" dependencies = [ "parity-scale-codec", "tracing", "tracing-core", - "tracing-subscriber", + "tracing-subscriber 0.3.18", ] [[package]] @@ -27780,7 +27550,7 @@ dependencies = [ "parity-scale-codec", "tracing", "tracing-core", - "tracing-subscriber", + "tracing-subscriber 0.3.18", ] [[package]] @@ -27798,7 +27568,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc4bf251059485a7dd38fe4afeda8792983511cc47f342ff4695e2dcae6b5247" dependencies = [ "sp-api 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -27825,7 +27595,7 @@ dependencies = [ "scale-info", "sp-core 34.0.0", "sp-inherents 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-trie 37.0.0", ] @@ -27847,7 +27617,7 @@ dependencies = [ "sp-core 28.0.0", "sp-externalities 0.25.0", "sp-runtime 31.0.1", - "thiserror 1.0.69", + "thiserror", "tracing", "trie-bench", "trie-db", @@ -27873,7 +27643,7 @@ dependencies = [ "schnellru", "sp-core 32.0.0", "sp-externalities 0.28.0", - "thiserror 1.0.69", + "thiserror", "tracing", "trie-db", "trie-root", @@ -27897,7 +27667,7 @@ dependencies = [ "schnellru", "sp-core 33.0.1", "sp-externalities 0.28.0", - "thiserror 1.0.69", + "thiserror", "tracing", "trie-db", "trie-root", @@ -27921,7 +27691,7 @@ dependencies = [ "schnellru", "sp-core 34.0.0", "sp-externalities 0.29.0", - "thiserror 1.0.69", + "thiserror", "tracing", "trie-db", "trie-root", @@ -27940,7 +27710,7 @@ dependencies = [ "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-version-proc-macro 13.0.0", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -27958,7 +27728,7 @@ dependencies = [ "sp-runtime 37.0.0", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-version-proc-macro 14.0.0", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -27973,10 +27743,10 @@ dependencies = [ "scale-info", "serde", "sp-crypto-hashing-proc-macro 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-version-proc-macro 14.0.0", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -27985,10 +27755,10 @@ version = "13.0.0" dependencies = [ "parity-scale-codec", "proc-macro-warning", - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "sp-version 29.0.0", - "syn 2.0.96", + "syn 2.0.87", ] [[package]] @@ -27998,31 +27768,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aee8f6730641a65fcf0c8f9b1e448af4b3bb083d08058b47528188bccc7b7a7" dependencies = [ "parity-scale-codec", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "sp-wasm-interface" -version = "20.0.0" +version = "14.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" dependencies = [ "anyhow", "impl-trait-for-tuples", "log", "parity-scale-codec", + "sp-std 8.0.0", "wasmtime", ] [[package]] name = "sp-wasm-interface" version = "20.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#f798111afc15f464a772cd7ed37910cc6208b713" dependencies = [ "anyhow", "impl-trait-for-tuples", "log", "parity-scale-codec", + "wasmtime", ] [[package]] @@ -28094,29 +27866,30 @@ dependencies = [ ] [[package]] -name = "spinning_top" -version = "0.3.0" +name = "spki" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" dependencies = [ - "lock_api", + "base64ct", + "der", ] [[package]] -name = "spki" -version = "0.7.3" +name = "sqlformat" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" dependencies = [ - "base64ct", - "der", + "nom", + "unicode_categories", ] [[package]] name = "sqlx" -version = "0.8.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4410e73b3c0d8442c5f99b425d7a435b5ee0ae4167b3196771dd3f7a01be745f" +checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e" dependencies = [ "sqlx-core", "sqlx-macros", @@ -28127,31 +27900,37 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.8.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a007b6936676aa9ab40207cde35daab0a04b823be8ae004368c0793b96a61e0" +checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e" dependencies = [ + "atoi", + "byteorder", "bytes", "crc", "crossbeam-queue", "either", - "event-listener 5.4.0", + "event-listener 5.3.1", + "futures-channel", "futures-core", "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.15.2", - "hashlink 0.10.0", + "hashbrown 0.14.5", + "hashlink 0.9.1", + "hex", "indexmap 2.7.0", "log", "memchr", "once_cell", + "paste", "percent-encoding", "serde", "serde_json", "sha2 0.10.8", "smallvec", - "thiserror 2.0.11", + "sqlformat", + "thiserror", "tokio", "tokio-stream", "tracing", @@ -28160,30 +27939,30 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.8.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3112e2ad78643fef903618d78cf0aec1cb3134b019730edb039b69eaf531f310" +checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "sqlx-core", "sqlx-macros-core", - "syn 2.0.96", + "syn 2.0.87", ] [[package]] name = "sqlx-macros-core" -version = "0.8.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e9f90acc5ab146a99bf5061a7eb4976b573f560bc898ef3bf8435448dd5e7ad" +checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5" dependencies = [ "dotenvy", "either", "heck 0.5.0", "hex", "once_cell", - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "serde", "serde_json", "sha2 0.10.8", @@ -28191,7 +27970,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.96", + "syn 2.0.87", "tempfile", "tokio", "url", @@ -28199,13 +27978,13 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.8.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4560278f0e00ce64938540546f59f590d60beee33fffbd3b9cd47851e5fff233" +checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.8.0", + "bitflags 2.6.0", "byteorder", "bytes", "crc", @@ -28234,26 +28013,27 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.11", + "thiserror", "tracing", "whoami", ] [[package]] name = "sqlx-postgres" -version = "0.8.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b98a57f363ed6764d5b3a12bfedf62f07aa16e1856a7ddc2a0bb190a959613" +checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.8.0", + "bitflags 2.6.0", "byteorder", "crc", "dotenvy", "etcetera", "futures-channel", "futures-core", + "futures-io", "futures-util", "hex", "hkdf", @@ -28271,16 +28051,16 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.11", + "thiserror", "tracing", "whoami", ] [[package]] name = "sqlx-sqlite" -version = "0.8.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f85ca71d3a5b24e64e1d08dd8fe36c6c95c339a896cc33068148906784620540" +checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680" dependencies = [ "atoi", "flume", @@ -28301,17 +28081,17 @@ dependencies = [ [[package]] name = "ss58-registry" -version = "1.51.0" +version = "1.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19409f13998e55816d1c728395af0b52ec066206341d939e22e7766df9b494b8" +checksum = "5e6915280e2d0db8911e5032a5c275571af6bdded2916abd691a659be25d3439" dependencies = [ "Inflector", "num-format", - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "serde", "serde_json", - "unicode-xid 0.2.6", + "unicode-xid 0.2.4", ] [[package]] @@ -28332,8 +28112,8 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f07d54c4d01a1713eb363b55ba51595da15f6f1211435b71466460da022aa140" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "syn 1.0.109", ] @@ -28347,7 +28127,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" name = "staging-chain-spec-builder" version = "1.6.1" dependencies = [ - "clap 4.5.26", + "clap 4.5.13", "cmd_lib", "docify", "log", @@ -28364,7 +28144,7 @@ version = "3.0.0-dev" dependencies = [ "array-bytes", "assert_cmd", - "clap 4.5.26", + "clap 4.5.13", "clap_complete", "criterion", "futures", @@ -28385,7 +28165,7 @@ dependencies = [ "scale-info", "serde", "serde_json", - "soketto 0.8.1", + "soketto 0.8.0", "sp-keyring 31.0.0", "staging-node-inspect", "substrate-cli-test-utils", @@ -28401,7 +28181,7 @@ dependencies = [ name = "staging-node-inspect" version = "0.12.0" dependencies = [ - "clap 4.5.26", + "clap 4.5.13", "parity-scale-codec", "sc-cli", "sc-client-api", @@ -28411,7 +28191,7 @@ dependencies = [ "sp-io 30.0.0", "sp-runtime 31.0.1", "sp-statement-store 10.0.0", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -28433,11 +28213,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d28266dfddbfff721d70ad2f873380845b569adfab32f257cf97d9cedd894b68" dependencies = [ "cumulus-primitives-core 0.16.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "scale-info", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", ] [[package]] @@ -28482,7 +28262,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-weights 31.0.0", "xcm-procedural 10.1.0", ] @@ -28520,22 +28300,22 @@ dependencies = [ [[package]] name = "staging-xcm-builder" -version = "17.0.3" +version = "17.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f7a92cfaec55a5ed0f9cbbb9076aa8ec0aff1ba90b9804cc5c8f2369fde59c" +checksum = "a3746adbbae27b1e6763f0cca622e15482ebcb94835a9e078c212dd7be896e35" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "impl-trait-for-tuples", "log", "pallet-asset-conversion 20.0.0", - "pallet-transaction-payment 38.0.2", + "pallet-transaction-payment 38.0.0", "parity-scale-codec", "polkadot-parachain-primitives 14.0.0", "scale-info", "sp-arithmetic 26.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-weights 31.0.0", "staging-xcm 14.2.0", "staging-xcm-executor 17.0.0", @@ -28568,14 +28348,14 @@ checksum = "79dd0c5332a5318e58f0300b20768b71cf9427c906f94a743c9dc7c3ee9e7fa9" dependencies = [ "environmental", "frame-benchmarking 38.0.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "impl-trait-for-tuples", "parity-scale-codec", "scale-info", "sp-arithmetic 26.0.0", "sp-core 34.0.0", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-weights 31.0.0", "staging-xcm 14.2.0", "tracing", @@ -28604,14 +28384,14 @@ dependencies = [ [[package]] name = "static_init_macro" -version = "1.0.4" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1389c88ddd739ec6d3f8f83343764a0e944cd23cfbf126a9796a714b0b6edd6f" +checksum = "70a2595fc3aa78f2d0e45dd425b22282dd863273761cc77780914b2cf3003acf" dependencies = [ "cfg_aliases 0.1.1", "memchr", - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "syn 1.0.109", ] @@ -28643,6 +28423,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strsim" version = "0.11.1" @@ -28668,8 +28454,8 @@ checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" dependencies = [ "heck 0.3.3", "proc-macro-error", - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "syn 1.0.109", ] @@ -28682,6 +28468,12 @@ dependencies = [ "strum_macros 0.24.3", ] +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" + [[package]] name = "strum" version = "0.26.3" @@ -28698,12 +28490,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "rustversion", "syn 1.0.109", ] +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck 0.4.1", + "proc-macro2 1.0.86", + "quote 1.0.37", + "rustversion", + "syn 2.0.87", +] + [[package]] name = "strum_macros" version = "0.26.4" @@ -28711,17 +28516,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ "heck 0.5.0", - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "rustversion", - "syn 2.0.96", + "syn 2.0.87", ] [[package]] name = "subkey" version = "9.0.0" dependencies = [ - "clap 4.5.26", + "clap 4.5.13", "sc-cli", ] @@ -28796,7 +28601,7 @@ dependencies = [ "scale-info", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror", "wasm-testbed", ] @@ -28846,11 +28651,11 @@ name = "substrate-prometheus-endpoint" version = "0.17.0" dependencies = [ "http-body-util", - "hyper 1.5.2", + "hyper 1.3.1", "hyper-util", "log", "prometheus", - "thiserror 1.0.69", + "thiserror", "tokio", ] @@ -28894,7 +28699,7 @@ dependencies = [ "sp-trie 29.0.0", "structopt", "strum 0.26.3", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -28924,7 +28729,7 @@ dependencies = [ "sp-io 35.0.0", "sp-runtime 36.0.0", "sp-wasm-interface 21.0.1", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -29052,7 +28857,7 @@ dependencies = [ "sp-blockchain", "sp-runtime 31.0.1", "substrate-test-runtime-client", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -29123,9 +28928,9 @@ checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" [[package]] name = "subtle" -version = "2.6.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "subtle-ng" @@ -29145,14 +28950,14 @@ dependencies = [ "log", "num-format", "rand", - "reqwest 0.12.12", + "reqwest 0.12.9", "scale-info", - "semver 1.0.24", + "semver 1.0.18", "serde", "serde_json", "sp-version 35.0.0", "substrate-differ", - "thiserror 1.0.69", + "thiserror", "url", "uuid", "wasm-loader", @@ -29188,7 +28993,7 @@ dependencies = [ "subxt-lightclient", "subxt-macro", "subxt-metadata", - "thiserror 1.0.69", + "thiserror", "tokio", "tokio-util", "tracing", @@ -29205,13 +29010,13 @@ checksum = "3cfcfb7d9589f3df0ac87c4988661cf3fb370761fcb19f2fd33104cc59daf22a" dependencies = [ "heck 0.5.0", "parity-scale-codec", - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "scale-info", "scale-typegen", "subxt-metadata", - "syn 2.0.96", - "thiserror 1.0.69", + "syn 2.0.87", + "thiserror", ] [[package]] @@ -29254,7 +29059,7 @@ dependencies = [ "serde", "serde_json", "smoldot-light 0.16.2", - "thiserror 1.0.69", + "thiserror", "tokio", "tokio-stream", "tracing", @@ -29269,11 +29074,11 @@ dependencies = [ "darling", "parity-scale-codec", "proc-macro-error2", - "quote 1.0.38", + "quote 1.0.37", "scale-typegen", "subxt-codegen", "subxt-utils-fetchmetadata", - "syn 2.0.96", + "syn 2.0.87", ] [[package]] @@ -29327,20 +29132,20 @@ checksum = "3082b17a86e3c3fe45d858d94d68f6b5247caace193dad6201688f24db8ba9bb" dependencies = [ "hex", "parity-scale-codec", - "thiserror 1.0.69", + "thiserror", ] [[package]] name = "sval" -version = "2.14.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c2f18f53c889ec3dfe1c08b20fd51406d09b14bf18b366416718763ccff05a" +checksum = "8b031320a434d3e9477ccf9b5756d57d4272937b8d22cb88af80b7633a1b78b1" [[package]] name = "sval_buffer" -version = "2.14.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8cb1bb48d0bed828b908e6b99e7ab8c7244994dc27948a2e31d42e8c4d77c1" +checksum = "6bf7e9412af26b342f3f2cc5cc4122b0105e9d16eb76046cd14ed10106cf6028" dependencies = [ "sval", "sval_ref", @@ -29348,18 +29153,18 @@ dependencies = [ [[package]] name = "sval_dynamic" -version = "2.14.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba574872d4ad653071a9db76c49656082db83a37cd5f559874273d36b4a02b9d" +checksum = "a0ef628e8a77a46ed3338db8d1b08af77495123cc229453084e47cd716d403cf" dependencies = [ "sval", ] [[package]] name = "sval_fmt" -version = "2.14.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "944450b2dbbf8aae98537776b399b23d72b19243ee42522cfd110305f3c9ba5a" +checksum = "7dc09e9364c2045ab5fa38f7b04d077b3359d30c4c2b3ec4bae67a358bd64326" dependencies = [ "itoa", "ryu", @@ -29368,65 +29173,55 @@ dependencies = [ [[package]] name = "sval_json" -version = "2.14.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "411bbd543c413796ccfbaa44f6676e20032b6c69e4996cb6c3e6ef30c79b96d1" +checksum = "ada6f627e38cbb8860283649509d87bc4a5771141daa41c78fd31f2b9485888d" dependencies = [ "itoa", "ryu", "sval", ] -[[package]] -name = "sval_nested" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30582d2a90869b380f8260559138c1b68ac3e0765520959f22a1a1fdca31769" -dependencies = [ - "sval", - "sval_buffer", - "sval_ref", -] - [[package]] name = "sval_ref" -version = "2.14.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "762d3fbf3c0869064b7c93808c67ad2ed0292dde9b060ac282817941d4707dff" +checksum = "703ca1942a984bd0d9b5a4c0a65ab8b4b794038d080af4eb303c71bc6bf22d7c" dependencies = [ "sval", ] [[package]] name = "sval_serde" -version = "2.14.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "752d307438c6a6a3d095a2fecf6950cfb946d301a5bd6b57f047db4f6f8d97b9" +checksum = "830926cd0581f7c3e5d51efae4d35c6b6fc4db583842652891ba2f1bed8db046" dependencies = [ "serde", "sval", - "sval_nested", + "sval_buffer", + "sval_fmt", ] [[package]] name = "symbolic-common" -version = "12.13.2" +version = "12.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8150eae9699e3c73a3e6431dc1f80d87748797c0457336af23e94c1de619ed24" +checksum = "167a4ffd7c35c143fd1030aa3c2caf76ba42220bd5a6b5f4781896434723b8c3" dependencies = [ "debugid", - "memmap2 0.9.5", + "memmap2 0.5.10", "stable_deref_trait", "uuid", ] [[package]] name = "symbolic-demangle" -version = "12.13.2" +version = "12.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95f4a9846f7a8933b6d198c022faa2c9bd89e1a970bed9d9a98d25708bf8de17" +checksum = "e378c50e80686c1c5c205674e1f86a2858bec3d2a7dfdd690331a8a19330f293" dependencies = [ - "cpp_demangle 0.4.4", + "cpp_demangle 0.4.3", "rustc-demangle", "symbolic-common", ] @@ -29448,19 +29243,19 @@ version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.96" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "unicode-ident", ] @@ -29471,21 +29266,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b837ef12ab88835251726eb12237655e61ec8dc8a280085d1961cdc3dfd047" dependencies = [ "paste", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "syn-solidity" -version = "0.8.18" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e89d8bf2768d277f40573c83a02a099e96d96dd3104e13ea676194e61ac4b0" +checksum = "219389c1ebe89f8333df8bdfb871f6631c552ff399c23cac02480b6088aad8f0" dependencies = [ "paste", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -29496,9 +29291,9 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "sync_wrapper" -version = "1.0.2" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" dependencies = [ "futures-core", ] @@ -29509,10 +29304,10 @@ version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "syn 1.0.109", - "unicode-xid 0.2.6", + "unicode-xid 0.2.4", ] [[package]] @@ -29521,16 +29316,16 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "sysinfo" -version = "0.30.13" +version = "0.30.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3" +checksum = "1fb4f3438c8f6389c864e61221cbc97e9bca98b4daf39a5beb7bea660f528bb2" dependencies = [ "cfg-if", "core-foundation-sys", @@ -29548,19 +29343,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", - "core-foundation 0.9.4", - "system-configuration-sys 0.5.0", -] - -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags 2.8.0", - "core-foundation 0.9.4", - "system-configuration-sys 0.6.0", + "core-foundation", + "system-configuration-sys", ] [[package]] @@ -29573,16 +29357,6 @@ dependencies = [ "libc", ] -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "tap" version = "1.0.1" @@ -29591,9 +29365,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tar" -version = "0.4.43" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c65998313f8e17d0d553d28f91a0df93e4dbbbf770279c7bc21ca0f09ea1a1f6" +checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" dependencies = [ "filetime", "libc", @@ -29602,27 +29376,20 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.16" +version = "0.12.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - -[[package]] -name = "target-triple" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a4d50cdb458045afc8131fd91b64904da29548bcb63c7236e0844936c13078" +checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" [[package]] name = "tempfile" -version = "3.15.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand 2.3.0", - "getrandom", "once_cell", - "rustix 0.38.43", + "rustix 0.38.42", "windows-sys 0.59.0", ] @@ -29631,7 +29398,7 @@ name = "template-zombienet-tests" version = "0.0.0" dependencies = [ "anyhow", - "env_logger 0.11.6", + "env_logger 0.11.3", "log", "tokio", "zombienet-sdk", @@ -29639,28 +29406,28 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.4.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] [[package]] name = "terminal_size" -version = "0.4.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "rustix 0.38.43", - "windows-sys 0.59.0", + "rustix 0.38.42", + "windows-sys 0.48.0", ] [[package]] name = "termtree" -version = "0.5.1" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "test-log" @@ -29668,9 +29435,9 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dffced63c2b5c7be278154d76b479f9f9920ed34e7574201407f0b14e2bbb93" dependencies = [ - "env_logger 0.11.6", + "env_logger 0.11.3", "test-log-macros", - "tracing-subscriber", + "tracing-subscriber 0.3.18", ] [[package]] @@ -29679,9 +29446,9 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -29700,7 +29467,7 @@ dependencies = [ name = "test-parachain-adder-collator" version = "1.0.0" dependencies = [ - "clap 4.5.26", + "clap 4.5.13", "futures", "futures-timer", "log", @@ -29747,7 +29514,7 @@ dependencies = [ name = "test-parachain-undying-collator" version = "1.0.0" dependencies = [ - "clap 4.5.26", + "clap 4.5.13", "futures", "futures-timer", "log", @@ -29811,11 +29578,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94bceae6f7c89d47daff6c7e05f712551a01379f61b07d494661941144878589" dependencies = [ "cumulus-primitives-core 0.16.0", - "frame-support 38.2.0", + "frame-support 38.0.0", "polkadot-core-primitives 15.0.0", "rococo-runtime-constants 17.0.0", "smallvec", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "staging-xcm 14.2.0", "westend-runtime-constants 17.0.0", ] @@ -29826,67 +29593,53 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ - "unicode-width 0.1.14", + "unicode-width", ] [[package]] -name = "thiserror" -version = "1.0.69" +name = "textwrap" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "2.0.11" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" dependencies = [ - "thiserror-impl 2.0.11", + "thiserror-impl", ] [[package]] name = "thiserror-core" -version = "1.0.50" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c001ee18b7e5e3f62cbf58c7fe220119e68d902bb7443179c0c8aef30090e999" +checksum = "0d97345f6437bb2004cd58819d8a9ef8e36cdd7661c2abc4bbde0a7c40d9f497" dependencies = [ "thiserror-core-impl", ] [[package]] name = "thiserror-core-impl" -version = "1.0.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c60d69f36615a077cc7663b9cb8e42275722d23e58a7fa3d2c7f2915d09d04" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +checksum = "10ac1c5050e43014d16b2f94d0d2ce79e65ffdd8b38d8048f9c8f6a8a6da62ac" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 1.0.109", ] [[package]] name = "thiserror-impl" -version = "2.0.11" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -29897,9 +29650,9 @@ checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ "cfg-if", "once_cell", @@ -29947,9 +29700,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.37" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", @@ -29970,9 +29723,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.19" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", @@ -29987,16 +29740,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "tinystr" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" -dependencies = [ - "displaydoc", - "zerovec", -] - [[package]] name = "tinytemplate" version = "1.2.1" @@ -30009,9 +29752,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.8.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] @@ -30024,9 +29767,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.43.0" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", @@ -30035,7 +29778,7 @@ dependencies = [ "parking_lot 0.12.3", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.8", + "socket2 0.5.7", "tokio-macros", "windows-sys 0.52.0", ] @@ -30052,13 +29795,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -30088,25 +29831,26 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.12", + "rustls 0.21.7", "tokio", ] [[package]] name = "tokio-rustls" -version = "0.26.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.21", + "rustls 0.23.18", + "rustls-pki-types", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" dependencies = [ "futures-core", "pin-project-lite", @@ -30135,7 +29879,7 @@ checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", "log", - "rustls 0.21.12", + "rustls 0.21.7", "rustls-native-certs 0.6.3", "tokio", "tokio-rustls 0.24.1", @@ -30144,9 +29888,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.13" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", @@ -30195,7 +29939,18 @@ checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap 2.7.0", "toml_datetime", - "winnow 0.5.40", + "winnow 0.5.15", +] + +[[package]] +name = "toml_edit" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +dependencies = [ + "indexmap 2.7.0", + "toml_datetime", + "winnow 0.5.15", ] [[package]] @@ -30208,7 +29963,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.24", + "winnow 0.6.18", ] [[package]] @@ -30228,21 +29983,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper 1.0.2", - "tokio", - "tower-layer", - "tower-service", -] - [[package]] name = "tower-http" version = "0.4.4" @@ -30250,12 +29990,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" dependencies = [ "base64 0.21.7", - "bitflags 2.8.0", + "bitflags 2.6.0", "bytes", "futures-core", "futures-util", - "http 0.2.12", - "http-body 0.4.6", + "http 0.2.9", + "http-body 0.4.5", "http-range-header", "mime", "pin-project-lite", @@ -30270,10 +30010,10 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.6.0", "bytes", - "http 1.2.0", - "http-body 1.0.1", + "http 1.1.0", + "http-body 1.0.0", "http-body-util", "pin-project-lite", "tower-layer", @@ -30282,21 +30022,21 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.3" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" [[package]] name = "tower-service" -version = "0.3.3" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "log", "pin-project-lite", @@ -30306,20 +30046,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", "valuable", @@ -30351,10 +30091,21 @@ version = "5.0.0" dependencies = [ "assert_matches", "expander", - "proc-macro-crate 3.2.0", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro-crate 3.1.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", ] [[package]] @@ -30368,14 +30119,46 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-serde" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +dependencies = [ + "serde", + "tracing-core", +] + [[package]] name = "tracing-subscriber" -version = "0.3.19" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" dependencies = [ + "ansi_term", "chrono", - "matchers", + "lazy_static", + "matchers 0.0.1", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log 0.1.3", + "tracing-serde", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "chrono", + "matchers 0.1.0", "nu-ansi-term", "once_cell", "parking_lot 0.12.3", @@ -30386,7 +30169,7 @@ dependencies = [ "time", "tracing", "tracing-core", - "tracing-log", + "tracing-log 0.2.0", ] [[package]] @@ -30438,24 +30221,24 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.5" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "trybuild" -version = "1.0.101" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dcd332a5496c026f1e14b7f3d2b7bd98e509660c04239c58b0ba38a12daded4" +checksum = "9a9d3ba662913483d6722303f619e75ea10b7855b0f8e0d72799cf8621bb488f" dependencies = [ + "basic-toml", "dissimilar", "glob", + "once_cell", "serde", "serde_derive", "serde_json", - "target-triple", "termcolor", - "toml 0.8.19", ] [[package]] @@ -30473,13 +30256,13 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 0.2.12", + "http 0.2.9", "httparse", "log", "rand", - "rustls 0.21.12", + "rustls 0.21.7", "sha1", - "thiserror 1.0.69", + "thiserror", "url", "utf-8", ] @@ -30493,15 +30276,15 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 1.2.0", + "http 1.1.0", "httparse", "log", "rand", "rustls 0.22.4", - "rustls-native-certs 0.7.3", + "rustls-native-certs 0.7.0", "rustls-pki-types", "sha1", - "thiserror 1.0.69", + "thiserror", "url", "utf-8", ] @@ -30524,23 +30307,17 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "typeid" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e" - [[package]] name = "typenum" -version = "1.17.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "ucd-trie" -version = "0.1.7" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" [[package]] name = "uint" @@ -30574,15 +30351,15 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-bidi" -version = "0.3.18" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-normalization" @@ -30601,21 +30378,15 @@ checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - -[[package]] -name = "unicode-width" -version = "0.2.0" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "unicode-xid" @@ -30625,9 +30396,15 @@ checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" [[package]] name = "unicode-xid" -version = "0.2.6" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "unicode_categories" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" [[package]] name = "universal-hash" @@ -30636,7 +30413,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ "crypto-common", - "subtle 2.6.1", + "subtle 2.5.0", ] [[package]] @@ -30693,30 +30470,30 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "2.12.1" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a" dependencies = [ "base64 0.22.1", "flate2", "log", "once_cell", - "rustls 0.23.21", + "rustls 0.23.18", "rustls-pki-types", "serde", "serde_json", "url", - "webpki-roots 0.26.7", + "webpki-roots 0.26.3", ] [[package]] name = "url" -version = "2.5.4" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", - "idna", + "idna 0.5.0", "percent-encoding", "serde", ] @@ -30727,29 +30504,17 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - [[package]] name = "utf8parse" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.12.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744018581f9a3454a9e15beb8a33b017183f1e7c0cd170232a2d1453b23a51c4" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" dependencies = [ "getrandom", ] @@ -30762,9 +30527,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.10.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" +checksum = "8fec26a25bd6fca441cdd0f769fd7f891bae119f996de31f86a5eddccef54c1d" dependencies = [ "value-bag-serde1", "value-bag-sval2", @@ -30772,9 +30537,9 @@ dependencies = [ [[package]] name = "value-bag-serde1" -version = "1.10.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb773bd36fd59c7ca6e336c94454d9c66386416734817927ac93d81cb3c5b0b" +checksum = "ead5b693d906686203f19a49e88c477fb8c15798b68cf72f60b4b5521b4ad891" dependencies = [ "erased-serde", "serde", @@ -30783,9 +30548,9 @@ dependencies = [ [[package]] name = "value-bag-sval2" -version = "1.10.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a916a702cac43a88694c97657d449775667bcd14b70419441d05b7fea4a83a" +checksum = "3b9d0f4a816370c3a0d7d82d603b62198af17675b12fe5e91de6b47ceb505882" dependencies = [ "sval", "sval_buffer", @@ -30810,9 +30575,9 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" -version = "0.9.5" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "void" @@ -30822,16 +30587,16 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "w3f-bls" -version = "0.1.8" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a3028804c8bbae2a97a15b71ffc0e308c4b01a520994aafa77d56e94e19024" +checksum = "7335e4c132c28cc43caef6adb339789e599e39adbe78da0c4d547fad48cbc331" dependencies = [ "ark-bls12-377", "ark-bls12-381", - "ark-ec 0.4.2", + "ark-ec", "ark-ff 0.4.2", "ark-serialize 0.4.2", - "ark-serialize-derive 0.4.2", + "ark-serialize-derive", "arrayref", "constcat", "digest 0.10.7", @@ -30840,7 +30605,7 @@ dependencies = [ "rand_core 0.6.4", "sha2 0.10.8", "sha3 0.10.8", - "thiserror 1.0.69", + "thiserror", "zeroize", ] @@ -30855,9 +30620,9 @@ dependencies = [ [[package]] name = "waker-fn" -version = "1.2.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" [[package]] name = "walkdir" @@ -30890,24 +30655,14 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" -[[package]] -name = "wasix" -version = "0.12.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d" -dependencies = [ - "wasi", -] - [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", "once_cell", - "rustversion", "serde", "serde_json", "wasm-bindgen-macro", @@ -30915,71 +30670,69 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.100" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "once_cell", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if", "js-sys", - "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ - "quote 1.0.38", + "quote 1.0.37", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" -dependencies = [ - "unicode-ident", -] +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "wasm-bindgen-test" -version = "0.3.50" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3" +checksum = "6e6e302a7ea94f83a6d09e78e7dc7d9ca7b186bc2829c24a22d0753efd680671" dependencies = [ + "console_error_panic_hook", "js-sys", - "minicov", + "scoped-tls", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test-macro", @@ -30987,23 +30740,21 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.50" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" +checksum = "ecb993dd8c836930ed130e020e77d9b2e65dd0fbab1b67c790b0f5d80b11a575" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", ] [[package]] name = "wasm-encoder" -version = "0.223.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e636076193fa68103e937ac951b5f2f587624097017d764b8984d9c0f149464" +checksum = "41763f20eafed1399fff1afb466496d3a959f58241436cfdc17e3f5ca954de16" dependencies = [ "leb128", - "wasmparser 0.223.0", ] [[package]] @@ -31023,12 +30774,12 @@ dependencies = [ "array-bytes", "log", "multibase 0.9.1", - "multihash 0.19.3", + "multihash 0.19.1", "serde", "serde_json", "sp-maybe-compressed-blob 11.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "subrpcer", - "thiserror 1.0.69", + "thiserror", "tungstenite 0.21.0", "ureq", "url", @@ -31036,16 +30787,16 @@ dependencies = [ [[package]] name = "wasm-opt" -version = "0.116.1" +version = "0.116.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd87a4c135535ffed86123b6fb0f0a5a0bc89e50416c942c5f0662c645f679c" +checksum = "fc942673e7684671f0c5708fc18993569d184265fd5223bb51fc8e5b9b6cfd52" dependencies = [ "anyhow", "libc", "strum 0.24.1", "strum_macros 0.24.3", "tempfile", - "thiserror 1.0.69", + "thiserror", "wasm-opt-cxx-sys", "wasm-opt-sys", ] @@ -31093,7 +30844,7 @@ dependencies = [ "sp-version 35.0.0", "sp-wasm-interface 21.0.1", "substrate-runtime-proposal-hash", - "thiserror 1.0.69", + "thiserror", "wasm-loader", ] @@ -31131,7 +30882,7 @@ version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50386c99b9c32bd2ed71a55b6dd4040af2580530fae8bdb9a6576571a80d0cca" dependencies = [ - "arrayvec 0.7.6", + "arrayvec 0.7.4", "multi-stash", "num-derive", "num-traits", @@ -31193,17 +30944,6 @@ dependencies = [ "url", ] -[[package]] -name = "wasmparser" -version = "0.223.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5a99faceb1a5a84dd6084ec4bfa4b2ab153b5793b43fd8f58b89232634afc35" -dependencies = [ - "bitflags 2.8.0", - "indexmap 2.7.0", - "semver 1.0.24", -] - [[package]] name = "wasmparser-nostd" version = "0.100.2" @@ -31232,7 +30972,7 @@ dependencies = [ "rayon", "serde", "target-lexicon", - "wasmparser 0.102.0", + "wasmparser", "wasmtime-cache", "wasmtime-cranelift", "wasmtime-environ", @@ -31262,7 +31002,7 @@ dependencies = [ "directories-next", "file-per-thread-logger", "log", - "rustix 0.36.17", + "rustix 0.36.15", "serde", "sha2 0.10.8", "toml 0.5.11", @@ -31286,8 +31026,8 @@ dependencies = [ "log", "object 0.30.4", "target-lexicon", - "thiserror 1.0.69", - "wasmparser 0.102.0", + "thiserror", + "wasmparser", "wasmtime-cranelift-shared", "wasmtime-environ", ] @@ -31321,8 +31061,8 @@ dependencies = [ "object 0.30.4", "serde", "target-lexicon", - "thiserror 1.0.69", - "wasmparser 0.102.0", + "thiserror", + "wasmparser", "wasmtime-types", ] @@ -31358,7 +31098,7 @@ checksum = "6e0554b84c15a27d76281d06838aed94e13a77d7bf604bbbaf548aa20eb93846" dependencies = [ "object 0.30.4", "once_cell", - "rustix 0.36.17", + "rustix 0.36.15", ] [[package]] @@ -31386,10 +31126,10 @@ dependencies = [ "log", "mach", "memfd", - "memoffset", + "memoffset 0.8.0", "paste", "rand", - "rustix 0.36.17", + "rustix 0.36.15", "wasmtime-asm-macros", "wasmtime-environ", "wasmtime-jit-debug", @@ -31404,37 +31144,36 @@ checksum = "a4f6fffd2a1011887d57f07654dd112791e872e3ff4a2e626aee8059ee17f06f" dependencies = [ "cranelift-entity", "serde", - "thiserror 1.0.69", - "wasmparser 0.102.0", + "thiserror", + "wasmparser", ] [[package]] name = "wast" -version = "223.0.0" +version = "63.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59b2ba8a2ff9f06194b7be9524f92e45e70149f4dacc0d0c7ad92b59ac875e4" +checksum = "2560471f60a48b77fccefaf40796fda61c97ce1e790b59dfcec9dc3995c9f63a" dependencies = [ - "bumpalo", "leb128", "memchr", - "unicode-width 0.2.0", + "unicode-width", "wasm-encoder", ] [[package]] name = "wat" -version = "1.223.0" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662786915c427e4918ff01eabb3c4756d4d947cd8f635761526b4cc9da2eaaad" +checksum = "3bdc306c2c4c2f2bf2ba69e083731d0d2a77437fc6a350a19db139636e7e416c" dependencies = [ "wast", ] [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", @@ -31462,15 +31201,15 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.4" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" [[package]] name = "webpki-roots" -version = "0.26.7" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" dependencies = [ "rustls-pki-types", ] @@ -31630,15 +31369,15 @@ version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06861bf945aadac59f4be23b44c85573029520ea9bd3d6c9ab21c8b306e81cdc" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "polkadot-primitives 16.0.0", "polkadot-runtime-common 17.0.0", "smallvec", "sp-core 34.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-weights 31.0.0", "staging-xcm 14.2.0", - "staging-xcm-builder 17.0.3", + "staging-xcm-builder 17.0.1", ] [[package]] @@ -31667,9 +31406,9 @@ dependencies = [ [[package]] name = "wide" -version = "0.7.32" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41b5576b9a81633f3e8df296ce0063042a73507636cbe956c61133dd7034ab22" +checksum = "aa469ffa65ef7e0ba0f164183697b89b854253fd31aeb92358b7b6155177d62f" dependencies = [ "bytemuck", "safe_arch", @@ -31677,9 +31416,9 @@ dependencies = [ [[package]] name = "widestring" -version = "1.1.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" +checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" [[package]] name = "winapi" @@ -31699,11 +31438,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "windows-sys 0.59.0", + "winapi", ] [[package]] @@ -31714,60 +31453,59 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.52.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-core 0.52.0", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] name = "windows" -version = "0.53.0" +version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efc5cf48f83140dcaab716eeaea345f9e93d0018fb81162753a3f76c3397b538" +checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" dependencies = [ - "windows-core 0.53.0", - "windows-targets 0.52.6", + "windows-core 0.51.1", + "windows-targets 0.48.5", ] [[package]] -name = "windows-core" +name = "windows" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ + "windows-core 0.52.0", "windows-targets 0.52.6", ] [[package]] name = "windows-core" -version = "0.53.0" +version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dcc5b895a6377f1ab9fa55acedab1fd5ac0db66ad1e6c7f47e28a22e446a5dd" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ - "windows-result 0.1.2", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] -name = "windows-registry" -version = "0.2.0" +name = "windows-core" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-result 0.2.0", - "windows-strings", "windows-targets 0.52.6", ] [[package]] -name = "windows-result" -version = "0.1.2" +name = "windows-registry" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ + "windows-result", + "windows-strings", "windows-targets 0.52.6", ] @@ -31786,7 +31524,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ - "windows-result 0.2.0", + "windows-result", "windows-targets 0.52.6", ] @@ -32006,18 +31744,18 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.5.40" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" dependencies = [ "memchr", ] [[package]] name = "winnow" -version = "0.6.24" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] @@ -32032,18 +31770,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - [[package]] name = "wyz" version = "0.5.1" @@ -32055,9 +31781,9 @@ dependencies = [ [[package]] name = "x25519-dalek" -version = "2.0.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" dependencies = [ "curve25519-dalek 4.1.3", "rand_core 0.6.4", @@ -32078,19 +31804,17 @@ dependencies = [ "nom", "oid-registry", "rusticata-macros", - "thiserror 1.0.69", + "thiserror", "time", ] [[package]] name = "xattr" -version = "1.4.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909" +checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" dependencies = [ "libc", - "linux-raw-sys 0.4.15", - "rustix 0.38.43", ] [[package]] @@ -32182,10 +31906,10 @@ version = "7.0.0" dependencies = [ "Inflector", "frame-support 28.0.0", - "proc-macro2 1.0.93", - "quote 1.0.38", + "proc-macro2 1.0.86", + "quote 1.0.37", "staging-xcm 7.0.0", - "syn 2.0.96", + "syn 2.0.87", "trybuild", ] @@ -32196,9 +31920,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87fb4f14094d65c500a59bcf540cf42b99ee82c706edd6226a92e769ad60563e" dependencies = [ "Inflector", - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -32226,11 +31950,11 @@ dependencies = [ [[package]] name = "xcm-runtime-apis" -version = "0.4.2" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f3d96bd7362d9e6884ef6762f08737d89205a358d059a0451353f3e91985ca5" +checksum = "69d4473a5d157e4d437d9ebcb1b99f9693a64983877ee57d97005f0167869935" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "parity-scale-codec", "scale-info", "sp-api 34.0.0", @@ -32266,7 +31990,7 @@ version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "058e21bfc3e1180bbd83cad3690d0e63f34f43ab309e338afe988160aa776fcf" dependencies = [ - "frame-support 38.2.0", + "frame-support 38.0.0", "frame-system 38.0.0", "parity-scale-codec", "paste", @@ -32276,10 +32000,10 @@ dependencies = [ "polkadot-runtime-parachains 17.0.1", "scale-info", "sp-io 38.0.0", - "sp-runtime 39.0.5", + "sp-runtime 39.0.2", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "staging-xcm 14.2.0", - "staging-xcm-builder 17.0.3", + "staging-xcm-builder 17.0.1", "staging-xcm-executor 17.0.0", ] @@ -32340,9 +32064,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.25" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" +checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" [[package]] name = "xmltree" @@ -32370,9 +32094,9 @@ dependencies = [ [[package]] name = "yamux" -version = "0.13.4" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17610762a1207ee816c6fadc29220904753648aba0a9ed61c7b8336e80a559c4" +checksum = "a31b5e376a8b012bee9c423acdbb835fc34d45001cfa3106236a624e4b738028" dependencies = [ "futures", "log", @@ -32386,9 +32110,9 @@ dependencies = [ [[package]] name = "yansi" -version = "1.0.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "yap" @@ -32405,70 +32129,24 @@ dependencies = [ "time", ] -[[package]] -name = "yoke" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", - "synstructure 0.13.1", -] - [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" dependencies = [ - "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", -] - -[[package]] -name = "zerofrom" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.5" +version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", - "synstructure 0.13.1", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -32486,31 +32164,9 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", -] - -[[package]] -name = "zerovec" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" -dependencies = [ - "proc-macro2 1.0.93", - "quote 1.0.38", - "syn 2.0.96", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", ] [[package]] @@ -32519,10 +32175,10 @@ version = "1.0.0" dependencies = [ "futures-util", "parity-scale-codec", - "reqwest 0.12.12", + "reqwest 0.12.9", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror", "tokio", "tokio-tungstenite", "tracing-gum", @@ -32537,12 +32193,12 @@ checksum = "5ced2fca1322821431f03d06dcf2ea74d3a7369760b6c587b372de6eada3ce43" dependencies = [ "anyhow", "lazy_static", - "multiaddr 0.18.2", + "multiaddr 0.18.1", "regex", "reqwest 0.11.27", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror", "tokio", "toml 0.8.19", "url", @@ -32562,7 +32218,7 @@ dependencies = [ "hex", "libp2p", "libsecp256k1", - "multiaddr 0.18.2", + "multiaddr 0.18.1", "rand", "regex", "reqwest 0.11.27", @@ -32572,7 +32228,7 @@ dependencies = [ "sp-core 34.0.0", "subxt", "subxt-signer", - "thiserror 1.0.69", + "thiserror", "tokio", "tracing", "uuid", @@ -32590,7 +32246,7 @@ checksum = "23702db0819a050c8a0130a769b105695137020a64207b4597aa021f06924552" dependencies = [ "pest", "pest_derive", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -32614,7 +32270,7 @@ dependencies = [ "serde_yaml", "sha2 0.10.8", "tar", - "thiserror 1.0.69", + "thiserror", "tokio", "tokio-util", "tracing", @@ -32655,7 +32311,7 @@ dependencies = [ "rand", "regex", "reqwest 0.11.27", - "thiserror 1.0.69", + "thiserror", "tokio", "tracing", "uuid", @@ -32701,10 +32357,11 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.13+zstd.1.5.6" +version = "2.0.8+zstd.1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" dependencies = [ "cc", + "libc", "pkg-config", ] From de93ecdf4440b0a8474ef66ceefabf9908f649c1 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 15 Jan 2025 15:39:53 +0000 Subject: [PATCH 077/169] onchain backup sorting, testing chain in kitchensink --- substrate/bin/node/cli/Cargo.toml | 3 + substrate/bin/node/cli/src/chain_spec.rs | 64 +++++++--- substrate/bin/node/runtime/Cargo.toml | 2 + substrate/bin/node/runtime/src/constants.rs | 4 + substrate/bin/node/runtime/src/lib.rs | 119 +++++++++++++++++- .../consensus/grandpa/src/aux_schema.rs | 4 +- .../election-provider-support/src/lib.rs | 50 ++++++++ .../election-provider-support/src/onchain.rs | 103 +++++++++++---- .../election-provider-support/src/tests.rs | 27 +++- 9 files changed, 330 insertions(+), 46 deletions(-) diff --git a/substrate/bin/node/cli/Cargo.toml b/substrate/bin/node/cli/Cargo.toml index 9e063ee3cde0f..6b4313d63ffd4 100644 --- a/substrate/bin/node/cli/Cargo.toml +++ b/substrate/bin/node/cli/Cargo.toml @@ -183,6 +183,9 @@ try-runtime = [ "polkadot-sdk/try-runtime", "substrate-cli-test-utils/try-runtime", ] +staking-playground = [ + "kitchensink-runtime/staking-playground", +] [[bench]] name = "transaction_pool" diff --git a/substrate/bin/node/cli/src/chain_spec.rs b/substrate/bin/node/cli/src/chain_spec.rs index 038aa2f609285..e90312543433b 100644 --- a/substrate/bin/node/cli/src/chain_spec.rs +++ b/substrate/bin/node/cli/src/chain_spec.rs @@ -276,7 +276,6 @@ fn configure_accounts( )>, initial_nominators: Vec, endowed_accounts: Option>, - stash: Balance, ) -> ( Vec<( AccountId, @@ -305,21 +304,31 @@ fn configure_accounts( } }); - // stakers: all validators and nominators. + use rand::Rng; let mut rng = rand::thread_rng(); + let mut rng2 = rand::thread_rng(); + // stakers: all validators and nominators. let stakers = initial_authorities .iter() - .map(|x| (x.0.clone(), x.0.clone(), stash, StakerStatus::Validator)) + .map(|x| { + ( + x.0.clone(), + x.0.clone(), + rng.gen_range(ENDOWMENT / 100..ENDOWMENT / 2), + StakerStatus::Validator, + ) + }) .chain(initial_nominators.iter().map(|x| { use rand::{seq::SliceRandom, Rng}; let limit = (MaxNominations::get() as usize).min(initial_authorities.len()); - let count = rng.gen::() % limit; + let count = (rng2.gen::() % limit).max(1); let nominations = initial_authorities .as_slice() - .choose_multiple(&mut rng, count) + .choose_multiple(&mut rng2, count) .into_iter() .map(|choice| choice.0.clone()) .collect::>(); + let stash = rng2.gen_range(ENDOWMENT / 100..ENDOWMENT / 2); (x.clone(), x.clone(), stash, StakerStatus::Nominator(nominations)) })) .collect::>(); @@ -346,7 +355,8 @@ pub fn testnet_genesis( endowed_accounts: Option>, ) -> serde_json::Value { let (initial_authorities, endowed_accounts, num_endowed_accounts, stakers) = - configure_accounts(initial_authorities, initial_nominators, endowed_accounts, STASH); + configure_accounts(initial_authorities, initial_nominators, endowed_accounts); + const MAX_COLLECTIVE_SIZE: usize = 50; serde_json::json!({ "balances": { @@ -372,8 +382,8 @@ pub fn testnet_genesis( .collect::>(), }, "staking": { - "validatorCount": initial_authorities.len() as u32, - "minimumValidatorCount": initial_authorities.len() as u32, + "validatorCount": (initial_authorities.len()/2usize) as u32, + "minimumValidatorCount": 4, "invulnerables": initial_authorities.iter().map(|x| x.0.clone()).collect::>(), "slashRewardFraction": Perbill::from_percent(10), "stakers": stakers.clone(), @@ -381,7 +391,7 @@ pub fn testnet_genesis( "elections": { "members": endowed_accounts .iter() - .take((num_endowed_accounts + 1) / 2) + .take(((num_endowed_accounts + 1) / 2).min(MAX_COLLECTIVE_SIZE)) .cloned() .map(|member| (member, STASH)) .collect::>(), @@ -389,7 +399,7 @@ pub fn testnet_genesis( "technicalCommittee": { "members": endowed_accounts .iter() - .take((num_endowed_accounts + 1) / 2) + .take(((num_endowed_accounts + 1) / 2).min(MAX_COLLECTIVE_SIZE)) .cloned() .collect::>(), }, @@ -410,12 +420,34 @@ pub fn testnet_genesis( } fn development_config_genesis_json() -> serde_json::Value { - testnet_genesis( - vec![authority_keys_from_seed("Alice")], - vec![], - Sr25519Keyring::Alice.to_account_id(), - None, - ) + if cfg!(feature = "staking-playground") { + let random_authorities_count = 100; + let random_nominators_count = 3000; + let mut random_authorities = (0..random_authorities_count) + .map(|i| authority_keys_from_seed(&format!("Random{}", i))) + .collect::>(); + let random_nominators = (0..random_nominators_count) + .map(|i| { + get_public_from_string_or_panic::(&format!("Random{}", i)).into() + }) + .collect::>(); + // Alice should also always be an authority. + random_authorities.push(authority_keys_from_seed("Alice")); + + testnet_genesis( + random_authorities, + random_nominators, + Sr25519Keyring::Alice.to_account_id(), + None, + ) + } else { + testnet_genesis( + vec![authority_keys_from_seed("Alice")], + vec![], + Sr25519Keyring::Alice.to_account_id(), + None, + ) + } } fn props() -> Properties { diff --git a/substrate/bin/node/runtime/Cargo.toml b/substrate/bin/node/runtime/Cargo.toml index 6d377cc92cce1..d532384aef5ff 100644 --- a/substrate/bin/node/runtime/Cargo.toml +++ b/substrate/bin/node/runtime/Cargo.toml @@ -74,3 +74,5 @@ experimental = [ "pallet-example-tasks/experimental", ] metadata-hash = ["substrate-wasm-builder/metadata-hash"] +# Test temp feature to allow this chain to be used for swift testing of staking elections. +staking-playground = [] diff --git a/substrate/bin/node/runtime/src/constants.rs b/substrate/bin/node/runtime/src/constants.rs index d13dca48d1f12..42629d53500ce 100644 --- a/substrate/bin/node/runtime/src/constants.rs +++ b/substrate/bin/node/runtime/src/constants.rs @@ -63,7 +63,11 @@ 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. + #[cfg(not(feature = "staking-playground"))] pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 10 * MINUTES; + #[cfg(feature = "staking-playground")] + pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 1 * MINUTES; + pub const EPOCH_DURATION_IN_SLOTS: u64 = { const SLOT_FILL_RATE: f64 = MILLISECS_PER_BLOCK as f64 / SLOT_DURATION as f64; diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index d052e3581ac44..a4bcaa31afd5b 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -127,6 +127,7 @@ pub use pallet_balances::Call as BalancesCall; pub use pallet_staking::StakerStatus; #[cfg(any(feature = "std", test))] pub use pallet_sudo::Call as SudoCall; +use sp_keyring; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; @@ -663,16 +664,119 @@ impl_opaque_keys! { } } +#[cfg(feature = "staking-playground")] +pub mod staking_playground { + use pallet_staking::Exposure; + + use super::*; + + /// An adapter to make the chain work with --dev only, even though it is running a large staking + /// election. + /// + /// It will ignore the staking election and just set the validator set to alice. + /// + /// Needs to be fed into `type SessionManager`. + pub struct AliceAsOnlyValidator; + impl pallet_session::SessionManager for AliceAsOnlyValidator { + fn end_session(end_index: sp_staking::SessionIndex) { + >::end_session(end_index) + } + + fn new_session(new_index: sp_staking::SessionIndex) -> Option> { + >::new_session(new_index).map( + |_ignored_validators| { + vec![sp_keyring::Sr25519Keyring::AliceStash.to_account_id().into()] + }, + ) + } + + fn new_session_genesis(new_index: sp_staking::SessionIndex) -> Option> { + >::new_session_genesis(new_index) + .map(|_ignored_validators| { + vec![sp_keyring::Sr25519Keyring::AliceStash.to_account_id().into()] + }) + } + + fn start_session(start_index: sp_staking::SessionIndex) { + >::start_session(start_index) + } + } + + impl pallet_session::historical::SessionManager> + for AliceAsOnlyValidator + { + fn end_session(end_index: sp_staking::SessionIndex) { + , + >>::end_session(end_index) + } + + fn new_session( + new_index: sp_staking::SessionIndex, + ) -> Option)>> { + , + >>::new_session(new_index) + .map(|_ignored| { + // construct a fake exposure for alice. + vec![( + sp_keyring::Sr25519Keyring::AliceStash.to_account_id().into(), + pallet_staking::Exposure { + total: 1_000_000_000, + own: 1_000_000_000, + others: vec![], + }, + )] + }) + } + + fn new_session_genesis( + new_index: sp_staking::SessionIndex, + ) -> Option)>> { + , + >>::new_session_genesis(new_index) + .map(|_ignored| { + // construct a fake exposure for alice. + vec![( + sp_keyring::Sr25519Keyring::AliceStash.to_account_id().into(), + pallet_staking::Exposure { + total: 1_000_000_000, + own: 1_000_000_000, + others: vec![], + }, + )] + }) + } + + fn start_session(start_index: sp_staking::SessionIndex) { + , + >>::start_session(start_index) + } + } +} + impl pallet_session::Config for Runtime { type RuntimeEvent = RuntimeEvent; type ValidatorId = ::AccountId; type ValidatorIdOf = pallet_staking::StashOf; type ShouldEndSession = Babe; type NextSessionRotation = Babe; - type SessionManager = pallet_session::historical::NoteHistoricalRoot; type SessionHandler = ::KeyTypeIdProviders; type Keys = SessionKeys; type WeightInfo = pallet_session::weights::SubstrateWeight; + #[cfg(not(feature = "staking-playground"))] + type SessionManager = pallet_session::historical::NoteHistoricalRoot; + #[cfg(feature = "staking-playground")] + type SessionManager = pallet_session::historical::NoteHistoricalRoot< + Self, + staking_playground::AliceAsOnlyValidator, + >; } impl pallet_session::historical::Config for Runtime { @@ -691,8 +795,16 @@ pallet_staking_reward_curve::build! { ); } +#[cfg(not(feature = "staking-playground"))] parameter_types! { pub const SessionsPerEra: sp_staking::SessionIndex = 6; +} +#[cfg(feature = "staking-playground")] +parameter_types! { + pub const SessionsPerEra: sp_staking::SessionIndex = 2; +} + +parameter_types! { pub const BondingDuration: sp_staking::EraIndex = 24 * 28; pub const SlashDeferDuration: sp_staking::EraIndex = 24 * 7; // 1/4 the bonding duration. pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; @@ -851,6 +963,7 @@ impl Get> for OffchainRandomBalancing { pub struct OnChainSeqPhragmen; impl onchain::Config for OnChainSeqPhragmen { + type Sort = ConstBool; type System = Runtime; type Solver = SequentialPhragmen< AccountId, @@ -1204,7 +1317,7 @@ parameter_types! { // additional data per vote is 32 bytes (account id). pub const VotingBondFactor: Balance = deposit(0, 32); pub const TermDuration: BlockNumber = 7 * DAYS; - pub const DesiredMembers: u32 = 13; + pub const DesiredMembers: u32 = CouncilMaxMembers::get(); pub const DesiredRunnersUp: u32 = 7; pub const MaxVotesPerVoter: u32 = 16; pub const MaxVoters: u32 = 512; @@ -1488,7 +1601,7 @@ parameter_types! { pub const ImOnlineUnsignedPriority: TransactionPriority = TransactionPriority::max_value(); /// We prioritize im-online heartbeats over election solution submission. pub const StakingUnsignedPriority: TransactionPriority = TransactionPriority::max_value() / 2; - pub const MaxAuthorities: u32 = 100; + pub const MaxAuthorities: u32 = 1000; pub const MaxKeys: u32 = 10_000; pub const MaxPeerInHeartbeats: u32 = 10_000; } diff --git a/substrate/client/consensus/grandpa/src/aux_schema.rs b/substrate/client/consensus/grandpa/src/aux_schema.rs index c42310dcd72cf..8ec882591be9a 100644 --- a/substrate/client/consensus/grandpa/src/aux_schema.rs +++ b/substrate/client/consensus/grandpa/src/aux_schema.rs @@ -743,9 +743,7 @@ mod test { substrate_test_runtime_client::runtime::Block, _, _, - >( - &client, H256::random(), 0, || unreachable!() - ) + >(&client, H256::random(), 0, || unreachable!()) .unwrap(); assert_eq!( diff --git a/substrate/frame/election-provider-support/src/lib.rs b/substrate/frame/election-provider-support/src/lib.rs index 8b2edf4452a87..f5043e0e32c41 100644 --- a/substrate/frame/election-provider-support/src/lib.rs +++ b/substrate/frame/election-provider-support/src/lib.rs @@ -769,6 +769,27 @@ impl> TryFrom> } } +impl> BoundedSupport { + pub fn sorted_truncate_from(mut support: sp_npos_elections::Support) -> Self { + // If bounds meet, then short circuit. + if let Ok(bounded) = support.clone().try_into() { + return bounded + } + + // sort support based on stake of each backer, low to high. + support.voters.sort_by(|a, b| a.1.cmp(&b.1)); + // then do the truncation. + let mut bounded = Self { voters: Default::default(), total: 0 }; + while let Some((voter, weight)) = support.voters.pop() { + if let Err(_) = bounded.voters.try_push((voter, weight)) { + break + } + bounded.total += weight; + } + bounded + } +} + /// A bounded vector of [`BoundedSupport`]. /// /// A [`BoundedSupports`] is a set of [`sp_npos_elections::Supports`] which are bounded in two @@ -877,6 +898,35 @@ impl, BInner: Get> TryFrom, BInner: Get> + BoundedSupports +{ + pub fn sorted_truncate_from(supports: Supports) -> Self { + // if bounds, meet, short circuit + if let Ok(bounded) = supports.clone().try_into() { + return bounded + } + + // first, convert all inner supports. + let mut inner_supports = supports + .into_iter() + .map(|(account, support)| { + (account, BoundedSupport::::sorted_truncate_from(support)) + }) + .collect::>(); + + // then sort outer supports based on total stake, high to low + inner_supports.sort_by(|a, b| b.1.total.cmp(&a.1.total)); + + // then take the first slice that can fit. + BoundedSupports( + BoundedVec::<(AccountId, BoundedSupport), BOuter>::truncate_from( + inner_supports, + ), + ) + } +} + /// Same as `BoundedSupports` but parameterized by an `ElectionProvider`. pub type BoundedSupportsOf = BoundedSupports< ::AccountId, diff --git a/substrate/frame/election-provider-support/src/onchain.rs b/substrate/frame/election-provider-support/src/onchain.rs index 379dccee2ce69..c18f8e1d54bda 100644 --- a/substrate/frame/election-provider-support/src/onchain.rs +++ b/substrate/frame/election-provider-support/src/onchain.rs @@ -39,9 +39,8 @@ pub enum Error { NposElections(sp_npos_elections::Error), /// Errors from the data provider. DataProvider(&'static str), - /// Configurational error caused by `desired_targets` requested by data provider exceeding - /// `MaxWinners`. - TooManyWinners, + /// Results failed to meet the bounds. + FailedToBound, /// Election page index not supported. UnsupportedPageIndex, } @@ -65,6 +64,12 @@ pub type BoundedExecution = OnChainExecution; /// Configuration trait for an onchain election execution. pub trait Config { + /// Whether to try and sort or not. + /// + /// If `true`, the supports will be sorted by descending total support to meet the bounds. If + /// `false`, `FailedToBound` error may be returned. + type Sort: Get; + /// Needed for weight registration. type System: frame_system::Config; @@ -113,9 +118,9 @@ impl OnChainExecution { let desired_targets = T::DataProvider::desired_targets().map_err(Error::DataProvider)?; - if desired_targets > T::MaxWinnersPerPage::get() { - // early exit - return Err(Error::TooManyWinners) + if (desired_targets > T::MaxWinnersPerPage::get()) && !T::Sort::get() { + // early exit what will fail in the last line anyways. + return Err(Error::FailedToBound) } let voters_len = voters.len() as u32; @@ -145,12 +150,13 @@ impl OnChainExecution { DispatchClass::Mandatory, ); - // defensive: Since npos solver returns a result always bounded by `desired_targets`, this - // is never expected to happen as long as npos solver does what is expected for it to do. - let supports: BoundedSupportsOf = - to_supports(&staked).try_into().map_err(|_| Error::TooManyWinners)?; - - Ok(supports) + let unbounded = to_supports(&staked); + let bounded = if T::Sort::get() { + BoundedSupportsOf::::sorted_truncate_from(unbounded) + } else { + unbounded.try_into().map_err(|_| Error::FailedToBound)? + }; + Ok(bounded) } } @@ -197,6 +203,7 @@ mod tests { use super::*; use crate::{ElectionProvider, PhragMMS, SequentialPhragmen}; use frame_support::{assert_noop, derive_impl, parameter_types}; + use sp_io::TestExternalities; use sp_npos_elections::Support; use sp_runtime::Perbill; type AccountId = u64; @@ -247,10 +254,12 @@ mod tests { pub static MaxWinnersPerPage: u32 = 10; pub static MaxBackersPerWinner: u32 = 20; pub static DesiredTargets: u32 = 2; + pub static Sort: bool = false; pub static Bounds: ElectionBounds = ElectionBoundsBuilder::default().voters_count(600.into()).targets_count(400.into()).build(); } impl Config for PhragmenParams { + type Sort = Sort; type System = Runtime; type Solver = SequentialPhragmen; type DataProvider = mock_data_provider::DataProvider; @@ -261,6 +270,7 @@ mod tests { } impl Config for PhragMMSParams { + type Sort = Sort; type System = Runtime; type Solver = PhragMMS; type DataProvider = mock_data_provider::DataProvider; @@ -312,8 +322,8 @@ mod tests { #[test] fn onchain_seq_phragmen_works() { - sp_io::TestExternalities::new_empty().execute_with(|| { - let expected_suports = vec![ + TestExternalities::new_empty().execute_with(|| { + let expected_supports = vec![ ( 10 as AccountId, Support { total: 25, voters: vec![(1 as AccountId, 10), (3, 15)] }, @@ -325,28 +335,77 @@ mod tests { assert_eq!( as ElectionProvider>::elect(0).unwrap(), - expected_suports, + expected_supports, ); }) } #[test] - fn too_many_winners_when_desired_targets_exceed_max_winners() { - sp_io::TestExternalities::new_empty().execute_with(|| { - // given desired targets larger than max winners - DesiredTargets::set(10); - MaxWinnersPerPage::set(9); + fn sorting_false_works() { + TestExternalities::new_empty().execute_with(|| { + // Default results would have 3 targets, but we allow for only 2. + DesiredTargets::set(3); + MaxWinnersPerPage::set(2); + + assert_noop!( + as ElectionProvider>::elect(0), + Error::FailedToBound, + ); + }); + + TestExternalities::new_empty().execute_with(|| { + // Default results would have 2 backers per winner + MaxBackersPerWinner::set(1); assert_noop!( as ElectionProvider>::elect(0), - Error::TooManyWinners, + Error::FailedToBound, + ); + }); + } + + #[test] + fn sorting_true_works_winners() { + Sort::set(true); + + TestExternalities::new_empty().execute_with(|| { + let expected_supports = + vec![(30, Support { total: 35, voters: vec![(2, 20), (3, 15)] })] + .try_into() + .unwrap(); + + // we want to allow 1 winner only, and allow sorting. + MaxWinnersPerPage::set(1); + + assert_eq!( + as ElectionProvider>::elect(0).unwrap(), + expected_supports, + ); + }); + + MaxWinnersPerPage::set(10); + + TestExternalities::new_empty().execute_with(|| { + let expected_supports = vec![ + (30, Support { total: 20, voters: vec![(2, 20)] }), + (10 as AccountId, Support { total: 15, voters: vec![(3 as AccountId, 15)] }), + ] + .try_into() + .unwrap(); + + // we want to allow 2 winners only but 1 backer each, and allow sorting. + MaxBackersPerWinner::set(1); + + assert_eq!( + as ElectionProvider>::elect(0).unwrap(), + expected_supports, ); }) } #[test] fn onchain_phragmms_works() { - sp_io::TestExternalities::new_empty().execute_with(|| { + TestExternalities::new_empty().execute_with(|| { assert_eq!( as ElectionProvider>::elect(0).unwrap(), vec![ diff --git a/substrate/frame/election-provider-support/src/tests.rs b/substrate/frame/election-provider-support/src/tests.rs index 6e3deb9e38346..b2bf223ed2fae 100644 --- a/substrate/frame/election-provider-support/src/tests.rs +++ b/substrate/frame/election-provider-support/src/tests.rs @@ -18,10 +18,10 @@ //! Tests for solution-type. #![cfg(test)] - -use crate::{mock::*, IndexAssignment, NposSolution}; +use crate::{mock::*, BoundedSupports, IndexAssignment, NposSolution}; use frame_support::traits::ConstU32; use rand::SeedableRng; +use sp_npos_elections::{Support, Supports}; mod solution_type { use super::*; @@ -452,3 +452,26 @@ fn index_assignments_generate_same_solution_as_plain_assignments() { assert_eq!(solution, index_compact); } + +#[test] +fn sorted_truncate_from_works() { + let supports: Supports = vec![ + (1, Support { total: 303, voters: vec![(100, 100), (101, 101), (102, 102)] }), + (2, Support { total: 201, voters: vec![(100, 100), (101, 101)] }), + (3, Support { total: 406, voters: vec![(100, 100), (101, 101), (102, 102), (103, 103)] }), + ]; + + let bounded = BoundedSupports::, ConstU32<2>>::sorted_truncate_from(supports); + // we trim 2 as it has least total support, and trim backers based on stake. + assert_eq!( + bounded + .clone() + .into_iter() + .map(|(k, v)| (k, Support { total: v.total, voters: v.voters.into_inner() })) + .collect::>(), + vec![ + (3, Support { total: 205, voters: vec![(103, 103), (102, 102)] }), + (1, Support { total: 203, voters: vec![(102, 102), (101, 101)] }) + ] + ); +} From 5d642936c027037652daa661082a8bae4194bba2 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 15 Jan 2025 15:41:15 +0000 Subject: [PATCH 078/169] fix migration --- substrate/frame/staking/src/migrations.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/substrate/frame/staking/src/migrations.rs b/substrate/frame/staking/src/migrations.rs index b9219b4acb80e..36a94bfdfc912 100644 --- a/substrate/frame/staking/src/migrations.rs +++ b/substrate/frame/staking/src/migrations.rs @@ -100,12 +100,15 @@ pub mod v17 { migrated_stashes.into(), ) } - } - #[cfg(feature = "try-runtime")] - fn post_upgrade(_state: Vec) -> Result<(), TryRuntimeError> { - frame_support::ensure!(Pallet::::on_chain_storage_version() >= 17, "v17 not applied"); - Ok(()) + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), TryRuntimeError> { + frame_support::ensure!( + Pallet::::on_chain_storage_version() >= 17, + "v17 not applied" + ); + Ok(()) + } } pub type MigrateV16ToV17 = VersionedMigration< From 01adfe63a1d72fe3cf883f7c831e3ecce9086439 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 16 Jan 2025 09:16:53 +0000 Subject: [PATCH 079/169] greenish CI now --- polkadot/runtime/test-runtime/src/lib.rs | 7 +++++-- polkadot/runtime/westend/src/lib.rs | 6 +++--- substrate/frame/babe/src/mock.rs | 6 ++++-- substrate/frame/beefy/src/mock.rs | 6 ++++-- substrate/frame/delegated-staking/src/mock.rs | 3 ++- .../election-provider-multi-phase/src/mock.rs | 3 ++- .../test-staking-e2e/src/mock.rs | 19 +++++++++++++++---- substrate/frame/fast-unstake/src/mock.rs | 4 ++-- substrate/frame/grandpa/src/mock.rs | 6 ++++-- .../test-delegate-stake/src/mock.rs | 2 +- .../test-transfer-stake/src/mock.rs | 2 +- substrate/frame/root-offences/src/mock.rs | 5 ++++- substrate/frame/staking/src/mock.rs | 2 ++ 13 files changed, 49 insertions(+), 22 deletions(-) diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs index 4f9ba8d8508cd..233ebb03034a1 100644 --- a/polkadot/runtime/test-runtime/src/lib.rs +++ b/polkadot/runtime/test-runtime/src/lib.rs @@ -77,7 +77,7 @@ use polkadot_runtime_common::{ use polkadot_runtime_parachains::reward_points::RewardValidatorsWithEraPoints; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; use sp_consensus_beefy::ecdsa_crypto::{AuthorityId as BeefyId, Signature as BeefySignature}; -use sp_core::{ConstU32, OpaqueMetadata}; +use sp_core::{ConstBool, ConstU32, OpaqueMetadata}; use sp_mmr_primitives as mmr; use sp_runtime::{ curve::PiecewiseLinear, @@ -359,7 +359,9 @@ impl onchain::Config for OnChainSeqPhragmen { type DataProvider = Staking; type WeightInfo = (); type Bounds = ElectionBoundsOnChain; - type MaxWinners = OnChainMaxWinners; + type MaxWinnersPerPage = OnChainMaxWinners; + type MaxBackersPerWinner = ConstU32<{ u32::MAX }>; + type Sort = ConstBool; } /// Upper limit on the number of NPOS nominations. @@ -396,6 +398,7 @@ impl pallet_staking::Config for Runtime { type EventListeners = (); type WeightInfo = (); type DisablingStrategy = pallet_staking::UpToLimitWithReEnablingDisablingStrategy; + type MaxValidatorSet = MaxAuthorities; } parameter_types! { diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 88f722e75a9a3..917f1689b1c03 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -94,7 +94,7 @@ use sp_consensus_beefy::{ ecdsa_crypto::{AuthorityId as BeefyId, Signature as BeefySignature}, mmr::{BeefyDataProvider, MmrLeafVersion}, }; -use sp_core::{ConstU8, OpaqueMetadata, RuntimeDebug, H256}; +use sp_core::{ConstBool, ConstU8, OpaqueMetadata, RuntimeDebug, H256}; use sp_runtime::{ generic, impl_opaque_keys, traits::{ @@ -600,6 +600,7 @@ frame_election_provider_support::generate_solution_type!( pub struct OnChainSeqPhragmen; impl onchain::Config for OnChainSeqPhragmen { + type Sort = ConstBool; type System = Runtime; type Solver = SequentialPhragmen; type DataProvider = Staking; @@ -1150,8 +1151,7 @@ impl InstanceFilter for ProxyType { matches!( c, RuntimeCall::Staking(..) | - RuntimeCall::Session(..) | - RuntimeCall::Utility(..) | + RuntimeCall::Session(..) | RuntimeCall::Utility(..) | RuntimeCall::FastUnstake(..) | RuntimeCall::VoterList(..) | RuntimeCall::NominationPools(..) diff --git a/substrate/frame/babe/src/mock.rs b/substrate/frame/babe/src/mock.rs index 23857470adc4a..12a9fc0ae179c 100644 --- a/substrate/frame/babe/src/mock.rs +++ b/substrate/frame/babe/src/mock.rs @@ -31,7 +31,7 @@ use pallet_session::historical as pallet_session_historical; use sp_consensus_babe::{AuthorityId, AuthorityPair, Randomness, Slot, VrfSignature}; use sp_core::{ crypto::{Pair, VrfSecret}, - U256, + ConstBool, U256, }; use sp_io; use sp_runtime::{ @@ -151,7 +151,9 @@ impl onchain::Config for OnChainSeqPhragmen { type Solver = SequentialPhragmen; type DataProvider = Staking; type WeightInfo = (); - type MaxWinners = ConstU32<100>; + type MaxWinnersPerPage = ConstU32<100>; + type MaxBackersPerWinner = ConstU32<100>; + type Sort = ConstBool; type Bounds = ElectionsBounds; } diff --git a/substrate/frame/beefy/src/mock.rs b/substrate/frame/beefy/src/mock.rs index 7ae41c609180e..369e171f81cc2 100644 --- a/substrate/frame/beefy/src/mock.rs +++ b/substrate/frame/beefy/src/mock.rs @@ -29,7 +29,7 @@ use frame_support::{ }; use frame_system::pallet_prelude::HeaderFor; use pallet_session::historical as pallet_session_historical; -use sp_core::{crypto::KeyTypeId, ConstU128}; +use sp_core::{crypto::KeyTypeId, ConstBool, ConstU128}; use sp_runtime::{ app_crypto::ecdsa::Public, curve::PiecewiseLinear, @@ -228,7 +228,9 @@ impl onchain::Config for OnChainSeqPhragmen { type Solver = SequentialPhragmen; type DataProvider = Staking; type WeightInfo = (); - type MaxWinners = ConstU32<100>; + type MaxWinnersPerPage = ConstU32<100>; + type MaxBackersPerWinner = ConstU32<100>; + type Sort = ConstBool; type Bounds = ElectionsBoundsOnChain; } diff --git a/substrate/frame/delegated-staking/src/mock.rs b/substrate/frame/delegated-staking/src/mock.rs index a7363698e0747..95e6833432928 100644 --- a/substrate/frame/delegated-staking/src/mock.rs +++ b/substrate/frame/delegated-staking/src/mock.rs @@ -32,7 +32,7 @@ use frame_election_provider_support::{ }; use frame_support::dispatch::RawOrigin; use pallet_staking::{ActiveEra, ActiveEraInfo, CurrentEra}; -use sp_core::U256; +use sp_core::{ConstBool, U256}; use sp_runtime::traits::Convert; use sp_staking::{Agent, Stake, StakingInterface}; @@ -98,6 +98,7 @@ impl onchain::Config for OnChainSeqPhragmen { type WeightInfo = (); type MaxWinnersPerPage = ConstU32<100>; type MaxBackersPerWinner = ConstU32<100>; + type Sort = ConstBool; type Bounds = ElectionsBoundsOnChain; } diff --git a/substrate/frame/election-provider-multi-phase/src/mock.rs b/substrate/frame/election-provider-multi-phase/src/mock.rs index 20fe4016375c8..c408296e48346 100644 --- a/substrate/frame/election-provider-multi-phase/src/mock.rs +++ b/substrate/frame/election-provider-multi-phase/src/mock.rs @@ -35,7 +35,7 @@ use sp_core::{ testing::{PoolState, TestOffchainExt, TestTransactionPoolExt}, OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, }, - H256, + ConstBool, H256, }; use sp_npos_elections::{ assignment_ratio_to_staked_normalized, seq_phragmen, to_supports, BalancingConfig, @@ -313,6 +313,7 @@ impl onchain::Config for OnChainSeqPhragmen { type WeightInfo = (); type MaxWinnersPerPage = MaxWinners; type MaxBackersPerWinner = MaxBackersPerWinner; + type Sort = ConstBool; type Bounds = OnChainElectionsBounds; } diff --git a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs index eaab848c16944..9d354f4cf6dab 100644 --- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs +++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs @@ -23,7 +23,7 @@ use frame_support::{ weights::constants, }; use frame_system::EnsureRoot; -use sp_core::{ConstU32, Get}; +use sp_core::{ConstBool, ConstU32, Get}; use sp_npos_elections::{ElectionScore, VoteWeight}; use sp_runtime::{ offchain::{ @@ -168,6 +168,8 @@ parameter_types! { pub static TransactionPriority: transaction_validity::TransactionPriority = 1; #[derive(Debug)] pub static MaxWinners: u32 = 100; + #[derive(Debug)] + pub static MaxBackersPerWinner: u32 = 100; pub static MaxVotesPerVoter: u32 = 16; pub static SignedFixedDeposit: Balance = 1; pub static SignedDepositIncreaseFactor: Percent = Percent::from_percent(10); @@ -196,12 +198,18 @@ impl pallet_election_provider_multi_phase::Config for Runtime { type SlashHandler = (); type RewardHandler = (); type DataProvider = Staking; - type Fallback = - frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, MaxWinners)>; + type Fallback = frame_election_provider_support::NoElection<( + AccountId, + BlockNumber, + Staking, + MaxWinners, + MaxBackersPerWinner, + )>; type GovernanceFallback = onchain::OnChainExecution; type Solver = SequentialPhragmen, ()>; type ForceOrigin = EnsureRoot; type MaxWinners = MaxWinners; + type MaxBackersPerWinner = MaxBackersPerWinner; type ElectionBounds = ElectionBounds; type BenchmarkingConfig = NoopElectionProviderBenchmarkConfig; type WeightInfo = (); @@ -215,6 +223,7 @@ impl MinerConfig for Runtime { type MaxLength = MinerMaxLength; type MaxWeight = MinerMaxWeight; type MaxWinners = MaxWinners; + type MaxBackersPerWinner = MaxBackersPerWinner; fn solution_weight(_v: u32, _t: u32, _a: u32, _d: u32) -> Weight { Weight::zero() @@ -334,6 +343,9 @@ parameter_types! { } impl onchain::Config for OnChainSeqPhragmen { + type MaxWinnersPerPage = MaxWinners; + type MaxBackersPerWinner = MaxBackersPerWinner; + type Sort = ConstBool; type System = Runtime; type Solver = SequentialPhragmen< AccountId, @@ -341,7 +353,6 @@ impl onchain::Config for OnChainSeqPhragmen { >; type DataProvider = Staking; type WeightInfo = (); - type MaxWinners = MaxWinners; type Bounds = ElectionBounds; } diff --git a/substrate/frame/fast-unstake/src/mock.rs b/substrate/frame/fast-unstake/src/mock.rs index 2104cfb46bed7..0b711bf4aa31d 100644 --- a/substrate/frame/fast-unstake/src/mock.rs +++ b/substrate/frame/fast-unstake/src/mock.rs @@ -203,7 +203,7 @@ impl ExtBuilder { (v, Exposure { total: 0, own: 0, others }) }) .for_each(|(validator, exposure)| { - pallet_staking::EraInfo::::set_exposure(era, &validator, exposure); + pallet_staking::EraInfo::::upsert_exposure(era, &validator, exposure); }); } @@ -301,7 +301,7 @@ pub fn create_exposed_nominator(exposed: AccountId, era: u32) { // create an exposed nominator in passed era let mut exposure = pallet_staking::EraInfo::::get_full_exposure(era, &VALIDATORS_PER_ERA); exposure.others.push(IndividualExposure { who: exposed, value: 0 as Balance }); - pallet_staking::EraInfo::::set_exposure(era, &VALIDATORS_PER_ERA, exposure); + pallet_staking::EraInfo::::upsert_exposure(era, &VALIDATORS_PER_ERA, exposure); Balances::make_free_balance_be(&exposed, 100); assert_ok!(Staking::bond( diff --git a/substrate/frame/grandpa/src/mock.rs b/substrate/frame/grandpa/src/mock.rs index 87369c23948ca..309383d475dcc 100644 --- a/substrate/frame/grandpa/src/mock.rs +++ b/substrate/frame/grandpa/src/mock.rs @@ -32,7 +32,7 @@ use frame_support::{ }; use pallet_session::historical as pallet_session_historical; use sp_consensus_grandpa::{RoundNumber, SetId, GRANDPA_ENGINE_ID}; -use sp_core::H256; +use sp_core::{ConstBool, H256}; use sp_keyring::Ed25519Keyring; use sp_runtime::{ curve::PiecewiseLinear, @@ -155,7 +155,9 @@ impl onchain::Config for OnChainSeqPhragmen { type Solver = SequentialPhragmen; type DataProvider = Staking; type WeightInfo = (); - type MaxWinners = ConstU32<100>; + type MaxWinnersPerPage = ConstU32<100>; + type MaxBackersPerWinner = ConstU32<100>; + type Sort = ConstBool; type Bounds = ElectionsBoundsOnChain; } diff --git a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs index d1bc4ef8ff281..370e1aa1b84b3 100644 --- a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs +++ b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs @@ -98,7 +98,7 @@ impl pallet_staking::Config for Runtime { type BondingDuration = BondingDuration; type EraPayout = pallet_staking::ConvertCurve; type ElectionProvider = - frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, ())>; + frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, (), ())>; type GenesisElectionProvider = Self::ElectionProvider; type VoterList = VoterList; type TargetList = pallet_staking::UseValidatorsMap; diff --git a/substrate/frame/nomination-pools/test-transfer-stake/src/mock.rs b/substrate/frame/nomination-pools/test-transfer-stake/src/mock.rs index d913c5fe6948c..8e545f7dffa93 100644 --- a/substrate/frame/nomination-pools/test-transfer-stake/src/mock.rs +++ b/substrate/frame/nomination-pools/test-transfer-stake/src/mock.rs @@ -90,7 +90,7 @@ impl pallet_staking::Config for Runtime { type BondingDuration = BondingDuration; type EraPayout = pallet_staking::ConvertCurve; type ElectionProvider = - frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, ())>; + frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, (), ())>; type GenesisElectionProvider = Self::ElectionProvider; type VoterList = VoterList; type TargetList = pallet_staking::UseValidatorsMap; diff --git a/substrate/frame/root-offences/src/mock.rs b/substrate/frame/root-offences/src/mock.rs index 7a96b8eade4e1..e64adc8fcc3bf 100644 --- a/substrate/frame/root-offences/src/mock.rs +++ b/substrate/frame/root-offences/src/mock.rs @@ -28,6 +28,7 @@ use frame_support::{ traits::{ConstU32, ConstU64, OneSessionHandler}, }; use pallet_staking::StakerStatus; +use sp_core::ConstBool; use sp_runtime::{curve::PiecewiseLinear, testing::UintAuthorityId, traits::Zero, BuildStorage}; use sp_staking::{EraIndex, SessionIndex}; @@ -110,7 +111,9 @@ impl onchain::Config for OnChainSeqPhragmen { type Solver = SequentialPhragmen; type DataProvider = Staking; type WeightInfo = (); - type MaxWinners = ConstU32<100>; + type MaxWinnersPerPage = ConstU32<100>; + type MaxBackersPerWinner = ConstU32<100>; + type Sort = ConstBool; type Bounds = ElectionsBounds; } diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index 4750f7e1438ad..90c35413b66b1 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -32,6 +32,7 @@ use frame_support::{ weights::constants::RocksDbWeight, }; use frame_system::{EnsureRoot, EnsureSignedBy}; +use sp_core::ConstBool; use sp_io; use sp_runtime::{curve::PiecewiseLinear, testing::UintAuthorityId, traits::Zero, BuildStorage}; use sp_staking::{ @@ -290,6 +291,7 @@ impl onchain::Config for OnChainSeqPhragmen { type DataProvider = Staking; type WeightInfo = (); type Bounds = ElectionsBounds; + type Sort = ConstBool; type MaxBackersPerWinner = ConstU32<{ u32::MAX }>; type MaxWinnersPerPage = MaxWinnersPerPage; } From eedd03a31d2409c4f1375b6d624633f48f8038d4 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 16 Jan 2025 09:17:25 +0000 Subject: [PATCH 080/169] fmt --- polkadot/runtime/westend/src/lib.rs | 3 ++- substrate/client/consensus/grandpa/src/aux_schema.rs | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 917f1689b1c03..5256799f12365 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -1151,7 +1151,8 @@ impl InstanceFilter for ProxyType { matches!( c, RuntimeCall::Staking(..) | - RuntimeCall::Session(..) | RuntimeCall::Utility(..) | + RuntimeCall::Session(..) | + RuntimeCall::Utility(..) | RuntimeCall::FastUnstake(..) | RuntimeCall::VoterList(..) | RuntimeCall::NominationPools(..) diff --git a/substrate/client/consensus/grandpa/src/aux_schema.rs b/substrate/client/consensus/grandpa/src/aux_schema.rs index 8ec882591be9a..c42310dcd72cf 100644 --- a/substrate/client/consensus/grandpa/src/aux_schema.rs +++ b/substrate/client/consensus/grandpa/src/aux_schema.rs @@ -743,7 +743,9 @@ mod test { substrate_test_runtime_client::runtime::Block, _, _, - >(&client, H256::random(), 0, || unreachable!()) + >( + &client, H256::random(), 0, || unreachable!() + ) .unwrap(); assert_eq!( From 33a37e69872e61a7bb485779ee97ae6f0ba20c9a Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 16 Jan 2025 09:18:25 +0000 Subject: [PATCH 081/169] fix unused import --- substrate/bin/node/runtime/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index a4bcaa31afd5b..b11dcd0448c1f 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -127,7 +127,6 @@ pub use pallet_balances::Call as BalancesCall; pub use pallet_staking::StakerStatus; #[cfg(any(feature = "std", test))] pub use pallet_sudo::Call as SudoCall; -use sp_keyring; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; From 7df54f14caf65ccd2f3c43f03b8325073b100a34 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 16 Jan 2025 09:25:11 +0000 Subject: [PATCH 082/169] fix some benchmarks too --- substrate/frame/nomination-pools/benchmarking/src/mock.rs | 2 +- substrate/frame/offences/benchmarking/src/mock.rs | 5 ++++- substrate/frame/session/benchmarking/src/mock.rs | 5 ++++- substrate/frame/staking/src/benchmarking.rs | 4 ++-- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/substrate/frame/nomination-pools/benchmarking/src/mock.rs b/substrate/frame/nomination-pools/benchmarking/src/mock.rs index 15d9e2c56031f..da7ce57d977c9 100644 --- a/substrate/frame/nomination-pools/benchmarking/src/mock.rs +++ b/substrate/frame/nomination-pools/benchmarking/src/mock.rs @@ -84,7 +84,7 @@ impl pallet_staking::Config for Runtime { type AdminOrigin = frame_system::EnsureRoot; type EraPayout = pallet_staking::ConvertCurve; type ElectionProvider = - frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, ())>; + frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, (), ())>; type GenesisElectionProvider = Self::ElectionProvider; type VoterList = VoterList; type TargetList = pallet_staking::UseValidatorsMap; diff --git a/substrate/frame/offences/benchmarking/src/mock.rs b/substrate/frame/offences/benchmarking/src/mock.rs index c5c178aa4443d..0fde556671b81 100644 --- a/substrate/frame/offences/benchmarking/src/mock.rs +++ b/substrate/frame/offences/benchmarking/src/mock.rs @@ -111,6 +111,7 @@ pallet_staking_reward_curve::build! { parameter_types! { pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; pub static ElectionsBounds: ElectionBounds = ElectionBoundsBuilder::default().build(); + pub const Sort: bool = true; } pub struct OnChainSeqPhragmen; @@ -119,7 +120,9 @@ impl onchain::Config for OnChainSeqPhragmen { type Solver = SequentialPhragmen; type DataProvider = Staking; type WeightInfo = (); - type MaxWinners = ConstU32<100>; + type MaxWinnersPerPage = ConstU32<100>; + type MaxBackersPerWinner = ConstU32<100>; + type Sort = Sort; type Bounds = ElectionsBounds; } diff --git a/substrate/frame/session/benchmarking/src/mock.rs b/substrate/frame/session/benchmarking/src/mock.rs index 346cd04c0fa9e..253fff1623a6b 100644 --- a/substrate/frame/session/benchmarking/src/mock.rs +++ b/substrate/frame/session/benchmarking/src/mock.rs @@ -119,6 +119,7 @@ pallet_staking_reward_curve::build! { parameter_types! { pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; pub static ElectionsBounds: ElectionBounds = ElectionBoundsBuilder::default().build(); + pub const Sort: bool = true; } pub struct OnChainSeqPhragmen; @@ -127,7 +128,9 @@ impl onchain::Config for OnChainSeqPhragmen { type Solver = SequentialPhragmen; type DataProvider = Staking; type WeightInfo = (); - type MaxWinners = ConstU32<100>; + type MaxWinnersPerPage = ConstU32<100>; + type MaxBackersPerWinner = ConstU32<100>; + type Sort = Sort; type Bounds = ElectionsBounds; } diff --git a/substrate/frame/staking/src/benchmarking.rs b/substrate/frame/staking/src/benchmarking.rs index 606c93e3fc00b..7dd7addeb7363 100644 --- a/substrate/frame/staking/src/benchmarking.rs +++ b/substrate/frame/staking/src/benchmarking.rs @@ -1251,7 +1251,7 @@ mod tests { false, false, RewardDestination::Staked, - CurrentEra::::get().unwrap(), + CurrentEra::::get().unwrap(), ) .unwrap(); @@ -1284,7 +1284,7 @@ mod tests { false, false, RewardDestination::Staked, - CurrentEra::::get().unwrap(), + CurrentEra::::get().unwrap(), ) .unwrap(); From 1b5ddc6de2124213c0a056b61010648d3c849c39 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 16 Jan 2025 09:27:35 +0000 Subject: [PATCH 083/169] better doc --- substrate/bin/node/runtime/Cargo.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/substrate/bin/node/runtime/Cargo.toml b/substrate/bin/node/runtime/Cargo.toml index d532384aef5ff..07c97f8c271e7 100644 --- a/substrate/bin/node/runtime/Cargo.toml +++ b/substrate/bin/node/runtime/Cargo.toml @@ -74,5 +74,9 @@ experimental = [ "pallet-example-tasks/experimental", ] metadata-hash = ["substrate-wasm-builder/metadata-hash"] -# Test temp feature to allow this chain to be used for swift testing of staking elections. +# Test temp feature to allow this chain to be used for swift testing of staking elections. should +# only be run by --dev chain. It will create a large staking election process as per the constants +# in `chain_spec.rs`, but `Alice` will be the only authority that is communicated to the node and +# ergo block production works fine with --dev and is independent of staking election. See ` pub +# struct AliceAsOnlyValidator`. staking-playground = [] From c5efc26411ebe1ee55000643d3d5fac519d3dfdf Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 16 Jan 2025 14:47:31 +0000 Subject: [PATCH 084/169] fix tests and a bug --- substrate/bin/node/runtime/src/lib.rs | 8 +- .../election-provider-multi-phase/src/lib.rs | 19 ++-- substrate/frame/staking/src/benchmarking.rs | 13 ++- substrate/frame/staking/src/pallet/impls.rs | 90 +++---------------- substrate/frame/staking/src/pallet/mod.rs | 35 +++++--- substrate/frame/staking/src/tests.rs | 9 +- .../frame/staking/src/tests_paged_election.rs | 79 +++------------- 7 files changed, 76 insertions(+), 177 deletions(-) diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index b11dcd0448c1f..b12c488584c40 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1018,7 +1018,13 @@ impl pallet_election_provider_multi_phase::Config for Runtime { type SlashHandler = (); // burn slashes type RewardHandler = (); // rewards are minted from the void type DataProvider = Staking; - type Fallback = onchain::OnChainExecution; + type Fallback = frame_election_provider_support::NoElection<( + AccountId, + BlockNumber, + Staking, + MaxActiveValidators, + MaxBackersPerWinner, + )>; type GovernanceFallback = onchain::OnChainExecution; type Solver = SequentialPhragmen, OffchainRandomBalancing>; type ForceOrigin = EnsureRootOrHalfCouncil; diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index ae1d3edb794b2..e2351252efa3a 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -778,9 +778,10 @@ pub mod pallet { log!( trace, - "current phase {:?}, next election {:?}, metadata: {:?}", + "current phase {:?}, next election {:?}, queued? {:?}, metadata: {:?}", current_phase, next_election, + QueuedSolution::::get().map(|rs| (rs.supports.len(), rs.compute, rs.score)), SnapshotMetadata::::get() ); match current_phase { @@ -1652,6 +1653,7 @@ impl Pallet { QueuedSolution::::take() .ok_or(ElectionError::::NothingQueued) .or_else(|_| { + log!(warn, "No solution queued, falling back to instant fallback.",); // default data provider bounds are unbounded. calling `instant_elect` with // unbounded data provider bounds means that the on-chain `T:Bounds` configs will // *not* be overwritten. @@ -1670,16 +1672,12 @@ impl Pallet { }) .map(|ReadySolution { compute, score, supports }| { Self::deposit_event(Event::ElectionFinalized { compute, score }); - if Round::::get() != 1 { - log!(info, "Finalized election round with compute {:?}.", compute); - } + log!(info, "Finalized election round with compute {:?}.", compute); supports }) .map_err(|err| { Self::deposit_event(Event::ElectionFailed); - if Round::::get() != 1 { - log!(warn, "Failed to finalize election round. reason {:?}", err); - } + log!(warn, "Failed to finalize election round. reason {:?}", err); err }) } @@ -1790,7 +1788,7 @@ impl ElectionProvider for Pallet { // Note: this pallet **MUST** only by used in the single-page mode. ensure!(page == SINGLE_PAGE, ElectionError::::MultiPageNotSupported); - match Self::do_elect() { + let res = match Self::do_elect() { Ok(bounded_supports) => { // All went okay, record the weight, put sign to be Off, clean snapshot, etc. Self::weigh_supports(&bounded_supports); @@ -1802,7 +1800,10 @@ impl ElectionProvider for Pallet { Self::phase_transition(Phase::Emergency); Err(why) }, - } + }; + + log!(info, "ElectionProvider::elect({}) => {:?}", page, res.as_ref().map(|s| s.len())); + res } fn ongoing() -> bool { diff --git a/substrate/frame/staking/src/benchmarking.rs b/substrate/frame/staking/src/benchmarking.rs index 7dd7addeb7363..7e7e5b59d5614 100644 --- a/substrate/frame/staking/src/benchmarking.rs +++ b/substrate/frame/staking/src/benchmarking.rs @@ -227,7 +227,7 @@ mod benchmarks { #[benchmark] fn on_initialize_noop() { assert!(ElectableStashes::::get().is_empty()); - assert_eq!(ElectingStartedAt::::get(), None); + assert_eq!(NextElectionPage::::get(), None); #[block] { @@ -235,7 +235,7 @@ mod benchmarks { } assert!(ElectableStashes::::get().is_empty()); - assert_eq!(ElectingStartedAt::::get(), None); + assert_eq!(NextElectionPage::::get(), None); } #[benchmark] @@ -272,14 +272,14 @@ mod benchmarks { } ElectableStashes::::set(stashes); - ElectingStartedAt::::set(Some(10u32.into())); + NextElectionPage::::set(Some(10u32.into())); #[block] { Pallet::::clear_election_metadata() } - assert!(ElectingStartedAt::::get().is_none()); + assert!(NextElectionPage::::get().is_none()); assert!(ElectableStashes::::get().is_empty()); Ok(()) @@ -1245,20 +1245,19 @@ mod tests { ExtBuilder::default().build_and_execute(|| { let n = 10; + let current_era = CurrentEra::::get().unwrap(); let (validator_stash, nominators) = create_validator_with_nominators::( n, <::MaxExposurePageSize as Get<_>>::get(), false, false, RewardDestination::Staked, - CurrentEra::::get().unwrap(), + current_era, ) .unwrap(); assert_eq!(nominators.len() as u32, n); - let current_era = CurrentEra::::get().unwrap(); - let original_stakeable_balance = asset::stakeable_balance::(&validator_stash); assert_ok!(Staking::payout_stakers_by_page( RuntimeOrigin::signed(1337), diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index 62ded203fdb92..96fb59abbf7d4 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -81,7 +81,8 @@ impl Pallet { /// Clears up all election preparation metadata in storage. pub(crate) fn clear_election_metadata() { - ElectingStartedAt::::kill(); + // TODO: voter snapshot status should also be killed? + NextElectionPage::::kill(); ElectableStashes::::kill(); } @@ -640,10 +641,9 @@ impl Pallet { // Clean old era information. if let Some(old_era) = new_planned_era.checked_sub(T::HistoryDepth::get() + 1) { + log!(trace, "Removing era information for {:?}", old_era); Self::clear_era_information(old_era); } - // Including election prep metadata. - Self::clear_election_metadata(); } /// Potentially plan a new era. @@ -713,12 +713,13 @@ impl Pallet { _ => {}, } // election failed, clear election prep metadata. - Self::clear_election_metadata(); Self::deposit_event(Event::StakingElectionFailed); + Self::clear_election_metadata(); None } else { Self::deposit_event(Event::StakersElected); + Self::clear_election_metadata(); Self::trigger_new_era(start_session_index); Some(validators) @@ -737,7 +738,7 @@ impl Pallet { /// result of the election. We ensure that only the winners that are part of the electable /// stashes have exposures collected for the next era. pub(crate) fn do_elect_paged(page: PageIndex) -> Weight { - let paged_result = match ::elect(page) { + let paged_result = match T::ElectionProvider::elect(page) { Ok(result) => result, Err(e) => { log!(warn, "election provider page failed due to {:?} (page: {})", e, page); @@ -2166,77 +2167,14 @@ impl Pallet { } /// Invariants: - /// * If the election preparation has started (i.e. `now` >= `expected_election - n_pages`): - /// * The election preparation metadata should be set (`ElectingStartedAt`); - /// * The electable stashes should not be empty; - /// * The exposures for the current electable stashes should have been collected; - /// * If the election preparation has not started yet: - /// * The election preparation metadata is empty; - /// * The electable stashes for this era is empty; - pub fn ensure_snapshot_metadata_state(now: BlockNumberFor) -> Result<(), TryRuntimeError> { - let pages: BlockNumberFor = Self::election_pages().into(); - let next_election = ::next_election_prediction(now); - let expect_election_start_at = next_election.saturating_sub(pages); - - let election_prep_started = now >= expect_election_start_at; - - if !election_prep_started { - // election prep should have not been started yet, no metadata in storage. - ensure!( - ElectableStashes::::get().is_empty(), - "unexpected electable stashes in storage while election prep hasn't started." - ); - ensure!( - ElectingStartedAt::::get().is_none(), - "unexpected election metadata while election prep hasn't started.", - ); - ensure!( - VoterSnapshotStatus::::get() == SnapshotStatus::Waiting, - "unexpected voter snapshot status in storage." - ); - - return Ok(()) - } - - // from now on, we expect the election to have started. check election metadata, electable - // targets and era exposures. - let maybe_electing_started = ElectingStartedAt::::get(); - - if maybe_electing_started.is_none() { - return Err( - "election prep should have started already, but no election metadata in storage." - .into(), - ); - } - - let started_at = maybe_electing_started.unwrap(); - - ensure!( - started_at == expect_election_start_at, - "unexpected electing_started_at block number in storage." - ); - ensure!( - !ElectableStashes::::get().is_empty(), - "election should have been started and the electable stashes non empty." - ); - - // all the current electable stashes exposures should have been collected and - // stored for the next era, and their total exposure should be > 0. - for s in ElectableStashes::::get().iter() { - ensure!( - EraInfo::::get_paged_exposure( - Self::current_era().unwrap_or_default().saturating_add(1), - s, - 0 - ) - .defensive_proof("electable stash exposure does not exist, unexpected.") - .unwrap() - .exposure_metadata - .total != Zero::zero(), - "no exposures collected for an electable stash." - ); - } - + /// + /// Test invariants of: + /// + /// - `NextElectionPage` + /// - `ElectableStashes` + /// - `VoterSnapshotStatus` + pub fn ensure_snapshot_metadata_state(_now: BlockNumberFor) -> Result<(), TryRuntimeError> { + // TODO: Ok(()) } diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index 62de4a0d3d45e..3e053ac64120e 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -31,7 +31,7 @@ use frame_support::{ }; use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; use sp_runtime::{ - traits::{One, SaturatedConversion, StaticLookup, Zero}, + traits::{SaturatedConversion, StaticLookup, Zero}, ArithmeticError, Perbill, Percent, Saturating, }; @@ -739,10 +739,12 @@ pub mod pallet { /// Keeps track of an ongoing multi-page election solution request. /// - /// Stores the block number of when the first election page was requested. `None` indicates - /// that the election results haven't started to be fetched. + /// If `Some(_)``, it is the next page that we intend to elect. If `None`, we are not in the + /// election process. + /// + /// This is only set in multi-block elections. Should always be `None` otherwise. #[pallet::storage] - pub(crate) type ElectingStartedAt = StorageValue<_, BlockNumberFor, OptionQuery>; + pub(crate) type NextElectionPage = StorageValue<_, PageIndex, OptionQuery>; /// A bounded list of the "electable" stashes that resulted from a successful election. #[pallet::storage] @@ -1016,27 +1018,34 @@ pub mod pallet { /// that the `ElectableStashes` has been populated with all validators from all pages at /// the time of the election. fn on_initialize(now: BlockNumberFor) -> Weight { - let pages: BlockNumberFor = Self::election_pages().into(); + let pages = Self::election_pages(); + crate::log!( + trace, + "now: {:?}, NextElectionPage: {:?}", + now, + NextElectionPage::::get() + ); // election ongoing, fetch the next page. - let inner_weight = if let Some(started_at) = ElectingStartedAt::::get() { - let next_page = - pages.saturating_sub(One::one()).saturating_sub(now.saturating_sub(started_at)); - - Self::do_elect_paged(next_page.saturated_into::()) + let inner_weight = if let Some(next_page) = NextElectionPage::::get() { + let next_next_page = next_page.checked_sub(1); + NextElectionPage::::set(next_next_page); + Self::do_elect_paged(next_page) } else { // election isn't ongoing yet, check if it should start. let next_election = ::next_election_prediction(now); - if now == (next_election.saturating_sub(pages)) { + if now == (next_election.saturating_sub(pages.into())) { crate::log!( trace, "elect(): start fetching solution pages. expected pages: {:?}", pages ); - ElectingStartedAt::::set(Some(now)); - Self::do_elect_paged(pages.saturated_into::().saturating_sub(1)) + let current_page = pages.saturating_sub(1); + let next_page = current_page.checked_sub(1); + NextElectionPage::::set(next_page); + Self::do_elect_paged(current_page) } else { Weight::default() } diff --git a/substrate/frame/staking/src/tests.rs b/substrate/frame/staking/src/tests.rs index 00b618b9b0837..690d8983dd315 100644 --- a/substrate/frame/staking/src/tests.rs +++ b/substrate/frame/staking/src/tests.rs @@ -3080,6 +3080,7 @@ fn deferred_slashes_are_deferred() { staking_events_since_last_call().as_slice(), &[ Event::SlashReported { validator: 11, slash_era: 1, .. }, + Event::PagedElectionProceeded { page: 0, result: Ok(()) }, Event::StakersElected, .., Event::Slashed { staker: 11, amount: 100 }, @@ -3416,6 +3417,7 @@ fn slash_kicks_validators_not_nominators_and_disables_nominator_for_kicked_valid assert_eq!( staking_events_since_last_call(), vec![ + Event::PagedElectionProceeded { page: 0, result: Ok(()) }, Event::StakersElected, Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 }, Event::SlashReported { @@ -3489,6 +3491,7 @@ fn non_slashable_offence_disables_validator() { assert_eq!( staking_events_since_last_call(), vec![ + Event::PagedElectionProceeded { page: 0, result: Ok(()) }, Event::StakersElected, Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 }, Event::SlashReported { @@ -3569,6 +3572,7 @@ fn slashing_independent_of_disabling_validator() { assert_eq!( staking_events_since_last_call(), vec![ + Event::PagedElectionProceeded { page: 0, result: Ok(()) }, Event::StakersElected, Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 }, Event::SlashReported { @@ -5560,7 +5564,6 @@ mod election_data_provider { // election run_to_block(20); assert_eq!(Staking::next_election_prediction(System::block_number()), 45); - assert_eq!(staking_events().len(), 1); assert_eq!(*staking_events().last().unwrap(), Event::StakersElected); for b in 21..45 { @@ -5571,7 +5574,6 @@ mod election_data_provider { // election run_to_block(45); assert_eq!(Staking::next_election_prediction(System::block_number()), 70); - assert_eq!(staking_events().len(), 3); assert_eq!(*staking_events().last().unwrap(), Event::StakersElected); Staking::force_no_eras(RuntimeOrigin::root()).unwrap(); @@ -5594,7 +5596,6 @@ 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(), 10); assert_eq!( *staking_events().last().unwrap(), Event::ForceEra { mode: Forcing::NotForcing } @@ -8630,6 +8631,7 @@ fn reenable_lower_offenders_mock() { assert_eq!( staking_events_since_last_call(), vec![ + Event::PagedElectionProceeded { page: 0, result: Ok(()) }, Event::StakersElected, Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 }, Event::SlashReported { @@ -8706,6 +8708,7 @@ fn do_not_reenable_higher_offenders_mock() { assert_eq!( staking_events_since_last_call(), vec![ + Event::PagedElectionProceeded { page: 0, result: Ok(()) }, Event::StakersElected, Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 }, Event::SlashReported { diff --git a/substrate/frame/staking/src/tests_paged_election.rs b/substrate/frame/staking/src/tests_paged_election.rs index 12dcfcfe6751d..0477cb20e2d7d 100644 --- a/substrate/frame/staking/src/tests_paged_election.rs +++ b/substrate/frame/staking/src/tests_paged_election.rs @@ -16,7 +16,7 @@ // limitations under the License. use crate::{mock::*, *}; -use frame_support::{assert_ok, testing_prelude::*, BoundedBTreeSet}; +use frame_support::{assert_ok, testing_prelude::*}; use substrate_test_utils::assert_eq_uvec; use frame_election_provider_support::{ @@ -145,7 +145,7 @@ mod paged_on_initialize { // 1. election prep hasn't started yet, election cursor and electable stashes are // not set yet. run_to_block(8); - assert_eq!(ElectingStartedAt::::get(), None); + assert_eq!(NextElectionPage::::get(), None); assert!(ElectableStashes::::get().is_empty()); assert_eq!(VoterSnapshotStatus::::get(), SnapshotStatus::Waiting); @@ -156,8 +156,8 @@ mod paged_on_initialize { run_to_block(9); assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); - // electing started at cursor is set once the election starts to be prepared. - assert_eq!(ElectingStartedAt::::get(), Some(9)); + // electing started, but since single-page, we don't set `NextElectionPage` at all. + assert_eq!(NextElectionPage::::get(), None); // now the electable stashes have been fetched and stored. assert_eq_uvec!( ElectableStashes::::get().into_iter().collect::>(), @@ -173,7 +173,7 @@ mod paged_on_initialize { assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); assert_eq!(current_era(), 1); // clears out election metadata for era. - assert!(ElectingStartedAt::::get().is_none()); + assert!(NextElectionPage::::get().is_none()); assert!(ElectableStashes::::get().into_iter().collect::>().is_empty()); assert_eq!(VoterSnapshotStatus::::get(), SnapshotStatus::Waiting); @@ -231,7 +231,7 @@ mod paged_on_initialize { assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); assert_eq!(current_era(), 0); // election haven't started yet. - assert_eq!(ElectingStartedAt::::get(), None); + assert_eq!(NextElectionPage::::get(), None); assert!(ElectableStashes::::get().is_empty()); // progress to era rotation session. @@ -347,7 +347,7 @@ mod paged_on_initialize { // not set yet. run_to_block(6); assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); - assert_eq!(ElectingStartedAt::::get(), None); + assert_eq!(NextElectionPage::::get(), None); assert!(ElectableStashes::::get().is_empty()); // 2. starts preparing election at the (election_prediction - n_pages) block. @@ -356,7 +356,7 @@ mod paged_on_initialize { assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); // electing started at cursor is set once the election starts to be prepared. - assert_eq!(ElectingStartedAt::::get(), Some(next_election - pages)); + assert_eq!(NextElectionPage::::get(), Some(1)); // now the electable stashes started to be fetched and stored. assert_eq_uvec!( ElectableStashes::::get().into_iter().collect::>(), @@ -379,7 +379,7 @@ mod paged_on_initialize { expected_elected ); // election cursor reamins unchanged during intermediate pages. - assert_eq!(ElectingStartedAt::::get(), Some(next_election - pages)); + assert_eq!(NextElectionPage::::get(), Some(0)); // exposures have been collected for all validators in the page. for s in expected_elected.iter() { // 2 pages fetched, 2 `other` exposures collected per electable stash. @@ -399,7 +399,7 @@ mod paged_on_initialize { // 3 pages fetched, 3 `other` exposures collected per electable stash. assert_eq!(Staking::eras_stakers(current_era() + 1, s).others.len(), 3); } - assert_eq!(ElectingStartedAt::::get(), Some(next_election - pages)); + assert_eq!(NextElectionPage::::get(), None); assert_eq!(staking_events_since_last_call(), vec![ Event::PagedElectionProceeded { page: 2, result: Ok(()) }, Event::PagedElectionProceeded { page: 1, result: Ok(()) }, @@ -409,13 +409,12 @@ mod paged_on_initialize { // upon fetching page 0, the electing started will remain in storage until the // era rotates. assert_eq!(current_era(), 0); - assert_eq!(ElectingStartedAt::::get(), Some(next_election - pages)); // Next block the era will rotate. run_to_block(10); assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); // and all the metadata has been cleared up and ready for the next election. - assert!(ElectingStartedAt::::get().is_none()); + assert!(NextElectionPage::::get().is_none()); assert!(ElectableStashes::::get().is_empty()); // events assert_eq!(staking_events_since_last_call(), vec![ @@ -501,62 +500,6 @@ mod paged_on_initialize { ); }) } - - #[test] - fn try_state_failure_works() { - ExtBuilder::default().build_and_execute(|| { - let pages: BlockNumber = - <::ElectionProvider as ElectionProvider>::Pages::get().into(); - let next_election = - ::next_election_prediction(System::block_number()); - - let mut invalid_stashes = BoundedBTreeSet::new(); - - run_to_block(next_election - pages - 1); - - // election hasn't started yet, no electable stashes expected in storage. - assert_ok!(invalid_stashes.try_insert(42)); - ElectableStashes::::set(invalid_stashes); - assert_err!( - Staking::ensure_snapshot_metadata_state(System::block_number()), - "unexpected electable stashes in storage while election prep hasn't started." - ); - Staking::clear_election_metadata(); - - // election hasn't started yet, no electable stashes expected in storage. - ElectingStartedAt::::set(Some(42)); - assert_err!( - Staking::ensure_snapshot_metadata_state(System::block_number()), - "unexpected election metadata while election prep hasn't started." - ); - Staking::clear_election_metadata(); - - run_to_block(next_election - pages); - - // election prep started, metadata, electable stashes and exposures are expected to - // exist. - let _ = ErasStakersOverview::::clear(u32::MAX, None); - let _ = ErasStakersPaged::::clear(u32::MAX, None); - assert_err!( - Staking::ensure_snapshot_metadata_state(System::block_number()), - "no exposures collected for an electable stash." - ); - - ElectingStartedAt::::kill(); - assert_err!( - Staking::ensure_snapshot_metadata_state(System::block_number()), - "election prep should have started already, but no election metadata in storage." - ); - ElectingStartedAt::::set(Some(424242)); - assert_err!( - Staking::ensure_snapshot_metadata_state(System::block_number()), - "unexpected electing_started_at block number in storage." - ); - - // skip final try state checks. - SkipTryStateCheck::set(true); - }) - } } mod paged_snapshot { From 4d690b764c72b7873228f17decc5773744c81df3 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 17 Jan 2025 09:18:56 +0000 Subject: [PATCH 085/169] more testing --- substrate/bin/node/cli/src/chain_spec.rs | 8 ++- substrate/bin/node/runtime/src/constants.rs | 2 +- substrate/bin/node/runtime/src/lib.rs | 30 ++++++----- .../src/unsigned.rs | 53 ++++++++----------- substrate/frame/staking/src/lib.rs | 2 +- substrate/frame/staking/src/pallet/impls.rs | 15 ++++-- .../primitives/npos-elections/src/lib.rs | 3 +- 7 files changed, 58 insertions(+), 55 deletions(-) diff --git a/substrate/bin/node/cli/src/chain_spec.rs b/substrate/bin/node/cli/src/chain_spec.rs index e90312543433b..5641b70aa9be2 100644 --- a/substrate/bin/node/cli/src/chain_spec.rs +++ b/substrate/bin/node/cli/src/chain_spec.rs @@ -421,8 +421,12 @@ pub fn testnet_genesis( fn development_config_genesis_json() -> serde_json::Value { if cfg!(feature = "staking-playground") { - let random_authorities_count = 100; - let random_nominators_count = 3000; + let random_authorities_count = std::option_env!("AUTHORITIES") + .map(|s| s.parse::().unwrap()) + .unwrap_or(100); + let random_nominators_count = std::option_env!("NOMINATORS") + .map(|s| s.parse::().unwrap()) + .unwrap_or(3000); let mut random_authorities = (0..random_authorities_count) .map(|i| authority_keys_from_seed(&format!("Random{}", i))) .collect::>(); diff --git a/substrate/bin/node/runtime/src/constants.rs b/substrate/bin/node/runtime/src/constants.rs index 42629d53500ce..3a892e2f2b358 100644 --- a/substrate/bin/node/runtime/src/constants.rs +++ b/substrate/bin/node/runtime/src/constants.rs @@ -66,7 +66,7 @@ pub mod time { #[cfg(not(feature = "staking-playground"))] pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 10 * MINUTES; #[cfg(feature = "staking-playground")] - pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 1 * MINUTES; + pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 2 * MINUTES; pub const EPOCH_DURATION_IN_SLOTS: u64 = { const SLOT_FILL_RATE: f64 = MILLISECS_PER_BLOCK as f64 / SLOT_DURATION as f64; diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 46570a72a47aa..8e30eac5e05a5 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -849,7 +849,7 @@ impl pallet_staking::Config for Runtime { type SessionInterface = Self; type EraPayout = pallet_staking::ConvertCurve; type NextNewSession = Session; - type MaxExposurePageSize = ConstU32<256>; + type MaxExposurePageSize = MaxExposurePageSize; type MaxValidatorSet = MaxActiveValidators; type ElectionProvider = ElectionProviderMultiPhase; type GenesisElectionProvider = onchain::OnChainExecution; @@ -878,9 +878,9 @@ impl pallet_fast_unstake::Config for Runtime { } parameter_types! { - // phase durations. 1/4 of the last session for each. - pub const SignedPhase: u32 = EPOCH_DURATION_IN_BLOCKS / 4; - pub const UnsignedPhase: u32 = EPOCH_DURATION_IN_BLOCKS / 4; + // phase durations. 1/2 of the last session for each. + pub const SignedPhase: u32 = EPOCH_DURATION_IN_BLOCKS / 2; + pub const UnsignedPhase: u32 = EPOCH_DURATION_IN_BLOCKS / 2; // signed config pub const SignedRewardBase: Balance = 1 * DOLLARS; @@ -907,25 +907,27 @@ frame_election_provider_support::generate_solution_type!( VoterIndex = u32, TargetIndex = u16, Accuracy = sp_runtime::PerU16, - MaxVoters = MaxElectingVotersSolution, + MaxVoters = ConstU32<22500>, >(16) ); parameter_types! { - // Note: the EPM in this runtime runs the election on-chain. The election bounds must be - // carefully set so that an election round fits in one block. + /// Note: the EPM in this runtime runs the election on-chain. The election bounds must be + /// carefully set so that an election round fits in one block. pub ElectionBoundsMultiPhase: ElectionBounds = ElectionBoundsBuilder::default() - .voters_count(10_000.into()).targets_count(1_500.into()).build(); + .voters_count(22500.into()).targets_count(1000.into()).build(); pub ElectionBoundsOnChain: ElectionBounds = ElectionBoundsBuilder::default() - .voters_count(5_000.into()).targets_count(1_250.into()).build(); + .voters_count(1000.into()).targets_count(100.into()).build(); pub MaxNominations: u32 = ::LIMIT as u32; - pub MaxElectingVotersSolution: u32 = 40_000; - // The maximum winners that can be elected by the Election pallet which is equivalent to the - // maximum active validators the staking pallet can have. + /// The maximum winners that can be elected by the Election pallet which is equivalent to the + /// maximum active validators the staking pallet can have. pub MaxActiveValidators: u32 = 1000; - // Unbounded number of backers per winner in the election solution. - pub MaxBackersPerWinner: u32 = u32::MAX; + /// 512 backers per winner in the election solution. + pub MaxBackersPerWinner: u32 = 512; + /// 64 backers per exposure page. + pub MaxExposurePageSize: u32 = 64; + } /// The numbers configured here could always be more than the the maximum limits of staking pallet diff --git a/substrate/frame/election-provider-multi-phase/src/unsigned.rs b/substrate/frame/election-provider-multi-phase/src/unsigned.rs index 1a1245dbfd435..5aabc3454d4df 100644 --- a/substrate/frame/election-provider-multi-phase/src/unsigned.rs +++ b/substrate/frame/election-provider-multi-phase/src/unsigned.rs @@ -98,6 +98,8 @@ pub enum MinerError { NoMoreVoters, /// An error from the solver. Solver, + /// Desired targets are mire than the maximum allowed winners. + TooManyDesiredTargets, } impl From for MinerError { @@ -202,6 +204,7 @@ impl>> Pallet { let RoundSnapshot { voters, targets } = Snapshot::::get().ok_or(MinerError::SnapshotUnAvailable)?; let desired_targets = DesiredTargets::::get().ok_or(MinerError::SnapshotUnAvailable)?; + ensure!(desired_targets <= T::MaxWinners::get(), MinerError::TooManyDesiredTargets); let (solution, score, size, is_trimmed) = Miner::::mine_solution_with_snapshot::( voters, @@ -270,16 +273,17 @@ impl>> Pallet { /// Mine a new solution as a call. Performs all checks. pub fn mine_checked_call() -> Result, MinerError> { // get the solution, with a load of checks to ensure if submitted, IT IS ABSOLUTELY VALID. - let (raw_solution, witness, _) = Self::mine_and_check()?; + let (raw_solution, witness, _trimming) = Self::mine_and_check()?; let score = raw_solution.score; let call: Call = Call::submit_unsigned { raw_solution: Box::new(raw_solution), witness }; log!( debug, - "mined a solution with score {:?} and size {}", + "mined a solution with score {:?} and size {} and trimming {:?}", score, - call.using_encoded(|b| b.len()) + call.using_encoded(|b| b.len()), + _trimming ); Ok(call) @@ -556,11 +560,7 @@ impl Miner { .voters .split_off(max_backers_per_winner) .into_iter() - .map(|(who, stake)| { - // update total support of the target where the edge will be removed. - support.total -= stake; - who - }) + .map(|(who, _stake)| who) .collect(); // remove lowest stake edges calculated above from assignments. @@ -685,7 +685,7 @@ impl Miner { let remove = assignments.len().saturating_sub(maximum_allowed_voters); log_no_system!( - debug, + trace, "from {} assignments, truncating to {} for length, removing {}", assignments.len(), maximum_allowed_voters, @@ -1939,29 +1939,20 @@ mod tests { let targets = vec![10, 20, 30, 40]; let voters = vec![ - (1, 10, bounded_vec![10, 20, 30]), - (2, 10, bounded_vec![10, 20, 30]), - (3, 10, bounded_vec![10, 20, 30]), - (4, 10, bounded_vec![10, 20, 30]), - (5, 10, bounded_vec![10, 20, 40]), + (1, 11, bounded_vec![10, 20, 30]), + (2, 12, bounded_vec![10, 20, 30]), + (3, 13, bounded_vec![10, 20, 30]), + (4, 14, bounded_vec![10, 20, 30]), + (5, 15, bounded_vec![10, 20, 40]), ]; let snapshot = RoundSnapshot { voters: voters.clone(), targets: targets.clone() }; let (round, desired_targets) = (1, 3); - let expected_score_unbounded = - ElectionScore { minimal_stake: 12, sum_stake: 50, sum_stake_squared: 874 }; - let expected_score_bounded = - ElectionScore { minimal_stake: 10, sum_stake: 30, sum_stake_squared: 300 }; - - // solution without max_backers_per_winner set will be higher than the score when bounds - // are set, confirming the trimming when using the same snapshot state. - assert!(expected_score_unbounded > expected_score_bounded); - // election with unbounded max backers per winnner. ExtBuilder::default().max_backers_per_winner(u32::MAX).build_and_execute(|| { assert_eq!(MaxBackersPerWinner::get(), u32::MAX); - let (solution, _, _, trimming_status) = + let (solution, expected_score_unbounded, _, trimming_status) = Miner::::mine_solution_with_snapshot::<::Solver>( voters.clone(), targets.clone(), @@ -1984,10 +1975,10 @@ mod tests { vec![ ( 10, - BoundedSupport { total: 21, voters: bounded_vec![(1, 10), (4, 8), (5, 3)] } + BoundedSupport { total: 25, voters: bounded_vec![(1, 11), (5, 5), (4, 9)] } ), - (20, BoundedSupport { total: 17, voters: bounded_vec![(2, 10), (5, 7)] }), - (30, BoundedSupport { total: 12, voters: bounded_vec![(3, 10), (4, 2)] }), + (20, BoundedSupport { total: 22, voters: bounded_vec![(2, 12), (5, 10)] }), + (30, BoundedSupport { total: 18, voters: bounded_vec![(3, 13), (4, 5)] }) ] ); @@ -1999,7 +1990,7 @@ mod tests { ExtBuilder::default().max_backers_per_winner(1).build_and_execute(|| { assert_eq!(MaxBackersPerWinner::get(), 1); - let (solution, _, _, trimming_status) = + let (solution, expected_score_bounded, _, trimming_status) = Miner::::mine_solution_with_snapshot::<::Solver>( voters, targets, @@ -2024,9 +2015,9 @@ mod tests { assert_eq!( ready_solution.supports.into_iter().collect::>(), vec![ - (10, BoundedSupport { total: 10, voters: bounded_vec![(1, 10)] }), - (20, BoundedSupport { total: 10, voters: bounded_vec![(2, 10)] }), - (30, BoundedSupport { total: 10, voters: bounded_vec![(3, 10)] }), + (10, BoundedSupport { total: 11, voters: bounded_vec![(1, 11)] }), + (20, BoundedSupport { total: 12, voters: bounded_vec![(2, 12)] }), + (30, BoundedSupport { total: 13, voters: bounded_vec![(3, 13)] }) ] ); diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index 5b2829c3556af..ec961e15a3ec5 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -1313,7 +1313,7 @@ impl EraInfo { let last_page_empty_slots = T::MaxExposurePageSize::get().saturating_sub(last_page.others.len() as u32); - // splits the exposure so that `exposures_append` will fit witin the last exposure + // splits the exposure so that `exposures_append` will fit within the last exposure // page, up to the max exposure page size. The remaining individual exposures in // `exposure` will be added to new pages. let exposures_append = exposure.split_others(last_page_empty_slots); diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index 96fb59abbf7d4..fb7a2f6a0b7e1 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -675,7 +675,7 @@ impl Pallet { .unwrap_or_default(); // set stakers info for genesis era (0). - Self::store_stakers_info(exposures, Zero::zero()); + let _ = Self::store_stakers_info(exposures, Zero::zero()); validators } else { @@ -689,7 +689,12 @@ impl Pallet { .expect("same bounds, will fit; qed.") }; - log!(info, "electable validators for session {:?}: {:?}", start_session_index, validators); + log!( + info, + "electable validators count for session {:?}: {:?}", + start_session_index, + validators.len() + ); if (validators.len() as u32) < MinimumValidatorCount::::get().max(1) { // Session will panic if we ever return an empty validator set, thus max(1) ^^. @@ -814,6 +819,7 @@ impl Pallet { // populate elected stash, stakers, exposures, and the snapshot of validator prefs. let mut total_stake_page: BalanceOf = Zero::zero(); let mut elected_stashes_page = Vec::with_capacity(exposures.len()); + let mut total_backers = 0u32; exposures.into_iter().for_each(|(stash, exposure)| { // build elected stash. @@ -821,7 +827,7 @@ impl Pallet { // accumulate total stake. total_stake_page = total_stake_page.saturating_add(exposure.total); // set or update staker exposure for this era. - + total_backers += exposure.others.len() as u32; EraInfo::::upsert_exposure(new_planned_era, &stash, exposure); }); @@ -842,8 +848,9 @@ impl Pallet { if new_planned_era > 0 { log!( info, - "updated validator set with {:?} validators for era {:?}", + "stored a page of stakers with {:?} validators and {:?} total backers for era {:?}", elected_stashes.len(), + total_backers, new_planned_era, ); } diff --git a/substrate/primitives/npos-elections/src/lib.rs b/substrate/primitives/npos-elections/src/lib.rs index 96af46e30f63f..8134c3ceeac02 100644 --- a/substrate/primitives/npos-elections/src/lib.rs +++ b/substrate/primitives/npos-elections/src/lib.rs @@ -482,8 +482,7 @@ pub fn to_support_map( supports } -/// Same as [`to_support_map`] except it returns a -/// flat vector. +/// Same as [`to_support_map`] except it returns a flat vector. pub fn to_supports( assignments: &[StakedAssignment], ) -> Supports { From 4b2febe18c6f2180a31a902433c00c30f8903ef7 Mon Sep 17 00:00:00 2001 From: Guillaume Thiolliere Date: Fri, 17 Jan 2025 20:46:28 +0900 Subject: [PATCH 086/169] Make frame crate not use the feature experimental (#7177) We already use it for lots of pallet. Keeping it feature gated by experimental means we lose the information of which pallet was using experimental before the migration to frame crate usage. We can consider `polkadot-sdk-frame` crate unstable but let's not use the feature `experimental`. --------- Co-authored-by: command-bot <> --- .../packages/guides/first-pallet/Cargo.toml | 2 +- .../packages/guides/first-runtime/Cargo.toml | 2 +- polkadot/xcm/docs/Cargo.toml | 2 +- prdoc/pr_7177.prdoc | 20 +++++++++++++++++++ substrate/frame/atomic-swap/Cargo.toml | 2 +- .../frame/examples/frame-crate/Cargo.toml | 2 +- substrate/frame/mixnet/Cargo.toml | 2 +- substrate/frame/multisig/Cargo.toml | 2 +- substrate/frame/node-authorization/Cargo.toml | 2 +- substrate/frame/proxy/Cargo.toml | 2 +- substrate/frame/salary/Cargo.toml | 2 +- substrate/frame/src/lib.rs | 3 +-- .../support/test/stg_frame_crate/Cargo.toml | 2 +- 13 files changed, 32 insertions(+), 13 deletions(-) create mode 100644 prdoc/pr_7177.prdoc diff --git a/docs/sdk/packages/guides/first-pallet/Cargo.toml b/docs/sdk/packages/guides/first-pallet/Cargo.toml index a1411580119da..e6325c31781a6 100644 --- a/docs/sdk/packages/guides/first-pallet/Cargo.toml +++ b/docs/sdk/packages/guides/first-pallet/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } docify = { workspace = true } -frame = { workspace = true, features = ["experimental", "runtime"] } +frame = { workspace = true, features = ["runtime"] } scale-info = { workspace = true } [features] diff --git a/docs/sdk/packages/guides/first-runtime/Cargo.toml b/docs/sdk/packages/guides/first-runtime/Cargo.toml index 303d5c5e7f5fc..8ed17dea1b71e 100644 --- a/docs/sdk/packages/guides/first-runtime/Cargo.toml +++ b/docs/sdk/packages/guides/first-runtime/Cargo.toml @@ -18,7 +18,7 @@ scale-info = { workspace = true } serde_json = { workspace = true } # this is a frame-based runtime, thus importing `frame` with runtime feature enabled. -frame = { workspace = true, features = ["experimental", "runtime"] } +frame = { workspace = true, features = ["runtime"] } # pallets that we want to use pallet-balances = { workspace = true } diff --git a/polkadot/xcm/docs/Cargo.toml b/polkadot/xcm/docs/Cargo.toml index 6fa7ea9a23a92..c3bda50619c15 100644 --- a/polkadot/xcm/docs/Cargo.toml +++ b/polkadot/xcm/docs/Cargo.toml @@ -18,7 +18,7 @@ xcm-simulator = { workspace = true, default-features = true } # For building FRAME runtimes codec = { workspace = true, default-features = true } -frame = { features = ["experimental", "runtime"], workspace = true, default-features = true } +frame = { features = ["runtime"], workspace = true, default-features = true } polkadot-parachain-primitives = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } polkadot-runtime-parachains = { workspace = true, default-features = true } diff --git a/prdoc/pr_7177.prdoc b/prdoc/pr_7177.prdoc new file mode 100644 index 0000000000000..9ab0be1f20a93 --- /dev/null +++ b/prdoc/pr_7177.prdoc @@ -0,0 +1,20 @@ +title: Make frame crate not experimental +doc: +- audience: Runtime Dev + description: |- + Frame crate may still be unstable, but it is no longer feature gated by the feature `experimental`. +crates: +- name: polkadot-sdk-frame + bump: minor +- name: pallet-salary + bump: patch +- name: pallet-multisig + bump: patch +- name: pallet-proxy + bump: patch +- name: pallet-atomic-swap + bump: patch +- name: pallet-mixnet + bump: patch +- name: pallet-node-authorization + bump: patch diff --git a/substrate/frame/atomic-swap/Cargo.toml b/substrate/frame/atomic-swap/Cargo.toml index 785bfee71b683..05a38ded91c51 100644 --- a/substrate/frame/atomic-swap/Cargo.toml +++ b/substrate/frame/atomic-swap/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -frame = { workspace = true, features = ["experimental", "runtime"] } +frame = { workspace = true, features = ["runtime"] } scale-info = { features = ["derive"], workspace = true } [dev-dependencies] diff --git a/substrate/frame/examples/frame-crate/Cargo.toml b/substrate/frame/examples/frame-crate/Cargo.toml index f174c6b9054b5..46db1afc34643 100644 --- a/substrate/frame/examples/frame-crate/Cargo.toml +++ b/substrate/frame/examples/frame-crate/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { workspace = true } scale-info = { features = ["derive"], workspace = true } -frame = { features = ["experimental", "runtime"], workspace = true } +frame = { features = ["runtime"], workspace = true } [features] diff --git a/substrate/frame/mixnet/Cargo.toml b/substrate/frame/mixnet/Cargo.toml index 0ae3b3938c608..33bf7146980d5 100644 --- a/substrate/frame/mixnet/Cargo.toml +++ b/substrate/frame/mixnet/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive", "max-encoded-len"], workspace = true } -frame = { workspace = true, features = ["experimental", "runtime"] } +frame = { workspace = true, features = ["runtime"] } log = { workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { features = ["derive"], workspace = true } diff --git a/substrate/frame/multisig/Cargo.toml b/substrate/frame/multisig/Cargo.toml index 0d175617c9c23..e18e14f2626bf 100644 --- a/substrate/frame/multisig/Cargo.toml +++ b/substrate/frame/multisig/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -frame = { workspace = true, features = ["experimental", "runtime"] } +frame = { workspace = true, features = ["runtime"] } scale-info = { features = ["derive"], workspace = true } # third party diff --git a/substrate/frame/node-authorization/Cargo.toml b/substrate/frame/node-authorization/Cargo.toml index 7e55ad178091f..86a78e6e36153 100644 --- a/substrate/frame/node-authorization/Cargo.toml +++ b/substrate/frame/node-authorization/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -frame = { workspace = true, features = ["experimental", "runtime"] } +frame = { workspace = true, features = ["runtime"] } log = { workspace = true } scale-info = { features = ["derive"], workspace = true } diff --git a/substrate/frame/proxy/Cargo.toml b/substrate/frame/proxy/Cargo.toml index a36b2c1cb9c3a..3f2565abac88d 100644 --- a/substrate/frame/proxy/Cargo.toml +++ b/substrate/frame/proxy/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["max-encoded-len"], workspace = true } -frame = { workspace = true, features = ["experimental", "runtime"] } +frame = { workspace = true, features = ["runtime"] } scale-info = { features = ["derive"], workspace = true } [dev-dependencies] diff --git a/substrate/frame/salary/Cargo.toml b/substrate/frame/salary/Cargo.toml index 626993a0547b5..84c55b110c8c2 100644 --- a/substrate/frame/salary/Cargo.toml +++ b/substrate/frame/salary/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -frame = { workspace = true, features = ["experimental", "runtime"] } +frame = { workspace = true, features = ["runtime"] } log = { workspace = true } pallet-ranked-collective = { optional = true, workspace = true } scale-info = { features = ["derive"], workspace = true } diff --git a/substrate/frame/src/lib.rs b/substrate/frame/src/lib.rs index e3e58fc01b5fa..18c7bd1239443 100644 --- a/substrate/frame/src/lib.rs +++ b/substrate/frame/src/lib.rs @@ -106,7 +106,7 @@ //! [dependencies] //! codec = { features = ["max-encoded-len"], workspace = true } //! scale-info = { features = ["derive"], workspace = true } -//! frame = { workspace = true, features = ["experimental", "runtime"] } +//! frame = { workspace = true, features = ["runtime"] } //! //! [features] //! default = ["std"] @@ -150,7 +150,6 @@ //! * `runtime::apis` should expose all common runtime APIs that all FRAME-based runtimes need. #![cfg_attr(not(feature = "std"), no_std)] -#![cfg(feature = "experimental")] #[doc(no_inline)] pub use frame_support::pallet; diff --git a/substrate/frame/support/test/stg_frame_crate/Cargo.toml b/substrate/frame/support/test/stg_frame_crate/Cargo.toml index f627d29cd5630..157361dbd5d6d 100644 --- a/substrate/frame/support/test/stg_frame_crate/Cargo.toml +++ b/substrate/frame/support/test/stg_frame_crate/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -frame = { features = ["experimental", "runtime"], workspace = true } +frame = { features = ["runtime"], workspace = true } scale-info = { features = ["derive"], workspace = true } [features] From d348cb019f21b1dc786326818b5338d46360efed Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Fri, 17 Jan 2025 12:22:02 +0000 Subject: [PATCH 087/169] Update substrate/primitives/staking/src/lib.rs Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com> --- substrate/primitives/staking/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/substrate/primitives/staking/src/lib.rs b/substrate/primitives/staking/src/lib.rs index 5f0b86c4050fe..8b58ea94347c5 100644 --- a/substrate/primitives/staking/src/lib.rs +++ b/substrate/primitives/staking/src/lib.rs @@ -522,7 +522,10 @@ where ) -> Self { let new_nominator_count = self.nominator_count.saturating_add(others_num); let new_page_count = - new_nominator_count.saturating_add(Max::get()).saturating_div(Max::get()); + new_nominator_count + .defensive_saturating_add(Max::get()) + .defensive_saturating_sub(1) + .saturating_div(Max::get()); Self { total: self.total.saturating_add(others_balance), From 7ce460cbb3f798e88518a9b59bc814d8ebc66138 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 17 Jan 2025 12:27:31 +0000 Subject: [PATCH 088/169] rename and fix --- cumulus/bin/pov-validator/Cargo.toml | 2 +- polkadot/runtime/westend/src/lib.rs | 4 +- substrate/frame/staking/src/benchmarking.rs | 6 +- substrate/frame/staking/src/lib.rs | 2 +- substrate/frame/staking/src/migrations.rs | 12 +-- substrate/frame/staking/src/pallet/impls.rs | 92 ++++++++++++------- substrate/frame/staking/src/pallet/mod.rs | 6 -- substrate/frame/staking/src/tests.rs | 2 +- .../frame/staking/src/tests_paged_election.rs | 2 +- substrate/primitives/staking/src/lib.rs | 37 +++++++- 10 files changed, 111 insertions(+), 54 deletions(-) diff --git a/cumulus/bin/pov-validator/Cargo.toml b/cumulus/bin/pov-validator/Cargo.toml index d7af29a6bcb25..a919e3f68eace 100644 --- a/cumulus/bin/pov-validator/Cargo.toml +++ b/cumulus/bin/pov-validator/Cargo.toml @@ -19,8 +19,8 @@ sc-executor.workspace = true sp-core.workspace = true sp-io.workspace = true sp-maybe-compressed-blob.workspace = true -tracing-subscriber.workspace = true tracing.workspace = true +tracing-subscriber.workspace = true [lints] workspace = true diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index e8df5f4083cc9..3c36453115c79 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -1153,8 +1153,7 @@ impl InstanceFilter for ProxyType { matches!( c, RuntimeCall::Staking(..) | - RuntimeCall::Session(..) | - RuntimeCall::Utility(..) | + RuntimeCall::Session(..) | RuntimeCall::Utility(..) | RuntimeCall::FastUnstake(..) | RuntimeCall::VoterList(..) | RuntimeCall::NominationPools(..) @@ -1855,6 +1854,7 @@ pub mod migrations { parachains_shared::migration::MigrateToV1, parachains_scheduler::migration::MigrateV2ToV3, pallet_staking::migrations::v16::MigrateV15ToV16, + pallet_staking::migrations::v17::MigrateV16ToV17, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, ); diff --git a/substrate/frame/staking/src/benchmarking.rs b/substrate/frame/staking/src/benchmarking.rs index 6ffdf09208a9b..984c4aadcd7d2 100644 --- a/substrate/frame/staking/src/benchmarking.rs +++ b/substrate/frame/staking/src/benchmarking.rs @@ -117,7 +117,7 @@ pub fn create_validator_with_nominators( ValidatorCount::::put(1); // Start a new Era - let new_validators = Staking::::try_trigger_new_era(SessionIndex::one(), true).unwrap(); + let new_validators = Staking::::try_plan_new_era(SessionIndex::one(), true).unwrap(); assert_eq!(new_validators.len(), 1); assert_eq!(new_validators[0], v_stash, "Our validator was not selected!"); @@ -904,7 +904,7 @@ mod benchmarks { #[block] { validators = - Staking::::try_trigger_new_era(session_index, true).ok_or("`new_era` failed")?; + Staking::::try_plan_new_era(session_index, true).ok_or("`new_era` failed")?; } assert!(validators.len() == v as usize); @@ -922,7 +922,7 @@ mod benchmarks { None, )?; // Start a new Era - let new_validators = Staking::::try_trigger_new_era(SessionIndex::one(), true).unwrap(); + let new_validators = Staking::::try_plan_new_era(SessionIndex::one(), true).unwrap(); assert!(new_validators.len() == v as usize); let current_era = CurrentEra::::get().unwrap(); diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index 44052a35f3cd5..07d38a139504d 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -161,7 +161,7 @@ //! //! Note: [`frame_election_provider_support::ElectionDataProvider`] trait supports mulit-paged //! target snaphsot. However, this pallet only supports and implements a single-page snapshot. -//! Calling [`ElectionDataProvider::electable_targets`] with a different index than 0 is redundant +//! Calling `ElectionDataProvider::electable_targets` with a different index than 0 is redundant //! and the single page idx 0 of targets be returned. //! //! ### Prepare an election ahead of time with `on_initialize` diff --git a/substrate/frame/staking/src/migrations.rs b/substrate/frame/staking/src/migrations.rs index 36a94bfdfc912..c616ce5ee330a 100644 --- a/substrate/frame/staking/src/migrations.rs +++ b/substrate/frame/staking/src/migrations.rs @@ -62,19 +62,19 @@ type StorageVersion = StorageValue, ObsoleteReleases, Value /// Migrates to multi-page election support. /// -/// See: https://github.com/paritytech/polkadot-sdk/pull/6034 +/// See: /// /// Important note: this migration should be released with the election provider configured by this /// pallet supporting up to 1 page. Thus, /// * `VoterSnapshotStatus` does not need migration, as it will always be `Status::Waiting` when /// the number of election pages is 1. /// * `ElectableStashes` must be populated iif there are collected exposures for a future era (i.e. -/// exposures have been collected but `fn try_trigger_new_era` was not called). +/// exposures have been collected but `fn try_plan_new_era` was not called). pub mod v17 { use super::*; pub struct VersionedMigrateV16ToV17(core::marker::PhantomData); - impl OnRuntimeUpgrade for VersionedMigrateV16ToV17 { + impl UncheckedOnRuntimeUpgrade for VersionedMigrateV16ToV17 { fn on_runtime_upgrade() -> Weight { // Populates the `ElectableStashes` with the exposures of the next planning era if it // is initialized (i.e. if the there are exposures collected for the next planning @@ -84,15 +84,15 @@ pub mod v17 { debug_assert!(Pallet::::election_pages() == 1); let next_era = CurrentEra::::get().defensive_unwrap_or_default().saturating_add(1); - let prepared_exposures = ErasStakersOverview::::iter() - .filter(|(era_idx, _, _)| *era_idx == next_era) - .map(|(_, v, _)| v) + let prepared_exposures = ErasStakersOverview::::iter_prefix(next_era) + .map(|(v, _)| v) .collect::>(); let migrated_stashes = prepared_exposures.len() as u32; let result = Pallet::::add_electables(prepared_exposures.into_iter()); debug_assert!(result.is_ok()); + log!(info, "v17 applied successfully, migrated {:?}.", migrated_stashes); T::DbWeight::get().reads_writes( // 1x read per history depth and current era read. (T::HistoryDepth::get() + 1u32).into(), diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index b2f720d1c3caf..b116286000be1 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -80,7 +80,7 @@ impl Pallet { /// Clears up all election preparation metadata in storage. pub(crate) fn clear_election_metadata() { - // TODO: voter snapshot status should also be killed? + VoterSnapshotStatus::::kill(); NextElectionPage::::kill(); ElectableStashes::::kill(); } @@ -459,8 +459,8 @@ impl Pallet { /// Plan a new session potentially trigger a new era. /// /// Subsequent function calls in the happy path are as follows: - /// 1. `try_trigger_new_era` - /// 2. `trigger_new_era` + /// 1. `try_plan_new_era` + /// 2. `plan_new_era` fn new_session( session_index: SessionIndex, is_genesis: bool, @@ -478,9 +478,9 @@ impl Pallet { match ForceEra::::get() { // Will be set to `NotForcing` again if a new era has been triggered. Forcing::ForceNew => (), - // Short circuit to `try_trigger_new_era`. + // Short circuit to `try_plan_new_era`. Forcing::ForceAlways => (), - // Only go to `try_trigger_new_era` if deadline reached. + // Only go to `try_plan_new_era` if deadline reached. Forcing::NotForcing if era_length >= T::SessionsPerEra::get() => (), _ => { // Either `Forcing::ForceNone`, @@ -490,7 +490,7 @@ impl Pallet { } // New era. - let maybe_new_era_validators = Self::try_trigger_new_era(session_index, is_genesis); + let maybe_new_era_validators = Self::try_plan_new_era(session_index, is_genesis); if maybe_new_era_validators.is_some() && matches!(ForceEra::::get(), Forcing::ForceNew) { @@ -501,7 +501,7 @@ impl Pallet { } else { // Set initial era. log!(debug, "Starting the first era."); - Self::try_trigger_new_era(session_index, is_genesis) + Self::try_plan_new_era(session_index, is_genesis) } } @@ -550,6 +550,7 @@ impl Pallet { fn start_era(start_session: SessionIndex) { let active_era = ActiveEra::::mutate(|active_era| { let new_index = active_era.as_ref().map(|info| info.index + 1).unwrap_or(0); + log!(debug, "starting active era {:?}", new_index); *active_era = Some(ActiveEraInfo { index: new_index, // Set new active era start in next `on_finalize`. To guarantee usage of `Time` @@ -621,28 +622,6 @@ impl Pallet { } } - /// Plan a new era. - /// - /// * Bump the current era storage (which holds the latest planned era). - /// * Store start session index for the new planned era. - /// * Clean old era information. - /// - /// The new validator set for this era is stored under [`ElectableStashes`]. - pub fn trigger_new_era(start_session_index: SessionIndex) { - // Increment or set current era. - let new_planned_era = CurrentEra::::mutate(|s| { - *s = Some(s.map(|s| s + 1).unwrap_or(0)); - s.unwrap() - }); - ErasStartSessionIndex::::insert(&new_planned_era, &start_session_index); - - // Clean old era information. - if let Some(old_era) = new_planned_era.checked_sub(T::HistoryDepth::get() + 1) { - log!(trace, "Removing era information for {:?}", old_era); - Self::clear_era_information(old_era); - } - } - /// Potentially plan a new era. /// /// The election results are either fetched directly from an election provider if it is the @@ -651,7 +630,7 @@ impl Pallet { /// In case election result has more than [`MinimumValidatorCount`] validator trigger a new era. /// /// In case a new era is planned, the new validator set is returned. - pub(crate) fn try_trigger_new_era( + pub(crate) fn try_plan_new_era( start_session_index: SessionIndex, is_genesis: bool, ) -> Option>> { @@ -722,12 +701,34 @@ impl Pallet { } else { Self::deposit_event(Event::StakersElected); Self::clear_election_metadata(); - Self::trigger_new_era(start_session_index); + Self::plan_new_era(start_session_index); Some(validators) } } + /// Plan a new era. + /// + /// * Bump the current era storage (which holds the latest planned era). + /// * Store start session index for the new planned era. + /// * Clean old era information. + /// + /// The new validator set for this era is stored under `ElectableStashes`. + pub fn plan_new_era(start_session_index: SessionIndex) { + // Increment or set current era. + let new_planned_era = CurrentEra::::mutate(|s| { + *s = Some(s.map(|s| s + 1).unwrap_or(0)); + s.unwrap() + }); + ErasStartSessionIndex::::insert(&new_planned_era, &start_session_index); + + // Clean old era information. + if let Some(old_era) = new_planned_era.checked_sub(T::HistoryDepth::get() + 1) { + log!(trace, "Removing era information for {:?}", old_era); + Self::clear_era_information(old_era); + } + } + /// Paginated elect. /// /// Fetches the election page with index `page` from the election provider. @@ -1594,6 +1595,15 @@ impl ElectionDataProvider for Pallet { /// Once the first new_session is planned, all session must start and then end in order, though /// some session can lag in between the newest session planned and the latest session started. impl pallet_session::SessionManager for Pallet { + // └── Self::new_session(new_index, false) + // └── Self::try_plan_new_era(session_index, is_genesis) + // └── T::GenesisElectionProvider::elect() OR ElectableStashes::::take() + // └── Self::collect_exposures() + // └── Self::store_stakers_info() + // └── Self::plan_new_era() + // └── CurrentEra increment + // └── ErasStartSessionIndex update + // └── Self::clear_era_information() fn new_session(new_index: SessionIndex) -> Option> { log!(trace, "planning new session {}", new_index); CurrentPlannedSession::::put(new_index); @@ -1604,6 +1614,19 @@ impl pallet_session::SessionManager for Pallet { CurrentPlannedSession::::put(new_index); Self::new_session(new_index, true).map(|v| v.into_inner()) } + // start_session(start_session: SessionIndex) + // └── Check if this is the start of next active era + // └── Self::start_era(start_session) + // └── Update active era index + // └── Set active era start timestamp + // └── Update BondedEras + // └── Self::apply_unapplied_slashes() + // └── Get slashes for era from UnappliedSlashes + // └── Apply each slash + // └── Clear slashes metadata + // └── Process disabled validators + // └── Get all disabled validators + // └── Call T::SessionInterface::disable_validator() for each fn start_session(start_index: SessionIndex) { log!(trace, "starting session {}", start_index); Self::start_session(start_index) @@ -2346,6 +2369,13 @@ impl Pallet { ::TargetList::count() == Validators::::count(), "wrong external count" ); + let max_validators_bound = MaxWinnersOf::::get(); + let max_winners_per_page_bound = MaxWinnersPerPageOf::::get(); + ensure!( + max_validators_bound >= max_winners_per_page_bound, + "max validators should be higher than per page bounds" + ); + ensure!(ValidatorCount::::get() <= max_validators_bound, Error::::TooManyValidators); Ok(()) } diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index 917ca1ce36fa6..70c3cd54215c7 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -1059,12 +1059,6 @@ pub mod pallet { /// the time of the election. fn on_initialize(now: BlockNumberFor) -> Weight { let pages = Self::election_pages(); - crate::log!( - trace, - "now: {:?}, NextElectionPage: {:?}", - now, - NextElectionPage::::get() - ); // election ongoing, fetch the next page. let inner_weight = if let Some(next_page) = NextElectionPage::::get() { diff --git a/substrate/frame/staking/src/tests.rs b/substrate/frame/staking/src/tests.rs index 6ff42022d7b18..c57df4dec9a36 100644 --- a/substrate/frame/staking/src/tests.rs +++ b/substrate/frame/staking/src/tests.rs @@ -3670,7 +3670,7 @@ fn slashing_independent_of_disabling_validator() { } #[test] -fn offence_threshold_doesnt_trigger_new_era() { +fn offence_threshold_doesnt_plan_new_era() { ExtBuilder::default() .validator_count(4) .set_status(41, StakerStatus::Validator) diff --git a/substrate/frame/staking/src/tests_paged_election.rs b/substrate/frame/staking/src/tests_paged_election.rs index 0477cb20e2d7d..72e17bf53c090 100644 --- a/substrate/frame/staking/src/tests_paged_election.rs +++ b/substrate/frame/staking/src/tests_paged_election.rs @@ -643,7 +643,7 @@ mod paged_exposures { let genesis_result = <::GenesisElectionProvider>::elect(0u32).unwrap(); let expected_exposures = Staking::collect_exposures(genesis_result.clone()); - Staking::try_trigger_new_era(0u32, true); + Staking::try_plan_new_era(0u32, true); // expected exposures are stored for the expected genesis validators. for exposure in expected_exposures { diff --git a/substrate/primitives/staking/src/lib.rs b/substrate/primitives/staking/src/lib.rs index 5f0b86c4050fe..1e6b116e79db7 100644 --- a/substrate/primitives/staking/src/lib.rs +++ b/substrate/primitives/staking/src/lib.rs @@ -511,7 +511,7 @@ where + Copy + sp_runtime::traits::Debug, { - /// Consomes self and returns the result of the metadata updated with `other_balances` and + /// Consumes self and returns the result of the metadata updated with `other_balances` and /// of adding `other_num` nominators to the metadata. /// /// `Max` is a getter of the maximum number of nominators per page. @@ -520,9 +520,10 @@ where others_balance: Balance, others_num: u32, ) -> Self { + let page_limit = Max::get().max(1); let new_nominator_count = self.nominator_count.saturating_add(others_num); let new_page_count = - new_nominator_count.saturating_add(Max::get()).saturating_div(Max::get()); + new_nominator_count.saturating_add(page_limit).saturating_div(page_limit); Self { total: self.total.saturating_add(others_balance), @@ -696,8 +697,40 @@ sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = " #[cfg(test)] mod tests { + use sp_core::ConstU32; + use super::*; + #[test] + fn update_with_works() { + let metadata = PagedExposureMetadata:: { + total: 1000, + own: 0, // don't care + nominator_count: 10, + page_count: 1, + }; + + assert_eq!( + metadata.clone().update_with::>(1, 1), + PagedExposureMetadata { total: 1001, own: 0, nominator_count: 11, page_count: 2 }, + ); + + assert_eq!( + metadata.clone().update_with::>(1, 1), + PagedExposureMetadata { total: 1001, own: 0, nominator_count: 11, page_count: 3 }, + ); + + assert_eq!( + metadata.clone().update_with::>(1, 1), + PagedExposureMetadata { total: 1001, own: 0, nominator_count: 11, page_count: 3 }, + ); + + assert_eq!( + metadata.clone().update_with::>(1, 1), + PagedExposureMetadata { total: 1001, own: 0, nominator_count: 11, page_count: 12 }, + ); + } + #[test] fn individual_exposures_to_exposure_works() { let exposure_1 = IndividualExposure { who: 1, value: 10u32 }; From 7f438db36fc2f3fed114fc96696d3c4effb60429 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 17 Jan 2025 12:41:06 +0000 Subject: [PATCH 089/169] fix --- substrate/frame/staking/src/mock.rs | 2 +- substrate/frame/staking/src/tests.rs | 3 +++ substrate/primitives/staking/src/lib.rs | 6 +++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index 1f6ce09182b5d..1ca018fcbcce4 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -209,7 +209,7 @@ parameter_types! { pub static MaxValidatorSet: u32 = 100; pub static ElectionsBounds: ElectionBounds = ElectionBoundsBuilder::default().build(); pub static AbsoluteMaxNominations: u32 = 16; - pub static MaxWinnersPerPage: u32 = 100; + pub static MaxWinnersPerPage: u32 = MaxValidatorSet::get(); } type VoterBagsListInstance = pallet_bags_list::Instance1; diff --git a/substrate/frame/staking/src/tests.rs b/substrate/frame/staking/src/tests.rs index c57df4dec9a36..7ae0a8607d67a 100644 --- a/substrate/frame/staking/src/tests.rs +++ b/substrate/frame/staking/src/tests.rs @@ -6594,6 +6594,7 @@ fn reducing_max_unlocking_chunks_abrupt() { fn cannot_set_unsupported_validator_count() { ExtBuilder::default().build_and_execute(|| { MaxValidatorSet::set(50); + MaxWinnersPerPage::set(50); // set validator count works assert_ok!(Staking::set_validator_count(RuntimeOrigin::root(), 30)); assert_ok!(Staking::set_validator_count(RuntimeOrigin::root(), 50)); @@ -6609,6 +6610,7 @@ fn cannot_set_unsupported_validator_count() { fn increase_validator_count_errors() { ExtBuilder::default().build_and_execute(|| { MaxValidatorSet::set(50); + MaxWinnersPerPage::set(50); assert_ok!(Staking::set_validator_count(RuntimeOrigin::root(), 40)); // increase works @@ -6627,6 +6629,7 @@ fn increase_validator_count_errors() { fn scale_validator_count_errors() { ExtBuilder::default().build_and_execute(|| { MaxValidatorSet::set(50); + MaxWinnersPerPage::set(50); assert_ok!(Staking::set_validator_count(RuntimeOrigin::root(), 20)); // scale value works diff --git a/substrate/primitives/staking/src/lib.rs b/substrate/primitives/staking/src/lib.rs index 8b5265dcc2156..262181987d167 100644 --- a/substrate/primitives/staking/src/lib.rs +++ b/substrate/primitives/staking/src/lib.rs @@ -523,8 +523,8 @@ where let page_limit = Max::get().max(1); let new_nominator_count = self.nominator_count.saturating_add(others_num); let new_page_count = new_nominator_count - .defensive_saturating_add(page_limit) - .defensive_saturating_sub(1) + .saturating_add(page_limit) + .saturating_sub(1) .saturating_div(page_limit); Self { @@ -729,7 +729,7 @@ mod tests { assert_eq!( metadata.clone().update_with::>(1, 1), - PagedExposureMetadata { total: 1001, own: 0, nominator_count: 11, page_count: 12 }, + PagedExposureMetadata { total: 1001, own: 0, nominator_count: 11, page_count: 11 }, ); } From 90807e22fc74d8a7637c8bf7a438effeaafd0c08 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 17 Jan 2025 13:35:39 +0000 Subject: [PATCH 090/169] add tests --- substrate/bin/node/cli/src/chain_spec.rs | 2 +- substrate/bin/node/runtime/src/constants.rs | 2 +- substrate/bin/node/runtime/src/lib.rs | 10 ++- substrate/frame/staking/src/pallet/impls.rs | 68 +++++++++++++++------ 4 files changed, 56 insertions(+), 26 deletions(-) diff --git a/substrate/bin/node/cli/src/chain_spec.rs b/substrate/bin/node/cli/src/chain_spec.rs index 5641b70aa9be2..400c6e1fdd6fc 100644 --- a/substrate/bin/node/cli/src/chain_spec.rs +++ b/substrate/bin/node/cli/src/chain_spec.rs @@ -382,7 +382,7 @@ pub fn testnet_genesis( .collect::>(), }, "staking": { - "validatorCount": (initial_authorities.len()/2usize) as u32, + "validatorCount": std::option_env!("VAL_COUNT").map(|v| v.parse::().unwrap()).unwrap_or((initial_authorities.len()/2usize) as u32), "minimumValidatorCount": 4, "invulnerables": initial_authorities.iter().map(|x| x.0.clone()).collect::>(), "slashRewardFraction": Perbill::from_percent(10), diff --git a/substrate/bin/node/runtime/src/constants.rs b/substrate/bin/node/runtime/src/constants.rs index 3a892e2f2b358..42629d53500ce 100644 --- a/substrate/bin/node/runtime/src/constants.rs +++ b/substrate/bin/node/runtime/src/constants.rs @@ -66,7 +66,7 @@ pub mod time { #[cfg(not(feature = "staking-playground"))] pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 10 * MINUTES; #[cfg(feature = "staking-playground")] - pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 2 * MINUTES; + pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 1 * MINUTES; pub const EPOCH_DURATION_IN_SLOTS: u64 = { const SLOT_FILL_RATE: f64 = MILLISECS_PER_BLOCK as f64 / SLOT_DURATION as f64; diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 32d7342ab1c80..b0748935dc1e4 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -919,18 +919,16 @@ parameter_types! { /// Note: the EPM in this runtime runs the election on-chain. The election bounds must be /// carefully set so that an election round fits in one block. pub ElectionBoundsMultiPhase: ElectionBounds = ElectionBoundsBuilder::default() - .voters_count(22500.into()).targets_count(1000.into()).build(); + .voters_count(5000.into()).targets_count(10.into()).build(); pub ElectionBoundsOnChain: ElectionBounds = ElectionBoundsBuilder::default() - .voters_count(1000.into()).targets_count(100.into()).build(); + .voters_count(1000.into()).targets_count(10.into()).build(); pub MaxNominations: u32 = ::LIMIT as u32; /// The maximum winners that can be elected by the Election pallet which is equivalent to the /// maximum active validators the staking pallet can have. pub MaxActiveValidators: u32 = 1000; - /// 512 backers per winner in the election solution. - pub MaxBackersPerWinner: u32 = 512; - /// 64 backers per exposure page. - pub MaxExposurePageSize: u32 = 64; + pub MaxBackersPerWinner: u32 = 32; + pub MaxExposurePageSize: u32 = 4; } diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index b116286000be1..8db6feda4710d 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -667,8 +667,8 @@ impl Pallet { log!( info, - "electable validators count for session {:?}: {:?}", - start_session_index, + "electable validators count for era {:?}: {:?}", + CurrentEra::::get().unwrap_or_default() + 1, validators.len() ); @@ -820,6 +820,12 @@ impl Pallet { let mut total_backers = 0u32; exposures.into_iter().for_each(|(stash, exposure)| { + log!( + trace, + "stored exposure for stash {:?} and {:?} backers", + stash, + exposure.others.len() + ); // build elected stash. elected_stashes_page.push(stash.clone()); // accumulate total stake. @@ -843,15 +849,13 @@ impl Pallet { >::insert(&new_planned_era, stash, pref); } - if new_planned_era > 0 { - log!( - info, - "stored a page of stakers with {:?} validators and {:?} total backers for era {:?}", - elected_stashes.len(), - total_backers, - new_planned_era, - ); - } + log!( + info, + "stored a page of stakers with {:?} validators and {:?} total backers for era {:?}", + elected_stashes.len(), + total_backers, + new_planned_era, + ); elected_stashes } @@ -2266,15 +2270,43 @@ impl Pallet { Self::ensure_disabled_validators_sorted() } - /// Invariants: - /// /// Test invariants of: /// - /// - `NextElectionPage` - /// - `ElectableStashes` - /// - `VoterSnapshotStatus` - pub fn ensure_snapshot_metadata_state(_now: BlockNumberFor) -> Result<(), TryRuntimeError> { - // TODO: + /// - `NextElectionPage`: should only be set if pages > 1 and if we are within `pages-election + /// -> election` + /// - `ElectableStashes`: should only be set in `pages-election -> election block` + /// - `VoterSnapshotStatus`: cannot be argued about as we don't know when we get a call to data + /// provider, but we know it should never be set if we have 1 page. + /// + /// -- SHOULD ONLY BE CALLED AT THE END OF A GIVEN BLOCK. + pub fn ensure_snapshot_metadata_state(now: BlockNumberFor) -> Result<(), TryRuntimeError> { + let next_election = Self::next_election_prediction(now); + let pages = Self::election_pages().saturated_into::>(); + let election_prep_start = next_election - pages; + + if now >= election_prep_start && now < next_election { + ensure!( + !ElectableStashes::::get().is_empty(), + "ElectableStashes should not be empty mid election" + ); + } + + if pages > One::one() && now >= election_prep_start { + ensure!( + NextElectionPage::::get().is_some() || next_election == now + One::one(), + "NextElectionPage should be set mid election, except for last block" + ); + } else if pages == One::one() { + ensure!( + NextElectionPage::::get().is_none(), + "NextElectionPage should not be set mid election" + ); + ensure!( + VoterSnapshotStatus::::get() == SnapshotStatus::Waiting, + "VoterSnapshotStatus should not be set mid election" + ); + } + Ok(()) } From d62a90c8c729acd98c7e9a5cab9803b8b211ffc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Fri, 17 Jan 2025 15:36:28 +0100 Subject: [PATCH 091/169] pallet_revive: Bump PolkaVM (#7203) Update to PolkaVM `0.19`. This version renumbers the opcodes in order to be in-line with the grey paper. Hopefully, for the last time. This means that it breaks existing contracts. --------- Signed-off-by: xermicus Co-authored-by: command-bot <> Co-authored-by: xermicus --- Cargo.lock | 91 +++++++++++++++++- prdoc/pr_7203.prdoc | 13 +++ substrate/frame/revive/Cargo.toml | 2 +- substrate/frame/revive/fixtures/Cargo.toml | 2 +- .../frame/revive/fixtures/build/_Cargo.toml | 2 +- .../revive/rpc/examples/js/pvm/Errors.polkavm | Bin 7274 -> 7274 bytes .../rpc/examples/js/pvm/EventExample.polkavm | Bin 2615 -> 2615 bytes .../rpc/examples/js/pvm/Flipper.polkavm | Bin 1738 -> 1738 bytes .../rpc/examples/js/pvm/FlipperCaller.polkavm | Bin 4584 -> 4584 bytes .../rpc/examples/js/pvm/PiggyBank.polkavm | Bin 5088 -> 5088 bytes substrate/frame/revive/uapi/Cargo.toml | 2 +- 11 files changed, 105 insertions(+), 7 deletions(-) create mode 100644 prdoc/pr_7203.prdoc diff --git a/Cargo.lock b/Cargo.lock index 42ed88fb0d06d..23271617e927d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14857,7 +14857,7 @@ dependencies = [ "pallet-utility 28.0.0", "parity-scale-codec", "paste", - "polkavm 0.18.0", + "polkavm 0.19.0", "pretty_assertions", "rlp 0.6.1", "scale-info", @@ -14946,7 +14946,7 @@ name = "pallet-revive-fixtures" version = "0.1.0" dependencies = [ "anyhow", - "polkavm-linker 0.18.0", + "polkavm-linker 0.19.0", "sp-core 28.0.0", "sp-io 30.0.0", "toml 0.8.19", @@ -15061,7 +15061,7 @@ dependencies = [ "pallet-revive-proc-macro 0.1.0", "parity-scale-codec", "paste", - "polkavm-derive 0.18.0", + "polkavm-derive 0.19.0", "scale-info", ] @@ -19933,6 +19933,19 @@ dependencies = [ "polkavm-linux-raw 0.18.0", ] +[[package]] +name = "polkavm" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8379bb48ff026aa8ae0645ea45f27920bfd21c82b2e82ed914224bb233d59f83" +dependencies = [ + "libc", + "log", + "polkavm-assembler 0.19.0", + "polkavm-common 0.19.0", + "polkavm-linux-raw 0.19.0", +] + [[package]] name = "polkavm-assembler" version = "0.9.0" @@ -19960,6 +19973,15 @@ dependencies = [ "log", ] +[[package]] +name = "polkavm-assembler" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57513b596cf0bafb052dab48e9c168f473c35f7522e17f70cc9f96603012d9b7" +dependencies = [ + "log", +] + [[package]] name = "polkavm-common" version = "0.9.0" @@ -19989,6 +20011,16 @@ dependencies = [ "polkavm-assembler 0.18.0", ] +[[package]] +name = "polkavm-common" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a972bd305ba8cbf0de79951d6d49d2abfad47c277596be5a2c6a0924a163abbd" +dependencies = [ + "log", + "polkavm-assembler 0.19.0", +] + [[package]] name = "polkavm-derive" version = "0.9.1" @@ -20016,6 +20048,15 @@ dependencies = [ "polkavm-derive-impl-macro 0.18.0", ] +[[package]] +name = "polkavm-derive" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8d866972a7532d82d05c26b4516563660dd6676d7ab9e64e681d8ef0e29255c" +dependencies = [ + "polkavm-derive-impl-macro 0.19.0", +] + [[package]] name = "polkavm-derive-impl" version = "0.9.0" @@ -20052,6 +20093,18 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "polkavm-derive-impl" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cffca9d51b21153395a192b65698457687bc51daa41026629895542ccaa65c2" +dependencies = [ + "polkavm-common 0.19.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", +] + [[package]] name = "polkavm-derive-impl-macro" version = "0.9.0" @@ -20082,6 +20135,16 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "polkavm-derive-impl-macro" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc0dc0cf2e8f4d30874131eccfa36bdabd4a52cfb79c15f8630508abaf06a2a6" +dependencies = [ + "polkavm-derive-impl 0.19.0", + "syn 2.0.87", +] + [[package]] name = "polkavm-linker" version = "0.9.2" @@ -20128,6 +20191,22 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "polkavm-linker" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caec2308f1328b5a667da45322c04fad7ff97ad8b36817d18c7635ea4dd6c6f4" +dependencies = [ + "dirs", + "gimli 0.31.1", + "hashbrown 0.14.5", + "log", + "object 0.36.1", + "polkavm-common 0.19.0", + "regalloc2 0.9.3", + "rustc-demangle", +] + [[package]] name = "polkavm-linux-raw" version = "0.9.0" @@ -20146,6 +20225,12 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23eff02c070c70f31878a3d915e88a914ecf3e153741e2fb572dde28cce20fde" +[[package]] +name = "polkavm-linux-raw" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136ae072ab6fa38e584a06d12b1b216cff19f54d5cd202a8f8c5ec2e92e7e4bb" + [[package]] name = "polling" version = "2.8.0" diff --git a/prdoc/pr_7203.prdoc b/prdoc/pr_7203.prdoc new file mode 100644 index 0000000000000..96a3d19472e9f --- /dev/null +++ b/prdoc/pr_7203.prdoc @@ -0,0 +1,13 @@ +title: 'pallet_revive: Bump PolkaVM' +doc: +- audience: Runtime Dev + description: Update to PolkaVM `0.19`. This version renumbers the opcodes in order + to be in-line with the grey paper. Hopefully, for the last time. This means that + it breaks existing contracts. +crates: +- name: pallet-revive + bump: patch +- name: pallet-revive-fixtures + bump: patch +- name: pallet-revive-uapi + bump: patch diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index 1284f5ee8947b..49a27cfdaab2a 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -25,7 +25,7 @@ hex = { workspace = true } impl-trait-for-tuples = { workspace = true } log = { workspace = true } paste = { workspace = true } -polkavm = { version = "0.18.0", default-features = false } +polkavm = { version = "0.19.0", default-features = false } rlp = { workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { features = [ diff --git a/substrate/frame/revive/fixtures/Cargo.toml b/substrate/frame/revive/fixtures/Cargo.toml index e17bc88a3847a..a6f25cc26f3c0 100644 --- a/substrate/frame/revive/fixtures/Cargo.toml +++ b/substrate/frame/revive/fixtures/Cargo.toml @@ -21,7 +21,7 @@ sp-io = { workspace = true, default-features = true, optional = true } [build-dependencies] anyhow = { workspace = true, default-features = true } -polkavm-linker = { version = "0.18.0" } +polkavm-linker = { version = "0.19.0" } toml = { workspace = true } [features] diff --git a/substrate/frame/revive/fixtures/build/_Cargo.toml b/substrate/frame/revive/fixtures/build/_Cargo.toml index bfb9aaedd6f5c..483d9775b12a6 100644 --- a/substrate/frame/revive/fixtures/build/_Cargo.toml +++ b/substrate/frame/revive/fixtures/build/_Cargo.toml @@ -14,7 +14,7 @@ edition = "2021" [dependencies] uapi = { package = 'pallet-revive-uapi', path = "", features = ["unstable-hostfn"], default-features = false } common = { package = 'pallet-revive-fixtures-common', path = "" } -polkavm-derive = { version = "0.18.0" } +polkavm-derive = { version = "0.19.0" } [profile.release] opt-level = 3 diff --git a/substrate/frame/revive/rpc/examples/js/pvm/Errors.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/Errors.polkavm index 77de4ff3b1b3fe1f378ae31bbba24ddb38cc6300..48de6e0aa0c6cc1604008ba4e65c237dd557675b 100644 GIT binary patch literal 7274 zcmc&&4R9OPo!^yY?bAxOSBmUi*^VPCn?wnBOo-Cfx#l=GqdZGF%4k*7y0lzkV&nBk zqF_6Co!xfK%BiIkQd>B9Z4R0VY5kRBxPh6NgzJRDHFC_%af28jej;gYB{|J$h0%&Az z6SOZutAZAVb|198b*-(t>+3q|cJGZH=vw*L8mp^c`PbfbsG+f;V|RN;tgWuGp}nE5 zb$44s$HBJ!?J(N3Z{NX=y8BxjvKx_+MY@OTS`Rkt?r5vq-~K>DTU$eYp3`LbEM#9} zqqV-_Kx?ea`ozRnOvaY`sD~((8m27tR(coxUAmhNn%Yd`rguz#ZT`Kv%JN6c<9XYx z>re{4i-v65*|*t~_Fvd59D5yy9d9|P{5$ghGv8k@TCh-n3cp=w=bqst=R3|4*J0Q8 zRl8Ru+~e-|+;0}~#aH=_!W}|=*AEYI0Vc|REx=L~ed@@zw1MKiYkE3oYrX;lY=GH9 zZ3FO3&nWJ{WuUKD0*se3rF&swmH|sTU<*(jOdWGlAFJE~trpGGYVjO>tcWsQcT3k{ zSY`EhW(MtyqOVbZGZl`OjFFp3; zO9!F9Mc0}19kCZKz$@j9r9123Hn#5I&WJ;$e*UXP3_*0{}rl6 zZfeh6v_8U>&0Ln4f3t(8&~b%a3FKT4Qogsk_1hPpK4O-te>(F0!}%y${eypb>aiz* zkbqUgul<|%zd&_|IzrjK)a z%L4WKX-03M=+`F^=(H{)`gNFW7U+mkHm&z94a_m`+J!RNaiajOR~Ud_6MH!9 zDa4)v?8(O-2lm*phs7S)1Yr+@Jyz_=!yXIvn2%G~W9q^l`ejO&7bv}Dp33~t?nald zUcE|`K?inbj%4pXFgh@aZAd~Qo8x+pil4V+*`B8&oX-U;+j1<+^Avb7%&+lcz8v$b zF<*xHQp}fN-h+7o^E~E@F<*pvH|AGi-i3K5<~ht4V!i_r{zFn%`2bRd>MXP3?gK00!A^5O_OdOoqyba;rqc;1$oU@j`QsO>2xyxyS0>-(T3 zpmozZMy@mI`YvKd$M^+^T-Qwy+X6!yR$CAqSL1RwxS6W~Mr{QPynmiz&3ZHVi;<;} z#`SbkSF`p&e8B>hdBLW`%FG#7ZxQIsBnq#e1RkD+HVW-HeXMl0=j?3#KdY01dWKV9 zKBC@EHQ6dA+(WLM?#)d{Skzj0>Jr&0}~%U zXT3F?JFQDNTlT?`RiIoEZc{rP)@=QZPHLO=CQjSx4BjlvJT|I<0C zM=1$?Kkr6f>h{(TBxY~k^)BFx^ipqHKIx@4PyMgNoP*(Oy%c!MFU(7AU1V{^OKn*X zEUsQ;@p>=ymt1`t_MHI3@66S*KJ7jvr{O zFrL>0zTfL_yG4_zH!qvSy=Jh703^#>>;5D$wdQPoB?w`G3kxWW?hdIB1{f07BHWk% z4k*5C4v@Uq<^SrUw;}1pb&h2*AO1_(-JEIp?6*uy=2-b3f0USurkpue>7yZiBBGzF zjU9?5W0SF;gAaV-;)wldJh9>2;ZFKuoXLziE>1(*@l@U-`+su+b{I0+0e}{XFm`1AM`EVwoG%2ADUvLIvHJLIaCbq@x0p=Fz%S20dktC|+PNFXi$f%D zykWeEkCQlYE*B>tK9KyflTa^7F<1^<7eNFAQze;Stv*(zk4GT$>&=`-nK4^z2+~E8 z_q|{n9P+|*6#a&ZcLI(?iy|EU_4^W&p)}a@XcvJ=hvBq>l;j#Llw)Bv7RsWv~!VPT%Q+5zXdGb{RtMo$cXdv zE1%4ZYo7mu#7w|235vHEaRPR)qLLy5Vod?WpnQlyj)x%TK+m=*Dk(J}t0uKZHsUC% zg@#lZo=p&aXc;p#kh0bj)*{?D*8)Q&S00weQ>#p1DX z2nd~e3rDM`Yt_k!dM1=fF`2ta6>2~&nap~YK}LP9D#|1;$2Vl1%K}tn_jbckotLwi zK?0jTO^QhR^Qa;rJ%i-T&Ah!-?#L89lPjF{<{9eZ5vZZ(pq88^k80CAg>6yh*B|A) z2XUzJ)7~uGv%vOur~V7_ADASfo~3@)3rVw=6n8A?p_Y$u|L{{FdGaHD&pf3u9t{ba ztwdu>2RjKUgG1clxB$*KGstLYwPq{R*mBLjW-x)k0S}IIgJ%WE;xnUY5FE2&(vVZL zaT@E=?5qAijb`)*Src5b^1QXSu;wh0L^l(WxiRnvK?2&2G{h=1-D5VbNHd zW=EQX)foHWID(T0U*!fnh0J*-a|j0WG`mG}STzK11`v5a0J8}plO|WuP`+k&XpRES zR!CTzL}YeI$V`#f5V38s+Y)EZ15c1kgC^`EOJbb^s$|~|P?4~@=X>u-Ovk00BnkI_ z2PmSKvXOd=|62=g=J>TjRI+bB26~D726`pNe~CyS-nj=3AcBi<^(C-@6NZ;9Q&R+0 zuGAC{1R6~#QDwiTl&Z2hqd`GBSrt8xPV@T%Mm9a7~<=xnNNK*y@`D0FsJJ`SBjm0yD{UzMMQ zu0WO3&=sokIp|b)%b_V?k~7emRJfL=m{rg-O|g)`s400Wyc^IIE3sOwlOfgQlJP!p z`|l)X-G|HZ?9;^i4;B{VJs08RKm1l=-dzB_2U+WKCCE#c{euZkZ}JkcW?wDo@@9~= z-V}kqdQ%A0S#Pdd)M(IOqO*{+))JaV1zs!ZO>&lI&oYhPbO7{FZ|tKoT{gWrL4nfi zE#1_j;)4oja}mACpt5w6Il9pt-SHJVz4=)fgPmTZKv(saG)!@N>p5!C06>?=yIPE1X8AH<$^I>saz;DhBqr03XEaDav|Rs z_9_<~#;~AVup7gia)DK*kTHamDVve3RHk?%xmlSiHj;j2s>n!sl_|H86qKn|Mv_ye zTuPcV5=coqjmk5Kr{&2&TL?=OeJM+m~7|rJ6w=!HUIO zjD~jh_awxzlVW%PId@)jcq(WM6O2=#p^dk70jSB1~DEXQ!l!X-n&6A6M(_%2}gnyKvb_5W*jrFOB-ur3Ds zD4*me;$K;j3i|p(B1r8-_Sd(3`yPVw@)wA*ltjGY><_FFiE_Yq;2Og03fLqQI_7qn GsQ&}g)MbzW literal 7274 zcmc&&4R9OPo!^yY?bAwjR*LLh*^VPCn?wnBOdO@HbIoyXMtPQU9HUiD>(X+GiH+AE z*<{xada^0wD^4vrShWR5ZF6v$kk(&ZhZ{T-lW>P9Ic&$w95(|)=GuaXIeKon_RO_M zO-RW7pX85(OEZue292KH>b?Ja@Be=P?~Q!puNbDel*xWrj(Mqy$uL!xt{)&eQUHz4 zZHM+HXjRan(C&qHsIIm3Kz&_%-GM_#j>gyiwH~RfU;EeA)X~t`(0-t;{fJT5*wEHc z*LuKcXg_9XZ7|w&`0%mzy8BuivK!HnWx5CIT8}jxXgBJ#w)-0lqoF>}X&Urd=)Qy7 zto02?TaU!8PfUEpG`RC#<^iUY8D=c(E_Ofr9kz=Nnhevp=^fLbntx-ivi#2Sc;0U7 zW|T(nq9NNJ{%!uW{S|w;h-WFg+(DOqA(Ya@f=Hu*6Y!-+b$-FB+-soimh-cYY;~uuQ`qHCMzH|)w zfB4nomIGF$+9N4npX8P5-jc`VSx2<`CopcxUjYoUH$n`tH-_vL2WgbpSnTVf`lr7H?In%7fCg*0)46rhw_#m$d0mEXD~9|p0L>J%NodbPdj{GlG+4u(&#f!o z1nY{I*UiJ+&!Jspj3Y+K|2os)ZGiqYMnBdB{i{r!*Jud&XBcl&n>XY?&(x?*ZMloq zM}*Rgmlf_`?Vu@aoX{(Qoa;f#_f)rj>t|2*nw9Dwk34idA4RLb|4&an`Xmriuxi9j zKd{Fet3XH=o+a=AItG0NdH}jH1U&8G zLlxthSuS%HWe7?oyqPmxU(B1elec)ib&=8GFuh4)<0BwN%CO#y*off$myZ1LW6_00Cl&=PuESyh7W1*_z@iK(_xZ8- z!p&Lyca&o>m}OA{!Ebr>Lxp=8hAE2@n?o_jVMD-lOfyOY=2oq(Bw(-8I*S9A2efvI zDBvurfaxIswO#9^s2&DbVc6LYz#ai)HGtL|aI|R;Qg|BxUl`@<0b!s*4VuAeZC0%_ zPiveFxd@k?yJK5!5~X&^A-&5Ia3>%S>?7#1^$Z#f7&Fvf(ZL&=SzP*c>C* znRI=en$Zby2_n~Z6V$fA(1!IE#Kt@0Y8SYfs{u}L1q-}yk>Smx8T`e_N=OqrV$9X7 zJy2h;#AKeg5m=cy%aay~%}k;2mMP%jS!kosPO>LT<~z>K*Z-qFCFy4c{duIHiJy(D zC$q=cy#bE5((oN%{Qh_UKm(+I{tpWGrWHaPjde8SDb*iAm?;39pFcYB;d9no!#QSM z!P#;cj;sRZiU`}=;IL-vXLM4#tv7K}f8vELML6v9c2fULozy7&u-d0{Qhkhq9?H9s zm%6?61BE-3cfAYvBE8g`mQQ-A9kc(da2XiB)=Po6{KCA{u4NY2ywuJuz~Y8w7O(eG zf6Ubz;OoKRtcTtO%?S=j0NmGsn<{|ud~j2aHgHPEz$t+j%Q~exaQr}Hh4H*5@cmxD zaf>EVZ(cQtd(2=D0Z5j2)%{-KYR%dFN|M5o5SCCF-4*K80~`%&5#h^!0Tf?02WVdG z@_%*N+tBplI>)k{5C5_3Zo#yA_FJYEbFBQ2-znVBOgVF`BBLQP5h2giR&-P(E2b)b z3O?|O=@EPP!w+rEbjH}}hq=s{V|otKj;HdL+5fXsu)~n)ChYI&0*V+M3G9E(|KP1T zlkIEVdm)j|B6r7D*tqOwM_1(m#bt17a5M{%Okj`pctcWYNRmR5J0uAq$q|xJNCKc` zDvZ6a|Bu4Wu{mD|9#f`S{&My4+2D>t&bORQ9|6C75!!3eW}szm7%w_#-gv`!5g(^< zB9n^~5FcoM8KcxIQVf;>*M$(lz*K4GS4+mK$an-Yzn;t)lo_*C3_-d`^SO@fhGpwJ0LsU%anyuQLYh*&U}a5g48%NK0ilsa(S+Hcrk_k&Jt$#4e zYIx{gIKReUYr8e9^K($&Em~Z;%5;DJ`%zA`F)IMwd1X=Ie!q&(r^}4b-z4VT^$cwI z^yeRPZY3{vT)k1miCp?`g?s)rVm8F3nbm+SN5=r@7IyFS6<7a4JWdgYUOan0m! z6>b8CX;8eyh!e1TwN6@OK&&Z%7?ck&$ngNg9O!vN>!hUyWYx6R$VMEk)51b349_Nr zKCFtl8c12|DQgko8=HWk;wv$A0hAp)1T9$gW+>>O{?HFygu-r~(OVZFwS>GA3dj|U zsD1A@3ir-s0>dY^R-NC9NGn+UBlh!i{3{8bRxgvo{P|%->Y;`Tp^Aqq#vvfYNQ=Pg zXKMASh<-MdNpqPyX%(7+S~8jSEI;J*=c=My@^XA@*10S}MRrdY92L8q#S9YI$uqQw zWIvB864EnB&fMJFE9H*Lu;+7yGikoaO!qbhB2VgcKWoGDA43ux!9fqU8u&tx4O(8lv zBxPplYlw>76}LTf&fNP1y);Nss>rFs|O9@*42K- zAceYm)F4H=nt(r|u699(bae_ko30K*$Ls1Sbaq`m37tb%UxO}RSD%HhKv!p=Tc@iT z=yZ6?VGuA$cvnSCI$X;jW*zj*AQl=J4U(tBy8(k(snr@Wj#iT^#(V$mzgD=-AFjr; zPZRGySXz$vLPSu1`zwWecM0?!WKGmckQY(?eF=dyd8t^lua=0q17uB_BJh_qg+QH2 zbJem&gZ@&Tg`~BX(lioytwfsCEX~1H8q#zW^pG_6Gnu%JG$$BPdeYLxEGs^!a5fhq zO@mC9ZYoDNnxi|pMn{^Tg)!LaH3oE*w9LSiKw2}*vH^fDk9)x+sF7#spiHK83pu*^ z6}kx%Eu{O-!A?**ciO4X15Ks{H)Z+)PptxW+74@J4kS!=I#Lsr+8mdf*rCl?Qxkq| zE-y9V)#faz2}zqXrzQk#&Xk%!+8nE0NDWtN7uKbQcW4(1Qp0}jLVjx4t6gxUh9&KS zJvA(77kF(JrG}6;YfB|7wOKKh+@Z}DrILPawlJ0SYP0TCQqpGEr;>s;>(XX~R03%; z&QxWkHnTBRxkH;NOI7-{nGLB*uQpSfs+6>ul2oOj%@k`dOQ}kvw@R!w;{m&f2+h_7 zg}d@W4D=~r14D$^u$tNO@ZK=MwEAybpa$By1v;=It!#n$QMYW{F1w?l%|G((>JEm| zr{y3jxAga)mfw)Kg-`Vdf88H>x<7hbeYDu=+-3`-@*wgCk+R84Kgo2>rf}HD4+u^t z8!*NoY`u6_Gtrw?`ur{QRy&zDRjO;KL`0#TNdiZ`77fi*H!4){KH4PkZ#Z1Q^OZ{02v^M8Oo BH1Gfb diff --git a/substrate/frame/revive/rpc/examples/js/pvm/EventExample.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/EventExample.polkavm index 6dbc5ca8b108c1ad04cc248b735b2d7d4f43f2a4..cea22e46adcad0dc9bf6375e676dd4923c624c89 100644 GIT binary patch delta 1583 zcmZuxO>7%g5PrK!`qt}EHmx1INof|u&jukdF{N%KkO8T(sZ`l1h#-1!B}=<@E9}C- z&v}s?zl~%ksYE2(@@rbD5g>WvL{%jYDfH3<5=*%t@WE6Z0;;$n_0R(}Y0T_~3JzUq zcUEs^zL{@k-fa3deb=8BN=q|J7*EM>g=K_04YHQPtRP#HSi8feP78M`cnY5k<2e+T5{kUMo5XRZD`sI1(b`qfUjom4 z!Xy1)3iOAI#aM;?H4$7}z)yHmIyl?|w_5cY(T0;XYQKpk} z;r&2=Y%jqB?T1M`7M4OtG;3y`CYZI2zM~~-5VvG|?9_xZ@(S2}$qW+LmQh@*!aIk2 z*hvQ-FRgGGgH=ctY!CN=E$K}{vLefn__&2WIiHS?-$3#4>xgnN!%^4r$S*rde5%K? zgtcPKin6r?YftB9-^nfIF6G*tuW{$L)FBeDs-5pJ6u|Atksi(D>G=dbH*K~6hw3Ss zH4=(?5zvGlFl5=3UHhW{>`A6ivrM8>^!o5tuh(;OWf`Me#4MAqH|v`xq$K`ym!6AK zBVx8x7c7=gp7~=ti9gzPkc=42_=&(+wDuc5Q(GSxDKW$766kZjUSUSGUPGBDJt9omnXReF8jF!E>Ca^Tn4!XE?#bdi_9(X z@UU!*%mm#K0XM|38^Z5~@VOxlx*-m5C&G}6Jmw-Fagm>Lkq?(#=usDX#3!x(udE9| zB46h$6RmT;CXwQyq}1MFx|$wX@TB>ZsplZ~B!P4&joy4UNcbrLM&cTZvIEe4SSTfzIWTOg-3@Bb2c))bS zX&w=~%t;FpfbC*0cz!O*-J}_Uxc-OEzS6L-NUw z_;o**>nrueq&#|O+k7GA_A;MPUOo*O*xT-;uEJ`g7d6(mmLAbdP3ER>}ny{OQNL>F?c+<0!1 vE=K4=h*q?CP%UR7W+g0%ESs16_g2Eha}amsLD_&6zqiWV?eNt>&$Gfmk)k}n delta 1583 zcmZuxPly|36#pi>+ix;i>uj5B(%sq}#XngQ&1|eq7nG<_5(_2SDz4Z=3W1rVWyn{- zc@qn1^^k0)J1zv=XcxWx6%VTvZ$b|}Xm`8u&8$Ta9hiB; z_ul)x_j~VsZ!NqQzVMZXFf>{mqb1>e^ycvbxgZ2ap8C^$0OoJFws@4E|&}@iHIt5Od2rpC&D5>5H^03n2~i-9a}Gs zCuxcVm;whqc{Dm)H54znz;W*@7s;Y7g?WO{+Q+y5T&^F>J>XM$7@x|+`RoS;bp5U9 zaArHfUETY6I+f%TBw(~Y8j5p9>*~<{8dJe9*)}^VCr-S8c9%hgYd|vMMzNEcdd=Gf5A@#naSOC8#`v$C3VJCC!#Jtf#9HtalSBI<`2_d`Vd5Me*WZa>5>%Zo7PBTxCr`+VfbeB`|~A9~VY&IOP(a#DMd-xEzw&epp>r}=bqPxIa(i)A>KOqn5wepF z>%7!k7{X<1#(LGpdhzURh$n9cW=BO5n58lN1SMQ3W{jN^S)FGKaz~z)XIV4F780x; p&&DLZlrri`J^h?R&b4D}H;G`P72l<(ynXZS{J!t)R^L|N z;-}2Q4C7=>zsz-n@YDH3qe9*C$&%9=KMMt&lM$4l2x-sq;R^(wlX*^JNNbeg#jo86 z^83F?MrEYrC2*Yi{jVe&m66lZi2IRb$(zP!3esj5Nfv2-v<3RoGAE}+)D%%BqEryq z(Fs;lrdnaDYN{Gl^=5@OS4gu=#>iDpW)Y`prsH;o2xT$XnMI(%DG%v5LBmMP&^}I* zC;&=5c(1?r?T-u@;6&NpkaldiXag?hA&!_H#D0IQE!le{BX5v28pw~dP|3Tloga|5 z&7nXFY2#+#u_mXOYsEO#F)n&fw86q20X>X>9!`+b;L=6sS~Pe`6UKxv(!xjyBiYs3 zCp0-DE#^nJ!KHn5gx$I;UAvIvp*AOa02Gx2yXUJ!K-(nA(C1+IGrid>h z5ubwvc9koTS;t3cHL}q98Y|0m^H2)KKoA_+&E}6}g#+Q_K&6Xz3kxGJj61@xg^~Ra zwCrzW$(`GpFTZ6-xH})Il&VgsT2j^1IhiXrS81~r$}PjRROVvM_0ZCC4EAI6BXS)$ zEG-zD!Mkw&9qJf!4_r|eZRBa*n-AU`m$rF@?tJ9L_SX!#QMAo}a^rv*y&W&Sb3Mbm zQ?f6?za`rVqMPg#Q!A=URgI~-S(0ruw~0#*YByJjIJJhvspqh&upS}*c&oJ8ntUNQ eOu5eF8kMW=hHHBtdIpYmU71)$5(@@Yk8AVx&koH8y&k=Z*6(=dAEmz^iuiYr} z`@c#?Wx@-xIL-Y2P_i+ZI3h;*?CeznxAZezO>A;6iHJgnMhJWTt_EZ zO_^$msk*5eR5imI4VOq*CFA5WCo_oC0@HE3NQAPO>&zg~;FQO7oTYK36=@$QITQh< z9=zAz`)<2P1~^f6Fr*zDc5J}KJjM~zquB3HwI%z2Wa2H7#v9}(TF48oYG()JO>-!c zN7{rLd8WxJ=2|IEb&Q?hxi(nYBcO*7(8CGx8eH0Ot|X%uG+|5%qacjDFmhe3eL`hV zTFg&wflK@92)j*p+O;b=9&2-=M?g_I(A$Mg6_1229++_4J-^Lj61@xg;Dwsw9;>6 zxt-gZuf7*aygMKHysA&CMou*gndwY5T%lnjmRW>pc{G~}*JAUFDcFzGPsnxPu(V)o z2JgZ7_o!ox3$7@OE-QK~n2TPYkhXb=?tJ9L*0&Z#m?g+n?C^*9Pw46haUc329}tNsl+>)ghYh6mnZgB%EZZcji>5E zUjTy`!h6s@f)M|!blaCOyhqX@tRTLUhYxrD?Lnp18&o2+eZ08H^<+w|;SDOCN3?wb z(cmm9wO>Nxt@V+h7L02_T??vOFzgs%v4vxX&dfMVs4N+-mPvI6SMxZAC(%AEA)Xq2 zAsh}7MVCluBZ|inGyB}UQcfwQly}S$&7vB`)@Z1OApXZGD`j1HWV@gu{(v|Pk3bxp z6rbZC+{KJuCzbWqhXNwU!TX4;><-}&(WRC**Q0o6t~kSgHp5Q#vdaa;Dp_QWS`(8q z<+3taxH?1H@CN)4{FYn{=PagUCq^A}m?or9U?YUZMrO*{d>bpsZc1SZsp?{u zPyi>byNtB%DHsJ9Syb9lAD^y|yhzJ3btPu>P?s>HkGjrTr}%TB^J$jsWiu+9jkCEh zo7XLB<*Toe>RiB@26yFxQhhz(PJ6Cnq0Q%q(l)& z>+@g>M&R>Lw?3x{fEf@2T!;V_P9cQ3P#}b4u#{S}A5keS(DqXT4Vpr!Ju8eCzuEA` z3{Auz@z@%Yg}OkKx??D?UgWu)v7d|fUwFHA?uSCO|98)S<2vSI{mv^lk1qu$`j|nO#fYNfQuXQw}1+6>IY27-Ej0%r1cQ2mCsG;AJCuiOP zj=~V!CPN_Lv_#D?OA~6U43N`9IE#GIcD}zF#htKq82AIxS}A6#R)HmSYnmnFn)z53 z(YZ3Bvr~x96dx1f^^YN3a)u@1%!LR5b(NLlY&uLq&rm_aek%cGK?;mauoL0xc>?-oC)Mij z?^UtmYv2S}is8d+&P>h(pDM^!m!4{c?Vef#oW_h{s6&ub>cu>U9wUST84>5e+VdWO zaT6qa1I9HNb)@)FApjPlf>U@=NgM#hmCQQw!;(2&+W%MHOC>L}4$S={ivREt8+ZDV&dbniKtHzHZfk9JBxnzGWZVLQ679}Q?xI}1AXaCI3wlfQ zw#wO>9pUW(;geHrtz9qRS8LAVM4zECs2JHTT9U=CBXb(1fVSxSiHm_kjvdtc7 zu?Ih8tK08*D9F_cxfMkoTWRYoL|onF#Xafug{2wnw_Csd{b=>*RsjuLe~;qw>Q>tP zH$6JJ>ZJUNsp`^Kdtv#pRgc|-T5(D7#lTSUXWNMErH2_$=euC$h^tG_yz+h&|7j&x zytr-0|4V!jR*XEq0$cg|JF#+?{?x}|$D`g?(4W`NZtE`toyFH54bexU{x>Mtu-;$jT`@Sw189XgGk)uWG3Z|~YE)8ayG)_be@ABDmDng@ zr`QXO$?W@tO-Q9Tx@vDUS6;=nqm9dpjg_xwNNrXeKVFAxYh@VKhMOvGbLB+KlHFXh z8<&66SW#hiW9`+Z+8JEg!&iPu;L=<9Uely88NX_f>a%$A%GGpptohrlgVavprI$$U zrNDANP&!rEmPluE_Z+mCb3uWxL>$^8p;0t-JUpsvHoJ;}m2g1Nhizg z40p93__lOj6=3`_4V1((cLxB)v;xJfBNEG-}bKVi+4McgFRceclUJI7xA8f zjvWKrt*&^F)w8u@Ao)N~M_g!l)+yNEI2Oiit&~<-b zkMoAza$e~}U41)yItHw+2X}1kv89*WC_owcN z_+#-n zucmuL>3B#iXK37@i5N|4v^P|o5QN4< zUaEWhb#FrVcIK)VU{bfKSIc^}q*n_%-@VI>%em@P|J_a)v)7kCl-`w2bec&i=w1x+ zVhK&i4-Hq*tej8zXi_SU^EBy$SM|CHqSuWAae>Cy zo8#$y=>*J!wlW_SmMuHK@`bYzo?oQBO>|15(=j>|qO(R|S75mKqEMW12PQGfXx3MJ z*`1%XM~n(@pG(8Y^@db6h<9H-2baUmv%>($@%aX!im(x31%@D8IwgugVZ9BPH;n#2 zU+zx@tctS=0v0%(brvN^WUNcBV(1G~qMYgJ=S**c%Xx-4hqC2auO`sc;0n6&W>5r9 zH5_=T;Q+%10k>6v-uVqR^Pt5{#vF9oWp05pdyRD5#{>nDWm555n8?OMubgs z>kNnn2{B*Cz@Qm7zLtUXtPMa2m@X|pVu?y-B1EZRCNv8F8y9e0^U0YAzXl_R00-hH zkfI4Kkfli@FiCr3dgBl=XC{a_Jx0u_>_yD4I*j+;V`)5=rZyTj7x2A<>NhicH5s$8wgURCy}a+NAyuF93FT%pR6Dqp6`D^+=gDwnHr znJSB_>{eyy;Z?a*l}l9FrOMo9Rc52A%sfrZgro{Su@s9INaZPFS_@2$IsxXaTYjIt zl1MISGvm{2fASz}CicUlCm|;w$00>FD7X+@hNOJ>d4!h%wa>Y&TfnWG(drghpI#?( zi+Y`89|ZeAxJ9kb$H_V9GZP9xZL*u|xy4+ZaPSA0ix-igZ{cXdW{%V9S~(Z6L;C{S z+c?AzCo)m~b5<0X&DFjUPQ>~cdfiJ%LI)w!#EkDJcKV5*zzHTC$4NHhMFQOfBNLiz zGLt8fx9cA)r3s^5UVB@8`dbBExdY$kYS(Hz%?C+i{lY0b-88;)D$28^m<~k%_Cpeo z&TkWlofiTzX;5U;35iZ(-3EPuteqQoi+0#eK|dWqp{Hk^v^_apg!6Nc9)I?^*nRGdx28@rO7t?=4aMI&m-t zrS_JOb;n8Tj~|4uPeM*WjzfxobBWcaA7Y|?*anO?GoVYm;i15G-hdn{Q#4NM%7PYmN0!pC-=ad`u!Hg`G}gZd@0 zyCk#oecEkpbX#G;+9XB>yjERPUK|%GrN3LuzTl>-V0qS2n55 zjbq>V?l&KVErM1>IcS)P*n`7sF?jd5A+^qZ65-!HZR35u1Xwmo-G?WCJpCQ<4d+FL zou@IpgZQ@7UqW2F7GvFGKLz4;O>zNo6ax<8EW#)>F&sRNfEuN~sL*gaO_6HMgkebG z5C4h%53f7#*`+0eNJBZ}L@ly+khD34M>dtEwgB~FyGG~c}AFj#(i|yJ%1a2 zXE5e%2m|kGfzN0XGq3dAH%P~61-l+`Rs#iADogIZFgw9%pmVV zXG4nq3SF@!#sx*(JdCE19pn1?uv30gT`f-$Zku>rlJgbOqSOX;MsYVPRZ*?>Isf&U)@(MrS4kF>kujyUphTxz1b0P2yS-hD zes5mW{Ih#Wp)wLKD77<6?MQn8S0h<1TxeIG%4mi5Xh8{Ql{d$XyKpqZSNr|7H7!Do zOR8*nRt<$hg+lw22b4W6t*y~QI9iz2YV|P2iH2KSl~(@^x<0E%3tEle-=Nnv=!nz(VV7gk1Vvmu@N`8N3jsI_n`ihE?R=>ZZ61sW|dzFU9 zhWTvvfb!GdwUhrAFK=)?}21$1_sPkfMxXYR3y<1QGcU DwkV1b diff --git a/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm index d0082db90e5e398832e4a32a9ec86dce83d16dd5..78455fcdd7c64a3a1f5e93b6d62cd03b46eb5953 100644 GIT binary patch literal 5088 zcmcIneQX3fRfi9wp*A|&mq_Bk5#2plN60j2-O zPLL0S+z;{)NH54BNC~6@vcIdh*Ooo$u7R#S`?E)eR^NKFUFp@g{+{7(`>nfA>mJzC zKalO~(z^S)59}G}dANH|Z})+LzV3meeTP>y{rkJUyT7}ucTat?A5`=_{P59%u6@1T z&JKHKS?TAydXIMR8R+Xe+<&0Eudh4pZehj_yX?cqjy9&dkMw4T8q2>3Fk@f(9QAq1 zpq``}m^+zW%tOow^H=whdp~=c?MIJrW$p*uM*boGL!P~!Yo1RvZEgN+^HlSLYhGRR z)wN#XcH!f~QQ?b1dg%DD5Mh)2Mk-yZF;)RStp0ZgHQw@yAs}OY{wN(`BPcE~XYXYZ#qbUEy{tenA9VYvhJ>4q z3ob4$H2#4eZ}Be~LX9%kzepKv7B#+pk;=C$rrRQHEQ0)T0UcOMuuszw1X{K#;>;N zYTFUDEu*&WrR#^@bYgng(4=wkDj0LqJVobbaz-w*H^MDNSl&%C(n!HeiqgcOpY+&A zs2wJyc8q}(K}INgTwctdTuh%ZOv#uKh!!#?5YeK>L~b&d8KG=N9zW>vs2vwz$1@;P zAg8FeF)KHm%bfbREfE$J07yJ@J;ClGS~xd2LV@=pD8lhHeHZdlOae6n6jTE^kq1oc z8=8K4Ey2nWmSm!^l3T`l&GXlitcx1Wgd;2+;Ubg(p8()%05AgpGXO9H05dlM-1;c! zdd(f-0N@A$j;x|=WKwt8Gpj&5C>uR-2mjqt!?)ZyZEpne3_YrS;WInG+5FoVe?TR6 ze)jTHUwx?|xwH6}lRtdiy}}CRCM=d&p}ajSKJ6x2YIN`uqATuJJ9I|vI0BLZ*}KfL zo0Qprr)~u}0}fe+JVgo;IGGd#LyM6DGBkx0V0-8*jftJlzzu4ykfn(?HM)McYLZKhXthHCK_C#@8y54=FQ%V0E=b06f^i-h z7jw_$GL*xWdQ|5$Kh3*FwJ;6W0_VaIf+PTgpKpYBfDtYd0#NPy3~O;1qK2l!5HqwR zFerwWfgxpRdza(XyY7ty`zTXS&~1(IqBBaz2$*z%UV;>2Kc?)*DSAoixJ0AzOpG=% zKY|4>+S3-jG_IWj6KeE0C^fWSgZ{XoEz(NI@8I<%m>z-gFOXR;(L7L$M|6_?vrWt- z`};<DrD8-)4? zF&h`!_rvy|Q{ZEAC~DcPgd#;UO122h%AVA#683qpkH!lQ|jEXE&OYetp(wPD=Xh26Ix-XWPw_NUzGeC77kCUKPz!Sg*F3 zrI21-Zq1eND*Y@ors{^BAiE7{SR3b@+xFh$T^T* zfp+!ex1Y6iPtv7|R@%_6cl zW^r6?inpdYOLx`stTogCK+IiWYsVpFrD|pFiq6%3#Lk6Lt$|-C@N@UuN$konm*5VA zLn6@mY^wY-!8R_DOk9;DRT5MQspw;h@koSqIW3RB9~S)ndch4jlC&2xVJvd=sx>6bT{=LG$7yE%jO zSy`_N4wprT%WV#q*E?MHI$U1oaJkjta*J*uhs$dnF0XO8-0X0<$>Flc;WF=VnbTiz zsLVQ4Zgi;ZHg&&lxlCQstp-yUbc-B z*$&e3iVL?t1xv(hjtgUCN+HuJJNWyux$G0!8DP(NVRNm3pj8|n`fzQCg`OH3KI<%N zUF3{IF}v(sL{`BwcaAd-Mr-HT1(UVSMYK?05Z}m&0E0nC^_f{oy!TUx}30#^RXnlH6RtuFXF5Ld~-x91JG{>QW z!=0RbrRx$FMJ%>qaXl8jSX_t2RxGw)QNZF_EUv*~GZve$=)oe7MGlJyi!2r!vFOI4 z3yTd{WUxqM)&_QHvkmOfCL7qHjW)1DJ{#Df4K}bt?KZHw3}}!HbQKbq?By1hmX_?_ z4CG*}Hp#({g35HI1ZX9=fj6O6Lh?$o2jM?xh5e%$sGU(G6N?Ms#lI%lB+SEYqTh3F zGn}CqsMQcPJPX|?#ekEZE^K~1wEfK$@7^&3yL@l1L{E%N$H`0z@_sQpk)4JF1-c~j;2L!k=X`8iPblQ-%nSOpHto3XqJ%Nw!m!}10! zw_{nxvV>(3%WYU*k7X~G*I~I8%Pm+Iu)G$_Yp~pm6!8_O;% zH=Lrd%nV_f{vkAL3ON*;Kko6Ow{EyCTswv!ZYxt@Y~_N{6T=)zpbvmu(cGY&zo0>h z3-5pC)dU-;8yf=jvAEff#Z6e;h(#Y3H(;?Hi!v7fH+1l#U$59_H)(bzu9%QvDkf$V z=!w@I+kz{$MZI9#mthjHT(%9U_y4o)ySK1y>8fK}r(;_xF5IoZl3@P?lVDq_?zeYg zS{lNr8{()J0;vZgsS85s0K}3%;_1^3JD%XH8)jVn5K@OBrn+8>}qNMGgc=h6S?=Rttdi9-)SL{2mAavC!ULOf{zP?hGcn)emwIlSKm+g|Z zV*v&_E_D9vW$LMXklP;gCY7yE1@6pu7K_D79&O1}t>wy@$>7#fJ+p4pj|2 zS}dLkj-Lr8-ZvgpcQMSeW^drWzzL-@U;lOj)Mq*?ghz0D=c7eo2Wr{U*$I30wU<^-I&tG@1UppqbYMKVO)Uo1ty|U8 z!On8U(Yvg>egc@5++B1|SXmE7Jbu#}Z$Kr%&M;WCt+R|fZO0rt)3K!-9IpgRWkF5u z+U8ihq7gPPUtmjlrh_{XoZhvpZ+E#BoLUZUxv{03SIRdIbC#V^86{Z?!lH8UG!EG2 zKL{&Yw}GP7V|L_&ba30&6R-;O-I#~D^=fNCU7b-|W96j1Y3jYlT>1$xoZoP;(^yD=^ta0*cd`uBnDy|5+D1Ze6-nf z=cJ(sDh+Lso_llez3=lr@ALaTAC7(RE{d94LD_#aOZ_@Zm8qzs|FwE+#sSg@;sv=5 z(52O<$1Cj?B0omEs*=fnPR9jEmww({|HJ9ExA8bo4y>)l=wp(xQU0Qq3 zw(gz>yV|t&uJ&EqdO99x-`3f_tEa2IXJ41Tr0KKo*6!~1w$5$U$!<{5@xTN7dfIk$ zw%a?bnFXc$+dB8PZ|mu5)4O-IcXhR=oDIyd?ywFY-dCGy-`n}1SzG*rj~V{a{nUP{ zk9vx#VQyfyF!wS2%m>bS=T7!0+l>sa$i2v|=I`Tgb!~TDbbY^Wef_=lPv5-a#}ala7&&Xee`_*BiNPZJNW1{XTrp44=A_wYrniT=`7p~nNm|;Ub(5TQAmb$+))8vU zD5bUxgXBQ^DSAkr>peV|I%tea#vy@d0pk!NTEsYHj+yCx%2MR=f-aZZavF9#3o;IJ zg!%%r%w98nH!L>2As$PrqvB~ zzq=S`D_#yzz0Kg0Y%mBd56#zFr3c5aY zhB*K@jDRCcC>!bIP1ej3&^F3ON6f~5t5ov~r>SiZBc7o%+7Iv9{PX%ZUVf2^Z@%~Z z(+A$JNo>x&bNHplor|ncuE1h}70MU0;s;KmB{P}tijJ65Z6S=>vKJ%`vVDPNS17Xp zPhJgh1{|^gd6Hx$a5BjXh887RWM~S>!uG&pweii*!VO+^BsSmmQ1f+va(orbp8_h| zmivF|Ff|J=2hR{~)B=`h!!SgNmV-ed@D`47hjoNYnxcwf;+aPma#BmFW;8iT0v-uB}f;~afBN3DI3Z%=5si_`UrKgVU zj8<;8LjCNiI!FprT~wtDtVkLi!AhPx%dt~8qw;BVHd~jd3pz+TdU6n*Bz%Qf(QtAA zkv)Nvr(p&(Dkr)ks zyqTtKu2iGi)VwtB$Y?^DZbOjc? zY)wzo^F!JZFyU2t2$UMy>!3eoXmhmE@)vl08>ahVdYFYalQZZ5xrQ$ zq@x$&tQ$&Dj2u_Uc#=#+$z&|l7s{4Tq4ES5It=y5DmZ7j0}uwID1qLh%nZ~&h}oFX zyc4#+LV=ITo=C}JB@`)=Rzf*oR_L%kBVmsVdwA^Ou!qH-TI_LRj{|#Zu!q4O8rK!Z zBKqvw!dOtBT~inf=(DQ}V_tpMQy7!<*;R!xL7#0d97g(#tWOJtd_|H?uLkAmZeL2@AdUq%GwD&gF}Px*Irs+ZZIaaOu+V4mn`P)2$rq^ z=0+^$MlI$lp~GZ%GBl~rN`(^<{ruX(iJ*RdP2og9Kfk(g!mFS66i!I``BjAzf_}ca zFp2b8S)UPXE{ison`|zxw7KlIxxB*Wa-+@V27MaYTwZQ-d6~`SdYj92HkVyCmwB7Z zoPN%xGHX-0)~2$vK)m|2qd+8mx~4z`eVQp`kWFsrN4=?QVnSg0pKg|sS}*BJ5o8po*iXN-F1~)9HmWYU}j+OY@RPa21^42FPs~5 zC0t3zRDi1l_;X{9satqRFXu*}&E4cM(qOw_?39chU}_cLYY`aN`zg_og^OU}60mRy zf@OPR#`8(zOw2f~0A!?7Do+E)BV@N1bS@;UnT3R9oQfvU^`B-mOD$BYm~h=Q|A@0* z&>Vvb4tKKil};oqidbyI;z}&KvA6<@jaY2JqJYKaSX_q1dMwsq(S=1GiyRga7FjIT zV$q332Nr9v$Y7DitOcyO)&kaCV*zWfwtzJ~7O>_j3s|$+0+z^t21!F#A%V$mZf<^l z-ulfzc1J5?9Q-I4oEXdlT5+!D6R4Gtyb|ng_zzlPZzc`3Gh(EpF(EkjL7Yv%JlrNS zY2RjVnqr_iL1kj)e~8HPDHhrxyWM%?W5C`#WIGh9zzz3$X3_7xluI7q93qVL{-6UA(>-X#HrhD)AiDeri+T&+l3# zYtw5m&@rL)_wQ0q5BRx_es@Ay|FrLhf!16uH!^_M4N#56kz-^2_4$!wBmVVju{BZL zk|>TJEBf=f;Sv9YveD<;px%*)E528TV?P^Cd@Yq|ZfMxRHEcp0Juoml*5BXnUq8{J zs=h~axnuq(j``!EC;aLbhFQ?;_TAw-sI(4Lza0ei>DH0rh+i%GeZFFGYq8jR$L>VY zdRL2zJvKC`>c-+AY|FR8!@sfh(VVaeHLPoGg*`i(^NS}PynHl*9jbcN_k@3gn)9pc z*Q-bUt;G>r?}F~?31C`cYtBAlaXlFE_!Vp1KJ^iQYY;5j&|1W;mSYBwwX7@ppBVAy zi-MZivca}?Q6p?#xWKyNWD9rDKe1&&-_~LyIJM|scX>;3Kq+26%wBd(Wt2qT4~vTa zqu6Jee>bdX+yIJ}j@dNer~Mn&AB0t)@A5p%tyCL*>e7tb7%e8OWeZb2@NzDvfZ1c4 P{Oa%kDsENRt)u<}4o5!@ diff --git a/substrate/frame/revive/uapi/Cargo.toml b/substrate/frame/revive/uapi/Cargo.toml index 7241d667fcdc7..cf006941cfd0a 100644 --- a/substrate/frame/revive/uapi/Cargo.toml +++ b/substrate/frame/revive/uapi/Cargo.toml @@ -22,7 +22,7 @@ paste = { workspace = true } scale-info = { features = ["derive"], optional = true, workspace = true } [target.'cfg(target_arch = "riscv64")'.dependencies] -polkavm-derive = { version = "0.18.0" } +polkavm-derive = { version = "0.19.0" } [package.metadata.docs.rs] features = ["unstable-hostfn"] From c2531dc12dedfb345c16200229038ef8d04972cc Mon Sep 17 00:00:00 2001 From: Yuri Volkov <0@mcornholio.ru> Date: Fri, 17 Jan 2025 18:00:04 +0100 Subject: [PATCH 092/169] review-bot upgrade (#7214) Upgrading PAPI in review-bot: https://github.com/paritytech/review-bot/issues/140 --- .github/workflows/review-bot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/review-bot.yml b/.github/workflows/review-bot.yml index 3dd5b1114813d..27c6162a0fc20 100644 --- a/.github/workflows/review-bot.yml +++ b/.github/workflows/review-bot.yml @@ -29,7 +29,7 @@ jobs: with: artifact-name: pr_number - name: "Evaluates PR reviews and assigns reviewers" - uses: paritytech/review-bot@v2.6.0 + uses: paritytech/review-bot@v2.7.0 with: repo-token: ${{ steps.app_token.outputs.token }} team-token: ${{ steps.app_token.outputs.token }} From 0047c4cb15d3361454c4042f08bc69f28bdada8f Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:48:11 +0000 Subject: [PATCH 093/169] enable-deprecation-warning for old command bot (#7221) Deprecation warning for old command bot --- .github/workflows/command-inform.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/command-inform.yml b/.github/workflows/command-inform.yml index 9734639531936..3431eadf70608 100644 --- a/.github/workflows/command-inform.yml +++ b/.github/workflows/command-inform.yml @@ -8,7 +8,7 @@ jobs: comment: runs-on: ubuntu-latest # Temporary disable the bot until the new command bot works properly - if: github.event.issue.pull_request && startsWith(github.event.comment.body, 'bot ') && false # disabled for now, until tested + if: github.event.issue.pull_request && startsWith(github.event.comment.body, 'bot ') steps: - name: Inform that the new command exist uses: actions/github-script@v7 @@ -18,5 +18,5 @@ jobs: issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - body: 'We have migrated the command bot to GHA

Please, see the new usage instructions
here. Soon the old commands will be disabled.' - }) \ No newline at end of file + body: 'We have migrated the command bot to GHA

Please, see the new usage instructions here or here. Soon the old commands will be disabled.' + }) From f90a785c1689f7a64bcb161490b4393dd0b65d65 Mon Sep 17 00:00:00 2001 From: Santi Balaguer Date: Fri, 17 Jan 2025 18:50:03 +0100 Subject: [PATCH 094/169] added new proxy ParaRegistration to Westend (#6995) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds a new Proxy type to Westend Runtime called ParaRegistration. This is related to: https://github.com/polkadot-fellows/runtimes/pull/520. This new proxy allows: 1. Reserve paraID 2. Register Parachain 3. Leverage Utilites pallet 4. Remove proxy. --------- Co-authored-by: command-bot <> Co-authored-by: Dónal Murray --- polkadot/runtime/westend/src/lib.rs | 10 ++++++++++ prdoc/pr_6995.prdoc | 14 ++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 prdoc/pr_6995.prdoc diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 8a5771fe7cc08..a9ba0778fe0ef 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -1087,6 +1087,7 @@ pub enum ProxyType { CancelProxy, Auction, NominationPools, + ParaRegistration, } impl Default for ProxyType { fn default() -> Self { @@ -1183,6 +1184,15 @@ impl InstanceFilter for ProxyType { RuntimeCall::Registrar(..) | RuntimeCall::Slots(..) ), + ProxyType::ParaRegistration => matches!( + c, + RuntimeCall::Registrar(paras_registrar::Call::reserve { .. }) | + RuntimeCall::Registrar(paras_registrar::Call::register { .. }) | + RuntimeCall::Utility(pallet_utility::Call::batch { .. }) | + RuntimeCall::Utility(pallet_utility::Call::batch_all { .. }) | + RuntimeCall::Utility(pallet_utility::Call::force_batch { .. }) | + RuntimeCall::Proxy(pallet_proxy::Call::remove_proxy { .. }) + ), } } fn is_superset(&self, o: &Self) -> bool { diff --git a/prdoc/pr_6995.prdoc b/prdoc/pr_6995.prdoc new file mode 100644 index 0000000000000..ffdb4738a6fd5 --- /dev/null +++ b/prdoc/pr_6995.prdoc @@ -0,0 +1,14 @@ +title: added new proxy ParaRegistration to Westend +doc: +- audience: Runtime User + description: |- + This adds a new Proxy type to Westend Runtime called ParaRegistration. This is related to: https://github.com/polkadot-fellows/runtimes/pull/520. + + This new proxy allows: + 1. Reserve paraID + 2. Register Parachain + 3. Leverage Utilites pallet + 4. Remove proxy. +crates: +- name: westend-runtime + bump: major From 7702fdd1bd869e518bf176ccf0268f83f8927f9b Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Fri, 17 Jan 2025 19:21:38 +0100 Subject: [PATCH 095/169] [pallet-revive] Add tracing support (1/3) (#7166) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add foundation for supporting call traces in pallet_revive Follow up: - PR #7167 Add changes to eth-rpc to introduce debug endpoint that will use pallet-revive tracing features - PR #6727 Add new RPC to the client and implement tracing runtime API that can capture traces on previous blocks --------- Co-authored-by: Alexander Theißen --- Cargo.lock | 1 + .../assets/asset-hub-westend/src/lib.rs | 1 - substrate/bin/node/runtime/src/lib.rs | 1 - substrate/frame/revive/Cargo.toml | 2 + .../frame/revive/fixtures/build/_Cargo.toml | 1 + .../revive/fixtures/contracts/tracing.rs | 75 ++++++ .../fixtures/contracts/tracing_callee.rs | 45 ++++ substrate/frame/revive/rpc/src/client.rs | 46 +--- .../frame/revive/src/benchmarking/mod.rs | 2 +- substrate/frame/revive/src/debug.rs | 109 -------- substrate/frame/revive/src/evm.rs | 45 ++++ substrate/frame/revive/src/evm/api.rs | 5 + substrate/frame/revive/src/evm/api/byte.rs | 74 +----- .../revive/src/evm/api/debug_rpc_types.rs | 219 ++++++++++++++++ .../frame/revive/src/evm/api/hex_serde.rs | 84 +++++++ substrate/frame/revive/src/evm/runtime.rs | 14 +- substrate/frame/revive/src/evm/tracing.rs | 134 ++++++++++ substrate/frame/revive/src/exec.rs | 76 ++++-- substrate/frame/revive/src/lib.rs | 12 +- substrate/frame/revive/src/tests.rs | 158 +++++++++++- .../frame/revive/src/tests/test_debug.rs | 235 ------------------ substrate/frame/revive/src/tracing.rs | 64 +++++ 22 files changed, 912 insertions(+), 491 deletions(-) create mode 100644 substrate/frame/revive/fixtures/contracts/tracing.rs create mode 100644 substrate/frame/revive/fixtures/contracts/tracing_callee.rs delete mode 100644 substrate/frame/revive/src/debug.rs create mode 100644 substrate/frame/revive/src/evm/api/debug_rpc_types.rs create mode 100644 substrate/frame/revive/src/evm/api/hex_serde.rs create mode 100644 substrate/frame/revive/src/evm/tracing.rs delete mode 100644 substrate/frame/revive/src/tests/test_debug.rs create mode 100644 substrate/frame/revive/src/tracing.rs diff --git a/Cargo.lock b/Cargo.lock index 23271617e927d..da4e85511919d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14839,6 +14839,7 @@ dependencies = [ "assert_matches", "derive_more 0.99.17", "environmental", + "ethabi-decode 2.0.0", "ethereum-types 0.15.1", "frame-benchmarking 28.0.0", "frame-support 28.0.0", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 3ef5e87f24c47..41f29fe2c56a0 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -1077,7 +1077,6 @@ impl pallet_revive::Config for Runtime { type InstantiateOrigin = EnsureSigned; type RuntimeHoldReason = RuntimeHoldReason; type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; - type Debug = (); type Xcm = pallet_xcm::Pallet; type ChainId = ConstU64<420_420_421>; type NativeToEthRatio = ConstU32<1_000_000>; // 10^(18 - 12) Eth is 10^18, Native is 10^12. diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 117d306e3060e..26f4dacf9a1e3 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1491,7 +1491,6 @@ impl pallet_revive::Config for Runtime { type InstantiateOrigin = EnsureSigned; type RuntimeHoldReason = RuntimeHoldReason; type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; - type Debug = (); type Xcm = (); type ChainId = ConstU64<420_420_420>; type NativeToEthRatio = ConstU32<1_000_000>; // 10^(18 - 12) Eth is 10^18, Native is 10^12. diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index 49a27cfdaab2a..0959cc50638ba 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -20,6 +20,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = ["derive", "max-encoded-len"], workspace = true } derive_more = { workspace = true } environmental = { workspace = true } +ethabi = { workspace = true } ethereum-types = { workspace = true, features = ["codec", "rlp", "serialize"] } hex = { workspace = true } impl-trait-for-tuples = { workspace = true } @@ -75,6 +76,7 @@ default = ["std"] std = [ "codec/std", "environmental/std", + "ethabi/std", "ethereum-types/std", "frame-benchmarking?/std", "frame-support/std", diff --git a/substrate/frame/revive/fixtures/build/_Cargo.toml b/substrate/frame/revive/fixtures/build/_Cargo.toml index 483d9775b12a6..1a0a635420ad5 100644 --- a/substrate/frame/revive/fixtures/build/_Cargo.toml +++ b/substrate/frame/revive/fixtures/build/_Cargo.toml @@ -14,6 +14,7 @@ edition = "2021" [dependencies] uapi = { package = 'pallet-revive-uapi', path = "", features = ["unstable-hostfn"], default-features = false } common = { package = 'pallet-revive-fixtures-common', path = "" } +hex-literal = { version = "0.4.1", default-features = false } polkavm-derive = { version = "0.19.0" } [profile.release] diff --git a/substrate/frame/revive/fixtures/contracts/tracing.rs b/substrate/frame/revive/fixtures/contracts/tracing.rs new file mode 100644 index 0000000000000..9cbef3bbc8435 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/tracing.rs @@ -0,0 +1,75 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This fixture calls itself as many times as passed as argument. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(calls_left: u32, callee_addr: &[u8; 20],); + if calls_left == 0 { + return + } + + let next_input = (calls_left - 1).to_le_bytes(); + api::deposit_event(&[], b"before"); + + // Call the callee, ignore revert. + let _ = api::call( + uapi::CallFlags::empty(), + callee_addr, + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value transferred to the contract. + &next_input, + None, + ); + + api::deposit_event(&[], b"after"); + + // own address + let mut addr = [0u8; 20]; + api::address(&mut addr); + let mut input = [0u8; 24]; + + input[..4].copy_from_slice(&next_input); + input[4..24].copy_from_slice(&callee_addr[..20]); + + // recurse + api::call( + uapi::CallFlags::ALLOW_REENTRY, + &addr, + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value transferred to the contract. + &input, + None, + ) + .unwrap(); +} diff --git a/substrate/frame/revive/fixtures/contracts/tracing_callee.rs b/substrate/frame/revive/fixtures/contracts/tracing_callee.rs new file mode 100644 index 0000000000000..d44771e417f9d --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/tracing_callee.rs @@ -0,0 +1,45 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(id: u32, ); + + match id { + // Revert with message "This function always fails" + 2 => { + let data = hex_literal::hex!( + "08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001a546869732066756e6374696f6e20616c77617973206661696c73000000000000" + ); + api::return_value(uapi::ReturnFlags::REVERT, &data) + }, + 1 => { + panic!("booum"); + }, + _ => api::return_value(uapi::ReturnFlags::empty(), &id.to_le_bytes()), + }; +} diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index cd0effe7faf2f..c61c5871f76ae 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -27,8 +27,9 @@ use crate::{ use jsonrpsee::types::{error::CALL_EXECUTION_FAILED_CODE, ErrorObjectOwned}; use pallet_revive::{ evm::{ - Block, BlockNumberOrTag, BlockNumberOrTagOrHash, GenericTransaction, ReceiptInfo, - SyncingProgress, SyncingStatus, TransactionSigned, H160, H256, U256, + extract_revert_message, Block, BlockNumberOrTag, BlockNumberOrTagOrHash, + GenericTransaction, ReceiptInfo, SyncingProgress, SyncingStatus, TransactionSigned, H160, + H256, U256, }, EthTransactError, EthTransactInfo, }; @@ -83,47 +84,6 @@ fn unwrap_call_err(err: &subxt::error::RpcError) -> Option { } } -/// Extract the revert message from a revert("msg") solidity statement. -fn extract_revert_message(exec_data: &[u8]) -> Option { - let error_selector = exec_data.get(0..4)?; - - match error_selector { - // assert(false) - [0x4E, 0x48, 0x7B, 0x71] => { - let panic_code: u32 = U256::from_big_endian(exec_data.get(4..36)?).try_into().ok()?; - - // See https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require - let msg = match panic_code { - 0x00 => "generic panic", - 0x01 => "assert(false)", - 0x11 => "arithmetic underflow or overflow", - 0x12 => "division or modulo by zero", - 0x21 => "enum overflow", - 0x22 => "invalid encoded storage byte array accessed", - 0x31 => "out-of-bounds array access; popping on an empty array", - 0x32 => "out-of-bounds access of an array or bytesN", - 0x41 => "out of memory", - 0x51 => "uninitialized function", - code => return Some(format!("execution reverted: unknown panic code: {code:#x}")), - }; - - Some(format!("execution reverted: {msg}")) - }, - // revert(string) - [0x08, 0xC3, 0x79, 0xA0] => { - let decoded = ethabi::decode(&[ethabi::ParamType::String], &exec_data[4..]).ok()?; - if let Some(ethabi::Token::String(msg)) = decoded.first() { - return Some(format!("execution reverted: {msg}")) - } - Some("execution reverted".to_string()) - }, - _ => { - log::debug!(target: LOG_TARGET, "Unknown revert function selector: {error_selector:?}"); - Some("execution reverted".to_string()) - }, - } -} - /// The error type for the client. #[derive(Error, Debug)] pub enum ClientError { diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index 18d7bb0afc31a..16bdd6d1a18a0 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -772,7 +772,7 @@ mod benchmarks { let mut setup = CallSetup::::default(); let input = setup.data(); let (mut ext, _) = setup.ext(); - ext.override_export(crate::debug::ExportedFunction::Constructor); + ext.override_export(crate::exec::ExportedFunction::Constructor); let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, input); diff --git a/substrate/frame/revive/src/debug.rs b/substrate/frame/revive/src/debug.rs deleted file mode 100644 index d1fc0823e03df..0000000000000 --- a/substrate/frame/revive/src/debug.rs +++ /dev/null @@ -1,109 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -pub use crate::{ - exec::{ExecResult, ExportedFunction}, - primitives::ExecReturnValue, -}; -use crate::{Config, LOG_TARGET}; -use sp_core::H160; - -/// Umbrella trait for all interfaces that serves for debugging. -pub trait Debugger: Tracing + CallInterceptor {} - -impl Debugger for V where V: Tracing + CallInterceptor {} - -/// Defines methods to capture contract calls, enabling external observers to -/// measure, trace, and react to contract interactions. -pub trait Tracing { - /// The type of [`CallSpan`] that is created by this trait. - type CallSpan: CallSpan; - - /// Creates a new call span to encompass the upcoming contract execution. - /// - /// This method should be invoked just before the execution of a contract and - /// marks the beginning of a traceable span of execution. - /// - /// # Arguments - /// - /// * `contract_address` - The address of the contract that is about to be executed. - /// * `entry_point` - Describes whether the call is the constructor or a regular call. - /// * `input_data` - The raw input data of the call. - fn new_call_span( - contract_address: &H160, - entry_point: ExportedFunction, - input_data: &[u8], - ) -> Self::CallSpan; -} - -/// Defines a span of execution for a contract call. -pub trait CallSpan { - /// Called just after the execution of a contract. - /// - /// # Arguments - /// - /// * `output` - The raw output of the call. - fn after_call(self, output: &ExecReturnValue); -} - -impl Tracing for () { - type CallSpan = (); - - fn new_call_span(contract_address: &H160, entry_point: ExportedFunction, input_data: &[u8]) { - log::trace!(target: LOG_TARGET, "call {entry_point:?} address: {contract_address:?}, input_data: {input_data:?}") - } -} - -impl CallSpan for () { - fn after_call(self, output: &ExecReturnValue) { - log::trace!(target: LOG_TARGET, "call result {output:?}") - } -} - -/// Provides an interface for intercepting contract calls. -pub trait CallInterceptor { - /// Allows to intercept contract calls and decide whether they should be executed or not. - /// If the call is intercepted, the mocked result of the call is returned. - /// - /// # Arguments - /// - /// * `contract_address` - The address of the contract that is about to be executed. - /// * `entry_point` - Describes whether the call is the constructor or a regular call. - /// * `input_data` - The raw input data of the call. - /// - /// # Expected behavior - /// - /// This method should return: - /// * `Some(ExecResult)` - if the call should be intercepted and the mocked result of the call - /// is returned. - /// * `None` - otherwise, i.e. the call should be executed normally. - fn intercept_call( - contract_address: &H160, - entry_point: ExportedFunction, - input_data: &[u8], - ) -> Option; -} - -impl CallInterceptor for () { - fn intercept_call( - _contract_address: &H160, - _entry_point: ExportedFunction, - _input_data: &[u8], - ) -> Option { - None - } -} diff --git a/substrate/frame/revive/src/evm.rs b/substrate/frame/revive/src/evm.rs index c8c967fbe091b..33660a36aa6ea 100644 --- a/substrate/frame/revive/src/evm.rs +++ b/substrate/frame/revive/src/evm.rs @@ -19,6 +19,51 @@ mod api; pub use api::*; +mod tracing; +pub use tracing::*; mod gas_encoder; pub use gas_encoder::*; pub mod runtime; + +use crate::alloc::{format, string::*}; + +/// Extract the revert message from a revert("msg") solidity statement. +pub fn extract_revert_message(exec_data: &[u8]) -> Option { + let error_selector = exec_data.get(0..4)?; + + match error_selector { + // assert(false) + [0x4E, 0x48, 0x7B, 0x71] => { + let panic_code: u32 = U256::from_big_endian(exec_data.get(4..36)?).try_into().ok()?; + + // See https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require + let msg = match panic_code { + 0x00 => "generic panic", + 0x01 => "assert(false)", + 0x11 => "arithmetic underflow or overflow", + 0x12 => "division or modulo by zero", + 0x21 => "enum overflow", + 0x22 => "invalid encoded storage byte array accessed", + 0x31 => "out-of-bounds array access; popping on an empty array", + 0x32 => "out-of-bounds access of an array or bytesN", + 0x41 => "out of memory", + 0x51 => "uninitialized function", + code => return Some(format!("execution reverted: unknown panic code: {code:#x}")), + }; + + Some(format!("execution reverted: {msg}")) + }, + // revert(string) + [0x08, 0xC3, 0x79, 0xA0] => { + let decoded = ethabi::decode(&[ethabi::ParamKind::String], &exec_data[4..]).ok()?; + if let Some(ethabi::Token::String(msg)) = decoded.first() { + return Some(format!("execution reverted: {}", String::from_utf8_lossy(msg))) + } + Some("execution reverted".to_string()) + }, + _ => { + log::debug!(target: crate::LOG_TARGET, "Unknown revert function selector: {error_selector:?}"); + Some("execution reverted".to_string()) + }, + } +} diff --git a/substrate/frame/revive/src/evm/api.rs b/substrate/frame/revive/src/evm/api.rs index fe18c8735bed4..7a34fdc83f9a5 100644 --- a/substrate/frame/revive/src/evm/api.rs +++ b/substrate/frame/revive/src/evm/api.rs @@ -16,6 +16,8 @@ // limitations under the License. //! JSON-RPC methods and types, for Ethereum. +mod hex_serde; + mod byte; pub use byte::*; @@ -25,6 +27,9 @@ pub use rlp; mod type_id; pub use type_id::*; +mod debug_rpc_types; +pub use debug_rpc_types::*; + mod rpc_types; mod rpc_types_gen; pub use rpc_types_gen::*; diff --git a/substrate/frame/revive/src/evm/api/byte.rs b/substrate/frame/revive/src/evm/api/byte.rs index c2d64f8e5e424..f11966d0072cf 100644 --- a/substrate/frame/revive/src/evm/api/byte.rs +++ b/substrate/frame/revive/src/evm/api/byte.rs @@ -15,79 +15,16 @@ // See the License for the specific language governing permissions and // limitations under the License. //! Define Byte wrapper types for encoding and decoding hex strings +use super::hex_serde::HexCodec; use alloc::{vec, vec::Vec}; use codec::{Decode, Encode}; use core::{ fmt::{Debug, Display, Formatter, Result as FmtResult}, str::FromStr, }; -use hex_serde::HexCodec; use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; -mod hex_serde { - #[cfg(not(feature = "std"))] - use alloc::{format, string::String, vec::Vec}; - use serde::{Deserialize, Deserializer, Serializer}; - - pub trait HexCodec: Sized { - type Error; - fn to_hex(&self) -> String; - fn from_hex(s: String) -> Result; - } - - impl HexCodec for u8 { - type Error = core::num::ParseIntError; - fn to_hex(&self) -> String { - format!("0x{:x}", self) - } - fn from_hex(s: String) -> Result { - u8::from_str_radix(s.trim_start_matches("0x"), 16) - } - } - - impl HexCodec for [u8; T] { - type Error = hex::FromHexError; - fn to_hex(&self) -> String { - format!("0x{}", hex::encode(self)) - } - fn from_hex(s: String) -> Result { - let data = hex::decode(s.trim_start_matches("0x"))?; - data.try_into().map_err(|_| hex::FromHexError::InvalidStringLength) - } - } - - impl HexCodec for Vec { - type Error = hex::FromHexError; - fn to_hex(&self) -> String { - format!("0x{}", hex::encode(self)) - } - fn from_hex(s: String) -> Result { - hex::decode(s.trim_start_matches("0x")) - } - } - - pub fn serialize(value: &T, serializer: S) -> Result - where - S: Serializer, - T: HexCodec, - { - let s = value.to_hex(); - serializer.serialize_str(&s) - } - - pub fn deserialize<'de, D, T>(deserializer: D) -> Result - where - D: Deserializer<'de>, - T: HexCodec, - ::Error: core::fmt::Debug, - { - let s = String::deserialize(deserializer)?; - let value = T::from_hex(s).map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?; - Ok(value) - } -} - impl FromStr for Bytes { type Err = hex::FromHexError; fn from_str(s: &str) -> Result { @@ -100,7 +37,7 @@ macro_rules! impl_hex { ($type:ident, $inner:ty, $default:expr) => { #[derive(Encode, Decode, Eq, PartialEq, TypeInfo, Clone, Serialize, Deserialize)] #[doc = concat!("`", stringify!($inner), "`", " wrapper type for encoding and decoding hex strings")] - pub struct $type(#[serde(with = "hex_serde")] pub $inner); + pub struct $type(#[serde(with = "crate::evm::api::hex_serde")] pub $inner); impl Default for $type { fn default() -> Self { @@ -131,6 +68,13 @@ macro_rules! impl_hex { }; } +impl Bytes { + /// See `Vec::is_empty` + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + impl_hex!(Byte, u8, 0u8); impl_hex!(Bytes, Vec, vec![]); impl_hex!(Bytes8, [u8; 8], [0u8; 8]); diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs new file mode 100644 index 0000000000000..0857a59fbf3b6 --- /dev/null +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -0,0 +1,219 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::evm::{Bytes, CallTracer}; +use alloc::{fmt, string::String, vec::Vec}; +use codec::{Decode, Encode}; +use scale_info::TypeInfo; +use serde::{ + de::{self, MapAccess, Visitor}, + Deserialize, Deserializer, Serialize, +}; +use sp_core::{H160, H256, U256}; + +/// Tracer configuration used to trace calls. +#[derive(TypeInfo, Debug, Clone, Encode, Decode, Serialize, PartialEq)] +#[serde(tag = "tracer", content = "tracerConfig")] +pub enum TracerConfig { + /// A tracer that captures call traces. + #[serde(rename = "callTracer")] + CallTracer { + /// Whether or not to capture logs. + #[serde(rename = "withLog")] + with_logs: bool, + }, +} + +impl TracerConfig { + /// Build the tracer associated to this config. + pub fn build(self, gas_mapper: G) -> CallTracer { + match self { + Self::CallTracer { with_logs } => CallTracer::new(with_logs, gas_mapper), + } + } +} + +/// Custom deserializer to support the following JSON format: +/// +/// ```json +/// { "tracer": "callTracer", "tracerConfig": { "withLogs": false } } +/// ``` +/// +/// ```json +/// { "tracer": "callTracer" } +/// ``` +impl<'de> Deserialize<'de> for TracerConfig { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct TracerConfigVisitor; + + impl<'de> Visitor<'de> for TracerConfigVisitor { + type Value = TracerConfig; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a map with tracer and optional tracerConfig") + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'de>, + { + let mut tracer_type: Option = None; + let mut with_logs = None; + + while let Some(key) = map.next_key::()? { + match key.as_str() { + "tracer" => { + tracer_type = map.next_value()?; + }, + "tracerConfig" => { + #[derive(Deserialize)] + struct CallTracerConfig { + #[serde(rename = "withLogs")] + with_logs: Option, + } + let inner: CallTracerConfig = map.next_value()?; + with_logs = inner.with_logs; + }, + _ => {}, + } + } + + match tracer_type.as_deref() { + Some("callTracer") => + Ok(TracerConfig::CallTracer { with_logs: with_logs.unwrap_or(true) }), + _ => Err(de::Error::custom("Unsupported or missing tracer type")), + } + } + } + + deserializer.deserialize_map(TracerConfigVisitor) + } +} + +#[test] +fn test_tracer_config_serialization() { + let tracers = vec![ + (r#"{"tracer": "callTracer"}"#, TracerConfig::CallTracer { with_logs: true }), + ( + r#"{"tracer": "callTracer", "tracerConfig": { "withLogs": true }}"#, + TracerConfig::CallTracer { with_logs: true }, + ), + ( + r#"{"tracer": "callTracer", "tracerConfig": { "withLogs": false }}"#, + TracerConfig::CallTracer { with_logs: false }, + ), + ]; + + for (json_data, expected) in tracers { + let result: TracerConfig = + serde_json::from_str(json_data).expect("Deserialization should succeed"); + assert_eq!(result, expected); + } +} + +impl Default for TracerConfig { + fn default() -> Self { + TracerConfig::CallTracer { with_logs: false } + } +} + +/// The type of call that was executed. +#[derive( + Default, TypeInfo, Encode, Decode, Serialize, Deserialize, Eq, PartialEq, Clone, Debug, +)] +#[serde(rename_all = "UPPERCASE")] +pub enum CallType { + /// A regular call. + #[default] + Call, + /// A read-only call. + StaticCall, + /// A delegate call. + DelegateCall, +} + +/// A smart contract execution call trace. +#[derive( + TypeInfo, Default, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq, +)] +pub struct CallTrace { + /// Address of the sender. + pub from: H160, + /// Address of the receiver. + pub to: H160, + /// Call input data. + pub input: Vec, + /// Amount of value transferred. + #[serde(skip_serializing_if = "U256::is_zero")] + pub value: U256, + /// Type of call. + #[serde(rename = "type")] + pub call_type: CallType, + /// Amount of gas provided for the call. + pub gas: Gas, + /// Amount of gas used. + #[serde(rename = "gasUsed")] + pub gas_used: Gas, + /// Return data. + #[serde(flatten, skip_serializing_if = "Bytes::is_empty")] + pub output: Bytes, + /// The error message if the call failed. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// The revert reason, if the call reverted. + #[serde(rename = "revertReason")] + pub revert_reason: Option, + /// List of sub-calls. + #[serde(skip_serializing_if = "Vec::is_empty")] + pub calls: Vec>, + /// List of logs emitted during the call. + #[serde(skip_serializing_if = "Vec::is_empty")] + pub logs: Vec, +} + +/// A log emitted during a call. +#[derive( + Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq, +)] +pub struct CallLog { + /// The address of the contract that emitted the log. + pub address: H160, + /// The log's data. + #[serde(skip_serializing_if = "Bytes::is_empty")] + pub data: Bytes, + /// The topics used to index the log. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub topics: Vec, + /// Position of the log relative to subcalls within the same trace + /// See for details + #[serde(with = "super::hex_serde")] + pub position: u32, +} + +/// A transaction trace +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct TransactionTrace { + /// The transaction hash. + #[serde(rename = "txHash")] + pub tx_hash: H256, + /// The trace of the transaction. + #[serde(rename = "result")] + pub trace: CallTrace, +} diff --git a/substrate/frame/revive/src/evm/api/hex_serde.rs b/substrate/frame/revive/src/evm/api/hex_serde.rs new file mode 100644 index 0000000000000..ba07b36fa4be6 --- /dev/null +++ b/substrate/frame/revive/src/evm/api/hex_serde.rs @@ -0,0 +1,84 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use alloc::{format, string::String, vec::Vec}; +use serde::{Deserialize, Deserializer, Serializer}; + +pub trait HexCodec: Sized { + type Error; + fn to_hex(&self) -> String; + fn from_hex(s: String) -> Result; +} + +macro_rules! impl_hex_codec { + ($($t:ty),*) => { + $( + impl HexCodec for $t { + type Error = core::num::ParseIntError; + fn to_hex(&self) -> String { + format!("0x{:x}", self) + } + fn from_hex(s: String) -> Result { + <$t>::from_str_radix(s.trim_start_matches("0x"), 16) + } + } + )* + }; +} + +impl_hex_codec!(u8, u32); + +impl HexCodec for [u8; T] { + type Error = hex::FromHexError; + fn to_hex(&self) -> String { + format!("0x{}", hex::encode(self)) + } + fn from_hex(s: String) -> Result { + let data = hex::decode(s.trim_start_matches("0x"))?; + data.try_into().map_err(|_| hex::FromHexError::InvalidStringLength) + } +} + +impl HexCodec for Vec { + type Error = hex::FromHexError; + fn to_hex(&self) -> String { + format!("0x{}", hex::encode(self)) + } + fn from_hex(s: String) -> Result { + hex::decode(s.trim_start_matches("0x")) + } +} + +pub fn serialize(value: &T, serializer: S) -> Result +where + S: Serializer, + T: HexCodec, +{ + let s = value.to_hex(); + serializer.serialize_str(&s) +} + +pub fn deserialize<'de, D, T>(deserializer: D) -> Result +where + D: Deserializer<'de>, + T: HexCodec, + ::Error: core::fmt::Debug, +{ + let s = String::deserialize(deserializer)?; + let value = T::from_hex(s).map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?; + Ok(value) +} diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index d4b344e20eb85..0e5fc3da545b5 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -20,7 +20,7 @@ use crate::{ api::{GenericTransaction, TransactionSigned}, GasEncoder, }, - AccountIdOf, AddressMapper, BalanceOf, Config, MomentOf, LOG_TARGET, + AccountIdOf, AddressMapper, BalanceOf, Config, MomentOf, Weight, LOG_TARGET, }; use alloc::vec::Vec; use codec::{Decode, Encode}; @@ -72,6 +72,18 @@ where } } +/// Convert a `Weight` into a gas value, using the fixed `GAS_PRICE`. +/// and the `Config::WeightPrice` to compute the fee. +/// The gas is calculated as `fee / GAS_PRICE`, rounded up to the nearest integer. +pub fn gas_from_weight(weight: Weight) -> U256 +where + BalanceOf: Into, +{ + use sp_runtime::traits::Convert; + let fee: BalanceOf = T::WeightPrice::convert(weight); + gas_from_fee(fee) +} + /// Wraps [`generic::UncheckedExtrinsic`] to support checking unsigned /// [`crate::Call::eth_transact`] extrinsic. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] diff --git a/substrate/frame/revive/src/evm/tracing.rs b/substrate/frame/revive/src/evm/tracing.rs new file mode 100644 index 0000000000000..7466ec1de4877 --- /dev/null +++ b/substrate/frame/revive/src/evm/tracing.rs @@ -0,0 +1,134 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use crate::{ + evm::{extract_revert_message, CallLog, CallTrace, CallType}, + primitives::ExecReturnValue, + tracing::Tracer, + DispatchError, Weight, +}; +use alloc::{format, string::ToString, vec::Vec}; +use sp_core::{H160, H256, U256}; + +/// A Tracer that reports logs and nested call traces transactions. +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub struct CallTracer { + /// Map Weight to Gas equivalent. + gas_mapper: GasMapper, + /// Store all in-progress CallTrace instances. + traces: Vec>, + /// Stack of indices to the current active traces. + current_stack: Vec, + /// whether or not to capture logs. + with_log: bool, +} + +impl CallTracer { + /// Create a new [`CallTracer`] instance. + pub fn new(with_log: bool, gas_mapper: GasMapper) -> Self { + Self { gas_mapper, traces: Vec::new(), current_stack: Vec::new(), with_log } + } + + /// Collect the traces and return them. + pub fn collect_traces(&mut self) -> Vec> { + core::mem::take(&mut self.traces) + } +} + +impl Gas> Tracer for CallTracer { + fn enter_child_span( + &mut self, + from: H160, + to: H160, + is_delegate_call: bool, + is_read_only: bool, + value: U256, + input: &[u8], + gas_left: Weight, + ) { + let call_type = if is_read_only { + CallType::StaticCall + } else if is_delegate_call { + CallType::DelegateCall + } else { + CallType::Call + }; + + self.traces.push(CallTrace { + from, + to, + value, + call_type, + input: input.to_vec(), + gas: (self.gas_mapper)(gas_left), + ..Default::default() + }); + + // Push the index onto the stack of the current active trace + self.current_stack.push(self.traces.len() - 1); + } + + fn log_event(&mut self, address: H160, topics: &[H256], data: &[u8]) { + if !self.with_log { + return; + } + + let current_index = self.current_stack.last().unwrap(); + let position = self.traces[*current_index].calls.len() as u32; + let log = + CallLog { address, topics: topics.to_vec(), data: data.to_vec().into(), position }; + + let current_index = *self.current_stack.last().unwrap(); + self.traces[current_index].logs.push(log); + } + + fn exit_child_span(&mut self, output: &ExecReturnValue, gas_used: Weight) { + // Set the output of the current trace + let current_index = self.current_stack.pop().unwrap(); + let trace = &mut self.traces[current_index]; + trace.output = output.data.clone().into(); + trace.gas_used = (self.gas_mapper)(gas_used); + + if output.did_revert() { + trace.revert_reason = extract_revert_message(&output.data); + trace.error = Some("execution reverted".to_string()); + } + + // Move the current trace into its parent + if let Some(parent_index) = self.current_stack.last() { + let child_trace = self.traces.remove(current_index); + self.traces[*parent_index].calls.push(child_trace); + } + } + fn exit_child_span_with_error(&mut self, error: DispatchError, gas_used: Weight) { + // Set the output of the current trace + let current_index = self.current_stack.pop().unwrap(); + let trace = &mut self.traces[current_index]; + trace.gas_used = (self.gas_mapper)(gas_used); + + trace.error = match error { + DispatchError::Module(sp_runtime::ModuleError { message, .. }) => + Some(message.unwrap_or_default().to_string()), + _ => Some(format!("{:?}", error)), + }; + + // Move the current trace into its parent + if let Some(parent_index) = self.current_stack.last() { + let child_trace = self.traces.remove(current_index); + self.traces[*parent_index].calls.push(child_trace); + } + } +} diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index f696f75a4a138..d2ef6c9c7ba6c 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -17,12 +17,12 @@ use crate::{ address::{self, AddressMapper}, - debug::{CallInterceptor, CallSpan, Tracing}, gas::GasMeter, limits, primitives::{ExecReturnValue, StorageDeposit}, runtime_decl_for_revive_api::{Decode, Encode, RuntimeDebugNoBound, TypeInfo}, storage::{self, meter::Diff, WriteOutcome}, + tracing::if_tracing, transient_storage::TransientStorage, BalanceOf, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf, Error, Event, ImmutableData, ImmutableDataOf, Pallet as Contracts, @@ -773,7 +773,25 @@ where )? { stack.run(executable, input_data).map(|_| stack.first_frame.last_frame_output) } else { - Self::transfer_from_origin(&origin, &origin, &dest, value) + if_tracing(|t| { + let address = + origin.account_id().map(T::AddressMapper::to_address).unwrap_or_default(); + let dest = T::AddressMapper::to_address(&dest); + t.enter_child_span(address, dest, false, false, value, &input_data, Weight::zero()); + }); + + let result = Self::transfer_from_origin(&origin, &origin, &dest, value); + match result { + Ok(ref output) => { + if_tracing(|t| { + t.exit_child_span(&output, Weight::zero()); + }); + }, + Err(e) => { + if_tracing(|t| t.exit_child_span_with_error(e.error.into(), Weight::zero())); + }, + } + result } } @@ -1018,6 +1036,7 @@ where fn run(&mut self, executable: E, input_data: Vec) -> Result<(), ExecError> { let frame = self.top_frame(); let entry_point = frame.entry_point; + let is_delegate_call = frame.delegate.is_some(); let delegated_code_hash = if frame.delegate.is_some() { Some(*executable.code_hash()) } else { None }; @@ -1038,6 +1057,9 @@ where let do_transaction = || -> ExecResult { let caller = self.caller(); let frame = top_frame_mut!(self); + let read_only = frame.read_only; + let value_transferred = frame.value_transferred; + let account_id = &frame.account_id.clone(); // We need to charge the storage deposit before the initial transfer so that // it can create the account in case the initial transfer is < ed. @@ -1045,10 +1067,11 @@ where // Root origin can't be used to instantiate a contract, so it is safe to assume that // if we reached this point the origin has an associated account. let origin = &self.origin.account_id()?; + frame.nested_storage.charge_instantiate( origin, - &frame.account_id, - frame.contract_info.get(&frame.account_id), + &account_id, + frame.contract_info.get(&account_id), executable.code_info(), self.skip_transfer, )?; @@ -1069,15 +1092,34 @@ where )?; } - let contract_address = T::AddressMapper::to_address(&top_frame!(self).account_id); - - let call_span = T::Debug::new_call_span(&contract_address, entry_point, &input_data); + let contract_address = T::AddressMapper::to_address(account_id); + let maybe_caller_address = caller.account_id().map(T::AddressMapper::to_address); + + if_tracing(|tracer| { + tracer.enter_child_span( + maybe_caller_address.unwrap_or_default(), + contract_address, + is_delegate_call, + read_only, + value_transferred, + &input_data, + frame.nested_gas.gas_left(), + ); + }); - let output = T::Debug::intercept_call(&contract_address, entry_point, &input_data) - .unwrap_or_else(|| executable.execute(self, entry_point, input_data)) - .map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?; + let output = executable.execute(self, entry_point, input_data).map_err(|e| { + if_tracing(|tracer| { + tracer.exit_child_span_with_error( + e.error, + top_frame_mut!(self).nested_gas.gas_consumed(), + ); + }); + ExecError { error: e.error, origin: ErrorOrigin::Callee } + })?; - call_span.after_call(&output); + if_tracing(|tracer| { + tracer.exit_child_span(&output, top_frame_mut!(self).nested_gas.gas_consumed()); + }); // Avoid useless work that would be reverted anyways. if output.did_revert() { @@ -1353,7 +1395,7 @@ where &mut self, gas_limit: Weight, deposit_limit: U256, - dest: &H160, + dest_addr: &H160, value: U256, input_data: Vec, allows_reentry: bool, @@ -1369,7 +1411,7 @@ where *self.last_frame_output_mut() = Default::default(); let try_call = || { - let dest = T::AddressMapper::to_account_id(dest); + let dest = T::AddressMapper::to_account_id(dest_addr); if !self.allows_reentry(&dest) { return Err(>::ReentranceDenied.into()); } @@ -1661,11 +1703,11 @@ where } fn deposit_event(&mut self, topics: Vec, data: Vec) { - Contracts::::deposit_event(Event::ContractEmitted { - contract: T::AddressMapper::to_address(self.account_id()), - data, - topics, + let contract = T::AddressMapper::to_address(self.account_id()); + if_tracing(|tracer| { + tracer.log_event(contract, &topics, &data); }); + Contracts::::deposit_event(Event::ContractEmitted { contract, data, topics }); } fn block_number(&self) -> U256 { diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index a9f2842c35f6a..c36cb3f47caed 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -35,9 +35,9 @@ mod wasm; mod tests; pub mod chain_extension; -pub mod debug; pub mod evm; pub mod test_utils; +pub mod tracing; pub mod weights; use crate::{ @@ -83,7 +83,6 @@ use sp_runtime::{ pub use crate::{ address::{create1, create2, AccountId32Mapper, AddressMapper}, - debug::Tracing, exec::{MomentOf, Origin}, pallet::*, }; @@ -118,7 +117,6 @@ const LOG_TARGET: &str = "runtime::revive"; #[frame_support::pallet] pub mod pallet { use super::*; - use crate::debug::Debugger; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; use sp_core::U256; @@ -255,12 +253,6 @@ pub mod pallet { #[pallet::no_default_bounds] type InstantiateOrigin: EnsureOrigin; - /// Debugging utilities for contracts. - /// For production chains, it's recommended to use the `()` implementation of this - /// trait. - #[pallet::no_default_bounds] - type Debug: Debugger; - /// A type that exposes XCM APIs, allowing contracts to interact with other parachains, and /// execute XCM programs. #[pallet::no_default_bounds] @@ -367,7 +359,6 @@ pub mod pallet { type InstantiateOrigin = EnsureSigned; type WeightInfo = (); type WeightPrice = Self; - type Debug = (); type Xcm = (); type RuntimeMemory = ConstU32<{ 128 * 1024 * 1024 }>; type PVFMemory = ConstU32<{ 512 * 1024 * 1024 }>; @@ -1146,7 +1137,6 @@ where DepositLimit::Unchecked }; - // TODO remove once we have revisited how we encode the gas limit. if tx.nonce.is_none() { tx.nonce = Some(>::account_nonce(&origin).into()); } diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 8398bc2cb66ff..90b9f053a03fb 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -16,12 +16,8 @@ // limitations under the License. mod pallet_dummy; -mod test_debug; -use self::{ - test_debug::TestDebug, - test_utils::{ensure_stored, expected_deposit}, -}; +use self::test_utils::{ensure_stored, expected_deposit}; use crate::{ self as pallet_revive, address::{create1, create2, AddressMapper}, @@ -29,13 +25,14 @@ use crate::{ ChainExtension, Environment, Ext, RegisteredChainExtension, Result as ExtensionResult, RetVal, ReturnFlags, }, - evm::{runtime::GAS_PRICE, GenericTransaction}, + evm::{runtime::GAS_PRICE, CallTrace, CallTracer, CallType, GenericTransaction}, exec::Key, limits, primitives::CodeUploadReturnValue, storage::DeletionQueueManager, test_utils::*, tests::test_utils::{get_contract, get_contract_checked}, + tracing::trace, wasm::Memory, weights::WeightInfo, AccountId32Mapper, BalanceOf, Code, CodeInfoOf, Config, ContractInfo, ContractInfoOf, @@ -523,7 +520,6 @@ impl Config for Test { type UploadOrigin = EnsureAccount; type InstantiateOrigin = EnsureAccount; type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; - type Debug = TestDebug; type ChainId = ChainId; } @@ -4554,3 +4550,151 @@ fn unstable_interface_rejected() { assert_ok!(builder::bare_instantiate(Code::Upload(code)).build().result); }); } + +#[test] +fn tracing_works_for_transfers() { + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000); + let mut tracer = CallTracer::new(false, |_| U256::zero()); + trace(&mut tracer, || { + builder::bare_call(BOB_ADDR).value(10_000_000).build_and_unwrap_result(); + }); + assert_eq!( + tracer.collect_traces(), + vec![CallTrace { + from: ALICE_ADDR, + to: BOB_ADDR, + value: U256::from(10_000_000), + call_type: CallType::Call, + ..Default::default() + },] + ) + }); +} + +#[test] +fn tracing_works() { + use crate::evm::*; + use CallType::*; + let (code, _code_hash) = compile_module("tracing").unwrap(); + let (wasm_callee, _) = compile_module("tracing_callee").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let Contract { addr: addr_callee, .. } = + builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_contract(); + + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let tracer_options = vec![ + ( false , vec![]), + ( + true , + vec![ + CallLog { + address: addr, + topics: Default::default(), + data: b"before".to_vec().into(), + position: 0, + }, + CallLog { + address: addr, + topics: Default::default(), + data: b"after".to_vec().into(), + position: 1, + }, + ], + ), + ]; + + // Verify that the first trace report the same weight reported by bare_call + let mut tracer = CallTracer::new(false, |w| w); + let gas_used = trace(&mut tracer, || { + builder::bare_call(addr).data((3u32, addr_callee).encode()).build().gas_consumed + }); + let traces = tracer.collect_traces(); + assert_eq!(&traces[0].gas_used, &gas_used); + + // Discarding gas usage, check that traces reported are correct + for (with_logs, logs) in tracer_options { + let mut tracer = CallTracer::new(with_logs, |_| U256::zero()); + trace(&mut tracer, || { + builder::bare_call(addr).data((3u32, addr_callee).encode()).build() + }); + + + assert_eq!( + tracer.collect_traces(), + vec![CallTrace { + from: ALICE_ADDR, + to: addr, + input: (3u32, addr_callee).encode(), + call_type: Call, + logs: logs.clone(), + calls: vec![ + CallTrace { + from: addr, + to: addr_callee, + input: 2u32.encode(), + output: hex_literal::hex!( + "08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001a546869732066756e6374696f6e20616c77617973206661696c73000000000000" + ).to_vec().into(), + revert_reason: Some( + "execution reverted: This function always fails".to_string() + ), + error: Some("execution reverted".to_string()), + call_type: Call, + ..Default::default() + }, + CallTrace { + from: addr, + to: addr, + input: (2u32, addr_callee).encode(), + call_type: Call, + logs: logs.clone(), + calls: vec![ + CallTrace { + from: addr, + to: addr_callee, + input: 1u32.encode(), + output: Default::default(), + error: Some("ContractTrapped".to_string()), + call_type: Call, + ..Default::default() + }, + CallTrace { + from: addr, + to: addr, + input: (1u32, addr_callee).encode(), + call_type: Call, + logs: logs.clone(), + calls: vec![ + CallTrace { + from: addr, + to: addr_callee, + input: 0u32.encode(), + output: 0u32.to_le_bytes().to_vec().into(), + call_type: Call, + ..Default::default() + }, + CallTrace { + from: addr, + to: addr, + input: (0u32, addr_callee).encode(), + call_type: Call, + ..Default::default() + }, + ], + ..Default::default() + }, + ], + ..Default::default() + }, + ], + ..Default::default() + },] + ); + } + }); +} diff --git a/substrate/frame/revive/src/tests/test_debug.rs b/substrate/frame/revive/src/tests/test_debug.rs deleted file mode 100644 index b1fdb2d47441e..0000000000000 --- a/substrate/frame/revive/src/tests/test_debug.rs +++ /dev/null @@ -1,235 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::*; - -use crate::{ - debug::{CallInterceptor, CallSpan, ExecResult, ExportedFunction, Tracing}, - primitives::ExecReturnValue, - test_utils::*, - DepositLimit, -}; -use frame_support::traits::Currency; -use pretty_assertions::assert_eq; -use sp_core::H160; -use std::cell::RefCell; - -#[derive(Clone, PartialEq, Eq, Debug)] -struct DebugFrame { - contract_address: sp_core::H160, - call: ExportedFunction, - input: Vec, - result: Option>, -} - -thread_local! { - static DEBUG_EXECUTION_TRACE: RefCell> = RefCell::new(Vec::new()); - static INTERCEPTED_ADDRESS: RefCell> = RefCell::new(None); -} - -pub struct TestDebug; -pub struct TestCallSpan { - contract_address: sp_core::H160, - call: ExportedFunction, - input: Vec, -} - -impl Tracing for TestDebug { - type CallSpan = TestCallSpan; - - fn new_call_span( - contract_address: &crate::H160, - entry_point: ExportedFunction, - input_data: &[u8], - ) -> TestCallSpan { - DEBUG_EXECUTION_TRACE.with(|d| { - d.borrow_mut().push(DebugFrame { - contract_address: *contract_address, - call: entry_point, - input: input_data.to_vec(), - result: None, - }) - }); - TestCallSpan { - contract_address: *contract_address, - call: entry_point, - input: input_data.to_vec(), - } - } -} - -impl CallInterceptor for TestDebug { - fn intercept_call( - contract_address: &sp_core::H160, - _entry_point: ExportedFunction, - _input_data: &[u8], - ) -> Option { - INTERCEPTED_ADDRESS.with(|i| { - if i.borrow().as_ref() == Some(contract_address) { - Some(Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![] })) - } else { - None - } - }) - } -} - -impl CallSpan for TestCallSpan { - fn after_call(self, output: &ExecReturnValue) { - DEBUG_EXECUTION_TRACE.with(|d| { - d.borrow_mut().push(DebugFrame { - contract_address: self.contract_address, - call: self.call, - input: self.input, - result: Some(output.data.clone()), - }) - }); - } -} - -#[test] -fn debugging_works() { - let (wasm_caller, _) = compile_module("call").unwrap(); - let (wasm_callee, _) = compile_module("store_call").unwrap(); - - fn current_stack() -> Vec { - DEBUG_EXECUTION_TRACE.with(|stack| stack.borrow().clone()) - } - - fn deploy(wasm: Vec) -> H160 { - Contracts::bare_instantiate( - RuntimeOrigin::signed(ALICE), - 0, - GAS_LIMIT, - DepositLimit::Balance(deposit_limit::()), - Code::Upload(wasm), - vec![], - Some([0u8; 32]), - ) - .result - .unwrap() - .addr - } - - fn constructor_frame(contract_address: &H160, after: bool) -> DebugFrame { - DebugFrame { - contract_address: *contract_address, - call: ExportedFunction::Constructor, - input: vec![], - result: if after { Some(vec![]) } else { None }, - } - } - - fn call_frame(contract_address: &H160, args: Vec, after: bool) -> DebugFrame { - DebugFrame { - contract_address: *contract_address, - call: ExportedFunction::Call, - input: args, - result: if after { Some(vec![]) } else { None }, - } - } - - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = Balances::deposit_creating(&ALICE, 1_000_000); - - assert_eq!(current_stack(), vec![]); - - let addr_caller = deploy(wasm_caller); - let addr_callee = deploy(wasm_callee); - - assert_eq!( - current_stack(), - vec![ - constructor_frame(&addr_caller, false), - constructor_frame(&addr_caller, true), - constructor_frame(&addr_callee, false), - constructor_frame(&addr_callee, true), - ] - ); - - let main_args = (100u32, &addr_callee.clone()).encode(); - let inner_args = (100u32).encode(); - - assert_ok!(Contracts::call( - RuntimeOrigin::signed(ALICE), - addr_caller, - 0, - GAS_LIMIT, - deposit_limit::(), - main_args.clone() - )); - - let stack_top = current_stack()[4..].to_vec(); - assert_eq!( - stack_top, - vec![ - call_frame(&addr_caller, main_args.clone(), false), - call_frame(&addr_callee, inner_args.clone(), false), - call_frame(&addr_callee, inner_args, true), - call_frame(&addr_caller, main_args, true), - ] - ); - }); -} - -#[test] -fn call_interception_works() { - let (wasm, _) = compile_module("dummy").unwrap(); - - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = Balances::deposit_creating(&ALICE, 1_000_000); - - let account_id = Contracts::bare_instantiate( - RuntimeOrigin::signed(ALICE), - 0, - GAS_LIMIT, - deposit_limit::().into(), - Code::Upload(wasm), - vec![], - // some salt to ensure that the address of this contract is unique among all tests - Some([0x41; 32]), - ) - .result - .unwrap() - .addr; - - // no interception yet - assert_ok!(Contracts::call( - RuntimeOrigin::signed(ALICE), - account_id, - 0, - GAS_LIMIT, - deposit_limit::(), - vec![], - )); - - // intercept calls to this contract - INTERCEPTED_ADDRESS.with(|i| *i.borrow_mut() = Some(account_id)); - - assert_err_ignore_postinfo!( - Contracts::call( - RuntimeOrigin::signed(ALICE), - account_id, - 0, - GAS_LIMIT, - deposit_limit::(), - vec![], - ), - >::ContractReverted, - ); - }); -} diff --git a/substrate/frame/revive/src/tracing.rs b/substrate/frame/revive/src/tracing.rs new file mode 100644 index 0000000000000..e9c05f8cb5058 --- /dev/null +++ b/substrate/frame/revive/src/tracing.rs @@ -0,0 +1,64 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{primitives::ExecReturnValue, DispatchError, Weight}; +use environmental::environmental; +use sp_core::{H160, H256, U256}; + +environmental!(tracer: dyn Tracer + 'static); + +/// Trace the execution of the given closure. +/// +/// # Warning +/// +/// Only meant to be called from off-chain code as its additional resource usage is +/// not accounted for in the weights or memory envelope. +pub fn trace R>(tracer: &mut (dyn Tracer + 'static), f: F) -> R { + tracer::using_once(tracer, f) +} + +/// Run the closure when tracing is enabled. +/// +/// This is safe to be called from on-chain code as tracing will never be activated +/// there. Hence the closure is not executed in this case. +pub(crate) fn if_tracing(f: F) { + tracer::with(f); +} + +/// Defines methods to trace contract interactions. +pub trait Tracer { + /// Called before a contract call is executed + fn enter_child_span( + &mut self, + from: H160, + to: H160, + is_delegate_call: bool, + is_read_only: bool, + value: U256, + input: &[u8], + gas: Weight, + ); + + /// Record a log event + fn log_event(&mut self, event: H160, topics: &[H256], data: &[u8]); + + /// Called after a contract call is executed + fn exit_child_span(&mut self, output: &ExecReturnValue, gas_left: Weight); + + /// Called when a contract call terminates with an error + fn exit_child_span_with_error(&mut self, error: DispatchError, gas_left: Weight); +} From fbc8733c71b6c4a837489cf73021597762268cfa Mon Sep 17 00:00:00 2001 From: kianenigma Date: Fri, 17 Jan 2025 19:09:58 +0000 Subject: [PATCH 096/169] add original pallet, almost compiles --- Cargo.lock | 23 + Cargo.toml | 1 + .../election-provider-multi-block/Cargo.toml | 85 + .../src/helpers.rs | 245 ++ .../election-provider-multi-block/src/lib.rs | 2042 +++++++++++++++++ .../src/mock/mod.rs | 657 ++++++ .../src/mock/signed.rs | 258 +++ .../src/mock/staking.rs | 230 ++ .../src/mock/weight_info.rs | 104 + .../src/signed/benchmarking.rs | 1 + .../src/signed/mod.rs | 700 ++++++ .../src/signed/tests.rs | 379 +++ .../src/types.rs | 402 ++++ .../src/unsigned/benchmarking.rs | 25 + .../src/unsigned/miner.rs | 1970 ++++++++++++++++ .../src/unsigned/mod.rs | 578 +++++ .../src/verifier/impls.rs | 892 +++++++ .../src/verifier/mod.rs | 273 +++ .../src/verifier/tests.rs | 1207 ++++++++++ .../src/weights.rs | 258 +++ .../election-provider-multi-phase/src/lib.rs | 2 +- .../solution-type/src/single_page.rs | 77 + .../election-provider-support/src/lib.rs | 10 +- .../election-provider-support/src/traits.rs | 18 + .../primitives/npos-elections/src/helpers.rs | 24 +- .../primitives/npos-elections/src/lib.rs | 6 + 26 files changed, 10463 insertions(+), 4 deletions(-) create mode 100644 substrate/frame/election-provider-multi-block/Cargo.toml create mode 100644 substrate/frame/election-provider-multi-block/src/helpers.rs create mode 100644 substrate/frame/election-provider-multi-block/src/lib.rs create mode 100644 substrate/frame/election-provider-multi-block/src/mock/mod.rs create mode 100644 substrate/frame/election-provider-multi-block/src/mock/signed.rs create mode 100644 substrate/frame/election-provider-multi-block/src/mock/staking.rs create mode 100644 substrate/frame/election-provider-multi-block/src/mock/weight_info.rs create mode 100644 substrate/frame/election-provider-multi-block/src/signed/benchmarking.rs create mode 100644 substrate/frame/election-provider-multi-block/src/signed/mod.rs create mode 100644 substrate/frame/election-provider-multi-block/src/signed/tests.rs create mode 100644 substrate/frame/election-provider-multi-block/src/types.rs create mode 100644 substrate/frame/election-provider-multi-block/src/unsigned/benchmarking.rs create mode 100644 substrate/frame/election-provider-multi-block/src/unsigned/miner.rs create mode 100644 substrate/frame/election-provider-multi-block/src/unsigned/mod.rs create mode 100644 substrate/frame/election-provider-multi-block/src/verifier/impls.rs create mode 100644 substrate/frame/election-provider-multi-block/src/verifier/mod.rs create mode 100644 substrate/frame/election-provider-multi-block/src/verifier/tests.rs create mode 100644 substrate/frame/election-provider-multi-block/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index 28da604b145c5..4395368515d73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13387,6 +13387,29 @@ dependencies = [ "sp-tracing 16.0.0", ] +[[package]] +name = "pallet-election-provider-multi-block" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking 28.0.0", + "frame-election-provider-support 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", + "log", + "pallet-balances 28.0.0", + "parity-scale-codec", + "parking_lot 0.12.3", + "rand", + "scale-info", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-npos-elections 26.0.0", + "sp-runtime 31.0.1", + "sp-std 14.0.0", + "sp-tracing 16.0.0", +] + [[package]] name = "pallet-election-provider-multi-phase" version = "27.0.0" diff --git a/Cargo.toml b/Cargo.toml index e17f08148b163..3767a097761b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -344,6 +344,7 @@ members = [ "substrate/frame/core-fellowship", "substrate/frame/delegated-staking", "substrate/frame/democracy", + "substrate/frame/election-provider-multi-block", "substrate/frame/election-provider-multi-phase", "substrate/frame/election-provider-multi-phase/test-staking-e2e", "substrate/frame/election-provider-support", diff --git a/substrate/frame/election-provider-multi-block/Cargo.toml b/substrate/frame/election-provider-multi-block/Cargo.toml new file mode 100644 index 0000000000000..7997e487058f6 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/Cargo.toml @@ -0,0 +1,85 @@ +[package] +name = "pallet-election-provider-multi-block" +version = "4.0.0-dev" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true +description = "PALLET multi phase+block election providers" +readme = "README.md" + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { features = [ + "derive", +], workspace = true } +log = { workspace = true } +scale-info = { features = [ + "derive", +], workspace = true } + +frame-support = { workspace = true } +frame-system = { workspace = true } +frame-election-provider-support = { workspace = true } + +sp-io = { workspace = true } +sp-std = { workspace = true } +sp-core = { workspace = true } +sp-runtime = { workspace = true } +sp-npos-elections = { workspace = true } +sp-arithmetic = { workspace = true } + +# Optional imports for benchmarking +frame-benchmarking = { optional = true, workspace = true } +rand = { features = ["alloc", "small_rng"], optional = true, workspace = true } + +[dev-dependencies] +frame-benchmarking = { workspace = true, default-features = true } +pallet-balances = { workspace = true, default-features = true } +parking_lot = { workspace = true, default-features = true } +sp-core = { workspace = true } +sp-io = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-election-provider-support/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-balances/std", + "rand/std", + "scale-info/std", + "sp-arithmetic/std", + "sp-core/std", + "sp-io/std", + "sp-npos-elections/std", + "sp-runtime/std", + "sp-std/std", + "sp-tracing/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-election-provider-support/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "rand", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-election-provider-support/try-runtime", + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-balances/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/substrate/frame/election-provider-multi-block/src/helpers.rs b/substrate/frame/election-provider-multi-block/src/helpers.rs new file mode 100644 index 0000000000000..68b514145ab39 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/helpers.rs @@ -0,0 +1,245 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 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. + +//! Some helper functions/macros for this crate. + +use crate::{ + types::{PageIndex, VoterOf}, + AllVoterPagesOf, Config, SolutionTargetIndexOf, SolutionVoterIndexOf, VoteWeight, +}; +use frame_support::{traits::Get, BoundedVec}; +use sp_runtime::SaturatedConversion; +use sp_std::{collections::btree_map::BTreeMap, convert::TryInto, prelude::*}; + +#[macro_export] +macro_rules! log { + ($level:tt, $pattern:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: $crate::LOG_PREFIX, + concat!("[#{:?}] 🗳🗳🗳 ", $pattern), >::block_number() $(, $values)* + ) + }; +} + +macro_rules! sublog { + ($level:tt, $sub_pallet:tt, $pattern:expr $(, $values:expr)* $(,)?) => { + #[cfg(not(feature = "std"))] + log!($level, $pattern $(, $values )*); + #[cfg(feature = "std")] + log::$level!( + target: format!("{}::{}", $crate::LOG_PREFIX, $sub_pallet).as_ref(), + concat!("[#{:?}] 🗳🗳🗳 ", $pattern), >::block_number() $(, $values )* + ) + }; +} + +/// Generate an `efficient closure of voters and the page in which they live in. +pub fn generate_voter_page_fn( + paged_snapshot: &AllVoterPagesOf, +) -> impl Fn(&T::AccountId) -> Option { + let mut cache: BTreeMap = BTreeMap::new(); + paged_snapshot + .iter() + .enumerate() + .map(|(page, whatever)| (page.saturated_into::(), whatever)) + .for_each(|(page, page_voters)| { + page_voters.iter().for_each(|(v, _, _)| { + let _existed = cache.insert(v.clone(), page); + // if a duplicate exists, we only consider the last one. Defensive only, should + // never happen. + debug_assert!(_existed.is_none()); + }); + }); + move |who| cache.get(who).copied() +} + +/// Generate a btree-map cache of the voters and their indices within the provided `snapshot`. +/// +/// This does not care about pagination. `snapshot` might be a single page or the entire blob of +/// voters. +/// +/// This can be used to efficiently build index getter closures. +pub fn generate_voter_cache>( + snapshot: &BoundedVec, AnyBound>, +) -> BTreeMap { + let mut cache: BTreeMap = BTreeMap::new(); + snapshot.iter().enumerate().for_each(|(i, (x, _, _))| { + let _existed = cache.insert(x.clone(), i); + // if a duplicate exists, we only consider the last one. Defensive only, should never + // happen. + debug_assert!(_existed.is_none()); + }); + + cache +} + +/// Create a function that returns the index of a voter in the snapshot. +/// +/// The returning index type is the same as the one defined in `T::Solution::Voter`. +/// +/// ## Warning +/// +/// Note that this will represent the snapshot data from which the `cache` is generated. +pub fn voter_index_fn( + cache: &BTreeMap, +) -> impl Fn(&T::AccountId) -> Option> + '_ { + move |who| { + cache + .get(who) + .and_then(|i| >>::try_into(*i).ok()) + } +} + +/// Create a function that returns the index of a voter in the snapshot. +/// +/// Same as [`voter_index_fn`] but the returned function owns all its necessary data; nothing is +/// borrowed. +pub fn voter_index_fn_owned( + cache: BTreeMap, +) -> impl Fn(&T::AccountId) -> Option> { + move |who| { + cache + .get(who) + .and_then(|i| >>::try_into(*i).ok()) + } +} + +/// Same as [`voter_index_fn`], but the returning index is converted into usize, if possible. +/// +/// ## Warning +/// +/// Note that this will represent the snapshot data from which the `cache` is generated. +pub fn voter_index_fn_usize( + cache: &BTreeMap, +) -> impl Fn(&T::AccountId) -> Option + '_ { + move |who| cache.get(who).cloned() +} + +/// A non-optimized, linear version of [`voter_index_fn`] that does not need a cache and does a +/// linear search. +/// +/// ## Warning +/// +/// Not meant to be used in production. +#[cfg(test)] +pub fn voter_index_fn_linear( + snapshot: &Vec>, +) -> impl Fn(&T::AccountId) -> Option> + '_ { + move |who| { + snapshot + .iter() + .position(|(x, _, _)| x == who) + .and_then(|i| >>::try_into(i).ok()) + } +} + +/// Create a function that returns the index of a target in the snapshot. +/// +/// The returned index type is the same as the one defined in `T::Solution::Target`. +/// +/// Note: to the extent possible, the returned function should be cached and reused. Producing that +/// function requires a `O(n log n)` data transform. Each invocation of that function completes +/// in `O(log n)`. +pub fn target_index_fn( + snapshot: &Vec, +) -> impl Fn(&T::AccountId) -> Option> + '_ { + let cache: BTreeMap<_, _> = + snapshot.iter().enumerate().map(|(idx, account_id)| (account_id, idx)).collect(); + move |who| { + cache + .get(who) + .and_then(|i| >>::try_into(*i).ok()) + } +} + +/// Create a function the returns the index to a target in the snapshot. +/// +/// The returned index type is the same as the one defined in `T::Solution::Target`. +/// +/// ## Warning +/// +/// Not meant to be used in production. +#[cfg(test)] +pub fn target_index_fn_linear( + snapshot: &Vec, +) -> impl Fn(&T::AccountId) -> Option> + '_ { + move |who| { + snapshot + .iter() + .position(|x| x == who) + .and_then(|i| >>::try_into(i).ok()) + } +} + +/// Create a function that can map a voter index ([`SolutionVoterIndexOf`]) to the actual voter +/// account using a linearly indexible snapshot. +pub fn voter_at_fn( + snapshot: &Vec>, +) -> impl Fn(SolutionVoterIndexOf) -> Option + '_ { + move |i| { + as TryInto>::try_into(i) + .ok() + .and_then(|i| snapshot.get(i).map(|(x, _, _)| x).cloned()) + } +} + +/// Create a function that can map a target index ([`SolutionTargetIndexOf`]) to the actual target +/// account using a linearly indexible snapshot. +pub fn target_at_fn( + snapshot: &Vec, +) -> impl Fn(SolutionTargetIndexOf) -> Option + '_ { + move |i| { + as TryInto>::try_into(i) + .ok() + .and_then(|i| snapshot.get(i).cloned()) + } +} + +/// Create a function to get the stake of a voter. +/// +/// This is not optimized and uses a linear search. +#[cfg(test)] +pub fn stake_of_fn_linear( + snapshot: &Vec>, +) -> impl Fn(&T::AccountId) -> VoteWeight + '_ { + move |who| { + snapshot + .iter() + .find(|(x, _, _)| x == who) + .map(|(_, x, _)| *x) + .unwrap_or_default() + } +} + +/// Create a function to get the stake of a voter. +/// +/// ## Warning +/// +/// The cache need must be derived from the same snapshot. Zero is returned if a voter is +/// non-existent. +pub fn stake_of_fn<'a, T: Config, AnyBound: Get>( + snapshot: &'a BoundedVec, AnyBound>, + cache: &'a BTreeMap, +) -> impl Fn(&T::AccountId) -> VoteWeight + 'a { + move |who| { + if let Some(index) = cache.get(who) { + snapshot.get(*index).map(|(_, x, _)| x).cloned().unwrap_or_default() + } else { + 0 + } + } +} diff --git a/substrate/frame/election-provider-multi-block/src/lib.rs b/substrate/frame/election-provider-multi-block/src/lib.rs new file mode 100644 index 0000000000000..d7c0ef860495a --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/lib.rs @@ -0,0 +1,2042 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Multi-phase, multi-block, election provider pallet. +//! +//! ## Overall idea +//! +//! [`pallet_election_provider_multi_phase`] provides the basic ability for NPoS solutions to be +//! computed offchain (essentially anywhere) and submitted back to the chain as signed or unsigned +//! transaction, with sensible configurations and fail-safe mechanisms to ensure system safety. +//! Nonetheless, it has a limited capacity in terms of number of voters it can process in a **single +//! block**. +//! +//! This pallet takes [`pallet_election_provider_multi_phase`], keeps most of its ideas and core +//! premises, and extends it to support paginated, multi-block operations. The final goal of this +//! pallet is scale linearly with the number of blocks allocated to the elections. Moreover, the +//! amount of work that it does in one block should be bounded and measurable, making it suitable +//! for a parachain. In principle, with large enough blocks (in a dedicated parachain), the number +//! of voters included in the NPoS system can grow significantly (yet, obviously not indefinitely). +//! +//! Note that this pallet does not consider how the recipient is processing the results. To ensure +//! scalability, of course, the recipient of this pallet's data (i.e. `pallet-staking`) must also be +//! capable of pagination and multi-block processing. +//! +//! ## Companion pallets +//! +//! This pallet is essentially hierarchical. This particular one is the top level one. It contains +//! the shared information that all child pallets use. All child pallets can depend on on the top +//! level pallet ONLY, but not the other way around. For those cases, traits are used. +//! +//! This pallet will only function in a sensible way if it is peered with its companion pallets. +//! +//! - The [`verifier`] pallet provides a standard implementation of the [`verifier::Verifier`]. This +//! pallet is mandatory. +//! - The [`unsigned`] module provides the implementation of unsigned submission by validators. If +//! this pallet is included, then [`Config::UnsignedPhase`] will determine its duration. +//! - The [`Signed`] module provides the implementation of the signed submission by any account. If +//! this pallet is included, the combined [`Config::SignedPhase`] and +//! [`Config::SignedValidationPhase`] will deter its duration +//! +//! ### Pallet Ordering: +//! +//! TODO: parent, verifier, signed, unsigned +//! +//! ## Pagination +//! +//! Most of the external APIs of this pallet are paginated. All pagination follow a patter where if +//! `N` pages exist, the first paginated call is `function(N-1)` and the last one is `function(0)`. +//! For example, with 3 pages, the `elect` of [`ElectionProvider`] is expected to be called as +//! `elect(2) -> elect(1) -> elect(0)`. In essence, calling a paginated function with index 0 is +//! always a signal of termination, meaning that no further calls will follow. +//! +//! ## Phases +//! +//! The timeline of pallet is as follows. At each block, +//! [`frame_election_provider_support::ElectionDataProvider::next_election_prediction`] is used to +//! estimate the time remaining to the next call to +//! [`frame_election_provider_support::ElectionProvider::elect`]. Based on this, a phase is chosen. +//! An example timeline is as follows: +//! +//! ```ignore +//! elect() +//! + <--T::SignedPhase--> + <--T::UnsignedPhase--> + +//! +-------------------------------------------------------------------+ +//! Phase::Off + Phase::Signed + Phase::Unsigned + +//! ``` +//! +//! The duration of both phases are configurable, and their existence is optional. Each of the +//! phases can be disabled by essentially setting their length to zero. If both phases have length +//! zero, then the pallet essentially runs only the fallback strategy, denoted by +//! [`Config::Fallback`]. +//! +//! - Note that the prediction of the election is assume to be the **first call** to elect. For +//! example, with 3 pages, the prediction must point to the `elect(2)`. +//! - Note that the unsigned phase starts [`pallet::Config::UnsignedPhase`] blocks before the +//! `next_election_prediction`, but only ends when a call to [`ElectionProvider::elect`] happens. If +//! no `elect` happens, the current phase (usually unsigned) is extended. +//! +//! > Given this, it is rather important for the user of this pallet to ensure it always terminates +//! election via `elect` before requesting a new one. +//! +//! TODO: test case: elect(2) -> elect(1) -> elect(2) +//! TODO: should we wipe the verifier afterwards, or just `::take()` the election result? +//! +//! ## Feasible Solution (correct solution) +//! +//! All submissions must undergo a feasibility check. Signed solutions are checked on by one at the +//! end of the signed phase, and the unsigned solutions are checked on the spot. A feasible solution +//! is as follows: +//! +//! 0. **all** of the used indices must be correct. +//! 1. present *exactly* correct number of winners. +//! 2. any assignment is checked to match with [`RoundSnapshot::voters`]. +//! 3. the claimed score is valid, based on the fixed point arithmetic accuracy. +//! +//! ### Signed Phase +//! +//! In the signed phase, solutions (of type [`RawSolution`]) are submitted and queued on chain. A +//! deposit is reserved, based on the size of the solution, for the cost of keeping this solution +//! on-chain for a number of blocks, and the potential weight of the solution upon being checked. A +//! maximum of `pallet::Config::MaxSignedSubmissions` solutions are stored. The queue is always +//! sorted based on score (worse to best). +//! +//! Upon arrival of a new solution: +//! +//! 1. If the queue is not full, it is stored in the appropriate sorted index. +//! 2. If the queue is full but the submitted solution is better than one of the queued ones, the +//! worse solution is discarded, the bond of the outgoing solution is returned, and the new +//! solution is stored in the correct index. +//! 3. If the queue is full and the solution is not an improvement compared to any of the queued +//! ones, it is instantly rejected and no additional bond is reserved. +//! +//! A signed solution cannot be reversed, taken back, updated, or retracted. In other words, the +//! origin can not bail out in any way, if their solution is queued. +//! +//! Upon the end of the signed phase, the solutions are examined from best to worse (i.e. `pop()`ed +//! until drained). Each solution undergoes an expensive `Pallet::feasibility_check`, which ensures +//! the score claimed by this score was correct, and it is valid based on the election data (i.e. +//! votes and candidates). At each step, if the current best solution passes the feasibility check, +//! it is considered to be the best one. The sender of the origin is rewarded, and the rest of the +//! queued solutions get their deposit back and are discarded, without being checked. +//! +//! The following example covers all of the cases at the end of the signed phase: +//! +//! ```ignore +//! Queue +//! +-------------------------------+ +//! |Solution(score=20, valid=false)| +--> Slashed +//! +-------------------------------+ +//! |Solution(score=15, valid=true )| +--> Rewarded, Saved +//! +-------------------------------+ +//! |Solution(score=10, valid=true )| +--> Discarded +//! +-------------------------------+ +//! |Solution(score=05, valid=false)| +--> Discarded +//! +-------------------------------+ +//! | None | +//! +-------------------------------+ +//! ``` +//! +//! Note that both of the bottom solutions end up being discarded and get their deposit back, +//! despite one of them being *invalid*. +//! +//! ## Unsigned Phase +//! +//! The unsigned phase will always follow the signed phase, with the specified duration. In this +//! phase, only validator nodes can submit solutions. A validator node who has offchain workers +//! enabled will start to mine a solution in this phase and submits it back to the chain as an +//! unsigned transaction, thus the name _unsigned_ phase. This unsigned transaction can never be +//! valid if propagated, and it acts similar to an inherent. +//! +//! Validators will only submit solutions if the one that they have computed is sufficiently better +//! than the best queued one (see [`pallet::Config::SolutionImprovementThreshold`]) and will limit +//! the weigh of the solution to [`pallet::Config::MinerMaxWeight`]. +//! +//! The unsigned phase can be made passive depending on how the previous signed phase went, by +//! setting the first inner value of [`Phase`] to `false`. For now, the signed phase is always +//! active. +//! +//! ### Emergency Phase and Fallback +//! +//! TODO: +//! +//! ## Accuracy +//! +//! TODO +//! +//! ## Error types +//! +//! TODO: +//! +//! ## Future Plans +//! +//! **Challenge Phase**. We plan on adding a third phase to the pallet, called the challenge phase. +//! This is a phase in which no further solutions are processed, and the current best solution might +//! be challenged by anyone (signed or unsigned). The main plan here is to enforce the solution to +//! be PJR. Checking PJR on-chain is quite expensive, yet proving that a solution is **not** PJR is +//! rather cheap. If a queued solution is successfully proven bad: +//! +//! 1. We must surely slash whoever submitted that solution (might be a challenge for unsigned +//! solutions). +//! 2. We will fallback to the emergency strategy (likely extending the current era). +//! +//! **Bailing out**. The functionality of bailing out of a queued solution is nice. A miner can +//! submit a solution as soon as they _think_ it is high probability feasible, and do the checks +//! afterwards, and remove their solution (for a small cost of probably just transaction fees, or a +//! portion of the bond). +//! +//! **Conditionally open unsigned phase**: Currently, the unsigned phase is always opened. This is +//! useful because an honest validator will run substrate OCW code, which should be good enough to +//! trump a mediocre or malicious signed submission (assuming in the absence of honest signed bots). +//! If there are signed submissions, they can be checked against an absolute measure (e.g. PJR), +//! then we can only open the unsigned phase in extreme conditions (i.e. "no good signed solution +//! received") to spare some work for the active validators. +//! +//! **Allow smaller solutions and build up**: For now we only allow solutions that are exactly +//! [`DesiredTargets`], no more, no less. Over time, we can change this to a [min, max] where any +//! solution within this range is acceptable, where bigger solutions are prioritized. +//! +//! **Score based on (byte) size**: We should always prioritize small solutions over bigger ones, if +//! there is a tie. Even more harsh should be to enforce the bound of the `reduce` algorithm. + +// Implementation notes: +// +// - Naming convention is: `${singular}_page` for singular, e.g. `voter_page` for `Vec`. +// `paged_${plural}` for plural, e.g. `paged_voters` for `Vec>`. +// +// - Since this crate has multiple `Pallet` and `Configs`, in each sub-pallet, we only reference the +// local `Pallet` without a prefix and allow it to be imported via `use`. Avoid `super::Pallet` +// except for the case of a modules that want to reference their local `Pallet` . The +// `crate::Pallet` is always reserved for the parent pallet. Other sibling pallets must be +// referenced with full path, e.g. `crate::Verifier::Pallet`. Do NOT write something like `use +// unsigned::Pallet as UnsignedPallet`. +// +// - Respecting private storage items with wrapper We move all implementations out of the `mod +// pallet` as much as possible to ensure we NEVER access the internal storage items directly. All +// operations should happen with the wrapper types. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_election_provider_support::{ + onchain, BoundedSupportsOf, ElectionDataProvider, ElectionProvider, PageIndex, +}; +use frame_support::{ + ensure, + traits::{ConstU32, Defensive, Get}, + BoundedVec, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, +}; +use frame_system::pallet_prelude::BlockNumberFor; +use scale_info::TypeInfo; +use sp_arithmetic::traits::Zero; +use sp_npos_elections::VoteWeight; +use sp_runtime::SaturatedConversion; +use sp_std::prelude::*; +use verifier::Verifier; + +#[cfg(test)] +mod mock; +#[macro_use] +pub mod helpers; + +const LOG_PREFIX: &'static str = "runtime::multiblock-election"; + +// pub mod signed; +pub mod signed; +pub mod types; +pub mod unsigned; +pub mod verifier; +pub mod weights; + +pub use pallet::*; +pub use types::*; +pub use weights::WeightInfo; + +/// A fallback implementation that transitions the pallet to the emergency phase. +pub struct InitiateEmergencyPhase(sp_std::marker::PhantomData); +impl ElectionProvider for InitiateEmergencyPhase { + type AccountId = T::AccountId; + type BlockNumber = BlockNumberFor; + type DataProvider = T::DataProvider; + type Error = &'static str; + type Pages = ConstU32<1>; + type MaxBackersPerWinner = T::MaxBackersPerWinner; + type MaxWinnersPerPage = T::MaxWinnersPerPage; + + fn elect(remaining: PageIndex) -> Result, Self::Error> { + ensure!(remaining == 0, "fallback should only have 1 page"); + log!(warn, "Entering emergency phase."); + Err("Emergency phase started.") + } + + fn ongoing() -> bool { + false + } +} + +/// Internal errors of the pallet. +/// +/// Note that this is different from [`pallet::Error`]. +#[derive( + frame_support::DebugNoBound, frame_support::PartialEqNoBound, frame_support::EqNoBound, +)] +pub enum ElectionError { + /// An error happened in the feasibility check sub-system. + Feasibility(verifier::FeasibilityError), + /// An error in the fallback. + Fallback(FallbackErrorOf), + /// An error in the onchain seq-phragmen implementation + OnChain(onchain::Error), + /// An error happened in the data provider. + DataProvider(&'static str), + /// the corresponding page in the queued supports is not available. + SupportPageNotAvailable, +} + +#[cfg(test)] +impl PartialEq for ElectionError { + fn eq(&self, other: &Self) -> bool { + matches!(self, other) + } +} + +impl From for ElectionError { + fn from(e: onchain::Error) -> Self { + ElectionError::OnChain(e) + } +} + +impl From for ElectionError { + fn from(e: verifier::FeasibilityError) -> Self { + ElectionError::Feasibility(e) + } +} + +/// Different operations that the [`Config::AdminOrigin`] can perform on the pallet. +#[derive( + Encode, + Decode, + MaxEncodedLen, + TypeInfo, + RuntimeDebugNoBound, + CloneNoBound, + PartialEqNoBound, + EqNoBound, +)] +#[codec(mel_bound(T: Config))] +#[scale_info(skip_type_params(T))] +pub enum AdminOperation { + /// Clear all storage items. + /// + /// This will probably end-up being quite expensive. It will clear the internals of all + /// pallets, setting cleaning all of them. + /// + /// Hopefully, this can result in a full reset of the system. + KillEverything, + /// Force-set the phase to the given phase. + /// + /// This can have many many combinations, use only with care and sufficient testing. + ForceSetPhase(Phase>), + /// Set the given (single page) emergency solution. + /// + /// This can be called in any phase and, can behave like any normal solution, but it should + /// probably be used only in [`Phase::Emergency`]. + SetSolution(SolutionOf, ElectionScore), + /// Trigger the (single page) fallback in `instant` mode, with the given parameters, and + /// queue it if correct. + /// + /// This can be called in any phase and, can behave like any normal solution, but it should + /// probably be used only in [`Phase::Emergency`]. + TriggerFallback, + /// Set the minimum untrusted score. This is directly communicated to the verifier component to + /// be taken into account. + /// + /// This is useful in preventing any serious issue where due to a bug we accept a very bad + /// solution. + SetMinUntrustedScore(ElectionScore), +} + +#[frame_support::pallet] +pub mod pallet { + use crate::{ + types::*, + verifier::{self}, + AdminOperation, WeightInfo, + }; + use frame_election_provider_support::{ + ElectionDataProvider, ElectionProvider, NposSolution, PageIndex, + }; + use frame_support::{pallet_prelude::*, traits::EnsureOrigin, Twox64Concat}; + use frame_system::pallet_prelude::*; + use sp_arithmetic::{traits::CheckedAdd, PerThing, UpperOf}; + use sp_runtime::traits::{Hash, Saturating, Zero}; + use sp_std::{borrow::ToOwned, prelude::*}; + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + + IsType<::RuntimeEvent> + + TryInto>; + + /// Duration of the unsigned phase. + #[pallet::constant] + type UnsignedPhase: Get>; + /// Duration of the signed phase. + #[pallet::constant] + type SignedPhase: Get>; + /// Duration of the singed validation phase. + /// + /// The duration of this should not be less than `T::Pages`, and there is no point in it + /// being more than `SignedPhase::MaxSubmission::get() * T::Pages`. TODO: integrity test for + /// it. + type SignedValidationPhase: Get>; + + /// The number of snapshot voters to fetch per block. + #[pallet::constant] + type VoterSnapshotPerBlock: Get; + + /// The number of snapshot targets to fetch per block. + #[pallet::constant] + type TargetSnapshotPerBlock: Get; + + /// The number of pages. + /// + /// The snapshot is created with this many keys in the storage map. + /// + /// The solutions may contain at MOST this many pages, but less pages are acceptable as + /// well. + #[pallet::constant] + type Pages: Get; + + /// Something that will provide the election data. + type DataProvider: ElectionDataProvider< + AccountId = Self::AccountId, + BlockNumber = BlockNumberFor, + >; + + /// The solution type. + type Solution: codec::FullCodec + + Default + + PartialEq + + Eq + + Clone + + sp_std::fmt::Debug + + Ord + + NposSolution + + TypeInfo + + MaxEncodedLen; + + /// The fallback type used for the election. + /// + /// This type is only used on the last page of the election, therefore it may at most have + /// 1 pages. + type Fallback: ElectionProvider< + AccountId = Self::AccountId, + BlockNumber = BlockNumberFor, + DataProvider = Self::DataProvider, + Pages = ConstU32<1>, + >; + + /// The verifier pallet's interface. + type Verifier: verifier::Verifier, AccountId = Self::AccountId> + + verifier::AsynchronousVerifier; + + /// The number of blocks ahead of time to try and have the election results ready by. + type Lookahead: Get>; + + /// The origin that can perform administration operations on this pallet. + type AdminOrigin: EnsureOrigin; + + /// The weight of the pallet. + type WeightInfo: WeightInfo; + } + + #[pallet::call] + impl Pallet { + #[pallet::weight(0)] + #[pallet::call_index(0)] + pub fn manage(_origin: OriginFor, op: AdminOperation) -> DispatchResultWithPostInfo { + todo!(); + } + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(now: BlockNumberFor) -> Weight { + // TODO + let todo_weight: Weight = Default::default(); + + // first, calculate the main phase switches thresholds. + let unsigned_deadline = T::UnsignedPhase::get(); + let signed_validation_deadline = + T::SignedValidationPhase::get().saturating_add(unsigned_deadline); + let signed_deadline = T::SignedPhase::get().saturating_add(signed_validation_deadline); + let snapshot_deadline = signed_deadline.saturating_add(T::Pages::get().into()); + + let next_election = T::DataProvider::next_election_prediction(now) + .saturating_sub(T::Lookahead::get()) + .max(now); + let remaining_blocks = next_election - now; + let current_phase = Self::current_phase(); + + log!( + trace, + "current phase {:?}, next election {:?}, remaining: {:?}, deadlines: [unsigned {:?} signed_validation {:?}, signed {:?}, snapshot {:?}]", + current_phase, + next_election, + remaining_blocks, + unsigned_deadline, + signed_validation_deadline, + signed_deadline, + snapshot_deadline, + ); + + match current_phase { + // start and continue snapshot. + Phase::Off + if remaining_blocks <= snapshot_deadline + // && remaining_blocks > signed_deadline + => + { + let remaining_pages = Self::msp(); + log!(info, "starting snapshot creation, remaining block: {}", remaining_pages); + let count = Self::create_targets_snapshot().unwrap(); + let count = Self::create_voters_snapshot_paged(remaining_pages).unwrap(); + CurrentPhase::::put(Phase::Snapshot(remaining_pages)); + todo_weight + }, + Phase::Snapshot(x) if x > 0 => { + // we don't check block numbers here, snapshot creation is mandatory. + let remaining_pages = x.saturating_sub(1); + log!(info, "continuing voter snapshot creation [{}]", remaining_pages); + CurrentPhase::::put(Phase::Snapshot(remaining_pages)); + Self::create_voters_snapshot_paged(remaining_pages).unwrap(); + todo_weight + }, + + // start signed. + Phase::Snapshot(0) + if remaining_blocks <= signed_deadline && + remaining_blocks > signed_validation_deadline => + { + // NOTE: if signed-phase length is zero, second part of the if-condition fails. + // TODO: even though we have the integrity test, what if we open the signed + // phase, and there's not enough blocks to finalize it? that can happen under + // any circumstance and we should deal with it. + + >::put(Phase::Signed); + Self::deposit_event(Event::SignedPhaseStarted(Self::round())); + todo_weight + }, + + // start signed verification. + Phase::Signed + if remaining_blocks <= signed_validation_deadline && + remaining_blocks > unsigned_deadline => + { + // Start verification of the signed stuff. + >::put(Phase::SignedValidation(now)); + Self::deposit_event(Event::SignedValidationPhaseStarted(Self::round())); + // we don't do anything else here. We expect the signed sub-pallet to handle + // whatever else needs to be done. + // TODO: this notification system based on block numbers is 100% based on the + // on_initialize of the parent pallet is called before the rest of them. + todo_weight + }, + + // start unsigned + Phase::Signed | Phase::SignedValidation(_) | Phase::Snapshot(0) + if remaining_blocks <= unsigned_deadline && remaining_blocks > Zero::zero() => + { + >::put(Phase::Unsigned(now)); + Self::deposit_event(Event::UnsignedPhaseStarted(Self::round())); + todo_weight + }, + _ => T::WeightInfo::on_initialize_nothing(), + } + } + + fn integrity_test() { + use sp_std::mem::size_of; + // The index type of both voters and targets need to be smaller than that of usize (very + // unlikely to be the case, but anyhow). + assert!(size_of::>() <= size_of::()); + assert!(size_of::>() <= size_of::()); + + // also, because `VoterSnapshotPerBlock` and `TargetSnapshotPerBlock` are in u32, we + // assert that both of these types are smaller than u32 as well. + assert!(size_of::>() <= size_of::()); + assert!(size_of::>() <= size_of::()); + + let pages_bn: BlockNumberFor = T::Pages::get().into(); + // pages must be at least 1. + assert!(T::Pages::get() > 0); + + // pages + the amount of Lookahead that we expect shall not be more than the length of + // any phase. + let lookahead = T::Lookahead::get(); + assert!(pages_bn + lookahead < T::SignedPhase::get()); + assert!(pages_bn + lookahead < T::UnsignedPhase::get()); + + // Based on the requirements of [`sp_npos_elections::Assignment::try_normalize`]. + let max_vote: usize = as NposSolution>::LIMIT; + + // 2. Maximum sum of [SolutionAccuracy; 16] must fit into `UpperOf`. + let maximum_chain_accuracy: Vec>> = (0..max_vote) + .map(|_| { + >>::from( + >::one().deconstruct(), + ) + }) + .collect(); + let _: UpperOf> = maximum_chain_accuracy + .iter() + .fold(Zero::zero(), |acc, x| acc.checked_add(x).unwrap()); + + // We only accept data provider who's maximum votes per voter matches our + // `T::Solution`'s `LIMIT`. + // + // NOTE that this pallet does not really need to enforce this in runtime. The + // solution cannot represent any voters more than `LIMIT` anyhow. + assert_eq!( + ::MaxVotesPerVoter::get(), + as NposSolution>::LIMIT as u32, + ); + + // The duration of the signed validation phase should be such that at least one solution + // can be verified. + assert!( + T::SignedValidationPhase::get() >= T::Pages::get().into(), + "signed validation phase should be at least as long as the number of pages." + ); + } + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// The signed phase of the given round has started. + SignedPhaseStarted(u32), + /// The unsigned validation phase of the given round has started. + SignedValidationPhaseStarted(u32), + /// The unsigned phase of the given round has started. + UnsignedPhaseStarted(u32), + } + + /// Error of the pallet that can be returned in response to dispatches. + #[pallet::error] + pub enum Error { + /// Submission is too early (or too late, depending on your point of reference). + EarlySubmission, + /// The round counter is wrong. + WrongRound, + /// Submission is too weak to be considered an improvement. + WeakSubmission, + /// Wrong number of pages in the solution. + WrongPageCount, + /// Wrong number of winners presented. + WrongWinnerCount, + /// The snapshot fingerprint is not a match. The solution is likely outdated. + WrongFingerprint, + } + + impl PartialEq for Error { + fn eq(&self, other: &Self) -> bool { + use Error::*; + match (self, other) { + (EarlySubmission, EarlySubmission) | + (WrongRound, WrongRound) | + (WeakSubmission, WeakSubmission) | + (WrongWinnerCount, WrongWinnerCount) | + (WrongPageCount, WrongPageCount) => true, + _ => false, + } + } + } + + /// Internal counter for the number of rounds. + /// + /// This is useful for de-duplication of transactions submitted to the pool, and general + /// diagnostics of the pallet. + /// + /// This is merely incremented once per every time that an upstream `elect` is called. + #[pallet::storage] + #[pallet::getter(fn round)] + pub type Round = StorageValue<_, u32, ValueQuery>; + + /// Current phase. + #[pallet::storage] + #[pallet::getter(fn current_phase)] + pub type CurrentPhase = StorageValue<_, Phase>, ValueQuery>; + + /// Wrapper struct for working with snapshots. + /// + /// It manages the following storage items: + /// + /// - [`DesiredTargets`]: The number of targets that we wish to collect. + /// - [`PagedVoterSnapshot`]: Paginated map of voters. + /// - [`PagedVoterSnapshotHash`]: Hash of the aforementioned. + /// - [`PagedTargetSnapshot`]: Paginated map of targets. + /// - [`PagedTargetSnapshotHash`]: Hash of the aforementioned. + /// + /// ### Invariants + /// + /// The following invariants must be met at **all times** for this storage item to be "correct". + /// + /// - [`PagedVoterSnapshotHash`] must always contain the correct the same number of keys, and + /// the corresponding hash of the [`PagedVoterSnapshot`]. + /// - [`PagedTargetSnapshotHash`] must always contain the correct the same number of keys, and + /// the corresponding hash of the [`PagedTargetSnapshot`]. + /// + /// - If any page from the paged voters/targets exists, then the aforementioned (desired + /// targets) must also exist. + /// + /// The following invariants might need to hold based on the current phase. + /// + /// - If `Phase` IS `Snapshot(_)`, then partial voter/target pages must exist from `msp` to + /// `lsp` based on the inner value. + /// - If `Phase` IS `Off`, then, no snapshot must exist. + /// - In all other phases, the snapshot must FULLY exist. + pub(crate) struct Snapshot(sp_std::marker::PhantomData); + impl Snapshot { + // ----------- mutable methods + pub(crate) fn set_desired_targets(d: u32) { + DesiredTargets::::put(d); + } + + pub(crate) fn set_targets(targets: BoundedVec) { + let hash = Self::write_storage_with_pre_allocate( + &PagedTargetSnapshot::::hashed_key_for(Pallet::::lsp()), + targets, + ); + PagedTargetSnapshotHash::::insert(Pallet::::lsp(), hash); + } + + pub(crate) fn set_voters(page: PageIndex, voters: VoterPageOf) { + let hash = Self::write_storage_with_pre_allocate( + &PagedVoterSnapshot::::hashed_key_for(page), + voters, + ); + PagedVoterSnapshotHash::::insert(page, hash); + } + + /// Destroy the entire snapshot. + /// + /// Should be called only once we transition to [`Phase::Off`]. + pub(crate) fn kill() { + DesiredTargets::::kill(); + PagedVoterSnapshot::::remove_all(None); + PagedVoterSnapshotHash::::remove_all(None); + PagedTargetSnapshot::::remove_all(None); + PagedTargetSnapshotHash::::remove_all(None); + } + + // ----------- non-mutables + pub(crate) fn desired_targets() -> Option { + DesiredTargets::::get() + } + + pub(crate) fn voters(page: PageIndex) -> Option> { + PagedVoterSnapshot::::get(page) + } + + pub(crate) fn voters_hash(page: PageIndex) -> Option { + PagedVoterSnapshotHash::::get(page) + } + + pub(crate) fn voters_decode_len(page: PageIndex) -> Option { + PagedVoterSnapshot::::decode_len(page) + } + + pub(crate) fn targets_decode_len() -> Option { + PagedVoterSnapshot::::decode_len(Pallet::::msp()) + } + + pub(crate) fn targets() -> Option> { + // NOTE: targets always have one index, which is 0, aka lsp. + PagedTargetSnapshot::::get(Pallet::::lsp()) + } + + pub(crate) fn targets_hash() -> Option { + PagedTargetSnapshotHash::::get(Pallet::::lsp()) + } + + /// Get a fingerprint of the snapshot, from all the hashes that are stored for each page of + /// the snapshot. + /// + /// This is computed as: `(target_hash, voter_hash_n, voter_hash_(n-1), ..., voter_hash_0)` + /// where `n` is `T::Pages - 1`. In other words, it is the concatenated hash of targets, and + /// voters, from `msp` to `lsp`. + pub fn fingerprint() -> T::Hash { + let mut hashed_target_and_voters = + PagedTargetSnapshotHash::::get(Pallet::::lsp()) + .unwrap_or_default() + .as_ref() + .to_vec(); + let hashed_voters = (Pallet::::msp()..=Pallet::::lsp()) + .map(|i| PagedVoterSnapshotHash::::get(i).unwrap_or_default()) + .map(|hash| >::as_ref(&hash).to_owned()) + .flatten() + .collect::>(); + hashed_target_and_voters.extend(hashed_voters); + T::Hashing::hash(&hashed_target_and_voters) + } + + fn write_storage_with_pre_allocate(key: &[u8], data: E) -> T::Hash { + let size = data.encoded_size(); + let mut buffer = Vec::with_capacity(size); + data.encode_to(&mut buffer); + + let hash = T::Hashing::hash(&buffer); + + // do some checks. + debug_assert_eq!(buffer, data.encode()); + // buffer should have not re-allocated since. + debug_assert!(buffer.len() == size && size == buffer.capacity()); + sp_io::storage::set(key, &buffer); + + hash + } + + #[cfg(any(test, debug_assertions))] + pub(crate) fn ensure_snapshot( + exists: bool, + mut up_to_page: PageIndex, + ) -> Result<(), &'static str> { + up_to_page = up_to_page.min(T::Pages::get()); + // NOTE: if someday we split the snapshot taking of voters(msp) and targets into two + // different blocks, then this assertion becomes obsolete. + ensure!(up_to_page > 0, "can't check snapshot up to page 0"); + + // if any number of pages supposed to exist, these must also exist. + ensure!(exists ^ Self::desired_targets().is_none(), "desired target mismatch"); + ensure!(exists ^ Self::targets().is_none(), "targets mismatch"); + ensure!(exists ^ Self::targets_hash().is_none(), "targets hash mismatch"); + + // and the hash is correct. + if let Some(targets) = Self::targets() { + let hash = Self::targets_hash().expect("must exist; qed"); + ensure!(hash == T::Hashing::hash(&targets.encode()), "targets hash mismatch"); + } + + // ensure that pages that should exist, indeed to exist.. + let mut sum_existing_voters = 0; + for p in (crate::Pallet::::lsp()..=crate::Pallet::::msp()) + .rev() + .take(up_to_page as usize) + { + ensure!( + (exists ^ Self::voters(p).is_none()) && + (exists ^ Self::voters_hash(p).is_none()), + "voter page existence mismatch" + ); + + if let Some(voters_page) = Self::voters(p) { + sum_existing_voters = sum_existing_voters.saturating_add(voters_page.len()); + let hash = Self::voters_hash(p).expect("must exist; qed"); + ensure!(hash == T::Hashing::hash(&voters_page.encode()), "voter hash mismatch"); + } + } + + // ..and those that should not exist, indeed DON'T. + for p in (crate::Pallet::::lsp()..=crate::Pallet::::msp()) + .take((T::Pages::get() - up_to_page) as usize) + { + ensure!( + (exists ^ Self::voters(p).is_some()) && + (exists ^ Self::voters_hash(p).is_some()), + "voter page non-existence mismatch" + ); + } + + Ok(()) + } + + #[cfg(any(test, debug_assertions))] + pub(crate) fn sanity_check() -> Result<(), &'static str> { + // check the snapshot existence based on the phase. This checks all of the needed + // conditions except for the metadata values. + let _ = match Pallet::::current_phase() { + // no page should exist in this phase. + Phase::Off => Self::ensure_snapshot(false, T::Pages::get()), + // exact number of pages must exist in this phase. + Phase::Snapshot(p) => Self::ensure_snapshot(true, T::Pages::get() - p), + // full snapshot must exist in these phases. + Phase::Emergency | + Phase::Signed | + Phase::SignedValidation(_) | + Phase::Export | + Phase::Unsigned(_) => Self::ensure_snapshot(true, T::Pages::get()), + // cannot assume anything. We might halt at any point. + Phase::Halted => Ok(()), + }?; + + Ok(()) + } + } + + #[cfg(test)] + impl Snapshot { + pub(crate) fn voter_pages() -> PageIndex { + use sp_runtime::SaturatedConversion; + PagedVoterSnapshot::::iter().count().saturated_into::() + } + + pub(crate) fn target_pages() -> PageIndex { + use sp_runtime::SaturatedConversion; + PagedTargetSnapshot::::iter().count().saturated_into::() + } + + pub(crate) fn voters_iter_flattened() -> impl Iterator> { + let key_range = + (crate::Pallet::::lsp()..=crate::Pallet::::msp()).collect::>(); + key_range + .into_iter() + .map(|k| PagedVoterSnapshot::::get(k).unwrap_or_default()) + .flatten() + } + + pub(crate) fn remove_voter_page(page: PageIndex) { + PagedVoterSnapshot::::remove(page); + } + + pub(crate) fn kill_desired_targets() { + DesiredTargets::::kill(); + } + + pub(crate) fn remove_target_page(page: PageIndex) { + PagedTargetSnapshot::::remove(page); + } + + pub(crate) fn remove_target(at: usize) { + PagedTargetSnapshot::::mutate(crate::Pallet::::lsp(), |maybe_targets| { + if let Some(targets) = maybe_targets { + targets.remove(at); + // and update the hash. + PagedTargetSnapshotHash::::insert( + crate::Pallet::::lsp(), + T::Hashing::hash(&targets.encode()), + ) + } + }) + } + } + + /// Desired number of targets to elect for this round. + #[pallet::storage] + type DesiredTargets = StorageValue<_, u32>; + /// Paginated voter snapshot. At most [`T::Pages`] keys will exist. + #[pallet::storage] + type PagedVoterSnapshot = StorageMap<_, Twox64Concat, PageIndex, VoterPageOf>; + /// Same as [`PagedVoterSnapshot`], but it will store the hash of the snapshot. + /// + /// The hash is generated using [`frame_system::Config::Hashing`]. + #[pallet::storage] + type PagedVoterSnapshotHash = StorageMap<_, Twox64Concat, PageIndex, T::Hash>; + /// Paginated target snapshot. + /// + /// For the time being, since we assume one pages of targets, at most ONE key will exist. + #[pallet::storage] + type PagedTargetSnapshot = + StorageMap<_, Twox64Concat, PageIndex, BoundedVec>; + /// Same as [`PagedTargetSnapshot`], but it will store the hash of the snapshot. + /// + /// The hash is generated using [`frame_system::Config::Hashing`]. + #[pallet::storage] + type PagedTargetSnapshotHash = StorageMap<_, Twox64Concat, PageIndex, T::Hash>; + + #[pallet::pallet] + pub struct Pallet(PhantomData); +} + +impl Pallet { + /// Returns the most significant page of the snapshot. + /// + /// Based on the contract of `ElectionDataProvider`, this is the first page that is filled. + fn msp() -> PageIndex { + T::Pages::get().checked_sub(1).defensive_unwrap_or_default() + } + + /// Returns the least significant page of the snapshot. + /// + /// Based on the contract of `ElectionDataProvider`, this is the last page that is filled. + fn lsp() -> PageIndex { + Zero::zero() + } + + /// Perform all the basic checks that are independent of the snapshot. TO be more specific, + /// these are all the checks that you can do without the need to read the massive blob of the + /// actual snapshot. This function only contains a handful of storage reads, with bounded size. + /// + /// A sneaky detail is that this does check the `DesiredTargets` aspect of the snapshot, but + /// neither of the large storage items. + /// + /// Moreover, we do optionally check the fingerprint of the snapshot, if provided. + /// + /// These compliment a feasibility-check, which is exactly the opposite: snapshot-dependent + /// checks. + pub(crate) fn snapshot_independent_checks( + paged_solution: &PagedRawSolution, + maybe_snapshot_fingerprint: Option, + ) -> Result<(), Error> { + // Note that the order of these checks are critical for the correctness and performance of + // `restore_or_compute_then_maybe_submit`. We want to make sure that we always check round + // first, so that if it has a wrong round, we can detect and delete it from the cache right + // from the get go. + + // ensure round is current + ensure!(Self::round() == paged_solution.round, Error::::WrongRound); + + // ensure score is being improved, if the claim is even correct. + ensure!( + ::ensure_claimed_score_improves(paged_solution.score), + Error::::WeakSubmission, + ); + + // ensure solution pages are no more than the snapshot + ensure!( + paged_solution.solution_pages.len().saturated_into::() <= T::Pages::get(), + Error::::WrongPageCount + ); + + // finally, check the winner count being correct. + if let Some(desired_targets) = Snapshot::::desired_targets() { + ensure!( + desired_targets == paged_solution.winner_count_single_page_target_snapshot() as u32, + Error::::WrongWinnerCount + ) + } + + // check the snapshot fingerprint, if asked for. + ensure!( + maybe_snapshot_fingerprint + .map_or(true, |snapshot_fingerprint| Snapshot::::fingerprint() == + snapshot_fingerprint), + Error::::WrongFingerprint + ); + + Ok(()) + } + + /// Creates the target snapshot. Writes new data to: + /// + /// Returns `Ok(num_created)` if operation is okay. + pub fn create_targets_snapshot() -> Result> { + // if requested, get the targets as well. + Snapshot::::set_desired_targets( + T::DataProvider::desired_targets().map_err(ElectionError::DataProvider)?, + ); + + let limit = Some(T::TargetSnapshotPerBlock::get().saturated_into::()); + let targets: BoundedVec<_, T::TargetSnapshotPerBlock> = + T::DataProvider::electable_targets(limit, 0) + .and_then(|v| v.try_into().map_err(|_| "try-into failed")) + .map_err(ElectionError::DataProvider)?; + + let count = targets.len() as u32; + log!(debug, "created target snapshot with {} targets.", count); + Snapshot::::set_targets(targets); + + Ok(count) + } + + /// Creates the voter snapshot. Writes new data to: + /// + /// Returns `Ok(num_created)` if operation is okay. + pub fn create_voters_snapshot_paged(remaining: PageIndex) -> Result> { + let limit = Some(T::VoterSnapshotPerBlock::get().saturated_into::()); + let voters: BoundedVec<_, T::VoterSnapshotPerBlock> = + T::DataProvider::electing_voters(limit, remaining) + .and_then(|v| v.try_into().map_err(|_| "try-into failed")) + .map_err(ElectionError::DataProvider)?; + + let count = voters.len() as u32; + Snapshot::::set_voters(remaining, voters); + log!(debug, "created voter snapshot with {} voters, {} remaining.", count, remaining); + + Ok(count) + } + + /// Perform the tasks to be done after a new `elect` has been triggered: + /// + /// 1. Increment round. + /// 2. Change phase to [`Phase::Off`] + /// 3. Clear all snapshot data. + fn rotate_round() { + // Inc round. + >::mutate(|r| *r += 1); + + // Phase is off now. + >::put(Phase::Off); + + // Kill everything in the verifier. + T::Verifier::kill(); + + // Kill the snapshot. + Snapshot::::kill(); + } + + #[cfg(any(test, debug_assertions))] + pub(crate) fn sanity_check() -> Result<(), &'static str> { + Snapshot::::sanity_check() + } +} + +impl ElectionProvider for Pallet +where + T::Fallback: ElectionProvider< + AccountId = T::AccountId, + BlockNumber = BlockNumberFor, + MaxBackersPerWinner = ::MaxBackersPerWinner, + MaxWinnersPerPage = ::MaxWinnersPerPage, + >, +{ + type AccountId = T::AccountId; + type BlockNumber = BlockNumberFor; + type Error = ElectionError; + type DataProvider = T::DataProvider; + type Pages = T::Pages; + type MaxWinnersPerPage = ::MaxWinnersPerPage; + type MaxBackersPerWinner = ::MaxBackersPerWinner; + + fn elect(remaining: PageIndex) -> Result, Self::Error> { + T::Verifier::get_queued_solution_page(remaining) + .ok_or(ElectionError::SupportPageNotAvailable) + .or_else(|err| { + // if this is the last page, we might use the fallback to recover something. + log!(error, "primary election provider failed due to: {:?}, trying fallback", err); + if remaining.is_zero() { + T::Fallback::elect(0).map_err(|fe| ElectionError::::Fallback(fe)) + } else { + Err(err) + } + }) + .map(|supports| { + // if either of `Verifier` or `Fallback` was okay, and if this is the last page, + // then clear everything. + if remaining.is_zero() { + log!(info, "receiving last call to elect(0), rotating round"); + Self::rotate_round() + } else { + >::put(Phase::Export); + } + supports.into() + }) + .map_err(|err| { + // if any pages returns an error, we go into the emergency phase and don't do + // anything else anymore. This will prevent any new submissions to signed and + // unsigned pallet, and thus the verifier will also be almost stuck, except for the + // submission of emergency solutions. + log!(error, "fetching page {} failed. entering emergency mode.", remaining); + >::put(Phase::Emergency); + err + }) + } + + fn ongoing() -> bool { + match >::get() { + Phase::Off | Phase::Emergency | Phase::Halted => false, + Phase::Signed | + Phase::SignedValidation(_) | + Phase::Unsigned(_) | + Phase::Snapshot(_) | + Phase::Export => true, + } + } +} + +#[cfg(test)] +mod phase_rotation { + use super::{Event, *}; + use crate::{mock::*, Phase}; + use frame_election_provider_support::ElectionProvider; + use frame_support::traits::Hooks; + + #[test] + fn single_page() { + ExtBuilder::full().pages(1).onchain_fallback(true).build_and_execute(|| { + // 0 -------- 14 15 --------- 20 ------------- 25 ---------- 30 + // | | | | | + // Snapshot Signed SignedValidation Unsigned elect() + + assert_eq!(System::block_number(), 0); + assert_eq!(MultiBlock::current_phase(), Phase::Off); + assert_ok!(Snapshot::::ensure_snapshot(false, 1)); + assert_eq!(MultiBlock::round(), 0); + + roll_to(4); + assert_eq!(MultiBlock::current_phase(), Phase::Off); + assert_eq!(MultiBlock::round(), 0); + + roll_to(13); + assert_eq!(MultiBlock::current_phase(), Phase::Off); + + roll_to(14); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0)); + + roll_to(15); + assert_eq!(MultiBlock::current_phase(), Phase::Signed); + assert_eq!(multi_block_events(), vec![Event::SignedPhaseStarted(0)]); + assert_ok!(Snapshot::::ensure_snapshot(true, 1)); + assert_eq!(MultiBlock::round(), 0); + + roll_to(19); + assert_eq!(MultiBlock::current_phase(), Phase::Signed); + assert_ok!(Snapshot::::ensure_snapshot(true, 1)); + assert_eq!(MultiBlock::round(), 0); + + roll_to(20); + assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(20)); + assert_eq!( + multi_block_events(), + vec![Event::SignedPhaseStarted(0), Event::SignedValidationPhaseStarted(0)], + ); + assert_ok!(Snapshot::::ensure_snapshot(true, 1)); + + roll_to(24); + assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(20)); + assert_ok!(Snapshot::::ensure_snapshot(true, 1)); + assert_eq!(MultiBlock::round(), 0); + + roll_to(25); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + assert_eq!( + multi_block_events(), + vec![ + Event::SignedPhaseStarted(0), + Event::SignedValidationPhaseStarted(0), + Event::UnsignedPhaseStarted(0) + ], + ); + assert_ok!(Snapshot::::ensure_snapshot(true, 1)); + + roll_to(30); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + assert_ok!(Snapshot::::ensure_snapshot(true, 1)); + + // We close when upstream tells us to elect. + roll_to(32); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + assert_ok!(Snapshot::::ensure_snapshot(true, 1)); + + MultiBlock::elect(0).unwrap(); + + assert!(MultiBlock::current_phase().is_off()); + assert_ok!(Snapshot::::ensure_snapshot(false, 1)); + assert_eq!(MultiBlock::round(), 1); + + roll_to(43); + assert_eq!(MultiBlock::current_phase(), Phase::Off); + + roll_to(44); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0)); + + roll_to(45); + assert!(MultiBlock::current_phase().is_signed()); + + roll_to(50); + assert!(MultiBlock::current_phase().is_signed_validation_open_at(50)); + + roll_to(55); + assert!(MultiBlock::current_phase().is_unsigned_open_at(55)); + }) + } + + #[test] + fn multi_page_2() { + ExtBuilder::full().pages(2).onchain_fallback(true).build_and_execute(|| { + // 0 -------13 14 15 ------- 20 ---- 25 ------- 30 + // | | | | | + // Snapshot Signed SigValid Unsigned Elect + + assert_eq!(System::block_number(), 0); + assert_eq!(MultiBlock::current_phase(), Phase::Off); + assert_ok!(Snapshot::::ensure_snapshot(false, 2)); + assert_eq!(MultiBlock::round(), 0); + + roll_to(4); + assert_eq!(MultiBlock::current_phase(), Phase::Off); + assert_eq!(MultiBlock::round(), 0); + + roll_to(12); + assert_eq!(MultiBlock::current_phase(), Phase::Off); + + roll_to(13); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1)); + assert_ok!(Snapshot::::ensure_snapshot(true, 1)); + + roll_to(14); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0)); + assert_ok!(Snapshot::::ensure_snapshot(true, 2)); + + roll_to(15); + assert_eq!(MultiBlock::current_phase(), Phase::Signed); + assert_eq!(multi_block_events(), vec![Event::SignedPhaseStarted(0)]); + assert_ok!(Snapshot::::ensure_snapshot(true, 2)); + assert_eq!(MultiBlock::round(), 0); + + roll_to(19); + assert_eq!(MultiBlock::current_phase(), Phase::Signed); + assert_ok!(Snapshot::::ensure_snapshot(true, 2)); + assert_eq!(MultiBlock::round(), 0); + + roll_to(20); + assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(20)); + assert_eq!( + multi_block_events(), + vec![Event::SignedPhaseStarted(0), Event::SignedValidationPhaseStarted(0)], + ); + assert_ok!(Snapshot::::ensure_snapshot(true, 2)); + + roll_to(24); + assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(20)); + assert_ok!(Snapshot::::ensure_snapshot(true, 2)); + assert_eq!(MultiBlock::round(), 0); + + roll_to(25); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + assert_eq!( + multi_block_events(), + vec![ + Event::SignedPhaseStarted(0), + Event::SignedValidationPhaseStarted(0), + Event::UnsignedPhaseStarted(0) + ], + ); + assert_ok!(Snapshot::::ensure_snapshot(true, 2)); + + roll_to(29); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + assert_ok!(Snapshot::::ensure_snapshot(true, 2)); + + roll_to(30); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + assert_ok!(Snapshot::::ensure_snapshot(true, 2)); + + // We close when upstream tells us to elect. + roll_to(32); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + + MultiBlock::elect(0).unwrap(); // and even this one's coming from the fallback. + assert!(MultiBlock::current_phase().is_off()); + + // all snapshots are gone. + assert_ok!(Snapshot::::ensure_snapshot(false, 2)); + assert_eq!(MultiBlock::round(), 1); + + roll_to(42); + assert_eq!(MultiBlock::current_phase(), Phase::Off); + + roll_to(43); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1)); + + roll_to(44); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0)); + + roll_to(45); + assert!(MultiBlock::current_phase().is_signed()); + + roll_to(50); + assert!(MultiBlock::current_phase().is_signed_validation_open_at(50)); + + roll_to(55); + assert!(MultiBlock::current_phase().is_unsigned_open_at(55)); + }) + } + + #[test] + fn multi_page_3() { + ExtBuilder::full().pages(3).onchain_fallback(true).build_and_execute(|| { + // 0 ------- 12 13 14 15 ----------- 20 ---------25 ------- 30 + // | | | | | + // Snapshot Signed SignedValidation Unsigned Elect + + assert_eq!(System::block_number(), 0); + assert_eq!(MultiBlock::current_phase(), Phase::Off); + assert_ok!(Snapshot::::ensure_snapshot(false, 3)); + assert_eq!(MultiBlock::round(), 0); + + roll_to(4); + assert_eq!(MultiBlock::current_phase(), Phase::Off); + assert_eq!(MultiBlock::round(), 0); + + roll_to(11); + assert_eq!(MultiBlock::current_phase(), Phase::Off); + + roll_to(12); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(2)); + assert_ok!(Snapshot::::ensure_snapshot(true, 1)); + + roll_to(13); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1)); + assert_ok!(Snapshot::::ensure_snapshot(true, 2)); + + roll_to(14); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0)); + assert_ok!(Snapshot::::ensure_snapshot(true, 3)); + + roll_to(15); + assert_eq!(MultiBlock::current_phase(), Phase::Signed); + assert_eq!(multi_block_events(), vec![Event::SignedPhaseStarted(0)]); + assert_eq!(MultiBlock::round(), 0); + + roll_to(19); + assert_eq!(MultiBlock::current_phase(), Phase::Signed); + assert_eq!(MultiBlock::round(), 0); + + roll_to(20); + assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(20)); + assert_eq!( + multi_block_events(), + vec![Event::SignedPhaseStarted(0), Event::SignedValidationPhaseStarted(0)], + ); + + roll_to(24); + assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(20)); + assert_eq!(MultiBlock::round(), 0); + + roll_to(25); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + assert_eq!( + multi_block_events(), + vec![ + Event::SignedPhaseStarted(0), + Event::SignedValidationPhaseStarted(0), + Event::UnsignedPhaseStarted(0) + ], + ); + + roll_to(29); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + + roll_to(30); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + + // We close when upstream tells us to elect. + roll_to(32); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + + MultiBlock::elect(0).unwrap(); + assert!(MultiBlock::current_phase().is_off()); + + // all snapshots are gone. + assert_none_snapshot(); + assert_eq!(MultiBlock::round(), 1); + + roll_to(41); + assert_eq!(MultiBlock::current_phase(), Phase::Off); + + roll_to(42); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(2)); + + roll_to(43); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1)); + + roll_to(44); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0)); + + roll_to(45); + assert!(MultiBlock::current_phase().is_signed()); + + roll_to(50); + assert!(MultiBlock::current_phase().is_signed_validation_open_at(50)); + + roll_to(55); + assert!(MultiBlock::current_phase().is_unsigned_open_at(55)); + }) + } + + #[test] + fn multi_with_lookahead() { + ExtBuilder::full() + .pages(3) + .lookahead(2) + .onchain_fallback(true) + .build_and_execute(|| { + // 0 ------- 10 11 12 13 ----------- 17 ---------22 ------- 27 + // | | | | | + // Snapshot Signed SignedValidation Unsigned Elect + + assert_eq!(System::block_number(), 0); + assert_eq!(MultiBlock::current_phase(), Phase::Off); + assert_none_snapshot(); + assert_eq!(MultiBlock::round(), 0); + + roll_to(4); + assert_eq!(MultiBlock::current_phase(), Phase::Off); + assert_eq!(MultiBlock::round(), 0); + + roll_to(9); + assert_eq!(MultiBlock::current_phase(), Phase::Off); + + roll_to(10); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(2)); + assert_ok!(Snapshot::::ensure_snapshot(true, 1)); + + roll_to(11); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1)); + assert_ok!(Snapshot::::ensure_snapshot(true, 2)); + + roll_to(12); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0)); + assert_ok!(Snapshot::::ensure_snapshot(true, 3)); + + roll_to(13); + assert_eq!(MultiBlock::current_phase(), Phase::Signed); + assert_eq!(multi_block_events(), vec![Event::SignedPhaseStarted(0)]); + assert_eq!(MultiBlock::round(), 0); + + roll_to(17); + assert_eq!(MultiBlock::current_phase(), Phase::Signed); + assert_full_snapshot(); + assert_eq!(MultiBlock::round(), 0); + + roll_to(18); + assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(18)); + assert_eq!( + multi_block_events(), + vec![Event::SignedPhaseStarted(0), Event::SignedValidationPhaseStarted(0)], + ); + + roll_to(22); + assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(18)); + assert_full_snapshot(); + assert_eq!(MultiBlock::round(), 0); + + roll_to(23); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(23)); + assert_eq!( + multi_block_events(), + vec![ + Event::SignedPhaseStarted(0), + Event::SignedValidationPhaseStarted(0), + Event::UnsignedPhaseStarted(0) + ], + ); + + roll_to(27); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(23)); + + roll_to(28); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(23)); + + // We close when upstream tells us to elect. + roll_to(30); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(23)); + + MultiBlock::elect(0).unwrap(); + assert!(MultiBlock::current_phase().is_off()); + + // all snapshots are gone. + assert_ok!(Snapshot::::ensure_snapshot(false, 3)); + assert_eq!(MultiBlock::round(), 1); + + roll_to(41 - 2); + assert_eq!(MultiBlock::current_phase(), Phase::Off); + + roll_to(42 - 2); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(2)); + + roll_to(43 - 2); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1)); + + roll_to(44 - 2); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0)); + + roll_to(45 - 2); + assert!(MultiBlock::current_phase().is_signed()); + + roll_to(50 - 2); + assert!(MultiBlock::current_phase().is_signed_validation_open_at(50 - 2)); + + roll_to(55 - 2); + assert!(MultiBlock::current_phase().is_unsigned_open_at(55 - 2)); + }) + } + + #[test] + fn no_unsigned_phase() { + ExtBuilder::full() + .pages(3) + .unsigned_phase(0) + .onchain_fallback(true) + .build_and_execute(|| { + // 0 --------------------- 17 ------ 20 ---------25 ------- 30 + // | | | | | + // Snapshot Signed SignedValidation Elect + + assert_eq!(System::block_number(), 0); + assert_eq!(MultiBlock::current_phase(), Phase::Off); + assert_none_snapshot(); + assert_eq!(MultiBlock::round(), 0); + + roll_to(4); + assert_eq!(MultiBlock::current_phase(), Phase::Off); + assert_eq!(MultiBlock::round(), 0); + + roll_to(17); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(2)); + roll_to(18); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1)); + roll_to(19); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0)); + + assert_full_snapshot(); + assert_eq!(MultiBlock::round(), 0); + + roll_to(20); + assert_eq!(MultiBlock::current_phase(), Phase::Signed); + roll_to(25); + assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(25)); + + assert_eq!( + multi_block_events(), + vec![Event::SignedPhaseStarted(0), Event::SignedValidationPhaseStarted(0)], + ); + + // Signed validation can now be expanded until a call to `elect` comes + roll_to(27); + assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(25)); + roll_to(32); + assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(25)); + + MultiBlock::elect(0).unwrap(); + assert!(MultiBlock::current_phase().is_off()); + + // all snapshots are gone. + assert_none_snapshot(); + assert_eq!(MultiBlock::round(), 1); + assert_ok!(signed::Submissions::::ensure_killed(0)); + verifier::QueuedSolution::::assert_killed(); + }) + } + + #[test] + fn no_signed_phase() { + ExtBuilder::full() + .pages(3) + .signed_phase(0, 0) + .onchain_fallback(true) + .build_and_execute(|| { + // 0 ------------------------- 22 ------ 25 ------- 30 + // | | | + // Snapshot Unsigned Elect + + assert_eq!(System::block_number(), 0); + assert_eq!(MultiBlock::current_phase(), Phase::Off); + assert_none_snapshot(); + assert_eq!(MultiBlock::round(), 0); + + roll_to(20); + assert_eq!(MultiBlock::current_phase(), Phase::Off); + assert_eq!(MultiBlock::round(), 0); + + roll_to(22); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(2)); + roll_to(23); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1)); + roll_to(24); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0)); + + assert_full_snapshot(); + assert_eq!(MultiBlock::round(), 0); + + roll_to(25); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + assert_eq!(multi_block_events(), vec![Event::UnsignedPhaseStarted(0)],); + + // Unsigned can now be expanded until a call to `elect` comes + roll_to(27); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + roll_to(32); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + + MultiBlock::elect(0).unwrap(); + assert!(MultiBlock::current_phase().is_off()); + + // all snapshots are gone. + assert_none_snapshot(); + assert_eq!(MultiBlock::round(), 1); + assert_ok!(signed::Submissions::::ensure_killed(0)); + verifier::QueuedSolution::::assert_killed(); + }) + } + + #[test] + fn no_any_phase() { + todo!() + } + + #[test] + #[should_panic( + expected = "signed validation phase should be at least as long as the number of pages" + )] + fn incorrect_signed_validation_phase() { + ExtBuilder::full() + .pages(3) + .signed_validation_phase(2) + .build_and_execute(|| >::integrity_test()) + } +} + +#[cfg(test)] +mod election_provider { + use super::*; + use crate::{mock::*, unsigned::miner::BaseMiner, verifier::Verifier, Phase}; + use frame_election_provider_support::ElectionProvider; + use frame_support::{assert_storage_noop, unsigned::ValidateUnsigned}; + + // This is probably the most important test of all, a basic, correct scenario. This test should + // be studied in detail, and all of the branches of how it can go wrong or diverge from the + // basic scenario assessed. + #[test] + fn multi_page_elect_simple_works() { + ExtBuilder::full().build_and_execute(|| { + roll_to_signed_open(); + assert_eq!(MultiBlock::current_phase(), Phase::Signed); + + // load a solution into the verifier + let paged = BaseMiner::::mine_solution(Pages::get(), false).unwrap(); + let score = paged.score.clone(); + + // now let's submit this one by one, into the signed phase. + load_signed_for_verification(99, paged); + + // now the solution should start being verified. + roll_to_signed_validation_open(); + + assert_eq!( + multi_block_events(), + vec![ + crate::Event::SignedPhaseStarted(0), + crate::Event::SignedValidationPhaseStarted(0) + ] + ); + assert_eq!(verifier_events(), vec![]); + + // there is no queued solution prior to the last page of the solution getting verified + assert_eq!(::Verifier::queued_score(), None); + + // proceed until it is fully verified. + roll_next(); + assert_eq!(verifier_events(), vec![verifier::Event::Verified(2, 2)]); + + roll_next(); + assert_eq!( + verifier_events(), + vec![verifier::Event::Verified(2, 2), verifier::Event::Verified(1, 2)] + ); + + roll_next(); + assert_eq!( + verifier_events(), + vec![ + verifier::Event::Verified(2, 2), + verifier::Event::Verified(1, 2), + verifier::Event::Verified(0, 2), + verifier::Event::Queued(score, None), + ] + ); + + // there is now a queued solution. + assert_eq!(::Verifier::queued_score(), Some(score)); + + // now let's go to unsigned phase, but we don't expect anything to happen there since we + // don't run OCWs. + roll_to_unsigned_open(); + + // pre-elect state + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + assert_eq!(MultiBlock::round(), 0); + assert_full_snapshot(); + + // call elect for each page + let _paged_solution = (MultiBlock::lsp()..MultiBlock::msp()) + .rev() // 2, 1, 0 + .map(|page| { + MultiBlock::elect(page as PageIndex).unwrap(); + if page == 0 { + assert!(MultiBlock::current_phase().is_off()) + } else { + assert!(MultiBlock::current_phase().is_export()) + } + }) + .collect::>(); + + // after the last elect, verifier is cleared, + verifier::QueuedSolution::::assert_killed(); + // the phase is off, + assert_eq!(MultiBlock::current_phase(), Phase::Off); + // the round is incremented, + assert_eq!(Round::::get(), 1); + // and the snapshot is cleared, + assert_storage_noop!(Snapshot::::kill()); + // signed pallet is clean. + // NOTE: in the future, if and when we add lazy cleanup to the signed pallet, this + // assertion might break. + assert_ok!(signed::Submissions::::ensure_killed(0)); + }); + } + + #[test] + fn multi_page_elect_fast_track() { + ExtBuilder::full().build_and_execute(|| { + roll_to_signed_open(); + let round = MultiBlock::round(); + assert_eq!(MultiBlock::current_phase(), Phase::Signed); + + // load a solution into the verifier + let paged = BaseMiner::::mine_solution(Pages::get(), false).unwrap(); + let score = paged.score.clone(); + load_signed_for_verification_and_start(99, paged, 0); + + // there is no queued solution prior to the last page of the solution getting verified + assert_eq!(::Verifier::queued_score(), None); + + // roll to the block it is finalized + roll_next(); + roll_next(); + roll_next(); + assert_eq!( + verifier_events(), + vec![ + verifier::Event::Verified(2, 2), + verifier::Event::Verified(1, 2), + verifier::Event::Verified(0, 2), + verifier::Event::Queued(score, None), + ] + ); + + // there is now a queued solution. + assert_eq!(::Verifier::queued_score(), Some(score)); + + // not much impact, just for the sane-ness of the test. + roll_to_unsigned_open(); + + // pre-elect state: + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + assert_eq!(Round::::get(), 0); + assert_full_snapshot(); + + // there are 3 pages (indexes 2..=0), but we short circuit by just calling 0. + let _solution = crate::Pallet::::elect(0).unwrap(); + + // round is incremented. + assert_eq!(MultiBlock::round(), round + 1); + // after elect(0) is called, verifier is cleared, + verifier::QueuedSolution::::assert_killed(); + // the phase is off, + assert_eq!(MultiBlock::current_phase(), Phase::Off); + // the round is incremented, + assert_eq!(Round::::get(), 1); + // the snapshot is cleared, + assert_none_snapshot(); + // and signed pallet is clean. + assert_ok!(signed::Submissions::::ensure_killed(round)); + }); + } + + #[test] + fn elect_does_not_finish_without_call_of_page_0() { + ExtBuilder::full().build_and_execute(|| { + roll_to_signed_open(); + assert_eq!(MultiBlock::current_phase(), Phase::Signed); + + // load a solution into the verifier + let paged = BaseMiner::::mine_solution(Pages::get(), false).unwrap(); + let score = paged.score.clone(); + load_signed_for_verification_and_start(99, paged, 0); + + // there is no queued solution prior to the last page of the solution getting verified + assert_eq!(::Verifier::queued_score(), None); + + // roll to the block it is finalized + roll_next(); + roll_next(); + roll_next(); + assert_eq!( + verifier_events(), + vec![ + verifier::Event::Verified(2, 2), + verifier::Event::Verified(1, 2), + verifier::Event::Verified(0, 2), + verifier::Event::Queued(score, None), + ] + ); + + // there is now a queued solution + assert_eq!(::Verifier::queued_score(), Some(score)); + + // not much impact, just for the sane-ness of the test. + roll_to_unsigned_open(); + + // pre-elect state: + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + assert_eq!(Round::::get(), 0); + assert_full_snapshot(); + + // call elect for page 2 and 1, but NOT 0 + let solutions = (1..=MultiBlock::msp()) + .rev() // 2, 1 + .map(|page| { + crate::Pallet::::elect(page as PageIndex).unwrap(); + assert!(MultiBlock::current_phase().is_export()); + }) + .collect::>(); + assert_eq!(solutions.len(), 2); + + // nothing changes from the prelect state, except phase is now export. + assert!(MultiBlock::current_phase().is_export()); + assert_eq!(Round::::get(), 0); + assert_full_snapshot(); + }); + } + + #[test] + fn when_passive_stay_in_phase_unsigned() { + ExtBuilder::full().build_and_execute(|| { + // once the unsigned phase starts, it will not be changed by on_initialize (something + // like `elect` must be called). + roll_to_unsigned_open(); + for _ in 0..100 { + roll_next(); + assert!(matches!(MultiBlock::current_phase(), Phase::Unsigned(_))); + } + }); + } + + #[test] + fn skip_unsigned_phase() { + ExtBuilder::full().build_and_execute(|| { + roll_to_signed_open(); + assert_eq!(MultiBlock::current_phase(), Phase::Signed); + let round = MultiBlock::round(); + + // load a solution into the verifier + let paged = BaseMiner::::mine_solution(Pages::get(), false).unwrap(); + + load_signed_for_verification_and_start_and_roll_to_verified(99, paged, 0); + + // and right here, in the middle of the signed verification phase, we close the round. + // Everything should work fine. + assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(20)); + assert_eq!(Round::::get(), 0); + assert_full_snapshot(); + + // fetch all pages. + let _paged_solution = (MultiBlock::lsp()..MultiBlock::msp()) + .rev() // 2, 1, 0 + .map(|page| { + MultiBlock::elect(page as PageIndex).unwrap(); + if page == 0 { + assert!(MultiBlock::current_phase().is_off()) + } else { + assert!(MultiBlock::current_phase().is_export()) + } + }) + .collect::>(); + + // round is incremented. + assert_eq!(MultiBlock::round(), round + 1); + // after elect(0) is called, verifier is cleared, + verifier::QueuedSolution::::assert_killed(); + // the phase is off, + assert_eq!(MultiBlock::current_phase(), Phase::Off); + // the round is incremented, + assert_eq!(Round::::get(), 1); + // the snapshot is cleared, + assert_storage_noop!(Snapshot::::kill()); + // and signed pallet is clean. + assert_ok!(signed::Submissions::::ensure_killed(round)); + }); + } + + #[test] + fn call_to_elect_should_prevent_any_submission() { + ExtBuilder::full().build_and_execute(|| { + roll_to_signed_open(); + assert_eq!(MultiBlock::current_phase(), Phase::Signed); + + // load a solution into the verifier + let paged = BaseMiner::::mine_solution(Pages::get(), false).unwrap(); + load_signed_for_verification_and_start_and_roll_to_verified(99, paged, 0); + + assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(20)); + + // fetch one page. + assert!(MultiBlock::elect(MultiBlock::msp()).is_ok()); + + // try submit one signed page: + assert_noop!( + SignedPallet::submit_page(Origin::signed(999), 0, Default::default()), + "phase not signed" + ); + assert_noop!( + SignedPallet::register(Origin::signed(999), Default::default()), + "phase not signed" + ); + assert_storage_noop!(assert!(::pre_dispatch( + &unsigned::Call::submit_unsigned { paged_solution: Default::default() } + ) + .is_err())); + }); + } + + #[test] + fn multi_page_elect_fallback_works() { + todo!() + } +} + +mod admin_ops { + use super::*; + + #[test] + fn elect_call_on_off_or_halt_phase() { + todo!(); + } + + #[test] + fn force_clear() { + todo!("something very similar to the scenario of elect_does_not_finish_without_call_of_page_0, where we want to forcefully clear and put everything into halt phase") + } +} + +#[cfg(test)] +mod snapshot { + use super::*; + + #[test] + fn fetches_exact_voters() { + todo!("fetches correct number of voters, based on T::VoterSnapshotPerBlock"); + } + + #[test] + fn fetches_exact_targets() { + todo!("fetches correct number of targets, based on T::TargetSnapshotPerBlock"); + } + + #[test] + fn fingerprint_works() { + todo!("one hardcoded test of the fingerprint value."); + } + + #[test] + fn snapshot_size_2second_weight() { + todo!() + } +} diff --git a/substrate/frame/election-provider-multi-block/src/mock/mod.rs b/substrate/frame/election-provider-multi-block/src/mock/mod.rs new file mode 100644 index 0000000000000..68d6e5827fc1b --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/mock/mod.rs @@ -0,0 +1,657 @@ +// 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. + +mod signed; +mod staking; +mod weight_info; +use super::*; +use crate::{ + self as multi_block, + signed::{self as signed_pallet}, + unsigned::{ + self as unsigned_pallet, + miner::{BaseMiner, MinerError}, + }, + verifier::{self as verifier_pallet, AsynchronousVerifier, Status}, +}; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_election_provider_support::NposSolution; +pub use frame_support::{assert_noop, assert_ok}; +use frame_support::{ + derive_impl, + pallet_prelude::*, + parameter_types, + traits::Hooks, + weights::{constants, Weight}, +}; +use frame_system::{pallet_prelude::*, EnsureRoot}; +use parking_lot::RwLock; +pub use signed::*; +use sp_core::{ + offchain::{ + testing::{PoolState, TestOffchainExt, TestTransactionPoolExt}, + OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, + }, + H256, +}; +use sp_npos_elections::EvaluateSupport; +use sp_runtime::{ + bounded_vec, + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + PerU16, Perbill, +}; +pub use staking::*; +use std::{sync::Arc, vec}; + +pub type Block = sp_runtime::generic::Block; +pub type Extrinsic = sp_runtime::testing::TestXt; +pub type UncheckedExtrinsic = + sp_runtime::generic::UncheckedExtrinsic; + +pub type Balance = u64; +pub type AccountId = u64; +pub type BlockNumber = u64; +pub type VoterIndex = u32; +pub type TargetIndex = u16; + +frame_support::construct_runtime!( + pub enum Runtime { + System: frame_system, + Balances: pallet_balances, + MultiBlock: multi_block, + SignedPallet: signed_pallet, + VerifierPallet: verifier_pallet, + UnsignedPallet: unsigned_pallet, + } +); + +frame_election_provider_support::generate_solution_type!( + pub struct TestNposSolution::< + VoterIndex = VoterIndex, + TargetIndex = TargetIndex, + Accuracy = PerU16, + MaxVoters = ConstU32::<2_000> + >(16) +); + +impl codec::MaxEncodedLen for TestNposSolution { + fn max_encoded_len() -> usize { + // TODO: https://github.com/paritytech/substrate/issues/10866 + unimplemented!(); + } +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Runtime { + type SS58Prefix = (); + type BaseCallFilter = frame_support::traits::Everything; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type BlockHashCount = (); + type DbWeight = (); + type BlockLength = (); + type BlockWeights = BlockWeights; + type AccountData = pallet_balances::AccountData; + type MaxConsumers = ConstU32<16>; +} + +const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights + ::with_sensible_defaults( + Weight::from_parts(2u64 * constants::WEIGHT_REF_TIME_PER_SECOND, u64::MAX), + NORMAL_DISPATCH_RATIO, + ); +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type WeightInfo = (); +} + +parameter_types! { + pub static Pages: PageIndex = 3; + pub static UnsignedPhase: BlockNumber = 5; + pub static SignedPhase: BlockNumber = 5; + pub static SignedValidationPhase: BlockNumber = 5; + + pub static OnChianFallback: bool = false; + pub static MinerTxPriority: u64 = 100; + pub static SolutionImprovementThreshold: Perbill = Perbill::zero(); + pub static OffchainRepeat: BlockNumber = 5; + pub static MinerMaxWeight: Weight = BlockWeights::get().max_block; + pub static MinerMaxLength: u32 = 256; + pub static MaxVotesPerVoter: u32 = ::LIMIT as u32; + + // by default we stick to 3 pages to host our 12 voters. + pub static VoterSnapshotPerBlock: VoterIndex = 4; + pub static TargetSnapshotPerBlock: TargetIndex = 8; + pub static Lookahead: BlockNumber = 0; + + // we have 12 voters in the default setting, this should be enough to make sure they are not + // trimmed accidentally in any test. + #[derive(Encode, Decode, PartialEq, Eq, Debug, scale_info::TypeInfo, MaxEncodedLen)] // TODO: should be removed + pub static MaxBackersPerWinner: u32 = 12; + // we have 4 targets in total and we desire `Desired` thereof, no single page can represent more + // than the min of these two. + #[derive(Encode, Decode, PartialEq, Eq, Debug, scale_info::TypeInfo, MaxEncodedLen)] + pub static MaxWinnersPerPage: u32 = (staking::Targets::get().len() as u32).min(staking::DesiredTargets::get()); +} + +impl crate::verifier::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type SolutionImprovementThreshold = SolutionImprovementThreshold; + type ForceOrigin = frame_system::EnsureRoot; + type MaxBackersPerWinner = MaxBackersPerWinner; + type MaxWinnersPerPage = MaxWinnersPerPage; + type SolutionDataProvider = signed::DualSignedPhase; + type WeightInfo = (); +} + +pub struct MockUnsignedWeightInfo; +impl crate::unsigned::WeightInfo for MockUnsignedWeightInfo { + fn submit_unsigned(_v: u32, _t: u32, a: u32, _d: u32) -> Weight { + a as Weight + } +} + +impl crate::unsigned::Config for Runtime { + type OffchainRepeat = OffchainRepeat; + type MinerMaxWeight = MinerMaxWeight; + type MinerMaxLength = MinerMaxLength; + type MinerTxPriority = MinerTxPriority; + type OffchainSolver = + frame_election_provider_support::SequentialPhragmen; + type WeightInfo = MockUnsignedWeightInfo; +} + +impl crate::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type SignedPhase = SignedPhase; + type SignedValidationPhase = SignedValidationPhase; + type UnsignedPhase = UnsignedPhase; + type DataProvider = staking::MockStaking; + type Fallback = MockFallback; + type TargetSnapshotPerBlock = TargetSnapshotPerBlock; + type VoterSnapshotPerBlock = VoterSnapshotPerBlock; + type Lookahead = Lookahead; + type Solution = TestNposSolution; + type WeightInfo = weight_info::DualMockWeightInfo; + type Verifier = VerifierPallet; + type AdminOrigin = EnsureRoot; + type Pages = Pages; +} + +impl onchain::Config for Runtime { + type Accuracy = sp_runtime::Perbill; + type DataProvider = staking::MockStaking; + type TargetPageSize = (); + type VoterPageSize = (); + type MaxBackersPerWinner = MaxBackersPerWinner; + type MaxWinnersPerPage = MaxWinnersPerPage; +} + +pub struct MockFallback; +impl ElectionProvider for MockFallback { + type AccountId = AccountId; + type BlockNumber = u64; + type Error = &'static str; + type DataProvider = staking::MockStaking; + type Pages = ConstU32<1>; + type MaxBackersPerWinner = MaxBackersPerWinner; + type MaxWinnersPerPage = MaxWinnersPerPage; + + fn elect(remaining: PageIndex) -> Result, Self::Error> { + if OnChianFallback::get() { + onchain::OnChainSequentialPhragmen::::elect(remaining) + .map_err(|_| "OnChainSequentialPhragmen failed") + } else { + // NOTE: this pesky little trick here is to avoid a clash of type, since `Ok` of our + // election provider and our fallback is not the same + let err = InitiateEmergencyPhase::::elect(remaining).unwrap_err(); + Err(err) + } + } +} + +impl frame_system::offchain::CreateTransactionBase for Runtime +where + RuntimeCall: From, +{ + type RuntimeCall = RuntimeCall; + type Extrinsic = Extrinsic; +} + +impl frame_system::offchain::CreateInherent for Runtime +where + RuntimeCall: From, +{ + fn create_inherent(call: Self::RuntimeCall) -> Self::Extrinsic { + Extrinsic::new_bare(call) + } +} + +pub struct ExtBuilder {} + +impl ExtBuilder { + pub fn full() -> Self { + Self {} + } + + pub fn verifier() -> Self { + SignedPhase::set(0); + SignedValidationPhase::set(0); + signed::SignedPhaseSwitch::set(signed::SignedSwitch::Mock); + Self {} + } + + pub fn unsigned() -> Self { + SignedPhase::set(0); + SignedValidationPhase::set(0); + signed::SignedPhaseSwitch::set(signed::SignedSwitch::Mock); + Self {} + } + + pub fn signed() -> Self { + UnsignedPhase::set(0); + Self {} + } +} + +impl ExtBuilder { + pub(crate) fn max_backing_per_target(self, c: u32) -> Self { + MaxBackersPerWinner::set(c); + self + } + pub(crate) fn miner_tx_priority(self, p: u64) -> Self { + MinerTxPriority::set(p); + self + } + pub(crate) fn solution_improvement_threshold(self, p: Perbill) -> Self { + SolutionImprovementThreshold::set(p); + self + } + pub(crate) fn pages(self, pages: PageIndex) -> Self { + Pages::set(pages); + self + } + pub(crate) fn lookahead(self, lookahead: BlockNumber) -> Self { + Lookahead::set(lookahead); + self + } + pub(crate) fn voter_per_page(self, count: u32) -> Self { + VoterSnapshotPerBlock::set(count); + self + } + pub(crate) fn miner_weight(self, weight: Weight) -> Self { + MinerMaxWeight::set(weight); + self + } + pub(crate) fn miner_max_length(self, len: u32) -> Self { + MinerMaxLength::set(len); + self + } + pub(crate) fn desired_targets(self, t: u32) -> Self { + staking::DesiredTargets::set(t); + self + } + pub(crate) fn signed_phase(self, d: BlockNumber, v: BlockNumber) -> Self { + SignedPhase::set(d); + SignedValidationPhase::set(v); + self + } + pub(crate) fn unsigned_phase(self, d: BlockNumber) -> Self { + UnsignedPhase::set(d); + self + } + pub(crate) fn signed_validation_phase(self, d: BlockNumber) -> Self { + SignedValidationPhase::set(d); + self + } + pub(crate) fn add_voter(self, who: AccountId, stake: Balance, targets: Vec) -> Self { + staking::VOTERS.with(|v| v.borrow_mut().push((who, stake, targets.try_into().unwrap()))); + self + } + pub(crate) fn onchain_fallback(self, enable: bool) -> Self { + OnChianFallback::set(enable); + self + } + pub(crate) fn build_unchecked(self) -> sp_io::TestExternalities { + sp_tracing::try_init_simple(); + let mut storage = + frame_system::GenesisConfig::default().build_storage::().unwrap(); + + let _ = pallet_balances::GenesisConfig:: { + balances: vec![ + // bunch of account for submitting stuff only. + (91, 100), + (92, 100), + (93, 100), + (94, 100), + (95, 100), + (96, 100), + (97, 100), + (99, 100), + (999, 100), + (9999, 100), + ], + } + .assimilate_storage(&mut storage); + + sp_io::TestExternalities::from(storage) + } + + /// Warning: this does not execute the post-sanity-checks. + pub(crate) fn build_offchainify(self) -> (sp_io::TestExternalities, Arc>) { + let mut ext = self.build_unchecked(); + let (offchain, _offchain_state) = TestOffchainExt::new(); + let (pool, pool_state) = TestTransactionPoolExt::new(); + + ext.register_extension(OffchainDbExt::new(offchain.clone())); + ext.register_extension(OffchainWorkerExt::new(offchain)); + ext.register_extension(TransactionPoolExt::new(pool)); + + (ext, pool_state) + } + + /// Build the externalities, and execute the given s`test` closure with it. + pub(crate) fn build_and_execute(self, test: impl FnOnce() -> ()) { + let mut ext = self.build_unchecked(); + ext.execute_with_sanity_checks(test); + } +} + +pub trait ExecuteWithSanityChecks { + fn execute_with_sanity_checks(&mut self, test: impl FnOnce() -> ()); +} + +impl ExecuteWithSanityChecks for sp_io::TestExternalities { + fn execute_with_sanity_checks(&mut self, test: impl FnOnce() -> ()) { + self.execute_with(test); + self.execute_with(all_pallets_sanity_checks) + } +} + +fn all_pallets_sanity_checks() { + let _ = VerifierPallet::sanity_check() + .and(UnsignedPallet::sanity_check()) + .and(MultiBlock::sanity_check()) + .and(SignedPallet::sanity_check()) + .unwrap(); +} + +/// Fully verify a solution. +/// +/// This will progress the blocks until the verifier pallet is done verifying it. +/// +/// The solution must have already been loaded via `load_and_start_verification`. +/// +/// Return the final supports, which is the outcome. If this succeeds, then the valid variant of the +/// `QueuedSolution` form `verifier` is ready to be read. +pub fn roll_to_full_verification() -> Vec> { + // we must be ready to verify. + assert_eq!(VerifierPallet::status(), Status::Ongoing(Pages::get() - 1)); + + while matches!(VerifierPallet::status(), Status::Ongoing(_)) { + roll_to(System::block_number() + 1); + } + + (MultiBlock::lsp()..=MultiBlock::msp()) + .map(|p| VerifierPallet::get_queued_solution_page(p).unwrap_or_default()) + .collect::>() +} + +/// Generate a single page of `TestNposSolution` from the give supports. +/// +/// All of the voters in this support must live in a single page of the snapshot, noted by +/// `snapshot_page`. +pub fn solution_from_supports( + supports: sp_npos_elections::Supports, + snapshot_page: PageIndex, +) -> TestNposSolution { + let staked = sp_npos_elections::supports_to_staked_assignment(supports); + let assignments = sp_npos_elections::assignment_staked_to_ratio_normalized(staked).unwrap(); + + let voters = crate::Snapshot::::voters(snapshot_page).unwrap(); + let targets = crate::Snapshot::::targets().unwrap(); + let voter_index = helpers::voter_index_fn_linear::(&voters); + let target_index = helpers::target_index_fn_linear::(&targets); + + TestNposSolution::from_assignment(&assignments, &voter_index, &target_index).unwrap() +} + +/// Generate a raw paged solution from the given vector of supports. +/// +/// Given vector must be aligned with the snapshot, at most need to be 'pagified' which we do +/// internally. +pub fn raw_paged_from_supports( + paged_supports: Vec>, + round: u32, +) -> PagedRawSolution { + let score = { + let flattened = paged_supports.iter().cloned().flatten().collect::>(); + flattened.evaluate() + }; + + let solution_pages = paged_supports + .pagify(Pages::get()) + .map(|(page_index, page_support)| solution_from_supports(page_support.to_vec(), page_index)) + .collect::>(); + + let solution_pages = solution_pages.try_into().unwrap(); + PagedRawSolution { solution_pages, score, round } +} + +/// ensure that the snapshot fully exists. +/// +/// NOTE: this should not be used that often, because we check snapshot in sanity checks, which are +/// called ALL THE TIME. +pub fn assert_full_snapshot() { + assert_ok!(Snapshot::::ensure_snapshot(true, Pages::get())); +} + +/// ensure that the no snapshot exists. +/// +/// NOTE: this should not be used that often, because we check snapshot in sanity checks, which are +/// called ALL THE TIME. +pub fn assert_none_snapshot() { + assert_ok!(Snapshot::::ensure_snapshot(false, Pages::get())); +} + +/// Simple wrapper for mining a new solution. Just more handy in case the interface of mine solution +/// changes. +/// +/// For testing, we never want to do reduce. +pub fn mine_full_solution() -> Result, MinerError> { + BaseMiner::::mine_solution(Pages::get(), false) +} + +/// Same as [`mine_full_solution`] but with custom pages. +pub fn mine_solution(pages: PageIndex) -> Result, MinerError> { + BaseMiner::::mine_solution(pages, false) +} + +/// Assert that `count` voters exist across `pages` number of pages. +pub fn ensure_voters(pages: PageIndex, count: usize) { + assert_eq!(crate::Snapshot::::voter_pages(), pages); + assert_eq!(crate::Snapshot::::voters_iter_flattened().count(), count); +} + +/// Assert that `count` targets exist across `pages` number of pages. +pub fn ensure_targets(pages: PageIndex, count: usize) { + assert_eq!(crate::Snapshot::::target_pages(), pages); + assert_eq!(crate::Snapshot::::targets().unwrap().len(), count); +} + +/// get the events of the multi-block pallet. +pub fn multi_block_events() -> Vec> { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let Event::MultiBlock(inner) = e { Some(inner) } else { None }) + .collect::>() +} + +/// get the events of the verifier pallet. +pub fn verifier_events() -> Vec> { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let Event::VerifierPallet(inner) = e { Some(inner) } else { None }) + .collect::>() +} + +/// proceed block number to `n`. +pub fn roll_to(n: BlockNumber) { + let now = System::block_number(); + for i in now + 1..=n { + System::set_block_number(i); + + MultiBlock::on_initialize(i); + VerifierPallet::on_initialize(i); + UnsignedPallet::on_initialize(i); + + if matches!(SignedPhaseSwitch::get(), SignedSwitch::Real) { + SignedPallet::on_initialize(i); + } + + // invariants must hold at the end of each block. + all_pallets_sanity_checks() + } +} + +/// proceed block number to whenever the snapshot is fully created (`Phase::Snapshot(0)`). +pub fn roll_to_snapshot_created() { + while !matches!(MultiBlock::current_phase(), Phase::Snapshot(0)) { + roll_next() + } + assert_full_snapshot(); +} + +/// proceed block number to whenever the unsigned phase is open (`Phase::Unsigned(_)`). +pub fn roll_to_unsigned_open() { + while !matches!(MultiBlock::current_phase(), Phase::Unsigned(_)) { + roll_next() + } +} + +/// proceed block number to whenever the signed phase is open (`Phase::Signed(_)`). +pub fn roll_to_signed_open() { + while !matches!(MultiBlock::current_phase(), Phase::Signed) { + roll_next(); + } +} + +/// proceed block number to whenever the signed validation phase is open +/// (`Phase::SignedValidation(_)`). +pub fn roll_to_signed_validation_open() { + while !matches!(MultiBlock::current_phase(), Phase::SignedValidation(_)) { + roll_next() + } +} + +/// Proceed one block. +pub fn roll_next() { + roll_to(System::block_number() + 1); +} + +/// Proceed one block, and execute offchain workers as well. +pub fn roll_next_with_ocw(maybe_pool: Option>>) { + roll_to_with_ocw(System::block_number() + 1, maybe_pool) +} + +/// proceed block number to `n`, while running all offchain workers as well. +pub fn roll_to_with_ocw(n: BlockNumber, maybe_pool: Option>>) { + use sp_runtime::traits::Dispatchable; + let now = System::block_number(); + for i in now + 1..=n { + // check the offchain transaction pool, and if anything's there, submit it. + if let Some(ref pool) = maybe_pool { + pool.read() + .transactions + .clone() + .into_iter() + .map(|uxt| ::decode(&mut &*uxt).unwrap()) + .for_each(|xt| { + xt.call.dispatch(frame_system::RawOrigin::None.into()).unwrap(); + }); + pool.try_write().unwrap().transactions.clear(); + } + + System::set_block_number(i); + + MultiBlock::on_initialize(i); + VerifierPallet::on_initialize(i); + UnsignedPallet::on_initialize(i); + if matches!(SignedPhaseSwitch::get(), SignedSwitch::Real) { + SignedPallet::on_initialize(i); + } + + MultiBlock::offchain_worker(i); + VerifierPallet::offchain_worker(i); + UnsignedPallet::offchain_worker(i); + if matches!(SignedPhaseSwitch::get(), SignedSwitch::Real) { + SignedPallet::offchain_worker(i); + } + + // invariants must hold at the end of each block. + all_pallets_sanity_checks() + } +} + +/// An invalid solution with any score. +pub fn fake_solution(score: ElectionScore) -> PagedRawSolution { + PagedRawSolution { + score, + solution_pages: bounded_vec![Default::default()], + ..Default::default() + } +} + +/// A real solution that's valid, but has a really bad score. +/// +/// This is different from `solution_from_supports` in that it does not require the snapshot to +/// exist. +// TODO: probably deprecate this. +pub fn raw_paged_solution_low_score() -> PagedRawSolution { + PagedRawSolution { + solution_pages: vec![TestNposSolution { + // 2 targets, both voting for themselves + votes1: vec![(0, 0), (1, 2)], + ..Default::default() + }] + .try_into() + .unwrap(), + round: 0, + score: ElectionScore { minimal_stake: 10, sum_stake: 20, sum_stake_squared: 200 }, + } +} + +/// Get the free and reserved balance of `who`. +pub fn balances(who: AccountId) -> (Balance, Balance) { + (Balances::free_balance(who), Balances::reserved_balance(who)) +} diff --git a/substrate/frame/election-provider-multi-block/src/mock/signed.rs b/substrate/frame/election-provider-multi-block/src/mock/signed.rs new file mode 100644 index 0000000000000..313ca13f698a8 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/mock/signed.rs @@ -0,0 +1,258 @@ +// 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 super::{Balance, Balances, Event, Pages, Runtime, SignedPallet, System}; +use crate::{ + mock::{ + balances, multi_block_events, roll_next, roll_to_signed_validation_open, verifier_events, + AccountId, Origin, VerifierPallet, + }, + signed::{self as signed_pallet, Event as SignedEvent, Submissions}, + verifier::{self, AsynchronousVerifier, SolutionDataProvider, VerificationResult, Verifier}, + PadSolutionPages, PagedRawSolution, Pagify, SolutionOf, +}; +use frame_election_provider_support::PageIndex; +use frame_support::{ + assert_ok, pallet_prelude::*, parameter_types, traits::EstimateCallFee, BoundedVec, +}; +use frame_system::pallet_prelude::*; +use sp_npos_elections::ElectionScore; +use sp_runtime::{traits::Zero, Perbill}; + +parameter_types! { + pub static MockSignedNextSolution: Option, Pages>> = None; + pub static MockSignedNextScore: Option = Default::default(); + pub static MockSignedResults: Vec = Default::default(); +} + +/// A simple implementation of the signed phase that can be controller by some static variables +/// directly. +/// +/// Useful for when you don't care too much about the signed phase. +pub struct MockSignedPhase; +impl SolutionDataProvider for MockSignedPhase { + type Solution = ::Solution; + fn get_page(page: PageIndex) -> Option { + MockSignedNextSolution::get().map(|i| i.get(page as usize).cloned().unwrap_or_default()) + } + + fn get_score() -> Option { + MockSignedNextScore::get() + } + + fn report_result(result: verifier::VerificationResult) { + MOCK_SIGNED_RESULTS.with(|r| r.borrow_mut().push(result)); + } +} + +pub struct FixedCallFee; +impl EstimateCallFee, Balance> for FixedCallFee { + fn estimate_call_fee( + _: &signed_pallet::Call, + _: frame_support::weights::PostDispatchInfo, + ) -> Balance { + 1 + } +} + +parameter_types! { + pub static SignedDepositBase: Balance = 5; + pub static SignedDepositPerPage: Balance = 1; + pub static SignedMaxSubmissions: u32 = 3; + pub static SignedRewardBase: Balance = 3; + pub static SignedPhaseSwitch: SignedSwitch = SignedSwitch::Real; + pub static BailoutGraceRatio: Perbill = Perbill::from_percent(20); +} + +impl crate::signed::Config for Runtime { + type Event = Event; + type Currency = Balances; + type DepositBase = SignedDepositBase; + type DepositPerPage = SignedDepositPerPage; + type EstimateCallFee = FixedCallFee; + type MaxSubmissions = SignedMaxSubmissions; + type RewardBase = SignedRewardBase; + type BailoutGraceRatio = BailoutGraceRatio; + type WeightInfo = (); +} + +/// Control which signed phase is being used. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum SignedSwitch { + Mock, + Real, +} + +pub struct DualSignedPhase; +impl SolutionDataProvider for DualSignedPhase { + type Solution = ::Solution; + fn get_page(page: PageIndex) -> Option { + match SignedPhaseSwitch::get() { + SignedSwitch::Mock => MockSignedNextSolution::get() + .map(|i| i.get(page as usize).cloned().unwrap_or_default()), + SignedSwitch::Real => SignedPallet::get_page(page), + } + } + + fn get_score() -> Option { + match SignedPhaseSwitch::get() { + SignedSwitch::Mock => MockSignedNextScore::get(), + SignedSwitch::Real => SignedPallet::get_score(), + } + } + + fn report_result(result: verifier::VerificationResult) { + match SignedPhaseSwitch::get() { + SignedSwitch::Mock => MOCK_SIGNED_RESULTS.with(|r| r.borrow_mut().push(result)), + SignedSwitch::Real => SignedPallet::report_result(result), + } + } +} + +/// get the events of the verifier pallet. +pub fn signed_events() -> Vec> { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let Event::SignedPallet(inner) = e { Some(inner) } else { None }) + .collect::>() +} + +/// Load a signed solution into its pallet. +pub fn load_signed_for_verification(who: AccountId, paged: PagedRawSolution) { + let initial_balance = Balances::free_balance(&who); + assert_eq!(balances(who), (initial_balance, 0)); + + assert_ok!(SignedPallet::register(Origin::signed(who), paged.score.clone())); + + assert_eq!( + balances(who), + (initial_balance - SignedDepositBase::get(), SignedDepositBase::get()) + ); + + for (page_index, solution_page) in paged.solution_pages.pagify(Pages::get()) { + assert_ok!(SignedPallet::submit_page( + Origin::signed(who), + page_index, + Some(solution_page.clone()) + )); + } + + let mut events = signed_events(); + for _ in 0..Pages::get() { + let event = events.pop().unwrap(); + assert!(matches!(event, SignedEvent::Stored(_, x, _) if x == who)) + } + assert!(matches!(events.pop().unwrap(), SignedEvent::Registered(_, x, _) if x == who)); + + let full_deposit = + SignedDepositBase::get() + (Pages::get() as Balance) * SignedDepositPerPage::get(); + assert_eq!(balances(who), (initial_balance - full_deposit, full_deposit)); +} + +/// Same as [`load_signed_for_verification`], but also goes forward to the beginning of the signed +/// verification phase. +pub fn load_signed_for_verification_and_start( + who: AccountId, + paged: PagedRawSolution, + round: u32, +) { + load_signed_for_verification(who, paged); + + // now the solution should start being verified. + roll_to_signed_validation_open(); + assert_eq!( + multi_block_events(), + vec![ + crate::Event::SignedPhaseStarted(round), + crate::Event::SignedValidationPhaseStarted(round) + ] + ); + assert_eq!(verifier_events(), vec![]); +} + +/// Same as [`load_signed_for_verification_and_start`], but also goes forward enough blocks for the +/// solution to be verified, assuming it is all correct. +/// +/// In other words, it goes [`Pages`] blocks forward. +pub fn load_signed_for_verification_and_start_and_roll_to_verified( + who: AccountId, + paged: PagedRawSolution, + round: u32, +) { + load_signed_for_verification(who, paged.clone()); + + // now the solution should start being verified. + roll_to_signed_validation_open(); + assert_eq!( + multi_block_events(), + vec![ + crate::Event::SignedPhaseStarted(round), + crate::Event::SignedValidationPhaseStarted(round) + ] + ); + assert_eq!(verifier_events(), vec![]); + + // there is no queued solution prior to the last page of the solution getting verified + assert_eq!(::Verifier::queued_score(), None); + + // roll to the block it is finalized. + for _ in 0..Pages::get() { + roll_next(); + } + + assert_eq!( + verifier_events(), + vec![ + // TODO: these are hardcoded for 3 page. + verifier::Event::Verified(2, 2), + verifier::Event::Verified(1, 2), + verifier::Event::Verified(0, 2), + verifier::Event::Queued(paged.score, None), + ] + ); + + // there is now a queued solution. + assert_eq!(::Verifier::queued_score(), Some(paged.score)); +} + +/// Load a full raw paged solution for verification. +/// +/// More or less the equivalent of `load_signed_for_verification_and_start`, but when +/// `SignedSwitch::Mock` is set. +pub fn load_mock_signed_and_start(raw_paged: PagedRawSolution) { + assert_eq!( + SignedPhaseSwitch::get(), + SignedSwitch::Mock, + "you should not use this if mock phase is not being mocked" + ); + MockSignedNextSolution::set(Some(raw_paged.solution_pages.pad_solution_pages(Pages::get()))); + MockSignedNextScore::set(Some(raw_paged.score)); + + // Let's gooooo! + assert_ok!(::start()); +} + +/// Ensure that no submission data exists in `round` for `who`. +pub fn assert_no_data_for(round: u32, who: AccountId) { + assert!(Submissions::::leaderboard(round) + .into_iter() + .find(|(x, _)| x == &who) + .is_none()); + assert!(Submissions::::metadata_of(round, who).is_none()); + assert!(Submissions::::pages_of(round, who).count().is_zero()); +} diff --git a/substrate/frame/election-provider-multi-block/src/mock/staking.rs b/substrate/frame/election-provider-multi-block/src/mock/staking.rs new file mode 100644 index 0000000000000..f73872c21df56 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/mock/staking.rs @@ -0,0 +1,230 @@ +// 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 super::{AccountId, MaxVotesPerVoter, Runtime}; +use crate::VoterOf; +use frame_election_provider_support::{data_provider, ElectionDataProvider, PageIndex, VoteWeight}; +use frame_support::{bounded_vec, pallet_prelude::*, BoundedVec}; +use frame_system::pallet_prelude::*; +use sp_std::prelude::*; + +frame_support::parameter_types! { + pub static Targets: Vec = vec![10, 20, 30, 40]; + pub static Voters: Vec> = vec![ + // page 2: + (1, 10, bounded_vec![10, 20]), + (2, 10, bounded_vec![30, 40]), + (3, 10, bounded_vec![40]), + (4, 10, bounded_vec![10, 20, 40]), + // page 1: + (5, 10, bounded_vec![10, 30, 40]), + (6, 10, bounded_vec![20, 30, 40]), + (7, 10, bounded_vec![20, 30]), + (8, 10, bounded_vec![10]), + // page 0: (self-votes) + (10, 10, bounded_vec![10]), + (20, 20, bounded_vec![20]), + (30, 30, bounded_vec![30]), + (40, 40, bounded_vec![40]), + ]; + pub static DesiredTargets: u32 = 2; + pub static EpochLength: u64 = 30; + + pub static LastIteratedVoterIndex: Option = None; +} + +pub struct MockStaking; +impl ElectionDataProvider for MockStaking { + type AccountId = AccountId; + type BlockNumber = u64; + type MaxVotesPerVoter = MaxVotesPerVoter; + + fn electable_targets( + maybe_max_len: Option, + remaining: PageIndex, + ) -> data_provider::Result> { + let targets = Targets::get(); + + if remaining != 0 { + return Err("targets shall not have more than a single page") + } + if maybe_max_len.map_or(false, |max_len| targets.len() > max_len) { + return Err("Targets too big") + } + + Ok(targets) + } + + fn electing_voters( + maybe_max_len: Option, + remaining: PageIndex, + ) -> data_provider::Result< + Vec<(AccountId, VoteWeight, BoundedVec)>, + > { + let mut voters = Voters::get(); + + // jump to the first non-iterated, if this is a follow up. + if let Some(index) = LastIteratedVoterIndex::get() { + voters = voters.iter().skip(index).cloned().collect::>(); + } + + // take as many as you can. + if let Some(max_len) = maybe_max_len { + voters.truncate(max_len) + } + + if voters.is_empty() { + return Ok(vec![]) + } + + if remaining > 0 { + let last = voters.last().cloned().unwrap(); + LastIteratedVoterIndex::set(Some( + Voters::get().iter().position(|v| v == &last).map(|i| i + 1).unwrap(), + )); + } else { + LastIteratedVoterIndex::set(None) + } + + Ok(voters) + } + + fn desired_targets() -> data_provider::Result { + Ok(DesiredTargets::get()) + } + + fn next_election_prediction(now: u64) -> u64 { + now + EpochLength::get() - now % EpochLength::get() + } + + #[cfg(any(feature = "runtime-benchmarks", test))] + fn put_snapshot( + voters: Vec<(AccountId, VoteWeight, BoundedVec)>, + targets: Vec, + _target_stake: Option, + ) { + Targets::set(targets); + Voters::set(voters); + } + + #[cfg(any(feature = "runtime-benchmarks", test))] + fn clear() { + Targets::set(vec![]); + Voters::set(vec![]); + } + + #[cfg(any(feature = "runtime-benchmarks", test))] + fn add_voter( + voter: AccountId, + weight: VoteWeight, + targets: BoundedVec, + ) { + let mut current = Voters::get(); + current.push((voter, weight, targets)); + Voters::set(current); + } + + #[cfg(any(feature = "runtime-benchmarks", test))] + fn add_target(target: AccountId) { + use super::ExistentialDeposit; + + let mut current = Targets::get(); + current.push(target); + Targets::set(current); + + // to be on-par with staking, we add a self vote as well. the stake is really not that + // important. + let mut current = Voters::get(); + current.push((target, ExistentialDeposit::get() as u64, vec![target].try_into().unwrap())); + Voters::set(current); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::ExtBuilder; + + #[test] + fn targets() { + ExtBuilder::full().build_and_execute(|| { + assert_eq!(Targets::get().len(), 4); + + // any non-zero page is error + assert!(MockStaking::electable_targets(None, 1).is_err()); + assert!(MockStaking::electable_targets(None, 2).is_err()); + + // but 0 is fine. + assert_eq!(MockStaking::electable_targets(None, 0).unwrap().len(), 4); + + // fetch less targets is error. + assert!(MockStaking::electable_targets(Some(2), 0).is_err()); + + // more targets is fine. + assert!(MockStaking::electable_targets(Some(4), 0).is_ok()); + assert!(MockStaking::electable_targets(Some(5), 0).is_ok()); + }); + } + + #[test] + fn multi_page_votes() { + ExtBuilder::full().build_and_execute(|| { + assert_eq!(MockStaking::electing_voters(None, 0).unwrap().len(), 12); + assert!(LastIteratedVoterIndex::get().is_none()); + + assert_eq!( + MockStaking::electing_voters(Some(4), 0) + .unwrap() + .into_iter() + .map(|(x, _, _)| x) + .collect::>(), + vec![1, 2, 3, 4], + ); + assert!(LastIteratedVoterIndex::get().is_none()); + + assert_eq!( + MockStaking::electing_voters(Some(4), 2) + .unwrap() + .into_iter() + .map(|(x, _, _)| x) + .collect::>(), + vec![1, 2, 3, 4], + ); + assert_eq!(LastIteratedVoterIndex::get().unwrap(), 4); + + assert_eq!( + MockStaking::electing_voters(Some(4), 1) + .unwrap() + .into_iter() + .map(|(x, _, _)| x) + .collect::>(), + vec![5, 6, 7, 8], + ); + assert_eq!(LastIteratedVoterIndex::get().unwrap(), 8); + + assert_eq!( + MockStaking::electing_voters(Some(4), 0) + .unwrap() + .into_iter() + .map(|(x, _, _)| x) + .collect::>(), + vec![10, 20, 30, 40], + ); + assert!(LastIteratedVoterIndex::get().is_none()); + }) + } +} diff --git a/substrate/frame/election-provider-multi-block/src/mock/weight_info.rs b/substrate/frame/election-provider-multi-block/src/mock/weight_info.rs new file mode 100644 index 0000000000000..9bdcfc5d5ef4f --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/mock/weight_info.rs @@ -0,0 +1,104 @@ +// 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. + +// TODO: would love to ditch this, too big to handle here. + +use crate::{self as multi_block}; +use frame_support::dispatch::Weight; +use sp_runtime::traits::Zero; + +frame_support::parameter_types! { + pub static MockWeightInfo: bool = false; +} + +pub struct DualMockWeightInfo; +impl multi_block::weights::WeightInfo for DualMockWeightInfo { + fn on_initialize_nothing() -> Weight { + if MockWeightInfo::get() { + Zero::zero() + } else { + <() as multi_block::weights::WeightInfo>::on_initialize_nothing() + } + } + fn on_initialize_open_signed() -> Weight { + if MockWeightInfo::get() { + Zero::zero() + } else { + <() as multi_block::weights::WeightInfo>::on_initialize_open_signed() + } + } + fn on_initialize_open_unsigned_with_snapshot() -> Weight { + if MockWeightInfo::get() { + Zero::zero() + } else { + <() as multi_block::weights::WeightInfo>::on_initialize_open_unsigned_with_snapshot() + } + } + fn on_initialize_open_unsigned_without_snapshot() -> Weight { + if MockWeightInfo::get() { + Zero::zero() + } else { + <() as multi_block::weights::WeightInfo>::on_initialize_open_unsigned_without_snapshot() + } + } + fn finalize_signed_phase_accept_solution() -> Weight { + if MockWeightInfo::get() { + Zero::zero() + } else { + <() as multi_block::weights::WeightInfo>::finalize_signed_phase_accept_solution() + } + } + fn finalize_signed_phase_reject_solution() -> Weight { + if MockWeightInfo::get() { + Zero::zero() + } else { + <() as multi_block::weights::WeightInfo>::finalize_signed_phase_reject_solution() + } + } + fn submit(c: u32) -> Weight { + if MockWeightInfo::get() { + Zero::zero() + } else { + <() as multi_block::weights::WeightInfo>::submit(c) + } + } + fn elect_queued(v: u32, t: u32, a: u32, d: u32) -> Weight { + if MockWeightInfo::get() { + Zero::zero() + } else { + <() as multi_block::weights::WeightInfo>::elect_queued(v, t, a, d) + } + } + fn submit_unsigned(v: u32, t: u32, a: u32, d: u32) -> Weight { + if MockWeightInfo::get() { + // 10 base + // 5 per edge. + (10 as Weight).saturating_add((5 as Weight).saturating_mul(a as Weight)) + } else { + <() as multi_block::weights::WeightInfo>::submit_unsigned(v, t, a, d) + } + } + fn feasibility_check(v: u32, t: u32, a: u32, d: u32) -> Weight { + if MockWeightInfo::get() { + // 10 base + // 5 per edge. + (10 as Weight).saturating_add((5 as Weight).saturating_mul(a as Weight)) + } else { + <() as multi_block::weights::WeightInfo>::feasibility_check(v, t, a, d) + } + } +} diff --git a/substrate/frame/election-provider-multi-block/src/signed/benchmarking.rs b/substrate/frame/election-provider-multi-block/src/signed/benchmarking.rs new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/signed/benchmarking.rs @@ -0,0 +1 @@ + diff --git a/substrate/frame/election-provider-multi-block/src/signed/mod.rs b/substrate/frame/election-provider-multi-block/src/signed/mod.rs new file mode 100644 index 0000000000000..d13bded3c8357 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/signed/mod.rs @@ -0,0 +1,700 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 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 signed phase of the multi-block election system. +//! +//! Signed submissions work on the basis of keeping a queue of submissions from random signed +//! accounts, and sorting them based on the best claimed score to the worse. +//! +//! Once the time to evaluate the signed phase comes, the solutions are checked from best-to-worse +//! claim, and they end up in either of the 3 buckets: +//! +//! 1. If they are the first, correct solution (and consequently the best one, since we start +//! evaluating from the best claim), they are rewarded. +//! 2. Any solution after the first correct solution is refunded in an unbiased way. +//! 3. Any invalid solution that wasted valuable blockchain time gets slashed for their deposit. +//! +//! # Future Plans: +//! +//! **Lazy deletion**: +//! Overall, this pallet can avoid the need to delete any storage item, by: +//! 1. outsource the storage of solution data to some other pallet. +//! 2. keep it here, but make everything be also a map of the round number, so that we can keep old +//! storage, and it is ONLY EVER removed, when after that round number is over. This can happen +//! for more or less free by the submitter itself, and by anyone else as well, in which case they +//! get a share of the the sum deposit. The share increases as times goes on. +//! +//! **Metadata update**: imagine you mis-computed your score. + +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_election_provider_support::PageIndex; +use frame_support::{ + traits::{Currency, Defensive, ReservableCurrency}, + BoundedVec, RuntimeDebugNoBound, +}; +use scale_info::TypeInfo; +use sp_npos_elections::ElectionScore; +use sp_runtime::traits::{Saturating, Zero}; +use sp_std::prelude::*; + +/// Exports of this pallet +pub use pallet::*; + +use crate::verifier::{AsynchronousVerifier, SolutionDataProvider, Status, VerificationResult}; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + +#[cfg(test)] +mod tests; + +type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + +/// All of the (meta) data around a signed submission +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Default, RuntimeDebugNoBound)] +#[cfg_attr(test, derive(frame_support::PartialEqNoBound, frame_support::EqNoBound))] +#[codec(mel_bound(T: Config))] +#[scale_info(skip_type_params(T))] +pub struct SubmissionMetadata { + /// The amount of deposit that has been held in reserve. + deposit: BalanceOf, + /// The amount of transaction fee that this submission has cost for its submitter so far. + fee: BalanceOf, + /// The amount of rewards that we expect to give to this submission, if deemed worthy. + reward: BalanceOf, + /// The score that this submission is claiming to achieve. + claimed_score: ElectionScore, + /// A bounded-bit-vec of pages that have been submitted so far. + pages: BoundedVec, +} + +impl SolutionDataProvider for Pallet { + type Solution = T::Solution; + fn get_page(page: PageIndex) -> Option { + // note: a non-existing page will still be treated as merely an empty page. This could be + // re-considered. + let current_round = Self::current_round(); + Submissions::::leader(current_round).map(|(who, _score)| { + sublog!(info, "signed", "returning page {} of {:?}'s submission as leader.", page, who); + Submissions::::get_page_of(current_round, &who, page).unwrap_or_default() + }) + } + + fn get_score() -> Option { + Submissions::::leader(Self::current_round()).map(|(_who, score)| score) + } + + fn report_result(result: crate::verifier::VerificationResult) { + // assumption of the trait. + debug_assert!(matches!(::status(), Status::Nothing)); + let current_round = Self::current_round(); + + match result { + VerificationResult::Queued => { + // defensive: if there is a result to be reported, then we must have had some + // leader. + if let Some((winner, metadata)) = + Submissions::::take_leader_with_data(Self::current_round()).defensive() + { + // first, let's give them their reward. + let reward = metadata.reward.saturating_add(metadata.fee); + let imbalance = T::Currency::deposit_creating(&winner, reward); + Self::deposit_event(Event::::Rewarded( + current_round, + winner.clone(), + reward, + )); + + // then, unreserve their deposit + let _remaining = T::Currency::unreserve(&winner, metadata.deposit); + debug_assert!(_remaining.is_zero()); + + // note: we could wipe this data either over time, or via transactions. + while let Some((discarded, metadata)) = + Submissions::::take_leader_with_data(Self::current_round()) + { + let _remaining = T::Currency::unreserve(&discarded, metadata.deposit); + debug_assert!(_remaining.is_zero()); + Self::deposit_event(Event::::Discarded(current_round, discarded)); + } + + // everything should have been clean. + #[cfg(debug_assertions)] + assert!(Submissions::::ensure_killed(current_round).is_ok()); + } + }, + VerificationResult::Rejected => { + // defensive: if there is a result to be reported, then we must have had some + // leader. + if let Some((loser, metadata)) = + Submissions::::take_leader_with_data(Self::current_round()).defensive() + { + // first, let's slash their deposit. + let slash = metadata.deposit; + let (imbalance, _remainder) = T::Currency::slash_reserved(&loser, slash); + debug_assert!(_remainder.is_zero()); + Self::deposit_event(Event::::Slashed(current_round, loser.clone(), slash)); + + // inform the verifier that they can now try again, if we're still in the signed + // validation phase. + if crate::Pallet::::current_phase().is_signed_validation() && + Submissions::::has_leader(current_round) + { + // defensive: verifier just reported back a result, it must be in clear + // state. + let _ = ::start().defensive(); + } + } + }, + VerificationResult::DataUnavailable => { + unreachable!("TODO") + }, + } + } +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use crate::verifier::AsynchronousVerifier; + use frame_support::{ + dispatch::DispatchResultWithPostInfo, + pallet_prelude::{StorageDoubleMap, ValueQuery, *}, + traits::{Defensive, DefensiveSaturating, EstimateCallFee, TryCollect}, + transactional, Twox64Concat, + }; + use frame_system::{ensure_signed, pallet_prelude::*}; + use sp_runtime::{traits::Zero, DispatchError, Perbill}; + use sp_std::collections::btree_set::BTreeSet; + + pub trait WeightInfo {} + impl WeightInfo for () {} + + #[pallet::config] + #[pallet::disable_frame_system_supertrait_check] + pub trait Config: crate::Config { + /// The overarching event type. + type RuntimeEvent: From> + + IsType<::RuntimeEvent> + + TryInto>; + + type Currency: ReservableCurrency; + + type DepositBase: Get>; + type DepositPerPage: Get>; + + type RewardBase: Get>; + + type MaxSubmissions: Get; + type BailoutGraceRatio: Get; + + type EstimateCallFee: EstimateCallFee, BalanceOf>; + + type WeightInfo: WeightInfo; + } + + /// Wrapper type for signed submissions. + /// + /// It handles 3 storage items: + /// + /// 1. [`SortedScores`]: A flat vector of all submissions' `(submitter_id, claimed_score)`. + /// 2. [`SubmissionStorage`]: Paginated map of of all submissions, keyed by submitter and page. + /// 3. [`SubmissionMetadataStorage`]: Map from submitter to the metadata of their submission. + /// + /// All storage items in this group are mapped, and their first key is the `round` to which they + /// belong to. In essence, we are storing multiple versions of each group. + /// + /// ### Invariants: + /// + /// This storage group is sane, clean, and consistent if the following invariants are held: + /// + /// Among the submissions of each round: + /// - `SortedScores` should never contain duplicate account ids. + /// - For any account id in `SortedScores`, a corresponding value should exist in + /// `SubmissionMetadataStorage` under that account id's key. + /// - And the value of `metadata.score` must be equal to the score stored in + /// `SortedScores`. + /// - And visa versa: for any key existing in `SubmissionMetadataStorage`, an item must exist in + /// `SortedScores`. TODO: + /// - For any first key existing in `SubmissionStorage`, a key must exist in + /// `SubmissionMetadataStorage`. + /// - For any first key in `SubmissionStorage`, the number of second keys existing should be the + /// same as the `true` count of `pages` in [`SubmissionMetadata`] (this already implies the + /// former, since it uses the metadata). + /// + /// All mutating functions are only allowed to transition into states where all of the above + /// conditions are met. + /// + /// No particular invariant exists between data that related to different rounds. They are + /// purely independent. + pub(crate) struct Submissions(sp_std::marker::PhantomData); + + #[pallet::storage] + type SortedScores = StorageMap< + _, + Twox64Concat, + u32, + BoundedVec<(T::AccountId, ElectionScore), T::MaxSubmissions>, + ValueQuery, + >; + + /// Triple map from (round, account, page) to a solution page. + #[pallet::storage] + type SubmissionStorage = StorageNMap< + _, + ( + NMapKey, + NMapKey, + NMapKey, + ), + T::Solution, + OptionQuery, + >; + + /// Map from account to the metadata of their submission. + /// + /// invariant: for any Key1 of type `AccountId` in [`Submissions`], this storage map also has a + /// value. + #[pallet::storage] + type SubmissionMetadataStorage = + StorageDoubleMap<_, Twox64Concat, u32, Twox64Concat, T::AccountId, SubmissionMetadata>; + + impl Submissions { + // -- mutating functions + + /// Generic checked mutation helper. + /// + /// All mutating functions must be fulled through this bad boy. The round at which the + /// mutation happens must be provided + fn mutate_checked R>(round: u32, mutate: F) -> R { + let result = mutate(); + + #[cfg(debug_assertions)] + { + assert!(Self::sanity_check_round(round).is_ok()); + assert!(Self::sanity_check_round(round + 1).is_ok()); + assert!(Self::sanity_check_round(round.saturating_sub(1)).is_ok()); + } + + result + } + + /// *Fully* **TAKE** (i.e. get and remove) the leader from storage, with all of its + /// associated data. + /// + /// This removes all associated data of the leader from storage, discarding the submission + /// data and score, returning the rest. + pub(crate) fn take_leader_with_data( + round: u32, + ) -> Option<(T::AccountId, SubmissionMetadata)> { + Self::mutate_checked(round, || { + SortedScores::::mutate(round, |sorted| sorted.pop()).and_then( + |(submitter, _score)| { + SubmissionStorage::::remove_prefix((round, &submitter), None); + SubmissionMetadataStorage::::take(round, &submitter) + .map(|metadata| (submitter, metadata)) + }, + ) + }) + } + + /// *Fully* **TAKE** (i.e. get and remove) a submission from storage, with all of its + /// associated data. + /// + /// This removes all associated data of the submitter from storage, discarding the + /// submission data and score, returning the metadata. + pub(crate) fn take_submission_with_data( + round: u32, + who: &T::AccountId, + ) -> Option> { + Self::mutate_checked(round, || { + SortedScores::::mutate(round, |sorted_scores| { + if let Some(index) = sorted_scores.iter().position(|(x, _)| x == who) { + sorted_scores.remove(index); + } + }); + SubmissionStorage::::remove_prefix((round, who), None); + SubmissionMetadataStorage::::take(round, who) + }) + } + + /// Try and register a new solution. + /// + /// Registration can only happen for the current round. + /// + /// registration might fail if the queue is already full, and the solution is not good + /// enough to eject the weakest. + fn try_register( + round: u32, + who: &T::AccountId, + metadata: SubmissionMetadata, + ) -> DispatchResultWithPostInfo { + Self::mutate_checked(round, || Self::try_register_inner(round, who, metadata)) + } + + fn try_register_inner( + round: u32, + who: &T::AccountId, + metadata: SubmissionMetadata, + ) -> DispatchResultWithPostInfo { + let mut sorted_scores = SortedScores::::get(round); + + if let Some(_) = sorted_scores.iter().position(|(x, _)| x == who) { + return Err("Duplicate".into()) + } else { + // must be new. + debug_assert!(!SubmissionMetadataStorage::::contains_key(round, who)); + + let pos = match sorted_scores + .binary_search_by_key(&metadata.claimed_score, |(_, y)| *y) + { + // an equal score exists, unlikely, but could very well happen. We just put them + // next to each other. + Ok(pos) => pos, + // new score, should be inserted in this pos. + Err(pos) => pos, + }; + + let record = (who.clone(), metadata.claimed_score); + match sorted_scores.force_insert_keep_right(pos, record) { + Ok(None) => {}, + Ok(Some((discarded, _score))) => { + let metadata = SubmissionMetadataStorage::::take(round, &discarded); + SubmissionStorage::::remove_prefix((round, &discarded), None); + let _remaining = T::Currency::unreserve( + &discarded, + metadata.map(|m| m.deposit).defensive_unwrap_or_default(), + ); + debug_assert!(_remaining.is_zero()); + Pallet::::deposit_event(Event::::Discarded(round, discarded)); + }, + Err(_) => return Err("QueueFull".into()), + } + } + + SortedScores::::insert(round, sorted_scores); + SubmissionMetadataStorage::::insert(round, who, metadata); + Ok(().into()) + } + + /// Submit a page of `solution` to the `page` index of `who`'s submission. + /// + /// Updates the deposit in the metadata accordingly. + /// + /// - If `maybe_solution` is `None`, then the given page is deleted. + /// - `who` must have already registered their submission. + /// - If the page is duplicate, it will replaced. + pub(crate) fn try_mutate_page( + round: u32, + who: &T::AccountId, + page: PageIndex, + maybe_solution: Option, + ) -> DispatchResultWithPostInfo { + Self::mutate_checked(round, || { + Self::try_mutate_page_inner(round, who, page, maybe_solution) + }) + } + + fn try_mutate_page_inner( + round: u32, + who: &T::AccountId, + page: PageIndex, + maybe_solution: Option, + ) -> DispatchResultWithPostInfo { + let mut metadata = + SubmissionMetadataStorage::::get(round, who).ok_or("NotRegistered")?; + ensure!(page < T::Pages::get(), "BadPageIndex"); + + // defensive only: we resize `meta.pages` once to be `T::Pages` elements once, and never + // resize it again; `page` is checked here to be in bound; element must exist; qed. + if let Some(page_bit) = metadata.pages.get_mut(page as usize).defensive() { + *page_bit = maybe_solution.is_some(); + } + + // update deposit. + let new_pages: BalanceOf = + (metadata.pages.iter().filter(|x| **x).count() as u32).into(); + let new_deposit = T::DepositBase::get() + T::DepositPerPage::get() * new_pages; + let old_deposit = metadata.deposit; + if new_deposit > old_deposit { + let to_reserve = new_deposit - old_deposit; + T::Currency::reserve(who, to_reserve)?; + } else { + let to_unreserve = old_deposit - new_deposit; + let _ = T::Currency::unreserve(who, to_unreserve); + }; + metadata.deposit = new_deposit; + + SubmissionStorage::::mutate_exists((round, who, page), |maybe_old_solution| { + *maybe_old_solution = maybe_solution + }); + SubmissionMetadataStorage::::insert(round, who, metadata); + Ok(().into()) + } + + // -- getter functions + pub(crate) fn has_leader(round: u32) -> bool { + !SortedScores::::get(round).is_empty() + } + + pub(crate) fn leader(round: u32) -> Option<(T::AccountId, ElectionScore)> { + SortedScores::::get(round).last().cloned() + } + + pub(crate) fn sorted_submitters(round: u32) -> BoundedVec { + SortedScores::::get(round).into_iter().map(|(x, _)| x).try_collect().unwrap() + } + + pub(crate) fn get_page_of( + round: u32, + who: &T::AccountId, + page: PageIndex, + ) -> Option { + SubmissionStorage::::get((round, who, &page)) + } + } + + #[cfg(any(test, debug_assertions))] + impl Submissions { + pub fn submissions_iter( + round: u32, + ) -> impl Iterator { + SubmissionStorage::::iter_prefix((round,)).map(|((x, y), z)| (x, y, z)) + } + + pub fn metadata_iter( + round: u32, + ) -> impl Iterator)> { + SubmissionMetadataStorage::::iter_prefix(round) + } + + pub fn metadata_of(round: u32, who: T::AccountId) -> Option> { + SubmissionMetadataStorage::::get(round, who) + } + + pub fn pages_of( + round: u32, + who: T::AccountId, + ) -> impl Iterator { + SubmissionStorage::::iter_prefix((round, who)) + } + + pub fn leaderboard( + round: u32, + ) -> BoundedVec<(T::AccountId, ElectionScore), T::MaxSubmissions> { + SortedScores::::get(round) + } + + /// Ensure that all the storage items associated with the given round are in `killed` state, + /// meaning that in the expect state after an election is OVER. + pub(crate) fn ensure_killed(round: u32) -> Result<(), &'static str> { + ensure!(Self::metadata_iter(round).count() == 0, "metadata_iter not cleared."); + ensure!(Self::submissions_iter(round).count() == 0, "submissions_iter not cleared."); + ensure!(Self::sorted_submitters(round).len() == 0, "sorted_submitters not cleared."); + + Ok(()) + } + + /// Perform all the sanity checks of this storage item group at the given round. + pub(crate) fn sanity_check_round(round: u32) -> Result<(), &'static str> { + let sorted_scores = SortedScores::::get(round); + assert_eq!( + sorted_scores.clone().into_iter().map(|(x, _)| x).collect::>().len(), + sorted_scores.len() + ); + + let _ = SubmissionMetadataStorage::::iter_prefix(round) + .map(|(submitter, meta)| { + let mut matches = SortedScores::::get(round) + .into_iter() + .filter(|(who, _score)| who == &submitter) + .collect::>(); + + ensure!( + matches.len() == 1, + "item existing in metadata but missing in sorted list.", + ); + + let (_, score) = matches.pop().expect("checked; qed"); + ensure!(score == meta.claimed_score, "score mismatch"); + Ok(()) + }) + .collect::, &'static str>>()?; + + ensure!( + SubmissionStorage::::iter_key_prefix((round,)).map(|(k1, _k2)| k1).all( + |submitter| SubmissionMetadataStorage::::contains_key(round, submitter) + ), + "missing metadata of submitter" + ); + + for submitter in SubmissionStorage::::iter_key_prefix((round,)).map(|(k1, _k2)| k1) { + let pages_count = + SubmissionStorage::::iter_key_prefix((round, &submitter)).count(); + let metadata = SubmissionMetadataStorage::::get(round, submitter) + .expect("metadata checked to exist for all keys; qed"); + let assumed_pages_count = metadata.pages.iter().filter(|x| **x).count(); + ensure!(pages_count == assumed_pages_count, "wrong page count"); + } + + Ok(()) + } + } + + #[pallet::pallet] + pub struct Pallet(PhantomData); + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Upcoming submission has been registered for the given account, with the given score. + Registered(u32, T::AccountId, ElectionScore), + /// A page of solution solution with the given index has been stored for the given account. + Stored(u32, T::AccountId, PageIndex), + Rewarded(u32, T::AccountId, BalanceOf), + Slashed(u32, T::AccountId, BalanceOf), + Discarded(u32, T::AccountId), + Bailed(u32, T::AccountId), + } + + #[pallet::call] + impl Pallet { + /// Submit an upcoming solution for registration. + /// + /// - no updating + /// - kept based on sorted scores. + #[pallet::weight(0)] + #[pallet::call_index(0)] + pub fn register( + origin: OriginFor, + claimed_score: ElectionScore, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + ensure!(crate::Pallet::::current_phase().is_signed(), "phase not signed"); + + // note: we could already check if this is a duplicate here, but prefer keeping the code + // simple for now. + + let deposit = T::DepositBase::get(); + let reward = T::RewardBase::get(); + // TODO: we should also accumulate the fee for submit calls, maybe? + let fee = T::EstimateCallFee::estimate_call_fee( + &Call::register { claimed_score }, + None.into(), + ); + let mut pages = BoundedVec::<_, _>::with_bounded_capacity(T::Pages::get() as usize); + pages.bounded_resize(T::Pages::get() as usize, false); + + let new_metadata = SubmissionMetadata { claimed_score, deposit, reward, fee, pages }; + + T::Currency::reserve(&who, deposit).map_err(|_| "insufficient funds")?; + let round = Self::current_round(); + let _ = Submissions::::try_register(round, &who, new_metadata)?; + + Self::deposit_event(Event::::Registered(round, who, claimed_score)); + Ok(().into()) + } + + #[pallet::weight(0)] + #[pallet::call_index(1)] + pub fn submit_page( + origin: OriginFor, + page: PageIndex, + maybe_solution: Option, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + ensure!(crate::Pallet::::current_phase().is_signed(), "phase not signed"); + + let round = Self::current_round(); + Submissions::::try_mutate_page(round, &who, page, maybe_solution)?; + Self::deposit_event(Event::::Stored(round, who, page)); + + Ok(().into()) + } + + /// Retract a submission. + /// + /// This will fully remove the solution from storage. + #[pallet::weight(0)] + #[pallet::call_index(2)] + #[transactional] + pub fn bail(origin: OriginFor) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + ensure!(crate::Pallet::::current_phase().is_signed(), "phase not signed"); + let round = Self::current_round(); + let metadata = Submissions::::take_submission_with_data(round, &who) + .ok_or::("NoSubmission".into())?; + + let deposit = metadata.deposit; + let to_refund = T::BailoutGraceRatio::get() * deposit; + let to_slash = deposit.defensive_saturating_sub(to_refund); + + // TODO: a nice defensive op for this. + let _remainder = T::Currency::unreserve(&who, to_refund); + debug_assert!(_remainder.is_zero()); + let (imbalance, _remainder) = T::Currency::slash_reserved(&who, to_slash); + debug_assert!(_remainder.is_zero()); + + Self::deposit_event(Event::::Bailed(round, who)); + + Ok(None.into()) + } + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(now: BlockNumberFor) -> Weight { + // TODO: we could rely on an explicit notification system instead of this.. but anyways. + if crate::Pallet::::current_phase().is_signed_validation_open_at(now) { + let maybe_leader = Submissions::::leader(Self::current_round()); + sublog!( + info, + "signed", + "signed validation started, sending validation start signal? {:?}", + maybe_leader.is_some() + ); + // start an attempt to verify our best thing. + if maybe_leader.is_some() { + // defensive: signed phase has just began, verifier should be in a clear state + // and ready to accept a solution. + ::start().defensive(); + } + } + + if crate::Pallet::::current_phase().is_unsigned_open_at(now) { + // signed validation phase just ended, make sure you stop any ongoing operation. + sublog!(info, "signed", "signed validation ended, sending validation stop signal",); + ::stop(); + } + + // TODO: weight + Default::default() + } + } +} + +impl Pallet { + #[cfg(any(debug_assertions, test))] + pub(crate) fn sanity_check() -> Result<(), &'static str> { + Submissions::::sanity_check_round(Self::current_round()) + } + + fn current_round() -> u32 { + crate::Pallet::::round() + } +} diff --git a/substrate/frame/election-provider-multi-block/src/signed/tests.rs b/substrate/frame/election-provider-multi-block/src/signed/tests.rs new file mode 100644 index 0000000000000..cf8b4753a3b69 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/signed/tests.rs @@ -0,0 +1,379 @@ +use super::{Event as SignedEvent, *}; +use crate::mock::*; + +mod calls { + use frame_support::bounded_vec; + + use super::*; + + #[test] + fn register_metadata_works() { + ExtBuilder::signed().build_and_execute(|| { + roll_to_signed_open(); + assert_full_snapshot(); + + assert_eq!(balances(99), (100, 0)); + let score = ElectionScore { minimal_stake: 100, ..Default::default() }; + + assert_ok!(SignedPallet::register(Origin::signed(99), score)); + assert_eq!(balances(99), (95, 5)); + + assert_eq!(Submissions::::metadata_iter(1).count(), 0); + assert_eq!(Submissions::::metadata_iter(0).count(), 1); + assert_eq!( + Submissions::::metadata_of(0, 99).unwrap(), + SubmissionMetadata { + claimed_score: score, + deposit: 5, + fee: 1, + pages: bounded_vec![false, false, false], + reward: 3 + } + ); + assert_eq!( + *Submissions::::leaderboard(0), + vec![(99, ElectionScore { minimal_stake: 100, ..Default::default() })] + ); + assert!(matches!(signed_events().as_slice(), &[ + SignedEvent::Registered(_, x, _), + ] if x == 99)); + + // second ones submits + assert_eq!(balances(999), (100, 0)); + let score = ElectionScore { minimal_stake: 90, ..Default::default() }; + assert_ok!(SignedPallet::register(Origin::signed(999), score)); + assert_eq!(balances(999), (95, 5)); + assert_eq!( + Submissions::::metadata_of(0, 999).unwrap(), + SubmissionMetadata { + claimed_score: score, + deposit: 5, + fee: 1, + pages: bounded_vec![false, false, false], + reward: 3 + } + ); + assert!(matches!(signed_events().as_slice(), &[ + SignedEvent::Registered(..), + SignedEvent::Registered(_, x, _), + ] if x == 999)); + + assert_eq!( + *Submissions::::leaderboard(0), + vec![ + (999, ElectionScore { minimal_stake: 90, ..Default::default() }), + (99, ElectionScore { minimal_stake: 100, ..Default::default() }) + ] + ); + assert_eq!(Submissions::::metadata_iter(1).count(), 0); + assert_eq!(Submissions::::metadata_iter(0).count(), 2); + + // submit again with a new score. + assert_noop!( + SignedPallet::register( + Origin::signed(999), + ElectionScore { minimal_stake: 80, ..Default::default() } + ), + "Duplicate", + ); + }) + } + + #[test] + fn metadata_submission_sorted_based_on_stake() { + ExtBuilder::signed().build_and_execute(|| { + roll_to_signed_open(); + assert_full_snapshot(); + + let score_from = |x| ElectionScore { minimal_stake: x, ..Default::default() }; + let assert_reserved = |x| assert_eq!(balances(x), (95, 5)); + let assert_unreserved = |x| assert_eq!(balances(x), (100, 0)); + + assert_ok!(SignedPallet::register(Origin::signed(91), score_from(100))); + assert_eq!(*Submissions::::leaderboard(0), vec![(91, score_from(100))]); + assert_reserved(91); + assert!( + matches!(signed_events().as_slice(), &[SignedEvent::Registered(_, x, _)] if x == 91) + ); + + // weaker one comes while we have space. + assert_ok!(SignedPallet::register(Origin::signed(92), score_from(90))); + assert_eq!( + *Submissions::::leaderboard(0), + vec![(92, score_from(90)), (91, score_from(100))] + ); + assert_reserved(92); + assert!(matches!(signed_events().as_slice(), &[ + SignedEvent::Registered(..), + SignedEvent::Registered(_, x, _), + ] if x == 92)); + + // stronger one comes while we have have space. + assert_ok!(SignedPallet::register(Origin::signed(93), score_from(110))); + assert_eq!( + *Submissions::::leaderboard(0), + vec![(92, score_from(90)), (91, score_from(100)), (93, score_from(110))] + ); + assert_reserved(93); + assert!(matches!(signed_events().as_slice(), &[ + SignedEvent::Registered(..), + SignedEvent::Registered(..), + SignedEvent::Registered(_, x, _), + ] if x == 93)); + + // weaker one comes while we don't have space. + assert_noop!(SignedPallet::register(Origin::signed(94), score_from(80)), "QueueFull"); + assert_eq!( + *Submissions::::leaderboard(0), + vec![(92, score_from(90)), (91, score_from(100)), (93, score_from(110))] + ); + assert_unreserved(94); + // no event has been emitted this time. + assert!(matches!( + signed_events().as_slice(), + &[ + SignedEvent::Registered(..), + SignedEvent::Registered(..), + SignedEvent::Registered(..), + ] + )); + + // stronger one comes while we don't have space. Eject the weakest + assert_ok!(SignedPallet::register(Origin::signed(94), score_from(120))); + assert_eq!( + *Submissions::::leaderboard(0), + vec![(91, score_from(100)), (93, score_from(110)), (94, score_from(120))] + ); + assert_reserved(94); + assert_unreserved(92); + assert!(matches!( + signed_events().as_slice(), + &[ + SignedEvent::Registered(..), + SignedEvent::Registered(..), + SignedEvent::Registered(..), + SignedEvent::Discarded(_, 92), + SignedEvent::Registered(_, 94, _), + ] + )); + + // another stronger one comes, only replace the weakest. + assert_ok!(SignedPallet::register(Origin::signed(95), score_from(105))); + assert_eq!( + *Submissions::::leaderboard(0), + vec![(95, score_from(105)), (93, score_from(110)), (94, score_from(120))] + ); + assert_reserved(95); + assert_unreserved(91); + assert!(matches!( + signed_events().as_slice(), + &[ + SignedEvent::Registered(..), + SignedEvent::Registered(..), + SignedEvent::Registered(..), + SignedEvent::Discarded(..), + SignedEvent::Registered(..), + SignedEvent::Discarded(_, 91), + SignedEvent::Registered(_, 95, _), + ] + )); + }) + } + + #[test] + fn can_bail_at_a_cost() { + ExtBuilder::signed().build_and_execute(|| { + roll_to_signed_open(); + assert_full_snapshot(); + + assert_ok!(SignedPallet::register( + Origin::signed(99), + ElectionScore { minimal_stake: 100, ..Default::default() } + )); + assert_eq!(balances(99), (95, 5)); + + // not submitted, cannot bailout. + assert_noop!(SignedPallet::bail(Origin::signed(999)), "NoSubmission"); + + // can bail. + assert_ok!(SignedPallet::bail(Origin::signed(99))); + // 20% of the deposit returned, which is 1, 4 is slashed. + assert_eq!(balances(99), (96, 0)); + assert_no_data_for(0, 99); + + assert!(matches!( + dbg!(signed_events()).as_slice(), + &[SignedEvent::Registered(..), SignedEvent::Bailed(..)] + )); + }); + } + + #[test] + fn can_submit_pages() { + ExtBuilder::signed().build_and_execute(|| { + roll_to_signed_open(); + assert_full_snapshot(); + + assert_noop!( + SignedPallet::submit_page(Origin::signed(99), 0, Default::default()), + "NotRegistered" + ); + + assert_ok!(SignedPallet::register( + Origin::signed(99), + ElectionScore { minimal_stake: 100, ..Default::default() } + )); + + assert_noop!( + SignedPallet::submit_page(Origin::signed(99), 3, Default::default()), + "BadPageIndex" + ); + assert_noop!( + SignedPallet::submit_page(Origin::signed(99), 4, Default::default()), + "BadPageIndex" + ); + + assert_eq!(Submissions::::pages_of(0, 99).count(), 0); + assert_eq!(balances(99), (95, 5)); + + // add the first page. + assert_ok!(SignedPallet::submit_page(Origin::signed(99), 0, Some(Default::default()))); + assert_eq!(Submissions::::pages_of(0, 99).count(), 1); + assert_eq!(balances(99), (94, 6)); + assert_eq!( + Submissions::::metadata_of(0, 99).unwrap().pages.into_inner(), + vec![true, false, false] + ); + + // replace it again, nada. + assert_ok!(SignedPallet::submit_page(Origin::signed(99), 0, Some(Default::default()))); + assert_eq!(Submissions::::pages_of(0, 99).count(), 1); + assert_eq!(balances(99), (94, 6)); + + // add a new one. + assert_ok!(SignedPallet::submit_page(Origin::signed(99), 1, Some(Default::default()))); + assert_eq!(Submissions::::pages_of(0, 99).count(), 2); + assert_eq!(balances(99), (93, 7)); + assert_eq!( + Submissions::::metadata_of(0, 99).unwrap().pages.into_inner(), + vec![true, true, false] + ); + + // remove one, deposit is back. + assert_ok!(SignedPallet::submit_page(Origin::signed(99), 0, None)); + assert_eq!(Submissions::::pages_of(0, 99).count(), 1); + assert_eq!(balances(99), (94, 6)); + assert_eq!( + Submissions::::metadata_of(0, 99).unwrap().pages.into_inner(), + vec![false, true, false] + ); + + assert!(matches!( + signed_events().as_slice(), + &[ + SignedEvent::Registered(..), + SignedEvent::Stored(.., 0), + SignedEvent::Stored(.., 0), + SignedEvent::Stored(.., 1), + SignedEvent::Stored(.., 0), + ] + )); + }); + } +} + +mod e2e { + use frame_election_provider_support::Support; + + use super::*; + #[test] + fn good_bad_evil() { + // an extensive scenario: 3 solutions submitted, once rewarded, one slashed, and one + // discarded. + ExtBuilder::signed().build_and_execute(|| { + roll_to_signed_open(); + assert_full_snapshot(); + + // a valid, but weak solution. + { + let score = + ElectionScore { minimal_stake: 10, sum_stake: 10, sum_stake_squared: 100 }; + assert_ok!(SignedPallet::register(Origin::signed(99), score)); + assert_ok!(SignedPallet::submit_page( + Origin::signed(99), + 0, + Some(Default::default()) + )); + + assert_eq!(balances(99), (94, 6)); + } + + // a valid, strong solution. + let strong_score = { + let paged = mine_full_solution().unwrap(); + load_signed_for_verification(999, paged.clone()); + assert_eq!(balances(999), (92, 8)); + paged.score + }; + + // an invalid, strong solution. + { + let mut score = strong_score; + score.minimal_stake *= 2; + assert_ok!(SignedPallet::register(Origin::signed(92), score)); + assert_eq!(balances(92), (95, 5)); + // we don't even bother to submit a page.. + } + + assert_eq!( + Submissions::::leaderboard(0) + .into_iter() + .map(|(x, _)| x) + .collect::>(), + vec![99, 999, 92] + ); + + roll_to_signed_validation_open(); + + // 92 is slashed in 3 blocks, 999 becomes rewarded in 3 blocks, , and 99 is discarded. + roll_next(); + roll_next(); + roll_next(); + + assert_eq!( + Submissions::::leaderboard(0) + .into_iter() + .map(|(x, _)| x) + .collect::>(), + vec![99, 999] + ); + + roll_next(); + roll_next(); + roll_next(); + + assert!(matches!( + signed_events().as_slice(), + &[ + SignedEvent::Registered(..), + SignedEvent::Stored(..), + SignedEvent::Registered(..), + SignedEvent::Stored(..), + SignedEvent::Stored(..), + SignedEvent::Stored(..), + SignedEvent::Registered(..), + SignedEvent::Slashed(0, 92, ..), + SignedEvent::Rewarded(0, 999, 4), // 3 reward + 1 tx-fee + SignedEvent::Discarded(0, 99), + ] + )); + + assert_eq!(balances(99), (100, 0)); + assert_eq!(balances(999), (104, 0)); + assert_eq!(balances(92), (95, 0)); + + // signed pallet should be in 100% clean state. + assert_ok!(Submissions::::ensure_killed(0)); + }) + } +} diff --git a/substrate/frame/election-provider-multi-block/src/types.rs b/substrate/frame/election-provider-multi-block/src/types.rs new file mode 100644 index 0000000000000..c73ce328a4ed7 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/types.rs @@ -0,0 +1,402 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 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 frame_support::{ + BoundedVec, CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, +}; +use sp_std::{collections::btree_set::BTreeSet, fmt::Debug, prelude::*}; + +use crate::Verifier; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_election_provider_support::{BoundedSupports, ElectionProvider}; +pub use frame_election_provider_support::{NposSolution, PageIndex}; +use scale_info::TypeInfo; +pub use sp_npos_elections::{ElectionResult, ElectionScore}; +use sp_runtime::{ + traits::{One, Saturating, Zero}, + SaturatedConversion, +}; + +/// The supports that's returned from a given [`Verifier`]. TODO: rename this +pub type SupportsOf = BoundedSupports< + ::AccountId, + ::MaxWinnersPerPage, + ::MaxBackersPerWinner, +>; + +/// The solution type used by this crate. +pub type SolutionOf = ::Solution; + +/// The voter index. Derived from [`SolutionOf`]. +pub type SolutionVoterIndexOf = as NposSolution>::VoterIndex; +/// The target index. Derived from [`SolutionOf`]. +pub type SolutionTargetIndexOf = as NposSolution>::TargetIndex; +/// The accuracy of the election, when submitted from offchain. Derived from [`SolutionOf`]. +pub type SolutionAccuracyOf = as NposSolution>::Accuracy; +/// The fallback election type. +pub type FallbackErrorOf = <::Fallback as ElectionProvider>::Error; + +/// The relative distribution of a voter's stake among the winning targets. +pub type AssignmentOf = + sp_npos_elections::Assignment<::AccountId, SolutionAccuracyOf>; + +#[derive( + TypeInfo, + Encode, + Decode, + RuntimeDebugNoBound, + CloneNoBound, + EqNoBound, + PartialEqNoBound, + MaxEncodedLen, + DefaultNoBound, +)] +#[codec(mel_bound(T: crate::Config))] +#[scale_info(skip_type_params(T))] +pub struct PagedRawSolution { + pub solution_pages: BoundedVec, T::Pages>, + pub score: ElectionScore, + pub round: u32, +} + +/// A helper trait to deal with the page index of partial solutions. +/// +/// This should only be called on the `Vec` or similar types. If the solution is *full*, +/// then it returns a normal iterator that is just mapping the index (usize) to `PageIndex`. +/// +/// if the solution is partial, it shifts the indices sufficiently so that the most significant page +/// of the solution matches with the most significant page of the snapshot onchain. +pub trait Pagify { + fn pagify(&self, bound: PageIndex) -> Box + '_>; + fn into_pagify(self, bound: PageIndex) -> Box>; +} + +impl Pagify for Vec { + fn pagify(&self, desired_pages: PageIndex) -> Box + '_> { + Box::new( + self.into_iter() + .enumerate() + .map(|(p, s)| (p.saturated_into::(), s)) + .map(move |(p, s)| { + let desired_pages_usize = desired_pages as usize; + // TODO: this could be an error. + debug_assert!(self.len() <= desired_pages_usize); + let padding = desired_pages_usize.saturating_sub(self.len()); + let new_page = p.saturating_add(padding.saturated_into::()); + (new_page, s) + }), + ) + } + + fn into_pagify(self, _: PageIndex) -> Box> { + todo!() + } +} + +pub trait PadSolutionPages: Sized { + fn pad_solution_pages(self, desired_pages: PageIndex) -> Self; +} + +impl> PadSolutionPages + for BoundedVec +{ + fn pad_solution_pages(self, desired_pages: PageIndex) -> Self { + let desired_pages_usize = (desired_pages).min(Bound::get()) as usize; + debug_assert!(self.len() <= desired_pages_usize); + if self.len() == desired_pages_usize { + return self + } + + // we basically need to prepend the list with this many items. + let empty_slots = desired_pages_usize.saturating_sub(self.len()); + let self_as_vec = sp_std::iter::repeat(Default::default()) + .take(empty_slots) + .chain(self.into_iter()) + .collect::>(); + self_as_vec.try_into().expect("sum of both iterators has at most `desired_pages_usize` items; `desired_pages_usize` is `min`-ed by `Bound`; conversion cannot fail; qed") + } +} + +impl PagedRawSolution { + /// Get the total number of voters, assuming that voters in each page are unique. + pub fn voter_count(&self) -> usize { + self.solution_pages + .iter() + .map(|page| page.voter_count()) + .fold(0usize, |acc, x| acc.saturating_add(x)) + } + + /// Get the total number of winners, assuming that there's only a single page of targets. + pub fn winner_count_single_page_target_snapshot(&self) -> usize { + self.solution_pages + .iter() + .map(|page| page.unique_targets()) + .into_iter() + .flatten() + .collect::>() + .len() + } + + /// Get the total number of edges. + pub fn edge_count(&self) -> usize { + self.solution_pages + .iter() + .map(|page| page.edge_count()) + .fold(0usize, |acc, x| acc.saturating_add(x)) + } +} + +// NOTE on naming conventions: type aliases that end with `Of` should always be `Of`. + +/// Alias for a voter, parameterized by this crate's config. +pub(crate) type VoterOf = + frame_election_provider_support::VoterOf<::DataProvider>; + +/// Alias for a page of voters, parameterized by this crate's config. +pub(crate) type VoterPageOf = + BoundedVec, ::VoterSnapshotPerBlock>; + +/// Alias for all pages of voters, parameterized by this crate's config. +pub(crate) type AllVoterPagesOf = BoundedVec, ::Pages>; + +/// Maximum number of items that [`AllVoterPagesOf`] can contain, when flattened. +pub(crate) struct MaxFlattenedVoters(sp_std::marker::PhantomData); +impl frame_support::traits::Get for MaxFlattenedVoters { + fn get() -> u32 { + T::VoterSnapshotPerBlock::get().saturating_mul(T::Pages::get()) + } +} + +/// Same as [`AllVoterPagesOf`], but instead of being a nested bounded vec, the entire voters are +/// flattened into one outer, unbounded `Vec` type. +/// +/// This is bounded by [`MaxFlattenedVoters`]. +pub(crate) type AllVoterPagesFlattenedOf = BoundedVec, MaxFlattenedVoters>; + +/// Encodes the length of a solution or a snapshot. +/// +/// This is stored automatically on-chain, and it contains the **size of the entire snapshot**. +/// This is also used in dispatchables as weight witness data and should **only contain the size of +/// the presented solution**, not the entire snapshot. +#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, Debug, Default, TypeInfo, MaxEncodedLen)] +pub struct SolutionOrSnapshotSize { + /// The length of voters. + #[codec(compact)] + pub voters: u32, + /// The length of targets. + #[codec(compact)] + pub targets: u32, +} + +/// The type of `Computation` that provided this election data. +#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] +pub enum ElectionCompute { + /// Election was computed on-chain. + OnChain, + /// Election was computed with a signed submission. + Signed, + /// Election was computed with an unsigned submission. + Unsigned, + /// Election was computed with emergency status. + Emergency, +} + +impl Default for ElectionCompute { + fn default() -> Self { + ElectionCompute::OnChain + } +} + +// TODO: maybe use it, else remove it. +#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, MaxEncodedLen, Debug, TypeInfo)] +pub enum PhaseExperimental { + Off, + Snapshot(BlockNumber), + Signed(BlockNumber), + SignedValidation(BlockNumber), + Unsigned(BlockNumber), + Emergency, +} + +impl PhaseExperimental { + pub fn tick(self, next_phase_len: BlockNumber) -> Self { + use PhaseExperimental::*; + match self { + Off => Snapshot(next_phase_len), + Snapshot(x) => + if x.is_zero() { + Signed(next_phase_len) + } else { + Snapshot(x.saturating_sub(One::one())) + }, + Signed(x) => + if x.is_zero() { + SignedValidation(next_phase_len) + } else { + Signed(x.saturating_sub(One::one())) + }, + SignedValidation(x) => + if x.is_zero() { + Unsigned(next_phase_len) + } else { + SignedValidation(x.saturating_sub(One::one())) + }, + + Unsigned(x) => + if x.is_zero() { + // note this: unsigned phase does not really end, only elect can end it. + Unsigned(Zero::zero()) + } else { + Unsigned(x.saturating_sub(One::one())) + }, + Emergency => Emergency, + } + } +} + +/// Current phase of the pallet. +#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, MaxEncodedLen, Debug, TypeInfo)] +pub enum Phase { + /// Nothing is happening, and nothing will happen. + Halted, + /// Nothing is happening, but it might. + Off, + /// Signed phase is open. + Signed, + /// We are validating results. + /// + /// The inner value is the block number at which this phase started. This helps with + /// synchronizing different sub-systems. + /// + /// This always follows the signed phase, and is a window of time in which we try to validate + /// our signed results. + SignedValidation(Bn), + /// Unsigned phase. First element is whether it is active or not, second the starting block + /// number. + /// + /// We do not yet check whether the unsigned phase is active or passive. The intent is for the + /// blockchain to be able to declare: "I believe that there exists an adequate signed + /// solution," advising validators not to bother running the unsigned offchain worker. + /// + /// As validator nodes are free to edit their OCW code, they could simply ignore this advisory + /// and always compute their own solution. However, by default, when the unsigned phase is + /// passive, the offchain workers will not bother running. + Unsigned(Bn), + /// Snapshot is being created. No other operation is allowed. This can be one or more blocks. + /// The inner value should be read as "`remaining` number of pages are left to be fetched". + /// Thus, if inner value is `0` if the snapshot is complete and we are ready to move on. + /// + /// This value should be interpreted after `on_initialize` of this pallet has already been + /// called. + Snapshot(PageIndex), + /// Exporting has begun. + /// + /// Once this is active, no more signed or solutions will be accepted. + Export, + /// The emergency phase. This is enabled upon a failing call to `T::ElectionProvider::elect`. + /// After that, the only way to leave this phase is through a successful + /// `T::ElectionProvider::elect`. + Emergency, +} + +impl Default for Phase { + fn default() -> Self { + Phase::Off + } +} + +impl Phase { + /// Whether the phase is emergency or not. + pub fn is_emergency(&self) -> bool { + matches!(self, Phase::Emergency) + } + + /// Whether the phase is signed or not. + pub fn is_signed(&self) -> bool { + matches!(self, Phase::Signed) + } + + /// Whether the phase is unsigned or not. + pub fn is_unsigned(&self) -> bool { + matches!(self, Phase::Unsigned(_)) + } + + /// Whether the phase is unsigned and open or not, with specific start. + pub fn is_unsigned_open_at(&self, at: Bn) -> bool { + matches!(self, Phase::Unsigned(real) if *real == at) + } + + /// Whether the phase is off or not. + pub fn is_off(&self) -> bool { + matches!(self, Phase::Off) + } + + /// Whether the phase is export or not. + pub fn is_export(&self) -> bool { + matches!(self, Phase::Export) + } + + /// Whether the phase is halted or not. + pub fn is_halted(&self) -> bool { + matches!(self, Phase::Halted) + } + + /// Whether the phase is signed validation or not. + pub fn is_signed_validation(&self) -> bool { + matches!(self, Phase::SignedValidation(_)) + } + + /// Whether the phase is signed validation or not, with specific start. + pub fn is_signed_validation_open_at(&self, at: Bn) -> bool { + matches!(self, Phase::SignedValidation(real) if *real == at) + } +} + +#[cfg(test)] +mod pagify { + use frame_support::{bounded_vec, traits::ConstU32, BoundedVec}; + + use super::{PadSolutionPages, Pagify}; + + #[test] + fn pagify_works() { + // is a noop when you have the same length + assert_eq!( + vec![10, 11, 12].pagify(3).collect::>(), + vec![(0, &10), (1, &11), (2, &12)] + ); + + // pads the values otherwise + assert_eq!(vec![10, 11].pagify(3).collect::>(), vec![(1, &10), (2, &11)]); + assert_eq!(vec![10].pagify(3).collect::>(), vec![(2, &10)]); + } + + #[test] + fn pad_solution_pages_works() { + // noop if the solution is complete, as with pagify. + let solution: BoundedVec<_, ConstU32<3>> = bounded_vec![1u32, 2, 3]; + assert_eq!(solution.pad_solution_pages(3).into_inner(), vec![1, 2, 3]); + + // pads the solution with default if partial.. + let solution: BoundedVec<_, ConstU32<3>> = bounded_vec![2, 3]; + assert_eq!(solution.pad_solution_pages(3).into_inner(), vec![0, 2, 3]); + + // behaves the same as `pad_solution_pages(3)`. + let solution: BoundedVec<_, ConstU32<3>> = bounded_vec![2, 3]; + assert_eq!(solution.pad_solution_pages(4).into_inner(), vec![0, 2, 3]); + } +} diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/benchmarking.rs b/substrate/frame/election-provider-multi-block/src/unsigned/benchmarking.rs new file mode 100644 index 0000000000000..c6f28255a1d93 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/unsigned/benchmarking.rs @@ -0,0 +1,25 @@ +use super::{Pallet as UnsignedPallet, *}; +use crate::{helpers, types::*}; +use frame_support::ensure; + +const SEED: u64 = 1; + +frame_benchmarking::benchmarks! { + // an unsigned solution is a function of the feasibility check of single page. + submit_unsigned { + let v in 4000 .. 8000; + let t in 500 .. 2000; + let a in 5000 .. 7000; + let d in 700 .. 1500; + + // make the snapshot + + // + }: {} verify {} +} + +frame_benchmarking::impl_benchmark_test_suite!( + UnsignedPallet, + crate::mock::ExtBuilder::unsigned().build_offchainify().0, + crate::mock::Runtime, +); diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs b/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs new file mode 100644 index 0000000000000..7a7ded1e085e9 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs @@ -0,0 +1,1970 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 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 super::{Call, Config, Pallet, WeightInfo}; +use crate::{ + helpers, + types::*, + verifier::{self}, +}; +use codec::Encode; +use frame_election_provider_support::{ExtendedBalance, NposSolver, Support, VoteWeight}; +use frame_support::{pallet_prelude::*, traits::Get, BoundedVec}; +use frame_system::pallet_prelude::*; +use sp_runtime::{ + offchain::storage::{MutateStorageError, StorageValueRef}, + traits::{SaturatedConversion, Saturating, Zero}, +}; +use sp_std::prelude::*; + +// TODO: fuzzer for the miner + +/// The type of the snapshot. +/// +/// Used to express errors. +#[derive(Debug, Eq, PartialEq)] +pub enum SnapshotType { + /// Voters at the given page missing. + Voters(PageIndex), + /// Targets missing. + Targets, + /// Metadata missing. + Metadata, + // Desired targets missing. + DesiredTargets, +} + +/// Error type of the pallet's [`crate::Config::Solver`]. +pub type OffchainSolverErrorOf = <::OffchainSolver as NposSolver>::Error; + +/// The errors related to the [`BaseMiner`]. +#[derive( + frame_support::DebugNoBound, frame_support::EqNoBound, frame_support::PartialEqNoBound, +)] +pub enum MinerError { + /// An internal error in the NPoS elections crate. + NposElections(sp_npos_elections::Error), + /// An internal error in the generic solver. + Solver(OffchainSolverErrorOf), + /// Snapshot data was unavailable unexpectedly. + SnapshotUnAvailable(SnapshotType), + /// The snapshot-independent checks failed for the mined solution. + SnapshotIndependentChecks(crate::Error), + /// The solution generated from the miner is not feasible. + Feasibility(verifier::FeasibilityError), + /// Some page index has been invalid. + InvalidPage, + /// Too many winners were removed during trimming. + TooManyWinnersRemoved, + /// A defensive error has occurred. + Defensive(&'static str), +} + +impl From for MinerError { + fn from(e: sp_npos_elections::Error) -> Self { + MinerError::NposElections(e) + } +} + +impl From for MinerError { + fn from(e: verifier::FeasibilityError) -> Self { + MinerError::Feasibility(e) + } +} + +/// The errors related to the [`OffchainMiner`]. +#[derive( + frame_support::DebugNoBound, frame_support::EqNoBound, frame_support::PartialEqNoBound, +)] +pub enum OffchainMinerError { + /// An error in the base miner. + BaseMiner(MinerError), + /// unsigned-specific checks failed. + // NOTE: This uses the error type of the parent crate for now. Might be reworked. + UnsignedChecks(crate::Error), + /// Something went wrong fetching the lock. + Lock(&'static str), + /// Submitting a transaction to the pool failed. + PoolSubmissionFailed, + /// Cannot restore a solution that was not stored. + NoStoredSolution, + /// Cached solution is not a `submit_unsigned` call. + SolutionCallInvalid, + /// Failed to store a solution. + FailedToStoreSolution, +} + +impl From> for OffchainMinerError { + fn from(e: MinerError) -> Self { + OffchainMinerError::BaseMiner(e) + } +} + +/// A base miner that is only capable of mining a new solution and checking it against the state of +/// this pallet for feasibility, and trimming its length/weight. +/// +/// The type of solver is generic and can be provided, as long as it has the same error and account +/// id type as the [`crate::Config::OffchainSolver`]. The default is whatever is fed to +/// [`crate::unsigned::Config::OffchainSolver`]. +pub struct BaseMiner::OffchainSolver>( + sp_std::marker::PhantomData<(T, Solver)>, +); + +impl>> + BaseMiner +{ + /// Mine a new npos solution, with the given number of pages. + /// + /// This miner is only capable of mining a solution that either uses all of the pages of the + /// snapshot, or the top `pages` thereof. + /// + /// This always trims the solution to match a few parameters: + /// + /// 1. [`crate::verifier::Config::MaxBackersPerWinner`] + /// 2. [`crate::unsigned::Config::MinerMaxLength`] + /// 3. [`crate::unsigned::Config::MinerMaxWeight`] + /// + /// The order of pages returned is aligned with the snapshot. For example, the index 0 of the + /// returning solution pages corresponds to the page 0 of the snapshot. + /// + /// The only difference is, if the solution is partial, then [`Pagify`] must be used to properly + /// pad the results. + pub fn mine_solution( + mut pages: PageIndex, + do_reduce: bool, + ) -> Result, MinerError> { + pages = pages.min(T::Pages::get()); + + // read the appropriate snapshot pages. + let desired_targets = crate::Snapshot::::desired_targets() + .ok_or(MinerError::SnapshotUnAvailable(SnapshotType::DesiredTargets))?; + let all_targets = crate::Snapshot::::targets() + .ok_or(MinerError::SnapshotUnAvailable(SnapshotType::Targets))?; + + // This is the range of voters that we are interested in. Mind the second `.rev`, it is + // super critical. + let voter_pages_range = (crate::Pallet::::lsp()..crate::Pallet::::msp() + 1) + .rev() + .take(pages as usize) + .rev(); + + sublog!( + debug, + "unsigned::base-miner", + "mining a solution with {} pages, voter snapshot range will be: {:?}", + pages, + voter_pages_range.clone().collect::>() + ); + + // NOTE: if `pages (2) < T::Pages (3)`, at this point this vector will have length 2, with a + // layout of `[snapshot(1), snapshot(2)]`, namely the two most significant pages of the + // snapshot. + let voter_pages: BoundedVec<_, T::Pages> = + voter_pages_range + .map(|p| { + crate::Snapshot::::voters(p) + .ok_or(MinerError::SnapshotUnAvailable(SnapshotType::Voters(p))) + }) + .collect::, _>>()? + .try_into() + .expect("`voter_pages_range` has `.take(pages)`; it must have length less than pages; it must convert to `BoundedVec`; qed"); + + // we also build this closure early, so we can let `targets` be consumed. + let voter_page_fn = helpers::generate_voter_page_fn::(&voter_pages); + let target_index_fn = helpers::target_index_fn::(&all_targets); + + // now flatten the voters, ready to be used as if pagination did not existed. + let all_voters: AllVoterPagesFlattenedOf = voter_pages + .iter() + .cloned() + .flatten() + .collect::>() + .try_into() + .expect("Flattening the voters into `AllVoterPagesFlattenedOf` cannot fail; qed"); + + let ElectionResult { winners: _, assignments } = Solver::solve( + desired_targets as usize, + all_targets.clone().to_vec(), + all_voters.clone().into_inner(), + ) + .map_err(|e| MinerError::Solver(e))?; + + // reduce and trim supports. We don't trim length and weight here, since those are dependent + // on the final form of the solution ([`PagedRawSolution`]), thus we do it later. + let trimmed_assignments = { + // Implementation note: the overall code path is as follows: election_results -> + // assignments -> staked assignments -> reduce -> supports -> trim supports -> staked + // assignments -> final assignments + // This is by no means the most performant, but is the clear and correct. + use sp_npos_elections::{ + assignment_ratio_to_staked_normalized, assignment_staked_to_ratio_normalized, + reduce, supports_to_staked_assignment, to_supports, EvaluateSupport, + }; + + // These closures are of no use in the rest of these code, since they only deal with the + // overall list of voters. + let cache = helpers::generate_voter_cache::(&all_voters); + let stake_of = helpers::stake_of_fn::(&all_voters, &cache); + + // 1. convert to staked and reduce + let (reduced_count, staked) = { + let mut staked = assignment_ratio_to_staked_normalized(assignments, &stake_of) + .map_err::, _>(Into::into)?; + + // first, reduce the solution if requested. This will already remove a lot of + // "redundant" and reduce the chance for the need of any further trimming. + let count = if do_reduce { reduce(&mut staked) } else { 0 }; + (count, staked) + }; + + // 2. trim the supports by backing. + let (_pre_score, trim_support_count, final_trimmed_assignments) = { + // these supports could very well be invalid for SCORE purposes. The reason is that + // you might trim out half of an account's stake, but we don't look for this + // account's other votes to fix it. + let mut supports_invalid_score = to_supports(&staked); + + let pre_score = (&supports_invalid_score).evaluate(); + let num_trimmed = Self::trim_supports(&mut supports_invalid_score); + + // now recreated the staked assignments + let staked = supports_to_staked_assignment(supports_invalid_score); + let assignments = assignment_staked_to_ratio_normalized(staked) + .map_err::, _>(Into::into)?; + (pre_score, num_trimmed, assignments) + }; + + sublog!( + debug, + "unsigned::base-miner", + "initial score = {:?}, reduced {} edges, trimmed {} supports", + _pre_score, + reduced_count, + trim_support_count, + ); + + final_trimmed_assignments + }; + + // split the assignments into different pages. + let mut paged_assignments: BoundedVec>, T::Pages> = + BoundedVec::with_bounded_capacity(pages as usize); + paged_assignments.bounded_resize(pages as usize, Default::default()); + for assignment in trimmed_assignments { + // NOTE: this `page` index is LOCAL. It does not correspond to the actual page index of + // the snapshot map, but rather the index in the `voter_pages`. + let page = voter_page_fn(&assignment.who).ok_or(MinerError::InvalidPage)?; + let assignment_page = + paged_assignments.get_mut(page as usize).ok_or(MinerError::InvalidPage)?; + assignment_page.push(assignment); + } + + // convert each page to a compact struct + let solution_pages: BoundedVec, T::Pages> = paged_assignments + .into_iter() + .enumerate() + .map(|(page_index, assignment_page)| { + // get the page of the snapshot that corresponds to this page of the assignments. + let page: PageIndex = page_index.saturated_into(); + let voter_snapshot_page = voter_pages + .get(page as usize) + .ok_or(MinerError::SnapshotUnAvailable(SnapshotType::Voters(page)))?; + + let voter_index_fn = { + let cache = helpers::generate_voter_cache::(&voter_snapshot_page); + helpers::voter_index_fn_owned::(cache) + }; + >::from_assignment( + &assignment_page, + &voter_index_fn, + &target_index_fn, + ) + .map_err::, _>(Into::into) + }) + .collect::, _>>()? + .try_into() + .expect("`paged_assignments` is bound by `T::Pages`; length cannot change in iter chain; qed"); + + // now do the weight and length trim. + let mut solution_pages_unbounded = solution_pages.into_inner(); + let _trim_length_weight = + Self::maybe_trim_weight_and_len(&mut solution_pages_unbounded, &voter_pages)?; + let solution_pages = solution_pages_unbounded + .try_into() + .expect("maybe_trim_weight_and_len cannot increase the length of its input; qed."); + sublog!( + debug, + "unsigned::base-miner", + "trimmed {} voters due to length/weight restriction.", + _trim_length_weight + ); + + // finally, wrap everything up. Assign a fake score here, since we might need to re-compute + // it. + let round = crate::Pallet::::round(); + let mut paged = PagedRawSolution { round, solution_pages, score: Default::default() }; + + // OPTIMIZATION: we do feasibility_check inside `compute_score`, and once later + // pre_dispatch. I think it is fine, but maybe we can improve it. + let score = Self::compute_score(&paged).map_err::, _>(Into::into)?; + paged.score = score.clone(); + + sublog!( + info, + "unsigned::base-miner", + "mined a solution with score {:?}, {} winners, {} voters, {} edges, and {} bytes", + score, + paged.winner_count_single_page_target_snapshot(), + paged.voter_count(), + paged.edge_count(), + paged.using_encoded(|b| b.len()) + ); + + Ok(paged) + } + + /// Mine a new solution. Performs the feasibility checks on it as well. + pub fn mine_checked_solution( + pages: PageIndex, + reduce: bool, + ) -> Result, MinerError> { + let paged_solution = Self::mine_solution(pages, reduce)?; + let _ = Self::check_solution(&paged_solution, None, true, "mined")?; + Ok(paged_solution) + } + + /// Check the solution, from the perspective of the base miner: + /// + /// 1. snapshot-independent checks. + /// - with the fingerprint check being an optional step fo that. + /// 2. optionally, feasibility check. + /// + /// In most cases, you should always use this either with `do_feasibility = true` or + /// `maybe_snapshot_fingerprint.is_some()`. Doing both could be an overkill. The snapshot + /// staying constant (which can be checked via the hash) is a string guarantee that the + /// feasibility still holds. + pub fn check_solution( + paged_solution: &PagedRawSolution, + maybe_snapshot_fingerprint: Option, + do_feasibility: bool, + solution_type: &str, + ) -> Result<(), MinerError> { + let _ = crate::Pallet::::snapshot_independent_checks( + paged_solution, + maybe_snapshot_fingerprint, + ) + .map_err(|pe| MinerError::SnapshotIndependentChecks(pe))?; + + if do_feasibility { + let _ = Self::check_feasibility(&paged_solution, solution_type)?; + } + + Ok(()) + } + + /// perform the feasibility check on all pages of a solution, returning `Ok(())` if all good and + /// the corresponding error otherwise. + pub fn check_feasibility( + paged_solution: &PagedRawSolution, + solution_type: &str, + ) -> Result>, MinerError> { + // check every solution page for feasibility. + paged_solution + .solution_pages + .pagify(T::Pages::get()) + .map(|(page_index, page_solution)| { + ::feasibility_check_page( + page_solution.clone(), + page_index as PageIndex, + ) + }) + .collect::, _>>() + .map_err(|err| { + sublog!( + warn, + "unsigned::base-miner", + "feasibility check failed for {} solution at: {:?}", + solution_type, + err + ); + MinerError::from(err) + }) + } + + /// Take the given raw paged solution and compute its score. This will replicate what the chain + /// would do as closely as possible, and expects all the corresponding snapshot data to be + /// available. + fn compute_score(paged_solution: &PagedRawSolution) -> Result> { + use sp_npos_elections::EvaluateSupport; + use sp_std::collections::btree_map::BTreeMap; + + let all_supports = Self::check_feasibility(paged_solution, "mined")?; + let mut total_backings: BTreeMap = BTreeMap::new(); + all_supports.into_iter().map(|x| x.0).flatten().for_each(|(who, support)| { + let backing = total_backings.entry(who).or_default(); + *backing = backing.saturating_add(support.total); + }); + + let all_supports = total_backings + .into_iter() + .map(|(who, total)| (who, Support { total, ..Default::default() })) + .collect::>(); + + Ok((&all_supports).evaluate()) + } + + /// Trim the given supports so that the count of backings in none of them exceeds + /// [`crate::verifier::Config::MaxBackersPerWinner`]. + /// + /// Note that this should only be called on the *global, non-paginated* supports. Calling this + /// on a single page of supports is essentially pointless and does not guarantee anything in + /// particular. + /// + /// Returns the count of supports trimmed. + pub fn trim_supports(supports: &mut sp_npos_elections::Supports) -> u32 { + let limit = ::MaxBackersPerWinner::get() as usize; + let mut count = 0; + supports + .iter_mut() + .filter_map( + |(_, support)| if support.voters.len() > limit { Some(support) } else { None }, + ) + .for_each(|support| { + support.voters.sort_unstable_by(|(_, b1), (_, b2)| b1.cmp(&b2).reverse()); + support.voters.truncate(limit); + support.total = support.voters.iter().fold(0, |acc, (_, x)| acc.saturating_add(*x)); + count.saturating_inc(); + }); + count + } + + /// Maybe tim the weight and length of the given multi-page solution. + /// + /// Returns the number of voters removed. + /// + /// If either of the bounds are not met, the trimming strategy is as follows: + /// + /// Start from the least significant page. Assume only this page is going to be trimmed. call + /// `page.sort()` on this page. This will make sure in each field (`votes1`, `votes2`, etc.) of + /// that page, the voters are sorted by descending stake. Then, we compare the last item of each + /// field. This is the process of removing the single least staked voter. + /// + /// We repeat this until satisfied, for both weight and length. If a full page is removed, but + /// the bound is not satisfied, we need to make sure that we sort the next least valuable page, + /// and repeat the same process. + /// + /// NOTE: this is a public function to be used by the `OffchainWorkerMiner` or any similar one, + /// based on the submission strategy. The length and weight bounds of a call are dependent on + /// the number of pages being submitted, the number of blocks over which we submit, and the type + /// of the transaction and its weight (e.g. signed or unsigned). + /// + /// NOTE: It could be that this function removes too many voters, and the solution becomes + /// invalid. This is not yet handled and only a warning is emitted. + pub fn maybe_trim_weight_and_len( + solution_pages: &mut Vec>, + paged_voters: &AllVoterPagesOf, + ) -> Result> { + debug_assert_eq!(solution_pages.len(), paged_voters.len()); + let size_limit = T::MinerMaxLength::get(); + let weight_limit = T::MinerMaxWeight::get(); + + let all_voters_count = crate::Snapshot::::voters_decode_len(crate::Pallet::::msp()) + .ok_or(MinerError::SnapshotUnAvailable(SnapshotType::Voters( + crate::Pallet::::msp(), + )))? as u32; + let all_targets_count = crate::Snapshot::::targets_decode_len() + .ok_or(MinerError::SnapshotUnAvailable(SnapshotType::Targets))? + as u32; + let desired_targets = crate::Snapshot::::desired_targets() + .ok_or(MinerError::SnapshotUnAvailable(SnapshotType::DesiredTargets))?; + + let winner_count_of = |solution_pages: &Vec>| { + solution_pages + .iter() + .map(|page| page.unique_targets()) + .flatten() + .collect::>() + .len() as u32 + }; + + let voter_count_of = |solution_pages: &Vec>| { + solution_pages + .iter() + .map(|page| page.voter_count()) + .fold(0, |acc, x| acc.saturating_add(x)) as u32 + }; + + let needs_any_trim = |solution_pages: &mut Vec>| { + let size = solution_pages.encoded_size() as u32; + + let next_active_targets = winner_count_of(solution_pages); + if next_active_targets < desired_targets { + sublog!(warn, "unsigned::base-miner", "trimming has cause a solution to have less targets than desired, this might fail feasibility"); + } + + let weight = ::WeightInfo::submit_unsigned( + all_voters_count, + all_targets_count, + // NOTE: we could not re-compute this all the time and instead assume that in each + // round, it is the previous value minus one. + voter_count_of(solution_pages), + next_active_targets, + ); + let needs_weight_trim = weight.any_gt(weight_limit); + let needs_len_trim = size > size_limit; + + needs_weight_trim || needs_len_trim + }; + + // Note the solution might be partial. In either case, this is its least significant page. + let mut current_trimming_page = 0; + let current_trimming_page_stake_of = |current_trimming_page: usize| { + Box::new(move |voter_index: &SolutionVoterIndexOf| -> VoteWeight { + paged_voters + .get(current_trimming_page) + .and_then(|page_voters| { + page_voters + .get((*voter_index).saturated_into::()) + .map(|(_, s, _)| *s) + }) + .unwrap_or_default() + }) + }; + + let sort_current_trimming_page = + |current_trimming_page: usize, solution_pages: &mut Vec>| { + solution_pages.get_mut(current_trimming_page).map(|solution_page| { + let stake_of_fn = current_trimming_page_stake_of(current_trimming_page); + solution_page.sort(stake_of_fn) + }); + }; + + let is_empty = |solution_pages: &Vec>| { + solution_pages.iter().all(|page| page.voter_count().is_zero()) + }; + + if needs_any_trim(solution_pages) { + sort_current_trimming_page(current_trimming_page, solution_pages) + } + + // Implementation note: we want `solution_pages` and `paged_voters` to remain in sync, so + // while one of the pages of `solution_pages` might become "empty" we prefer not removing + // it. This has a slight downside that even an empty pages consumes a few dozens of bytes, + // which we accept for code simplicity. + + let mut removed = 0; + while needs_any_trim(solution_pages) && !is_empty(solution_pages) { + if let Some(removed_idx) = + solution_pages.get_mut(current_trimming_page).and_then(|page| { + let stake_of_fn = current_trimming_page_stake_of(current_trimming_page); + page.remove_weakest_sorted(&stake_of_fn) + }) { + sublog!( + trace, + "unsigned::base-miner", + "removed voter at index {:?} of (un-pagified) page {} as the weakest due to weight/length limits.", + removed_idx, + current_trimming_page + ); + // we removed one person, continue. + removed.saturating_inc(); + } else { + // this page cannot support remove anymore. Try and go to the next page. + sublog!( + debug, + "unsigned::base-miner", + "page {} seems to be fully empty now, moving to the next one", + current_trimming_page + ); + let next_page = current_trimming_page.saturating_add(1); + if paged_voters.len() > next_page { + current_trimming_page = next_page; + sort_current_trimming_page(current_trimming_page, solution_pages); + } else { + sublog!( + warn, + "unsigned::base-miner", + "no more pages to trim from at page {}, already trimmed", + current_trimming_page + ); + break + } + } + } + + Ok(removed) + } +} + +/// A miner that is suited to work inside offchain worker environment. +pub(crate) struct OffchainWorkerMiner(sp_std::marker::PhantomData); + +impl OffchainWorkerMiner { + /// Storage key used to store the offchain worker running status. + pub(crate) const OFFCHAIN_LOCK: &'static [u8] = b"parity/multi-block-unsigned-election/lock"; + /// Storage key used to store the last block number at which offchain worker ran. + const OFFCHAIN_LAST_BLOCK: &'static [u8] = b"parity/multi-block-unsigned-election"; + /// Storage key used to cache the solution `call` and its snapshot fingerprint. + const OFFCHAIN_CACHED_CALL: &'static [u8] = b"parity/multi-block-unsigned-election/call"; + /// The number of pages that the offchain worker miner will try and mine. + const MINING_PAGES: PageIndex = 1; + + /// Get a checked solution from the base miner, ensure unsigned-specific checks also pass, then + /// return an submittable call. + fn mine_checked_call() -> Result, OffchainMinerError> { + // we always do reduce in the offchain worker miner. + let reduce = true; + + // NOTE: we don't run any checks in the base miner, and run all of them via + // `Self::full_checks`. + let paged_solution = + BaseMiner::::mine_solution(Self::MINING_PAGES, reduce) + .map_err::, _>(Into::into)?; + // check the call fully, no fingerprinting. + let _ = Self::check_solution(&paged_solution, None, true, "mined")?; + + let call: Call = + Call::::submit_unsigned { paged_solution: Box::new(paged_solution) }.into(); + + Ok(call) + } + + /// Mine a new checked solution, cache it, and submit it back to the chain as an unsigned + /// transaction. + pub fn mine_check_save_submit() -> Result<(), OffchainMinerError> { + sublog!(debug, "unsigned::ocw-miner", "miner attempting to compute an unsigned solution."); + let call = Self::mine_checked_call()?; + Self::save_solution(&call, crate::Snapshot::::fingerprint())?; + Self::submit_call(call) + } + + /// Check the solution, from the perspective of the offchain-worker miner: + /// + /// 1. unsigned-specific checks. + /// 2. full-checks of the base miner + /// 1. optionally feasibility check. + /// 2. snapshot-independent checks. + /// 1. optionally, snapshot fingerprint. + pub fn check_solution( + paged_solution: &PagedRawSolution, + maybe_snapshot_fingerprint: Option, + do_feasibility: bool, + solution_type: &str, + ) -> Result<(), OffchainMinerError> { + // NOTE: we prefer cheap checks first, so first run unsigned checks. + Pallet::unsigned_specific_checks(paged_solution) + .map_err(|pe| OffchainMinerError::UnsignedChecks(pe)) + .and_then(|_| { + BaseMiner::::check_solution( + paged_solution, + maybe_snapshot_fingerprint, + do_feasibility, + solution_type, + ) + .map_err(OffchainMinerError::BaseMiner) + }) + } + + fn submit_call(call: Call) -> Result<(), OffchainMinerError> { + sublog!( + debug, + "unsigned::ocw-miner", + "miner submitting a solution as an unsigned transaction" + ); + frame_system::offchain::SubmitTransaction::>::submit_unsigned_transaction( + call.into(), + ) + .map_err(|_| OffchainMinerError::PoolSubmissionFailed) + } + + /// Attempt to restore a solution from cache. Otherwise, compute it fresh. Either way, + /// submit if our call's score is greater than that of the cached solution. + pub fn restore_or_compute_then_maybe_submit() -> Result<(), OffchainMinerError> { + sublog!( + debug, + "unsigned::ocw-miner", + "miner attempting to restore or compute an unsigned solution." + ); + + let call = Self::restore_solution() + .and_then(|(call, snapshot_fingerprint)| { + // ensure the cached call is still current before submitting + if let Call::submit_unsigned { paged_solution, .. } = &call { + // we check the snapshot fingerprint instead of doing a full feasibility. + OffchainWorkerMiner::::check_solution( + paged_solution, + Some(snapshot_fingerprint), + false, + "restored" + ).map_err::, _>(Into::into)?; + Ok(call) + } else { + Err(OffchainMinerError::SolutionCallInvalid) + } + }) + .or_else::, _>(|error| { + use MinerError::*; + use OffchainMinerError::*; + + match error { + NoStoredSolution => { + // IFF, not present regenerate. + let call = Self::mine_checked_call()?; + Self::save_solution(&call, crate::Snapshot::::fingerprint())?; + Ok(call) + }, + UnsignedChecks(ref e) => { + sublog!( + error, + "unsigned::ocw-miner", + "unsigned specific checks failed ({:?}) while restoring solution. This should never happen. clearing cache.", + e, + ); + Self::clear_offchain_solution_cache(); + Err(error) + }, + BaseMiner(Feasibility(_)) + | BaseMiner(SnapshotIndependentChecks(crate::Error::::WrongRound)) + | BaseMiner(SnapshotIndependentChecks(crate::Error::::WrongFingerprint)) + => { + // note that failing `Feasibility` can only mean that the solution was + // computed over a snapshot that has changed due to a fork. + sublog!(warn, "unsigned::ocw-miner", "wiping infeasible solution ({:?}).", error); + // kill the "bad" solution. + Self::clear_offchain_solution_cache(); + + // .. then return the error as-is. + Err(error) + }, + _ => { + sublog!(debug, "unsigned::ocw-miner", "unhandled error in restoring offchain solution {:?}", error); + // nothing to do. Return the error as-is. + Err(error) + }, + } + })?; + + Self::submit_call(call) + } + + /// Checks if an execution of the offchain worker is permitted at the given block number, or + /// not. + /// + /// This makes sure that + /// 1. we don't run on previous blocks in case of a re-org + /// 2. we don't run twice within a window of length `T::OffchainRepeat`. + /// + /// Returns `Ok(())` if offchain worker limit is respected, `Err(reason)` otherwise. If + /// `Ok()` is returned, `now` is written in storage and will be used in further calls as the + /// baseline. + pub fn ensure_offchain_repeat_frequency( + now: BlockNumberFor, + ) -> Result<(), OffchainMinerError> { + let threshold = T::OffchainRepeat::get(); + let last_block = StorageValueRef::persistent(&Self::OFFCHAIN_LAST_BLOCK); + + let mutate_stat = last_block.mutate::<_, &'static str, _>( + |maybe_head: Result>, _>| { + match maybe_head { + Ok(Some(head)) if now < head => Err("fork."), + Ok(Some(head)) if now >= head && now <= head + threshold => + Err("recently executed."), + Ok(Some(head)) if now > head + threshold => { + // we can run again now. Write the new head. + Ok(now) + }, + _ => { + // value doesn't exists. Probably this node just booted up. Write, and + // run + Ok(now) + }, + } + }, + ); + + match mutate_stat { + // all good + Ok(_) => Ok(()), + // failed to write. + Err(MutateStorageError::ConcurrentModification(_)) => Err(OffchainMinerError::Lock( + "failed to write to offchain db (concurrent modification).", + )), + // fork etc. + Err(MutateStorageError::ValueFunctionFailed(why)) => Err(OffchainMinerError::Lock(why)), + } + } + + /// Save a given call into OCW storage. + fn save_solution( + call: &Call, + snapshot_fingerprint: T::Hash, + ) -> Result<(), OffchainMinerError> { + sublog!(debug, "unsigned::ocw-miner", "saving a call to the offchain storage."); + let storage = StorageValueRef::persistent(&Self::OFFCHAIN_CACHED_CALL); + match storage.mutate::<_, (), _>(|_| Ok((call.clone(), snapshot_fingerprint))) { + Ok(_) => Ok(()), + Err(MutateStorageError::ConcurrentModification(_)) => + Err(OffchainMinerError::FailedToStoreSolution), + Err(MutateStorageError::ValueFunctionFailed(_)) => { + // this branch should be unreachable according to the definition of + // `StorageValueRef::mutate`: that function should only ever `Err` if the closure we + // pass it returns an error. however, for safety in case the definition changes, we + // do not optimize the branch away or panic. + Err(OffchainMinerError::FailedToStoreSolution) + }, + } + } + + /// Get a saved solution from OCW storage if it exists. + fn restore_solution() -> Result<(Call, T::Hash), OffchainMinerError> { + StorageValueRef::persistent(&Self::OFFCHAIN_CACHED_CALL) + .get() + .ok() + .flatten() + .ok_or(OffchainMinerError::NoStoredSolution) + } + + /// Clear a saved solution from OCW storage. + fn clear_offchain_solution_cache() { + sublog!(debug, "unsigned::ocw-miner", "clearing offchain call cache storage."); + let mut storage = StorageValueRef::persistent(&Self::OFFCHAIN_CACHED_CALL); + storage.clear(); + } + + #[cfg(test)] + fn cached_solution() -> Option> { + StorageValueRef::persistent(&Self::OFFCHAIN_CACHED_CALL) + .get::>() + .unwrap() + } +} + +// This will only focus on testing the internals of `maybe_trim_weight_and_len_works`. +#[cfg(test)] +mod trim_weight_length { + use super::*; + use crate::{mock::*, verifier::Verifier}; + use frame_election_provider_support::TryIntoBoundedSupportsVec; + use sp_npos_elections::Support; + + #[test] + fn trim_weight_basic() { + // This is just demonstration to show the normal election result with new votes, without any + // trimming. + ExtBuilder::unsigned().build_and_execute(|| { + let mut current_voters = Voters::get(); + current_voters.iter_mut().for_each(|(who, stake, ..)| *stake = *who); + Voters::set(current_voters); + + roll_to_snapshot_created(); + ensure_voters(3, 12); + + let solution = mine_full_solution().unwrap(); + + // 4 of these will be trimmed. + assert_eq!( + solution.solution_pages.iter().map(|page| page.voter_count()).sum::(), + 8 + ); + + load_mock_signed_and_start(solution); + let supports = roll_to_full_verification(); + + // NOTE: this test is a bit funny because our msp snapshot page actually contains voters + // with less stake than lsp.. but that's not relevant here. + assert_eq!( + supports, + vec![ + // supports from 30, 40, both will be removed. + vec![ + (30, Support { total: 30, voters: vec![(30, 30)] }), + (40, Support { total: 40, voters: vec![(40, 40)] }) + ], + // supports from 5, 6, 7. 5 and 6 will be removed. + vec![ + (30, Support { total: 11, voters: vec![(7, 7), (5, 2), (6, 2)] }), + (40, Support { total: 7, voters: vec![(5, 3), (6, 4)] }) + ], + // all will stay + vec![(40, Support { total: 9, voters: vec![(2, 2), (3, 3), (4, 4)] })] + ] + .try_into_bounded_supports_vec() + .unwrap() + ); + }); + + // now we get to the real test... + ExtBuilder::unsigned().miner_weight(4).build_and_execute(|| { + // first, replace the stake of all voters with their account id. + let mut current_voters = Voters::get(); + current_voters.iter_mut().for_each(|(who, stake, ..)| *stake = *who); + Voters::set(current_voters); + + // with 1 weight unit per voter, this can only support 4 voters, despite having 12 in + // the snapshot. + roll_to_snapshot_created(); + ensure_voters(3, 12); + + let solution = mine_full_solution().unwrap(); + assert_eq!( + solution.solution_pages.iter().map(|page| page.voter_count()).sum::(), + 4 + ); + + load_mock_signed_and_start(solution); + let supports = roll_to_full_verification(); + + // a solution is queued. + assert!(VerifierPallet::queued_score().is_some()); + + assert_eq!( + supports, + vec![ + vec![], + vec![(30, Support { total: 7, voters: vec![(7, 7)] })], + vec![(40, Support { total: 9, voters: vec![(2, 2), (3, 3), (4, 4)] })] + ] + .try_into_bounded_supports_vec() + .unwrap() + ); + }) + } + + #[test] + fn trim_weight_partial_solution() { + // This is just demonstration to show the normal election result with new votes, without any + // trimming. + ExtBuilder::unsigned().build_and_execute(|| { + let mut current_voters = Voters::get(); + current_voters.iter_mut().for_each(|(who, stake, ..)| *stake = *who); + Voters::set(current_voters); + + roll_to_snapshot_created(); + ensure_voters(3, 12); + + let solution = mine_solution(2).unwrap(); + + // 3 of these will be trimmed. + assert_eq!( + solution.solution_pages.iter().map(|page| page.voter_count()).sum::(), + 7 + ); + + load_mock_signed_and_start(solution); + let supports = roll_to_full_verification(); + + // a solution is queued. + assert!(VerifierPallet::queued_score().is_some()); + + assert_eq!( + supports, + vec![ + vec![], + // 5, 6, 7 will be removed in the next test block + vec![ + (10, Support { total: 10, voters: vec![(8, 8), (5, 2)] }), + (30, Support { total: 16, voters: vec![(6, 6), (7, 7), (5, 3)] }) + ], + vec![ + (10, Support { total: 5, voters: vec![(1, 1), (4, 4)] }), + (30, Support { total: 2, voters: vec![(2, 2)] }) + ] + ] + .try_into_bounded_supports_vec() + .unwrap() + ); + }); + + // now we get to the real test... + ExtBuilder::unsigned().miner_weight(4).build_and_execute(|| { + // first, replace the stake of all voters with their account id. + let mut current_voters = Voters::get(); + current_voters.iter_mut().for_each(|(who, stake, ..)| *stake = *who); + Voters::set(current_voters); + + roll_to_snapshot_created(); + ensure_voters(3, 12); + + let solution = mine_solution(2).unwrap(); + assert_eq!( + solution.solution_pages.iter().map(|page| page.voter_count()).sum::(), + 4 + ); + + load_mock_signed_and_start(solution); + let supports = roll_to_full_verification(); + + // a solution is queued. + assert!(VerifierPallet::queued_score().is_some()); + + assert_eq!( + supports, + vec![ + vec![], + vec![(10, Support { total: 8, voters: vec![(8, 8)] })], + vec![ + (10, Support { total: 5, voters: vec![(1, 1), (4, 4)] }), + (30, Support { total: 2, voters: vec![(2, 2)] }) + ] + ] + .try_into_bounded_supports_vec() + .unwrap() + ); + }) + } + + #[test] + fn trim_weight_too_much_makes_solution_invalid() { + // with just 1 units, we can support 1 voter. This is not enough to have 2 winner which we + // want. + ExtBuilder::unsigned().miner_weight(1).build_and_execute(|| { + let mut current_voters = Voters::get(); + current_voters.iter_mut().for_each(|(who, stake, ..)| *stake = *who); + Voters::set(current_voters); + + roll_to_snapshot_created(); + ensure_voters(3, 12); + + let solution = mine_full_solution().unwrap(); + assert_eq!( + solution.solution_pages.iter().map(|page| page.voter_count()).sum::(), + 1 + ); + + load_mock_signed_and_start(solution); + let supports = roll_to_full_verification(); + + // nothing is queued + assert!(VerifierPallet::queued_score().is_none()); + assert_eq!( + supports, + vec![vec![], vec![], vec![]].try_into_bounded_supports_vec().unwrap() + ); + }) + } + + #[test] + fn trim_length() { + // This is just demonstration to show the normal election result with new votes, without any + // trimming. + ExtBuilder::unsigned().build_and_execute(|| { + let mut current_voters = Voters::get(); + current_voters.iter_mut().for_each(|(who, stake, ..)| *stake = *who); + Voters::set(current_voters); + + roll_to_snapshot_created(); + ensure_voters(3, 12); + + let solution = mine_full_solution().unwrap(); + + assert_eq!( + solution.solution_pages.iter().map(|page| page.voter_count()).sum::(), + 8 + ); + + assert_eq!(solution.solution_pages.encoded_size(), 105); + + load_mock_signed_and_start(solution); + let supports = roll_to_full_verification(); + + // a solution is queued. + assert!(VerifierPallet::queued_score().is_some()); + + assert_eq!( + supports, + vec![ + // if we set any limit less than 105, 30 will be the first to leave. + vec![ + (30, Support { total: 30, voters: vec![(30, 30)] }), + (40, Support { total: 40, voters: vec![(40, 40)] }) + ], + vec![ + (30, Support { total: 11, voters: vec![(7, 7), (5, 2), (6, 2)] }), + (40, Support { total: 7, voters: vec![(5, 3), (6, 4)] }) + ], + vec![(40, Support { total: 9, voters: vec![(2, 2), (3, 3), (4, 4)] })] + ] + .try_into_bounded_supports_vec() + .unwrap() + ); + }); + + ExtBuilder::unsigned().miner_max_length(104).build_and_execute(|| { + let mut current_voters = Voters::get(); + current_voters.iter_mut().for_each(|(who, stake, ..)| *stake = *who); + Voters::set(current_voters); + + roll_to_snapshot_created(); + ensure_voters(3, 12); + + let solution = mine_full_solution().unwrap(); + + assert_eq!( + solution.solution_pages.iter().map(|page| page.voter_count()).sum::(), + 7 + ); + + assert_eq!(solution.solution_pages.encoded_size(), 99); + + load_mock_signed_and_start(solution); + let supports = roll_to_full_verification(); + + // a solution is queued. + assert!(VerifierPallet::queued_score().is_some()); + + assert_eq!( + supports, + vec![ + // 30 is gone! + vec![(40, Support { total: 40, voters: vec![(40, 40)] })], + vec![ + (30, Support { total: 11, voters: vec![(7, 7), (5, 2), (6, 2)] }), + (40, Support { total: 7, voters: vec![(5, 3), (6, 4)] }) + ], + vec![(40, Support { total: 9, voters: vec![(2, 2), (3, 3), (4, 4)] })] + ] + .try_into_bounded_supports_vec() + .unwrap() + ); + }); + } +} + +#[cfg(test)] +mod base_miner { + use super::*; + use crate::{mock::*, Snapshot}; + use frame_election_provider_support::TryIntoBoundedSupportsVec; + use sp_npos_elections::Support; + use sp_runtime::PerU16; + + #[test] + fn pagination_does_not_affect_score() { + let score_1 = ExtBuilder::unsigned() + .pages(1) + .voter_per_page(12) + .build_unchecked() + .execute_with(|| { + roll_to_snapshot_created(); + mine_full_solution().unwrap().score + }); + let score_2 = ExtBuilder::unsigned() + .pages(2) + .voter_per_page(6) + .build_unchecked() + .execute_with(|| { + roll_to_snapshot_created(); + mine_full_solution().unwrap().score + }); + let score_3 = ExtBuilder::unsigned() + .pages(3) + .voter_per_page(4) + .build_unchecked() + .execute_with(|| { + roll_to_snapshot_created(); + mine_full_solution().unwrap().score + }); + + assert_eq!(score_1, score_2); + assert_eq!(score_2, score_3); + } + + #[test] + fn mine_solution_single_page_works() { + ExtBuilder::unsigned().pages(1).voter_per_page(8).build_and_execute(|| { + roll_to_snapshot_created(); + + ensure_voters(1, 8); + ensure_targets(1, 4); + + assert_eq!( + Snapshot::::voters(0) + .unwrap() + .into_iter() + .map(|(x, _, _)| x) + .collect::>(), + vec![1, 2, 3, 4, 5, 6, 7, 8] + ); + + let paged = mine_full_solution().unwrap(); + assert_eq!(paged.solution_pages.len(), 1); + + // this solution must be feasible and submittable. + BaseMiner::::check_solution(&paged, None, true, "mined").unwrap(); + + // now do a realistic full verification + load_mock_signed_and_start(paged.clone()); + let supports = roll_to_full_verification(); + + assert_eq!( + supports, + vec![vec![ + (10, Support { total: 30, voters: vec![(1, 10), (8, 10), (4, 5), (5, 5)] }), + ( + 40, + Support { + total: 40, + voters: vec![(2, 10), (3, 10), (6, 10), (4, 5), (5, 5)] + } + ) + ]] + .try_into_bounded_supports_vec() + .unwrap() + ); + + // NOTE: this is the same as the score of any other test that contains the first 8 + // voters, we already test for this in `pagination_does_not_affect_score`. + assert_eq!( + paged.score, + ElectionScore { minimal_stake: 30, sum_stake: 70, sum_stake_squared: 2500 } + ); + }) + } + + #[test] + fn mine_solution_double_page_works() { + ExtBuilder::unsigned().pages(2).voter_per_page(4).build_and_execute(|| { + roll_to_snapshot_created(); + + // 2 pages of 8 voters + ensure_voters(2, 8); + // 1 page of 4 targets + ensure_targets(1, 4); + + // voters in pages. note the reverse page index. + assert_eq!( + Snapshot::::voters(0) + .unwrap() + .into_iter() + .map(|(x, _, _)| x) + .collect::>(), + vec![5, 6, 7, 8] + ); + assert_eq!( + Snapshot::::voters(1) + .unwrap() + .into_iter() + .map(|(x, _, _)| x) + .collect::>(), + vec![1, 2, 3, 4] + ); + // targets in pages. + assert_eq!(Snapshot::::targets().unwrap(), vec![10, 20, 30, 40]); + let paged = mine_full_solution().unwrap(); + + assert_eq!( + paged.solution_pages, + vec![ + TestNposSolution { + // voter 6 (index 1) is backing 40 (index 3). + // voter 8 (index 3) is backing 10 (index 0) + votes1: vec![(1, 3), (3, 0)], + // voter 5 (index 0) is backing 40 (index 10) and 10 (index 0) + votes2: vec![(0, [(0, PerU16::from_parts(32768))], 3)], + ..Default::default() + }, + TestNposSolution { + // voter 1 (index 0) is backing 10 (index 0) + // voter 2 (index 1) is backing 40 (index 3) + // voter 3 (index 2) is backing 40 (index 3) + votes1: vec![(0, 0), (1, 3), (2, 3)], + // voter 4 (index 3) is backing 40 (index 10) and 10 (index 0) + votes2: vec![(3, [(0, PerU16::from_parts(32768))], 3)], + ..Default::default() + }, + ] + ); + + // this solution must be feasible and submittable. + BaseMiner::::check_solution(&paged, None, false, "mined").unwrap(); + + // it must also be verified in the verifier + load_mock_signed_and_start(paged.clone()); + let supports = roll_to_full_verification(); + + assert_eq!( + supports, + vec![ + // page0, supports from voters 5, 6, 7, 8 + vec![ + (10, Support { total: 15, voters: vec![(8, 10), (5, 5)] }), + (40, Support { total: 15, voters: vec![(6, 10), (5, 5)] }) + ], + // page1 supports from voters 1, 2, 3, 4 + vec![ + (10, Support { total: 15, voters: vec![(1, 10), (4, 5)] }), + (40, Support { total: 25, voters: vec![(2, 10), (3, 10), (4, 5)] }) + ] + ] + .try_into_bounded_supports_vec() + .unwrap() + ); + + assert_eq!( + paged.score, + ElectionScore { minimal_stake: 30, sum_stake: 70, sum_stake_squared: 2500 } + ); + }) + } + + #[test] + fn mine_solution_triple_page_works() { + ExtBuilder::unsigned().pages(3).voter_per_page(4).build_and_execute(|| { + roll_to_snapshot_created(); + + ensure_voters(3, 12); + ensure_targets(1, 4); + + // voters in pages. note the reverse page index. + assert_eq!( + Snapshot::::voters(2) + .unwrap() + .into_iter() + .map(|(x, _, _)| x) + .collect::>(), + vec![1, 2, 3, 4] + ); + assert_eq!( + Snapshot::::voters(1) + .unwrap() + .into_iter() + .map(|(x, _, _)| x) + .collect::>(), + vec![5, 6, 7, 8] + ); + assert_eq!( + Snapshot::::voters(0) + .unwrap() + .into_iter() + .map(|(x, _, _)| x) + .collect::>(), + vec![10, 20, 30, 40] + ); + + let paged = mine_full_solution().unwrap(); + assert_eq!( + paged.solution_pages, + vec![ + TestNposSolution { votes1: vec![(2, 2), (3, 3)], ..Default::default() }, + TestNposSolution { + votes1: vec![(2, 2)], + votes2: vec![ + (0, [(2, PerU16::from_parts(32768))], 3), + (1, [(2, PerU16::from_parts(32768))], 3) + ], + ..Default::default() + }, + TestNposSolution { + votes1: vec![(2, 3), (3, 3)], + votes2: vec![(1, [(2, PerU16::from_parts(32768))], 3)], + ..Default::default() + }, + ] + ); + + // this solution must be feasible and submittable. + BaseMiner::::check_solution(&paged, None, true, "mined").unwrap(); + // now do a realistic full verification + load_mock_signed_and_start(paged.clone()); + let supports = roll_to_full_verification(); + + assert_eq!( + supports, + vec![ + // page 0: self-votes. + vec![ + (30, Support { total: 30, voters: vec![(30, 30)] }), + (40, Support { total: 40, voters: vec![(40, 40)] }) + ], + // page 1: 5, 6, 7, 8 + vec![ + (30, Support { total: 20, voters: vec![(7, 10), (5, 5), (6, 5)] }), + (40, Support { total: 10, voters: vec![(5, 5), (6, 5)] }) + ], + // page 2: 1, 2, 3, 4 + vec![ + (30, Support { total: 5, voters: vec![(2, 5)] }), + (40, Support { total: 25, voters: vec![(3, 10), (4, 10), (2, 5)] }) + ] + ] + .try_into_bounded_supports_vec() + .unwrap() + ); + + assert_eq!( + paged.score, + ElectionScore { minimal_stake: 55, sum_stake: 130, sum_stake_squared: 8650 } + ); + }) + } + + #[test] + fn mine_solution_choses_most_significant_pages() { + ExtBuilder::unsigned().pages(2).voter_per_page(4).build_and_execute(|| { + roll_to_snapshot_created(); + + ensure_voters(2, 8); + ensure_targets(1, 4); + + // these folks should be ignored safely. + assert_eq!( + Snapshot::::voters(0) + .unwrap() + .into_iter() + .map(|(x, _, _)| x) + .collect::>(), + vec![5, 6, 7, 8] + ); + // voters in pages 1, this is the most significant page. + assert_eq!( + Snapshot::::voters(1) + .unwrap() + .into_iter() + .map(|(x, _, _)| x) + .collect::>(), + vec![1, 2, 3, 4] + ); + + // now we ask for just 1 page of solution. + let paged = mine_solution(1).unwrap(); + + assert_eq!( + paged.solution_pages, + vec![TestNposSolution { + // voter 1 (index 0) is backing 10 (index 0) + // voter 2 (index 1) is backing 40 (index 3) + // voter 3 (index 2) is backing 40 (index 3) + votes1: vec![(0, 0), (1, 3), (2, 3)], + // voter 4 (index 3) is backing 40 (index 10) and 10 (index 0) + votes2: vec![(3, [(0, PerU16::from_parts(32768))], 3)], + ..Default::default() + }] + ); + + // this solution must be feasible and submittable. + BaseMiner::::check_solution(&paged, None, true, "mined").unwrap(); + // now do a realistic full verification. + load_mock_signed_and_start(paged.clone()); + let supports = roll_to_full_verification(); + + assert_eq!( + supports, + vec![ + // page0: non existent. + vec![], + // page1 supports from voters 1, 2, 3, 4 + vec![ + (10, Support { total: 15, voters: vec![(1, 10), (4, 5)] }), + (40, Support { total: 25, voters: vec![(2, 10), (3, 10), (4, 5)] }) + ] + ] + .try_into_bounded_supports_vec() + .unwrap() + ); + + assert_eq!( + paged.score, + ElectionScore { minimal_stake: 15, sum_stake: 40, sum_stake_squared: 850 } + ); + }) + } + + #[test] + fn mine_solution_2_out_of_3_pages() { + ExtBuilder::unsigned().pages(3).voter_per_page(4).build_and_execute(|| { + roll_to_snapshot_created(); + + ensure_voters(3, 12); + ensure_targets(1, 4); + + assert_eq!( + Snapshot::::voters(0) + .unwrap() + .into_iter() + .map(|(x, _, _)| x) + .collect::>(), + vec![10, 20, 30, 40] + ); + assert_eq!( + Snapshot::::voters(1) + .unwrap() + .into_iter() + .map(|(x, _, _)| x) + .collect::>(), + vec![5, 6, 7, 8] + ); + assert_eq!( + Snapshot::::voters(2) + .unwrap() + .into_iter() + .map(|(x, _, _)| x) + .collect::>(), + vec![1, 2, 3, 4] + ); + + // now we ask for just 1 page of solution. + let paged = mine_solution(2).unwrap(); + + // this solution must be feasible and submittable. + BaseMiner::::check_solution(&paged, None, true, "mined").unwrap(); + + assert_eq!( + paged.solution_pages, + vec![ + // this can be 'pagified" to snapshot at index 1, which contains 5, 6, 7, 8 + // in which: + // 6 (index:1) votes for 40 (index:3) + // 8 (index:1) votes for 10 (index:0) + // 5 votes for both 10 and 40 + TestNposSolution { + votes1: vec![(1, 3), (3, 0)], + votes2: vec![(0, [(0, PerU16::from_parts(32768))], 3)], + ..Default::default() + }, + // this can be 'pagified" to snapshot at index 2, which contains 1, 2, 3, 4 + // in which: + // 1 (index:0) votes for 10 (index:0) + // 2 (index:1) votes for 40 (index:3) + // 3 (index:2) votes for 40 (index:3) + // 4 votes for both 10 and 40 + TestNposSolution { + votes1: vec![(0, 0), (1, 3), (2, 3)], + votes2: vec![(3, [(0, PerU16::from_parts(32768))], 3)], + ..Default::default() + } + ] + ); + + // this solution must be feasible and submittable. + BaseMiner::::check_solution(&paged, None, true, "mined").unwrap(); + // now do a realistic full verification. + load_mock_signed_and_start(paged.clone()); + let supports = roll_to_full_verification(); + + assert_eq!( + supports, + vec![ + // empty page 0. + vec![], + // supports from voters 5, 6, 7, 8 + vec![ + (10, Support { total: 15, voters: vec![(8, 10), (5, 5)] }), + (40, Support { total: 15, voters: vec![(6, 10), (5, 5)] }) + ], + // supports from voters 1, 2, 3, 4 + vec![ + (10, Support { total: 15, voters: vec![(1, 10), (4, 5)] }), + (40, Support { total: 25, voters: vec![(2, 10), (3, 10), (4, 5)] }) + ] + ] + .try_into_bounded_supports_vec() + .unwrap() + ); + + assert_eq!( + paged.score, + ElectionScore { minimal_stake: 30, sum_stake: 70, sum_stake_squared: 2500 } + ); + }) + } + + #[test] + fn can_reduce_solution() { + ExtBuilder::unsigned().build_and_execute(|| { + roll_to_snapshot_created(); + let full_edges = BaseMiner::::mine_solution(Pages::get(), false) + .unwrap() + .solution_pages + .iter() + .fold(0, |acc, x| acc + x.edge_count()); + let reduced_edges = BaseMiner::::mine_solution(Pages::get(), true) + .unwrap() + .solution_pages + .iter() + .fold(0, |acc, x| acc + x.edge_count()); + + assert!(reduced_edges < full_edges, "{} < {} not fulfilled", reduced_edges, full_edges); + }) + } + + #[test] + fn trim_backings_works() { + ExtBuilder::unsigned() + .max_backing_per_target(5) + .voter_per_page(8) + .build_and_execute(|| { + // 10 and 40 are the default winners, we add a lot more votes to them. + for i in 100..105 { + VOTERS.with(|v| v.borrow_mut().push((i, i - 96, vec![10].try_into().unwrap()))); + } + roll_to_snapshot_created(); + + ensure_voters(3, 17); + + // now we let the miner mine something for us.. + let paged = mine_full_solution().unwrap(); + load_mock_signed_and_start(paged.clone()); + + // this must be correct + let supports = roll_to_full_verification(); + + // 10 has no more than 5 backings, and from the new voters that we added in this + // test, the most staked ones stayed (103, 104) and the rest trimmed. + assert_eq!( + supports, + vec![ + // 1 backing for 10 + vec![(10, Support { total: 8, voters: vec![(104, 8)] })], + // 2 backings for 10 + vec![ + (10, Support { total: 17, voters: vec![(10, 10), (103, 7)] }), + (40, Support { total: 40, voters: vec![(40, 40)] }) + ], + // 20 backings for 10 + vec![ + (10, Support { total: 20, voters: vec![(1, 10), (8, 10)] }), + ( + 40, + Support { + total: 40, + voters: vec![(2, 10), (3, 10), (4, 10), (6, 10)] + } + ) + ] + ] + .try_into_bounded_supports_vec() + .unwrap() + ); + }) + } +} + +#[cfg(test)] +mod offchain_worker_miner { + use crate::verifier::Verifier; + use frame_support::traits::Hooks; + use sp_runtime::offchain::storage_lock::{BlockAndTime, StorageLock}; + + use super::*; + use crate::mock::*; + + #[test] + fn lock_prevents_frequent_execution() { + let (mut ext, _) = ExtBuilder::unsigned().build_offchainify(); + ext.execute_with_sanity_checks(|| { + let offchain_repeat = ::OffchainRepeat::get(); + + // first execution -- okay. + assert!(OffchainWorkerMiner::::ensure_offchain_repeat_frequency(25).is_ok()); + + // next block: rejected. + assert_noop!( + OffchainWorkerMiner::::ensure_offchain_repeat_frequency(26), + OffchainMinerError::Lock("recently executed.") + ); + + // allowed after `OFFCHAIN_REPEAT` + assert!(OffchainWorkerMiner::::ensure_offchain_repeat_frequency( + (26 + offchain_repeat).into() + ) + .is_ok()); + + // a fork like situation: re-execute last 3. + assert!(OffchainWorkerMiner::::ensure_offchain_repeat_frequency( + (26 + offchain_repeat - 3).into() + ) + .is_err()); + assert!(OffchainWorkerMiner::::ensure_offchain_repeat_frequency( + (26 + offchain_repeat - 2).into() + ) + .is_err()); + assert!(OffchainWorkerMiner::::ensure_offchain_repeat_frequency( + (26 + offchain_repeat - 1).into() + ) + .is_err()); + }) + } + + #[test] + fn lock_released_after_successful_execution() { + // first, ensure that a successful execution releases the lock + let (mut ext, pool) = ExtBuilder::unsigned().build_offchainify(); + ext.execute_with_sanity_checks(|| { + let guard = StorageValueRef::persistent(&OffchainWorkerMiner::::OFFCHAIN_LOCK); + let last_block = + StorageValueRef::persistent(&OffchainWorkerMiner::::OFFCHAIN_LAST_BLOCK); + + roll_to(25); + assert!(MultiBlock::current_phase().is_unsigned()); + + // initially, the lock is not set. + assert!(guard.get::().unwrap().is_none()); + + // a successful a-z execution. + UnsignedPallet::offchain_worker(25); + assert_eq!(pool.read().transactions.len(), 1); + + // afterwards, the lock is not set either.. + assert!(guard.get::().unwrap().is_none()); + assert_eq!(last_block.get::().unwrap(), Some(25)); + }); + } + + #[test] + fn lock_prevents_overlapping_execution() { + // ensure that if the guard is in hold, a new execution is not allowed. + let (mut ext, pool) = ExtBuilder::unsigned().build_offchainify(); + ext.execute_with_sanity_checks(|| { + roll_to(25); + assert!(MultiBlock::current_phase().is_unsigned()); + + // artificially set the value, as if another thread is mid-way. + let mut lock = StorageLock::>::with_block_deadline( + OffchainWorkerMiner::::OFFCHAIN_LOCK, + UnsignedPhase::get().saturated_into(), + ); + let guard = lock.lock(); + + // nothing submitted. + UnsignedPallet::offchain_worker(25); + assert_eq!(pool.read().transactions.len(), 0); + UnsignedPallet::offchain_worker(26); + assert_eq!(pool.read().transactions.len(), 0); + + drop(guard); + + // 🎉 ! + UnsignedPallet::offchain_worker(25); + assert_eq!(pool.read().transactions.len(), 1); + }); + } + + #[test] + fn initial_ocw_runs_and_saves_new_cache() { + let (mut ext, pool) = ExtBuilder::unsigned().build_offchainify(); + ext.execute_with_sanity_checks(|| { + roll_to(25); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + + let last_block = + StorageValueRef::persistent(&OffchainWorkerMiner::::OFFCHAIN_LAST_BLOCK); + let cache = + StorageValueRef::persistent(&OffchainWorkerMiner::::OFFCHAIN_CACHED_CALL); + + assert_eq!(last_block.get::(), Ok(None)); + assert_eq!(cache.get::>(), Ok(None)); + + // creates, caches, submits without expecting previous cache value + UnsignedPallet::offchain_worker(25); + assert_eq!(pool.read().transactions.len(), 1); + + assert_eq!(last_block.get::(), Ok(Some(25))); + assert!(matches!(cache.get::>(), Ok(Some(_)))); + }) + } + + #[test] + fn ocw_pool_submission_works() { + let (mut ext, pool) = ExtBuilder::unsigned().build_offchainify(); + ext.execute_with_sanity_checks(|| { + roll_to_with_ocw(25, None); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + // OCW must have submitted now + + let encoded = pool.read().transactions[0].clone(); + let extrinsic: Extrinsic = codec::Decode::decode(&mut &*encoded).unwrap(); + let call = extrinsic.call; + assert!(matches!( + call, + crate::mock::Call::UnsignedPallet(crate::unsigned::Call::submit_unsigned { .. }) + )); + }) + } + + #[test] + fn resubmits_after_offchain_repeat() { + let (mut ext, pool) = ExtBuilder::unsigned().build_offchainify(); + ext.execute_with_sanity_checks(|| { + let offchain_repeat = ::OffchainRepeat::get(); + roll_to(25); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + + assert!(OffchainWorkerMiner::::cached_solution().is_none()); + // creates, caches, submits without expecting previous cache value + UnsignedPallet::offchain_worker(25); + assert_eq!(pool.read().transactions.len(), 1); + let tx_cache = pool.read().transactions[0].clone(); + // assume that the tx has been processed + pool.try_write().unwrap().transactions.clear(); + + // attempts to resubmit the tx after the threshold has expired. + UnsignedPallet::offchain_worker(25 + 1 + offchain_repeat); + assert_eq!(pool.read().transactions.len(), 1); + + // resubmitted tx is identical to first submission + let tx = &pool.read().transactions[0]; + assert_eq!(&tx_cache, tx); + }) + } + + #[test] + fn regenerates_and_resubmits_after_offchain_repeat_if_no_cache() { + let (mut ext, pool) = ExtBuilder::unsigned().build_offchainify(); + ext.execute_with_sanity_checks(|| { + let offchain_repeat = ::OffchainRepeat::get(); + roll_to(25); + + assert!(OffchainWorkerMiner::::cached_solution().is_none()); + // creates, caches, submits without expecting previous cache value. + UnsignedPallet::offchain_worker(25); + assert_eq!(pool.read().transactions.len(), 1); + let tx_cache = pool.read().transactions[0].clone(); + // assume that the tx has been processed + pool.try_write().unwrap().transactions.clear(); + + // remove the cached submitted tx. + // this ensures that when the resubmit window rolls around, we're ready to regenerate + // from scratch if necessary + let mut call_cache = + StorageValueRef::persistent(&OffchainWorkerMiner::::OFFCHAIN_CACHED_CALL); + assert!(matches!(call_cache.get::>(), Ok(Some(_)))); + call_cache.clear(); + + // attempts to resubmit the tx after the threshold has expired + UnsignedPallet::offchain_worker(25 + 1 + offchain_repeat); + assert_eq!(pool.read().transactions.len(), 1); + + // resubmitted tx is identical to first submission + let tx = &pool.read().transactions[0]; + assert_eq!(&tx_cache, tx); + }) + } + + #[test] + fn altering_snapshot_invalidates_solution_cache() { + // by infeasible, we mean here that if the snapshot fingerprint has changed. + let (mut ext, pool) = ExtBuilder::unsigned().build_offchainify(); + ext.execute_with_sanity_checks(|| { + let offchain_repeat = ::OffchainRepeat::get(); + roll_to_with_ocw(25, None); + + // something is submitted.. + assert_eq!(pool.read().transactions.len(), 1); + pool.try_write().unwrap().transactions.clear(); + + // ..and cached + let call_cache = + StorageValueRef::persistent(&OffchainWorkerMiner::::OFFCHAIN_CACHED_CALL); + assert!(matches!(call_cache.get::>(), Ok(Some(_)))); + + // now change the snapshot, ofc this is rare in reality. This makes the cached call + // infeasible. + assert_eq!(crate::Snapshot::::targets().unwrap(), vec![10, 20, 30, 40]); + let pre_fingerprint = crate::Snapshot::::fingerprint(); + crate::Snapshot::::remove_target(0); + let post_fingerprint = crate::Snapshot::::fingerprint(); + assert_eq!(crate::Snapshot::::targets().unwrap(), vec![20, 30, 40]); + assert_ne!(pre_fingerprint, post_fingerprint); + + // now run ocw again + roll_to_with_ocw(25 + offchain_repeat + 1, None); + // nothing is submitted this time.. + assert_eq!(pool.read().transactions.len(), 0); + // .. and the cache is gone. + assert_eq!(call_cache.get::>(), Ok(None)); + + // upon the next run, we re-generate and submit something fresh again. + roll_to_with_ocw(25 + offchain_repeat + offchain_repeat + 2, None); + assert_eq!(pool.read().transactions.len(), 1); + assert!(matches!(call_cache.get::>(), Ok(Some(_)))); + }) + } + + #[test] + fn wont_resubmit_if_weak_score() { + // common case, if the score is weak, don't bother with anything, ideally check from the + // logs that we don't run feasibility in this call path. Score check must come before. + let (mut ext, pool) = ExtBuilder::unsigned().build_offchainify(); + ext.execute_with_sanity_checks(|| { + let offchain_repeat = ::OffchainRepeat::get(); + // unfortunately there's no pretty way to run the ocw code such that it generates a + // weak, but correct solution. We just write it to cache directly. + + roll_to_with_ocw(25, Some(pool.clone())); + + // something is submitted.. + assert_eq!(pool.read().transactions.len(), 1); + + // ..and cached + let call_cache = + StorageValueRef::persistent(&OffchainWorkerMiner::::OFFCHAIN_CACHED_CALL); + assert!(matches!(call_cache.get::>(), Ok(Some(_)))); + + // and replace it with something weak. + let weak_solution = raw_paged_from_supports( + vec![vec![(40, Support { total: 10, voters: vec![(3, 10)] })]], + 0, + ); + let weak_call = + crate::unsigned::Call::submit_unsigned { paged_solution: Box::new(weak_solution) }; + call_cache.set(&weak_call); + + // run again + roll_to_with_ocw(25 + offchain_repeat + 1, Some(pool.clone())); + // nothing is submitted this time.. + assert_eq!(pool.read().transactions.len(), 0); + // .. and the cache IS STILL THERE! + assert!(matches!(call_cache.get::>(), Ok(Some(_)))); + }) + } + + #[test] + fn ocw_submission_e2e_works() { + let (mut ext, pool) = ExtBuilder::unsigned().build_offchainify(); + ext.execute_with_sanity_checks(|| { + assert!(VerifierPallet::queued_score().is_none()); + roll_to_with_ocw(25 + 1, Some(pool.clone())); + assert!(VerifierPallet::queued_score().is_some()); + + // call is cached. + let call_cache = + StorageValueRef::persistent(&OffchainWorkerMiner::::OFFCHAIN_CACHED_CALL); + assert!(matches!(call_cache.get::>(), Ok(Some(_)))); + + // pool is empty + assert_eq!(pool.read().transactions.len(), 0); + }) + } + + #[test] + fn will_not_mine_if_not_enough_winners() { + // also see `trim_weight_too_much_makes_solution_invalid`. + let (mut ext, _) = ExtBuilder::unsigned().desired_targets(77).build_offchainify(); + ext.execute_with_sanity_checks(|| { + roll_to_unsigned_open(); + ensure_voters(3, 12); + + // beautiful errors, isn't it? + assert_eq!( + OffchainWorkerMiner::::mine_checked_call().unwrap_err(), + OffchainMinerError::BaseMiner(MinerError::SnapshotIndependentChecks( + crate::Error::::WrongWinnerCount + )) + ); + }); + } +} diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs b/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs new file mode 100644 index 0000000000000..d9878f34c3832 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs @@ -0,0 +1,578 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 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 unsigned phase, and its miner. + +/// Exports of this pallet +pub use pallet::*; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + +/// The miner. +pub mod miner; + +#[frame_support::pallet] +mod pallet { + use crate::{ + types::*, + unsigned::miner::{self}, + verifier::Verifier, + }; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use sp_runtime::traits::SaturatedConversion; + use sp_std::prelude::*; + + /// convert a DispatchError to a custom InvalidTransaction with the inner code being the error + /// number. + fn dispatch_error_to_invalid(error: sp_runtime::DispatchError) -> InvalidTransaction { + use sp_runtime::ModuleError; + let error_number = match error { + DispatchError::Module(ModuleError { error, .. }) => error, + _ => [0u8, 0, 0, 0], + }; + InvalidTransaction::Custom(error_number[0] as u8) + } + + pub trait WeightInfo { + fn submit_unsigned(v: u32, t: u32, a: u32, d: u32) -> Weight; + } + + impl WeightInfo for () { + fn submit_unsigned(_v: u32, _t: u32, _a: u32, _d: u32) -> Weight { + Default::default() + } + } + + #[pallet::config] + #[pallet::disable_frame_system_supertrait_check] + pub trait Config: crate::Config { + /// The repeat threshold of the offchain worker. + /// + /// For example, if it is 5, that means that at least 5 blocks will elapse between attempts + /// to submit the worker's solution. + type OffchainRepeat: Get>; + + /// The solver used in hte offchain worker miner + type OffchainSolver: frame_election_provider_support::NposSolver< + AccountId = Self::AccountId, + >; + + /// The priority of the unsigned transaction submitted in the unsigned-phase + type MinerTxPriority: Get; + /// Maximum weight that the miner should consume. + /// + /// The miner will ensure that the total weight of the unsigned solution will not exceed + /// this value, based on [`WeightInfo::submit_unsigned`]. + type MinerMaxWeight: Get; + /// Maximum length (bytes) that the mined solution should consume. + /// + /// The miner will ensure that the total length of the unsigned solution will not exceed + /// this value. + type MinerMaxLength: Get; + + type WeightInfo: WeightInfo; + } + + #[pallet::pallet] + pub struct Pallet(PhantomData); + + #[pallet::call] + impl Pallet { + /// Submit an unsigned solution. + /// + /// This works very much like an inherent, as only the validators are permitted to submit + /// anything. By default validators will compute this call in their `offchain_worker` hook + /// and try and submit it back. + #[pallet::weight((0, DispatchClass::Operational))] + #[pallet::call_index(0)] + pub fn submit_unsigned( + origin: OriginFor, + paged_solution: Box>, + ) -> DispatchResultWithPostInfo { + ensure_none(origin)?; + let error_message = "Invalid unsigned submission must produce invalid block and \ + deprive validator from their authoring reward."; + + // phase, round, claimed score, page-count and hash are checked in pre-dispatch. we + // don't check them here anymore. + debug_assert!(Self::validate_unsigned_checks(&paged_solution).is_ok()); + + let only_page = paged_solution + .solution_pages + .into_inner() + .pop() + .expect("length of `solution_pages` is always `T::Pages`, `T::Pages` is always greater than 1, can be popped; qed."); + let claimed_score = paged_solution.score; + let _supports = ::verify_synchronous( + only_page, + claimed_score, + crate::Pallet::::msp(), + ) + .expect(error_message); + + sublog!( + info, + "unsigned", + "queued an unsigned solution with score {:?} and {} winners", + claimed_score, + _supports.len() + ); + Ok(None.into()) + } + } + + #[pallet::validate_unsigned] + impl ValidateUnsigned for Pallet { + type Call = Call; + fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity { + if let Call::submit_unsigned { paged_solution, .. } = call { + match source { + TransactionSource::Local | TransactionSource::InBlock => { /* allowed */ }, + _ => return InvalidTransaction::Call.into(), + } + + let _ = Self::validate_unsigned_checks(paged_solution.as_ref()) + .map_err(|err| { + sublog!( + debug, + "unsigned", + "unsigned transaction validation failed due to {:?}", + err + ); + err + }) + .map_err(dispatch_error_to_invalid)?; + + ValidTransaction::with_tag_prefix("OffchainElection") + // The higher the score.minimal_stake, the better a paged_solution is. + .priority( + T::MinerTxPriority::get() + .saturating_add(paged_solution.score.minimal_stake.saturated_into()), + ) + // Used to deduplicate unsigned solutions: each validator should produce one + // paged_solution per round at most, and solutions are not propagate. + .and_provides(paged_solution.round) + // Transaction should stay in the pool for the duration of the unsigned phase. + .longevity(T::UnsignedPhase::get().saturated_into::()) + // We don't propagate this. This can never be validated at a remote node. + .propagate(false) + .build() + } else { + InvalidTransaction::Call.into() + } + } + + fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> { + if let Call::submit_unsigned { paged_solution, .. } = call { + Self::validate_unsigned_checks(paged_solution.as_ref()) + .map_err(dispatch_error_to_invalid) + .map_err(Into::into) + } else { + Err(InvalidTransaction::Call.into()) + } + } + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn offchain_worker(now: BlockNumberFor) { + use sp_runtime::offchain::storage_lock::{BlockAndTime, StorageLock}; + + // Create a lock with the maximum deadline of number of blocks in the unsigned phase. + // This should only come useful in an **abrupt** termination of execution, otherwise the + // guard will be dropped upon successful execution. + let mut lock = + StorageLock::>>::with_block_deadline( + miner::OffchainWorkerMiner::::OFFCHAIN_LOCK, + T::UnsignedPhase::get().saturated_into(), + ); + + match lock.try_lock() { + Ok(_guard) => { + Self::do_synchronized_offchain_worker(now); + }, + Err(deadline) => { + sublog!( + debug, + "unsigned", + "offchain worker lock not released, deadline is {:?}", + deadline + ); + }, + }; + } + } + + impl Pallet { + /// Internal logic of the offchain worker, to be executed only when the offchain lock is + /// acquired with success. + fn do_synchronized_offchain_worker(now: BlockNumberFor) { + use miner::OffchainWorkerMiner; + + let current_phase = crate::Pallet::::current_phase(); + sublog!( + trace, + "unsigned", + "lock for offchain worker acquired. Phase = {:?}", + current_phase + ); + match current_phase { + Phase::Unsigned(opened) if opened == now => { + // Mine a new solution, cache it, and attempt to submit it + let initial_output = + OffchainWorkerMiner::::ensure_offchain_repeat_frequency(now) + .and_then(|_| OffchainWorkerMiner::::mine_check_save_submit()); + sublog!( + debug, + "unsigned", + "initial offchain worker output: {:?}", + initial_output + ); + }, + Phase::Unsigned(opened) if opened < now => { + // Try and resubmit the cached solution, and recompute ONLY if it is not + // feasible. + let resubmit_output = + OffchainWorkerMiner::::ensure_offchain_repeat_frequency(now).and_then( + |_| OffchainWorkerMiner::::restore_or_compute_then_maybe_submit(), + ); + sublog!( + debug, + "unsigned", + "resubmit offchain worker output: {:?}", + resubmit_output + ); + }, + _ => {}, + } + } + + /// The checks that should happen in the `ValidateUnsigned`'s `pre_dispatch` and + /// `validate_unsigned` functions. + /// + /// These check both for snapshot independent checks, and some checks that are specific to + /// the unsigned phase. + pub(crate) fn validate_unsigned_checks( + paged_solution: &PagedRawSolution, + ) -> DispatchResult { + Self::unsigned_specific_checks(paged_solution) + .and(crate::Pallet::::snapshot_independent_checks(paged_solution, None)) + .map_err(Into::into) + } + + /// The checks that are specific to the (this) unsigned pallet. + /// + /// ensure solution has the correct phase, and it has only 1 page. + pub fn unsigned_specific_checks( + paged_solution: &PagedRawSolution, + ) -> Result<(), crate::Error> { + ensure!( + crate::Pallet::::current_phase().is_unsigned(), + crate::Error::::EarlySubmission + ); + + ensure!(paged_solution.solution_pages.len() == 1, crate::Error::::WrongPageCount); + + Ok(()) + } + + #[cfg(test)] + pub(crate) fn sanity_check() -> Result<(), &'static str> { + Ok(()) + } + } +} + +#[cfg(test)] +mod validate_unsigned { + use frame_election_provider_support::Support; + use frame_support::{ + pallet_prelude::InvalidTransaction, + unsigned::{TransactionSource, TransactionValidityError, ValidateUnsigned}, + }; + + use super::Call; + use crate::{mock::*, types::*, verifier::Verifier}; + + #[test] + fn retracts_weak_score_accepts_threshold_better() { + ExtBuilder::unsigned() + .solution_improvement_threshold(sp_runtime::Perbill::from_percent(10)) + .build_and_execute(|| { + roll_to_snapshot_created(); + + let solution = mine_full_solution().unwrap(); + load_mock_signed_and_start(solution.clone()); + roll_to_full_verification(); + + // Some good solution is queued now. + assert_eq!( + ::queued_score(), + Some(ElectionScore { + minimal_stake: 55, + sum_stake: 130, + sum_stake_squared: 8650 + }) + ); + + roll_to_unsigned_open(); + + // this is just worse + let attempt = + fake_solution(ElectionScore { minimal_stake: 20, ..Default::default() }); + let call = Call::submit_unsigned { paged_solution: Box::new(attempt) }; + assert_eq!( + UnsignedPallet::validate_unsigned(TransactionSource::Local, &call).unwrap_err(), + TransactionValidityError::Invalid(InvalidTransaction::Custom(2)), + ); + + // this is better, but not enough better. + let insufficient_improvement = 55 * 105 / 100; + let attempt = fake_solution(ElectionScore { + minimal_stake: insufficient_improvement, + ..Default::default() + }); + let call = Call::submit_unsigned { paged_solution: Box::new(attempt) }; + assert_eq!( + UnsignedPallet::validate_unsigned(TransactionSource::Local, &call).unwrap_err(), + TransactionValidityError::Invalid(InvalidTransaction::Custom(2)), + ); + + // note that we now have to use a solution with 2 winners, just to pass all of the + // snapshot independent checks. + let mut paged = raw_paged_from_supports( + vec![vec![ + (40, Support { total: 10, voters: vec![(3, 5)] }), + (30, Support { total: 10, voters: vec![(3, 5)] }), + ]], + 0, + ); + let sufficient_improvement = 55 * 115 / 100; + paged.score = + ElectionScore { minimal_stake: sufficient_improvement, ..Default::default() }; + let call = Call::submit_unsigned { paged_solution: Box::new(paged) }; + assert!(UnsignedPallet::validate_unsigned(TransactionSource::Local, &call).is_ok()); + }) + } + + #[test] + fn retracts_wrong_round() { + ExtBuilder::unsigned().build_and_execute(|| { + roll_to_unsigned_open(); + + let mut attempt = + fake_solution(ElectionScore { minimal_stake: 5, ..Default::default() }); + attempt.round += 1; + let call = Call::submit_unsigned { paged_solution: Box::new(attempt) }; + + assert_eq!( + UnsignedPallet::validate_unsigned(TransactionSource::Local, &call).unwrap_err(), + // WrongRound is index 1 + TransactionValidityError::Invalid(InvalidTransaction::Custom(1)), + ); + }) + } + + #[test] + fn retracts_too_many_pages_unsigned() { + ExtBuilder::unsigned().build_and_execute(|| { + // NOTE: unsigned solutions should have just 1 page, regardless of the configured + // page count. + roll_to_unsigned_open(); + let attempt = mine_full_solution().unwrap(); + let call = Call::submit_unsigned { paged_solution: Box::new(attempt) }; + + assert_eq!( + UnsignedPallet::validate_unsigned(TransactionSource::Local, &call).unwrap_err(), + // WrongPageCount is index 3 + TransactionValidityError::Invalid(InvalidTransaction::Custom(3)), + ); + + let attempt = mine_solution(2).unwrap(); + let call = Call::submit_unsigned { paged_solution: Box::new(attempt) }; + + assert_eq!( + UnsignedPallet::validate_unsigned(TransactionSource::Local, &call).unwrap_err(), + TransactionValidityError::Invalid(InvalidTransaction::Custom(3)), + ); + + let attempt = mine_solution(1).unwrap(); + let call = Call::submit_unsigned { paged_solution: Box::new(attempt) }; + + assert!(UnsignedPallet::validate_unsigned(TransactionSource::Local, &call).is_ok(),); + }) + } + + #[test] + fn retracts_wrong_winner_count() { + ExtBuilder::unsigned().desired_targets(2).build_and_execute(|| { + roll_to_unsigned_open(); + + let paged = raw_paged_from_supports( + vec![vec![(40, Support { total: 10, voters: vec![(3, 10)] })]], + 0, + ); + + let call = Call::submit_unsigned { paged_solution: Box::new(paged) }; + + assert_eq!( + UnsignedPallet::validate_unsigned(TransactionSource::Local, &call).unwrap_err(), + // WrongWinnerCount is index 4 + TransactionValidityError::Invalid(InvalidTransaction::Custom(4)), + ); + }); + } + + #[test] + fn retracts_wrong_phase() { + ExtBuilder::unsigned().signed_phase(5, 0).build_and_execute(|| { + let solution = raw_paged_solution_low_score(); + let call = Call::submit_unsigned { paged_solution: Box::new(solution.clone()) }; + + // initial + assert_eq!(MultiBlock::current_phase(), Phase::Off); + assert!(matches!( + ::validate_unsigned( + TransactionSource::Local, + &call + ) + .unwrap_err(), + // because EarlySubmission is index 0. + TransactionValidityError::Invalid(InvalidTransaction::Custom(0)) + )); + assert!(matches!( + ::pre_dispatch(&call).unwrap_err(), + TransactionValidityError::Invalid(InvalidTransaction::Custom(0)) + )); + + // signed + roll_to(20); + assert_eq!(MultiBlock::current_phase(), Phase::Signed); + assert!(matches!( + ::validate_unsigned( + TransactionSource::Local, + &call + ) + .unwrap_err(), + TransactionValidityError::Invalid(InvalidTransaction::Custom(0)) + )); + assert!(matches!( + ::pre_dispatch(&call).unwrap_err(), + TransactionValidityError::Invalid(InvalidTransaction::Custom(0)) + )); + + // unsigned + roll_to(25); + assert!(MultiBlock::current_phase().is_unsigned()); + + assert_ok!(::validate_unsigned( + TransactionSource::Local, + &call + )); + assert_ok!(::pre_dispatch(&call)); + }) + } + + #[test] + fn priority_is_set() { + ExtBuilder::unsigned() + .miner_tx_priority(20) + .desired_targets(0) + .build_and_execute(|| { + roll_to(25); + assert!(MultiBlock::current_phase().is_unsigned()); + + let solution = + fake_solution(ElectionScore { minimal_stake: 5, ..Default::default() }); + let call = Call::submit_unsigned { paged_solution: Box::new(solution.clone()) }; + + assert_eq!( + ::validate_unsigned( + TransactionSource::Local, + &call + ) + .unwrap() + .priority, + 25 + ); + }) + } +} + +#[cfg(test)] +mod call { + use crate::{mock::*, verifier::Verifier, Snapshot}; + + #[test] + fn unsigned_submission_e2e() { + let (mut ext, pool) = ExtBuilder::unsigned().build_offchainify(); + ext.execute_with_sanity_checks(|| { + roll_to_snapshot_created(); + + // snapshot is created.. + assert_full_snapshot(); + // ..txpool is empty.. + assert_eq!(pool.read().transactions.len(), 0); + // ..but nothing queued. + assert_eq!(::queued_score(), None); + + // now the OCW should submit something. + roll_next_with_ocw(Some(pool.clone())); + assert_eq!(pool.read().transactions.len(), 1); + assert_eq!(::queued_score(), None); + + // and now it should be applied. + roll_next_with_ocw(Some(pool.clone())); + assert_eq!(pool.read().transactions.len(), 0); + assert!(matches!(::queued_score(), Some(_))); + }) + } + + #[test] + #[should_panic( + expected = "Invalid unsigned submission must produce invalid block and deprive validator from their authoring reward." + )] + fn unfeasible_solution_panics() { + let (mut ext, pool) = ExtBuilder::unsigned().build_offchainify(); + ext.execute_with_sanity_checks(|| { + roll_to_snapshot_created(); + + // snapshot is created.. + assert_full_snapshot(); + // ..txpool is empty.. + assert_eq!(pool.read().transactions.len(), 0); + // ..but nothing queued. + assert_eq!(::queued_score(), None); + + // now the OCW should submit something. + roll_next_with_ocw(Some(pool.clone())); + assert_eq!(pool.read().transactions.len(), 1); + assert_eq!(::queued_score(), None); + + // now we change the snapshot -- this should ensure that the solution becomes invalid. + // Note that we don't change the known fingerprint of the solution. + Snapshot::::remove_target(2); + + // and now it should be applied. + roll_next_with_ocw(Some(pool.clone())); + assert_eq!(pool.read().transactions.len(), 0); + assert!(matches!(::queued_score(), Some(_))); + }) + } +} diff --git a/substrate/frame/election-provider-multi-block/src/verifier/impls.rs b/substrate/frame/election-provider-multi-block/src/verifier/impls.rs new file mode 100644 index 0000000000000..89725bf69c979 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/verifier/impls.rs @@ -0,0 +1,892 @@ +// 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. + +// TODO: clean and standardize the imports + +use super::*; +use crate::{helpers, SolutionOf, SupportsOf}; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_election_provider_support::{ExtendedBalance, NposSolution, PageIndex}; +use frame_support::{ + ensure, + pallet_prelude::*, + traits::{Defensive, Get}, +}; +use frame_system::pallet_prelude::*; +use sp_npos_elections::{ElectionScore, EvaluateSupport}; +use sp_runtime::{Perbill, RuntimeDebug}; +use sp_std::{collections::btree_map::BTreeMap, prelude::*}; + +use pallet::*; + +/// The status of this pallet. +#[derive(Encode, Decode, scale_info::TypeInfo, Clone, Copy, MaxEncodedLen, RuntimeDebug)] +#[cfg_attr(any(test, debug_assertions), derive(PartialEq, Eq))] +pub enum Status { + /// A verification is ongoing, and the next page that will be verified is indicated with the + /// inner value. + Ongoing(PageIndex), + /// Nothing is happening. + Nothing, +} + +impl Default for Status { + fn default() -> Self { + Self::Nothing + } +} + +/// Enum to point to the valid variant of the [`QueuedSolution`]. +#[derive(Encode, Decode, scale_info::TypeInfo, Clone, Copy, MaxEncodedLen)] +enum ValidSolution { + X, + Y, +} + +impl Default for ValidSolution { + fn default() -> Self { + ValidSolution::Y + } +} + +impl ValidSolution { + fn other(&self) -> Self { + match *self { + ValidSolution::X => ValidSolution::Y, + ValidSolution::Y => ValidSolution::X, + } + } +} + +/// A simple newtype that represents the partial backing of a winner. It only stores the total +/// backing, and the sum of backings, as opposed to a [`sp_npos_elections::Support`] that also +/// stores all of the backers' individual contribution. +/// +/// This is mainly here to allow us to implement `Backings` for it. +#[derive(Default, Encode, Decode, MaxEncodedLen, scale_info::TypeInfo)] +pub struct PartialBackings { + /// The total backing of this particular winner. + pub total: ExtendedBalance, + /// The number of backers. + pub backers: u32, +} + +impl sp_npos_elections::Backings for PartialBackings { + fn total(&self) -> ExtendedBalance { + self.total + } +} + +#[frame_support::pallet] +pub(crate) mod pallet { + use crate::{types::SupportsOf, verifier::Verifier}; + + use super::*; + use frame_support::pallet_prelude::{ValueQuery, *}; + use sp_npos_elections::evaluate_support; + use sp_runtime::Perbill; + + #[pallet::config] + #[pallet::disable_frame_system_supertrait_check] + pub trait Config: crate::Config { + /// The overarching event type. + type RuntimeEvent: From> + + IsType<::RuntimeEvent> + + TryInto>; + + /// Origin that can control this pallet. Note that any action taken by this origin (such) + /// as providing an emergency solution is not checked. Thus, it must be a trusted origin. + type ForceOrigin: EnsureOrigin; + + /// The minimum amount of improvement to the solution score that defines a solution as + /// "better". + #[pallet::constant] + type SolutionImprovementThreshold: Get; + + /// Maximum number of voters that can support a single target, among ALL pages of a + /// verifying solution. It can only ever be checked on the last page of any given + /// verification. + /// + /// This must be set such that the memory limits in the rest of the system are well + /// respected. + type MaxBackersPerWinner: Get; + + /// Maximum number of supports (aka. winners/validators/targets) that can be represented in + /// a page of results. + type MaxWinnersPerPage: Get; + + /// Something that can provide the solution data to the verifier. + /// + /// In reality, this will be fulfilled by the signed phase. + type SolutionDataProvider: crate::verifier::SolutionDataProvider; + + /// The weight information of this pallet. + type WeightInfo; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// The verification data was unavailable and it could not continue. + VerificationDataUnavailable, + /// A verification failed at the given page. + /// + /// NOTE: if the index is 0, then this could mean either the feasibility of the last page + /// was wrong, or the final checks of `finalize_verification` failed. + VerificationFailed(PageIndex, FeasibilityError), + /// The given page of a solution has been verified, with the given number of winners being + /// found in it. + Verified(PageIndex, u32), + /// A solution with the given score has replaced our current best solution. + Queued(ElectionScore, Option), + } + + /// A wrapper interface for the storage items related to the queued solution. + /// + /// It wraps the following: + /// + /// - [`QueuedSolutionX`]. + /// - [`QueuedSolutionY`]. + /// - [`QueuedValidVariant`]. + /// - [`QueuedSolutionScore`]. + /// - [`QueuedSolutionBackings`]. + /// + /// As the name suggests, [`QueuedValidVariant`] points to the correct variant between + /// [`QueuedSolutionX`] and [`QueuedSolutionY`]. In the context of this pallet, by VALID and + /// INVALID variant we mean either of these two storage items, based on the value of + /// [`QueuedValidVariant`]. + /// + /// ### Invariants + /// + /// The following conditions must be met at all times for this group of storage items to be + /// sane. + /// + /// - [`QueuedSolutionScore`] must always be correct. In other words, it should correctly be the + /// score of [`QueuedValidVariant`]. + /// - [`QueuedSolutionScore`] must always be [`Config::SolutionImprovementThreshold`] better + /// than [`MinimumScore`]. + /// - The number of existing keys in [`QueuedSolutionBackings`] must always match that of the + /// INVALID variant. + /// + /// Moreover, the following conditions must be met when this pallet is in [`Status::Nothing`], + /// meaning that no ongoing asynchronous verification is ongoing. + /// + /// - No keys should exist in the INVALID variant variant. + /// - This implies that no data should exist in `QueuedSolutionBackings`. + /// + /// > Note that some keys *might* exist in the queued variant, but since partial solutions + /// > (having less than `T::Pages` pages) are in principle correct, we cannot assert anything on + /// > the number of keys in the VALID variant. In fact, an empty solution with score of [0, 0, + /// > 0] can also be correct. + /// + /// No additional conditions must be met when the pallet is in [`Status::Ongoing`]. The number + /// of pages in + pub struct QueuedSolution(sp_std::marker::PhantomData); + impl QueuedSolution { + /// Private helper for mutating the storage group. + fn mutate_checked(mutate: impl FnOnce() -> R) -> R { + let r = mutate(); + #[cfg(debug_assertions)] + assert!(Self::sanity_check().is_ok()); + r + } + + /// Finalize a correct solution. + /// + /// Should be called at the end of a verification process, once we are sure that a certain + /// solution is 100% correct. + /// + /// It stores its score, flips the pointer to it being the current best one, and clears all + /// the backings and the invalid variant. (note: in principle, we can skip clearing the + /// backings here) + pub(crate) fn finalize_correct(score: ElectionScore) { + sublog!( + info, + "verifier", + "finalizing verification a correct solution, replacing old score {:?} with {:?}", + QueuedSolutionScore::::get(), + score + ); + + Self::mutate_checked(|| { + QueuedValidVariant::::mutate(|v| *v = v.other()); + QueuedSolutionScore::::put(score); + + // Clear what was previously the valid variant. Also clears the partial backings. + Self::clear_invalid_and_backings_unchecked(); + }); + } + + /// Clear all relevant information of an invalid solution. + /// + /// Should be called at any step, if we encounter an issue which makes the solution + /// infeasible. + pub(crate) fn clear_invalid_and_backings() { + Self::mutate_checked(Self::clear_invalid_and_backings_unchecked) + } + + /// Same as [`clear_invalid_and_backings`], but without any checks for the integrity of the + /// storage item group. + pub(crate) fn clear_invalid_and_backings_unchecked() { + match Self::invalid() { + ValidSolution::X => QueuedSolutionX::::remove_all(None), + ValidSolution::Y => QueuedSolutionY::::remove_all(None), + }; + QueuedSolutionBackings::::remove_all(None); + } + + /// Write a single page of a valid solution into the `invalid` variant of the storage. + /// + /// This should only be called once we are sure that this particular page is 100% correct. + /// + /// This is called after *a page* has been validated, but the entire solution is not yet + /// known to be valid. At this stage, we write to the invalid variant. Once all pages are + /// verified, a call to [`finalize_correct`] will seal the correct pages and flip the + /// invalid/valid variants. + pub(crate) fn set_invalid_page(page: PageIndex, supports: SupportsOf>) { + use frame_support::traits::TryCollect; + Self::mutate_checked(|| { + let backings: BoundedVec<_, _> = supports + .iter() + .map(|(x, s)| (x.clone(), PartialBackings { total: s.total, backers: s.voters.len() as u32 } )) + .try_collect() + .expect("`SupportsOf` is bounded by as Verifier>::MaxWinnersPerPage, which is assured to be the same as `T::MaxWinnersPerPage` in an integrity test"); + QueuedSolutionBackings::::insert(page, backings); + + match Self::invalid() { + ValidSolution::X => QueuedSolutionX::::insert(page, supports), + ValidSolution::Y => QueuedSolutionY::::insert(page, supports), + } + }) + } + + /// Write a single page to the valid variant directly. + /// + /// This is not the normal flow of writing, and the solution is not checked. + /// + /// This is only useful to override the valid solution with a single (likely backup) + /// solution. + pub(crate) fn force_set_single_page_valid( + page: PageIndex, + supports: SupportsOf>, + score: ElectionScore, + ) { + Self::mutate_checked(|| { + // clear everything about valid solutions. + match Self::valid() { + ValidSolution::X => QueuedSolutionX::::remove_all(None), + ValidSolution::Y => QueuedSolutionY::::remove_all(None), + }; + QueuedSolutionScore::::kill(); + + // write a single new page. + match Self::valid() { + ValidSolution::X => QueuedSolutionX::::insert(page, supports), + ValidSolution::Y => QueuedSolutionY::::insert(page, supports), + } + + // write the score. + QueuedSolutionScore::::put(score); + }) + } + + /// Clear all storage items. + /// + /// Should only be called once everything is done. + pub(crate) fn kill() { + Self::mutate_checked(|| { + QueuedSolutionX::::remove_all(None); + QueuedSolutionY::::remove_all(None); + QueuedValidVariant::::kill(); + QueuedSolutionBackings::::remove_all(None); + QueuedSolutionScore::::kill(); + }) + } + + // -- non-mutating methods. + + /// Return the `score` and `winner_count` of verifying solution. + /// + /// Assumes that all the corresponding pages of `QueuedSolutionBackings` exist, then it + /// computes the final score of the solution that is currently at the end of its + /// verification process. + /// + /// This solution corresponds to whatever is stored in the INVALID variant of + /// `QueuedSolution`. Recall that the score of this solution is not yet verified, so it + /// should never become `valid`. + pub(crate) fn compute_invalid_score() -> Result<(ElectionScore, u32), FeasibilityError> { + // ensure that this is only called when all pages are verified individually. + // TODO: this is a very EXPENSIVE, and perhaps unreasonable check. A partial solution + // could very well be valid. + if QueuedSolutionBackings::::iter_keys().count() != T::Pages::get() as usize { + return Err(FeasibilityError::Incomplete) + } + + let mut total_supports: BTreeMap = Default::default(); + for (who, PartialBackings { backers, total }) in + QueuedSolutionBackings::::iter().map(|(_, pb)| pb).flatten() + { + let mut entry = total_supports.entry(who).or_default(); + entry.total = entry.total.saturating_add(total); + entry.backers = entry.backers.saturating_add(backers); + + if entry.backers > T::MaxBackersPerWinner::get() { + return Err(FeasibilityError::TooManyBackings) + } + } + + let winner_count = total_supports.len() as u32; + let score = evaluate_support(total_supports.into_iter().map(|(_who, pb)| pb)); + + Ok((score, winner_count)) + } + + /// The score of the current best solution, if any. + pub(crate) fn queued_score() -> Option { + QueuedSolutionScore::::get() + } + + /// Get a page of the current queued (aka valid) solution. + pub(crate) fn get_queued_solution_page(page: PageIndex) -> Option>> { + match Self::valid() { + ValidSolution::X => QueuedSolutionX::::get(page), + ValidSolution::Y => QueuedSolutionY::::get(page), + } + } + + fn valid() -> ValidSolution { + QueuedValidVariant::::get() + } + + fn invalid() -> ValidSolution { + Self::valid().other() + } + } + + #[cfg(any(test, debug_assertions))] + impl QueuedSolution { + pub(crate) fn valid_iter() -> impl Iterator>)> { + match Self::valid() { + ValidSolution::X => QueuedSolutionX::::iter(), + ValidSolution::Y => QueuedSolutionY::::iter(), + } + } + + pub(crate) fn invalid_iter() -> impl Iterator>)> { + match Self::invalid() { + ValidSolution::X => QueuedSolutionX::::iter(), + ValidSolution::Y => QueuedSolutionY::::iter(), + } + } + + pub(crate) fn get_valid_page(page: PageIndex) -> Option>> { + match Self::valid() { + ValidSolution::X => QueuedSolutionX::::get(page), + ValidSolution::Y => QueuedSolutionY::::get(page), + } + } + + pub(crate) fn backing_iter() -> impl Iterator< + Item = (PageIndex, BoundedVec<(T::AccountId, PartialBackings), T::MaxWinnersPerPage>), + > { + QueuedSolutionBackings::::iter() + } + + /// Ensure that all the storage items managed by this struct are in `kill` state, meaning + /// that in the expect state after an election is OVER. + pub(crate) fn assert_killed() { + use frame_support::assert_storage_noop; + assert_storage_noop!(Self::kill()); + } + + /// Ensure this storage item group is in correct state. + pub(crate) fn sanity_check() -> Result<(), &'static str> { + // score is correct and better than min-score. + ensure!( + Pallet::::minimum_score() + .zip(Self::queued_score()) + .map_or(true, |(min_score, score)| score + .strict_threshold_better(min_score, Perbill::zero())), + "queued solution has weak score (min-score)" + ); + + if let Some(queued_score) = Self::queued_score() { + let mut backing_map: BTreeMap = BTreeMap::new(); + Self::valid_iter().map(|(_, supports)| supports).flatten().for_each( + |(who, support)| { + let mut entry = backing_map.entry(who).or_default(); + entry.total = entry.total.saturating_add(support.total); + }, + ); + let real_score = evaluate_support(backing_map.into_iter().map(|(_who, pb)| pb)); + ensure!(real_score == queued_score, "queued solution has wrong score"); + } + + // The number of existing keys in `QueuedSolutionBackings` must always match that of + // the INVALID variant. + ensure!( + QueuedSolutionBackings::::iter().count() == Self::invalid_iter().count(), + "incorrect number of backings pages", + ); + + if let Status::Nothing = StatusStorage::::get() { + ensure!(Self::invalid_iter().count() == 0, "dangling data in invalid variant"); + } + + Ok(()) + } + } + + /// The `X` variant of the current queued solution. Might be the valid one or not. + /// + /// The two variants of this storage item is to avoid the need of copying. Recall that once a + /// `VerifyingSolution` is being processed, it needs to write its partial supports *somewhere*. + /// Writing theses supports on top of a *good* queued supports is wrong, since we might bail. + /// Writing them to a bugger and copying at the ned is slightly better, but expensive. This flag + /// system is best of both worlds. + #[pallet::storage] + type QueuedSolutionX = StorageMap<_, Twox64Concat, PageIndex, SupportsOf>>; + #[pallet::storage] + /// The `Y` variant of the current queued solution. Might be the valid one or not. + type QueuedSolutionY = StorageMap<_, Twox64Concat, PageIndex, SupportsOf>>; + /// Pointer to the variant of [`QueuedSolutionX`] or [`QueuedSolutionY`] that is currently + /// valid. + #[pallet::storage] + type QueuedValidVariant = StorageValue<_, ValidSolution, ValueQuery>; + /// The `(amount, count)` of backings, divided per page. + /// + /// This is stored because in the last block of verification we need them to compute the score, + /// and check `MaxBackersPerWinner`. + /// + /// This can only ever live for the invalid variant of the solution. Once it is valid, we don't + /// need this information anymore; the score is already computed once in + /// [`QueuedSolutionScore`], and the backing counts are checked. + #[pallet::storage] + type QueuedSolutionBackings = StorageMap< + _, + Twox64Concat, + PageIndex, + BoundedVec<(T::AccountId, PartialBackings), T::MaxWinnersPerPage>, + >; + /// The score of the valid variant of [`QueuedSolution`]. + /// + /// This only ever lives for the `valid` variant. + #[pallet::storage] + type QueuedSolutionScore = StorageValue<_, ElectionScore>; + + /// The minimum score that each solution must attain in order to be considered feasible. + #[pallet::storage] + #[pallet::getter(fn minimum_score)] + pub(crate) type MinimumScore = StorageValue<_, ElectionScore>; + + /// Storage item for [`Status`]. + #[pallet::storage] + #[pallet::getter(fn status_storage)] + pub(crate) type StatusStorage = StorageValue<_, Status, ValueQuery>; + + #[pallet::pallet] + pub struct Pallet(PhantomData); + + #[pallet::call] + impl Pallet {} + + #[pallet::hooks] + impl Hooks> for Pallet { + fn integrity_test() { + // ensure that we have funneled some of our type parameters EXACTLY as-is to the + // verifier pallet. + assert_eq!(T::MaxWinnersPerPage::get(), ::MaxWinnersPerPage::get()); + assert_eq!( + T::MaxBackersPerWinner::get(), + ::MaxBackersPerWinner::get() + ); + } + + fn on_initialize(_n: BlockNumberFor) -> Weight { + Self::do_on_initialize() + } + } +} + +impl Pallet { + fn do_on_initialize() -> Weight { + if let Status::Ongoing(current_page) = Self::status_storage() { + let maybe_page_solution = + ::get_page(current_page); + + if maybe_page_solution.is_none() { + // the data provider has zilch, revert to a clean state, waiting for a new `start`. + sublog!( + error, + "verifier", + "T::SolutionDataProvider failed to deliver page {}. This is an expected error and should not happen.", + current_page, + ); + + QueuedSolution::::clear_invalid_and_backings(); + StatusStorage::::put(Status::Nothing); + T::SolutionDataProvider::report_result(VerificationResult::DataUnavailable); + + Self::deposit_event(Event::::VerificationDataUnavailable); + // TODO: weight + return Default::default(); + } + + let page_solution = maybe_page_solution.expect("Option checked to not be None; qed"); + let maybe_supports = Self::feasibility_check_page_inner(page_solution, current_page); + + sublog!( + debug, + "verifier", + "verified page {} of a solution, outcome = {:?}", + current_page, + maybe_supports.as_ref().map(|s| s.len()) + ); + + match maybe_supports { + Ok(supports) => { + Self::deposit_event(Event::::Verified(current_page, supports.len() as u32)); + QueuedSolution::::set_invalid_page(current_page, supports); + + if current_page > crate::Pallet::::lsp() { + // not last page, just tick forward. + StatusStorage::::put(Status::Ongoing(current_page.saturating_sub(1))); + } else { + // last page, finalize everything. Solution data provider must always have a + // score for us at this point. Not much point in reporting a result, we just + // assume default score, which will almost certainly fail and cause a proper + // cleanup of the pallet, which is what we want anyways. + let claimed_score = + T::SolutionDataProvider::get_score().defensive_unwrap_or_default(); + + // in both cases of the following match, we are not back to the nothing + // state. + StatusStorage::::put(Status::Nothing); + + match Self::finalize_async_verification(claimed_score) { + Ok(_) => { + T::SolutionDataProvider::report_result(VerificationResult::Queued); + }, + Err(_) => { + T::SolutionDataProvider::report_result( + VerificationResult::Rejected, + ); + // In case of any of the errors, kill the solution. + QueuedSolution::::clear_invalid_and_backings(); + }, + } + } + }, + Err(err) => { + // the page solution was invalid. + Self::deposit_event(Event::::VerificationFailed(current_page, err)); + StatusStorage::::put(Status::Nothing); + QueuedSolution::::clear_invalid_and_backings(); + T::SolutionDataProvider::report_result(VerificationResult::Rejected) + }, + } + } + + // TODO: weight + Default::default() + } + + fn do_verify_synchronous( + partial_solution: T::Solution, + claimed_score: ElectionScore, + page: PageIndex, + ) -> Result, FeasibilityError> { + // first, ensure this score will be good enough, even if valid.. + let _ = Self::ensure_score_quality(claimed_score)?; + + // then actually check feasibility... + // NOTE: `MaxBackersPerWinner` is also already checked here. + let supports = Self::feasibility_check_page_inner(partial_solution, page)?; + + // then check that the number of winners was exactly enough.. + let desired_targets = + crate::Snapshot::::desired_targets().ok_or(FeasibilityError::SnapshotUnavailable)?; + ensure!(supports.len() as u32 == desired_targets, FeasibilityError::WrongWinnerCount); + + // then check the score was truth.. + let truth_score = supports.evaluate(); + ensure!(truth_score == claimed_score, FeasibilityError::InvalidScore); + + // and finally queue the solution. + QueuedSolution::::force_set_single_page_valid(0, supports.clone(), truth_score); + + Ok(supports) + } + + /// Finalize an asynchronous verification. Checks the final score for correctness, and ensures + /// that it matches all of the criteria. + /// + /// This should only be called when all pages of an async verification are done. + /// + /// Returns: + /// - `Ok()` if everything is okay, at which point the valid variant of the queued solution will + /// be updated. Returns + /// - `Err(Feasibility)` if any of the last verification steps fail. + fn finalize_async_verification(claimed_score: ElectionScore) -> Result<(), FeasibilityError> { + let outcome = QueuedSolution::::compute_invalid_score() + .and_then(|(final_score, winner_count)| { + let desired_targets = crate::Snapshot::::desired_targets().unwrap(); + // claimed_score checked prior in seal_unverified_solution + match (final_score == claimed_score, winner_count == desired_targets) { + (true, true) => { + // all good, finalize this solution + // NOTE: must be before the call to `finalize_correct`. + Self::deposit_event(Event::::Queued( + final_score, + QueuedSolution::::queued_score(), + )); + QueuedSolution::::finalize_correct(final_score); + Ok(()) + }, + (false, true) => Err(FeasibilityError::InvalidScore), + (true, false) => Err(FeasibilityError::WrongWinnerCount), + (false, false) => Err(FeasibilityError::InvalidScore), + } + }) + .map_err(|err| { + sublog!(warn, "verifier", "Finalizing solution was invalid due to {:?}.", err); + // and deposit an event about it. + Self::deposit_event(Event::::VerificationFailed(0, err.clone())); + err + }); + sublog!(debug, "verifier", "finalize verification outcome: {:?}", outcome); + outcome + } + + /// Ensure that the given score is: + /// + /// - better than the queued solution, if one exists. + /// - greater than the minimum untrusted score. + pub(crate) fn ensure_score_quality(score: ElectionScore) -> Result<(), FeasibilityError> { + let is_improvement = ::queued_score().map_or(true, |best_score| { + score.strict_threshold_better(best_score, T::SolutionImprovementThreshold::get()) + }); + ensure!(is_improvement, FeasibilityError::ScoreTooLow); + + let is_greater_than_min_trusted = Self::minimum_score() + .map_or(true, |min_score| score.strict_threshold_better(min_score, Perbill::zero())); + ensure!(is_greater_than_min_trusted, FeasibilityError::ScoreTooLow); + + Ok(()) + } + + /// Do the full feasibility check: + /// + /// - check all edges. + /// - checks `MaxBackersPerWinner` to be respected IN THIS PAGE. + /// - checks the number of winners to be less than or equal to `DesiredTargets` IN THIS PAGE + /// ONLY. + pub(super) fn feasibility_check_page_inner( + partial_solution: SolutionOf, + page: PageIndex, + ) -> Result, FeasibilityError> { + // Read the corresponding snapshots. + let snapshot_targets = + crate::Snapshot::::targets().ok_or(FeasibilityError::SnapshotUnavailable)?; + let snapshot_voters = + crate::Snapshot::::voters(page).ok_or(FeasibilityError::SnapshotUnavailable)?; + + // ----- Start building. First, we need some closures. + let cache = helpers::generate_voter_cache::(&snapshot_voters); + let voter_at = helpers::voter_at_fn::(&snapshot_voters); + let target_at = helpers::target_at_fn::(&snapshot_targets); + let voter_index = helpers::voter_index_fn_usize::(&cache); + + // Then convert solution -> assignment. This will fail if any of the indices are + // gibberish. + let assignments = partial_solution + .into_assignment(voter_at, target_at) + .map_err::(Into::into)?; + + // Ensure that assignments are all correct. + let _ = assignments + .iter() + .map(|ref assignment| { + // Check that assignment.who is actually a voter (defensive-only). NOTE: while + // using the index map from `voter_index` is better than a blind linear search, + // this *still* has room for optimization. Note that we had the index when we + // did `solution -> assignment` and we lost it. Ideal is to keep the index + // around. + + // Defensive-only: must exist in the snapshot. + let snapshot_index = + voter_index(&assignment.who).ok_or(FeasibilityError::InvalidVoter)?; + // Defensive-only: index comes from the snapshot, must exist. + let (_voter, _stake, targets) = + snapshot_voters.get(snapshot_index).ok_or(FeasibilityError::InvalidVoter)?; + debug_assert!(*_voter == assignment.who); + + // Check that all of the targets are valid based on the snapshot. + if assignment.distribution.iter().any(|(t, _)| !targets.contains(t)) { + return Err(FeasibilityError::InvalidVote) + } + Ok(()) + }) + .collect::>()?; + + // ----- Start building support. First, we need one more closure. + let stake_of = helpers::stake_of_fn::(&snapshot_voters, &cache); + + // This might fail if the normalization fails. Very unlikely. See `integrity_test`. + let staked_assignments = + sp_npos_elections::assignment_ratio_to_staked_normalized(assignments, stake_of) + .map_err::(Into::into)?; + + let supports = sp_npos_elections::to_supports(&staked_assignments); + + // Check the maximum number of backers per winner. If this is a single-page solution, this + // is enough to check `MaxBackersPerWinner`. Else, this is just a heuristic, and needs to be + // checked again at the end (via `QueuedSolutionBackings`). + ensure!( + supports + .iter() + .all(|(_, s)| (s.voters.len() as u32) <= T::MaxBackersPerWinner::get()), + FeasibilityError::TooManyBackings + ); + + // Ensure some heuristics. These conditions must hold in the **entire** support, this is + // just a single page. But, they must hold in a single page as well. + let desired_targets = + crate::Snapshot::::desired_targets().ok_or(FeasibilityError::SnapshotUnavailable)?; + ensure!((supports.len() as u32) <= desired_targets, FeasibilityError::WrongWinnerCount); + + // almost-defensive-only: `MaxBackersPerWinner` is already checked. A sane value of + // `MaxWinnersPerPage` should be more than any possible value of `desired_targets()`, which + // is ALSO checked, so this conversion can almost never fail. + let bounded_supports = + supports.try_into().map_err(|_| FeasibilityError::WrongWinnerCount)?; + Ok(bounded_supports) + } + + #[cfg(debug_assertions)] + pub(crate) fn sanity_check() -> Result<(), &'static str> { + QueuedSolution::::sanity_check() + } +} + +impl Verifier for Pallet { + type AccountId = T::AccountId; + type Solution = SolutionOf; + type MaxBackersPerWinner = T::MaxBackersPerWinner; + type MaxWinnersPerPage = T::MaxWinnersPerPage; + + fn set_minimum_score(score: ElectionScore) { + MinimumScore::::put(score); + } + + fn ensure_claimed_score_improves(claimed_score: ElectionScore) -> bool { + Self::ensure_score_quality(claimed_score).is_ok() + } + + fn queued_score() -> Option { + QueuedSolution::::queued_score() + } + + fn kill() { + QueuedSolution::::kill(); + >::put(Status::Nothing); + } + + fn get_queued_solution_page(page: PageIndex) -> Option> { + QueuedSolution::::get_queued_solution_page(page) + } + + fn verify_synchronous( + partial_solution: Self::Solution, + claimed_score: ElectionScore, + page: PageIndex, + ) -> Result, FeasibilityError> { + let maybe_current_score = Self::queued_score(); + match Self::do_verify_synchronous(partial_solution, claimed_score, page) { + Ok(supports) => { + sublog!(info, "verifier", "queued a sync solution with score {:?}.", claimed_score); + Self::deposit_event(Event::::Verified(page, supports.len() as u32)); + Self::deposit_event(Event::::Queued(claimed_score, maybe_current_score)); + Ok(supports) + }, + Err(fe) => { + sublog!(warn, "verifier", "sync verification failed due to {:?}.", fe); + Self::deposit_event(Event::::VerificationFailed(page, fe.clone())); + Err(fe) + }, + } + } + + fn feasibility_check_page( + partial_solution: Self::Solution, + page: PageIndex, + ) -> Result, FeasibilityError> { + Self::feasibility_check_page_inner(partial_solution, page) + } +} + +impl AsynchronousVerifier for Pallet { + type SolutionDataProvider = T::SolutionDataProvider; + + fn status() -> Status { + Pallet::::status_storage() + } + + fn start() -> Result<(), &'static str> { + if let Status::Nothing = Self::status() { + let claimed_score = Self::SolutionDataProvider::get_score().unwrap_or_default(); + if Self::ensure_score_quality(claimed_score).is_err() { + // don't do anything, report back that this solution was garbage. + Self::deposit_event(Event::::VerificationFailed( + crate::Pallet::::msp(), + FeasibilityError::ScoreTooLow, + )); + T::SolutionDataProvider::report_result(VerificationResult::Rejected); + // Despite being an instant-reject, this was a successful `start` operation. + Ok(()) + } else { + StatusStorage::::put(Status::Ongoing(crate::Pallet::::msp())); + Ok(()) + } + } else { + sublog!(warn, "verifier", "start signal received while busy. This will be ignored."); + Err("verification ongoing") + } + } + + fn stop() { + sublog!(warn, "verifier", "stop signal received. clearing everything."); + + // we clear any ongoing solution's no been verified in any case, although this should only + // exist if we were doing something. + #[cfg(debug_assertions)] + assert!( + !matches!(StatusStorage::::get(), Status::Ongoing(_)) || + (matches!(StatusStorage::::get(), Status::Ongoing(_)) && + QueuedSolution::::invalid_iter().count() > 0) + ); + QueuedSolution::::clear_invalid_and_backings_unchecked(); + + // we also mutate the status back to doing nothing. + StatusStorage::::mutate(|old| { + if matches!(old, Status::Ongoing(_)) { + T::SolutionDataProvider::report_result(VerificationResult::Rejected) + } + *old = Status::Nothing; + }); + } +} diff --git a/substrate/frame/election-provider-multi-block/src/verifier/mod.rs b/substrate/frame/election-provider-multi-block/src/verifier/mod.rs new file mode 100644 index 0000000000000..92690e9457862 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/verifier/mod.rs @@ -0,0 +1,273 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 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 Verifier Pallet +//! +//! ### *Feasibility* Check +//! +//! Before explaining the pallet itself, it should be explained what a *verification* even means. +//! Verification of a solution page ([`crate::Config::Solution`]) includes the process of checking +//! all of its edges against a snapshot to be correct. For instance, all voters that are presented +//! in a solution page must have actually voted for the winner that they are backing, based on the +//! snapshot kept in the parent pallet. +//! +//! After checking all of the edges, a handful of other checks are performed: +//! +//! 1. Check that the total number of winners is sufficient. +//! 2. Check that the claimed score ([`sp_npos_elections::ElectionScore`]) is correct, +//! 3. and more than the minimum score that can be specified via [`Verifier::set_minimum_score`]. +//! 4. Check that all of the bounds of the solution are respected, namely +//! [`Verifier::MaxBackersPerWinner`]. +//! +//! Note that the common factor of all of these checks is that they can ONLY be checked after all +//! pages are already verified. So, In the case of a multi-page verification, these checks are only +//! performed after all pages have already been verified. +//! +//! The errors that can arise while performing the feasibility check are encapsulated in +//! [`FeasibilityError`]. +//! +//! ## Modes of Verification +//! +//! The verifier pallet provide two modes of functionality: +//! +//! 1. Single-page, synchronous verification. This is useful in the context of single-page, +//! emergency, or unsigned solutions that need to be verified on the fly. +//! 2. Multi-page, asynchronous verification. This is useful in the context of multi-page, signed +//! solutions. +//! +//! Both of this, plus some helper functions, is exposed via the [`Verifier`] trait. +//! +//! ### Synchronous verification +//! +//! ### Asynchronous verification +//! +//! ## Queued Solution +//! +//! once a solution has been verified, it is called a *queued solution*. It is sitting in a single +//! spot queue, waiting for either of: +//! +//! 1. being challenged and potentially replaced by better solution, if any. +//! 2. being exported as the final outcome of the election. +//! +//! ## Future Plans: +//! +//! - TODO: allow less winners, and backport it. + +mod impls; +#[cfg(test)] +mod tests; + +// internal imports +use crate::SupportsOf; +use frame_election_provider_support::PageIndex; +use frame_support::pallet_prelude::*; +use frame_system::pallet_prelude::*; +use sp_npos_elections::ElectionScore; +use sp_runtime::RuntimeDebug; +use sp_std::{fmt::Debug, prelude::*}; + +pub use impls::{pallet::*, Status}; + +/// Errors that can happen in the feasibility check. +#[derive(Debug, Eq, PartialEq, codec::Encode, codec::Decode, scale_info::TypeInfo, Clone)] +pub enum FeasibilityError { + /// Wrong number of winners presented. + WrongWinnerCount, + /// The snapshot is not available. + /// + /// Kinda defensive: The pallet should technically never attempt to do a feasibility check + /// when no snapshot is present. + SnapshotUnavailable, + /// A vote is invalid. + InvalidVote, + /// A voter is invalid. + InvalidVoter, + /// A winner is invalid. + InvalidWinner, + /// The given score was invalid. + InvalidScore, + /// The provided round is incorrect. + InvalidRound, + /// Solution does not have a good enough score. + ScoreTooLow, + /// A single target has too many backings + TooManyBackings, + /// Internal error from the election crate. + #[codec(skip)] + NposElection(sp_npos_elections::Error), + /// The solution is incomplete, it has too few pages. + /// + /// This is (somewhat) synonym to `WrongPageCount` in other places. + Incomplete, +} + +impl From for FeasibilityError { + fn from(e: sp_npos_elections::Error) -> Self { + FeasibilityError::NposElection(e) + } +} + +/// The interface of something that can verify solutions for other sub-pallets in the multi-block +/// election pallet-network. +pub trait Verifier { + /// The solution type. + type Solution; + /// The account if type. + type AccountId; + + /// Maximum number of backers that each winner could have. + /// + /// In multi-block verification, this can only be checked after all pages are known to be valid + /// and are already checked. + type MaxBackersPerWinner: frame_support::traits::Get; + + /// Maximum number of winners that can be represented in each page. + /// + /// A reasonable value for this should be the maximum number of winners that the election user + /// (e.g. the staking pallet) could ever desire. + type MaxWinnersPerPage: frame_support::traits::Get; + + /// Set the minimum score that is acceptable for any solution. + /// + /// Henceforth, all solutions must have at least this degree of quality, single-page or + /// multi-page. + fn set_minimum_score(score: ElectionScore); + + /// The score of the current best solution. `None` if there is none. + fn queued_score() -> Option; + + /// Check if the claimed score is sufficient to challenge the current queued solution, if any. + fn ensure_claimed_score_improves(claimed_score: ElectionScore) -> bool; + + /// Clear all storage items, there's nothing else to do until further notice. + fn kill(); + + /// Get a single page of the best verified solution, if any. + /// + /// It is the responsibility of the call site to call this function with all appropriate + /// `page` arguments. + fn get_queued_solution_page(page: PageIndex) -> Option>; + + /// Perform the feasibility check on the given single-page solution. + /// + /// This will perform: + /// + /// 1. feasibility-check + /// 2. claimed score is correct and an improvement. + /// 3. bounds are respected + /// + /// Corresponding snapshot (represented by `page`) is assumed to be available. + /// + /// If all checks pass, the solution is also queued. + fn verify_synchronous( + partial_solution: Self::Solution, + claimed_score: ElectionScore, + page: PageIndex, + ) -> Result, FeasibilityError>; + + /// Just perform a single-page feasibility-check, based on the standards of this pallet, without + /// writing anything to anywhere. + /// + /// No score check is part of this. + fn feasibility_check_page( + partial_solution: Self::Solution, + page: PageIndex, + ) -> Result, FeasibilityError>; +} + +/// Simple enum to encapsulate the result of the verification of a candidate solution. +#[derive(Clone, Copy, RuntimeDebug)] +#[cfg_attr(test, derive(PartialEq, Eq))] +pub enum VerificationResult { + /// Solution is valid and is queued. + Queued, + /// Solution is rejected, for whichever of the multiple reasons that it could be. + Rejected, + /// The data needed (solution pages or the score) was unavailable. This should rarely happen. + DataUnavailable, +} + +/// Something that can provide candidate solutions to the verifier. +/// +/// In reality, this can be implemented by the [`crate::signed::Pallet`], where signed solutions are +/// queued and sorted based on claimed score, and they are put forth one by one, from best to worse. +pub trait SolutionDataProvider { + /// The opaque solution type. + type Solution; + + /// Return the `page`th page of the current best solution that the data provider has in store. + /// + /// If no candidate solutions are available, then None is returned. + fn get_page(page: PageIndex) -> Option; + + /// Get the claimed score of the current best solution. + fn get_score() -> Option; + + /// Hook to report back the results of the verification of the current candidate solution that + /// is being exposed via [`get_page`] and [`get_score`]. + /// + /// Every time that this is called, the verifier [`AsynchronousVerifier`] goes back to the + /// [`Status::Nothing`] state, and it is the responsibility of [`Self`] to call `start` again, + /// if desired. + fn report_result(result: VerificationResult); +} + +/// Something that can do the verification asynchronously. +pub trait AsynchronousVerifier: Verifier { + /// The data provider that can provide the candidate solution, and to whom we report back the + /// results. + type SolutionDataProvider: SolutionDataProvider; + + /// Get the current stage of the verification process. + fn status() -> Status; + + /// Start a verification process. + /// + /// Returns `Ok(())` if verification started successfully, and `Err(..)` if a verification is + /// already ongoing and therefore a new one cannot be started. + /// + /// From the coming block onwards, the verifier will start and fetch the relevant information + /// and solution pages from [`SolutionDataProvider`]. It is expected that the + /// [`SolutionDataProvider`] is ready before calling [`start`]. + /// + /// Pages of the solution are fetched sequentially and in order from [`SolutionDataProvider`], + /// from `msp` to `lsp`. + /// + /// This ends in either of the two: + /// + /// 1. All pages, including the final checks (like score and other facts that can only be + /// derived from a full solution) are valid and the solution is verified. The solution is + /// queued and is ready for further export. + /// 2. The solution checks verification at one of the steps. Nothing is stored inside the + /// verifier pallet and all intermediary data is removed. + /// + /// In both cases, the [`SolutionDataProvider`] is informed via + /// [`SolutionDataProvider::report_result`]. It is sensible for the data provide to call `start` + /// again if the verification has failed, and nothing otherwise. Indeed, the + /// [`SolutionDataProvider`] must adjust its internal state such that it returns a new candidate + /// solution after each failure. + fn start() -> Result<(), &'static str>; + + /// Stop the verification. + /// + /// This is a force-stop operation, and should only be used in extreme cases where the + /// [`SolutionDataProvider`] wants to suddenly bail-out. + /// + /// An implementation should make sure that no loose ends remain state-wise, and everything is + /// cleaned. + fn stop(); +} diff --git a/substrate/frame/election-provider-multi-block/src/verifier/tests.rs b/substrate/frame/election-provider-multi-block/src/verifier/tests.rs new file mode 100644 index 0000000000000..a5f05e42ca5a1 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/verifier/tests.rs @@ -0,0 +1,1207 @@ +// 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::{ + mock::*, + types::*, + verifier::{impls::Status, *}, + *, +}; + +use frame_election_provider_support::Support; +use frame_support::{assert_noop, assert_ok}; +use sp_runtime::traits::Bounded; + +mod feasibility_check { + use super::*; + + #[test] + fn missing_snapshot() { + ExtBuilder::verifier().build_unchecked().execute_with(|| { + // create snapshot just so that we can create a solution.. + roll_to_snapshot_created(); + let paged = mine_full_solution().unwrap(); + + // ..remove the only page of the target snapshot. + crate::Snapshot::::remove_target_page(0); + + assert_noop!( + VerifierPallet::feasibility_check_page_inner(paged.solution_pages[0].clone(), 0), + FeasibilityError::SnapshotUnavailable + ); + }); + + ExtBuilder::verifier().pages(2).build_unchecked().execute_with(|| { + roll_to_snapshot_created(); + let paged = mine_full_solution().unwrap(); + + // ..remove just one of the pages of voter snapshot that is relevant. + crate::Snapshot::::remove_voter_page(0); + + assert_noop!( + VerifierPallet::feasibility_check_page_inner(paged.solution_pages[0].clone(), 0), + FeasibilityError::SnapshotUnavailable + ); + }); + + ExtBuilder::verifier().pages(2).build_unchecked().execute_with(|| { + roll_to_snapshot_created(); + let paged = mine_full_solution().unwrap(); + + // ..removing this page is not important, because we check page 0. + crate::Snapshot::::remove_voter_page(1); + + assert_ok!(VerifierPallet::feasibility_check_page_inner( + paged.solution_pages[0].clone(), + 0 + )); + }); + + ExtBuilder::verifier().pages(2).build_unchecked().execute_with(|| { + roll_to_snapshot_created(); + let paged = mine_full_solution().unwrap(); + + // `DesiredTargets` missing is also an error + crate::Snapshot::::kill_desired_targets(); + + assert_noop!( + VerifierPallet::feasibility_check_page_inner(paged.solution_pages[0].clone(), 0), + FeasibilityError::SnapshotUnavailable + ); + }); + + ExtBuilder::verifier().pages(2).build_unchecked().execute_with(|| { + roll_to_snapshot_created(); + let paged = mine_full_solution().unwrap(); + + // `DesiredTargets` is not checked here. + crate::Snapshot::::remove_target_page(0); + + assert_noop!( + VerifierPallet::feasibility_check_page_inner(paged.solution_pages[1].clone(), 0), + FeasibilityError::SnapshotUnavailable + ); + }); + } + + #[test] + fn winner_indices_single_page_must_be_in_bounds() { + ExtBuilder::verifier().pages(1).desired_targets(2).build_and_execute(|| { + roll_to_snapshot_created(); + let mut paged = mine_full_solution().unwrap(); + assert_eq!(crate::Snapshot::::targets().unwrap().len(), 4); + // ----------------------------------------------------^^ valid range is [0..3]. + + // Swap all votes from 3 to 4. here are only 4 targets, so index 4 is invalid. + paged.solution_pages[0] + .votes1 + .iter_mut() + .filter(|(_, t)| *t == TargetIndex::from(3u16)) + .for_each(|(_, t)| *t += 1); + + assert_noop!( + VerifierPallet::feasibility_check_page_inner(paged.solution_pages[0].clone(), 0), + FeasibilityError::NposElection(sp_npos_elections::Error::SolutionInvalidIndex) + ); + }) + } + + #[test] + fn voter_indices_per_page_must_be_in_bounds() { + ExtBuilder::verifier() + .pages(1) + .voter_per_page(Bounded::max_value()) + .desired_targets(2) + .build_and_execute(|| { + roll_to_snapshot_created(); + let mut paged = mine_full_solution().unwrap(); + + assert_eq!(crate::Snapshot::::voters(0).unwrap().len(), 12); + // ------------------------------------------------^^ valid range is [0..11] in page + // 0. + + // Check that there is an index 11 in votes1, and flip to 12. There are only 12 + // voters, so index 12 is invalid. + assert!( + paged.solution_pages[0] + .votes1 + .iter_mut() + .filter(|(v, _)| *v == VoterIndex::from(11u32)) + .map(|(v, _)| *v = 12) + .count() > 0 + ); + assert_noop!( + VerifierPallet::feasibility_check_page_inner( + paged.solution_pages[0].clone(), + 0 + ), + FeasibilityError::NposElection(sp_npos_elections::Error::SolutionInvalidIndex), + ); + }) + } + + #[test] + fn voter_must_have_same_targets_as_snapshot() { + ExtBuilder::verifier() + .pages(1) + .voter_per_page(Bounded::max_value()) + .desired_targets(2) + .build_and_execute(|| { + roll_to_snapshot_created(); + let mut paged = mine_full_solution().unwrap(); + + // First, check that voter at index 11 (40) actually voted for 3 (40) -- this is + // self vote. Then, change the vote to 2 (30). + + assert_eq!( + paged.solution_pages[0] + .votes1 + .iter_mut() + .filter(|(v, t)| *v == 11 && *t == 3) + .map(|(_, t)| *t = 2) + .count(), + 1, + ); + assert_noop!( + VerifierPallet::feasibility_check_page_inner( + paged.solution_pages[0].clone(), + 0 + ), + FeasibilityError::InvalidVote, + ); + }) + } + + #[test] + fn heuristic_max_backers_per_winner_per_page() { + ExtBuilder::verifier().max_backing_per_target(2).build_and_execute(|| { + roll_to_snapshot_created(); + + // these votes are all valid, but some dude has 3 supports in a single page. + let solution = solution_from_supports( + vec![(40, Support { total: 30, voters: vec![(2, 10), (3, 10), (4, 10)] })], + // all these voters are in page of the snapshot, the msp! + 2, + ); + + assert_noop!( + VerifierPallet::feasibility_check_page_inner(solution, 2), + FeasibilityError::TooManyBackings, + ); + }) + } + + #[test] + fn heuristic_desired_target_check_per_page() { + ExtBuilder::verifier().desired_targets(2).build_and_execute(|| { + roll_to(25); + assert_full_snapshot(); + + // all of these votes are valid, but this solution is already presenting 3 winners, + // while we just one 2. + let solution = solution_from_supports( + vec![ + (10, Support { total: 30, voters: vec![(4, 2)] }), + (20, Support { total: 30, voters: vec![(4, 2)] }), + (40, Support { total: 30, voters: vec![(4, 6)] }), + ], + // all these voters are in page 2 of the snapshot, the msp! + 2, + ); + + assert_noop!( + VerifierPallet::feasibility_check_page_inner(solution, 2), + FeasibilityError::WrongWinnerCount, + ); + }) + } +} + +mod async_verification { + use frame_support::{assert_storage_noop, bounded_vec}; + + use super::*; + // disambiguate event + use crate::verifier::Event; + + #[test] + fn basic_single_verification_works() { + ExtBuilder::verifier().pages(1).build_and_execute(|| { + // load a solution after the snapshot has been created. + roll_to_snapshot_created(); + + let solution = mine_full_solution().unwrap(); + load_mock_signed_and_start(solution.clone()); + + // now let it verify + roll_next(); + + // It done after just one block. + assert_eq!(VerifierPallet::status(), Status::Nothing); + assert_eq!( + verifier_events(), + vec![ + Event::::Verified(0, 2), + Event::::Queued(solution.score, None) + ] + ); + assert_eq!(MockSignedResults::get(), vec![VerificationResult::Queued]); + }); + } + + #[test] + fn basic_multi_verification_works() { + ExtBuilder::verifier().pages(3).build_and_execute(|| { + // load a solution after the snapshot has been created. + roll_to_snapshot_created(); + + let solution = mine_full_solution().unwrap(); + // ------------- ^^^^^^^^^^^^ + + load_mock_signed_and_start(solution.clone()); + assert_eq!(VerifierPallet::status(), Status::Ongoing(2)); + assert_eq!(QueuedSolution::::valid_iter().count(), 0); + + // now let it verify + roll_next(); + assert_eq!(VerifierPallet::status(), Status::Ongoing(1)); + assert_eq!(verifier_events(), vec![Event::::Verified(2, 2)]); + // 1 page verified, stored as invalid. + assert_eq!(QueuedSolution::::invalid_iter().count(), 1); + + roll_next(); + assert_eq!(VerifierPallet::status(), Status::Ongoing(0)); + assert_eq!( + verifier_events(), + vec![Event::::Verified(2, 2), Event::::Verified(1, 2),] + ); + // 2 pages verified, stored as invalid. + assert_eq!(QueuedSolution::::invalid_iter().count(), 2); + + // nothing is queued yet. + assert_eq!(MockSignedResults::get(), vec![]); + assert_eq!(QueuedSolution::::valid_iter().count(), 0); + assert!(QueuedSolution::::queued_score().is_none()); + + // last block. + roll_next(); + assert_eq!(VerifierPallet::status(), Status::Nothing); + assert_eq!( + verifier_events(), + vec![ + Event::::Verified(2, 2), + Event::::Verified(1, 2), + Event::::Verified(0, 2), + Event::::Queued(solution.score, None), + ] + ); + assert_eq!(MockSignedResults::get(), vec![VerificationResult::Queued]); + + // a solution has been queued + assert_eq!(QueuedSolution::::valid_iter().count(), 3); + assert!(QueuedSolution::::queued_score().is_some()); + }); + } + + #[test] + fn basic_multi_verification_partial() { + ExtBuilder::verifier().pages(3).build_and_execute(|| { + // load a solution after the snapshot has been created. + roll_to_snapshot_created(); + + let solution = mine_solution(2).unwrap(); + // -------------------------^^^ + + load_mock_signed_and_start(solution.clone()); + + assert_eq!(VerifierPallet::status(), Status::Ongoing(2)); + assert_eq!(QueuedSolution::::valid_iter().count(), 0); + + // now let it verify + roll_next(); + assert_eq!(VerifierPallet::status(), Status::Ongoing(1)); + assert_eq!(verifier_events(), vec![Event::::Verified(2, 2)]); + // 1 page verified, stored as invalid. + assert_eq!(QueuedSolution::::invalid_iter().count(), 1); + + roll_next(); + assert_eq!(VerifierPallet::status(), Status::Ongoing(0)); + assert_eq!( + verifier_events(), + vec![Event::::Verified(2, 2), Event::::Verified(1, 2),] + ); + // 2 page verified, stored as invalid. + assert_eq!(QueuedSolution::::invalid_iter().count(), 2); + + // nothing is queued yet. + assert_eq!(MockSignedResults::get(), vec![]); + assert_eq!(QueuedSolution::::valid_iter().count(), 0); + assert!(QueuedSolution::::queued_score().is_none()); + + roll_next(); + assert_eq!(VerifierPallet::status(), Status::Nothing); + + assert_eq!( + verifier_events(), + vec![ + Event::::Verified(2, 2), + Event::::Verified(1, 2), + // this is a partial solution, no one in this page (lsp). + Event::::Verified(0, 0), + Event::::Queued(solution.score, None), + ] + ); + + // a solution has been queued + assert_eq!(MockSignedResults::get(), vec![VerificationResult::Queued]); + assert_eq!(QueuedSolution::::valid_iter().count(), 3); + assert!(QueuedSolution::::queued_score().is_some()); + + // page 0 is empty.. + assert_eq!(QueuedSolution::::get_valid_page(0).unwrap().len(), 0); + // .. the other two are not. + assert_eq!(QueuedSolution::::get_valid_page(1).unwrap().len(), 2); + assert_eq!(QueuedSolution::::get_valid_page(2).unwrap().len(), 2); + }); + } + + #[test] + fn solution_data_provider_failing_initial() { + ExtBuilder::verifier().build_and_execute(|| { + // not super important, but anyways.. + roll_to_snapshot_created(); + + // The solution data provider is empty. + assert_eq!(SignedPhaseSwitch::get(), SignedSwitch::Mock); + assert_eq!(MockSignedNextSolution::get(), None); + + // nothing happens.. + assert_eq!(VerifierPallet::status(), Status::Nothing); + assert_ok!(::start()); + assert_eq!(VerifierPallet::status(), Status::Ongoing(2)); + + roll_next(); + + // we instantly stop. + assert_eq!(verifier_events(), vec![Event::::VerificationDataUnavailable]); + assert_eq!(VerifierPallet::status(), Status::Nothing); + assert!(QueuedSolution::::invalid_iter().count().is_zero()); + assert!(QueuedSolution::::backing_iter().count().is_zero()); + + // and we report invalid back. + assert_eq!(MockSignedResults::get(), vec![VerificationResult::DataUnavailable]); + }); + } + + #[test] + fn solution_data_provider_failing_midway() { + ExtBuilder::verifier().build_and_execute(|| { + roll_to_snapshot_created(); + + let solution = mine_full_solution().unwrap(); + load_mock_signed_and_start(solution.clone()); + + assert_eq!(VerifierPallet::status(), Status::Ongoing(2)); + + // now let it verify. first one goes fine. + roll_next(); + assert_eq!(VerifierPallet::status(), Status::Ongoing(1)); + assert_eq!(verifier_events(), vec![Event::::Verified(2, 2)]); + assert_eq!(MockSignedResults::get(), vec![]); + + // 1 page verified, stored as invalid. + assert_eq!(QueuedSolution::::invalid_iter().count(), 1); + assert_eq!(QueuedSolution::::backing_iter().count(), 1); + assert_eq!(QueuedSolution::::valid_iter().count(), 0); + + // suddenly clear this guy. + MockSignedNextSolution::set(None); + MockSignedNextScore::set(None); + + roll_next(); + + // we instantly stop. + assert_eq!( + verifier_events(), + vec![ + Event::::Verified(2, 2), + Event::::VerificationDataUnavailable + ] + ); + assert_eq!(VerifierPallet::status(), Status::Nothing); + assert_eq!(QueuedSolution::::invalid_iter().count(), 0); + assert_eq!(QueuedSolution::::valid_iter().count(), 0); + assert_eq!(QueuedSolution::::backing_iter().count(), 0); + + // and we report invalid back. + assert_eq!(MockSignedResults::get(), vec![VerificationResult::DataUnavailable]); + }) + } + + #[test] + fn rejects_new_verification_via_start_if_ongoing() { + ExtBuilder::verifier().build_and_execute(|| { + roll_to_snapshot_created(); + + let solution = mine_full_solution().unwrap(); + load_mock_signed_and_start(solution.clone()); + + assert_eq!(VerifierPallet::status(), Status::Ongoing(2)); + + // nada + assert_noop!(::start(), "verification ongoing"); + + // now let it verify. first one goes fine. + roll_next(); + assert_eq!(VerifierPallet::status(), Status::Ongoing(1)); + assert_eq!(verifier_events(), vec![Event::::Verified(2, 2)]); + assert_eq!(MockSignedResults::get(), vec![]); + + // retry, still nada. + assert_noop!(::start(), "verification ongoing"); + }) + } + + #[test] + fn stop_clears_everything() { + ExtBuilder::verifier().build_and_execute(|| { + roll_to_snapshot_created(); + + let solution = mine_full_solution().unwrap(); + load_mock_signed_and_start(solution.clone()); + + assert_eq!(VerifierPallet::status(), Status::Ongoing(2)); + + roll_next(); + assert_eq!(VerifierPallet::status(), Status::Ongoing(1)); + assert_eq!(verifier_events(), vec![Event::::Verified(2, 2)]); + + roll_next(); + assert_eq!(VerifierPallet::status(), Status::Ongoing(0)); + assert_eq!( + verifier_events(), + vec![Event::::Verified(2, 2), Event::::Verified(1, 2)] + ); + + // now suddenly, we stop + ::stop(); + assert_eq!(VerifierPallet::status(), Status::Nothing); + + // everything is cleared. + assert_eq!(QueuedSolution::::invalid_iter().count(), 0); + assert_eq!(QueuedSolution::::valid_iter().count(), 0); + assert_eq!(QueuedSolution::::backing_iter().count(), 0); + + // and we report invalid back that something was rejected. + assert_eq!(MockSignedResults::get(), vec![VerificationResult::Rejected]); + }) + } + + #[test] + fn weak_valid_solution_is_insta_rejected() { + ExtBuilder::verifier().build_and_execute(|| { + roll_to_snapshot_created(); + + let paged = mine_full_solution().unwrap(); + load_mock_signed_and_start(paged.clone()); + let _ = roll_to_full_verification(); + + assert_eq!( + verifier_events(), + vec![ + Event::Verified(2, 2), + Event::Verified(1, 2), + Event::Verified(0, 2), + Event::Queued(paged.score, None) + ] + ); + assert_eq!(MockSignedResults::get(), vec![VerificationResult::Queued]); + + // good boi, but you are too weak. This solution also does not have the full pages, + // which is also fine. See `basic_multi_verification_partial`. + let weak_page_partial = + solution_from_supports(vec![(10, Support { total: 10, voters: vec![(1, 10)] })], 2); + let weak_paged = PagedRawSolution:: { + solution_pages: bounded_vec![weak_page_partial], + score: ElectionScore { minimal_stake: 10, sum_stake: 10, sum_stake_squared: 100 }, + ..Default::default() + }; + + load_mock_signed_and_start(weak_paged.clone()); + // this is insta-rejected, no need to proceed any more blocks. + + assert_eq!( + verifier_events(), + vec![ + Event::Verified(2, 2), + Event::Verified(1, 2), + Event::Verified(0, 2), + Event::Queued(paged.score, None), + Event::VerificationFailed(2, FeasibilityError::ScoreTooLow) + ] + ); + + assert_eq!( + MockSignedResults::get(), + vec![VerificationResult::Queued, VerificationResult::Rejected] + ); + }) + } + + #[test] + fn better_valid_solution_replaces() { + ExtBuilder::verifier().build_and_execute(|| { + roll_to_snapshot_created(); + + // a weak one, which we will still accept. + let weak_page_partial = solution_from_supports( + vec![ + (10, Support { total: 10, voters: vec![(1, 10)] }), + (20, Support { total: 10, voters: vec![(4, 10)] }), + ], + 2, + ); + let weak_paged = PagedRawSolution:: { + solution_pages: bounded_vec![weak_page_partial], + score: ElectionScore { minimal_stake: 10, sum_stake: 20, sum_stake_squared: 200 }, + ..Default::default() + }; + + load_mock_signed_and_start(weak_paged.clone()); + let _ = roll_to_full_verification(); + + assert_eq!( + verifier_events(), + vec![ + Event::Verified(2, 2), + Event::Verified(1, 0), // note: partial solution! + Event::Verified(0, 0), // note: partial solution! + Event::Queued(weak_paged.score, None) + ] + ); + assert_eq!(MockSignedResults::get(), vec![VerificationResult::Queued]); + + let paged = mine_full_solution().unwrap(); + load_mock_signed_and_start(paged.clone()); + let _ = roll_to_full_verification(); + + assert_eq!( + verifier_events(), + vec![ + Event::Verified(2, 2), + Event::Verified(1, 0), + Event::Verified(0, 0), + Event::Queued(weak_paged.score, None), + Event::Verified(2, 2), + Event::Verified(1, 2), + Event::Verified(0, 2), + Event::Queued(paged.score, Some(weak_paged.score)) + ] + ); + assert_eq!( + MockSignedResults::get(), + vec![VerificationResult::Queued, VerificationResult::Queued] + ); + }) + } + + #[test] + fn invalid_solution_bad_score() { + ExtBuilder::verifier().build_and_execute(|| { + roll_to_snapshot_created(); + let mut paged = mine_full_solution().unwrap(); + + // just tweak score. + paged.score.minimal_stake += 1; + assert!(::queued_score().is_none()); + + load_mock_signed_and_start(paged); + roll_to_full_verification(); + + // nothing is verified. + assert!(::queued_score().is_none()); + assert_eq!( + verifier_events(), + vec![ + Event::::Verified(2, 2), + Event::::Verified(1, 2), + Event::::Verified(0, 2), + Event::::VerificationFailed(0, FeasibilityError::InvalidScore) + ] + ); + + assert_eq!(MockSignedResults::get(), vec![VerificationResult::Rejected]); + }) + } + + #[test] + fn invalid_solution_bad_minimum_score() { + ExtBuilder::verifier().build_and_execute(|| { + roll_to_snapshot_created(); + let paged = mine_full_solution().unwrap(); + + // our minimum score is our score, just a bit better. + let mut better_score = paged.score.clone(); + better_score.minimal_stake += 1; + ::set_minimum_score(better_score); + + load_mock_signed_and_start(paged); + + // note that we don't need to call to `roll_to_full_verification`, since this solution + // is pretty much insta-rejected; + assert_eq!( + verifier_events(), + vec![Event::::VerificationFailed(2, FeasibilityError::ScoreTooLow)] + ); + + // nothing is verified.. + assert!(::queued_score().is_none()); + + // result is reported back. + assert_eq!(MockSignedResults::get(), vec![VerificationResult::Rejected]); + }) + } + + #[test] + fn invalid_solution_bad_desired_targets() { + ExtBuilder::verifier().build_and_execute(|| { + roll_to_snapshot_created(); + assert_eq!(crate::Snapshot::::desired_targets().unwrap(), 2); + let paged = mine_full_solution().unwrap(); + + // tweak this, for whatever reason. + crate::Snapshot::::set_desired_targets(3); + + load_mock_signed_and_start(paged); + roll_to_full_verification(); + + // we detect this only in the last page. + assert_eq!( + verifier_events(), + vec![ + Event::Verified(2, 2), + Event::Verified(1, 2), + Event::Verified(0, 2), + Event::VerificationFailed(0, FeasibilityError::WrongWinnerCount) + ] + ); + + // nothing is verified.. + assert!(::queued_score().is_none()); + // result is reported back. + assert_eq!(MockSignedResults::get(), vec![VerificationResult::Rejected]); + }) + } + + #[test] + fn invalid_solution_bad_bounds() { + ExtBuilder::verifier() + .desired_targets(1) + .max_backing_per_target(2) + .build_and_execute(|| { + roll_to_snapshot_created(); + + // This is a sneaky custom solution where in each page 10 has 1 backers, so only in + // the last page we can catch the son of the fidge. + let page0 = solution_from_supports( + vec![(10, Support { total: 10, voters: vec![(1, 10)] })], + 2, + ); + let page1 = solution_from_supports( + vec![(10, Support { total: 10, voters: vec![(5, 10)] })], + 1, + ); + let page2 = solution_from_supports( + vec![(10, Support { total: 10, voters: vec![(10, 10)] })], + 0, + ); + let paged = PagedRawSolution { + solution_pages: bounded_vec![page0, page1, page2], + score: ElectionScore { + minimal_stake: 30, + sum_stake: 30, + sum_stake_squared: 900, + }, + ..Default::default() + }; + + load_mock_signed_and_start(paged); + roll_to_full_verification(); + + // we detect this only in the last page. + assert_eq!( + verifier_events(), + vec![ + Event::Verified(2, 1), + Event::Verified(1, 1), + Event::Verified(0, 1), + Event::VerificationFailed(0, FeasibilityError::TooManyBackings) + ] + ); + + // nothing is verified.. + assert!(::queued_score().is_none()); + // result is reported back. + assert_eq!(MockSignedResults::get(), vec![VerificationResult::Rejected]); + }) + } + + #[test] + fn invalid_solution_does_not_alter_queue() { + ExtBuilder::verifier().build_and_execute(|| { + roll_to_snapshot_created(); + let mut paged = mine_full_solution().unwrap(); + let correct_score = paged.score; + + assert!(::queued_score().is_none()); + + load_mock_signed_and_start(paged.clone()); + roll_to_full_verification(); + + assert_eq!(::queued_score(), Some(correct_score)); + assert!(QueuedSolution::::invalid_iter().count().is_zero()); + assert!(QueuedSolution::::backing_iter().count().is_zero()); + + // just tweak score. Note that we tweak for a higher score, so the verifier will accept + // it. + paged.score.minimal_stake += 1; + load_mock_signed_and_start(paged.clone()); + roll_to_full_verification(); + + // nothing is verified. + assert_eq!(::queued_score(), Some(correct_score)); + assert_eq!( + verifier_events(), + vec![ + Event::::Verified(2, 2), + Event::::Verified(1, 2), + Event::::Verified(0, 2), + Event::::Queued(correct_score, None), + Event::::Verified(2, 2), + Event::::Verified(1, 2), + Event::::Verified(0, 2), + Event::::VerificationFailed(0, FeasibilityError::InvalidScore), + ] + ); + + // the verification results. + assert_eq!( + MockSignedResults::get(), + vec![VerificationResult::Queued, VerificationResult::Rejected] + ); + + // and the queue is still in good shape. + assert_eq!(::queued_score(), Some(correct_score)); + assert!(QueuedSolution::::invalid_iter().count().is_zero()); + assert!(QueuedSolution::::backing_iter().count().is_zero()); + }) + } +} + +mod sync_verification { + use frame_election_provider_support::Support; + use frame_support::bounded_vec; + use sp_npos_elections::ElectionScore; + use sp_runtime::Perbill; + + use crate::{ + mock::{ + fake_solution, mine_solution, roll_to_snapshot_created, solution_from_supports, + verifier_events, ExtBuilder, MaxBackersPerWinner, MaxWinnersPerPage, MultiBlock, + Runtime, VerifierPallet, + }, + verifier::{Event, FeasibilityError, Verifier}, + PagedRawSolution, Snapshot, + }; + + #[test] + fn basic_sync_verification_works() { + ExtBuilder::verifier().build_and_execute(|| { + roll_to_snapshot_created(); + let single_page = mine_solution(1).unwrap(); + + assert_eq!(verifier_events(), vec![]); + assert_eq!(::queued_score(), None); + + let _ = ::verify_synchronous( + single_page.solution_pages.first().cloned().unwrap(), + single_page.score, + MultiBlock::msp(), + ) + .unwrap(); + + assert_eq!( + verifier_events(), + vec![ + Event::::Verified(2, 2), + Event::::Queued(single_page.score, None) + ] + ); + assert_eq!(::queued_score(), Some(single_page.score)); + }) + } + + #[test] + fn winner_count_more() { + ExtBuilder::verifier().build_and_execute(|| { + roll_to_snapshot_created(); + let single_page = mine_solution(1).unwrap(); + + // change the snapshot, as if the desired targets is now 1. This solution is then valid, + // but has too many. + Snapshot::::set_desired_targets(1); + + assert_eq!(verifier_events(), vec![]); + assert_eq!(::queued_score(), None); + + // note: this is NOT a storage_noop! because we do emit events. + assert_eq!( + ::verify_synchronous( + single_page.solution_pages.first().cloned().unwrap(), + single_page.score, + MultiBlock::msp(), + ) + .unwrap_err(), + FeasibilityError::WrongWinnerCount + ); + + assert_eq!( + verifier_events(), + vec![Event::::VerificationFailed(2, FeasibilityError::WrongWinnerCount)] + ); + assert_eq!(::queued_score(), None); + }) + } + + #[test] + fn winner_count_less() { + ExtBuilder::verifier().build_and_execute(|| { + roll_to_snapshot_created(); + let single_page = mine_solution(1).unwrap(); + + assert_eq!(verifier_events(), vec![]); + assert_eq!(::queued_score(), None); + + // Valid solution, but has now too few. + Snapshot::::set_desired_targets(3); + + assert_eq!( + ::verify_synchronous( + single_page.solution_pages.first().cloned().unwrap(), + single_page.score, + MultiBlock::msp(), + ) + .unwrap_err(), + FeasibilityError::WrongWinnerCount + ); + + assert_eq!( + verifier_events(), + vec![Event::::VerificationFailed(2, FeasibilityError::WrongWinnerCount)] + ); + assert_eq!(::queued_score(), None); + }) + } + + #[test] + fn incorrect_score_is_rejected() { + ExtBuilder::verifier().build_and_execute(|| { + roll_to_snapshot_created(); + + let single_page = mine_solution(1).unwrap(); + let mut score_incorrect = single_page.score; + score_incorrect.minimal_stake += 1; + + assert_eq!( + ::verify_synchronous( + single_page.solution_pages.first().cloned().unwrap(), + score_incorrect, + MultiBlock::msp(), + ) + .unwrap_err(), + FeasibilityError::InvalidScore + ); + + assert_eq!( + verifier_events(), + vec![Event::::VerificationFailed(2, FeasibilityError::InvalidScore),] + ); + }) + } + + #[test] + fn minimum_untrusted_score_is_rejected() { + ExtBuilder::verifier().build_and_execute(|| { + roll_to_snapshot_created(); + + let single_page = mine_solution(1).unwrap(); + + // raise the bar such that we don't meet it. + let mut unattainable_score = single_page.score; + unattainable_score.minimal_stake += 1; + + ::set_minimum_score(unattainable_score); + + assert_eq!( + ::verify_synchronous( + single_page.solution_pages.first().cloned().unwrap(), + single_page.score, + MultiBlock::msp(), + ) + .unwrap_err(), + FeasibilityError::ScoreTooLow + ); + + assert_eq!( + verifier_events(), + vec![Event::::VerificationFailed(2, FeasibilityError::ScoreTooLow)] + ); + }) + } + + #[test] + fn bad_bounds_rejected() { + // MaxBackersPerWinner. + ExtBuilder::verifier().build_and_execute(|| { + roll_to_snapshot_created(); + + let single_page = mine_solution(1).unwrap(); + // note: change this after the miner is done, otherwise it is smart enough to trim. + MaxBackersPerWinner::set(1); + + assert_eq!( + ::verify_synchronous( + single_page.solution_pages.first().cloned().unwrap(), + single_page.score, + MultiBlock::msp(), + ) + .unwrap_err(), + FeasibilityError::TooManyBackings + ); + + assert_eq!( + verifier_events(), + vec![Event::::VerificationFailed(2, FeasibilityError::TooManyBackings)] + ); + }); + + // MaxWinnersPerPage. + ExtBuilder::verifier().build_and_execute(|| { + roll_to_snapshot_created(); + + let single_page = mine_solution(1).unwrap(); + // note: the miner does feasibility internally, change this parameter afterwards. + MaxWinnersPerPage::set(1); + + assert_eq!( + ::verify_synchronous( + single_page.solution_pages.first().cloned().unwrap(), + single_page.score, + MultiBlock::msp(), + ) + .unwrap_err(), + FeasibilityError::WrongWinnerCount + ); + + assert_eq!( + verifier_events(), + vec![Event::::VerificationFailed(2, FeasibilityError::WrongWinnerCount)] + ); + }); + } + + #[test] + fn solution_improvement_threshold_respected() { + ExtBuilder::verifier() + .solution_improvement_threshold(Perbill::from_percent(10)) + .build_and_execute(|| { + roll_to_snapshot_created(); + + // submit something good. + let single_page = mine_solution(1).unwrap(); + let _ = ::verify_synchronous( + single_page.solution_pages.first().cloned().unwrap(), + single_page.score, + MultiBlock::msp(), + ) + .unwrap(); + + // the slightly better solution need not even be correct. We improve it by 5%, but + // we need 10%. + let mut better_score = single_page.score; + let improvement = Perbill::from_percent(5) * better_score.minimal_stake; + better_score.minimal_stake += improvement; + let slightly_better = fake_solution(better_score); + + assert_eq!( + ::verify_synchronous( + slightly_better.solution_pages.first().cloned().unwrap(), + slightly_better.score, + MultiBlock::msp(), + ) + .unwrap_err(), + FeasibilityError::ScoreTooLow + ); + }); + } + + #[test] + fn weak_score_is_insta_rejected() { + ExtBuilder::verifier().build_and_execute(|| { + roll_to_snapshot_created(); + + // queue something useful. + let single_page = mine_solution(1).unwrap(); + let _ = ::verify_synchronous( + single_page.solution_pages.first().cloned().unwrap(), + single_page.score, + MultiBlock::msp(), + ) + .unwrap(); + assert_eq!(::queued_score(), Some(single_page.score)); + + // now try and submit that's really weak. Doesn't even need to be valid, since the score + // is checked first. + let mut bad_score = single_page.score; + bad_score.minimal_stake -= 1; + let weak = fake_solution(bad_score); + + assert_eq!( + ::verify_synchronous( + weak.solution_pages.first().cloned().unwrap(), + weak.score, + MultiBlock::msp(), + ) + .unwrap_err(), + FeasibilityError::ScoreTooLow + ); + + assert_eq!( + verifier_events(), + vec![ + Event::::Verified(2, 2), + Event::::Queued(single_page.score, None), + Event::::VerificationFailed(2, FeasibilityError::ScoreTooLow), + ] + ); + }) + } + + #[test] + fn good_solution_replaces() { + ExtBuilder::verifier().build_and_execute(|| { + roll_to_snapshot_created(); + + let weak_solution = solution_from_supports( + vec![ + (10, Support { total: 10, voters: vec![(1, 10)] }), + (20, Support { total: 10, voters: vec![(4, 10)] }), + ], + 2, + ); + + let weak_paged = PagedRawSolution:: { + solution_pages: bounded_vec![weak_solution], + score: ElectionScore { minimal_stake: 10, sum_stake: 20, sum_stake_squared: 200 }, + ..Default::default() + }; + + let _ = ::verify_synchronous( + weak_paged.solution_pages.first().cloned().unwrap(), + weak_paged.score, + MultiBlock::msp(), + ) + .unwrap(); + assert_eq!(::queued_score(), Some(weak_paged.score)); + + // now get a better solution. + let better = mine_solution(1).unwrap(); + + let _ = ::verify_synchronous( + better.solution_pages.first().cloned().unwrap(), + better.score, + MultiBlock::msp(), + ) + .unwrap(); + + assert_eq!(::queued_score(), Some(better.score)); + + assert_eq!( + verifier_events(), + vec![ + Event::::Verified(2, 2), + Event::::Queued(weak_paged.score, None), + Event::::Verified(2, 2), + Event::::Queued(better.score, Some(weak_paged.score)), + ] + ); + }) + } + + #[test] + fn weak_valid_is_discarded() { + ExtBuilder::verifier().build_and_execute(|| { + roll_to_snapshot_created(); + + // first, submit something good + let better = mine_solution(1).unwrap(); + let _ = ::verify_synchronous( + better.solution_pages.first().cloned().unwrap(), + better.score, + MultiBlock::msp(), + ) + .unwrap(); + assert_eq!(::queued_score(), Some(better.score)); + + // then try with something weaker. + let weak_solution = solution_from_supports( + vec![ + (10, Support { total: 10, voters: vec![(1, 10)] }), + (20, Support { total: 10, voters: vec![(4, 10)] }), + ], + 2, + ); + let weak_paged = PagedRawSolution:: { + solution_pages: bounded_vec![weak_solution], + score: ElectionScore { minimal_stake: 10, sum_stake: 20, sum_stake_squared: 200 }, + ..Default::default() + }; + + assert_eq!( + ::verify_synchronous( + weak_paged.solution_pages.first().cloned().unwrap(), + weak_paged.score, + MultiBlock::msp(), + ) + .unwrap_err(), + FeasibilityError::ScoreTooLow + ); + + // queued solution has not changed. + assert_eq!(::queued_score(), Some(better.score)); + + assert_eq!( + verifier_events(), + vec![ + Event::::Verified(2, 2), + Event::::Queued(better.score, None), + Event::::VerificationFailed(2, FeasibilityError::ScoreTooLow), + ] + ); + }) + } +} diff --git a/substrate/frame/election-provider-multi-block/src/weights.rs b/substrate/frame/election-provider-multi-block/src/weights.rs new file mode 100644 index 0000000000000..f91985b04ea0b --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/weights.rs @@ -0,0 +1,258 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 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_election_provider_multi_phase +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2021-08-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 + +// Executed Command: +// target/release/substrate +// benchmark +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_election_provider_multi_phase +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/election-provider-multi-phase/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs + + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(unused)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_election_provider_multi_phase. +pub trait WeightInfo { + fn on_initialize_nothing() -> Weight; + fn on_initialize_open_signed() -> Weight; + fn on_initialize_open_unsigned_with_snapshot() -> Weight; + fn on_initialize_open_unsigned_without_snapshot() -> Weight; + fn finalize_signed_phase_accept_solution() -> Weight; + fn finalize_signed_phase_reject_solution() -> Weight; + fn elect_queued(v: u32, t: u32, a: u32, d: u32, ) -> Weight; + fn submit(c: u32, ) -> Weight; + fn submit_unsigned(v: u32, t: u32, a: u32, d: u32, ) -> Weight; + fn feasibility_check(v: u32, t: u32, a: u32, d: u32, ) -> Weight; +} + +/// Weights for pallet_election_provider_multi_phase using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Staking CurrentPlannedSession (r:1 w:0) + // Storage: Staking ErasStartSessionIndex (r:1 w:0) + // Storage: Babe EpochIndex (r:1 w:0) + // Storage: Babe GenesisSlot (r:1 w:0) + // Storage: Babe CurrentSlot (r:1 w:0) + // Storage: Staking ForceEra (r:1 w:0) + // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) + fn on_initialize_nothing() -> Weight { + todo!() + } + // Storage: Staking CounterForValidators (r:1 w:0) + // Storage: Staking Validators (r:2 w:0) + // Storage: Staking CounterForNominators (r:1 w:0) + // Storage: Staking SlashingSpans (r:1 w:0) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Ledger (r:1 w:0) + // Storage: Staking Nominators (r:1 w:0) + // Storage: Staking ValidatorCount (r:1 w:0) + // Storage: ElectionProviderMultiPhase Round (r:1 w:0) + // Storage: ElectionProviderMultiPhase SnapshotMetadata (r:0 w:1) + // Storage: ElectionProviderMultiPhase DesiredTargets (r:0 w:1) + // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) + // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) + fn on_initialize_open_signed() -> Weight { + todo!() + } + // Storage: Staking CounterForValidators (r:1 w:0) + // Storage: Staking Validators (r:2 w:0) + // Storage: Staking CounterForNominators (r:1 w:0) + // Storage: Staking SlashingSpans (r:1 w:0) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Ledger (r:1 w:0) + // Storage: Staking Nominators (r:1 w:0) + // Storage: Staking ValidatorCount (r:1 w:0) + // Storage: ElectionProviderMultiPhase Round (r:1 w:0) + // Storage: ElectionProviderMultiPhase SnapshotMetadata (r:0 w:1) + // Storage: ElectionProviderMultiPhase DesiredTargets (r:0 w:1) + // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) + // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) + fn on_initialize_open_unsigned_with_snapshot() -> Weight { + todo!() + } + // Storage: ElectionProviderMultiPhase Round (r:1 w:0) + // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) + fn on_initialize_open_unsigned_without_snapshot() -> Weight { + todo!() + } + // Storage: System Account (r:1 w:1) + // Storage: ElectionProviderMultiPhase QueuedSolution (r:0 w:1) + fn finalize_signed_phase_accept_solution() -> Weight { + todo!() + } + // Storage: System Account (r:1 w:1) + fn finalize_signed_phase_reject_solution() -> Weight { + todo!() + } + // Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) + // Storage: ElectionProviderMultiPhase SignedSubmissionNextIndex (r:1 w:1) + // Storage: ElectionProviderMultiPhase SnapshotMetadata (r:1 w:1) + // Storage: ElectionProviderMultiPhase SignedSubmissionsMap (r:1 w:0) + // Storage: ElectionProviderMultiPhase QueuedSolution (r:1 w:1) + // Storage: ElectionProviderMultiPhase Round (r:1 w:1) + // Storage: ElectionProviderMultiPhase DesiredTargets (r:0 w:1) + // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) + // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) + fn elect_queued(v: u32, t: u32, a: u32, d: u32, ) -> Weight { + todo!() + } + // Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) + // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) + // Storage: ElectionProviderMultiPhase SnapshotMetadata (r:1 w:0) + // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) + // Storage: ElectionProviderMultiPhase SignedSubmissionNextIndex (r:1 w:1) + // Storage: ElectionProviderMultiPhase SignedSubmissionsMap (r:0 w:1) + fn submit(c: u32, ) -> Weight { + todo!() + } + // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) + // Storage: ElectionProviderMultiPhase Round (r:1 w:0) + // Storage: ElectionProviderMultiPhase DesiredTargets (r:1 w:0) + // Storage: ElectionProviderMultiPhase QueuedSolution (r:1 w:1) + // Storage: ElectionProviderMultiPhase SnapshotMetadata (r:1 w:0) + // Storage: ElectionProviderMultiPhase MinimumUntrustedScore (r:1 w:0) + // Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) + fn submit_unsigned(v: u32, t: u32, a: u32, d: u32, ) -> Weight { + todo!() + } + // Storage: ElectionProviderMultiPhase Round (r:1 w:0) + // Storage: ElectionProviderMultiPhase DesiredTargets (r:1 w:0) + // Storage: ElectionProviderMultiPhase MinimumUntrustedScore (r:1 w:0) + // Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) + fn feasibility_check(v: u32, t: u32, a: u32, d: u32, ) -> Weight { + todo!() + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Staking CurrentPlannedSession (r:1 w:0) + // Storage: Staking ErasStartSessionIndex (r:1 w:0) + // Storage: Babe EpochIndex (r:1 w:0) + // Storage: Babe GenesisSlot (r:1 w:0) + // Storage: Babe CurrentSlot (r:1 w:0) + // Storage: Staking ForceEra (r:1 w:0) + // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) + fn on_initialize_nothing() -> Weight { + todo!() + } + // Storage: Staking CounterForValidators (r:1 w:0) + // Storage: Staking Validators (r:2 w:0) + // Storage: Staking CounterForNominators (r:1 w:0) + // Storage: Staking SlashingSpans (r:1 w:0) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Ledger (r:1 w:0) + // Storage: Staking Nominators (r:1 w:0) + // Storage: Staking ValidatorCount (r:1 w:0) + // Storage: ElectionProviderMultiPhase Round (r:1 w:0) + // Storage: ElectionProviderMultiPhase SnapshotMetadata (r:0 w:1) + // Storage: ElectionProviderMultiPhase DesiredTargets (r:0 w:1) + // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) + // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) + fn on_initialize_open_signed() -> Weight { + todo!() + } + // Storage: Staking CounterForValidators (r:1 w:0) + // Storage: Staking Validators (r:2 w:0) + // Storage: Staking CounterForNominators (r:1 w:0) + // Storage: Staking SlashingSpans (r:1 w:0) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Staking Ledger (r:1 w:0) + // Storage: Staking Nominators (r:1 w:0) + // Storage: Staking ValidatorCount (r:1 w:0) + // Storage: ElectionProviderMultiPhase Round (r:1 w:0) + // Storage: ElectionProviderMultiPhase SnapshotMetadata (r:0 w:1) + // Storage: ElectionProviderMultiPhase DesiredTargets (r:0 w:1) + // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) + // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) + fn on_initialize_open_unsigned_with_snapshot() -> Weight { + todo!() + } + // Storage: ElectionProviderMultiPhase Round (r:1 w:0) + // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) + fn on_initialize_open_unsigned_without_snapshot() -> Weight { + todo!() + } + // Storage: System Account (r:1 w:1) + // Storage: ElectionProviderMultiPhase QueuedSolution (r:0 w:1) + fn finalize_signed_phase_accept_solution() -> Weight { + todo!() + } + // Storage: System Account (r:1 w:1) + fn finalize_signed_phase_reject_solution() -> Weight { + todo!() + } + // Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) + // Storage: ElectionProviderMultiPhase SignedSubmissionNextIndex (r:1 w:1) + // Storage: ElectionProviderMultiPhase SnapshotMetadata (r:1 w:1) + // Storage: ElectionProviderMultiPhase SignedSubmissionsMap (r:1 w:0) + // Storage: ElectionProviderMultiPhase QueuedSolution (r:1 w:1) + // Storage: ElectionProviderMultiPhase Round (r:1 w:1) + // Storage: ElectionProviderMultiPhase DesiredTargets (r:0 w:1) + // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) + // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) + fn elect_queued(v: u32, t: u32, a: u32, d: u32, ) -> Weight { + todo!() + } + // Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) + // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) + // Storage: ElectionProviderMultiPhase SnapshotMetadata (r:1 w:0) + // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) + // Storage: ElectionProviderMultiPhase SignedSubmissionNextIndex (r:1 w:1) + // Storage: ElectionProviderMultiPhase SignedSubmissionsMap (r:0 w:1) + fn submit(c: u32, ) -> Weight { + todo!() + } + // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) + // Storage: ElectionProviderMultiPhase Round (r:1 w:0) + // Storage: ElectionProviderMultiPhase DesiredTargets (r:1 w:0) + // Storage: ElectionProviderMultiPhase QueuedSolution (r:1 w:1) + // Storage: ElectionProviderMultiPhase SnapshotMetadata (r:1 w:0) + // Storage: ElectionProviderMultiPhase MinimumUntrustedScore (r:1 w:0) + // Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) + fn submit_unsigned(v: u32, t: u32, a: u32, d: u32, ) -> Weight { + todo!() + } + // Storage: ElectionProviderMultiPhase Round (r:1 w:0) + // Storage: ElectionProviderMultiPhase DesiredTargets (r:1 w:0) + // Storage: ElectionProviderMultiPhase MinimumUntrustedScore (r:1 w:0) + // Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) + fn feasibility_check(v: u32, t: u32, a: u32, d: u32, ) -> Weight { + todo!() + } +} diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index e2351252efa3a..cfad31510ba93 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -599,7 +599,7 @@ pub mod pallet { use sp_runtime::traits::Convert; #[pallet::config] - pub trait Config: frame_system::Config + CreateInherent> { + pub trait Config: frame_system::Config + nherent> { type RuntimeEvent: From> + IsType<::RuntimeEvent> + TryInto>; diff --git a/substrate/frame/election-provider-support/solution-type/src/single_page.rs b/substrate/frame/election-provider-support/solution-type/src/single_page.rs index 35ac5a7394f3f..e07a2368ee2d0 100644 --- a/substrate/frame/election-provider-support/solution-type/src/single_page.rs +++ b/substrate/frame/election-provider-support/solution-type/src/single_page.rs @@ -84,6 +84,8 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { Eq, Clone, Debug, + Ord, + PartialOrd, _fepsp::codec::Encode, _fepsp::codec::Decode, _fepsp::scale_info::TypeInfo, @@ -96,6 +98,8 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { let from_impl = from_impl(&struct_name, count); let into_impl = into_impl(&assignment_name, count, weight_type.clone()); let from_index_impl = crate::index_assignment::from_impl(&struct_name, count); + let sort_impl = sort_impl(count); + let remove_weakest_sorted_impl = remove_weakest_sorted_impl(count); Ok(quote! ( /// A struct to encode a election assignment in a compact way. @@ -178,6 +182,20 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { all_targets.into_iter().collect() } + + fn sort(&mut self, mut voter_stake: F) + where + F: FnMut(&Self::VoterIndex) -> _feps::VoteWeight + { + #sort_impl + } + + fn remove_weakest_sorted(&mut self, mut voter_stake: F) -> Option + where + F: FnMut(&Self::VoterIndex) -> _feps::VoteWeight + { + #remove_weakest_sorted_impl + } } type __IndexAssignment = _feps::IndexAssignment< @@ -227,6 +245,65 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { )) } +fn sort_impl(count: usize) -> TokenStream2 { + (1..=count) + .map(|c| { + let field = vote_field(c); + quote! { + // NOTE: self.filed here is sometimes `Vec<(voter, weight)>` and sometimes + // `Vec<(voter, weights, last_weight)>`, but Rust's great patter matching makes it + // all work super nice. + self.#field.sort_by(|(a, ..), (b, ..)| voter_stake(&b).cmp(&voter_stake(&a))); + // ---------------------------------^^ in all fields, the index 0 is the voter id. + } + }) + .collect::() +} + +fn remove_weakest_sorted_impl(count: usize) -> TokenStream2 { + // check minium from field 2 onwards. We assume 0 is minimum + let check_minimum = (2..=count).map(|c| { + let filed = vote_field(c); + quote! { + let filed_value = self.#filed + .last() + .map(|(x, ..)| voter_stake(x)) + .unwrap_or_else(|| _feps::sp_arithmetic::traits::Bounded::max_value()); + if filed_value < minimum { + minimum = filed_value; + minimum_filed = #c + } + } + }); + + let remove_minimum_match = (1..=count).map(|c| { + let filed = vote_field(c); + quote! { + #c => self.#filed.pop().map(|(x, ..)| x), + } + }); + + let first_filed = vote_field(1); + quote! { + // we assume first one is the minimum. No problem if it is empty. + let mut minimum_filed = 1; + let mut minimum = self.#first_filed + .last() + .map(|(x, ..)| voter_stake(x)) + .unwrap_or_else(|| _feps::sp_arithmetic::traits::Bounded::max_value()); + + #( #check_minimum )* + + match minimum_filed { + #( #remove_minimum_match )* + _ => { + debug_assert!(false); + None + } + } + } +} + fn remove_voter_impl(count: usize) -> TokenStream2 { let field_name = vote_field(1); let single = quote! { diff --git a/substrate/frame/election-provider-support/src/lib.rs b/substrate/frame/election-provider-support/src/lib.rs index f5043e0e32c41..b506776556375 100644 --- a/substrate/frame/election-provider-support/src/lib.rs +++ b/substrate/frame/election-provider-support/src/lib.rs @@ -735,7 +735,7 @@ pub struct BoundedSupport> { pub voters: BoundedVec<(AccountId, ExtendedBalance), Bound>, } -impl> sp_npos_elections::Backings for BoundedSupport { +impl> sp_npos_elections::Backings for &BoundedSupport { fn total(&self) -> ExtendedBalance { self.total } @@ -804,6 +804,14 @@ pub struct BoundedSupports, BInner: Get>( pub BoundedVec<(AccountId, BoundedSupport), BOuter>, ); +impl, BInner: Get> sp_npos_elections::EvaluateSupport + for BoundedSupports +{ + fn evaluate(&self) -> sp_npos_elections::ElectionScore { + sp_npos_elections::evaluate_support(self.iter().map(|(_, s)| s)) + } +} + impl, BInner: Get> sp_std::ops::DerefMut for BoundedSupports { diff --git a/substrate/frame/election-provider-support/src/traits.rs b/substrate/frame/election-provider-support/src/traits.rs index 84fd57992d343..faa9ab9a436ab 100644 --- a/substrate/frame/election-provider-support/src/traits.rs +++ b/substrate/frame/election-provider-support/src/traits.rs @@ -42,6 +42,8 @@ where + Clone + Bounded + Encode + + Ord + + PartialOrd + TypeInfo; /// The target type. Needs to be an index (convert to usize). @@ -53,6 +55,8 @@ where + Clone + Bounded + Encode + + Ord + + PartialOrd + TypeInfo; /// The weight/accuracy type of each vote. @@ -123,4 +127,18 @@ where voter_at: impl Fn(Self::VoterIndex) -> Option, target_at: impl Fn(Self::TargetIndex) -> Option, ) -> Result>, Error>; + + /// Sort self by the means of the given function. + /// + /// This might be helpful to allow for easier trimming. + fn sort(&mut self, voter_stake: F) + where + F: FnMut(&Self::VoterIndex) -> VoteWeight; + + /// Remove the least staked voter. + /// + /// This is ONLY sensible to do if [`Self::sort`] has been called on the struct at least once. + fn remove_weakest_sorted(&mut self, voter_stake: F) -> Option + where + F: FnMut(&Self::VoterIndex) -> VoteWeight; } diff --git a/substrate/primitives/npos-elections/src/helpers.rs b/substrate/primitives/npos-elections/src/helpers.rs index 7df6ec9d9dbaa..45455b42fb6ca 100644 --- a/substrate/primitives/npos-elections/src/helpers.rs +++ b/substrate/primitives/npos-elections/src/helpers.rs @@ -17,8 +17,11 @@ //! Helper methods for npos-elections. -use crate::{Assignment, Error, IdentifierT, PerThing128, StakedAssignment, VoteWeight}; -use alloc::vec::Vec; +use crate::{ + Assignment, Error, ExtendedBalance, IdentifierT, PerThing128, StakedAssignment, Supports, + VoteWeight, +}; +use alloc::{collections::BTreeMap, vec::Vec}; use sp_arithmetic::PerThing; /// Converts a vector of ratio assignments into ones with absolute budget value. @@ -75,6 +78,23 @@ pub fn assignment_staked_to_ratio_normalized( Ok(ratio) } +/// Convert some [`Supports`]s into vector of [`StakedAssignment`] +pub fn supports_to_staked_assignment( + supports: Supports, +) -> Vec> { + let mut staked: BTreeMap> = BTreeMap::new(); + for (target, support) in supports { + for (voter, amount) in support.voters { + staked.entry(voter).or_default().push((target.clone(), amount)) + } + } + + staked + .into_iter() + .map(|(who, distribution)| StakedAssignment { who, distribution }) + .collect::>() +} + #[cfg(test)] mod tests { use super::*; diff --git a/substrate/primitives/npos-elections/src/lib.rs b/substrate/primitives/npos-elections/src/lib.rs index 8134c3ceeac02..6f6991a14853e 100644 --- a/substrate/primitives/npos-elections/src/lib.rs +++ b/substrate/primitives/npos-elections/src/lib.rs @@ -446,6 +446,12 @@ impl Default for Support { } } +impl Support { + pub fn self_vote_only(who: AccountId, amount: ExtendedBalance) -> (AccountId, Self) { + (who.clone(), Self { total: amount, voters: vec![(who, amount)] }) + } +} + impl Backings for &Support { fn total(&self) -> ExtendedBalance { self.total From 06f5d486f552e2ead543024168035bcdbb29c027 Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Mon, 20 Jan 2025 09:25:43 +0100 Subject: [PATCH 097/169] Collator: Fix `can_build_upon` by always allowing to build on included block (#7205) Follow-up to #6825, which introduced this bug. We use the `can_build_upon` method to ask the runtime if it is fine to build another block. The runtime checks this based on the [`ConsensusHook`](https://github.com/paritytech/polkadot-sdk/blob/c1b7c3025aa4423d4cf3e57309b60fb7602c2db6/cumulus/pallets/aura-ext/src/consensus_hook.rs#L110-L110) implementation, the most popular one being the `FixedConsensusHook`. In #6825 I removed a check that would always allow us to build when we are building on an included block. Turns out this check is still required when: 1. The [`UnincludedSegment` ](https://github.com/paritytech/polkadot-sdk/blob/c1b7c3025aa4423d4cf3e57309b60fb7602c2db6/cumulus/pallets/parachain-system/src/lib.rs#L758-L758) storage item in pallet-parachain-system is equal or larger than the unincluded segment. 2. We are calling the `can_build_upon` runtime API where the included block has progressed offchain to the current parent block (so last entry in the `UnincludedSegment` storage item). In this scenario the last entry in `UnincludedSegment` does not have a hash assigned yet (because it was not available in `on_finalize` of the previous block). So the unincluded segment will be reported at its maximum length which will forbid building another block. Ideally we would have a more elegant solution than to rely on the node-side here. But for now the check is reintroduced and a test is added to not break it again by accident. --------- Co-authored-by: command-bot <> Co-authored-by: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> --- Cargo.lock | 3 + cumulus/client/consensus/aura/Cargo.toml | 5 + .../consensus/aura/src/collators/mod.rs | 132 +++++++++++++++++- prdoc/pr_7205.prdoc | 10 ++ 4 files changed, 144 insertions(+), 6 deletions(-) create mode 100644 prdoc/pr_7205.prdoc diff --git a/Cargo.lock b/Cargo.lock index da4e85511919d..c9a139f307446 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4640,6 +4640,8 @@ dependencies = [ "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", "cumulus-relay-chain-interface", + "cumulus-test-client", + "cumulus-test-relay-sproof-builder 0.7.0", "futures", "parity-scale-codec", "parking_lot 0.12.3", @@ -4664,6 +4666,7 @@ dependencies = [ "sp-consensus-aura 0.32.0", "sp-core 28.0.0", "sp-inherents 26.0.0", + "sp-keyring 31.0.0", "sp-keystore 0.34.0", "sp-runtime 31.0.1", "sp-state-machine 0.35.0", diff --git a/cumulus/client/consensus/aura/Cargo.toml b/cumulus/client/consensus/aura/Cargo.toml index 7022309386455..8637133a5f5cb 100644 --- a/cumulus/client/consensus/aura/Cargo.toml +++ b/cumulus/client/consensus/aura/Cargo.toml @@ -59,6 +59,11 @@ polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } +[dev-dependencies] +cumulus-test-client = { workspace = true } +cumulus-test-relay-sproof-builder = { workspace = true } +sp-keyring = { workspace = true } + [features] # Allows collator to use full PoV size for block building full-pov-size = [] diff --git a/cumulus/client/consensus/aura/src/collators/mod.rs b/cumulus/client/consensus/aura/src/collators/mod.rs index 031fa963ba6ae..66c6086eaf9ee 100644 --- a/cumulus/client/consensus/aura/src/collators/mod.rs +++ b/cumulus/client/consensus/aura/src/collators/mod.rs @@ -179,12 +179,19 @@ where let authorities = runtime_api.authorities(parent_hash).ok()?; let author_pub = aura_internal::claim_slot::

(para_slot, &authorities, keystore).await?; - let Ok(Some(api_version)) = - runtime_api.api_version::>(parent_hash) - else { - return (parent_hash == included_block) - .then(|| SlotClaim::unchecked::

(author_pub, para_slot, timestamp)); - }; + // This function is typically called when we want to build block N. At that point, the + // unincluded segment in the runtime is unaware of the hash of block N-1. If the unincluded + // segment in the runtime is full, but block N-1 is the included block, the unincluded segment + // should have length 0 and we can build. Since the hash is not available to the runtime + // however, we need this extra check here. + if parent_hash == included_block { + return Some(SlotClaim::unchecked::

(author_pub, para_slot, timestamp)); + } + + let api_version = runtime_api + .api_version::>(parent_hash) + .ok() + .flatten()?; let slot = if api_version > 1 { relay_slot } else { para_slot }; @@ -243,3 +250,116 @@ where .max_by_key(|a| a.depth) .map(|parent| (included_block, parent)) } + +#[cfg(test)] +mod tests { + use crate::collators::can_build_upon; + use codec::Encode; + use cumulus_primitives_aura::Slot; + use cumulus_primitives_core::BlockT; + use cumulus_relay_chain_interface::PHash; + use cumulus_test_client::{ + runtime::{Block, Hash}, + Client, DefaultTestClientBuilderExt, InitBlockBuilder, TestClientBuilder, + TestClientBuilderExt, + }; + use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; + use polkadot_primitives::HeadData; + use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy}; + use sp_consensus::BlockOrigin; + use sp_keystore::{Keystore, KeystorePtr}; + use sp_timestamp::Timestamp; + use std::sync::Arc; + + async fn import_block>( + importer: &I, + block: Block, + origin: BlockOrigin, + import_as_best: bool, + ) { + let (header, body) = block.deconstruct(); + + let mut block_import_params = BlockImportParams::new(origin, header); + block_import_params.fork_choice = Some(ForkChoiceStrategy::Custom(import_as_best)); + block_import_params.body = Some(body); + importer.import_block(block_import_params).await.unwrap(); + } + + fn sproof_with_parent_by_hash(client: &Client, hash: PHash) -> RelayStateSproofBuilder { + let header = client.header(hash).ok().flatten().expect("No header for parent block"); + let included = HeadData(header.encode()); + let mut builder = RelayStateSproofBuilder::default(); + builder.para_id = cumulus_test_client::runtime::PARACHAIN_ID.into(); + builder.included_para_head = Some(included); + + builder + } + async fn build_and_import_block(client: &Client, included: Hash) -> Block { + let sproof = sproof_with_parent_by_hash(client, included); + + let block_builder = client.init_block_builder(None, sproof).block_builder; + + let block = block_builder.build().unwrap().block; + + let origin = BlockOrigin::NetworkInitialSync; + import_block(client, block.clone(), origin, true).await; + block + } + + fn set_up_components() -> (Arc, KeystorePtr) { + let keystore = Arc::new(sp_keystore::testing::MemoryKeystore::new()) as Arc<_>; + for key in sp_keyring::Sr25519Keyring::iter() { + Keystore::sr25519_generate_new( + &*keystore, + sp_application_crypto::key_types::AURA, + Some(&key.to_seed()), + ) + .expect("Can insert key into MemoryKeyStore"); + } + (Arc::new(TestClientBuilder::new().build()), keystore) + } + + /// This tests a special scenario where the unincluded segment in the runtime + /// is full. We are calling `can_build_upon`, passing the last built block as the + /// included one. In the runtime we will not find the hash of the included block in the + /// unincluded segment. The `can_build_upon` runtime API would therefore return `false`, but + /// we are ensuring on the node side that we are are always able to build on the included block. + #[tokio::test] + async fn test_can_build_upon() { + let (client, keystore) = set_up_components(); + + let genesis_hash = client.chain_info().genesis_hash; + let mut last_hash = genesis_hash; + + // Fill up the unincluded segment tracker in the runtime. + while can_build_upon::<_, _, sp_consensus_aura::sr25519::AuthorityPair>( + Slot::from(u64::MAX), + Slot::from(u64::MAX), + Timestamp::default(), + last_hash, + genesis_hash, + &*client, + &keystore, + ) + .await + .is_some() + { + let block = build_and_import_block(&client, genesis_hash).await; + last_hash = block.header().hash(); + } + + // Blocks were built with the genesis hash set as included block. + // We call `can_build_upon` with the last built block as the included block. + let result = can_build_upon::<_, _, sp_consensus_aura::sr25519::AuthorityPair>( + Slot::from(u64::MAX), + Slot::from(u64::MAX), + Timestamp::default(), + last_hash, + last_hash, + &*client, + &keystore, + ) + .await; + assert!(result.is_some()); + } +} diff --git a/prdoc/pr_7205.prdoc b/prdoc/pr_7205.prdoc new file mode 100644 index 0000000000000..758beb0b6313c --- /dev/null +++ b/prdoc/pr_7205.prdoc @@ -0,0 +1,10 @@ +title: 'Collator: Fix `can_build_upon` by always allowing to build on included block' +doc: +- audience: Node Dev + description: |- + Fixes a bug introduced in #6825. + We should always allow building on the included block of parachains. In situations where the unincluded segment + is full, but the included block moved to the most recent block, building was wrongly disallowed. +crates: +- name: cumulus-client-consensus-aura + bump: minor From 4937f779068d1ab947c9eada8e1d3f5b7191eb94 Mon Sep 17 00:00:00 2001 From: seemantaggarwal <32275622+seemantaggarwal@users.noreply.github.com> Date: Mon, 20 Jan 2025 15:51:29 +0530 Subject: [PATCH 098/169] Use docify export for parachain template hardcoded configuration and embed it in its README #6333 (#7093) Use docify export for parachain template hardcoded configuration and embed it in its README #6333 Docify currently has a limitation of not being able to embed a variable/const in its code, without embedding it's definition, even if do something in a string like "this is a sample string ${sample_variable}" It will embed the entire string "this is a sample string ${sample_variable}" without replacing the value of sample_variable from the code Hence, the goal was just to make it obvious in the README where the PARACHAIN_ID value is coming from, so a note has been added at the start for the same, so whenever somebody is running these commands, they will be aware about the value and replace accordingly. To make it simpler, we added a rust ignore block so the user can just look it up in the readme itself and does not have to scan through the runtime directory for the value. --------- Co-authored-by: Iulian Barbu <14218860+iulianbarbu@users.noreply.github.com> --- .github/scripts/generate-prdoc.py | 4 +- .github/workflows/misc-sync-templates.yml | 6 + Cargo.lock | 220 ++++++++------- Cargo.toml | 1 + prdoc/pr_7093.prdoc | 8 + templates/parachain/Cargo.toml | 16 ++ templates/parachain/README.docify.md | 254 ++++++++++++++++++ templates/parachain/README.md | 46 ++-- .../runtime/src/genesis_config_presets.rs | 1 + templates/parachain/src/lib.rs | 22 ++ 10 files changed, 465 insertions(+), 113 deletions(-) create mode 100644 prdoc/pr_7093.prdoc create mode 100644 templates/parachain/Cargo.toml create mode 100644 templates/parachain/README.docify.md create mode 100644 templates/parachain/src/lib.rs diff --git a/.github/scripts/generate-prdoc.py b/.github/scripts/generate-prdoc.py index 9154f185e64b9..43e8437a0c960 100644 --- a/.github/scripts/generate-prdoc.py +++ b/.github/scripts/generate-prdoc.py @@ -86,10 +86,10 @@ def create_prdoc(pr, audience, title, description, patch, bump, force): if p == '/': exit(1) p = os.path.dirname(p) - + with open(os.path.join(p, "Cargo.toml")) as f: manifest = toml.load(f) - + if not "package" in manifest: continue diff --git a/.github/workflows/misc-sync-templates.yml b/.github/workflows/misc-sync-templates.yml index 8d06d89621d78..ac66e697562b3 100644 --- a/.github/workflows/misc-sync-templates.yml +++ b/.github/workflows/misc-sync-templates.yml @@ -131,6 +131,12 @@ jobs: - name: Copy over the new changes run: | cp -r polkadot-sdk/templates/${{ matrix.template }}/* "${{ env.template-path }}/" + - name: Remove unnecessary files from parachain template + if: ${{ matrix.template == 'parachain' }} + run: | + rm -f "${{ env.template-path }}/README.docify.md" + rm -f "${{ env.template-path }}/Cargo.toml" + rm -f "${{ env.template-path }}/src/lib.rs" - name: Run psvm on monorepo workspace dependencies run: psvm -o -v ${{ github.event.inputs.stable_release_branch }} -p ./Cargo.toml diff --git a/Cargo.lock b/Cargo.lock index c9a139f307446..0907830c5e7bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -36,6 +36,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "adler32" version = "1.2.0" @@ -112,9 +118,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.4" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -363,23 +369,24 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.11" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" @@ -401,12 +408,12 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.1" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -1679,7 +1686,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.7.1", "object 0.32.2", "rustc-demangle", ] @@ -3079,12 +3086,12 @@ dependencies = [ [[package]] name = "bstr" -version = "1.6.0" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" dependencies = [ "memchr", - "regex-automata 0.3.6", + "regex-automata 0.4.8", "serde", ] @@ -3187,9 +3194,9 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.3" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cfa25e60aea747ec7e1124f238816749faa93759c6ff5b31f1ccdda137f4479" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" dependencies = [ "serde", ] @@ -3202,7 +3209,7 @@ checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" dependencies = [ "camino", "cargo-platform", - "semver 1.0.18", + "semver 1.0.24", "serde", "serde_json", "thiserror", @@ -3487,12 +3494,12 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.13" +version = "4.5.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" +checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" dependencies = [ "clap_builder", - "clap_derive 4.5.13", + "clap_derive 4.5.24", ] [[package]] @@ -3506,24 +3513,24 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.13" +version = "4.5.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" +checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" dependencies = [ "anstream", "anstyle", - "clap_lex 0.7.0", + "clap_lex 0.7.4", "strsim 0.11.1", "terminal_size", ] [[package]] name = "clap_complete" -version = "4.5.13" +version = "4.5.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa3c596da3cf0983427b0df0dba359df9182c13bd5b519b585a482b0c351f4e8" +checksum = "33a7e468e750fa4b6be660e8b5651ad47372e8fb114030b594c2d75d48c5ffd0" dependencies = [ - "clap 4.5.13", + "clap 4.5.26", ] [[package]] @@ -3541,9 +3548,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.13" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" dependencies = [ "heck 0.5.0", "proc-macro2 1.0.86", @@ -3562,9 +3569,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "cmd_lib" @@ -3750,23 +3757,23 @@ dependencies = [ [[package]] name = "color-print" -version = "0.3.4" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2a5e6504ed8648554968650feecea00557a3476bc040d0ffc33080e66b646d0" +checksum = "3aa954171903797d5623e047d9ab69d91b493657917bdfb8c2c80ecaf9cdb6f4" dependencies = [ "color-print-proc-macro", ] [[package]] name = "color-print-proc-macro" -version = "0.3.4" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51beaa537d73d2d1ff34ee70bc095f170420ab2ec5d687ecd3ec2b0d092514b" +checksum = "692186b5ebe54007e45a59aea47ece9eb4108e141326c304cdc91699a7118a22" dependencies = [ "nom", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 1.0.109", + "syn 2.0.87", ] [[package]] @@ -4441,7 +4448,7 @@ dependencies = [ "anes", "cast", "ciborium", - "clap 4.5.13", + "clap 4.5.26", "criterion-plot", "futures", "is-terminal", @@ -4586,7 +4593,7 @@ dependencies = [ name = "cumulus-client-cli" version = "0.7.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.26", "parity-scale-codec", "sc-chain-spec", "sc-cli", @@ -5250,7 +5257,7 @@ name = "cumulus-pov-validator" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.5.13", + "clap 4.5.26", "parity-scale-codec", "polkadot-node-primitives", "polkadot-parachain-primitives 6.0.0", @@ -5690,7 +5697,7 @@ name = "cumulus-test-service" version = "0.1.0" dependencies = [ "async-trait", - "clap 4.5.13", + "clap 4.5.26", "criterion", "cumulus-client-cli", "cumulus-client-collator", @@ -5784,9 +5791,9 @@ dependencies = [ [[package]] name = "curl-sys" -version = "0.4.72+curl-8.6.0" +version = "0.4.78+curl-8.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29cbdc8314c447d11e8fd156dcdd031d9e02a7a976163e396b548c03153bc9ea" +checksum = "8eec768341c5c7789611ae51cf6c459099f22e64a5d5d0ce4892434e33821eaf" dependencies = [ "cc", "libc", @@ -6938,14 +6945,14 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.22" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", - "windows-sys 0.48.0", + "libredox", + "windows-sys 0.59.0", ] [[package]] @@ -7022,12 +7029,12 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.27" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.8.2", ] [[package]] @@ -7180,7 +7187,7 @@ dependencies = [ "Inflector", "array-bytes", "chrono", - "clap 4.5.13", + "clap 4.5.26", "comfy-table", "cumulus-client-parachain-inherent", "cumulus-primitives-proof-size-hostfunction 0.2.0", @@ -7346,7 +7353,7 @@ dependencies = [ name = "frame-election-solution-type-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 4.5.13", + "clap 4.5.26", "frame-election-provider-solution-type 13.0.0", "frame-election-provider-support 28.0.0", "frame-support 28.0.0", @@ -7479,7 +7486,7 @@ name = "frame-omni-bencher" version = "0.1.0" dependencies = [ "assert_cmd", - "clap 4.5.13", + "clap 4.5.26", "cumulus-primitives-proof-size-hostfunction 0.2.0", "cumulus-test-runtime", "frame-benchmarking-cli", @@ -9194,6 +9201,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "isahc" version = "1.7.2" @@ -10285,6 +10298,17 @@ dependencies = [ "yamux 0.13.3", ] +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", + "redox_syscall 0.5.8", +] + [[package]] name = "librocksdb-sys" version = "0.11.0+8.1.1" @@ -10361,9 +10385,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.12" +version = "1.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" +checksum = "df9b68e50e6e0b26f672573834882eb57759f6db9b3be2ea3c35c91188bb4eaa" dependencies = [ "cc", "libc", @@ -10832,7 +10856,7 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" name = "minimal-template-node" version = "0.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.26", "docify", "futures", "futures-timer", @@ -10862,6 +10886,15 @@ dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +dependencies = [ + "adler2", +] + [[package]] name = "mio" version = "1.0.2" @@ -11339,7 +11372,7 @@ version = "0.9.0-dev" dependencies = [ "array-bytes", "async-trait", - "clap 4.5.13", + "clap 4.5.26", "derive_more 0.99.17", "fs_extra", "futures", @@ -11415,7 +11448,7 @@ dependencies = [ name = "node-runtime-generate-bags" version = "3.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.26", "generate-bags", "kitchensink-runtime", ] @@ -11424,7 +11457,7 @@ dependencies = [ name = "node-template-release" version = "3.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.26", "flate2", "fs_extra", "glob", @@ -14916,7 +14949,7 @@ name = "pallet-revive-eth-rpc" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.5.13", + "clap 4.5.26", "env_logger 0.11.3", "ethabi", "futures", @@ -16252,11 +16285,18 @@ dependencies = [ "staging-xcm-builder 17.0.1", ] +[[package]] +name = "parachain-template" +version = "0.0.0" +dependencies = [ + "docify", +] + [[package]] name = "parachain-template-node" version = "0.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.26", "color-print", "docify", "futures", @@ -17240,7 +17280,7 @@ name = "polkadot-cli" version = "7.0.0" dependencies = [ "cfg-if", - "clap 4.5.13", + "clap 4.5.26", "frame-benchmarking-cli", "futures", "log", @@ -18111,7 +18151,7 @@ version = "0.1.0" dependencies = [ "assert_cmd", "async-trait", - "clap 4.5.13", + "clap 4.5.26", "color-print", "cumulus-client-cli", "cumulus-client-collator", @@ -19629,7 +19669,7 @@ dependencies = [ "async-trait", "bincode", "bitvec", - "clap 4.5.13", + "clap 4.5.26", "clap-num", "color-eyre", "colored", @@ -19731,7 +19771,7 @@ version = "1.0.0" dependencies = [ "assert_matches", "async-trait", - "clap 4.5.13", + "clap 4.5.26", "color-eyre", "futures", "futures-timer", @@ -19873,7 +19913,7 @@ dependencies = [ name = "polkadot-voter-bags" version = "7.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.26", "generate-bags", "sp-io 30.0.0", "westend-runtime", @@ -21197,12 +21237,6 @@ dependencies = [ "regex-syntax 0.6.29", ] -[[package]] -name = "regex-automata" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" - [[package]] name = "regex-automata" version = "0.4.8" @@ -21302,7 +21336,7 @@ dependencies = [ name = "remote-ext-tests-bags-list" version = "1.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.26", "frame-system 28.0.0", "log", "pallet-bags-list-remote-tests", @@ -21915,7 +21949,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.18", + "semver 1.0.24", ] [[package]] @@ -22334,7 +22368,7 @@ name = "sc-chain-spec" version = "28.0.0" dependencies = [ "array-bytes", - "clap 4.5.13", + "clap 4.5.26", "docify", "log", "memmap2 0.9.3", @@ -22377,7 +22411,7 @@ version = "0.36.0" dependencies = [ "array-bytes", "chrono", - "clap 4.5.13", + "clap 4.5.26", "fdlimit", "futures", "futures-timer", @@ -23725,7 +23759,7 @@ dependencies = [ name = "sc-storage-monitor" version = "0.16.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.26", "fs4", "log", "sp-core 28.0.0", @@ -24299,9 +24333,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.18" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" dependencies = [ "serde", ] @@ -25613,7 +25647,7 @@ dependencies = [ name = "solochain-template-node" version = "0.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.26", "frame-benchmarking-cli", "frame-metadata-hash-extension 0.1.0", "frame-system 28.0.0", @@ -26996,7 +27030,7 @@ dependencies = [ name = "sp-npos-elections-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 4.5.13", + "clap 4.5.26", "honggfuzz", "rand", "sp-npos-elections 26.0.0", @@ -28220,7 +28254,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" name = "staging-chain-spec-builder" version = "1.6.1" dependencies = [ - "clap 4.5.13", + "clap 4.5.26", "cmd_lib", "docify", "log", @@ -28237,7 +28271,7 @@ version = "3.0.0-dev" dependencies = [ "array-bytes", "assert_cmd", - "clap 4.5.13", + "clap 4.5.26", "clap_complete", "criterion", "futures", @@ -28274,7 +28308,7 @@ dependencies = [ name = "staging-node-inspect" version = "0.12.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.26", "parity-scale-codec", "sc-cli", "sc-client-api", @@ -28619,7 +28653,7 @@ dependencies = [ name = "subkey" version = "9.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.26", "sc-cli", ] @@ -29045,7 +29079,7 @@ dependencies = [ "rand", "reqwest 0.12.9", "scale-info", - "semver 1.0.18", + "semver 1.0.24", "serde", "serde_json", "sp-version 35.0.0", @@ -29458,9 +29492,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tar" -version = "0.4.40" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" +checksum = "c65998313f8e17d0d553d28f91a0df93e4dbbbf770279c7bc21ca0f09ea1a1f6" dependencies = [ "filetime", "libc", @@ -29508,12 +29542,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" dependencies = [ "rustix 0.38.42", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -29560,7 +29594,7 @@ dependencies = [ name = "test-parachain-adder-collator" version = "1.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.26", "futures", "futures-timer", "log", @@ -29607,7 +29641,7 @@ dependencies = [ name = "test-parachain-undying-collator" version = "1.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.26", "futures", "futures-timer", "log", @@ -31903,11 +31937,13 @@ dependencies = [ [[package]] name = "xattr" -version = "1.0.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" +checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909" dependencies = [ "libc", + "linux-raw-sys 0.4.14", + "rustix 0.38.42", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e17f08148b163..18c1dd2c68d2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -538,6 +538,7 @@ members = [ "templates/minimal/node", "templates/minimal/pallets/template", "templates/minimal/runtime", + "templates/parachain", "templates/parachain/node", "templates/parachain/pallets/template", "templates/parachain/runtime", diff --git a/prdoc/pr_7093.prdoc b/prdoc/pr_7093.prdoc new file mode 100644 index 0000000000000..cad4477e8832f --- /dev/null +++ b/prdoc/pr_7093.prdoc @@ -0,0 +1,8 @@ +title: 'initial docify readme with some content #6333' +doc: +- audience: Runtime Dev + description: | + Docifying the README.MD under templates/parachain by adding a Docify. + Also Adding the Cargo.toml under the same folder, essentially making it a crate as Docify acts + for Readmes only under the same crate. +crates: [ ] diff --git a/templates/parachain/Cargo.toml b/templates/parachain/Cargo.toml new file mode 100644 index 0000000000000..84b9d5e29bbe8 --- /dev/null +++ b/templates/parachain/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "parachain-template" +description = "A parachain-template helper crate to keep documentation in sync with the template's components." +version = "0.0.0" +license = "Unlicense" +authors.workspace = true +homepage.workspace = true +repository.workspace = true +edition.workspace = true +publish = false + +[dependencies] +docify = "0.2.9" + +[features] +generate-readme = [] diff --git a/templates/parachain/README.docify.md b/templates/parachain/README.docify.md new file mode 100644 index 0000000000000..47385e0bbf197 --- /dev/null +++ b/templates/parachain/README.docify.md @@ -0,0 +1,254 @@ +

+ +## Table of Contents + +- [Intro](#intro) + +- [Template Structure](#template-structure) + +- [Getting Started](#getting-started) + +- [Starting a Development Chain](#starting-a-development-chain) + + - [Omni Node](#omni-node-prerequisites) + - [Zombienet setup with Omni Node](#zombienet-setup-with-omni-node) + - [Parachain Template Node](#parachain-template-node) + - [Connect with the Polkadot-JS Apps Front-End](#connect-with-the-polkadot-js-apps-front-end) + - [Takeaways](#takeaways) + +- [Runtime development](#runtime-development) +- [Contributing](#contributing) +- [Getting Help](#getting-help) + +## Intro + +- ⏫ This template provides a starting point to build a [parachain](https://wiki.polkadot.network/docs/learn-parachains). + +- ☁️ It is based on the + [Cumulus](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/polkadot_sdk/cumulus/index.html) framework. + +- 🔧 Its runtime is configured with a single custom pallet as a starting point, and a handful of ready-made pallets + such as a [Balances pallet](https://paritytech.github.io/polkadot-sdk/master/pallet_balances/index.html). + +- 👉 Learn more about parachains [here](https://wiki.polkadot.network/docs/learn-parachains) + +## Template Structure + +A Polkadot SDK based project such as this one consists of: + +- 🧮 the [Runtime](./runtime/README.md) - the core logic of the parachain. +- 🎨 the [Pallets](./pallets/README.md) - from which the runtime is constructed. +- 💿 a [Node](./node/README.md) - the binary application, not part of the project default-members list and not compiled unless + building the project with `--workspace` flag, which builds all workspace members, and is an alternative to + [Omni Node](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/omni_node/index.html). + +## Getting Started + +- 🦀 The template is using the Rust language. + +- 👉 Check the + [Rust installation instructions](https://www.rust-lang.org/tools/install) for your system. + +- 🛠️ Depending on your operating system and Rust version, there might be additional + packages required to compile this template - please take note of the Rust compiler output. + +Fetch parachain template code: + +```sh +git clone https://github.com/paritytech/polkadot-sdk-parachain-template.git parachain-template + +cd parachain-template +``` + +## Starting a Development Chain + +The parachain template relies on a hardcoded parachain id which is defined in the runtime code +and referenced throughout the contents of this file as `{{PARACHAIN_ID}}`. Please replace +any command or file referencing this placeholder with the value of the `PARACHAIN_ID` constant: + + + +### Omni Node Prerequisites + +[Omni Node](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/omni_node/index.html) can +be used to run the parachain template's runtime. `polkadot-omni-node` binary crate usage is described at a high-level +[on crates.io](https://crates.io/crates/polkadot-omni-node). + +#### Install `polkadot-omni-node` + +Please see the installation section at [`crates.io/omni-node`](https://crates.io/crates/polkadot-omni-node). + +#### Build `parachain-template-runtime` + +```sh +cargo build --release +``` + +#### Install `staging-chain-spec-builder` + +Please see the installation section at [`crates.io/staging-chain-spec-builder`](https://crates.io/crates/staging-chain-spec-builder). + +#### Use `chain-spec-builder` to generate the `chain_spec.json` file + +```sh +chain-spec-builder create --relay-chain "rococo-local" --para-id {{PARACHAIN_ID}} --runtime \ + target/release/wbuild/parachain-template-runtime/parachain_template_runtime.wasm named-preset development +``` + +**Note**: the `relay-chain` and `para-id` flags are mandatory information required by +Omni Node, and for parachain template case the value for `para-id` must be set to `{{PARACHAIN_ID}}`, since this +is also the value injected through [ParachainInfo](https://docs.rs/staging-parachain-info/0.17.0/staging_parachain_info/) +pallet into the `parachain-template-runtime`'s storage. The `relay-chain` value is set in accordance +with the relay chain ID where this instantiation of parachain-template will connect to. + +#### Run Omni Node + +Start Omni Node with the generated chain spec. We'll start it in development mode (without a relay chain config), producing +and finalizing blocks based on manual seal, configured below to seal a block with each second. + +```bash +polkadot-omni-node --chain --dev --dev-block-time 1000 +``` + +However, such a setup is not close to what would run in production, and for that we need to setup a local +relay chain network that will help with the block finalization. In this guide we'll setup a local relay chain +as well. We'll not do it manually, by starting one node at a time, but we'll use [zombienet](https://paritytech.github.io/zombienet/intro.html). + +Follow through the next section for more details on how to do it. + +### Zombienet setup with Omni Node + +Assuming we continue from the last step of the previous section, we have a chain spec and we need to setup a relay chain. +We can install `zombienet` as described [here](https://paritytech.github.io/zombienet/install.html#installation), and +`zombienet-omni-node.toml` contains the network specification we want to start. + +#### Relay chain prerequisites + +Download the `polkadot` (and the accompanying `polkadot-prepare-worker` and `polkadot-execute-worker`) binaries from +[Polkadot SDK releases](https://github.com/paritytech/polkadot-sdk/releases). Then expose them on `PATH` like so: + +```sh +export PATH="$PATH:" +``` + +#### Update `zombienet-omni-node.toml` with a valid chain spec path + +```toml +# ... +[[parachains]] +id = {{PARACHAIN_ID}} +chain_spec_path = "" +# ... +``` + +#### Start the network + +```sh +zombienet --provider native spawn zombienet-omni-node.toml +``` + +### Parachain Template Node + +As mentioned in the `Template Structure` section, the `node` crate is optionally compiled and it is an alternative +to `Omni Node`. Similarly, it requires setting up a relay chain, and we'll use `zombienet` once more. + +#### Install the `parachain-template-node` + +```sh +cargo install --path node +``` + +#### Setup and start the network + +For setup, please consider the instructions for `zombienet` installation [here](https://paritytech.github.io/zombienet/install.html#installation) +and [relay chain prerequisites](#relay-chain-prerequisites). + +We're left just with starting the network: + +```sh +zombienet --provider native spawn zombienet.toml +``` + +### Connect with the Polkadot-JS Apps Front-End + +- 🌐 You can interact with your local node using the + hosted version of the Polkadot/Substrate Portal: + [relay chain](https://polkadot.js.org/apps/#/explorer?rpc=ws://localhost:9944) + and [parachain](https://polkadot.js.org/apps/#/explorer?rpc=ws://localhost:9988). + +- 🪐 A hosted version is also + available on [IPFS](https://dotapps.io/). + +- 🧑‍🔧 You can also find the source code and instructions for hosting your own instance in the + [`polkadot-js/apps`](https://github.com/polkadot-js/apps) repository. + +### Takeaways + +Development parachains: + +- 🔗 Connect to relay chains, and we showcased how to connect to a local one. +- 🧹 Do not persist the state. +- 💰 Are preconfigured with a genesis state that includes several prefunded development accounts. +- 🧑‍⚖️ Development accounts are used as validators, collators, and `sudo` accounts. + +## Runtime development + +We recommend using [`chopsticks`](https://github.com/AcalaNetwork/chopsticks) when the focus is more on the runtime +development and `OmniNode` is enough as is. + +### Install chopsticks + +To use `chopsticks`, please install the latest version according to the installation [guide](https://github.com/AcalaNetwork/chopsticks?tab=readme-ov-file#install). + +### Build a raw chain spec + +Build the `parachain-template-runtime` as mentioned before in this guide and use `chain-spec-builder` +again but this time by passing `--raw-storage` flag: + +```sh +chain-spec-builder create --raw-storage --relay-chain "rococo-local" --para-id {{PARACHAIN_ID}} --runtime \ + target/release/wbuild/parachain-template-runtime/parachain_template_runtime.wasm named-preset development +``` + +### Start `chopsticks` with the chain spec + +```sh +npx @acala-network/chopsticks@latest --chain-spec +``` + +### Alternatives + +`OmniNode` can be still used for runtime development if using the `--dev` flag, while `parachain-template-node` doesn't +support it at this moment. It can still be used to test a runtime in a full setup where it is started alongside a +relay chain network (see [Parachain Template node](#parachain-template-node) setup). + +## Contributing + +- 🔄 This template is automatically updated after releases in the main [Polkadot SDK monorepo](https://github.com/paritytech/polkadot-sdk). + +- ➡️ Any pull requests should be directed to this [source](https://github.com/paritytech/polkadot-sdk/tree/master/templates/parachain). + +- 😇 Please refer to the monorepo's + [contribution guidelines](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md) and + [Code of Conduct](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CODE_OF_CONDUCT.md). + +## Getting Help + +- 🧑‍🏫 To learn about Polkadot in general, [Polkadot.network](https://polkadot.network/) website is a good starting point. + +- 🧑‍🔧 For technical introduction, [here](https://github.com/paritytech/polkadot-sdk#-documentation) are + the Polkadot SDK documentation resources. + +- 👥 Additionally, there are [GitHub issues](https://github.com/paritytech/polkadot-sdk/issues) and + [Substrate StackExchange](https://substrate.stackexchange.com/). diff --git a/templates/parachain/README.md b/templates/parachain/README.md index c1e333df9e9ee..15e9f7fe61cf0 100644 --- a/templates/parachain/README.md +++ b/templates/parachain/README.md @@ -36,10 +36,10 @@ - ⏫ This template provides a starting point to build a [parachain](https://wiki.polkadot.network/docs/learn-parachains). - ☁️ It is based on the -[Cumulus](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/polkadot_sdk/cumulus/index.html) framework. + [Cumulus](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/polkadot_sdk/cumulus/index.html) framework. - 🔧 Its runtime is configured with a single custom pallet as a starting point, and a handful of ready-made pallets -such as a [Balances pallet](https://paritytech.github.io/polkadot-sdk/master/pallet_balances/index.html). + such as a [Balances pallet](https://paritytech.github.io/polkadot-sdk/master/pallet_balances/index.html). - 👉 Learn more about parachains [here](https://wiki.polkadot.network/docs/learn-parachains) @@ -50,18 +50,18 @@ A Polkadot SDK based project such as this one consists of: - 🧮 the [Runtime](./runtime/README.md) - the core logic of the parachain. - 🎨 the [Pallets](./pallets/README.md) - from which the runtime is constructed. - 💿 a [Node](./node/README.md) - the binary application, not part of the project default-members list and not compiled unless -building the project with `--workspace` flag, which builds all workspace members, and is an alternative to -[Omni Node](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/omni_node/index.html). + building the project with `--workspace` flag, which builds all workspace members, and is an alternative to + [Omni Node](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/omni_node/index.html). ## Getting Started - 🦀 The template is using the Rust language. - 👉 Check the -[Rust installation instructions](https://www.rust-lang.org/tools/install) for your system. + [Rust installation instructions](https://www.rust-lang.org/tools/install) for your system. - 🛠️ Depending on your operating system and Rust version, there might be additional -packages required to compile this template - please take note of the Rust compiler output. + packages required to compile this template - please take note of the Rust compiler output. Fetch parachain template code: @@ -73,6 +73,14 @@ cd parachain-template ## Starting a Development Chain +The parachain template relies on a hardcoded parachain id which is defined in the runtime code +and referenced throughout the contents of this file as `{{PARACHAIN_ID}}`. Please replace +any command or file referencing this placeholder with the value of the `PARACHAIN_ID` constant: + +```rust,ignore +pub const PARACHAIN_ID: u32 = 1000; +``` + ### Omni Node Prerequisites [Omni Node](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/omni_node/index.html) can @@ -96,12 +104,12 @@ Please see the installation section at [`crates.io/staging-chain-spec-builder`]( #### Use `chain-spec-builder` to generate the `chain_spec.json` file ```sh -chain-spec-builder create --relay-chain "rococo-local" --para-id 1000 --runtime \ +chain-spec-builder create --relay-chain "rococo-local" --para-id {{PARACHAIN_ID}} --runtime \ target/release/wbuild/parachain-template-runtime/parachain_template_runtime.wasm named-preset development ``` **Note**: the `relay-chain` and `para-id` flags are mandatory information required by -Omni Node, and for parachain template case the value for `para-id` must be set to `1000`, since this +Omni Node, and for parachain template case the value for `para-id` must be set to `{{PARACHAIN_ID}}`, since this is also the value injected through [ParachainInfo](https://docs.rs/staging-parachain-info/0.17.0/staging_parachain_info/) pallet into the `parachain-template-runtime`'s storage. The `relay-chain` value is set in accordance with the relay chain ID where this instantiation of parachain-template will connect to. @@ -141,7 +149,7 @@ export PATH="$PATH:" ```toml # ... [[parachains]] -id = 1000 +id = {{PARACHAIN_ID}} chain_spec_path = "" # ... ``` @@ -177,15 +185,15 @@ zombienet --provider native spawn zombienet.toml ### Connect with the Polkadot-JS Apps Front-End - 🌐 You can interact with your local node using the -hosted version of the Polkadot/Substrate Portal: -[relay chain](https://polkadot.js.org/apps/#/explorer?rpc=ws://localhost:9944) -and [parachain](https://polkadot.js.org/apps/#/explorer?rpc=ws://localhost:9988). + hosted version of the Polkadot/Substrate Portal: + [relay chain](https://polkadot.js.org/apps/#/explorer?rpc=ws://localhost:9944) + and [parachain](https://polkadot.js.org/apps/#/explorer?rpc=ws://localhost:9988). - 🪐 A hosted version is also -available on [IPFS](https://dotapps.io/). + available on [IPFS](https://dotapps.io/). - 🧑‍🔧 You can also find the source code and instructions for hosting your own instance in the -[`polkadot-js/apps`](https://github.com/polkadot-js/apps) repository. + [`polkadot-js/apps`](https://github.com/polkadot-js/apps) repository. ### Takeaways @@ -211,7 +219,7 @@ Build the `parachain-template-runtime` as mentioned before in this guide and use again but this time by passing `--raw-storage` flag: ```sh -chain-spec-builder create --raw-storage --relay-chain "rococo-local" --para-id 1000 --runtime \ +chain-spec-builder create --raw-storage --relay-chain "rococo-local" --para-id {{PARACHAIN_ID}} --runtime \ target/release/wbuild/parachain-template-runtime/parachain_template_runtime.wasm named-preset development ``` @@ -234,15 +242,15 @@ relay chain network (see [Parachain Template node](#parachain-template-node) set - ➡️ Any pull requests should be directed to this [source](https://github.com/paritytech/polkadot-sdk/tree/master/templates/parachain). - 😇 Please refer to the monorepo's -[contribution guidelines](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md) and -[Code of Conduct](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CODE_OF_CONDUCT.md). + [contribution guidelines](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md) and + [Code of Conduct](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CODE_OF_CONDUCT.md). ## Getting Help - 🧑‍🏫 To learn about Polkadot in general, [Polkadot.network](https://polkadot.network/) website is a good starting point. - 🧑‍🔧 For technical introduction, [here](https://github.com/paritytech/polkadot-sdk#-documentation) are -the Polkadot SDK documentation resources. + the Polkadot SDK documentation resources. - 👥 Additionally, there are [GitHub issues](https://github.com/paritytech/polkadot-sdk/issues) and -[Substrate StackExchange](https://substrate.stackexchange.com/). + [Substrate StackExchange](https://substrate.stackexchange.com/). diff --git a/templates/parachain/runtime/src/genesis_config_presets.rs b/templates/parachain/runtime/src/genesis_config_presets.rs index f1b24e4372476..8cdadca5060ad 100644 --- a/templates/parachain/runtime/src/genesis_config_presets.rs +++ b/templates/parachain/runtime/src/genesis_config_presets.rs @@ -17,6 +17,7 @@ use sp_keyring::Sr25519Keyring; /// The default XCM version to set in genesis config. const SAFE_XCM_VERSION: u32 = xcm::prelude::XCM_VERSION; /// Parachain id used for genesis config presets of parachain template. +#[docify::export_content] pub const PARACHAIN_ID: u32 = 1000; /// Generate the session keys from individual elements. diff --git a/templates/parachain/src/lib.rs b/templates/parachain/src/lib.rs new file mode 100644 index 0000000000000..d3c5b8ba3101a --- /dev/null +++ b/templates/parachain/src/lib.rs @@ -0,0 +1,22 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +// The parachain-template crate helps with keeping the README.md in sync +// with code sections across the components under the template: node, +// pallets & runtime, by using `docify`. + +#[cfg(feature = "generate-readme")] +docify::compile_markdown!("README.docify.md", "README.md"); From d5d9b1276a088a6bd7a8c2c698320dad3d0ee2c4 Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Mon, 20 Jan 2025 12:02:59 +0100 Subject: [PATCH 099/169] Stabilize `ensure_execute_processes_have_correct_num_threads` test (#7253) Saw this test flake a few times, last time [here](https://github.com/paritytech/polkadot-sdk/actions/runs/12834432188/job/35791830215). We first fetch all processes in the test, then query `/proc//stat` for every one of them. When the file was not found, we would error. Now we tolerate not finding this file. Ran 200 times locally without error, before would fail a few times, probably depending on process fluctuation (which I expect to be high on CI runners). --- polkadot/node/core/pvf/tests/it/process.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/polkadot/node/core/pvf/tests/it/process.rs b/polkadot/node/core/pvf/tests/it/process.rs index 353367b394f34..29326365b5baa 100644 --- a/polkadot/node/core/pvf/tests/it/process.rs +++ b/polkadot/node/core/pvf/tests/it/process.rs @@ -77,7 +77,9 @@ fn find_process_by_sid_and_name( let mut found = None; for process in all_processes { - let stat = process.stat().expect("/proc existed above. Potential race occurred"); + let Ok(stat) = process.stat() else { + continue; + }; if stat.session != sid || !process.exe().unwrap().to_str().unwrap().contains(exe_name) { continue From ea27696aeed8e76cfb82492f6f3665948d766fe5 Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Mon, 20 Jan 2025 12:47:29 +0100 Subject: [PATCH 100/169] [pallet-revive] eth-rpc error logging (#7251) Log error instead of failing with an error when block processing fails --------- Co-authored-by: command-bot <> --- .../runtimes/assets/asset-hub-westend/src/lib.rs | 2 +- prdoc/pr_7251.prdoc | 7 +++++++ substrate/frame/revive/rpc/src/client.rs | 4 +++- 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 prdoc/pr_7251.prdoc diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 41f29fe2c56a0..f56c4568f2d1f 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -129,7 +129,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("westmint"), impl_name: alloc::borrow::Cow::Borrowed("westmint"), authoring_version: 1, - spec_version: 1_017_004, + spec_version: 1_017_005, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 16, diff --git a/prdoc/pr_7251.prdoc b/prdoc/pr_7251.prdoc new file mode 100644 index 0000000000000..98e371dc940ff --- /dev/null +++ b/prdoc/pr_7251.prdoc @@ -0,0 +1,7 @@ +title: '[pallet-revive] eth-rpc error logging' +doc: +- audience: Runtime Dev + description: Log error instead of failing with an error when block processing fails +crates: +- name: pallet-revive-eth-rpc + bump: minor diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index c61c5871f76ae..a5a022f97228d 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -310,7 +310,9 @@ impl Client { }; log::debug!(target: LOG_TARGET, "Pushing block: {}", block.number()); - callback(block).await?; + if let Err(err) = callback(block).await { + log::error!(target: LOG_TARGET, "Failed to process block: {err:?}"); + } } log::info!(target: LOG_TARGET, "Block subscription ended"); From 115ff4e98ecc301a3d380a2fc53ec2304647c69d Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Mon, 20 Jan 2025 13:48:25 +0100 Subject: [PATCH 101/169] Apply a few minor fixes found while addressing the fellows PR for weights. (#7098) This PR addresses a few minor issues found while working on the polkadot-fellows PR [https://github.com/polkadot-fellows/runtimes/pull/522](https://github.com/polkadot-fellows/runtimes/pull/522): - Incorrect generic type for `InboundLaneData` in `check_message_lane_weights`. - Renaming leftovers: `assigner_on_demand` -> `on_demand`. --- bridges/bin/runtime-common/src/integrity.rs | 11 +++++++---- bridges/modules/messages/src/lib.rs | 4 ++-- polkadot/runtime/rococo/src/lib.rs | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/bridges/bin/runtime-common/src/integrity.rs b/bridges/bin/runtime-common/src/integrity.rs index 535f1a26e5e89..61dbf09109acc 100644 --- a/bridges/bin/runtime-common/src/integrity.rs +++ b/bridges/bin/runtime-common/src/integrity.rs @@ -21,11 +21,11 @@ use bp_header_chain::ChainWithGrandpa; use bp_messages::{ChainWithMessages, InboundLaneData, MessageNonce}; -use bp_runtime::Chain; +use bp_runtime::{AccountIdOf, Chain}; use codec::Encode; use frame_support::{storage::generator::StorageValue, traits::Get, weights::Weight}; use frame_system::limits; -use pallet_bridge_messages::WeightInfoExt as _; +use pallet_bridge_messages::{ThisChainOf, WeightInfoExt as _}; // Re-export to avoid include all dependencies everywhere. #[doc(hidden)] @@ -364,8 +364,11 @@ pub fn check_message_lane_weights< ); // check that weights allow us to receive delivery confirmations - let max_incoming_inbound_lane_data_proof_size = - InboundLaneData::<()>::encoded_size_hint_u32(this_chain_max_unrewarded_relayers as _); + let max_incoming_inbound_lane_data_proof_size = InboundLaneData::< + AccountIdOf>, + >::encoded_size_hint_u32( + this_chain_max_unrewarded_relayers as _ + ); pallet_bridge_messages::ensure_able_to_receive_confirmation::>( C::max_extrinsic_size(), C::max_extrinsic_weight(), diff --git a/bridges/modules/messages/src/lib.rs b/bridges/modules/messages/src/lib.rs index af14257db99c1..61763186cb021 100644 --- a/bridges/modules/messages/src/lib.rs +++ b/bridges/modules/messages/src/lib.rs @@ -230,8 +230,8 @@ pub mod pallet { // why do we need to know the weight of this (`receive_messages_proof`) call? Because // we may want to return some funds for not-dispatching (or partially dispatching) some // messages to the call origin (relayer). And this is done by returning actual weight - // from the call. But we only know dispatch weight of every messages. So to refund - // relayer because we have not dispatched Message, we need to: + // from the call. But we only know dispatch weight of every message. So to refund + // relayer because we have not dispatched message, we need to: // // ActualWeight = DeclaredWeight - Message.DispatchWeight // diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index b3f2a0033278a..c2c3d35ee5b42 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -1822,7 +1822,7 @@ mod benches { [polkadot_runtime_parachains::initializer, Initializer] [polkadot_runtime_parachains::paras_inherent, ParaInherent] [polkadot_runtime_parachains::paras, Paras] - [polkadot_runtime_parachains::assigner_on_demand, OnDemandAssignmentProvider] + [polkadot_runtime_parachains::on_demand, OnDemandAssignmentProvider] // Substrate [pallet_balances, Balances] [pallet_balances, NisCounterpartBalances] From 377b32b59dd4cda869e496f1b645e62370641a38 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 20 Jan 2025 12:50:24 +0000 Subject: [PATCH 102/169] pallet compiles and all tests pass again --- .../election-provider-multi-block/src/lib.rs | 24 +- .../src/mock/mod.rs | 72 +++--- .../src/mock/signed.rs | 20 +- .../src/mock/staking.rs | 47 ++-- .../src/mock/weight_info.rs | 10 +- .../src/signed/tests.rs | 66 +++--- .../src/types.rs | 4 +- .../src/unsigned/miner.rs | 208 ++++++++++-------- .../src/unsigned/mod.rs | 4 +- .../src/verifier/impls.rs | 7 +- .../src/verifier/mod.rs | 2 - .../src/verifier/tests.rs | 4 +- .../src/weights.rs | 40 ++-- .../solution-type/src/single_page.rs | 4 +- .../election-provider-support/src/bounds.rs | 10 + .../election-provider-support/src/lib.rs | 24 +- substrate/frame/staking/src/pallet/impls.rs | 10 +- 17 files changed, 304 insertions(+), 252 deletions(-) diff --git a/substrate/frame/election-provider-multi-block/src/lib.rs b/substrate/frame/election-provider-multi-block/src/lib.rs index d7c0ef860495a..f7e25552a66ac 100644 --- a/substrate/frame/election-provider-multi-block/src/lib.rs +++ b/substrate/frame/election-provider-multi-block/src/lib.rs @@ -233,7 +233,7 @@ use codec::{Decode, Encode, MaxEncodedLen}; use frame_election_provider_support::{ - onchain, BoundedSupportsOf, ElectionDataProvider, ElectionProvider, PageIndex, + onchain, BoundedSupportsOf, DataProviderBounds, ElectionDataProvider, ElectionProvider, }; use frame_support::{ ensure, @@ -245,7 +245,6 @@ use scale_info::TypeInfo; use sp_arithmetic::traits::Zero; use sp_npos_elections::VoteWeight; use sp_runtime::SaturatedConversion; -use sp_std::prelude::*; use verifier::Verifier; #[cfg(test)] @@ -307,13 +306,6 @@ pub enum ElectionError { SupportPageNotAvailable, } -#[cfg(test)] -impl PartialEq for ElectionError { - fn eq(&self, other: &Self) -> bool { - matches!(self, other) - } -} - impl From for ElectionError { fn from(e: onchain::Error) -> Self { ElectionError::OnChain(e) @@ -1041,9 +1033,10 @@ impl Pallet { T::DataProvider::desired_targets().map_err(ElectionError::DataProvider)?, ); - let limit = Some(T::TargetSnapshotPerBlock::get().saturated_into::()); + let count = T::TargetSnapshotPerBlock::get(); + let bounds = DataProviderBounds { count: Some(count.into()), size: None }; let targets: BoundedVec<_, T::TargetSnapshotPerBlock> = - T::DataProvider::electable_targets(limit, 0) + T::DataProvider::electable_targets(bounds, 0) .and_then(|v| v.try_into().map_err(|_| "try-into failed")) .map_err(ElectionError::DataProvider)?; @@ -1058,9 +1051,10 @@ impl Pallet { /// /// Returns `Ok(num_created)` if operation is okay. pub fn create_voters_snapshot_paged(remaining: PageIndex) -> Result> { - let limit = Some(T::VoterSnapshotPerBlock::get().saturated_into::()); + let count = T::VoterSnapshotPerBlock::get(); + let bounds = DataProviderBounds { count: Some(count.into()), size: None }; let voters: BoundedVec<_, T::VoterSnapshotPerBlock> = - T::DataProvider::electing_voters(limit, remaining) + T::DataProvider::electing_voters(bounds, remaining) .and_then(|v| v.try_into().map_err(|_| "try-into failed")) .map_err(ElectionError::DataProvider)?; @@ -1982,11 +1976,11 @@ mod election_provider { // try submit one signed page: assert_noop!( - SignedPallet::submit_page(Origin::signed(999), 0, Default::default()), + SignedPallet::submit_page(RuntimeOrigin::signed(999), 0, Default::default()), "phase not signed" ); assert_noop!( - SignedPallet::register(Origin::signed(999), Default::default()), + SignedPallet::register(RuntimeOrigin::signed(999), Default::default()), "phase not signed" ); assert_storage_noop!(assert!(::pre_dispatch( diff --git a/substrate/frame/election-provider-multi-block/src/mock/mod.rs b/substrate/frame/election-provider-multi-block/src/mock/mod.rs index 68d6e5827fc1b..05d734a394ca3 100644 --- a/substrate/frame/election-provider-multi-block/src/mock/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/mock/mod.rs @@ -29,7 +29,10 @@ use crate::{ verifier::{self as verifier_pallet, AsynchronousVerifier, Status}, }; use codec::{Decode, Encode, MaxEncodedLen}; -use frame_election_provider_support::NposSolution; +use frame_election_provider_support::{ + bounds::{ElectionBounds, ElectionBoundsBuilder}, + NposSolution, SequentialPhragmen, TryFromUnboundedPagedSupports, +}; pub use frame_support::{assert_noop, assert_ok}; use frame_support::{ derive_impl, @@ -46,19 +49,18 @@ use sp_core::{ testing::{PoolState, TestOffchainExt, TestTransactionPoolExt}, OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, }, - H256, + ConstBool, }; use sp_npos_elections::EvaluateSupport; use sp_runtime::{ bounded_vec, testing::Header, traits::{BlakeTwo256, IdentityLookup}, - PerU16, Perbill, + BuildStorage, PerU16, Perbill, }; pub use staking::*; use std::{sync::Arc, vec}; -pub type Block = sp_runtime::generic::Block; pub type Extrinsic = sp_runtime::testing::TestXt; pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; @@ -82,38 +84,27 @@ frame_support::construct_runtime!( frame_election_provider_support::generate_solution_type!( pub struct TestNposSolution::< - VoterIndex = VoterIndex, - TargetIndex = TargetIndex, - Accuracy = PerU16, - MaxVoters = ConstU32::<2_000> + VoterIndex = VoterIndex, + TargetIndex = TargetIndex, + Accuracy = PerU16, + MaxVoters = ConstU32::<2_000> >(16) ); -impl codec::MaxEncodedLen for TestNposSolution { - fn max_encoded_len() -> usize { - // TODO: https://github.com/paritytech/substrate/issues/10866 - unimplemented!(); - } -} - #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Runtime { - type SS58Prefix = (); - type BaseCallFilter = frame_support::traits::Everything; - type Hash = H256; type Hashing = BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; - type BlockHashCount = (); - type DbWeight = (); type BlockLength = (); type BlockWeights = BlockWeights; type AccountData = pallet_balances::AccountData; - type MaxConsumers = ConstU32<16>; + type Block = frame_system::mocking::MockBlock; } const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); parameter_types! { + pub const ExistentialDeposit: Balance = 1; pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights ::with_sensible_defaults( Weight::from_parts(2u64 * constants::WEIGHT_REF_TIME_PER_SECOND, u64::MAX), @@ -154,7 +145,7 @@ parameter_types! { // we have 12 voters in the default setting, this should be enough to make sure they are not // trimmed accidentally in any test. - #[derive(Encode, Decode, PartialEq, Eq, Debug, scale_info::TypeInfo, MaxEncodedLen)] // TODO: should be removed + #[derive(Encode, Decode, PartialEq, Eq, Debug, scale_info::TypeInfo, MaxEncodedLen)] pub static MaxBackersPerWinner: u32 = 12; // we have 4 targets in total and we desire `Desired` thereof, no single page can represent more // than the min of these two. @@ -175,7 +166,7 @@ impl crate::verifier::Config for Runtime { pub struct MockUnsignedWeightInfo; impl crate::unsigned::WeightInfo for MockUnsignedWeightInfo { fn submit_unsigned(_v: u32, _t: u32, a: u32, _d: u32) -> Weight { - a as Weight + Weight::from_parts(a as u64, 0) } } @@ -206,13 +197,19 @@ impl crate::Config for Runtime { type Pages = Pages; } +parameter_types! { + pub static OnChainElectionBounds: ElectionBounds = ElectionBoundsBuilder::default().build(); +} + impl onchain::Config for Runtime { - type Accuracy = sp_runtime::Perbill; type DataProvider = staking::MockStaking; - type TargetPageSize = (); - type VoterPageSize = (); type MaxBackersPerWinner = MaxBackersPerWinner; type MaxWinnersPerPage = MaxWinnersPerPage; + type Sort = ConstBool; + type Solver = SequentialPhragmen; + type System = Runtime; + type WeightInfo = (); + type Bounds = OnChainElectionBounds; } pub struct MockFallback; @@ -227,8 +224,8 @@ impl ElectionProvider for MockFallback { fn elect(remaining: PageIndex) -> Result, Self::Error> { if OnChianFallback::get() { - onchain::OnChainSequentialPhragmen::::elect(remaining) - .map_err(|_| "OnChainSequentialPhragmen failed") + onchain::OnChainExecution::::elect(remaining) + .map_err(|_| "onchain::OnChainExecution failed") } else { // NOTE: this pesky little trick here is to avoid a clash of type, since `Ok` of our // election provider and our fallback is not the same @@ -236,6 +233,10 @@ impl ElectionProvider for MockFallback { Err(err) } } + + fn ongoing() -> bool { + false + } } impl frame_system::offchain::CreateTransactionBase for Runtime @@ -343,7 +344,7 @@ impl ExtBuilder { pub(crate) fn build_unchecked(self) -> sp_io::TestExternalities { sp_tracing::try_init_simple(); let mut storage = - frame_system::GenesisConfig::default().build_storage::().unwrap(); + frame_system::GenesisConfig::::default().build_storage().unwrap(); let _ = pallet_balances::GenesisConfig:: { balances: vec![ @@ -512,7 +513,7 @@ pub fn multi_block_events() -> Vec> { System::events() .into_iter() .map(|r| r.event) - .filter_map(|e| if let Event::MultiBlock(inner) = e { Some(inner) } else { None }) + .filter_map(|e| if let RuntimeEvent::MultiBlock(inner) = e { Some(inner) } else { None }) .collect::>() } @@ -521,7 +522,9 @@ pub fn verifier_events() -> Vec> { System::events() .into_iter() .map(|r| r.event) - .filter_map(|e| if let Event::VerifierPallet(inner) = e { Some(inner) } else { None }) + .filter_map( + |e| if let RuntimeEvent::VerifierPallet(inner) = e { Some(inner) } else { None }, + ) .collect::>() } @@ -597,7 +600,7 @@ pub fn roll_to_with_ocw(n: BlockNumber, maybe_pool: Option .into_iter() .map(|uxt| ::decode(&mut &*uxt).unwrap()) .for_each(|xt| { - xt.call.dispatch(frame_system::RawOrigin::None.into()).unwrap(); + xt.function.dispatch(frame_system::RawOrigin::None.into()).unwrap(); }); pool.try_write().unwrap().transactions.clear(); } @@ -655,3 +658,8 @@ pub fn raw_paged_solution_low_score() -> PagedRawSolution { pub fn balances(who: AccountId) -> (Balance, Balance) { (Balances::free_balance(who), Balances::reserved_balance(who)) } + +/// Election bounds based on just the given count. +pub fn bound_by_count(count: Option) -> DataProviderBounds { + DataProviderBounds { count: count.map(|x| x.into()), size: None } +} diff --git a/substrate/frame/election-provider-multi-block/src/mock/signed.rs b/substrate/frame/election-provider-multi-block/src/mock/signed.rs index 313ca13f698a8..0f356dd42cdba 100644 --- a/substrate/frame/election-provider-multi-block/src/mock/signed.rs +++ b/substrate/frame/election-provider-multi-block/src/mock/signed.rs @@ -15,11 +15,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::{Balance, Balances, Event, Pages, Runtime, SignedPallet, System}; +use super::{Balance, Balances, Pages, Runtime, RuntimeEvent, SignedPallet, System}; use crate::{ mock::{ balances, multi_block_events, roll_next, roll_to_signed_validation_open, verifier_events, - AccountId, Origin, VerifierPallet, + AccountId, RuntimeOrigin, VerifierPallet, }, signed::{self as signed_pallet, Event as SignedEvent, Submissions}, verifier::{self, AsynchronousVerifier, SolutionDataProvider, VerificationResult, Verifier}, @@ -27,7 +27,8 @@ use crate::{ }; use frame_election_provider_support::PageIndex; use frame_support::{ - assert_ok, pallet_prelude::*, parameter_types, traits::EstimateCallFee, BoundedVec, + assert_ok, dispatch::PostDispatchInfo, pallet_prelude::*, parameter_types, + traits::EstimateCallFee, BoundedVec, }; use frame_system::pallet_prelude::*; use sp_npos_elections::ElectionScore; @@ -61,10 +62,7 @@ impl SolutionDataProvider for MockSignedPhase { pub struct FixedCallFee; impl EstimateCallFee, Balance> for FixedCallFee { - fn estimate_call_fee( - _: &signed_pallet::Call, - _: frame_support::weights::PostDispatchInfo, - ) -> Balance { + fn estimate_call_fee(_: &signed_pallet::Call, _: PostDispatchInfo) -> Balance { 1 } } @@ -79,7 +77,7 @@ parameter_types! { } impl crate::signed::Config for Runtime { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type Currency = Balances; type DepositBase = SignedDepositBase; type DepositPerPage = SignedDepositPerPage; @@ -128,7 +126,7 @@ pub fn signed_events() -> Vec> { System::events() .into_iter() .map(|r| r.event) - .filter_map(|e| if let Event::SignedPallet(inner) = e { Some(inner) } else { None }) + .filter_map(|e| if let RuntimeEvent::SignedPallet(inner) = e { Some(inner) } else { None }) .collect::>() } @@ -137,7 +135,7 @@ pub fn load_signed_for_verification(who: AccountId, paged: PagedRawSolution, + bounds: DataProviderBounds, remaining: PageIndex, ) -> data_provider::Result> { let targets = Targets::get(); @@ -62,7 +65,7 @@ impl ElectionDataProvider for MockStaking { if remaining != 0 { return Err("targets shall not have more than a single page") } - if maybe_max_len.map_or(false, |max_len| targets.len() > max_len) { + if bounds.slice_exhausted(&targets) { return Err("Targets too big") } @@ -70,7 +73,7 @@ impl ElectionDataProvider for MockStaking { } fn electing_voters( - maybe_max_len: Option, + bounds: DataProviderBounds, remaining: PageIndex, ) -> data_provider::Result< Vec<(AccountId, VoteWeight, BoundedVec)>, @@ -83,7 +86,7 @@ impl ElectionDataProvider for MockStaking { } // take as many as you can. - if let Some(max_len) = maybe_max_len { + if let Some(max_len) = bounds.count.map(|c| c.0 as usize) { voters.truncate(max_len) } @@ -111,7 +114,7 @@ impl ElectionDataProvider for MockStaking { now + EpochLength::get() - now % EpochLength::get() } - #[cfg(any(feature = "runtime-benchmarks", test))] + #[cfg(feature = "runtime-benchmarks")] fn put_snapshot( voters: Vec<(AccountId, VoteWeight, BoundedVec)>, targets: Vec, @@ -121,13 +124,13 @@ impl ElectionDataProvider for MockStaking { Voters::set(voters); } - #[cfg(any(feature = "runtime-benchmarks", test))] + #[cfg(feature = "runtime-benchmarks")] fn clear() { Targets::set(vec![]); Voters::set(vec![]); } - #[cfg(any(feature = "runtime-benchmarks", test))] + #[cfg(feature = "runtime-benchmarks")] fn add_voter( voter: AccountId, weight: VoteWeight, @@ -138,7 +141,7 @@ impl ElectionDataProvider for MockStaking { Voters::set(current); } - #[cfg(any(feature = "runtime-benchmarks", test))] + #[cfg(feature = "runtime-benchmarks")] fn add_target(target: AccountId) { use super::ExistentialDeposit; @@ -157,7 +160,7 @@ impl ElectionDataProvider for MockStaking { #[cfg(test)] mod tests { use super::*; - use crate::mock::ExtBuilder; + use crate::mock::{bound_by_count, ExtBuilder}; #[test] fn targets() { @@ -165,29 +168,29 @@ mod tests { assert_eq!(Targets::get().len(), 4); // any non-zero page is error - assert!(MockStaking::electable_targets(None, 1).is_err()); - assert!(MockStaking::electable_targets(None, 2).is_err()); + assert!(MockStaking::electable_targets(bound_by_count(None), 1).is_err()); + assert!(MockStaking::electable_targets(bound_by_count(None), 2).is_err()); // but 0 is fine. - assert_eq!(MockStaking::electable_targets(None, 0).unwrap().len(), 4); + assert_eq!(MockStaking::electable_targets(bound_by_count(None), 0).unwrap().len(), 4); // fetch less targets is error. - assert!(MockStaking::electable_targets(Some(2), 0).is_err()); + assert!(MockStaking::electable_targets(bound_by_count(Some(2)), 0).is_err()); // more targets is fine. - assert!(MockStaking::electable_targets(Some(4), 0).is_ok()); - assert!(MockStaking::electable_targets(Some(5), 0).is_ok()); + assert!(MockStaking::electable_targets(bound_by_count(Some(4)), 0).is_ok()); + assert!(MockStaking::electable_targets(bound_by_count(Some(5)), 0).is_ok()); }); } #[test] fn multi_page_votes() { ExtBuilder::full().build_and_execute(|| { - assert_eq!(MockStaking::electing_voters(None, 0).unwrap().len(), 12); + assert_eq!(MockStaking::electing_voters(bound_by_count(None), 0).unwrap().len(), 12); assert!(LastIteratedVoterIndex::get().is_none()); assert_eq!( - MockStaking::electing_voters(Some(4), 0) + MockStaking::electing_voters(bound_by_count(Some(4)), 0) .unwrap() .into_iter() .map(|(x, _, _)| x) @@ -197,7 +200,7 @@ mod tests { assert!(LastIteratedVoterIndex::get().is_none()); assert_eq!( - MockStaking::electing_voters(Some(4), 2) + MockStaking::electing_voters(bound_by_count(Some(4)), 2) .unwrap() .into_iter() .map(|(x, _, _)| x) @@ -207,7 +210,7 @@ mod tests { assert_eq!(LastIteratedVoterIndex::get().unwrap(), 4); assert_eq!( - MockStaking::electing_voters(Some(4), 1) + MockStaking::electing_voters(bound_by_count(Some(4)), 1) .unwrap() .into_iter() .map(|(x, _, _)| x) @@ -217,7 +220,7 @@ mod tests { assert_eq!(LastIteratedVoterIndex::get().unwrap(), 8); assert_eq!( - MockStaking::electing_voters(Some(4), 0) + MockStaking::electing_voters(bound_by_count(Some(4)), 0) .unwrap() .into_iter() .map(|(x, _, _)| x) diff --git a/substrate/frame/election-provider-multi-block/src/mock/weight_info.rs b/substrate/frame/election-provider-multi-block/src/mock/weight_info.rs index 9bdcfc5d5ef4f..7db05d4d2476d 100644 --- a/substrate/frame/election-provider-multi-block/src/mock/weight_info.rs +++ b/substrate/frame/election-provider-multi-block/src/mock/weight_info.rs @@ -18,8 +18,8 @@ // TODO: would love to ditch this, too big to handle here. use crate::{self as multi_block}; -use frame_support::dispatch::Weight; -use sp_runtime::traits::Zero; +use frame_support::weights::Weight; +use sp_runtime::traits::{Bounded, Zero}; frame_support::parameter_types! { pub static MockWeightInfo: bool = false; @@ -87,7 +87,8 @@ impl multi_block::weights::WeightInfo for DualMockWeightInfo { if MockWeightInfo::get() { // 10 base // 5 per edge. - (10 as Weight).saturating_add((5 as Weight).saturating_mul(a as Weight)) + let ref_time = 10 + 5 * a; + Weight::from_parts(ref_time as u64, Default::default()) } else { <() as multi_block::weights::WeightInfo>::submit_unsigned(v, t, a, d) } @@ -96,7 +97,8 @@ impl multi_block::weights::WeightInfo for DualMockWeightInfo { if MockWeightInfo::get() { // 10 base // 5 per edge. - (10 as Weight).saturating_add((5 as Weight).saturating_mul(a as Weight)) + let ref_time = 10 + 5 * a; + Weight::from_parts(ref_time as u64, Default::default()) } else { <() as multi_block::weights::WeightInfo>::feasibility_check(v, t, a, d) } diff --git a/substrate/frame/election-provider-multi-block/src/signed/tests.rs b/substrate/frame/election-provider-multi-block/src/signed/tests.rs index cf8b4753a3b69..23ace5708c1df 100644 --- a/substrate/frame/election-provider-multi-block/src/signed/tests.rs +++ b/substrate/frame/election-provider-multi-block/src/signed/tests.rs @@ -1,9 +1,8 @@ use super::{Event as SignedEvent, *}; use crate::mock::*; +use sp_core::bounded_vec; mod calls { - use frame_support::bounded_vec; - use super::*; #[test] @@ -15,7 +14,7 @@ mod calls { assert_eq!(balances(99), (100, 0)); let score = ElectionScore { minimal_stake: 100, ..Default::default() }; - assert_ok!(SignedPallet::register(Origin::signed(99), score)); + assert_ok!(SignedPallet::register(RuntimeOrigin::signed(99), score)); assert_eq!(balances(99), (95, 5)); assert_eq!(Submissions::::metadata_iter(1).count(), 0); @@ -41,7 +40,7 @@ mod calls { // second ones submits assert_eq!(balances(999), (100, 0)); let score = ElectionScore { minimal_stake: 90, ..Default::default() }; - assert_ok!(SignedPallet::register(Origin::signed(999), score)); + assert_ok!(SignedPallet::register(RuntimeOrigin::signed(999), score)); assert_eq!(balances(999), (95, 5)); assert_eq!( Submissions::::metadata_of(0, 999).unwrap(), @@ -71,7 +70,7 @@ mod calls { // submit again with a new score. assert_noop!( SignedPallet::register( - Origin::signed(999), + RuntimeOrigin::signed(999), ElectionScore { minimal_stake: 80, ..Default::default() } ), "Duplicate", @@ -89,7 +88,7 @@ mod calls { let assert_reserved = |x| assert_eq!(balances(x), (95, 5)); let assert_unreserved = |x| assert_eq!(balances(x), (100, 0)); - assert_ok!(SignedPallet::register(Origin::signed(91), score_from(100))); + assert_ok!(SignedPallet::register(RuntimeOrigin::signed(91), score_from(100))); assert_eq!(*Submissions::::leaderboard(0), vec![(91, score_from(100))]); assert_reserved(91); assert!( @@ -97,7 +96,7 @@ mod calls { ); // weaker one comes while we have space. - assert_ok!(SignedPallet::register(Origin::signed(92), score_from(90))); + assert_ok!(SignedPallet::register(RuntimeOrigin::signed(92), score_from(90))); assert_eq!( *Submissions::::leaderboard(0), vec![(92, score_from(90)), (91, score_from(100))] @@ -109,7 +108,7 @@ mod calls { ] if x == 92)); // stronger one comes while we have have space. - assert_ok!(SignedPallet::register(Origin::signed(93), score_from(110))); + assert_ok!(SignedPallet::register(RuntimeOrigin::signed(93), score_from(110))); assert_eq!( *Submissions::::leaderboard(0), vec![(92, score_from(90)), (91, score_from(100)), (93, score_from(110))] @@ -122,7 +121,10 @@ mod calls { ] if x == 93)); // weaker one comes while we don't have space. - assert_noop!(SignedPallet::register(Origin::signed(94), score_from(80)), "QueueFull"); + assert_noop!( + SignedPallet::register(RuntimeOrigin::signed(94), score_from(80)), + "QueueFull" + ); assert_eq!( *Submissions::::leaderboard(0), vec![(92, score_from(90)), (91, score_from(100)), (93, score_from(110))] @@ -139,7 +141,7 @@ mod calls { )); // stronger one comes while we don't have space. Eject the weakest - assert_ok!(SignedPallet::register(Origin::signed(94), score_from(120))); + assert_ok!(SignedPallet::register(RuntimeOrigin::signed(94), score_from(120))); assert_eq!( *Submissions::::leaderboard(0), vec![(91, score_from(100)), (93, score_from(110)), (94, score_from(120))] @@ -158,7 +160,7 @@ mod calls { )); // another stronger one comes, only replace the weakest. - assert_ok!(SignedPallet::register(Origin::signed(95), score_from(105))); + assert_ok!(SignedPallet::register(RuntimeOrigin::signed(95), score_from(105))); assert_eq!( *Submissions::::leaderboard(0), vec![(95, score_from(105)), (93, score_from(110)), (94, score_from(120))] @@ -187,16 +189,16 @@ mod calls { assert_full_snapshot(); assert_ok!(SignedPallet::register( - Origin::signed(99), + RuntimeOrigin::signed(99), ElectionScore { minimal_stake: 100, ..Default::default() } )); assert_eq!(balances(99), (95, 5)); // not submitted, cannot bailout. - assert_noop!(SignedPallet::bail(Origin::signed(999)), "NoSubmission"); + assert_noop!(SignedPallet::bail(RuntimeOrigin::signed(999)), "NoSubmission"); // can bail. - assert_ok!(SignedPallet::bail(Origin::signed(99))); + assert_ok!(SignedPallet::bail(RuntimeOrigin::signed(99))); // 20% of the deposit returned, which is 1, 4 is slashed. assert_eq!(balances(99), (96, 0)); assert_no_data_for(0, 99); @@ -215,21 +217,21 @@ mod calls { assert_full_snapshot(); assert_noop!( - SignedPallet::submit_page(Origin::signed(99), 0, Default::default()), + SignedPallet::submit_page(RuntimeOrigin::signed(99), 0, Default::default()), "NotRegistered" ); assert_ok!(SignedPallet::register( - Origin::signed(99), + RuntimeOrigin::signed(99), ElectionScore { minimal_stake: 100, ..Default::default() } )); assert_noop!( - SignedPallet::submit_page(Origin::signed(99), 3, Default::default()), + SignedPallet::submit_page(RuntimeOrigin::signed(99), 3, Default::default()), "BadPageIndex" ); assert_noop!( - SignedPallet::submit_page(Origin::signed(99), 4, Default::default()), + SignedPallet::submit_page(RuntimeOrigin::signed(99), 4, Default::default()), "BadPageIndex" ); @@ -237,7 +239,11 @@ mod calls { assert_eq!(balances(99), (95, 5)); // add the first page. - assert_ok!(SignedPallet::submit_page(Origin::signed(99), 0, Some(Default::default()))); + assert_ok!(SignedPallet::submit_page( + RuntimeOrigin::signed(99), + 0, + Some(Default::default()) + )); assert_eq!(Submissions::::pages_of(0, 99).count(), 1); assert_eq!(balances(99), (94, 6)); assert_eq!( @@ -246,12 +252,20 @@ mod calls { ); // replace it again, nada. - assert_ok!(SignedPallet::submit_page(Origin::signed(99), 0, Some(Default::default()))); + assert_ok!(SignedPallet::submit_page( + RuntimeOrigin::signed(99), + 0, + Some(Default::default()) + )); assert_eq!(Submissions::::pages_of(0, 99).count(), 1); assert_eq!(balances(99), (94, 6)); // add a new one. - assert_ok!(SignedPallet::submit_page(Origin::signed(99), 1, Some(Default::default()))); + assert_ok!(SignedPallet::submit_page( + RuntimeOrigin::signed(99), + 1, + Some(Default::default()) + )); assert_eq!(Submissions::::pages_of(0, 99).count(), 2); assert_eq!(balances(99), (93, 7)); assert_eq!( @@ -260,7 +274,7 @@ mod calls { ); // remove one, deposit is back. - assert_ok!(SignedPallet::submit_page(Origin::signed(99), 0, None)); + assert_ok!(SignedPallet::submit_page(RuntimeOrigin::signed(99), 0, None)); assert_eq!(Submissions::::pages_of(0, 99).count(), 1); assert_eq!(balances(99), (94, 6)); assert_eq!( @@ -283,8 +297,6 @@ mod calls { } mod e2e { - use frame_election_provider_support::Support; - use super::*; #[test] fn good_bad_evil() { @@ -298,9 +310,9 @@ mod e2e { { let score = ElectionScore { minimal_stake: 10, sum_stake: 10, sum_stake_squared: 100 }; - assert_ok!(SignedPallet::register(Origin::signed(99), score)); + assert_ok!(SignedPallet::register(RuntimeOrigin::signed(99), score)); assert_ok!(SignedPallet::submit_page( - Origin::signed(99), + RuntimeOrigin::signed(99), 0, Some(Default::default()) )); @@ -320,7 +332,7 @@ mod e2e { { let mut score = strong_score; score.minimal_stake *= 2; - assert_ok!(SignedPallet::register(Origin::signed(92), score)); + assert_ok!(SignedPallet::register(RuntimeOrigin::signed(92), score)); assert_eq!(balances(92), (95, 5)); // we don't even bother to submit a page.. } diff --git a/substrate/frame/election-provider-multi-block/src/types.rs b/substrate/frame/election-provider-multi-block/src/types.rs index c73ce328a4ed7..a70fdf0612b80 100644 --- a/substrate/frame/election-provider-multi-block/src/types.rs +++ b/substrate/frame/election-provider-multi-block/src/types.rs @@ -368,9 +368,9 @@ impl Phase { #[cfg(test)] mod pagify { - use frame_support::{bounded_vec, traits::ConstU32, BoundedVec}; - use super::{PadSolutionPages, Pagify}; + use frame_support::{traits::ConstU32, BoundedVec}; + use sp_core::bounded_vec; #[test] fn pagify_works() { diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs b/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs index 7a7ded1e085e9..0f3465a38939b 100644 --- a/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs +++ b/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs @@ -23,7 +23,7 @@ use crate::{ }; use codec::Encode; use frame_election_provider_support::{ExtendedBalance, NposSolver, Support, VoteWeight}; -use frame_support::{pallet_prelude::*, traits::Get, BoundedVec}; +use frame_support::{traits::Get, BoundedVec}; use frame_system::pallet_prelude::*; use sp_runtime::{ offchain::storage::{MutateStorageError, StorageValueRef}, @@ -680,15 +680,22 @@ impl OffchainWorkerMiner { } fn submit_call(call: Call) -> Result<(), OffchainMinerError> { + // TODO: need to pagify the unsigned solution as well, maybe sublog!( debug, "unsigned::ocw-miner", "miner submitting a solution as an unsigned transaction" ); - frame_system::offchain::SubmitTransaction::>::submit_unsigned_transaction( - call.into(), - ) - .map_err(|_| OffchainMinerError::PoolSubmissionFailed) + let xt = T::create_inherent(call.into()); + frame_system::offchain::SubmitTransaction::>::submit_transaction(xt) + .map(|_| { + sublog!( + debug, + "unsigned::ocw-miner", + "miner submitted a solution as an unsigned transaction", + ); + }) + .map_err(|_| OffchainMinerError::PoolSubmissionFailed) } /// Attempt to restore a solution from cache. Otherwise, compute it fresh. Either way, @@ -858,7 +865,8 @@ impl OffchainWorkerMiner { mod trim_weight_length { use super::*; use crate::{mock::*, verifier::Verifier}; - use frame_election_provider_support::TryIntoBoundedSupportsVec; + use frame_election_provider_support::TryFromUnboundedPagedSupports; + use frame_support::pallet_prelude::*; use sp_npos_elections::Support; #[test] @@ -902,46 +910,48 @@ mod trim_weight_length { // all will stay vec![(40, Support { total: 9, voters: vec![(2, 2), (3, 3), (4, 4)] })] ] - .try_into_bounded_supports_vec() + .try_from_unbounded_paged() .unwrap() ); }); // now we get to the real test... - ExtBuilder::unsigned().miner_weight(4).build_and_execute(|| { - // first, replace the stake of all voters with their account id. - let mut current_voters = Voters::get(); - current_voters.iter_mut().for_each(|(who, stake, ..)| *stake = *who); - Voters::set(current_voters); + ExtBuilder::unsigned() + .miner_weight(Weight::from_parts(4, u64::MAX)) + .build_and_execute(|| { + // first, replace the stake of all voters with their account id. + let mut current_voters = Voters::get(); + current_voters.iter_mut().for_each(|(who, stake, ..)| *stake = *who); + Voters::set(current_voters); - // with 1 weight unit per voter, this can only support 4 voters, despite having 12 in - // the snapshot. - roll_to_snapshot_created(); - ensure_voters(3, 12); + // with 1 weight unit per voter, this can only support 4 voters, despite having 12 + // in the snapshot. + roll_to_snapshot_created(); + ensure_voters(3, 12); - let solution = mine_full_solution().unwrap(); - assert_eq!( - solution.solution_pages.iter().map(|page| page.voter_count()).sum::(), - 4 - ); + let solution = mine_full_solution().unwrap(); + assert_eq!( + solution.solution_pages.iter().map(|page| page.voter_count()).sum::(), + 4 + ); - load_mock_signed_and_start(solution); - let supports = roll_to_full_verification(); + load_mock_signed_and_start(solution); + let supports = roll_to_full_verification(); - // a solution is queued. - assert!(VerifierPallet::queued_score().is_some()); + // a solution is queued. + assert!(VerifierPallet::queued_score().is_some()); - assert_eq!( - supports, - vec![ - vec![], - vec![(30, Support { total: 7, voters: vec![(7, 7)] })], - vec![(40, Support { total: 9, voters: vec![(2, 2), (3, 3), (4, 4)] })] - ] - .try_into_bounded_supports_vec() - .unwrap() - ); - }) + assert_eq!( + supports, + vec![ + vec![], + vec![(30, Support { total: 7, voters: vec![(7, 7)] })], + vec![(40, Support { total: 9, voters: vec![(2, 2), (3, 3), (4, 4)] })] + ] + .try_from_unbounded_paged() + .unwrap() + ); + }) } #[test] @@ -984,77 +994,81 @@ mod trim_weight_length { (30, Support { total: 2, voters: vec![(2, 2)] }) ] ] - .try_into_bounded_supports_vec() + .try_from_unbounded_paged() .unwrap() ); }); // now we get to the real test... - ExtBuilder::unsigned().miner_weight(4).build_and_execute(|| { - // first, replace the stake of all voters with their account id. - let mut current_voters = Voters::get(); - current_voters.iter_mut().for_each(|(who, stake, ..)| *stake = *who); - Voters::set(current_voters); + ExtBuilder::unsigned() + .miner_weight(Weight::from_parts(4, u64::MAX)) + .build_and_execute(|| { + // first, replace the stake of all voters with their account id. + let mut current_voters = Voters::get(); + current_voters.iter_mut().for_each(|(who, stake, ..)| *stake = *who); + Voters::set(current_voters); - roll_to_snapshot_created(); - ensure_voters(3, 12); + roll_to_snapshot_created(); + ensure_voters(3, 12); - let solution = mine_solution(2).unwrap(); - assert_eq!( - solution.solution_pages.iter().map(|page| page.voter_count()).sum::(), - 4 - ); + let solution = mine_solution(2).unwrap(); + assert_eq!( + solution.solution_pages.iter().map(|page| page.voter_count()).sum::(), + 4 + ); - load_mock_signed_and_start(solution); - let supports = roll_to_full_verification(); + load_mock_signed_and_start(solution); + let supports = roll_to_full_verification(); - // a solution is queued. - assert!(VerifierPallet::queued_score().is_some()); + // a solution is queued. + assert!(VerifierPallet::queued_score().is_some()); - assert_eq!( - supports, - vec![ - vec![], - vec![(10, Support { total: 8, voters: vec![(8, 8)] })], + assert_eq!( + supports, vec![ - (10, Support { total: 5, voters: vec![(1, 1), (4, 4)] }), - (30, Support { total: 2, voters: vec![(2, 2)] }) + vec![], + vec![(10, Support { total: 8, voters: vec![(8, 8)] })], + vec![ + (10, Support { total: 5, voters: vec![(1, 1), (4, 4)] }), + (30, Support { total: 2, voters: vec![(2, 2)] }) + ] ] - ] - .try_into_bounded_supports_vec() - .unwrap() - ); - }) + .try_from_unbounded_paged() + .unwrap() + ); + }) } #[test] fn trim_weight_too_much_makes_solution_invalid() { // with just 1 units, we can support 1 voter. This is not enough to have 2 winner which we // want. - ExtBuilder::unsigned().miner_weight(1).build_and_execute(|| { - let mut current_voters = Voters::get(); - current_voters.iter_mut().for_each(|(who, stake, ..)| *stake = *who); - Voters::set(current_voters); + ExtBuilder::unsigned() + .miner_weight(Weight::from_parts(1, u64::MAX)) + .build_and_execute(|| { + let mut current_voters = Voters::get(); + current_voters.iter_mut().for_each(|(who, stake, ..)| *stake = *who); + Voters::set(current_voters); - roll_to_snapshot_created(); - ensure_voters(3, 12); + roll_to_snapshot_created(); + ensure_voters(3, 12); - let solution = mine_full_solution().unwrap(); - assert_eq!( - solution.solution_pages.iter().map(|page| page.voter_count()).sum::(), - 1 - ); + let solution = mine_full_solution().unwrap(); + assert_eq!( + solution.solution_pages.iter().map(|page| page.voter_count()).sum::(), + 1 + ); - load_mock_signed_and_start(solution); - let supports = roll_to_full_verification(); + load_mock_signed_and_start(solution); + let supports = roll_to_full_verification(); - // nothing is queued - assert!(VerifierPallet::queued_score().is_none()); - assert_eq!( - supports, - vec![vec![], vec![], vec![]].try_into_bounded_supports_vec().unwrap() - ); - }) + // nothing is queued + assert!(VerifierPallet::queued_score().is_none()); + assert_eq!( + supports, + vec![vec![], vec![], vec![]].try_from_unbounded_paged().unwrap() + ); + }) } #[test] @@ -1098,7 +1112,7 @@ mod trim_weight_length { ], vec![(40, Support { total: 9, voters: vec![(2, 2), (3, 3), (4, 4)] })] ] - .try_into_bounded_supports_vec() + .try_from_unbounded_paged() .unwrap() ); }); @@ -1137,7 +1151,7 @@ mod trim_weight_length { ], vec![(40, Support { total: 9, voters: vec![(2, 2), (3, 3), (4, 4)] })] ] - .try_into_bounded_supports_vec() + .try_from_unbounded_paged() .unwrap() ); }); @@ -1148,7 +1162,7 @@ mod trim_weight_length { mod base_miner { use super::*; use crate::{mock::*, Snapshot}; - use frame_election_provider_support::TryIntoBoundedSupportsVec; + use frame_election_provider_support::TryFromUnboundedPagedSupports; use sp_npos_elections::Support; use sp_runtime::PerU16; @@ -1222,7 +1236,7 @@ mod base_miner { } ) ]] - .try_into_bounded_supports_vec() + .try_from_unbounded_paged() .unwrap() ); @@ -1310,7 +1324,7 @@ mod base_miner { (40, Support { total: 25, voters: vec![(2, 10), (3, 10), (4, 5)] }) ] ] - .try_into_bounded_supports_vec() + .try_from_unbounded_paged() .unwrap() ); @@ -1401,7 +1415,7 @@ mod base_miner { (40, Support { total: 25, voters: vec![(3, 10), (4, 10), (2, 5)] }) ] ] - .try_into_bounded_supports_vec() + .try_from_unbounded_paged() .unwrap() ); @@ -1472,7 +1486,7 @@ mod base_miner { (40, Support { total: 25, voters: vec![(2, 10), (3, 10), (4, 5)] }) ] ] - .try_into_bounded_supports_vec() + .try_from_unbounded_paged() .unwrap() ); @@ -1571,7 +1585,7 @@ mod base_miner { (40, Support { total: 25, voters: vec![(2, 10), (3, 10), (4, 5)] }) ] ] - .try_into_bounded_supports_vec() + .try_from_unbounded_paged() .unwrap() ); @@ -1646,7 +1660,7 @@ mod base_miner { ) ] ] - .try_into_bounded_supports_vec() + .try_from_unbounded_paged() .unwrap() ); }) @@ -1787,10 +1801,12 @@ mod offchain_worker_miner { let encoded = pool.read().transactions[0].clone(); let extrinsic: Extrinsic = codec::Decode::decode(&mut &*encoded).unwrap(); - let call = extrinsic.call; + let call = extrinsic.function; assert!(matches!( call, - crate::mock::Call::UnsignedPallet(crate::unsigned::Call::submit_unsigned { .. }) + crate::mock::RuntimeCall::UnsignedPallet( + crate::unsigned::Call::submit_unsigned { .. } + ) )); }) } diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs b/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs index d9878f34c3832..932a513129e0a 100644 --- a/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs @@ -34,7 +34,7 @@ mod pallet { verifier::Verifier, }; use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; + use frame_system::{offchain::CreateInherent, pallet_prelude::*}; use sp_runtime::traits::SaturatedConversion; use sp_std::prelude::*; @@ -61,7 +61,7 @@ mod pallet { #[pallet::config] #[pallet::disable_frame_system_supertrait_check] - pub trait Config: crate::Config { + pub trait Config: crate::Config + CreateInherent> { /// The repeat threshold of the offchain worker. /// /// For example, if it is 5, that means that at least 5 blocks will elapse between attempts diff --git a/substrate/frame/election-provider-multi-block/src/verifier/impls.rs b/substrate/frame/election-provider-multi-block/src/verifier/impls.rs index 89725bf69c979..3706a6b1a1837 100644 --- a/substrate/frame/election-provider-multi-block/src/verifier/impls.rs +++ b/substrate/frame/election-provider-multi-block/src/verifier/impls.rs @@ -15,8 +15,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// TODO: clean and standardize the imports - use super::*; use crate::{helpers, SolutionOf, SupportsOf}; use codec::{Decode, Encode, MaxEncodedLen}; @@ -27,12 +25,11 @@ use frame_support::{ traits::{Defensive, Get}, }; use frame_system::pallet_prelude::*; +use pallet::*; use sp_npos_elections::{ElectionScore, EvaluateSupport}; use sp_runtime::{Perbill, RuntimeDebug}; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; -use pallet::*; - /// The status of this pallet. #[derive(Encode, Decode, scale_info::TypeInfo, Clone, Copy, MaxEncodedLen, RuntimeDebug)] #[cfg_attr(any(test, debug_assertions), derive(PartialEq, Eq))] @@ -96,7 +93,7 @@ pub(crate) mod pallet { use crate::{types::SupportsOf, verifier::Verifier}; use super::*; - use frame_support::pallet_prelude::{ValueQuery, *}; + use frame_support::pallet_prelude::ValueQuery; use sp_npos_elections::evaluate_support; use sp_runtime::Perbill; diff --git a/substrate/frame/election-provider-multi-block/src/verifier/mod.rs b/substrate/frame/election-provider-multi-block/src/verifier/mod.rs index 92690e9457862..f2f721aa48ac0 100644 --- a/substrate/frame/election-provider-multi-block/src/verifier/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/verifier/mod.rs @@ -74,8 +74,6 @@ mod tests; // internal imports use crate::SupportsOf; use frame_election_provider_support::PageIndex; -use frame_support::pallet_prelude::*; -use frame_system::pallet_prelude::*; use sp_npos_elections::ElectionScore; use sp_runtime::RuntimeDebug; use sp_std::{fmt::Debug, prelude::*}; diff --git a/substrate/frame/election-provider-multi-block/src/verifier/tests.rs b/substrate/frame/election-provider-multi-block/src/verifier/tests.rs index a5f05e42ca5a1..364c199bad618 100644 --- a/substrate/frame/election-provider-multi-block/src/verifier/tests.rs +++ b/substrate/frame/election-provider-multi-block/src/verifier/tests.rs @@ -232,7 +232,7 @@ mod feasibility_check { } mod async_verification { - use frame_support::{assert_storage_noop, bounded_vec}; + use sp_core::bounded_vec; use super::*; // disambiguate event @@ -814,7 +814,7 @@ mod async_verification { mod sync_verification { use frame_election_provider_support::Support; - use frame_support::bounded_vec; + use sp_core::bounded_vec; use sp_npos_elections::ElectionScore; use sp_runtime::Perbill; diff --git a/substrate/frame/election-provider-multi-block/src/weights.rs b/substrate/frame/election-provider-multi-block/src/weights.rs index f91985b04ea0b..45cc64431d24c 100644 --- a/substrate/frame/election-provider-multi-block/src/weights.rs +++ b/substrate/frame/election-provider-multi-block/src/weights.rs @@ -70,7 +70,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking ForceEra (r:1 w:0) // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) fn on_initialize_nothing() -> Weight { - todo!() + Default::default() } // Storage: Staking CounterForValidators (r:1 w:0) // Storage: Staking Validators (r:2 w:0) @@ -86,7 +86,7 @@ impl WeightInfo for SubstrateWeight { // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) fn on_initialize_open_signed() -> Weight { - todo!() + Default::default() } // Storage: Staking CounterForValidators (r:1 w:0) // Storage: Staking Validators (r:2 w:0) @@ -102,21 +102,21 @@ impl WeightInfo for SubstrateWeight { // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) fn on_initialize_open_unsigned_with_snapshot() -> Weight { - todo!() + Default::default() } // Storage: ElectionProviderMultiPhase Round (r:1 w:0) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) fn on_initialize_open_unsigned_without_snapshot() -> Weight { - todo!() + Default::default() } // Storage: System Account (r:1 w:1) // Storage: ElectionProviderMultiPhase QueuedSolution (r:0 w:1) fn finalize_signed_phase_accept_solution() -> Weight { - todo!() + Default::default() } // Storage: System Account (r:1 w:1) fn finalize_signed_phase_reject_solution() -> Weight { - todo!() + Default::default() } // Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) // Storage: ElectionProviderMultiPhase SignedSubmissionNextIndex (r:1 w:1) @@ -128,7 +128,7 @@ impl WeightInfo for SubstrateWeight { // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) fn elect_queued(v: u32, t: u32, a: u32, d: u32, ) -> Weight { - todo!() + Default::default() } // Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) @@ -137,7 +137,7 @@ impl WeightInfo for SubstrateWeight { // Storage: ElectionProviderMultiPhase SignedSubmissionNextIndex (r:1 w:1) // Storage: ElectionProviderMultiPhase SignedSubmissionsMap (r:0 w:1) fn submit(c: u32, ) -> Weight { - todo!() + Default::default() } // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) // Storage: ElectionProviderMultiPhase Round (r:1 w:0) @@ -147,14 +147,14 @@ impl WeightInfo for SubstrateWeight { // Storage: ElectionProviderMultiPhase MinimumUntrustedScore (r:1 w:0) // Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) fn submit_unsigned(v: u32, t: u32, a: u32, d: u32, ) -> Weight { - todo!() + Default::default() } // Storage: ElectionProviderMultiPhase Round (r:1 w:0) // Storage: ElectionProviderMultiPhase DesiredTargets (r:1 w:0) // Storage: ElectionProviderMultiPhase MinimumUntrustedScore (r:1 w:0) // Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) fn feasibility_check(v: u32, t: u32, a: u32, d: u32, ) -> Weight { - todo!() + Default::default() } } @@ -169,7 +169,7 @@ impl WeightInfo for () { // Storage: Staking ForceEra (r:1 w:0) // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) fn on_initialize_nothing() -> Weight { - todo!() + Default::default() } // Storage: Staking CounterForValidators (r:1 w:0) // Storage: Staking Validators (r:2 w:0) @@ -185,7 +185,7 @@ impl WeightInfo for () { // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) fn on_initialize_open_signed() -> Weight { - todo!() + Default::default() } // Storage: Staking CounterForValidators (r:1 w:0) // Storage: Staking Validators (r:2 w:0) @@ -201,21 +201,21 @@ impl WeightInfo for () { // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) fn on_initialize_open_unsigned_with_snapshot() -> Weight { - todo!() + Default::default() } // Storage: ElectionProviderMultiPhase Round (r:1 w:0) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) fn on_initialize_open_unsigned_without_snapshot() -> Weight { - todo!() + Default::default() } // Storage: System Account (r:1 w:1) // Storage: ElectionProviderMultiPhase QueuedSolution (r:0 w:1) fn finalize_signed_phase_accept_solution() -> Weight { - todo!() + Default::default() } // Storage: System Account (r:1 w:1) fn finalize_signed_phase_reject_solution() -> Weight { - todo!() + Default::default() } // Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) // Storage: ElectionProviderMultiPhase SignedSubmissionNextIndex (r:1 w:1) @@ -227,7 +227,7 @@ impl WeightInfo for () { // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) fn elect_queued(v: u32, t: u32, a: u32, d: u32, ) -> Weight { - todo!() + Default::default() } // Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) @@ -236,7 +236,7 @@ impl WeightInfo for () { // Storage: ElectionProviderMultiPhase SignedSubmissionNextIndex (r:1 w:1) // Storage: ElectionProviderMultiPhase SignedSubmissionsMap (r:0 w:1) fn submit(c: u32, ) -> Weight { - todo!() + Default::default() } // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) // Storage: ElectionProviderMultiPhase Round (r:1 w:0) @@ -246,13 +246,13 @@ impl WeightInfo for () { // Storage: ElectionProviderMultiPhase MinimumUntrustedScore (r:1 w:0) // Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) fn submit_unsigned(v: u32, t: u32, a: u32, d: u32, ) -> Weight { - todo!() + Default::default() } // Storage: ElectionProviderMultiPhase Round (r:1 w:0) // Storage: ElectionProviderMultiPhase DesiredTargets (r:1 w:0) // Storage: ElectionProviderMultiPhase MinimumUntrustedScore (r:1 w:0) // Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) fn feasibility_check(v: u32, t: u32, a: u32, d: u32, ) -> Weight { - todo!() + Default::default() } } diff --git a/substrate/frame/election-provider-support/solution-type/src/single_page.rs b/substrate/frame/election-provider-support/solution-type/src/single_page.rs index e07a2368ee2d0..ca551ee7f36df 100644 --- a/substrate/frame/election-provider-support/solution-type/src/single_page.rs +++ b/substrate/frame/election-provider-support/solution-type/src/single_page.rs @@ -268,7 +268,7 @@ fn remove_weakest_sorted_impl(count: usize) -> TokenStream2 { let filed_value = self.#filed .last() .map(|(x, ..)| voter_stake(x)) - .unwrap_or_else(|| _feps::sp_arithmetic::traits::Bounded::max_value()); + .unwrap_or_else(|| _fepsp::sp_arithmetic::traits::Bounded::max_value()); if filed_value < minimum { minimum = filed_value; minimum_filed = #c @@ -290,7 +290,7 @@ fn remove_weakest_sorted_impl(count: usize) -> TokenStream2 { let mut minimum = self.#first_filed .last() .map(|(x, ..)| voter_stake(x)) - .unwrap_or_else(|| _feps::sp_arithmetic::traits::Bounded::max_value()); + .unwrap_or_else(|| _fepsp::sp_arithmetic::traits::Bounded::max_value()); #( #check_minimum )* diff --git a/substrate/frame/election-provider-support/src/bounds.rs b/substrate/frame/election-provider-support/src/bounds.rs index 6b2423b7fece6..6ef0604cb4bef 100644 --- a/substrate/frame/election-provider-support/src/bounds.rs +++ b/substrate/frame/election-provider-support/src/bounds.rs @@ -54,6 +54,7 @@ //! A default or `None` bound means that no bounds are enforced (i.e. unlimited result size). In //! general, be careful when using unbounded election bounds in production. +use codec::Encode; use core::ops::Add; use sp_runtime::traits::Zero; @@ -154,6 +155,15 @@ impl DataProviderBounds { self.size_exhausted(given_size.unwrap_or(SizeBound::zero())) } + /// Ensures the given encode-able slice meets both the length and count bounds. + /// + /// Same as `exhausted` but a better syntax. + pub fn slice_exhausted(self, input: &[T]) -> bool { + let size = Some((input.encoded_size() as u32).into()); + let count = Some((input.len() as u32).into()); + self.exhausted(size, count) + } + /// Returns an instance of `Self` that is constructed by capping both the `count` and `size` /// fields. If `self` is None, overwrite it with the provided bounds. pub fn max(self, bounds: DataProviderBounds) -> Self { diff --git a/substrate/frame/election-provider-support/src/lib.rs b/substrate/frame/election-provider-support/src/lib.rs index b506776556375..322e1dc34d7b4 100644 --- a/substrate/frame/election-provider-support/src/lib.rs +++ b/substrate/frame/election-provider-support/src/lib.rs @@ -804,6 +804,26 @@ pub struct BoundedSupports, BInner: Get>( pub BoundedVec<(AccountId, BoundedSupport), BOuter>, ); +pub trait TryFromUnboundedPagedSupports, BInner: Get> { + fn try_from_unbounded_paged( + self, + ) -> Result>, crate::Error> + where + Self: Sized; +} + +impl, BInner: Get> + TryFromUnboundedPagedSupports for Vec> +{ + fn try_from_unbounded_paged( + self, + ) -> Result>, crate::Error> { + self.into_iter() + .map(|s| s.try_into().map_err(|_| crate::Error::BoundsExceeded)) + .collect::, _>>() + } +} + impl, BInner: Get> sp_npos_elections::EvaluateSupport for BoundedSupports { @@ -877,12 +897,12 @@ impl, BInner: Get> IntoIterator } } -impl, BInner: Get> TryFrom> +impl, BInner: Get> TryFrom> for BoundedSupports { type Error = crate::Error; - fn try_from(supports: sp_npos_elections::Supports) -> Result { + fn try_from(supports: Supports) -> Result { // optimization note: pre-allocate outer bounded vec. let mut outer_bounded_supports = BoundedVec::< (AccountId, BoundedSupport), diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index 8db6feda4710d..d23770332c30b 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -1452,10 +1452,7 @@ impl ElectionDataProvider for Pallet { ) -> data_provider::Result>> { let voters = Self::get_npos_voters(bounds, page); - debug_assert!(!bounds.exhausted( - SizeBound(voters.encoded_size() as u32).into(), - CountBound(voters.len() as u32).into() - )); + debug_assert!(!bounds.slice_exhausted(&voters)); Ok(voters) } @@ -1475,10 +1472,7 @@ impl ElectionDataProvider for Pallet { return Err("Target snapshot too big") } - debug_assert!(!bounds.exhausted( - SizeBound(targets.encoded_size() as u32).into(), - CountBound(targets.len() as u32).into() - )); + debug_assert!(!bounds.slice_exhausted(&targets)); Ok(targets) } From 569ce71e2c759b26601608f145d9b5efcb906919 Mon Sep 17 00:00:00 2001 From: Ron Date: Mon, 20 Jan 2025 22:16:57 +0800 Subject: [PATCH 103/169] Migrate pallet-mmr to umbrella crate (#7081) Part of https://github.com/paritytech/polkadot-sdk/issues/6504 --- Cargo.lock | 8 +- prdoc/pr_7081.prdoc | 14 +++ .../frame/merkle-mountain-range/Cargo.toml | 24 +----- .../merkle-mountain-range/src/benchmarking.rs | 10 ++- .../src/default_weights.rs | 9 +- .../frame/merkle-mountain-range/src/lib.rs | 85 +++++++++---------- .../merkle-mountain-range/src/mmr/mmr.rs | 45 +++++----- .../merkle-mountain-range/src/mmr/mod.rs | 5 +- .../merkle-mountain-range/src/mmr/storage.rs | 38 +++++---- .../frame/merkle-mountain-range/src/mock.rs | 18 ++-- .../frame/merkle-mountain-range/src/tests.rs | 23 ++--- 11 files changed, 137 insertions(+), 142 deletions(-) create mode 100644 prdoc/pr_7081.prdoc diff --git a/Cargo.lock b/Cargo.lock index 0907830c5e7bb..50d36338cd2cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14147,18 +14147,12 @@ dependencies = [ name = "pallet-mmr" version = "27.0.0" dependencies = [ - "array-bytes", - "frame-benchmarking 28.0.0", - "frame-support 28.0.0", - "frame-system 28.0.0", "itertools 0.11.0", "log", "parity-scale-codec", + "polkadot-sdk-frame 0.1.0", "scale-info", - "sp-core 28.0.0", - "sp-io 30.0.0", "sp-mmr-primitives 26.0.0", - "sp-runtime 31.0.1", "sp-tracing 16.0.0", ] diff --git a/prdoc/pr_7081.prdoc b/prdoc/pr_7081.prdoc new file mode 100644 index 0000000000000..be1d8aa6ee013 --- /dev/null +++ b/prdoc/pr_7081.prdoc @@ -0,0 +1,14 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: '[pallet-mmr] Migrate to using frame umbrella crate' + +doc: + - audience: Runtime Dev + description: This PR migrates the pallet-mmr to use the frame umbrella crate. This + is part of the ongoing effort to migrate all pallets to use the frame umbrella crate. + The effort is tracked [here](https://github.com/paritytech/polkadot-sdk/issues/6504). + +crates: + - name: pallet-mmr + bump: minor diff --git a/substrate/frame/merkle-mountain-range/Cargo.toml b/substrate/frame/merkle-mountain-range/Cargo.toml index 04f5ab64100d3..ecbef01a9205c 100644 --- a/substrate/frame/merkle-mountain-range/Cargo.toml +++ b/substrate/frame/merkle-mountain-range/Cargo.toml @@ -16,18 +16,12 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -frame-benchmarking = { optional = true, workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } +frame = { workspace = true, features = ["runtime"] } log = { workspace = true } scale-info = { features = ["derive"], workspace = true } -sp-core = { workspace = true } -sp-io = { workspace = true } sp-mmr-primitives = { workspace = true } -sp-runtime = { workspace = true } [dev-dependencies] -array-bytes = { workspace = true, default-features = true } itertools = { workspace = true } sp-tracing = { workspace = true, default-features = true } @@ -35,24 +29,14 @@ sp-tracing = { workspace = true, default-features = true } default = ["std"] std = [ "codec/std", - "frame-benchmarking?/std", - "frame-support/std", - "frame-system/std", + "frame/std", "log/std", "scale-info/std", - "sp-core/std", - "sp-io/std", "sp-mmr-primitives/std", - "sp-runtime/std", ] runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", + "frame/runtime-benchmarks", ] try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", - "sp-runtime/try-runtime", + "frame/try-runtime", ] diff --git a/substrate/frame/merkle-mountain-range/src/benchmarking.rs b/substrate/frame/merkle-mountain-range/src/benchmarking.rs index 07afd9529eb26..407f1f7ead60a 100644 --- a/substrate/frame/merkle-mountain-range/src/benchmarking.rs +++ b/substrate/frame/merkle-mountain-range/src/benchmarking.rs @@ -20,8 +20,10 @@ #![cfg(feature = "runtime-benchmarks")] use crate::*; -use frame_benchmarking::v1::benchmarks_instance_pallet; -use frame_support::traits::OnInitialize; +use frame::{ + benchmarking::prelude::v1::benchmarks_instance_pallet, + deps::frame_support::traits::OnInitialize, +}; benchmarks_instance_pallet! { on_initialize { @@ -31,10 +33,10 @@ benchmarks_instance_pallet! { <>::BenchmarkHelper as BenchmarkHelper>::setup(); for leaf in 0..(leaves - 1) { - Pallet::::on_initialize((leaf as u32).into()); + as OnInitialize>>::on_initialize((leaf as u32).into()); } }: { - Pallet::::on_initialize((leaves as u32 - 1).into()); + as OnInitialize>>::on_initialize((leaves as u32 - 1).into()); } verify { assert_eq!(crate::NumberOfLeaves::::get(), leaves); } diff --git a/substrate/frame/merkle-mountain-range/src/default_weights.rs b/substrate/frame/merkle-mountain-range/src/default_weights.rs index b0ef0539018cd..d1ed12edd0628 100644 --- a/substrate/frame/merkle-mountain-range/src/default_weights.rs +++ b/substrate/frame/merkle-mountain-range/src/default_weights.rs @@ -18,16 +18,13 @@ //! Default weights for the MMR Pallet //! This file was not auto-generated. -use frame_support::weights::{ - constants::{RocksDbWeight as DbWeight, WEIGHT_REF_TIME_PER_NANOS}, - Weight, -}; +use frame::{deps::frame_support::weights::constants::*, weights_prelude::*}; impl crate::WeightInfo for () { fn on_initialize(peaks: u32) -> Weight { let peaks = u64::from(peaks); // Reading the parent hash. - let leaf_weight = DbWeight::get().reads(1); + let leaf_weight = RocksDbWeight::get().reads(1); // Blake2 hash cost. let hash_weight = Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_NANOS, 0); // No-op hook. @@ -36,6 +33,6 @@ impl crate::WeightInfo for () { leaf_weight .saturating_add(hash_weight) .saturating_add(hook_weight) - .saturating_add(DbWeight::get().reads_writes(2 + peaks, 2 + peaks)) + .saturating_add(RocksDbWeight::get().reads_writes(2 + peaks, 2 + peaks)) } } diff --git a/substrate/frame/merkle-mountain-range/src/lib.rs b/substrate/frame/merkle-mountain-range/src/lib.rs index 7dfe95c83361c..76d6c2a1ac76f 100644 --- a/substrate/frame/merkle-mountain-range/src/lib.rs +++ b/substrate/frame/merkle-mountain-range/src/lib.rs @@ -59,20 +59,17 @@ extern crate alloc; use alloc::vec::Vec; -use frame_support::weights::Weight; -use frame_system::pallet_prelude::{BlockNumberFor, HeaderFor}; use log; -use sp_mmr_primitives::utils; -use sp_runtime::{ - traits::{self, One, Saturating}, - SaturatedConversion, -}; -pub use pallet::*; +use frame::prelude::*; + pub use sp_mmr_primitives::{ - self as primitives, utils::NodesUtils, Error, LeafDataProvider, LeafIndex, NodeIndex, + self as primitives, utils, utils::NodesUtils, AncestryProof, Error, FullLeaf, LeafDataProvider, + LeafIndex, LeafProof, NodeIndex, OnNewRoot, }; +pub use pallet::*; + #[cfg(feature = "runtime-benchmarks")] mod benchmarking; mod default_weights; @@ -90,11 +87,11 @@ mod tests; /// crate-local wrapper over [frame_system::Pallet]. Since the current block hash /// is not available (since the block is not finished yet), /// we use the `parent_hash` here along with parent block number. -pub struct ParentNumberAndHash { - _phantom: core::marker::PhantomData, +pub struct ParentNumberAndHash { + _phantom: PhantomData, } -impl LeafDataProvider for ParentNumberAndHash { +impl LeafDataProvider for ParentNumberAndHash { type LeafData = (BlockNumberFor, ::Hash); fn leaf_data() -> Self::LeafData { @@ -111,13 +108,11 @@ pub trait BlockHashProvider { } /// Default implementation of BlockHashProvider using frame_system. -pub struct DefaultBlockHashProvider { +pub struct DefaultBlockHashProvider { _phantom: core::marker::PhantomData, } -impl BlockHashProvider, T::Hash> - for DefaultBlockHashProvider -{ +impl BlockHashProvider, T::Hash> for DefaultBlockHashProvider { fn block_hash(block_number: BlockNumberFor) -> T::Hash { frame_system::Pallet::::block_hash(block_number) } @@ -142,17 +137,16 @@ impl BenchmarkHelper for () { type ModuleMmr = mmr::Mmr>; /// Leaf data. -type LeafOf = <>::LeafData as primitives::LeafDataProvider>::LeafData; +type LeafOf = <>::LeafData as LeafDataProvider>::LeafData; /// Hashing used for the pallet. pub(crate) type HashingOf = >::Hashing; /// Hash type used for the pallet. -pub(crate) type HashOf = <>::Hashing as traits::Hash>::Output; +pub(crate) type HashOf = <>::Hashing as Hash>::Output; -#[frame_support::pallet] +#[frame::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; #[pallet::pallet] pub struct Pallet(PhantomData<(T, I)>); @@ -180,7 +174,7 @@ pub mod pallet { /// /// Then we create a tuple of these two hashes, SCALE-encode it (concatenate) and /// hash, to obtain a new MMR inner node - the new peak. - type Hashing: traits::Hash; + type Hashing: Hash; /// Data stored in the leaf nodes. /// @@ -198,7 +192,7 @@ pub mod pallet { /// two forks with identical line of ancestors compete to write the same offchain key, but /// that's fine as long as leaves only contain data coming from ancestors - conflicting /// writes are identical). - type LeafData: primitives::LeafDataProvider; + type LeafData: LeafDataProvider; /// A hook to act on the new MMR root. /// @@ -206,7 +200,7 @@ pub mod pallet { /// apart from having it in the storage. For instance you might output it in the header /// digest (see [`frame_system::Pallet::deposit_log`]) to make it available for Light /// Clients. Hook complexity should be `O(1)`. - type OnNewRoot: primitives::OnNewRoot>; + type OnNewRoot: OnNewRoot>; /// Block hash provider for a given block number. type BlockHashProvider: BlockHashProvider< @@ -248,9 +242,8 @@ pub mod pallet { #[pallet::hooks] impl, I: 'static> Hooks> for Pallet { fn on_initialize(_n: BlockNumberFor) -> Weight { - use primitives::LeafDataProvider; let leaves = NumberOfLeaves::::get(); - let peaks_before = sp_mmr_primitives::utils::NodesUtils::new(leaves).number_of_peaks(); + let peaks_before = NodesUtils::new(leaves).number_of_peaks(); let data = T::LeafData::leaf_data(); // append new leaf to MMR @@ -268,12 +261,12 @@ pub mod pallet { return T::WeightInfo::on_initialize(peaks_before as u32) }, }; - >::on_new_root(&root); + >::on_new_root(&root); NumberOfLeaves::::put(leaves); RootHash::::put(root); - let peaks_after = sp_mmr_primitives::utils::NodesUtils::new(leaves).number_of_peaks(); + let peaks_after = NodesUtils::new(leaves).number_of_peaks(); T::WeightInfo::on_initialize(peaks_before.max(peaks_after) as u32) } @@ -290,28 +283,28 @@ pub mod pallet { pub fn verify_leaves_proof( root: H::Output, leaves: Vec>, - proof: primitives::LeafProof, -) -> Result<(), primitives::Error> + proof: LeafProof, +) -> Result<(), Error> where - H: traits::Hash, - L: primitives::FullLeaf, + H: Hash, + L: FullLeaf, { let is_valid = mmr::verify_leaves_proof::(root, leaves, proof)?; if is_valid { Ok(()) } else { - Err(primitives::Error::Verify.log_debug(("The proof is incorrect.", root))) + Err(Error::Verify.log_debug(("The proof is incorrect.", root))) } } /// Stateless ancestry proof verification. pub fn verify_ancestry_proof( root: H::Output, - ancestry_proof: primitives::AncestryProof, + ancestry_proof: AncestryProof, ) -> Result where - H: traits::Hash, - L: primitives::FullLeaf, + H: Hash, + L: FullLeaf, { mmr::verify_ancestry_proof::(root, ancestry_proof) .map_err(|_| Error::Verify.log_debug(("The ancestry proof is incorrect.", root))) @@ -383,7 +376,7 @@ impl, I: 'static> Pallet { pub fn generate_proof( block_numbers: Vec>, best_known_block_number: Option>, - ) -> Result<(Vec>, primitives::LeafProof>), primitives::Error> { + ) -> Result<(Vec>, LeafProof>), Error> { // check whether best_known_block_number provided, else use current best block let best_known_block_number = best_known_block_number.unwrap_or_else(|| >::block_number()); @@ -393,7 +386,7 @@ impl, I: 'static> Pallet { // we need to translate the block_numbers into leaf indices. let leaf_indices = block_numbers .iter() - .map(|block_num| -> Result { + .map(|block_num| -> Result { Self::block_num_to_leaf_index(*block_num) }) .collect::, _>>()?; @@ -410,14 +403,15 @@ impl, I: 'static> Pallet { /// or the proof is invalid. pub fn verify_leaves( leaves: Vec>, - proof: primitives::LeafProof>, - ) -> Result<(), primitives::Error> { + proof: LeafProof>, + ) -> Result<(), Error> { if proof.leaf_count > NumberOfLeaves::::get() || proof.leaf_count == 0 || proof.items.len().saturating_add(leaves.len()) as u64 > proof.leaf_count { - return Err(primitives::Error::Verify - .log_debug("The proof has incorrect number of leaves or proof items.")) + return Err( + Error::Verify.log_debug("The proof has incorrect number of leaves or proof items.") + ) } let mmr: ModuleMmr = mmr::Mmr::new(proof.leaf_count); @@ -425,14 +419,14 @@ impl, I: 'static> Pallet { if is_valid { Ok(()) } else { - Err(primitives::Error::Verify.log_debug("The proof is incorrect.")) + Err(Error::Verify.log_debug("The proof is incorrect.")) } } pub fn generate_ancestry_proof( prev_block_number: BlockNumberFor, best_known_block_number: Option>, - ) -> Result>, Error> { + ) -> Result>, Error> { // check whether best_known_block_number provided, else use current best block let best_known_block_number = best_known_block_number.unwrap_or_else(|| >::block_number()); @@ -445,8 +439,7 @@ impl, I: 'static> Pallet { } #[cfg(feature = "runtime-benchmarks")] - pub fn generate_mock_ancestry_proof() -> Result>, Error> - { + pub fn generate_mock_ancestry_proof() -> Result>, Error> { let leaf_count = Self::block_num_to_leaf_count(>::block_number())?; let mmr: ModuleMmr = mmr::Mmr::new(leaf_count); mmr.generate_mock_ancestry_proof() @@ -454,7 +447,7 @@ impl, I: 'static> Pallet { pub fn verify_ancestry_proof( root: HashOf, - ancestry_proof: primitives::AncestryProof>, + ancestry_proof: AncestryProof>, ) -> Result, Error> { verify_ancestry_proof::, LeafOf>(root, ancestry_proof) } diff --git a/substrate/frame/merkle-mountain-range/src/mmr/mmr.rs b/substrate/frame/merkle-mountain-range/src/mmr/mmr.rs index f9a4580b9bb30..a9818ba471019 100644 --- a/substrate/frame/merkle-mountain-range/src/mmr/mmr.rs +++ b/substrate/frame/merkle-mountain-range/src/mmr/mmr.rs @@ -20,11 +20,14 @@ use crate::{ storage::{OffchainStorage, RuntimeStorage, Storage}, Hasher, Node, NodeOf, }, - primitives::{self, Error, NodeIndex}, + primitives::{ + mmr_lib, mmr_lib::MMRStoreReadOps, utils::NodesUtils, AncestryProof, Error, FullLeaf, + LeafIndex, LeafProof, NodeIndex, + }, Config, HashOf, HashingOf, }; use alloc::vec::Vec; -use sp_mmr_primitives::{mmr_lib, mmr_lib::MMRStoreReadOps, utils::NodesUtils, LeafIndex}; +use frame::prelude::*; /// Stateless verification of the proof for a batch of leaves. /// Note, the leaves should be sorted such that corresponding leaves and leaf indices have the @@ -33,11 +36,11 @@ use sp_mmr_primitives::{mmr_lib, mmr_lib::MMRStoreReadOps, utils::NodesUtils, Le pub fn verify_leaves_proof( root: H::Output, leaves: Vec>, - proof: primitives::LeafProof, + proof: LeafProof, ) -> Result where - H: sp_runtime::traits::Hash, - L: primitives::FullLeaf, + H: Hash, + L: FullLeaf, { let size = NodesUtils::new(proof.leaf_count).size(); @@ -62,11 +65,11 @@ where pub fn verify_ancestry_proof( root: H::Output, - ancestry_proof: primitives::AncestryProof, + ancestry_proof: AncestryProof, ) -> Result where - H: sp_runtime::traits::Hash, - L: primitives::FullLeaf, + H: Hash, + L: FullLeaf, { let mmr_size = NodesUtils::new(ancestry_proof.leaf_count).size(); @@ -104,7 +107,7 @@ pub struct Mmr where T: Config, I: 'static, - L: primitives::FullLeaf, + L: FullLeaf, Storage: MMRStoreReadOps> + mmr_lib::MMRStoreWriteOps>, { @@ -116,7 +119,7 @@ impl Mmr where T: Config, I: 'static, - L: primitives::FullLeaf, + L: FullLeaf, Storage: MMRStoreReadOps> + mmr_lib::MMRStoreWriteOps>, { @@ -133,7 +136,7 @@ where pub fn verify_leaves_proof( &self, leaves: Vec, - proof: primitives::LeafProof>, + proof: LeafProof>, ) -> Result { let p = mmr_lib::MerkleProof::, Hasher, L>>::new( self.mmr.mmr_size(), @@ -167,7 +170,7 @@ impl Mmr where T: Config, I: 'static, - L: primitives::FullLeaf, + L: FullLeaf, { /// Push another item to the MMR. /// @@ -195,7 +198,7 @@ impl Mmr where T: Config, I: 'static, - L: primitives::FullLeaf + codec::Decode, + L: FullLeaf + codec::Decode, { /// Generate a proof for given leaf indices. /// @@ -204,7 +207,7 @@ where pub fn generate_proof( &self, leaf_indices: Vec, - ) -> Result<(Vec, primitives::LeafProof>), Error> { + ) -> Result<(Vec, LeafProof>), Error> { let positions = leaf_indices .iter() .map(|index| mmr_lib::leaf_index_to_pos(*index)) @@ -222,7 +225,7 @@ where self.mmr .gen_proof(positions) .map_err(|e| Error::GenerateProof.log_error(e)) - .map(|p| primitives::LeafProof { + .map(|p| LeafProof { leaf_indices, leaf_count, items: p.proof_items().iter().map(|x| x.hash()).collect(), @@ -233,14 +236,14 @@ where pub fn generate_ancestry_proof( &self, prev_leaf_count: LeafIndex, - ) -> Result>, Error> { + ) -> Result>, Error> { let prev_mmr_size = NodesUtils::new(prev_leaf_count).size(); let raw_ancestry_proof = self .mmr .gen_ancestry_proof(prev_mmr_size) .map_err(|e| Error::GenerateProof.log_error(e))?; - Ok(primitives::AncestryProof { + Ok(AncestryProof { prev_peaks: raw_ancestry_proof.prev_peaks.into_iter().map(|p| p.hash()).collect(), prev_leaf_count, leaf_count: self.leaves, @@ -258,12 +261,10 @@ where /// The generated proof contains all the leafs in the MMR, so this way we can generate a proof /// with exactly `leaf_count` items. #[cfg(feature = "runtime-benchmarks")] - pub fn generate_mock_ancestry_proof( - &self, - ) -> Result>, Error> { + pub fn generate_mock_ancestry_proof(&self) -> Result>, Error> { use crate::ModuleMmr; use alloc::vec; - use sp_mmr_primitives::mmr_lib::helper; + use mmr_lib::helper; let mmr: ModuleMmr = Mmr::new(self.leaves); let store = >::default(); @@ -289,7 +290,7 @@ where proof_items.push((leaf_pos, leaf)); } - Ok(sp_mmr_primitives::AncestryProof { + Ok(AncestryProof { prev_peaks, prev_leaf_count: self.leaves, leaf_count: self.leaves, diff --git a/substrate/frame/merkle-mountain-range/src/mmr/mod.rs b/substrate/frame/merkle-mountain-range/src/mmr/mod.rs index 5b73f53506e92..85d00f8a65dee 100644 --- a/substrate/frame/merkle-mountain-range/src/mmr/mod.rs +++ b/substrate/frame/merkle-mountain-range/src/mmr/mod.rs @@ -18,10 +18,9 @@ mod mmr; pub mod storage; -use sp_mmr_primitives::{mmr_lib, DataOrHash, FullLeaf}; -use sp_runtime::traits; - pub use self::mmr::{verify_ancestry_proof, verify_leaves_proof, Mmr}; +use crate::primitives::{mmr_lib, DataOrHash, FullLeaf}; +use frame::traits; /// Node type for runtime `T`. pub type NodeOf = Node<>::Hashing, L>; diff --git a/substrate/frame/merkle-mountain-range/src/mmr/storage.rs b/substrate/frame/merkle-mountain-range/src/mmr/storage.rs index 02852388b4171..c201c0ea846d3 100644 --- a/substrate/frame/merkle-mountain-range/src/mmr/storage.rs +++ b/substrate/frame/merkle-mountain-range/src/mmr/storage.rs @@ -17,18 +17,22 @@ //! An MMR storage implementation. -use alloc::{vec, vec::Vec}; -use codec::Encode; -use core::iter::Peekable; -use log::{debug, trace}; -use sp_core::offchain::StorageKind; -use sp_mmr_primitives::{mmr_lib, mmr_lib::helper, utils::NodesUtils}; - use crate::{ mmr::{Node, NodeOf}, - primitives::{self, NodeIndex}, + primitives::{mmr_lib, mmr_lib::helper, utils::NodesUtils, FullLeaf, NodeIndex}, BlockHashProvider, Config, Nodes, NumberOfLeaves, Pallet, }; +use alloc::{vec, vec::Vec}; +use codec::Encode; +use core::iter::Peekable; +use frame::{ + deps::{ + sp_core::offchain::StorageKind, + sp_io::{offchain, offchain_index}, + }, + prelude::*, +}; +use log::{debug, trace}; /// A marker type for runtime-specific storage implementation. /// @@ -48,20 +52,20 @@ pub struct OffchainStorage; impl OffchainStorage { fn get(key: &[u8]) -> Option> { - sp_io::offchain::local_storage_get(StorageKind::PERSISTENT, &key) + offchain::local_storage_get(StorageKind::PERSISTENT, &key) } #[cfg(not(feature = "runtime-benchmarks"))] fn set, I: 'static>(key: &[u8], value: &[u8]) { - sp_io::offchain_index::set(key, value); + offchain_index::set(key, value); } #[cfg(feature = "runtime-benchmarks")] fn set, I: 'static>(key: &[u8], value: &[u8]) { if crate::pallet::UseLocalStorage::::get() { - sp_io::offchain::local_storage_set(StorageKind::PERSISTENT, key, value); + offchain::local_storage_set(StorageKind::PERSISTENT, key, value); } else { - sp_io::offchain_index::set(key, value); + offchain_index::set(key, value); } } } @@ -82,7 +86,7 @@ impl mmr_lib::MMRStoreReadOps> for Storage, I: 'static, - L: primitives::FullLeaf + codec::Decode, + L: FullLeaf + Decode, { fn get_elem(&self, pos: NodeIndex) -> mmr_lib::Result>> { // Find out which leaf added node `pos` in the MMR. @@ -120,7 +124,7 @@ impl mmr_lib::MMRStoreWriteOps> for Storage, I: 'static, - L: primitives::FullLeaf + codec::Decode, + L: FullLeaf + Decode, { fn append(&mut self, _: NodeIndex, _: Vec>) -> mmr_lib::Result<()> { panic!("MMR must not be altered in the off-chain context.") @@ -131,7 +135,7 @@ impl mmr_lib::MMRStoreReadOps> for Storage, I: 'static, - L: primitives::FullLeaf, + L: FullLeaf, { fn get_elem(&self, pos: NodeIndex) -> mmr_lib::Result>> { Ok(Nodes::::get(pos).map(Node::Hash)) @@ -142,7 +146,7 @@ impl mmr_lib::MMRStoreWriteOps> for Storage, I: 'static, - L: primitives::FullLeaf, + L: FullLeaf, { fn append(&mut self, pos: NodeIndex, elems: Vec>) -> mmr_lib::Result<()> { if elems.is_empty() { @@ -205,7 +209,7 @@ impl Storage where T: Config, I: 'static, - L: primitives::FullLeaf, + L: FullLeaf, { fn store_to_offchain( pos: NodeIndex, diff --git a/substrate/frame/merkle-mountain-range/src/mock.rs b/substrate/frame/merkle-mountain-range/src/mock.rs index 606719c6deba1..4c234e0d94aaf 100644 --- a/substrate/frame/merkle-mountain-range/src/mock.rs +++ b/substrate/frame/merkle-mountain-range/src/mock.rs @@ -18,14 +18,20 @@ use crate as pallet_mmr; use crate::*; +use crate::{ + frame_system::DefaultConfig, + primitives::{Compact, LeafDataProvider}, +}; use codec::{Decode, Encode}; -use frame_support::{derive_impl, parameter_types}; -use sp_mmr_primitives::{Compact, LeafDataProvider}; -use sp_runtime::traits::Keccak256; +use frame::{ + deps::frame_support::derive_impl, + prelude::{frame_system, frame_system::config_preludes::TestDefaultConfig}, + testing_prelude::*, +}; -type Block = frame_system::mocking::MockBlock; +type Block = MockBlock; -frame_support::construct_runtime!( +construct_runtime!( pub enum Test { System: frame_system, @@ -33,7 +39,7 @@ frame_support::construct_runtime!( } ); -#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +#[derive_impl(TestDefaultConfig)] impl frame_system::Config for Test { type Block = Block; } diff --git a/substrate/frame/merkle-mountain-range/src/tests.rs b/substrate/frame/merkle-mountain-range/src/tests.rs index 93e3d06eaa0af..ae0c58e91aba4 100644 --- a/substrate/frame/merkle-mountain-range/src/tests.rs +++ b/substrate/frame/merkle-mountain-range/src/tests.rs @@ -17,19 +17,21 @@ use crate::{mock::*, *}; -use frame_support::traits::{Get, OnInitialize}; -use sp_core::{ - offchain::{testing::TestOffchainExt, OffchainDbExt, OffchainWorkerExt}, - H256, +use crate::primitives::{mmr_lib::helper, utils, Compact, LeafProof}; + +use frame::{ + deps::sp_core::{ + offchain::{testing::TestOffchainExt, OffchainDbExt, OffchainWorkerExt}, + H256, + }, + testing_prelude::*, }; -use sp_mmr_primitives::{mmr_lib::helper, utils, Compact, LeafProof}; -use sp_runtime::BuildStorage; -pub(crate) fn new_test_ext() -> sp_io::TestExternalities { +pub(crate) fn new_test_ext() -> TestState { frame_system::GenesisConfig::::default().build_storage().unwrap().into() } -fn register_offchain_ext(ext: &mut sp_io::TestExternalities) { +fn register_offchain_ext(ext: &mut TestState) { let (offchain, _offchain_state) = TestOffchainExt::with_offchain_db(ext.offchain_db()); ext.register_extension(OffchainDbExt::new(offchain.clone())); ext.register_extension(OffchainWorkerExt::new(offchain)); @@ -54,7 +56,7 @@ pub(crate) fn hex(s: &str) -> H256 { s.parse().unwrap() } -type BlockNumber = frame_system::pallet_prelude::BlockNumberFor; +type BlockNumber = BlockNumberFor; fn decode_node( v: Vec, @@ -517,7 +519,7 @@ fn should_verify() { } fn generate_and_verify_batch_proof( - ext: &mut sp_io::TestExternalities, + ext: &mut TestExternalities, block_numbers: &Vec, blocks_to_add: usize, ) { @@ -719,7 +721,6 @@ fn should_verify_on_the_next_block_since_there_is_no_pruning_yet() { #[test] fn should_verify_canonicalized() { - use frame_support::traits::Hooks; sp_tracing::init_for_tests(); // How deep is our fork-aware storage (in terms of blocks/leaves, nodes will be more). From b56e7395110d4bb4e8151347022f73ccfa8f205b Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 20 Jan 2025 14:27:40 +0000 Subject: [PATCH 104/169] refactor events --- .../election-provider-multi-block/src/lib.rs | 423 ++++++++++-------- .../src/mock/signed.rs | 12 +- .../src/types.rs | 6 +- 3 files changed, 256 insertions(+), 185 deletions(-) diff --git a/substrate/frame/election-provider-multi-block/src/lib.rs b/substrate/frame/election-provider-multi-block/src/lib.rs index f7e25552a66ac..7d231813f9f6d 100644 --- a/substrate/frame/election-provider-multi-block/src/lib.rs +++ b/substrate/frame/election-provider-multi-block/src/lib.rs @@ -39,8 +39,8 @@ //! ## Companion pallets //! //! This pallet is essentially hierarchical. This particular one is the top level one. It contains -//! the shared information that all child pallets use. All child pallets can depend on on the top -//! level pallet ONLY, but not the other way around. For those cases, traits are used. +//! the shared information that all child pallets use. All child pallets depend on the top level +//! pallet ONLY, but not the other way around. For those cases, traits are used. //! //! This pallet will only function in a sensible way if it is peered with its companion pallets. //! @@ -54,7 +54,13 @@ //! //! ### Pallet Ordering: //! -//! TODO: parent, verifier, signed, unsigned +//! The ordering of these pallets in a runtime should be: +//! 1. parent +//! 2. verifier +//! 3. signed +//! 4. unsigned +//! +//! This should be manually checked, there is not automated way to test it. //! //! ## Pagination //! @@ -66,35 +72,27 @@ //! //! ## Phases //! -//! The timeline of pallet is as follows. At each block, -//! [`frame_election_provider_support::ElectionDataProvider::next_election_prediction`] is used to -//! estimate the time remaining to the next call to -//! [`frame_election_provider_support::ElectionProvider::elect`]. Based on this, a phase is chosen. -//! An example timeline is as follows: +//! The timeline of pallet is overall as follows: //! //! ```ignore -//! elect() -//! + <--T::SignedPhase--> + <--T::UnsignedPhase--> + -//! +-------------------------------------------------------------------+ -//! Phase::Off + Phase::Signed + Phase::Unsigned + +//! < Off > +//! 0 ------- 12 13 14 15 ----------- 20 ---------25 ------- 30 +//! | | | | | +//! Snapshot Signed SignedValidation Unsigned Elect //! ``` //! -//! The duration of both phases are configurable, and their existence is optional. Each of the -//! phases can be disabled by essentially setting their length to zero. If both phases have length -//! zero, then the pallet essentially runs only the fallback strategy, denoted by -//! [`Config::Fallback`]. -//! -//! - Note that the prediction of the election is assume to be the **first call** to elect. For -//! example, with 3 pages, the prediction must point to the `elect(2)`. -//! - Note that the unsigned phase starts [`pallet::Config::UnsignedPhase`] blocks before the -//! `next_election_prediction`, but only ends when a call to [`ElectionProvider::elect`] happens. If -//! no `elect` happens, the current phase (usually unsigned) is extended. +//! * Duration of `Snapshot` is determined by [`Config::Pages`]. +//! * Duration of `Signed`, `SignedValidation` and `Unsigned` are determined by +//! [`Config::SignedPhase`], [`Config::SignedValidationPhase`] and [`Config::UnsignedPhase`] +//! respectively. +//! * [`Config::Pages`] calls to elect are expected, but all in all the pallet will close a round +//! once `elect(0)` is called. +//! * The pallet strives to be ready for the first call to `elect`, for example `elect(2)` if 3 +//! pages. +//! * This pallet can be commanded to to be ready sooner with [`Config::Lookahead`]. //! //! > Given this, it is rather important for the user of this pallet to ensure it always terminates -//! election via `elect` before requesting a new one. -//! -//! TODO: test case: elect(2) -> elect(1) -> elect(2) -//! TODO: should we wipe the verifier afterwards, or just `::take()` the election result? +//! > election via `elect` before requesting a new one. //! //! ## Feasible Solution (correct solution) //! @@ -107,111 +105,24 @@ //! 2. any assignment is checked to match with [`RoundSnapshot::voters`]. //! 3. the claimed score is valid, based on the fixed point arithmetic accuracy. //! -//! ### Signed Phase -//! -//! In the signed phase, solutions (of type [`RawSolution`]) are submitted and queued on chain. A -//! deposit is reserved, based on the size of the solution, for the cost of keeping this solution -//! on-chain for a number of blocks, and the potential weight of the solution upon being checked. A -//! maximum of `pallet::Config::MaxSignedSubmissions` solutions are stored. The queue is always -//! sorted based on score (worse to best). -//! -//! Upon arrival of a new solution: +//! ### Emergency Phase and Fallback //! -//! 1. If the queue is not full, it is stored in the appropriate sorted index. -//! 2. If the queue is full but the submitted solution is better than one of the queued ones, the -//! worse solution is discarded, the bond of the outgoing solution is returned, and the new -//! solution is stored in the correct index. -//! 3. If the queue is full and the solution is not an improvement compared to any of the queued -//! ones, it is instantly rejected and no additional bond is reserved. +//! Works similar to the multi-phase pallet: //! -//! A signed solution cannot be reversed, taken back, updated, or retracted. In other words, the -//! origin can not bail out in any way, if their solution is queued. +//! * [`Config::Fallback`] is used, only in page 0, in case no queued solution is present. +//! * [`Phase::Emergency`] is entered if also the fallback fails. At this point, only +//! [`AdminOperation::SetSolution`] can be used to recover. //! -//! Upon the end of the signed phase, the solutions are examined from best to worse (i.e. `pop()`ed -//! until drained). Each solution undergoes an expensive `Pallet::feasibility_check`, which ensures -//! the score claimed by this score was correct, and it is valid based on the election data (i.e. -//! votes and candidates). At each step, if the current best solution passes the feasibility check, -//! it is considered to be the best one. The sender of the origin is rewarded, and the rest of the -//! queued solutions get their deposit back and are discarded, without being checked. +//! TODO: test that multi-page seq-phragmen with fallback works. //! -//! The following example covers all of the cases at the end of the signed phase: //! -//! ```ignore -//! Queue -//! +-------------------------------+ -//! |Solution(score=20, valid=false)| +--> Slashed -//! +-------------------------------+ -//! |Solution(score=15, valid=true )| +--> Rewarded, Saved -//! +-------------------------------+ -//! |Solution(score=10, valid=true )| +--> Discarded -//! +-------------------------------+ -//! |Solution(score=05, valid=false)| +--> Discarded -//! +-------------------------------+ -//! | None | -//! +-------------------------------+ -//! ``` +//! ### Signed Phase //! -//! Note that both of the bottom solutions end up being discarded and get their deposit back, -//! despite one of them being *invalid*. +//! TODO //! //! ## Unsigned Phase //! -//! The unsigned phase will always follow the signed phase, with the specified duration. In this -//! phase, only validator nodes can submit solutions. A validator node who has offchain workers -//! enabled will start to mine a solution in this phase and submits it back to the chain as an -//! unsigned transaction, thus the name _unsigned_ phase. This unsigned transaction can never be -//! valid if propagated, and it acts similar to an inherent. -//! -//! Validators will only submit solutions if the one that they have computed is sufficiently better -//! than the best queued one (see [`pallet::Config::SolutionImprovementThreshold`]) and will limit -//! the weigh of the solution to [`pallet::Config::MinerMaxWeight`]. -//! -//! The unsigned phase can be made passive depending on how the previous signed phase went, by -//! setting the first inner value of [`Phase`] to `false`. For now, the signed phase is always -//! active. -//! -//! ### Emergency Phase and Fallback -//! -//! TODO: -//! -//! ## Accuracy -//! //! TODO -//! -//! ## Error types -//! -//! TODO: -//! -//! ## Future Plans -//! -//! **Challenge Phase**. We plan on adding a third phase to the pallet, called the challenge phase. -//! This is a phase in which no further solutions are processed, and the current best solution might -//! be challenged by anyone (signed or unsigned). The main plan here is to enforce the solution to -//! be PJR. Checking PJR on-chain is quite expensive, yet proving that a solution is **not** PJR is -//! rather cheap. If a queued solution is successfully proven bad: -//! -//! 1. We must surely slash whoever submitted that solution (might be a challenge for unsigned -//! solutions). -//! 2. We will fallback to the emergency strategy (likely extending the current era). -//! -//! **Bailing out**. The functionality of bailing out of a queued solution is nice. A miner can -//! submit a solution as soon as they _think_ it is high probability feasible, and do the checks -//! afterwards, and remove their solution (for a small cost of probably just transaction fees, or a -//! portion of the bond). -//! -//! **Conditionally open unsigned phase**: Currently, the unsigned phase is always opened. This is -//! useful because an honest validator will run substrate OCW code, which should be good enough to -//! trump a mediocre or malicious signed submission (assuming in the absence of honest signed bots). -//! If there are signed submissions, they can be checked against an absolute measure (e.g. PJR), -//! then we can only open the unsigned phase in extreme conditions (i.e. "no good signed solution -//! received") to spare some work for the active validators. -//! -//! **Allow smaller solutions and build up**: For now we only allow solutions that are exactly -//! [`DesiredTargets`], no more, no less. Over time, we can change this to a [min, max] where any -//! solution within this range is acceptable, where bigger solutions are prioritized. -//! -//! **Score based on (byte) size**: We should always prioritize small solutions over bigger ones, if -//! there is a tie. Even more harsh should be to enforce the bound of the `reduce` algorithm. // Implementation notes: // @@ -304,6 +215,8 @@ pub enum ElectionError { DataProvider(&'static str), /// the corresponding page in the queued supports is not available. SupportPageNotAvailable, + /// The election is not ongoing and therefore no results may be queried. + NotOngoing, } impl From for ElectionError { @@ -347,7 +260,7 @@ pub enum AdminOperation { /// /// This can be called in any phase and, can behave like any normal solution, but it should /// probably be used only in [`Phase::Emergency`]. - SetSolution(SolutionOf, ElectionScore), + SetSolution(Box>, ElectionScore), /// Trigger the (single page) fallback in `instant` mode, with the given parameters, and /// queue it if correct. /// @@ -505,18 +418,16 @@ pub mod pallet { => { let remaining_pages = Self::msp(); - log!(info, "starting snapshot creation, remaining block: {}", remaining_pages); let count = Self::create_targets_snapshot().unwrap(); let count = Self::create_voters_snapshot_paged(remaining_pages).unwrap(); - CurrentPhase::::put(Phase::Snapshot(remaining_pages)); + Self::phase_transition(Phase::Snapshot(remaining_pages)); todo_weight }, Phase::Snapshot(x) if x > 0 => { // we don't check block numbers here, snapshot creation is mandatory. let remaining_pages = x.saturating_sub(1); - log!(info, "continuing voter snapshot creation [{}]", remaining_pages); - CurrentPhase::::put(Phase::Snapshot(remaining_pages)); Self::create_voters_snapshot_paged(remaining_pages).unwrap(); + Self::phase_transition(Phase::Snapshot(remaining_pages)); todo_weight }, @@ -529,9 +440,7 @@ pub mod pallet { // TODO: even though we have the integrity test, what if we open the signed // phase, and there's not enough blocks to finalize it? that can happen under // any circumstance and we should deal with it. - - >::put(Phase::Signed); - Self::deposit_event(Event::SignedPhaseStarted(Self::round())); + Self::phase_transition(Phase::Signed); todo_weight }, @@ -541,8 +450,7 @@ pub mod pallet { remaining_blocks > unsigned_deadline => { // Start verification of the signed stuff. - >::put(Phase::SignedValidation(now)); - Self::deposit_event(Event::SignedValidationPhaseStarted(Self::round())); + Self::phase_transition(Phase::SignedValidation(now)); // we don't do anything else here. We expect the signed sub-pallet to handle // whatever else needs to be done. // TODO: this notification system based on block numbers is 100% based on the @@ -554,8 +462,7 @@ pub mod pallet { Phase::Signed | Phase::SignedValidation(_) | Phase::Snapshot(0) if remaining_blocks <= unsigned_deadline && remaining_blocks > Zero::zero() => { - >::put(Phase::Unsigned(now)); - Self::deposit_event(Event::UnsignedPhaseStarted(Self::round())); + Self::phase_transition(Phase::Unsigned(now)); todo_weight }, _ => T::WeightInfo::on_initialize_nothing(), @@ -621,12 +528,9 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// The signed phase of the given round has started. - SignedPhaseStarted(u32), - /// The unsigned validation phase of the given round has started. - SignedValidationPhaseStarted(u32), - /// The unsigned phase of the given round has started. - UnsignedPhaseStarted(u32), + /// A phase transition happened. Only checks major changes in the variants, not minor inner + /// values. + PhaseTransitioned { from: Phase>, to: Phase> }, } /// Error of the pallet that can be returned in response to dispatches. @@ -871,7 +775,7 @@ pub mod pallet { Phase::Emergency | Phase::Signed | Phase::SignedValidation(_) | - Phase::Export | + Phase::Export(_) | Phase::Unsigned(_) => Self::ensure_snapshot(true, T::Pages::get()), // cannot assume anything. We might halt at any point. Phase::Halted => Ok(()), @@ -970,6 +874,16 @@ impl Pallet { Zero::zero() } + pub(crate) fn phase_transition(to: Phase>) { + log!(debug, "transitioning phase from {:?} to {:?}", Self::current_phase(), to); + let from = Self::current_phase(); + use sp_std::mem::discriminant; + if discriminant(&from) != discriminant(&to) { + Self::deposit_event(Event::PhaseTransitioned { from, to }); + } + >::put(to); + } + /// Perform all the basic checks that are independent of the snapshot. TO be more specific, /// these are all the checks that you can do without the need to read the massive blob of the /// actual snapshot. This function only contains a handful of storage reads, with bounded size. @@ -1075,7 +989,7 @@ impl Pallet { >::mutate(|r| *r += 1); // Phase is off now. - >::put(Phase::Off); + Self::phase_transition(Phase::Off); // Kill everything in the verifier. T::Verifier::kill(); @@ -1108,12 +1022,20 @@ where type MaxBackersPerWinner = ::MaxBackersPerWinner; fn elect(remaining: PageIndex) -> Result, Self::Error> { + if !Self::ongoing() { + return Err(ElectionError::NotOngoing); + } + T::Verifier::get_queued_solution_page(remaining) .ok_or(ElectionError::SupportPageNotAvailable) .or_else(|err| { // if this is the last page, we might use the fallback to recover something. - log!(error, "primary election provider failed due to: {:?}, trying fallback", err); if remaining.is_zero() { + log!( + error, + "primary election provider failed due to: {:?}, trying fallback", + err + ); T::Fallback::elect(0).map_err(|fe| ElectionError::::Fallback(fe)) } else { Err(err) @@ -1126,7 +1048,7 @@ where log!(info, "receiving last call to elect(0), rotating round"); Self::rotate_round() } else { - >::put(Phase::Export); + Self::phase_transition(Phase::Export(remaining)) } supports.into() }) @@ -1136,7 +1058,7 @@ where // unsigned pallet, and thus the verifier will also be almost stuck, except for the // submission of emergency solutions. log!(error, "fetching page {} failed. entering emergency mode.", remaining); - >::put(Phase::Emergency); + Self::phase_transition(Phase::Emergency); err }) } @@ -1148,7 +1070,7 @@ where Phase::SignedValidation(_) | Phase::Unsigned(_) | Phase::Snapshot(_) | - Phase::Export => true, + Phase::Export(_) => true, } } } @@ -1184,7 +1106,13 @@ mod phase_rotation { roll_to(15); assert_eq!(MultiBlock::current_phase(), Phase::Signed); - assert_eq!(multi_block_events(), vec![Event::SignedPhaseStarted(0)]); + assert_eq!( + multi_block_events(), + vec![ + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(0) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed } + ] + ); assert_ok!(Snapshot::::ensure_snapshot(true, 1)); assert_eq!(MultiBlock::round(), 0); @@ -1197,7 +1125,14 @@ mod phase_rotation { assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(20)); assert_eq!( multi_block_events(), - vec![Event::SignedPhaseStarted(0), Event::SignedValidationPhaseStarted(0)], + vec![ + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(0) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, + Event::PhaseTransitioned { + from: Phase::Signed, + to: Phase::SignedValidation(20) + } + ], ); assert_ok!(Snapshot::::ensure_snapshot(true, 1)); @@ -1211,9 +1146,16 @@ mod phase_rotation { assert_eq!( multi_block_events(), vec![ - Event::SignedPhaseStarted(0), - Event::SignedValidationPhaseStarted(0), - Event::UnsignedPhaseStarted(0) + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(0) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, + Event::PhaseTransitioned { + from: Phase::Signed, + to: Phase::SignedValidation(20) + }, + Event::PhaseTransitioned { + from: Phase::SignedValidation(20), + to: Phase::Unsigned(25) + } ], ); assert_ok!(Snapshot::::ensure_snapshot(true, 1)); @@ -1279,7 +1221,13 @@ mod phase_rotation { roll_to(15); assert_eq!(MultiBlock::current_phase(), Phase::Signed); - assert_eq!(multi_block_events(), vec![Event::SignedPhaseStarted(0)]); + assert_eq!( + multi_block_events(), + vec![ + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(1) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed } + ] + ); assert_ok!(Snapshot::::ensure_snapshot(true, 2)); assert_eq!(MultiBlock::round(), 0); @@ -1292,7 +1240,14 @@ mod phase_rotation { assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(20)); assert_eq!( multi_block_events(), - vec![Event::SignedPhaseStarted(0), Event::SignedValidationPhaseStarted(0)], + vec![ + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(1) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, + Event::PhaseTransitioned { + from: Phase::Signed, + to: Phase::SignedValidation(20) + } + ], ); assert_ok!(Snapshot::::ensure_snapshot(true, 2)); @@ -1306,9 +1261,16 @@ mod phase_rotation { assert_eq!( multi_block_events(), vec![ - Event::SignedPhaseStarted(0), - Event::SignedValidationPhaseStarted(0), - Event::UnsignedPhaseStarted(0) + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(1) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, + Event::PhaseTransitioned { + from: Phase::Signed, + to: Phase::SignedValidation(20) + }, + Event::PhaseTransitioned { + from: Phase::SignedValidation(20), + to: Phase::Unsigned(25) + } ], ); assert_ok!(Snapshot::::ensure_snapshot(true, 2)); @@ -1385,7 +1347,13 @@ mod phase_rotation { roll_to(15); assert_eq!(MultiBlock::current_phase(), Phase::Signed); - assert_eq!(multi_block_events(), vec![Event::SignedPhaseStarted(0)]); + assert_eq!( + multi_block_events(), + vec![ + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(2) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed } + ] + ); assert_eq!(MultiBlock::round(), 0); roll_to(19); @@ -1396,7 +1364,14 @@ mod phase_rotation { assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(20)); assert_eq!( multi_block_events(), - vec![Event::SignedPhaseStarted(0), Event::SignedValidationPhaseStarted(0)], + vec![ + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(2) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, + Event::PhaseTransitioned { + from: Phase::Signed, + to: Phase::SignedValidation(20) + } + ] ); roll_to(24); @@ -1408,10 +1383,17 @@ mod phase_rotation { assert_eq!( multi_block_events(), vec![ - Event::SignedPhaseStarted(0), - Event::SignedValidationPhaseStarted(0), - Event::UnsignedPhaseStarted(0) - ], + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(2) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, + Event::PhaseTransitioned { + from: Phase::Signed, + to: Phase::SignedValidation(20) + }, + Event::PhaseTransitioned { + from: Phase::SignedValidation(20), + to: Phase::Unsigned(25) + } + ] ); roll_to(29); @@ -1491,7 +1473,13 @@ mod phase_rotation { roll_to(13); assert_eq!(MultiBlock::current_phase(), Phase::Signed); - assert_eq!(multi_block_events(), vec![Event::SignedPhaseStarted(0)]); + assert_eq!( + multi_block_events(), + vec![ + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(2) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed } + ] + ); assert_eq!(MultiBlock::round(), 0); roll_to(17); @@ -1503,7 +1491,14 @@ mod phase_rotation { assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(18)); assert_eq!( multi_block_events(), - vec![Event::SignedPhaseStarted(0), Event::SignedValidationPhaseStarted(0)], + vec![ + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(2) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, + Event::PhaseTransitioned { + from: Phase::Signed, + to: Phase::SignedValidation(18) + } + ] ); roll_to(22); @@ -1516,10 +1511,17 @@ mod phase_rotation { assert_eq!( multi_block_events(), vec![ - Event::SignedPhaseStarted(0), - Event::SignedValidationPhaseStarted(0), - Event::UnsignedPhaseStarted(0) - ], + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(2) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, + Event::PhaseTransitioned { + from: Phase::Signed, + to: Phase::SignedValidation(18) + }, + Event::PhaseTransitioned { + from: Phase::SignedValidation(18), + to: Phase::Unsigned(23) + } + ] ); roll_to(27); @@ -1599,7 +1601,14 @@ mod phase_rotation { assert_eq!( multi_block_events(), - vec![Event::SignedPhaseStarted(0), Event::SignedValidationPhaseStarted(0)], + vec![ + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(2) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, + Event::PhaseTransitioned { + from: Phase::Signed, + to: Phase::SignedValidation(25) + }, + ] ); // Signed validation can now be expanded until a call to `elect` comes @@ -1651,7 +1660,16 @@ mod phase_rotation { roll_to(25); assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); - assert_eq!(multi_block_events(), vec![Event::UnsignedPhaseStarted(0)],); + assert_eq!( + multi_block_events(), + vec![ + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(2) }, + Event::PhaseTransitioned { + from: Phase::Snapshot(0), + to: Phase::Unsigned(25) + }, + ] + ); // Unsigned can now be expanded until a call to `elect` comes roll_to(27); @@ -1716,8 +1734,12 @@ mod election_provider { assert_eq!( multi_block_events(), vec![ - crate::Event::SignedPhaseStarted(0), - crate::Event::SignedValidationPhaseStarted(0) + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(2) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, + Event::PhaseTransitioned { + from: Phase::Signed, + to: Phase::SignedValidation(20) + } ] ); assert_eq!(verifier_events(), vec![]); @@ -1950,8 +1972,6 @@ mod election_provider { verifier::QueuedSolution::::assert_killed(); // the phase is off, assert_eq!(MultiBlock::current_phase(), Phase::Off); - // the round is incremented, - assert_eq!(Round::::get(), 1); // the snapshot is cleared, assert_storage_noop!(Snapshot::::kill()); // and signed pallet is clean. @@ -1992,7 +2012,48 @@ mod election_provider { #[test] fn multi_page_elect_fallback_works() { - todo!() + ExtBuilder::full().onchain_fallback(true).build_and_execute(|| { + roll_to_signed_open(); + + // but then we immediately call `elect`. + assert_eq!(MultiBlock::elect(2), Err(ElectionError::SupportPageNotAvailable)); + + // This will set us to emergency phase, because we don't know wtf to do. + assert_eq!(MultiBlock::current_phase(), Phase::Emergency); + }); + + ExtBuilder::full().onchain_fallback(true).build_and_execute(|| { + roll_to_signed_open(); + + // but then we immediately call `elect`, this will work + assert!(MultiBlock::elect(0).is_ok()); + + assert_eq!( + multi_block_events(), + vec![ + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(2) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, + Event::PhaseTransitioned { from: Phase::Signed, to: Phase::Off } + ] + ); + + // This will set us to the off phase, since fallback saved us. + assert_eq!(MultiBlock::current_phase(), Phase::Off); + }); + } + + #[test] + fn elect_call_when_not_ongoing() { + ExtBuilder::full().onchain_fallback(true).build_and_execute(|| { + roll_to_snapshot_created(); + assert_eq!(MultiBlock::ongoing(), true); + assert!(MultiBlock::elect(0).is_ok()); + }); + ExtBuilder::full().onchain_fallback(true).build_and_execute(|| { + roll_to(10); + assert_eq!(MultiBlock::ongoing(), false); + assert_eq!(MultiBlock::elect(0), Err(ElectionError::NotOngoing)); + }); } } @@ -2000,12 +2061,20 @@ mod admin_ops { use super::*; #[test] - fn elect_call_on_off_or_halt_phase() { - todo!(); + fn set_solution_emergency() { + todo!() } #[test] - fn force_clear() { + fn set_minimum_solution_score() { + todo!() + } + + #[test] + fn trigger_fallback() {} + + #[test] + fn force_kill_all() { todo!("something very similar to the scenario of elect_does_not_finish_without_call_of_page_0, where we want to forcefully clear and put everything into halt phase") } } diff --git a/substrate/frame/election-provider-multi-block/src/mock/signed.rs b/substrate/frame/election-provider-multi-block/src/mock/signed.rs index 0f356dd42cdba..bbc9590708942 100644 --- a/substrate/frame/election-provider-multi-block/src/mock/signed.rs +++ b/substrate/frame/election-provider-multi-block/src/mock/signed.rs @@ -23,7 +23,7 @@ use crate::{ }, signed::{self as signed_pallet, Event as SignedEvent, Submissions}, verifier::{self, AsynchronousVerifier, SolutionDataProvider, VerificationResult, Verifier}, - PadSolutionPages, PagedRawSolution, Pagify, SolutionOf, + Event, PadSolutionPages, PagedRawSolution, Pagify, Phase, SolutionOf, }; use frame_election_provider_support::PageIndex; use frame_support::{ @@ -176,8 +176,9 @@ pub fn load_signed_for_verification_and_start( assert_eq!( multi_block_events(), vec![ - crate::Event::SignedPhaseStarted(round), - crate::Event::SignedValidationPhaseStarted(round) + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(2) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, + Event::PhaseTransitioned { from: Phase::Signed, to: Phase::SignedValidation(20) } ] ); assert_eq!(verifier_events(), vec![]); @@ -199,8 +200,9 @@ pub fn load_signed_for_verification_and_start_and_roll_to_verified( assert_eq!( multi_block_events(), vec![ - crate::Event::SignedPhaseStarted(round), - crate::Event::SignedValidationPhaseStarted(round) + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(2) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, + Event::PhaseTransitioned { from: Phase::Signed, to: Phase::SignedValidation(20) } ] ); assert_eq!(verifier_events(), vec![]); diff --git a/substrate/frame/election-provider-multi-block/src/types.rs b/substrate/frame/election-provider-multi-block/src/types.rs index a70fdf0612b80..2a60c1f249e5a 100644 --- a/substrate/frame/election-provider-multi-block/src/types.rs +++ b/substrate/frame/election-provider-multi-block/src/types.rs @@ -303,10 +303,10 @@ pub enum Phase { /// This value should be interpreted after `on_initialize` of this pallet has already been /// called. Snapshot(PageIndex), - /// Exporting has begun. + /// Exporting has begun, and the given page was the last one received. /// /// Once this is active, no more signed or solutions will be accepted. - Export, + Export(PageIndex), /// The emergency phase. This is enabled upon a failing call to `T::ElectionProvider::elect`. /// After that, the only way to leave this phase is through a successful /// `T::ElectionProvider::elect`. @@ -347,7 +347,7 @@ impl Phase { /// Whether the phase is export or not. pub fn is_export(&self) -> bool { - matches!(self, Phase::Export) + matches!(self, Phase::Export(_)) } /// Whether the phase is halted or not. From 28487af09fa2bcc1e7f1e116b8f4c0d55044dd08 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 20 Jan 2025 16:01:07 +0000 Subject: [PATCH 105/169] clean up main pallet a bit --- .../election-provider-multi-block/src/lib.rs | 176 +++++++++++++++--- .../src/mock/mod.rs | 6 + .../src/signed/mod.rs | 6 +- .../src/verifier/impls.rs | 25 ++- .../src/verifier/mod.rs | 6 + 5 files changed, 181 insertions(+), 38 deletions(-) diff --git a/substrate/frame/election-provider-multi-block/src/lib.rs b/substrate/frame/election-provider-multi-block/src/lib.rs index 7d231813f9f6d..c907f8482e079 100644 --- a/substrate/frame/election-provider-multi-block/src/lib.rs +++ b/substrate/frame/election-provider-multi-block/src/lib.rs @@ -245,28 +245,21 @@ impl From for ElectionError { #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub enum AdminOperation { - /// Clear all storage items. - /// - /// This will probably end-up being quite expensive. It will clear the internals of all - /// pallets, setting cleaning all of them. - /// - /// Hopefully, this can result in a full reset of the system. - KillEverything, + /// Forcefully go to the next round, starting from the Off Phase. + ForceRotateRound, /// Force-set the phase to the given phase. /// /// This can have many many combinations, use only with care and sufficient testing. ForceSetPhase(Phase>), /// Set the given (single page) emergency solution. /// - /// This can be called in any phase and, can behave like any normal solution, but it should - /// probably be used only in [`Phase::Emergency`]. - SetSolution(Box>, ElectionScore), + /// Can only be called in emergency phase. + EmergencySetSolution(Box>>, ElectionScore), /// Trigger the (single page) fallback in `instant` mode, with the given parameters, and /// queue it if correct. /// - /// This can be called in any phase and, can behave like any normal solution, but it should - /// probably be used only in [`Phase::Emergency`]. - TriggerFallback, + /// Can only be called in emergency phase. + EmergencyFallback, /// Set the minimum untrusted score. This is directly communicated to the verifier component to /// be taken into account. /// @@ -353,6 +346,8 @@ pub mod pallet { AccountId = Self::AccountId, BlockNumber = BlockNumberFor, DataProvider = Self::DataProvider, + MaxBackersPerWinner = ::MaxBackersPerWinner, + MaxWinnersPerPage = ::MaxWinnersPerPage, Pages = ConstU32<1>, >; @@ -374,8 +369,37 @@ pub mod pallet { impl Pallet { #[pallet::weight(0)] #[pallet::call_index(0)] - pub fn manage(_origin: OriginFor, op: AdminOperation) -> DispatchResultWithPostInfo { - todo!(); + pub fn manage(origin: OriginFor, op: AdminOperation) -> DispatchResultWithPostInfo { + use crate::verifier::Verifier; + use sp_npos_elections::EvaluateSupport; + + let _ = T::AdminOrigin::ensure_origin(origin); + match op { + AdminOperation::EmergencyFallback => { + ensure!(Self::current_phase() == Phase::Emergency, Error::::UnexpectedPhase); + let fallback = T::Fallback::elect(0).map_err(|_| Error::::Fallback)?; + let score = fallback.evaluate(); + T::Verifier::force_set_single_page_valid(fallback, 0, score); + Ok(().into()) + }, + AdminOperation::EmergencySetSolution(supports, score) => { + ensure!(Self::current_phase() == Phase::Emergency, Error::::UnexpectedPhase); + T::Verifier::force_set_single_page_valid(*supports, 0, score); + Ok(().into()) + }, + AdminOperation::ForceSetPhase(phase) => { + Self::phase_transition(phase); + Ok(().into()) + }, + AdminOperation::ForceRotateRound => { + Self::rotate_round(); + Ok(().into()) + }, + AdminOperation::SetMinUntrustedScore(score) => { + T::Verifier::set_minimum_score(score); + Ok(().into()) + }, + } } } @@ -548,6 +572,10 @@ pub mod pallet { WrongWinnerCount, /// The snapshot fingerprint is not a match. The solution is likely outdated. WrongFingerprint, + /// Triggering the `Fallback` failed. + Fallback, + /// Unexpected phase + UnexpectedPhase, } impl PartialEq for Error { @@ -635,10 +663,10 @@ pub mod pallet { /// Should be called only once we transition to [`Phase::Off`]. pub(crate) fn kill() { DesiredTargets::::kill(); - PagedVoterSnapshot::::remove_all(None); - PagedVoterSnapshotHash::::remove_all(None); - PagedTargetSnapshot::::remove_all(None); - PagedTargetSnapshotHash::::remove_all(None); + PagedVoterSnapshot::::clear(u32::MAX, None); + PagedVoterSnapshotHash::::clear(u32::MAX, None); + PagedTargetSnapshot::::clear(u32::MAX, None); + PagedTargetSnapshotHash::::clear(u32::MAX, None); } // ----------- non-mutables @@ -1065,11 +1093,12 @@ where fn ongoing() -> bool { match >::get() { - Phase::Off | Phase::Emergency | Phase::Halted => false, + Phase::Off | Phase::Halted => false, Phase::Signed | Phase::SignedValidation(_) | Phase::Unsigned(_) | Phase::Snapshot(_) | + Phase::Emergency | Phase::Export(_) => true, } } @@ -2057,25 +2086,118 @@ mod election_provider { } } +#[cfg(test)] mod admin_ops { use super::*; + use crate::mock::*; + use frame_support::assert_ok; #[test] - fn set_solution_emergency() { - todo!() + fn set_solution_emergency_works() { + ExtBuilder::full().build_and_execute(|| { + roll_to_signed_open(); + + // we get a call to elect(0). this will cause emergency, since no fallback is allowed. + assert_eq!( + MultiBlock::elect(0), + Err(ElectionError::Fallback("Emergency phase started.")) + ); + assert_eq!(MultiBlock::current_phase(), Phase::Emergency); + + // we can now set the solution to emergency. + let (emergency, score) = emergency_solution(); + assert_ok!(MultiBlock::manage( + RuntimeOrigin::root(), + AdminOperation::EmergencySetSolution(Box::new(emergency), score) + )); + + assert_eq!(MultiBlock::current_phase(), Phase::Emergency); + assert_ok!(MultiBlock::elect(0)); + assert_eq!(MultiBlock::current_phase(), Phase::Off); + + assert_eq!( + multi_block_events(), + vec![ + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(2) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, + Event::PhaseTransitioned { from: Phase::Signed, to: Phase::Emergency }, + Event::PhaseTransitioned { from: Phase::Emergency, to: Phase::Off } + ] + ); + assert_eq!( + verifier_events(), + vec![verifier::Event::Queued( + ElectionScore { minimal_stake: 55, sum_stake: 130, sum_stake_squared: 8650 }, + None + )] + ); + }) } #[test] - fn set_minimum_solution_score() { - todo!() + fn trigger_fallback_works() { + ExtBuilder::full().build_and_execute(|| { + roll_to_signed_open(); + + // we get a call to elect(0). this will cause emergency, since no fallback is allowed. + assert_eq!( + MultiBlock::elect(0), + Err(ElectionError::Fallback("Emergency phase started.")) + ); + assert_eq!(MultiBlock::current_phase(), Phase::Emergency); + + // we can now set the solution to emergency. + OnChianFallback::set(true); + assert_ok!(MultiBlock::manage( + RuntimeOrigin::root(), + AdminOperation::EmergencyFallback + )); + + assert_eq!(MultiBlock::current_phase(), Phase::Emergency); + assert_ok!(MultiBlock::elect(0)); + assert_eq!(MultiBlock::current_phase(), Phase::Off); + + assert_eq!( + multi_block_events(), + vec![ + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(2) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, + Event::PhaseTransitioned { from: Phase::Signed, to: Phase::Emergency }, + Event::PhaseTransitioned { from: Phase::Emergency, to: Phase::Off } + ] + ); + assert_eq!( + verifier_events(), + vec![verifier::Event::Queued( + ElectionScore { minimal_stake: 55, sum_stake: 130, sum_stake_squared: 8650 }, + None + )] + ); + }) } #[test] - fn trigger_fallback() {} + fn force_rotate_round() { + // clears the snapshot and verifier data. + // leaves the signed data as is since we bump the round. + } #[test] - fn force_kill_all() { - todo!("something very similar to the scenario of elect_does_not_finish_without_call_of_page_0, where we want to forcefully clear and put everything into halt phase") + fn set_minimum_solution_score() { + ExtBuilder::full().build_and_execute(|| { + assert_eq!(VerifierPallet::minimum_score(), None); + assert_ok!(MultiBlock::manage( + RuntimeOrigin::root(), + AdminOperation::SetMinUntrustedScore(ElectionScore { + minimal_stake: 100, + ..Default::default() + }) + )); + assert_eq!( + VerifierPallet::minimum_score().unwrap(), + ElectionScore { minimal_stake: 100, ..Default::default() } + ); + }); } } diff --git a/substrate/frame/election-provider-multi-block/src/mock/mod.rs b/substrate/frame/election-provider-multi-block/src/mock/mod.rs index 05d734a394ca3..f0d32412802ab 100644 --- a/substrate/frame/election-provider-multi-block/src/mock/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/mock/mod.rs @@ -663,3 +663,9 @@ pub fn balances(who: AccountId) -> (Balance, Balance) { pub fn bound_by_count(count: Option) -> DataProviderBounds { DataProviderBounds { count: count.map(|x| x.into()), size: None } } + +pub fn emergency_solution() -> (BoundedSupportsOf, ElectionScore) { + let supports = onchain::OnChainExecution::::elect(0).unwrap(); + let score = supports.evaluate(); + (supports, score) +} diff --git a/substrate/frame/election-provider-multi-block/src/signed/mod.rs b/substrate/frame/election-provider-multi-block/src/signed/mod.rs index d13bded3c8357..709d5ead7b013 100644 --- a/substrate/frame/election-provider-multi-block/src/signed/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/signed/mod.rs @@ -305,7 +305,7 @@ pub mod pallet { Self::mutate_checked(round, || { SortedScores::::mutate(round, |sorted| sorted.pop()).and_then( |(submitter, _score)| { - SubmissionStorage::::remove_prefix((round, &submitter), None); + SubmissionStorage::::clear_prefix((round, &submitter), u32::MAX, None); SubmissionMetadataStorage::::take(round, &submitter) .map(|metadata| (submitter, metadata)) }, @@ -328,7 +328,7 @@ pub mod pallet { sorted_scores.remove(index); } }); - SubmissionStorage::::remove_prefix((round, who), None); + SubmissionStorage::::clear_prefix((round, who), u32::MAX, None); SubmissionMetadataStorage::::take(round, who) }) } @@ -375,7 +375,7 @@ pub mod pallet { Ok(None) => {}, Ok(Some((discarded, _score))) => { let metadata = SubmissionMetadataStorage::::take(round, &discarded); - SubmissionStorage::::remove_prefix((round, &discarded), None); + SubmissionStorage::::clear_prefix((round, &discarded), u32::MAX, None); let _remaining = T::Currency::unreserve( &discarded, metadata.map(|m| m.deposit).defensive_unwrap_or_default(), diff --git a/substrate/frame/election-provider-multi-block/src/verifier/impls.rs b/substrate/frame/election-provider-multi-block/src/verifier/impls.rs index 3706a6b1a1837..c26860861b859 100644 --- a/substrate/frame/election-provider-multi-block/src/verifier/impls.rs +++ b/substrate/frame/election-provider-multi-block/src/verifier/impls.rs @@ -240,10 +240,10 @@ pub(crate) mod pallet { /// storage item group. pub(crate) fn clear_invalid_and_backings_unchecked() { match Self::invalid() { - ValidSolution::X => QueuedSolutionX::::remove_all(None), - ValidSolution::Y => QueuedSolutionY::::remove_all(None), + ValidSolution::X => QueuedSolutionX::::clear(u32::MAX, None), + ValidSolution::Y => QueuedSolutionY::::clear(u32::MAX, None), }; - QueuedSolutionBackings::::remove_all(None); + QueuedSolutionBackings::::clear(u32::MAX, None); } /// Write a single page of a valid solution into the `invalid` variant of the storage. @@ -285,8 +285,8 @@ pub(crate) mod pallet { Self::mutate_checked(|| { // clear everything about valid solutions. match Self::valid() { - ValidSolution::X => QueuedSolutionX::::remove_all(None), - ValidSolution::Y => QueuedSolutionY::::remove_all(None), + ValidSolution::X => QueuedSolutionX::::clear(u32::MAX, None), + ValidSolution::Y => QueuedSolutionY::::clear(u32::MAX, None), }; QueuedSolutionScore::::kill(); @@ -306,10 +306,10 @@ pub(crate) mod pallet { /// Should only be called once everything is done. pub(crate) fn kill() { Self::mutate_checked(|| { - QueuedSolutionX::::remove_all(None); - QueuedSolutionY::::remove_all(None); + QueuedSolutionX::::clear(u32::MAX, None); + QueuedSolutionY::::clear(u32::MAX, None); QueuedValidVariant::::kill(); - QueuedSolutionBackings::::remove_all(None); + QueuedSolutionBackings::::clear(u32::MAX, None); QueuedSolutionScore::::kill(); }) } @@ -834,6 +834,15 @@ impl Verifier for Pallet { ) -> Result, FeasibilityError> { Self::feasibility_check_page_inner(partial_solution, page) } + + fn force_set_single_page_valid( + partial_supports: SupportsOf, + page: PageIndex, + score: ElectionScore, + ) { + Self::deposit_event(Event::::Queued(score, QueuedSolution::::queued_score())); + QueuedSolution::::force_set_single_page_valid(page, partial_supports, score); + } } impl AsynchronousVerifier for Pallet { diff --git a/substrate/frame/election-provider-multi-block/src/verifier/mod.rs b/substrate/frame/election-provider-multi-block/src/verifier/mod.rs index f2f721aa48ac0..75a993c459dea 100644 --- a/substrate/frame/election-provider-multi-block/src/verifier/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/verifier/mod.rs @@ -185,6 +185,12 @@ pub trait Verifier { partial_solution: Self::Solution, page: PageIndex, ) -> Result, FeasibilityError>; + + fn force_set_single_page_valid( + partial_supports: SupportsOf, + page: PageIndex, + score: ElectionScore, + ); } /// Simple enum to encapsulate the result of the verification of a candidate solution. From 711e6ff33373bc08b026446ce19b73920bfe068c Mon Sep 17 00:00:00 2001 From: runcomet Date: Mon, 20 Jan 2025 08:12:44 -0800 Subject: [PATCH 106/169] Migrate `pallet-assets-freezer` to umbrella crate (#6599) Part of https://github.com/paritytech/polkadot-sdk/issues/6504 ### Added modules - `utility`: Traits not tied to any direct operation in the runtime. polkadot address: 14SRqZTC1d8rfxL8W1tBTnfUBPU23ACFVPzp61FyGf4ftUFg --------- Co-authored-by: Giuseppe Re --- Cargo.lock | 7 +--- substrate/frame/assets-freezer/Cargo.toml | 23 ++--------- substrate/frame/assets-freezer/src/impls.rs | 12 ++---- substrate/frame/assets-freezer/src/lib.rs | 43 +++++++++++---------- substrate/frame/assets-freezer/src/mock.rs | 23 ++++------- substrate/frame/assets-freezer/src/tests.rs | 16 ++------ substrate/frame/src/lib.rs | 29 ++++++++++---- 7 files changed, 64 insertions(+), 89 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 50d36338cd2cb..397d0c7fe823a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12175,17 +12175,12 @@ dependencies = [ name = "pallet-assets-freezer" version = "0.1.0" dependencies = [ - "frame-benchmarking 28.0.0", - "frame-support 28.0.0", - "frame-system 28.0.0", "log", "pallet-assets 29.1.0", "pallet-balances 28.0.0", "parity-scale-codec", + "polkadot-sdk-frame 0.1.0", "scale-info", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-runtime 31.0.1", ] [[package]] diff --git a/substrate/frame/assets-freezer/Cargo.toml b/substrate/frame/assets-freezer/Cargo.toml index 3fffa4d0627fa..d8c0ee6e442b2 100644 --- a/substrate/frame/assets-freezer/Cargo.toml +++ b/substrate/frame/assets-freezer/Cargo.toml @@ -16,46 +16,31 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -frame-benchmarking = { optional = true, workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } +frame = { workspace = true, features = ["runtime"] } log = { workspace = true } pallet-assets = { workspace = true } scale-info = { features = ["derive"], workspace = true } -sp-runtime = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true } -sp-core = { workspace = true } -sp-io = { workspace = true } [features] default = ["std"] std = [ "codec/std", - "frame-benchmarking?/std", - "frame-support/std", - "frame-system/std", + "frame/std", "log/std", "pallet-assets/std", "pallet-balances/std", "scale-info/std", - "sp-core/std", - "sp-io/std", - "sp-runtime/std", ] runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", + "frame/runtime-benchmarks", "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", ] try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", + "frame/try-runtime", "pallet-assets/try-runtime", "pallet-balances/try-runtime", - "sp-runtime/try-runtime", ] diff --git a/substrate/frame/assets-freezer/src/impls.rs b/substrate/frame/assets-freezer/src/impls.rs index cd383f1c3cd1e..8c9f148e1e9a4 100644 --- a/substrate/frame/assets-freezer/src/impls.rs +++ b/substrate/frame/assets-freezer/src/impls.rs @@ -16,13 +16,7 @@ // limitations under the License. use super::*; - -use frame_support::traits::{ - fungibles::{Inspect, InspectFreeze, MutateFreeze}, - tokens::{DepositConsequence, Fortitude, Preservation, Provenance, WithdrawConsequence}, -}; use pallet_assets::FrozenBalance; -use sp_runtime::traits::Zero; // Implements [`FrozenBalance`] from [`pallet-assets`], so it can understand how much of an // account balance is frozen, and is able to signal to this pallet when to clear the state of an @@ -115,7 +109,7 @@ impl, I: 'static> MutateFreeze for Pallet { id: &Self::Id, who: &T::AccountId, amount: Self::Balance, - ) -> sp_runtime::DispatchResult { + ) -> DispatchResult { if amount.is_zero() { return Self::thaw(asset, id, who); } @@ -135,7 +129,7 @@ impl, I: 'static> MutateFreeze for Pallet { id: &Self::Id, who: &T::AccountId, amount: Self::Balance, - ) -> sp_runtime::DispatchResult { + ) -> DispatchResult { if amount.is_zero() { return Ok(()); } @@ -150,7 +144,7 @@ impl, I: 'static> MutateFreeze for Pallet { Self::update_freezes(asset, who, freezes.as_bounded_slice()) } - fn thaw(asset: Self::AssetId, id: &Self::Id, who: &T::AccountId) -> sp_runtime::DispatchResult { + fn thaw(asset: Self::AssetId, id: &Self::Id, who: &T::AccountId) -> DispatchResult { let mut freezes = Freezes::::get(asset.clone(), who); freezes.retain(|f| &f.id != id); Self::update_freezes(asset, who, freezes.as_bounded_slice()) diff --git a/substrate/frame/assets-freezer/src/lib.rs b/substrate/frame/assets-freezer/src/lib.rs index b42d41ac1d925..5f718ed84820f 100644 --- a/substrate/frame/assets-freezer/src/lib.rs +++ b/substrate/frame/assets-freezer/src/lib.rs @@ -18,10 +18,10 @@ //! # Assets Freezer Pallet //! //! A pallet capable of freezing fungibles from `pallet-assets`. This is an extension of -//! `pallet-assets`, wrapping [`fungibles::Inspect`](`frame_support::traits::fungibles::Inspect`). +//! `pallet-assets`, wrapping [`fungibles::Inspect`](`Inspect`). //! It implements both -//! [`fungibles::freeze::Inspect`](frame_support::traits::fungibles::freeze::Inspect) and -//! [`fungibles::freeze::Mutate`](frame_support::traits::fungibles::freeze::Mutate). The complexity +//! [`fungibles::freeze::Inspect`](InspectFreeze) and +//! [`fungibles::freeze::Mutate`](MutateFreeze). The complexity //! of the operations is `O(n)`. where `n` is the variant count of `RuntimeFreezeReason`. //! //! ## Pallet API @@ -35,26 +35,27 @@ //! //! - Pallet hooks allowing [`pallet-assets`] to know the frozen balance for an account on a given //! asset (see [`pallet_assets::FrozenBalance`]). -//! - An implementation of -//! [`fungibles::freeze::Inspect`](frame_support::traits::fungibles::freeze::Inspect) and -//! [`fungibles::freeze::Mutate`](frame_support::traits::fungibles::freeze::Mutate), allowing -//! other pallets to manage freezes for the `pallet-assets` assets. +//! - An implementation of [`fungibles::freeze::Inspect`](InspectFreeze) and +//! [`fungibles::freeze::Mutate`](MutateFreeze), allowing other pallets to manage freezes for the +//! `pallet-assets` assets. #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::{ - pallet_prelude::*, - traits::{tokens::IdAmount, VariantCount, VariantCountOf}, - BoundedVec, -}; -use frame_system::pallet_prelude::BlockNumberFor; -use sp_runtime::{ - traits::{Saturating, Zero}, - BoundedSlice, +use frame::{ + prelude::*, + traits::{ + fungibles::{Inspect, InspectFreeze, MutateFreeze}, + tokens::{ + DepositConsequence, Fortitude, IdAmount, Preservation, Provenance, WithdrawConsequence, + }, + }, }; pub use pallet::*; +#[cfg(feature = "try-runtime")] +use frame::try_runtime::TryRuntimeError; + #[cfg(test)] mod mock; #[cfg(test)] @@ -62,7 +63,7 @@ mod tests; mod impls; -#[frame_support::pallet] +#[frame::pallet] pub mod pallet { use super::*; @@ -125,7 +126,7 @@ pub mod pallet { #[pallet::hooks] impl, I: 'static> Hooks> for Pallet { #[cfg(feature = "try-runtime")] - fn try_state(_: BlockNumberFor) -> Result<(), sp_runtime::TryRuntimeError> { + fn try_state(_: BlockNumberFor) -> Result<(), TryRuntimeError> { Self::do_try_state() } } @@ -159,13 +160,13 @@ impl, I: 'static> Pallet { Ok(()) } - #[cfg(any(test, feature = "try-runtime"))] - fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> { + #[cfg(feature = "try-runtime")] + fn do_try_state() -> Result<(), TryRuntimeError> { for (asset, who, _) in FrozenBalances::::iter() { let max_frozen_amount = Freezes::::get(asset.clone(), who.clone()).iter().map(|l| l.amount).max(); - frame_support::ensure!( + ensure!( FrozenBalances::::get(asset, who) == max_frozen_amount, "The `FrozenAmount` is not equal to the maximum amount in `Freezes` for (`asset`, `who`)" ); diff --git a/substrate/frame/assets-freezer/src/mock.rs b/substrate/frame/assets-freezer/src/mock.rs index bc903a018f7b8..ad08787aba27d 100644 --- a/substrate/frame/assets-freezer/src/mock.rs +++ b/substrate/frame/assets-freezer/src/mock.rs @@ -20,23 +20,15 @@ use crate as pallet_assets_freezer; pub use crate::*; use codec::{Compact, Decode, Encode, MaxEncodedLen}; -use frame_support::{ - derive_impl, - traits::{AsEnsureOriginWithArg, ConstU64}, -}; +use frame::testing_prelude::*; use scale_info::TypeInfo; -use sp_core::{ConstU32, H256}; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; pub type AccountId = u64; pub type Balance = u64; pub type AssetId = u32; type Block = frame_system::mocking::MockBlock; -frame_support::construct_runtime!( +construct_runtime!( pub enum Test { System: frame_system, @@ -48,7 +40,7 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; + type BaseCallFilter = Everything; type BlockWeights = (); type BlockLength = (); type DbWeight = (); @@ -70,7 +62,7 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; + type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { @@ -132,7 +124,7 @@ impl Config for Test { type RuntimeEvent = RuntimeEvent; } -pub fn new_test_ext(execute: impl FnOnce()) -> sp_io::TestExternalities { +pub fn new_test_ext(execute: impl FnOnce()) -> TestExternalities { let t = RuntimeGenesisConfig { assets: pallet_assets::GenesisConfig { assets: vec![(1, 0, true, 1)], @@ -145,11 +137,12 @@ pub fn new_test_ext(execute: impl FnOnce()) -> sp_io::TestExternalities { } .build_storage() .unwrap(); - let mut ext: sp_io::TestExternalities = t.into(); + let mut ext: TestExternalities = t.into(); ext.execute_with(|| { System::set_block_number(1); execute(); - frame_support::assert_ok!(AssetsFreezer::do_try_state()); + #[cfg(feature = "try-runtime")] + assert_ok!(AssetsFreezer::do_try_state()); }); ext diff --git a/substrate/frame/assets-freezer/src/tests.rs b/substrate/frame/assets-freezer/src/tests.rs index 4f2dea79c705a..b890dc98b5741 100644 --- a/substrate/frame/assets-freezer/src/tests.rs +++ b/substrate/frame/assets-freezer/src/tests.rs @@ -17,22 +17,16 @@ //! Tests for pallet-assets-freezer. -use crate::mock::*; +use crate::mock::{self, *}; use codec::Compact; -use frame_support::{ - assert_ok, assert_storage_noop, - traits::{ - fungibles::{Inspect, InspectFreeze, MutateFreeze}, - tokens::{Fortitude, Preservation}, - }, -}; +use frame::testing_prelude::*; use pallet_assets::FrozenBalance; const WHO: AccountId = 1; -const ASSET_ID: AssetId = 1; +const ASSET_ID: mock::AssetId = 1; -fn test_set_freeze(id: DummyFreezeReason, amount: Balance) { +fn test_set_freeze(id: DummyFreezeReason, amount: mock::Balance) { let mut freezes = Freezes::::get(ASSET_ID, WHO); if let Some(i) = freezes.iter_mut().find(|l| l.id == id) { @@ -281,8 +275,6 @@ mod impl_mutate_freeze { } mod with_pallet_assets { - use frame_support::assert_noop; - use super::*; #[test] diff --git a/substrate/frame/src/lib.rs b/substrate/frame/src/lib.rs index 18c7bd1239443..1c4b2ed5b821d 100644 --- a/substrate/frame/src/lib.rs +++ b/substrate/frame/src/lib.rs @@ -202,12 +202,10 @@ pub mod prelude { /// Dispatch types from `frame-support`, other fundamental traits #[doc(no_inline)] pub use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo}; - pub use frame_support::{ - defensive, defensive_assert, - traits::{ - Contains, EitherOf, EstimateNextSessionRotation, IsSubType, MapSuccess, NoOpPoll, - OnRuntimeUpgrade, OneSessionHandler, RankedMembers, RankedMembersSwapHandler, - }, + pub use frame_support::traits::{ + Contains, EitherOf, EstimateNextSessionRotation, Everything, IsSubType, MapSuccess, + NoOpPoll, OnRuntimeUpgrade, OneSessionHandler, RankedMembers, RankedMembersSwapHandler, + VariantCount, VariantCountOf, }; /// Pallet prelude of `frame-system`. @@ -225,6 +223,9 @@ pub mod prelude { /// All hashing related things pub use super::hashing::*; + /// All account related things. + pub use super::account::*; + /// All arithmetic types and traits used for safe math. pub use super::arithmetic::*; @@ -234,6 +235,10 @@ pub mod prelude { BlockNumberProvider, Bounded, Convert, DispatchInfoOf, Dispatchable, ReduceBy, ReplaceWithDefault, SaturatedConversion, Saturating, StaticLookup, TrailingZeroInput, }; + + /// Bounded storage related types. + pub use sp_runtime::{BoundedSlice, BoundedVec}; + /// Other error/result types for runtime #[doc(no_inline)] pub use sp_runtime::{ @@ -321,7 +326,7 @@ pub mod testing_prelude { /// Other helper macros from `frame_support` that help with asserting in tests. pub use frame_support::{ assert_err, assert_err_ignore_postinfo, assert_error_encoded_size, assert_noop, assert_ok, - assert_storage_noop, hypothetically, storage_alias, + assert_storage_noop, ensure, hypothetically, storage_alias, }; pub use frame_system::{self, mocking::*, RunToBlockHooks}; @@ -551,6 +556,16 @@ pub mod hashing { pub use sp_runtime::traits::{BlakeTwo256, Hash, Keccak256}; } +/// All account management related traits. +/// +/// This is already part of the [`prelude`]. +pub mod account { + pub use frame_support::traits::{ + AsEnsureOriginWithArg, ChangeMembers, EitherOfDiverse, InitializeMembers, + }; + pub use sp_runtime::traits::{IdentifyAccount, IdentityLookup}; +} + /// Access to all of the dependencies of this crate. In case the prelude re-exports are not enough, /// this module can be used. /// From 68bc3cd77f368a5a6e669dd2ce675ef1c74f4b77 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 20 Jan 2025 18:33:18 +0000 Subject: [PATCH 107/169] migrate signed to fungibles --- .../src/mock/mod.rs | 15 +- .../src/mock/signed.rs | 7 +- .../src/mock/weight_info.rs | 2 +- .../src/signed/mod.rs | 143 ++++++++++++++---- .../src/signed/tests.rs | 20 +-- 5 files changed, 135 insertions(+), 52 deletions(-) diff --git a/substrate/frame/election-provider-multi-block/src/mock/mod.rs b/substrate/frame/election-provider-multi-block/src/mock/mod.rs index f0d32412802ab..244f1f9bf72d5 100644 --- a/substrate/frame/election-provider-multi-block/src/mock/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/mock/mod.rs @@ -21,7 +21,7 @@ mod weight_info; use super::*; use crate::{ self as multi_block, - signed::{self as signed_pallet}, + signed::{self as signed_pallet, HoldReason}, unsigned::{ self as unsigned_pallet, miner::{BaseMiner, MinerError}, @@ -31,14 +31,14 @@ use crate::{ use codec::{Decode, Encode, MaxEncodedLen}; use frame_election_provider_support::{ bounds::{ElectionBounds, ElectionBoundsBuilder}, - NposSolution, SequentialPhragmen, TryFromUnboundedPagedSupports, + NposSolution, SequentialPhragmen, }; pub use frame_support::{assert_noop, assert_ok}; use frame_support::{ derive_impl, pallet_prelude::*, parameter_types, - traits::Hooks, + traits::{fungible::InspectHold, Hooks}, weights::{constants, Weight}, }; use frame_system::{pallet_prelude::*, EnsureRoot}; @@ -62,8 +62,6 @@ pub use staking::*; use std::{sync::Arc, vec}; pub type Extrinsic = sp_runtime::testing::TestXt; -pub type UncheckedExtrinsic = - sp_runtime::generic::UncheckedExtrinsic; pub type Balance = u64; pub type AccountId = u64; @@ -654,9 +652,12 @@ pub fn raw_paged_solution_low_score() -> PagedRawSolution { } } -/// Get the free and reserved balance of `who`. +/// Get the free and held balance of `who`. pub fn balances(who: AccountId) -> (Balance, Balance) { - (Balances::free_balance(who), Balances::reserved_balance(who)) + ( + Balances::free_balance(who), + Balances::balance_on_hold(&HoldReason::SignedSubmission.into(), &who), + ) } /// Election bounds based on just the given count. diff --git a/substrate/frame/election-provider-multi-block/src/mock/signed.rs b/substrate/frame/election-provider-multi-block/src/mock/signed.rs index bbc9590708942..d83c65183f316 100644 --- a/substrate/frame/election-provider-multi-block/src/mock/signed.rs +++ b/substrate/frame/election-provider-multi-block/src/mock/signed.rs @@ -19,7 +19,7 @@ use super::{Balance, Balances, Pages, Runtime, RuntimeEvent, SignedPallet, Syste use crate::{ mock::{ balances, multi_block_events, roll_next, roll_to_signed_validation_open, verifier_events, - AccountId, RuntimeOrigin, VerifierPallet, + AccountId, RuntimeHoldReason, RuntimeOrigin, VerifierPallet, }, signed::{self as signed_pallet, Event as SignedEvent, Submissions}, verifier::{self, AsynchronousVerifier, SolutionDataProvider, VerificationResult, Verifier}, @@ -27,10 +27,8 @@ use crate::{ }; use frame_election_provider_support::PageIndex; use frame_support::{ - assert_ok, dispatch::PostDispatchInfo, pallet_prelude::*, parameter_types, - traits::EstimateCallFee, BoundedVec, + assert_ok, dispatch::PostDispatchInfo, parameter_types, traits::EstimateCallFee, BoundedVec, }; -use frame_system::pallet_prelude::*; use sp_npos_elections::ElectionScore; use sp_runtime::{traits::Zero, Perbill}; @@ -78,6 +76,7 @@ parameter_types! { impl crate::signed::Config for Runtime { type RuntimeEvent = RuntimeEvent; + type RuntimeHoldReason = RuntimeHoldReason; type Currency = Balances; type DepositBase = SignedDepositBase; type DepositPerPage = SignedDepositPerPage; diff --git a/substrate/frame/election-provider-multi-block/src/mock/weight_info.rs b/substrate/frame/election-provider-multi-block/src/mock/weight_info.rs index 7db05d4d2476d..08bf9247215ef 100644 --- a/substrate/frame/election-provider-multi-block/src/mock/weight_info.rs +++ b/substrate/frame/election-provider-multi-block/src/mock/weight_info.rs @@ -19,7 +19,7 @@ use crate::{self as multi_block}; use frame_support::weights::Weight; -use sp_runtime::traits::{Bounded, Zero}; +use sp_runtime::traits::Zero; frame_support::parameter_types! { pub static MockWeightInfo: bool = false; diff --git a/substrate/frame/election-provider-multi-block/src/signed/mod.rs b/substrate/frame/election-provider-multi-block/src/signed/mod.rs index 709d5ead7b013..545dbc60e2ac7 100644 --- a/substrate/frame/election-provider-multi-block/src/signed/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/signed/mod.rs @@ -20,15 +20,15 @@ //! Signed submissions work on the basis of keeping a queue of submissions from random signed //! accounts, and sorting them based on the best claimed score to the worse. //! -//! Once the time to evaluate the signed phase comes, the solutions are checked from best-to-worse -//! claim, and they end up in either of the 3 buckets: +//! Once the time to evaluate the signed phase comes (`Phase::SignedValidation`), the solutions are +//! checked from best-to-worse claim, and they end up in either of the 3 buckets: //! //! 1. If they are the first, correct solution (and consequently the best one, since we start //! evaluating from the best claim), they are rewarded. //! 2. Any solution after the first correct solution is refunded in an unbiased way. //! 3. Any invalid solution that wasted valuable blockchain time gets slashed for their deposit. //! -//! # Future Plans: +//! ## Future Plans: //! //! **Lazy deletion**: //! Overall, this pallet can avoid the need to delete any storage item, by: @@ -43,12 +43,18 @@ use codec::{Decode, Encode, MaxEncodedLen}; use frame_election_provider_support::PageIndex; use frame_support::{ - traits::{Currency, Defensive, ReservableCurrency}, + traits::{ + tokens::{ + fungible::{Inspect, InspectHold, Mutate, MutateHold}, + Fortitude, Precision, + }, + Defensive, + }, BoundedVec, RuntimeDebugNoBound, }; use scale_info::TypeInfo; use sp_npos_elections::ElectionScore; -use sp_runtime::traits::{Saturating, Zero}; +use sp_runtime::traits::Saturating; use sp_std::prelude::*; /// Exports of this pallet @@ -63,7 +69,7 @@ mod benchmarking; mod tests; type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; + <::Currency as Inspect<::AccountId>>::Balance; /// All of the (meta) data around a signed submission #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Default, RuntimeDebugNoBound)] @@ -79,7 +85,7 @@ pub struct SubmissionMetadata { reward: BalanceOf, /// The score that this submission is claiming to achieve. claimed_score: ElectionScore, - /// A bounded-bit-vec of pages that have been submitted so far. + /// A bounded-bool-vec of pages that have been submitted so far. pages: BoundedVec, } @@ -113,7 +119,7 @@ impl SolutionDataProvider for Pallet { { // first, let's give them their reward. let reward = metadata.reward.saturating_add(metadata.fee); - let imbalance = T::Currency::deposit_creating(&winner, reward); + let imbalance = T::Currency::mint_into(&winner, reward); Self::deposit_event(Event::::Rewarded( current_round, winner.clone(), @@ -121,15 +127,25 @@ impl SolutionDataProvider for Pallet { )); // then, unreserve their deposit - let _remaining = T::Currency::unreserve(&winner, metadata.deposit); - debug_assert!(_remaining.is_zero()); + let _res = T::Currency::release( + &HoldReason::SignedSubmission.into(), + &winner, + metadata.deposit, + Precision::BestEffort, + ); + debug_assert!(_res.is_ok()); // note: we could wipe this data either over time, or via transactions. while let Some((discarded, metadata)) = Submissions::::take_leader_with_data(Self::current_round()) { - let _remaining = T::Currency::unreserve(&discarded, metadata.deposit); - debug_assert!(_remaining.is_zero()); + let _res = T::Currency::release( + &HoldReason::SignedSubmission.into(), + &discarded, + metadata.deposit, + Precision::BestEffort, + ); + debug_assert_eq!(_res, Ok(metadata.deposit)); Self::deposit_event(Event::::Discarded(current_round, discarded)); } @@ -146,8 +162,14 @@ impl SolutionDataProvider for Pallet { { // first, let's slash their deposit. let slash = metadata.deposit; - let (imbalance, _remainder) = T::Currency::slash_reserved(&loser, slash); - debug_assert!(_remainder.is_zero()); + let _res = T::Currency::burn_held( + &HoldReason::SignedSubmission.into(), + &loser, + slash, + Precision::BestEffort, + Fortitude::Force, + ); + debug_assert_eq!(_res, Ok(slash)); Self::deposit_event(Event::::Slashed(current_round, loser.clone(), slash)); // inform the verifier that they can now try again, if we're still in the signed @@ -175,10 +197,11 @@ pub mod pallet { use frame_support::{ dispatch::DispatchResultWithPostInfo, pallet_prelude::{StorageDoubleMap, ValueQuery, *}, - traits::{Defensive, DefensiveSaturating, EstimateCallFee, TryCollect}, + traits::{tokens::Precision, Defensive, DefensiveSaturating, EstimateCallFee, TryCollect}, transactional, Twox64Concat, }; use frame_system::{ensure_signed, pallet_prelude::*}; + use sp_io::MultiRemovalResults; use sp_runtime::{traits::Zero, DispatchError, Perbill}; use sp_std::collections::btree_set::BTreeSet; @@ -193,21 +216,44 @@ pub mod pallet { + IsType<::RuntimeEvent> + TryInto>; - type Currency: ReservableCurrency; + /// Handler to the currency. + type Currency: Inspect + + Mutate + + MutateHold; + /// Base deposit amount for a submission. type DepositBase: Get>; + + /// Extra deposit per-page. type DepositPerPage: Get>; + /// Base reward that is given to the winner. type RewardBase: Get>; + /// Maximum number of submissions. This, combined with `SignedValidationPhase` and `Pages` + /// dictates how many signed solutions we can verify. type MaxSubmissions: Get; + + /// The ratio of the deposit to return in case a signed account submits a solution via + /// [`Pallet::register`], but later calls [`Pallet::bail`]. type BailoutGraceRatio: Get; + /// Handler to estimate the fee of a call. Useful to refund the transaction fee of the + /// submitter for the winner. type EstimateCallFee: EstimateCallFee, BalanceOf>; + /// Overarching hold reason. + type RuntimeHoldReason: From; + type WeightInfo: WeightInfo; } + #[pallet::composite_enum] + pub enum HoldReason { + #[codec(index = 0)] + SignedSubmission, + } + /// Wrapper type for signed submissions. /// /// It handles 3 storage items: @@ -305,7 +351,14 @@ pub mod pallet { Self::mutate_checked(round, || { SortedScores::::mutate(round, |sorted| sorted.pop()).and_then( |(submitter, _score)| { - SubmissionStorage::::clear_prefix((round, &submitter), u32::MAX, None); + // NOTE: safe to remove unbounded, as at most `Pages` pages are stored. + let r: MultiRemovalResults = SubmissionStorage::::clear_prefix( + (round, &submitter), + u32::MAX, + None, + ); + debug_assert!(r.unique <= T::Pages::get()); + SubmissionMetadataStorage::::take(round, &submitter) .map(|metadata| (submitter, metadata)) }, @@ -328,7 +381,10 @@ pub mod pallet { sorted_scores.remove(index); } }); - SubmissionStorage::::clear_prefix((round, who), u32::MAX, None); + // Note: safe to remove unbounded, as at most `Pages` pages are stored. + let r = SubmissionStorage::::clear_prefix((round, who), u32::MAX, None); + debug_assert!(r.unique <= T::Pages::get()); + SubmissionMetadataStorage::::take(round, who) }) } @@ -375,12 +431,21 @@ pub mod pallet { Ok(None) => {}, Ok(Some((discarded, _score))) => { let metadata = SubmissionMetadataStorage::::take(round, &discarded); - SubmissionStorage::::clear_prefix((round, &discarded), u32::MAX, None); - let _remaining = T::Currency::unreserve( - &discarded, - metadata.map(|m| m.deposit).defensive_unwrap_or_default(), + // Note: safe to remove unbounded, as at most `Pages` pages are stored. + let _r = SubmissionStorage::::clear_prefix( + (round, &discarded), + u32::MAX, + None, ); - debug_assert!(_remaining.is_zero()); + debug_assert!(_r.unique <= T::Pages::get()); + let to_refund = metadata.map(|m| m.deposit).defensive_unwrap_or_default(); + let _released = T::Currency::release( + &HoldReason::SignedSubmission.into(), + &discarded, + to_refund, + Precision::BestEffort, + )?; + debug_assert_eq!(_released, to_refund); Pallet::::deposit_event(Event::::Discarded(round, discarded)); }, Err(_) => return Err("QueueFull".into()), @@ -433,10 +498,16 @@ pub mod pallet { let old_deposit = metadata.deposit; if new_deposit > old_deposit { let to_reserve = new_deposit - old_deposit; - T::Currency::reserve(who, to_reserve)?; + T::Currency::hold(&HoldReason::SignedSubmission.into(), who, to_reserve)?; } else { let to_unreserve = old_deposit - new_deposit; - let _ = T::Currency::unreserve(who, to_unreserve); + let _res = T::Currency::release( + &HoldReason::SignedSubmission.into(), + who, + to_unreserve, + Precision::BestEffort, + ); + debug_assert_eq!(_res, Ok(to_unreserve)); }; metadata.deposit = new_deposit; @@ -602,7 +673,8 @@ pub mod pallet { let new_metadata = SubmissionMetadata { claimed_score, deposit, reward, fee, pages }; - T::Currency::reserve(&who, deposit).map_err(|_| "insufficient funds")?; + T::Currency::hold(&HoldReason::SignedSubmission.into(), &who, deposit) + .map_err(|_| "insufficient funds")?; let round = Self::current_round(); let _ = Submissions::::try_register(round, &who, new_metadata)?; @@ -645,10 +717,21 @@ pub mod pallet { let to_slash = deposit.defensive_saturating_sub(to_refund); // TODO: a nice defensive op for this. - let _remainder = T::Currency::unreserve(&who, to_refund); - debug_assert!(_remainder.is_zero()); - let (imbalance, _remainder) = T::Currency::slash_reserved(&who, to_slash); - debug_assert!(_remainder.is_zero()); + let _res = T::Currency::release( + &HoldReason::SignedSubmission.into(), + &who, + to_refund, + Precision::BestEffort, + ); + debug_assert_eq!(_res, Ok(to_refund)); + let _res = T::Currency::burn_held( + &HoldReason::SignedSubmission.into(), + &who, + to_slash, + Precision::BestEffort, + Fortitude::Force, + ); + debug_assert_eq!(_res, Ok(to_slash)); Self::deposit_event(Event::::Bailed(round, who)); diff --git a/substrate/frame/election-provider-multi-block/src/signed/tests.rs b/substrate/frame/election-provider-multi-block/src/signed/tests.rs index 23ace5708c1df..a5c521997b6fc 100644 --- a/substrate/frame/election-provider-multi-block/src/signed/tests.rs +++ b/substrate/frame/election-provider-multi-block/src/signed/tests.rs @@ -85,12 +85,12 @@ mod calls { assert_full_snapshot(); let score_from = |x| ElectionScore { minimal_stake: x, ..Default::default() }; - let assert_reserved = |x| assert_eq!(balances(x), (95, 5)); - let assert_unreserved = |x| assert_eq!(balances(x), (100, 0)); + let assert_held = |x| assert_eq!(balances(x), (95, 5)); + let assert_unheld = |x| assert_eq!(balances(x), (100, 0)); assert_ok!(SignedPallet::register(RuntimeOrigin::signed(91), score_from(100))); assert_eq!(*Submissions::::leaderboard(0), vec![(91, score_from(100))]); - assert_reserved(91); + assert_held(91); assert!( matches!(signed_events().as_slice(), &[SignedEvent::Registered(_, x, _)] if x == 91) ); @@ -101,7 +101,7 @@ mod calls { *Submissions::::leaderboard(0), vec![(92, score_from(90)), (91, score_from(100))] ); - assert_reserved(92); + assert_held(92); assert!(matches!(signed_events().as_slice(), &[ SignedEvent::Registered(..), SignedEvent::Registered(_, x, _), @@ -113,7 +113,7 @@ mod calls { *Submissions::::leaderboard(0), vec![(92, score_from(90)), (91, score_from(100)), (93, score_from(110))] ); - assert_reserved(93); + assert_held(93); assert!(matches!(signed_events().as_slice(), &[ SignedEvent::Registered(..), SignedEvent::Registered(..), @@ -129,7 +129,7 @@ mod calls { *Submissions::::leaderboard(0), vec![(92, score_from(90)), (91, score_from(100)), (93, score_from(110))] ); - assert_unreserved(94); + assert_unheld(94); // no event has been emitted this time. assert!(matches!( signed_events().as_slice(), @@ -146,8 +146,6 @@ mod calls { *Submissions::::leaderboard(0), vec![(91, score_from(100)), (93, score_from(110)), (94, score_from(120))] ); - assert_reserved(94); - assert_unreserved(92); assert!(matches!( signed_events().as_slice(), &[ @@ -158,6 +156,8 @@ mod calls { SignedEvent::Registered(_, 94, _), ] )); + assert_held(94); + assert_unheld(92); // another stronger one comes, only replace the weakest. assert_ok!(SignedPallet::register(RuntimeOrigin::signed(95), score_from(105))); @@ -165,8 +165,8 @@ mod calls { *Submissions::::leaderboard(0), vec![(95, score_from(105)), (93, score_from(110)), (94, score_from(120))] ); - assert_reserved(95); - assert_unreserved(91); + assert_held(95); + assert_unheld(91); assert!(matches!( signed_events().as_slice(), &[ From 8186246358f4db67870a19087515ecad36d2a3ae Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 20 Jan 2025 19:01:49 +0000 Subject: [PATCH 108/169] cleanip signed phase a bit more --- .../src/signed/mod.rs | 43 +++--- .../src/signed/tests.rs | 124 +++++++++++++----- 2 files changed, 117 insertions(+), 50 deletions(-) diff --git a/substrate/frame/election-provider-multi-block/src/signed/mod.rs b/substrate/frame/election-provider-multi-block/src/signed/mod.rs index 545dbc60e2ac7..f8b05943ddd50 100644 --- a/substrate/frame/election-provider-multi-block/src/signed/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/signed/mod.rs @@ -119,7 +119,8 @@ impl SolutionDataProvider for Pallet { { // first, let's give them their reward. let reward = metadata.reward.saturating_add(metadata.fee); - let imbalance = T::Currency::mint_into(&winner, reward); + let _r = T::Currency::mint_into(&winner, reward); + debug_assert!(_r.is_ok()); Self::deposit_event(Event::::Rewarded( current_round, winner.clone(), @@ -411,7 +412,7 @@ pub mod pallet { let mut sorted_scores = SortedScores::::get(round); if let Some(_) = sorted_scores.iter().position(|(x, _)| x == who) { - return Err("Duplicate".into()) + return Err(Error::::Duplicate.into()); } else { // must be new. debug_assert!(!SubmissionMetadataStorage::::contains_key(round, who)); @@ -448,7 +449,7 @@ pub mod pallet { debug_assert_eq!(_released, to_refund); Pallet::::deposit_event(Event::::Discarded(round, discarded)); }, - Err(_) => return Err("QueueFull".into()), + Err(_) => return Err(Error::::QueueFull.into()), } } @@ -482,8 +483,8 @@ pub mod pallet { maybe_solution: Option, ) -> DispatchResultWithPostInfo { let mut metadata = - SubmissionMetadataStorage::::get(round, who).ok_or("NotRegistered")?; - ensure!(page < T::Pages::get(), "BadPageIndex"); + SubmissionMetadataStorage::::get(round, who).ok_or(Error::::NotRegistered)?; + ensure!(page < T::Pages::get(), Error::::BadPageIndex); // defensive only: we resize `meta.pages` once to be `T::Pages` elements once, and never // resize it again; `page` is checked here to be in bound; element must exist; qed. @@ -643,12 +644,25 @@ pub mod pallet { Bailed(u32, T::AccountId), } + #[pallet::error] + pub enum Error { + /// The phase is not signed. + PhaseNotSigned, + /// The submission is a duplicate. + Duplicate, + /// The queue is full. + QueueFull, + /// The page index is out of bounds. + BadPageIndex, + /// The account is not registered. + NotRegistered, + /// No submission found. + NoSubmission, + } + #[pallet::call] impl Pallet { - /// Submit an upcoming solution for registration. - /// - /// - no updating - /// - kept based on sorted scores. + /// Register oneself for an upcoming signed election. #[pallet::weight(0)] #[pallet::call_index(0)] pub fn register( @@ -656,7 +670,7 @@ pub mod pallet { claimed_score: ElectionScore, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - ensure!(crate::Pallet::::current_phase().is_signed(), "phase not signed"); + ensure!(crate::Pallet::::current_phase().is_signed(), Error::::PhaseNotSigned); // note: we could already check if this is a duplicate here, but prefer keeping the code // simple for now. @@ -673,8 +687,7 @@ pub mod pallet { let new_metadata = SubmissionMetadata { claimed_score, deposit, reward, fee, pages }; - T::Currency::hold(&HoldReason::SignedSubmission.into(), &who, deposit) - .map_err(|_| "insufficient funds")?; + T::Currency::hold(&HoldReason::SignedSubmission.into(), &who, deposit)?; let round = Self::current_round(); let _ = Submissions::::try_register(round, &who, new_metadata)?; @@ -690,7 +703,7 @@ pub mod pallet { maybe_solution: Option, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - ensure!(crate::Pallet::::current_phase().is_signed(), "phase not signed"); + ensure!(crate::Pallet::::current_phase().is_signed(), Error::::PhaseNotSigned); let round = Self::current_round(); Submissions::::try_mutate_page(round, &who, page, maybe_solution)?; @@ -707,10 +720,10 @@ pub mod pallet { #[transactional] pub fn bail(origin: OriginFor) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - ensure!(crate::Pallet::::current_phase().is_signed(), "phase not signed"); + ensure!(crate::Pallet::::current_phase().is_signed(), Error::::PhaseNotSigned); let round = Self::current_round(); let metadata = Submissions::::take_submission_with_data(round, &who) - .ok_or::("NoSubmission".into())?; + .ok_or(Error::::NoSubmission)?; let deposit = metadata.deposit; let to_refund = T::BailoutGraceRatio::get() * deposit; diff --git a/substrate/frame/election-provider-multi-block/src/signed/tests.rs b/substrate/frame/election-provider-multi-block/src/signed/tests.rs index a5c521997b6fc..5a500f3c1b9fd 100644 --- a/substrate/frame/election-provider-multi-block/src/signed/tests.rs +++ b/substrate/frame/election-provider-multi-block/src/signed/tests.rs @@ -2,8 +2,46 @@ use super::{Event as SignedEvent, *}; use crate::mock::*; use sp_core::bounded_vec; +pub type T = Runtime; + mod calls { use super::*; + use crate::Phase; + use sp_runtime::{DispatchError, TokenError::FundsUnavailable}; + + #[test] + fn cannot_register_with_insufficient_balance() { + ExtBuilder::signed().build_and_execute(|| { + roll_to_signed_open(); + // 777 is not funded. + assert_noop!( + SignedPallet::register(RuntimeOrigin::signed(777), Default::default()), + DispatchError::Token(FundsUnavailable) + ); + }); + + ExtBuilder::signed().build_and_execute(|| { + roll_to_signed_open(); + // 99 is funded but deposit is too high. + assert_eq!(balances(99), (100, 0)); + SignedDepositBase::set(101); + assert_noop!( + SignedPallet::register(RuntimeOrigin::signed(99), Default::default()), + DispatchError::Token(FundsUnavailable) + ); + }) + } + + #[test] + fn cannot_register_if_not_signed() { + ExtBuilder::signed().build_and_execute(|| { + assert!(crate::Pallet::::current_phase() != Phase::Signed); + assert_noop!( + SignedPallet::register(RuntimeOrigin::signed(99), Default::default()), + Error::::PhaseNotSigned + ); + }) + } #[test] fn register_metadata_works() { @@ -42,6 +80,7 @@ mod calls { let score = ElectionScore { minimal_stake: 90, ..Default::default() }; assert_ok!(SignedPallet::register(RuntimeOrigin::signed(999), score)); assert_eq!(balances(999), (95, 5)); + assert_eq!( Submissions::::metadata_of(0, 999).unwrap(), SubmissionMetadata { @@ -73,7 +112,7 @@ mod calls { RuntimeOrigin::signed(999), ElectionScore { minimal_stake: 80, ..Default::default() } ), - "Duplicate", + Error::::Duplicate, ); }) } @@ -123,7 +162,7 @@ mod calls { // weaker one comes while we don't have space. assert_noop!( SignedPallet::register(RuntimeOrigin::signed(94), score_from(80)), - "QueueFull" + Error::::QueueFull ); assert_eq!( *Submissions::::leaderboard(0), @@ -188,14 +227,12 @@ mod calls { roll_to_signed_open(); assert_full_snapshot(); - assert_ok!(SignedPallet::register( - RuntimeOrigin::signed(99), - ElectionScore { minimal_stake: 100, ..Default::default() } - )); + let score = ElectionScore { minimal_stake: 100, ..Default::default() }; + assert_ok!(SignedPallet::register(RuntimeOrigin::signed(99), score)); assert_eq!(balances(99), (95, 5)); // not submitted, cannot bailout. - assert_noop!(SignedPallet::bail(RuntimeOrigin::signed(999)), "NoSubmission"); + assert_noop!(SignedPallet::bail(RuntimeOrigin::signed(999)), Error::::NoSubmission); // can bail. assert_ok!(SignedPallet::bail(RuntimeOrigin::signed(99))); @@ -203,10 +240,10 @@ mod calls { assert_eq!(balances(99), (96, 0)); assert_no_data_for(0, 99); - assert!(matches!( - dbg!(signed_events()).as_slice(), - &[SignedEvent::Registered(..), SignedEvent::Bailed(..)] - )); + assert_eq!( + signed_events(), + vec![Event::Registered(0, 99, score), Event::Bailed(0, 99)] + ); }); } @@ -218,7 +255,7 @@ mod calls { assert_noop!( SignedPallet::submit_page(RuntimeOrigin::signed(99), 0, Default::default()), - "NotRegistered" + Error::::NotRegistered ); assert_ok!(SignedPallet::register( @@ -226,18 +263,15 @@ mod calls { ElectionScore { minimal_stake: 100, ..Default::default() } )); + assert_eq!(Submissions::::pages_of(0, 99).count(), 0); + assert_eq!(balances(99), (95, 5)); + + // indices 0, 1, 2 are valid. assert_noop!( SignedPallet::submit_page(RuntimeOrigin::signed(99), 3, Default::default()), - "BadPageIndex" - ); - assert_noop!( - SignedPallet::submit_page(RuntimeOrigin::signed(99), 4, Default::default()), - "BadPageIndex" + Error::::BadPageIndex ); - assert_eq!(Submissions::::pages_of(0, 99).count(), 0); - assert_eq!(balances(99), (95, 5)); - // add the first page. assert_ok!(SignedPallet::submit_page( RuntimeOrigin::signed(99), @@ -306,7 +340,7 @@ mod e2e { roll_to_signed_open(); assert_full_snapshot(); - // a valid, but weak solution. + // an invalid, but weak solution. { let score = ElectionScore { minimal_stake: 10, sum_stake: 10, sum_stake_squared: 100 }; @@ -364,21 +398,41 @@ mod e2e { roll_next(); roll_next(); - assert!(matches!( - signed_events().as_slice(), - &[ - SignedEvent::Registered(..), - SignedEvent::Stored(..), - SignedEvent::Registered(..), - SignedEvent::Stored(..), - SignedEvent::Stored(..), - SignedEvent::Stored(..), - SignedEvent::Registered(..), - SignedEvent::Slashed(0, 92, ..), - SignedEvent::Rewarded(0, 999, 4), // 3 reward + 1 tx-fee - SignedEvent::Discarded(0, 99), + assert_eq!( + signed_events(), + vec![ + Event::Registered( + 0, + 99, + ElectionScore { minimal_stake: 10, sum_stake: 10, sum_stake_squared: 100 } + ), + Event::Stored(0, 99, 0), + Event::Registered( + 0, + 999, + ElectionScore { + minimal_stake: 55, + sum_stake: 130, + sum_stake_squared: 8650 + } + ), + Event::Stored(0, 999, 0), + Event::Stored(0, 999, 1), + Event::Stored(0, 999, 2), + Event::Registered( + 0, + 92, + ElectionScore { + minimal_stake: 110, + sum_stake: 130, + sum_stake_squared: 8650 + } + ), + Event::Slashed(0, 92, 5), + Event::Rewarded(0, 999, 4), + Event::Discarded(0, 99) ] - )); + ); assert_eq!(balances(99), (100, 0)); assert_eq!(balances(999), (104, 0)); From 2c4ceccebe2c338029eef243645455d525a5a78b Mon Sep 17 00:00:00 2001 From: Benjamin Gallois Date: Mon, 20 Jan 2025 22:19:48 +0100 Subject: [PATCH 109/169] Fix `frame-benchmarking-cli` not buildable without rocksdb (#7263) ## Description The `frame-benchmarking-cli` crate has not been buildable without the `rocksdb` feature since version 1.17.0. **Error:** ```rust self.database()?.unwrap_or(Database::RocksDb), ^^^^^^^ variant or associated item not found in `Database` ``` This issue is also related to the `rocksdb` feature bleeding (#3793), where the `rocksdb` feature was always activated even when compiling this crate with `--no-default-features`. **Fix:** - Resolved the error by choosing `paritydb` as the default database when compiled without the `rocksdb` feature. - Fixed the issue where the `sc-cli` crate's `rocksdb` feature was always active, even compiling `frame-benchmarking-cli` with `--no-default-features`. ## Review Notes Fix the crate to be built without rocksdb, not intended to solve #3793. --------- Co-authored-by: command-bot <> --- polkadot/node/metrics/Cargo.toml | 2 +- prdoc/pr_7263.prdoc | 28 +++++++++++++++++++ .../benchmarking-cli/src/overhead/command.rs | 2 +- 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 prdoc/pr_7263.prdoc diff --git a/polkadot/node/metrics/Cargo.toml b/polkadot/node/metrics/Cargo.toml index 454337cb63f87..318deca4f2438 100644 --- a/polkadot/node/metrics/Cargo.toml +++ b/polkadot/node/metrics/Cargo.toml @@ -18,7 +18,7 @@ gum = { workspace = true, default-features = true } metered = { features = ["futures_channel"], workspace = true } # Both `sc-service` and `sc-cli` are required by runtime metrics `logger_hook()`. -sc-cli = { workspace = true, default-features = true } +sc-cli = { workspace = true } sc-service = { workspace = true, default-features = true } bs58 = { features = ["alloc"], workspace = true, default-features = true } diff --git a/prdoc/pr_7263.prdoc b/prdoc/pr_7263.prdoc new file mode 100644 index 0000000000000..892e804939559 --- /dev/null +++ b/prdoc/pr_7263.prdoc @@ -0,0 +1,28 @@ +title: Fix `frame-benchmarking-cli` not buildable without rocksdb +doc: +- audience: Runtime Dev + description: |- + ## Description + + The `frame-benchmarking-cli` crate has not been buildable without the `rocksdb` feature since version 1.17.0. + + **Error:** + ```rust + self.database()?.unwrap_or(Database::RocksDb), + ^^^^^^^ variant or associated item not found in `Database` + ``` + + This issue is also related to the `rocksdb` feature bleeding (#3793), where the `rocksdb` feature was always activated even when compiling this crate with `--no-default-features`. + + **Fix:** + - Resolved the error by choosing `paritydb` as the default database when compiled without the `rocksdb` feature. + - Fixed the issue where the `sc-cli` crate's `rocksdb` feature was always active, even compiling `frame-benchmarking-cli` with `--no-default-features`. + + ## Review Notes + + Fix the crate to be built without rocksdb, not intended to solve #3793. +crates: +- name: polkadot-node-metrics + bump: patch +- name: frame-benchmarking-cli + bump: patch diff --git a/substrate/utils/frame/benchmarking-cli/src/overhead/command.rs b/substrate/utils/frame/benchmarking-cli/src/overhead/command.rs index 8df8ee5464f71..847f8e16c0df0 100644 --- a/substrate/utils/frame/benchmarking-cli/src/overhead/command.rs +++ b/substrate/utils/frame/benchmarking-cli/src/overhead/command.rs @@ -482,7 +482,7 @@ impl OverheadCmd { let database_source = self.database_config( &base_path.path().to_path_buf(), self.database_cache_size()?.unwrap_or(1024), - self.database()?.unwrap_or(Database::RocksDb), + self.database()?.unwrap_or(Database::Auto), )?; let backend = new_db_backend(DatabaseSettings { From cbf3925e1fe1383b998cfb428038c46da1577501 Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Mon, 20 Jan 2025 23:58:21 +0100 Subject: [PATCH 110/169] [eth-indexer] subscribe to finalize blocks instead of best blocks (#7260) For eth-indexer, it's probably safer to use `subscribe_finalized` and index these blocks into the DB rather than `subscribe_best` --------- Co-authored-by: command-bot <> --- prdoc/pr_7260.prdoc | 10 ++++++ substrate/frame/revive/rpc/src/client.rs | 45 +++++++++++++++--------- 2 files changed, 39 insertions(+), 16 deletions(-) create mode 100644 prdoc/pr_7260.prdoc diff --git a/prdoc/pr_7260.prdoc b/prdoc/pr_7260.prdoc new file mode 100644 index 0000000000000..62f73120bc198 --- /dev/null +++ b/prdoc/pr_7260.prdoc @@ -0,0 +1,10 @@ +title: '[eth-indexer] subscribe to finalize blocks instead of best blocks' +doc: +- audience: Runtime Dev + description: 'For eth-indexer, it''s probably safer to use `subscribe_finalized` + and index these blocks into the DB rather than `subscribe_best` + + ' +crates: +- name: pallet-revive-eth-rpc + bump: minor diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index a5a022f97228d..7a72f8e26b0b4 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -68,6 +68,14 @@ pub type Shared = Arc>; /// The runtime balance type. pub type Balance = u128; +/// The subscription type used to listen to new blocks. +pub enum SubscriptionType { + /// Subscribe to the best blocks. + BestBlocks, + /// Subscribe to the finalized blocks. + FinalizedBlocks, +} + /// Unwrap the original `jsonrpsee::core::client::Error::Call` error. fn unwrap_call_err(err: &subxt::error::RpcError) -> Option { use subxt::backend::rpc::reconnecting_rpc_client; @@ -278,19 +286,23 @@ impl Client { /// Subscribe to new best blocks, and execute the async closure with /// the extracted block and ethereum transactions - async fn subscribe_new_blocks(&self, callback: F) -> Result<(), ClientError> + async fn subscribe_new_blocks( + &self, + subscription_type: SubscriptionType, + callback: F, + ) -> Result<(), ClientError> where F: Fn(SubstrateBlock) -> Fut + Send + Sync, Fut: std::future::Future> + Send, { log::info!(target: LOG_TARGET, "Subscribing to new blocks"); - let mut block_stream = match self.api.blocks().subscribe_best().await { - Ok(s) => s, - Err(err) => { - log::error!(target: LOG_TARGET, "Failed to subscribe to blocks: {err:?}"); - return Err(err.into()); - }, - }; + let mut block_stream = match subscription_type { + SubscriptionType::BestBlocks => self.api.blocks().subscribe_best().await, + SubscriptionType::FinalizedBlocks => self.api.blocks().subscribe_finalized().await, + } + .inspect_err(|err| { + log::error!(target: LOG_TARGET, "Failed to subscribe to blocks: {err:?}"); + })?; while let Some(block) = block_stream.next().await { let block = match block { @@ -324,7 +336,7 @@ impl Client { let client = self.clone(); spawn_handle.spawn("subscribe-blocks", None, async move { let res = client - .subscribe_new_blocks(|block| async { + .subscribe_new_blocks(SubscriptionType::BestBlocks, |block| async { let receipts = extract_receipts_from_block(&block).await?; client.receipt_provider.insert(&block.hash(), &receipts).await; @@ -347,13 +359,14 @@ impl Client { &self, oldest_block: Option, ) -> Result<(), ClientError> { - let new_blocks_fut = self.subscribe_new_blocks(|block| async move { - let receipts = extract_receipts_from_block(&block).await.inspect_err(|err| { - log::error!(target: LOG_TARGET, "Failed to extract receipts from block: {err:?}"); - })?; - self.receipt_provider.insert(&block.hash(), &receipts).await; - Ok(()) - }); + let new_blocks_fut = + self.subscribe_new_blocks(SubscriptionType::FinalizedBlocks, |block| async move { + let receipts = extract_receipts_from_block(&block).await.inspect_err(|err| { + log::error!(target: LOG_TARGET, "Failed to extract receipts from block: {err:?}"); + })?; + self.receipt_provider.insert(&block.hash(), &receipts).await; + Ok(()) + }); let Some(oldest_block) = oldest_block else { return new_blocks_fut.await }; From b5b21ff64f855de8dd0bb14ab9757ac2c9732e87 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 21 Jan 2025 09:02:05 +0000 Subject: [PATCH 111/169] integrate try-runtime --- .../src/mock/mod.rs | 10 +-- .../src/signed/mod.rs | 66 +++++++++++-------- 2 files changed, 43 insertions(+), 33 deletions(-) diff --git a/substrate/frame/election-provider-multi-block/src/mock/mod.rs b/substrate/frame/election-provider-multi-block/src/mock/mod.rs index 244f1f9bf72d5..ade4002c89e9f 100644 --- a/substrate/frame/election-provider-multi-block/src/mock/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/mock/mod.rs @@ -396,11 +396,11 @@ impl ExecuteWithSanityChecks for sp_io::TestExternalities { } fn all_pallets_sanity_checks() { - let _ = VerifierPallet::sanity_check() - .and(UnsignedPallet::sanity_check()) - .and(MultiBlock::sanity_check()) - .and(SignedPallet::sanity_check()) - .unwrap(); + let now = System::block_number(); + let _ = VerifierPallet::sanity_check().unwrap(); + let _ = UnsignedPallet::sanity_check().unwrap(); + let _ = MultiBlock::sanity_check().unwrap(); + let _ = SignedPallet::do_try_state(now).unwrap(); } /// Fully verify a solution. diff --git a/substrate/frame/election-provider-multi-block/src/signed/mod.rs b/substrate/frame/election-provider-multi-block/src/signed/mod.rs index f8b05943ddd50..e24cdcc5c36d0 100644 --- a/substrate/frame/election-provider-multi-block/src/signed/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/signed/mod.rs @@ -40,28 +40,34 @@ //! //! **Metadata update**: imagine you mis-computed your score. +use crate::verifier::{AsynchronousVerifier, SolutionDataProvider, Status, VerificationResult}; use codec::{Decode, Encode, MaxEncodedLen}; use frame_election_provider_support::PageIndex; use frame_support::{ + dispatch::DispatchResultWithPostInfo, + pallet_prelude::{StorageDoubleMap, ValueQuery, *}, traits::{ tokens::{ fungible::{Inspect, InspectHold, Mutate, MutateHold}, Fortitude, Precision, }, - Defensive, + Defensive, DefensiveSaturating, EstimateCallFee, TryCollect, }, - BoundedVec, RuntimeDebugNoBound, + transactional, BoundedVec, RuntimeDebugNoBound, Twox64Concat, }; +use frame_system::{ensure_signed, pallet_prelude::*}; use scale_info::TypeInfo; +use sp_io::MultiRemovalResults; use sp_npos_elections::ElectionScore; -use sp_runtime::traits::Saturating; -use sp_std::prelude::*; +use sp_runtime::{ + traits::{Saturating, Zero}, + DispatchError, Perbill, TryRuntimeError, +}; +use sp_std::{collections::btree_set::BTreeSet, prelude::*}; /// Exports of this pallet pub use pallet::*; -use crate::verifier::{AsynchronousVerifier, SolutionDataProvider, Status, VerificationResult}; - #[cfg(feature = "runtime-benchmarks")] mod benchmarking; @@ -194,18 +200,6 @@ impl SolutionDataProvider for Pallet { #[frame_support::pallet] pub mod pallet { use super::*; - use crate::verifier::AsynchronousVerifier; - use frame_support::{ - dispatch::DispatchResultWithPostInfo, - pallet_prelude::{StorageDoubleMap, ValueQuery, *}, - traits::{tokens::Precision, Defensive, DefensiveSaturating, EstimateCallFee, TryCollect}, - transactional, Twox64Concat, - }; - use frame_system::{ensure_signed, pallet_prelude::*}; - use sp_io::MultiRemovalResults; - use sp_runtime::{traits::Zero, DispatchError, Perbill}; - use sp_std::collections::btree_set::BTreeSet; - pub trait WeightInfo {} impl WeightInfo for () {} @@ -541,7 +535,7 @@ pub mod pallet { } } - #[cfg(any(test, debug_assertions))] + #[cfg(any(test, debug_assertions, feature = "try-runtime"))] impl Submissions { pub fn submissions_iter( round: u32, @@ -574,7 +568,7 @@ pub mod pallet { /// Ensure that all the storage items associated with the given round are in `killed` state, /// meaning that in the expect state after an election is OVER. - pub(crate) fn ensure_killed(round: u32) -> Result<(), &'static str> { + pub(crate) fn ensure_killed(round: u32) -> DispatchResult { ensure!(Self::metadata_iter(round).count() == 0, "metadata_iter not cleared."); ensure!(Self::submissions_iter(round).count() == 0, "submissions_iter not cleared."); ensure!(Self::sorted_submitters(round).len() == 0, "sorted_submitters not cleared."); @@ -583,7 +577,7 @@ pub mod pallet { } /// Perform all the sanity checks of this storage item group at the given round. - pub(crate) fn sanity_check_round(round: u32) -> Result<(), &'static str> { + pub(crate) fn sanity_check_round(round: u32) -> DispatchResult { let sorted_scores = SortedScores::::get(round); assert_eq!( sorted_scores.clone().into_iter().map(|(x, _)| x).collect::>().len(), @@ -695,6 +689,14 @@ pub mod pallet { Ok(().into()) } + /// Submit a single page of a solution. + /// + /// Must always come after [`Pallet::register`]. + /// + /// `maybe_solution` can be set to `None` to erase the page. + /// + /// Collects deposits from the signed origin based on [`Config::DepositBase`] and + /// [`Config::DepositPerPage`]. #[pallet::weight(0)] #[pallet::call_index(1)] pub fn submit_page( @@ -714,6 +716,8 @@ pub mod pallet { /// Retract a submission. /// + /// A portion of the deposit may be returned, based on the [`Config::BailoutGraceRatio`]. + /// /// This will fully remove the solution from storage. #[pallet::weight(0)] #[pallet::call_index(2)] @@ -729,21 +733,23 @@ pub mod pallet { let to_refund = T::BailoutGraceRatio::get() * deposit; let to_slash = deposit.defensive_saturating_sub(to_refund); - // TODO: a nice defensive op for this. let _res = T::Currency::release( &HoldReason::SignedSubmission.into(), &who, to_refund, Precision::BestEffort, - ); + ) + .defensive(); debug_assert_eq!(_res, Ok(to_refund)); + let _res = T::Currency::burn_held( &HoldReason::SignedSubmission.into(), &who, to_slash, Precision::BestEffort, Fortitude::Force, - ); + ) + .defensive(); debug_assert_eq!(_res, Ok(to_slash)); Self::deposit_event(Event::::Bailed(round, who)); @@ -755,7 +761,6 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { fn on_initialize(now: BlockNumberFor) -> Weight { - // TODO: we could rely on an explicit notification system instead of this.. but anyways. if crate::Pallet::::current_phase().is_signed_validation_open_at(now) { let maybe_leader = Submissions::::leader(Self::current_round()); sublog!( @@ -768,7 +773,7 @@ pub mod pallet { if maybe_leader.is_some() { // defensive: signed phase has just began, verifier should be in a clear state // and ready to accept a solution. - ::start().defensive(); + let _ = ::start().defensive(); } } @@ -781,12 +786,17 @@ pub mod pallet { // TODO: weight Default::default() } + + #[cfg(feature = "try-runtime")] + fn try_state(n: BlockNumberFor) -> Result<(), TryRuntimeError> { + Self::do_try_state(n) + } } } impl Pallet { - #[cfg(any(debug_assertions, test))] - pub(crate) fn sanity_check() -> Result<(), &'static str> { + #[cfg(any(feature = "try-runtime", test))] + pub(crate) fn do_try_state(_n: BlockNumberFor) -> Result<(), TryRuntimeError> { Submissions::::sanity_check_round(Self::current_round()) } From 12ed0f4ffe4dcf3a8fe8928e3791141a110fad8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Molina=20Colmenero?= Date: Tue, 21 Jan 2025 10:49:09 +0100 Subject: [PATCH 112/169] Add an extra_constant to pallet-collator-selection (#7206) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently `pallet-collator-selection` does not expose a way to query the assigned pot account derived from the `PotId` configuration item. Without it, it is not possible to transfer the existential deposit to it. This PR addresses this issue by exposing an extra constant. --------- Co-authored-by: Bastian Köcher --- cumulus/pallets/collator-selection/src/lib.rs | 13 +++++++++++++ prdoc/pr_7206.prdoc | 13 +++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 prdoc/pr_7206.prdoc diff --git a/cumulus/pallets/collator-selection/src/lib.rs b/cumulus/pallets/collator-selection/src/lib.rs index 9d7e62af3c68f..34c6ca8b36eab 100644 --- a/cumulus/pallets/collator-selection/src/lib.rs +++ b/cumulus/pallets/collator-selection/src/lib.rs @@ -150,22 +150,27 @@ pub mod pallet { type UpdateOrigin: EnsureOrigin; /// Account Identifier from which the internal Pot is generated. + #[pallet::constant] type PotId: Get; /// Maximum number of candidates that we should have. /// /// This does not take into account the invulnerables. + #[pallet::constant] type MaxCandidates: Get; /// Minimum number eligible collators. Should always be greater than zero. This includes /// Invulnerable collators. This ensures that there will always be one collator who can /// produce a block. + #[pallet::constant] type MinEligibleCollators: Get; /// Maximum number of invulnerables. + #[pallet::constant] type MaxInvulnerables: Get; // Will be kicked if block is not produced in threshold. + #[pallet::constant] type KickThreshold: Get>; /// A stable ID for a validator. @@ -183,6 +188,14 @@ pub mod pallet { type WeightInfo: WeightInfo; } + #[pallet::extra_constants] + impl Pallet { + /// Gets this pallet's derived pot account. + fn pot_account() -> T::AccountId { + Self::account_id() + } + } + /// Basic information about a collation candidate. #[derive( PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen, diff --git a/prdoc/pr_7206.prdoc b/prdoc/pr_7206.prdoc new file mode 100644 index 0000000000000..d605308ba54c0 --- /dev/null +++ b/prdoc/pr_7206.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Add an extra_constant to pallet-collator-selection" + +doc: + - audience: Runtime Dev + description: | + - Allows to query collator-selection's pot account via extra constant. + +crates: + - name: pallet-collator-selection + bump: minor \ No newline at end of file From 0f81ebadabbf2c52c0eef9af67ebc1beae2f7c57 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 21 Jan 2025 10:19:21 +0000 Subject: [PATCH 113/169] fix up signed phase, lgtm --- .../src/mock/mod.rs | 10 ++- .../src/signed/tests.rs | 23 +++++- .../src/types.rs | 3 +- .../src/unsigned/miner.rs | 2 +- .../src/verifier/impls.rs | 70 ++++++++---------- .../src/verifier/mod.rs | 27 ++++--- .../src/verifier/tests.rs | 73 +++++++++++++++++-- 7 files changed, 144 insertions(+), 64 deletions(-) diff --git a/substrate/frame/election-provider-multi-block/src/mock/mod.rs b/substrate/frame/election-provider-multi-block/src/mock/mod.rs index ade4002c89e9f..3a23f76855fb4 100644 --- a/substrate/frame/election-provider-multi-block/src/mock/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/mock/mod.rs @@ -145,6 +145,7 @@ parameter_types! { // trimmed accidentally in any test. #[derive(Encode, Decode, PartialEq, Eq, Debug, scale_info::TypeInfo, MaxEncodedLen)] pub static MaxBackersPerWinner: u32 = 12; + pub static MaxBackersPerWinnerFinal: u32 = 12; // we have 4 targets in total and we desire `Desired` thereof, no single page can represent more // than the min of these two. #[derive(Encode, Decode, PartialEq, Eq, Debug, scale_info::TypeInfo, MaxEncodedLen)] @@ -154,7 +155,7 @@ parameter_types! { impl crate::verifier::Config for Runtime { type RuntimeEvent = RuntimeEvent; type SolutionImprovementThreshold = SolutionImprovementThreshold; - type ForceOrigin = frame_system::EnsureRoot; + type MaxBackersPerWinnerFinal = MaxBackersPerWinnerFinal; type MaxBackersPerWinner = MaxBackersPerWinner; type MaxWinnersPerPage = MaxWinnersPerPage; type SolutionDataProvider = signed::DualSignedPhase; @@ -282,10 +283,14 @@ impl ExtBuilder { } impl ExtBuilder { - pub(crate) fn max_backing_per_target(self, c: u32) -> Self { + pub(crate) fn max_backers_per_winner(self, c: u32) -> Self { MaxBackersPerWinner::set(c); self } + pub(crate) fn max_backers_per_winner_final(self, c: u32) -> Self { + MaxBackersPerWinnerFinal::set(c); + self + } pub(crate) fn miner_tx_priority(self, p: u64) -> Self { MinerTxPriority::set(p); self @@ -396,6 +401,7 @@ impl ExecuteWithSanityChecks for sp_io::TestExternalities { } fn all_pallets_sanity_checks() { + // TODO: refactor all to use try-runtime instead let now = System::block_number(); let _ = VerifierPallet::sanity_check().unwrap(); let _ = UnsignedPallet::sanity_check().unwrap(); diff --git a/substrate/frame/election-provider-multi-block/src/signed/tests.rs b/substrate/frame/election-provider-multi-block/src/signed/tests.rs index 5a500f3c1b9fd..429076a925544 100644 --- a/substrate/frame/election-provider-multi-block/src/signed/tests.rs +++ b/substrate/frame/election-provider-multi-block/src/signed/tests.rs @@ -1,5 +1,5 @@ use super::{Event as SignedEvent, *}; -use crate::mock::*; +use crate::{mock::*, verifier::FeasibilityError}; use sp_core::bounded_vec; pub type T = Runtime; @@ -434,6 +434,27 @@ mod e2e { ] ); + assert_eq!( + verifier_events(), + vec![ + crate::verifier::Event::Verified(2, 0), + crate::verifier::Event::Verified(1, 0), + crate::verifier::Event::Verified(0, 0), + crate::verifier::Event::VerificationFailed(0, FeasibilityError::InvalidScore), + crate::verifier::Event::Verified(2, 2), + crate::verifier::Event::Verified(1, 2), + crate::verifier::Event::Verified(0, 2), + crate::verifier::Event::Queued( + ElectionScore { + minimal_stake: 55, + sum_stake: 130, + sum_stake_squared: 8650 + }, + None + ) + ] + ); + assert_eq!(balances(99), (100, 0)); assert_eq!(balances(999), (104, 0)); assert_eq!(balances(92), (95, 0)); diff --git a/substrate/frame/election-provider-multi-block/src/types.rs b/substrate/frame/election-provider-multi-block/src/types.rs index 2a60c1f249e5a..859774be247c8 100644 --- a/substrate/frame/election-provider-multi-block/src/types.rs +++ b/substrate/frame/election-provider-multi-block/src/types.rs @@ -31,7 +31,8 @@ use sp_runtime::{ SaturatedConversion, }; -/// The supports that's returned from a given [`Verifier`]. TODO: rename this +/// The supports that's returned from a given [`Verifier`]. +/// TODO: rename this pub type SupportsOf = BoundedSupports< ::AccountId, ::MaxWinnersPerPage, diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs b/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs index 0f3465a38939b..e696de2414a25 100644 --- a/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs +++ b/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs @@ -1618,7 +1618,7 @@ mod base_miner { #[test] fn trim_backings_works() { ExtBuilder::unsigned() - .max_backing_per_target(5) + .max_backers_per_winner(5) .voter_per_page(8) .build_and_execute(|| { // 10 and 40 are the default winners, we add a lot more votes to them. diff --git a/substrate/frame/election-provider-multi-block/src/verifier/impls.rs b/substrate/frame/election-provider-multi-block/src/verifier/impls.rs index c26860861b859..b60882f64c8e0 100644 --- a/substrate/frame/election-provider-multi-block/src/verifier/impls.rs +++ b/substrate/frame/election-provider-multi-block/src/verifier/impls.rs @@ -16,17 +16,17 @@ // limitations under the License. use super::*; -use crate::{helpers, SolutionOf, SupportsOf}; +use crate::{helpers, types::SupportsOf, verifier::Verifier, SolutionOf}; use codec::{Decode, Encode, MaxEncodedLen}; use frame_election_provider_support::{ExtendedBalance, NposSolution, PageIndex}; use frame_support::{ ensure, - pallet_prelude::*, + pallet_prelude::{ValueQuery, *}, traits::{Defensive, Get}, }; use frame_system::pallet_prelude::*; use pallet::*; -use sp_npos_elections::{ElectionScore, EvaluateSupport}; +use sp_npos_elections::{evaluate_support, ElectionScore, EvaluateSupport}; use sp_runtime::{Perbill, RuntimeDebug}; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; @@ -90,13 +90,7 @@ impl sp_npos_elections::Backings for PartialBackings { #[frame_support::pallet] pub(crate) mod pallet { - use crate::{types::SupportsOf, verifier::Verifier}; - use super::*; - use frame_support::pallet_prelude::ValueQuery; - use sp_npos_elections::evaluate_support; - use sp_runtime::Perbill; - #[pallet::config] #[pallet::disable_frame_system_supertrait_check] pub trait Config: crate::Config { @@ -105,21 +99,17 @@ pub(crate) mod pallet { + IsType<::RuntimeEvent> + TryInto>; - /// Origin that can control this pallet. Note that any action taken by this origin (such) - /// as providing an emergency solution is not checked. Thus, it must be a trusted origin. - type ForceOrigin: EnsureOrigin; - /// The minimum amount of improvement to the solution score that defines a solution as /// "better". #[pallet::constant] - type SolutionImprovementThreshold: Get; + type SolutionImprovementThreshold: Get; - /// Maximum number of voters that can support a single target, among ALL pages of a - /// verifying solution. It can only ever be checked on the last page of any given - /// verification. + /// Maximum number of backers, per winner, among all pages of an election. /// - /// This must be set such that the memory limits in the rest of the system are well - /// respected. + /// This can only be checked at the very final step of verification. + type MaxBackersPerWinnerFinal: Get; + + /// Maximum number of backers, per winner, per page. type MaxBackersPerWinner: Get; /// Maximum number of supports (aka. winners/validators/targets) that can be represented in @@ -239,11 +229,14 @@ pub(crate) mod pallet { /// Same as [`clear_invalid_and_backings`], but without any checks for the integrity of the /// storage item group. pub(crate) fn clear_invalid_and_backings_unchecked() { + // clear is safe as we delete at most `Pages` entries, and `Pages` is bounded. + // TODO: safe wrapper around this that clears exactly pages keys, and ensures none is + // left. match Self::invalid() { ValidSolution::X => QueuedSolutionX::::clear(u32::MAX, None), ValidSolution::Y => QueuedSolutionY::::clear(u32::MAX, None), }; - QueuedSolutionBackings::::clear(u32::MAX, None); + let _ = QueuedSolutionBackings::::clear(u32::MAX, None); } /// Write a single page of a valid solution into the `invalid` variant of the storage. @@ -337,12 +330,12 @@ pub(crate) mod pallet { for (who, PartialBackings { backers, total }) in QueuedSolutionBackings::::iter().map(|(_, pb)| pb).flatten() { - let mut entry = total_supports.entry(who).or_default(); + let entry = total_supports.entry(who).or_default(); entry.total = entry.total.saturating_add(total); entry.backers = entry.backers.saturating_add(backers); - if entry.backers > T::MaxBackersPerWinner::get() { - return Err(FeasibilityError::TooManyBackings) + if entry.backers > T::MaxBackersPerWinnerFinal::get() { + return Err(FeasibilityError::FailedToBoundSupport) } } @@ -425,7 +418,7 @@ pub(crate) mod pallet { let mut backing_map: BTreeMap = BTreeMap::new(); Self::valid_iter().map(|(_, supports)| supports).flatten().for_each( |(who, support)| { - let mut entry = backing_map.entry(who).or_default(); + let entry = backing_map.entry(who).or_default(); entry.total = entry.total.saturating_add(support.total); }, ); @@ -448,6 +441,8 @@ pub(crate) mod pallet { } } + // -- private storage items, managed by `QueuedSolution`. + /// The `X` variant of the current queued solution. Might be the valid one or not. /// /// The two variants of this storage item is to avoid the need of copying. Recall that once a @@ -467,7 +462,7 @@ pub(crate) mod pallet { /// The `(amount, count)` of backings, divided per page. /// /// This is stored because in the last block of verification we need them to compute the score, - /// and check `MaxBackersPerWinner`. + /// and check `MaxBackersPerWinnerFinal`. /// /// This can only ever live for the invalid variant of the solution. Once it is valid, we don't /// need this information anymore; the score is already computed once in @@ -484,6 +479,7 @@ pub(crate) mod pallet { /// This only ever lives for the `valid` variant. #[pallet::storage] type QueuedSolutionScore = StorageValue<_, ElectionScore>; + // -- ^^ private storage items, managed by `QueuedSolution`. /// The minimum score that each solution must attain in order to be considered feasible. #[pallet::storage] @@ -505,12 +501,13 @@ pub(crate) mod pallet { impl Hooks> for Pallet { fn integrity_test() { // ensure that we have funneled some of our type parameters EXACTLY as-is to the - // verifier pallet. + // verifier trait interface we implement. assert_eq!(T::MaxWinnersPerPage::get(), ::MaxWinnersPerPage::get()); assert_eq!( T::MaxBackersPerWinner::get(), ::MaxBackersPerWinner::get() ); + assert!(T::MaxBackersPerWinner::get() <= T::MaxBackersPerWinnerFinal::get()); } fn on_initialize(_n: BlockNumberFor) -> Weight { @@ -530,7 +527,7 @@ impl Pallet { sublog!( error, "verifier", - "T::SolutionDataProvider failed to deliver page {}. This is an expected error and should not happen.", + "T::SolutionDataProvider failed to deliver page {}. This is an unexpected error.", current_page, ); @@ -611,7 +608,7 @@ impl Pallet { let _ = Self::ensure_score_quality(claimed_score)?; // then actually check feasibility... - // NOTE: `MaxBackersPerWinner` is also already checked here. + // NOTE: `MaxBackersPerWinnerFinal` is also already checked here. let supports = Self::feasibility_check_page_inner(partial_solution, page)?; // then check that the number of winners was exactly enough.. @@ -649,7 +646,8 @@ impl Pallet { // NOTE: must be before the call to `finalize_correct`. Self::deposit_event(Event::::Queued( final_score, - QueuedSolution::::queued_score(), + QueuedSolution::::queued_score(), /* the previous score, now + * ejected. */ )); QueuedSolution::::finalize_correct(final_score); Ok(()) @@ -750,16 +748,6 @@ impl Pallet { let supports = sp_npos_elections::to_supports(&staked_assignments); - // Check the maximum number of backers per winner. If this is a single-page solution, this - // is enough to check `MaxBackersPerWinner`. Else, this is just a heuristic, and needs to be - // checked again at the end (via `QueuedSolutionBackings`). - ensure!( - supports - .iter() - .all(|(_, s)| (s.voters.len() as u32) <= T::MaxBackersPerWinner::get()), - FeasibilityError::TooManyBackings - ); - // Ensure some heuristics. These conditions must hold in the **entire** support, this is // just a single page. But, they must hold in a single page as well. let desired_targets = @@ -770,7 +758,7 @@ impl Pallet { // `MaxWinnersPerPage` should be more than any possible value of `desired_targets()`, which // is ALSO checked, so this conversion can almost never fail. let bounded_supports = - supports.try_into().map_err(|_| FeasibilityError::WrongWinnerCount)?; + supports.try_into().map_err(|_| FeasibilityError::FailedToBoundSupport)?; Ok(bounded_supports) } @@ -853,6 +841,7 @@ impl AsynchronousVerifier for Pallet { } fn start() -> Result<(), &'static str> { + sublog!(info, "verifier", "start signal received."); if let Status::Nothing = Self::status() { let claimed_score = Self::SolutionDataProvider::get_score().unwrap_or_default(); if Self::ensure_score_quality(claimed_score).is_err() { @@ -865,6 +854,7 @@ impl AsynchronousVerifier for Pallet { // Despite being an instant-reject, this was a successful `start` operation. Ok(()) } else { + // This solution is good enough to win, we start verifying it in the next block. StatusStorage::::put(Status::Ongoing(crate::Pallet::::msp())); Ok(()) } diff --git a/substrate/frame/election-provider-multi-block/src/verifier/mod.rs b/substrate/frame/election-provider-multi-block/src/verifier/mod.rs index 75a993c459dea..f2f332fe707e5 100644 --- a/substrate/frame/election-provider-multi-block/src/verifier/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/verifier/mod.rs @@ -27,15 +27,16 @@ //! //! After checking all of the edges, a handful of other checks are performed: //! -//! 1. Check that the total number of winners is sufficient. +//! 1. Check that the total number of winners is sufficient (`DesiredTargets`). //! 2. Check that the claimed score ([`sp_npos_elections::ElectionScore`]) is correct, //! 3. and more than the minimum score that can be specified via [`Verifier::set_minimum_score`]. //! 4. Check that all of the bounds of the solution are respected, namely -//! [`Verifier::MaxBackersPerWinner`]. +//! [`Verifier::MaxBackersPerWinner`], [`Verifier::MaxWinnersPerPage`] and +//! [`Config::MaxBackersPerWinnerFinal`]. //! //! Note that the common factor of all of these checks is that they can ONLY be checked after all -//! pages are already verified. So, In the case of a multi-page verification, these checks are only -//! performed after all pages have already been verified. +//! pages are already verified. So, In the case of a multi-page verification, these checks are +//! performed at the last page. //! //! The errors that can arise while performing the feasibility check are encapsulated in //! [`FeasibilityError`]. @@ -45,7 +46,8 @@ //! The verifier pallet provide two modes of functionality: //! //! 1. Single-page, synchronous verification. This is useful in the context of single-page, -//! emergency, or unsigned solutions that need to be verified on the fly. +//! emergency, or unsigned solutions that need to be verified on the fly. This is similar to how +//! the old school `multi-phase` pallet works. //! 2. Multi-page, asynchronous verification. This is useful in the context of multi-page, signed //! solutions. //! @@ -74,12 +76,12 @@ mod tests; // internal imports use crate::SupportsOf; use frame_election_provider_support::PageIndex; +pub use impls::{pallet::*, Status}; +use sp_core::Get; use sp_npos_elections::ElectionScore; use sp_runtime::RuntimeDebug; use sp_std::{fmt::Debug, prelude::*}; -pub use impls::{pallet::*, Status}; - /// Errors that can happen in the feasibility check. #[derive(Debug, Eq, PartialEq, codec::Encode, codec::Decode, scale_info::TypeInfo, Clone)] pub enum FeasibilityError { @@ -102,8 +104,11 @@ pub enum FeasibilityError { InvalidRound, /// Solution does not have a good enough score. ScoreTooLow, - /// A single target has too many backings - TooManyBackings, + /// The support type failed to be bounded. + /// + /// Relates to [`Config::MaxWinnersPerPage`], [`Config::MaxBackersPerWinner`] or + /// `MaxBackersPerWinnerFinal` + FailedToBoundSupport, /// Internal error from the election crate. #[codec(skip)] NposElection(sp_npos_elections::Error), @@ -131,13 +136,13 @@ pub trait Verifier { /// /// In multi-block verification, this can only be checked after all pages are known to be valid /// and are already checked. - type MaxBackersPerWinner: frame_support::traits::Get; + type MaxBackersPerWinner: Get; /// Maximum number of winners that can be represented in each page. /// /// A reasonable value for this should be the maximum number of winners that the election user /// (e.g. the staking pallet) could ever desire. - type MaxWinnersPerPage: frame_support::traits::Get; + type MaxWinnersPerPage: Get; /// Set the minimum score that is acceptable for any solution. /// diff --git a/substrate/frame/election-provider-multi-block/src/verifier/tests.rs b/substrate/frame/election-provider-multi-block/src/verifier/tests.rs index 364c199bad618..17014547111c5 100644 --- a/substrate/frame/election-provider-multi-block/src/verifier/tests.rs +++ b/substrate/frame/election-provider-multi-block/src/verifier/tests.rs @@ -188,7 +188,7 @@ mod feasibility_check { #[test] fn heuristic_max_backers_per_winner_per_page() { - ExtBuilder::verifier().max_backing_per_target(2).build_and_execute(|| { + ExtBuilder::verifier().max_backers_per_winner(2).build_and_execute(|| { roll_to_snapshot_created(); // these votes are all valid, but some dude has 3 supports in a single page. @@ -200,7 +200,7 @@ mod feasibility_check { assert_noop!( VerifierPallet::feasibility_check_page_inner(solution, 2), - FeasibilityError::TooManyBackings, + FeasibilityError::FailedToBoundSupport, ); }) } @@ -708,15 +708,65 @@ mod async_verification { } #[test] - fn invalid_solution_bad_bounds() { + fn invalid_solution_bad_bounds_per_page() { ExtBuilder::verifier() .desired_targets(1) - .max_backing_per_target(2) + .max_backers_per_winner(1) // in each page we allow 1 baker to be presented. + .max_backers_per_winner_final(12) + .build_and_execute(|| { + roll_to_snapshot_created(); + + // This is a sneaky custom solution where it will fail in the second page. + let page0 = solution_from_supports( + vec![(10, Support { total: 10, voters: vec![(1, 10)] })], + 2, + ); + let page1 = solution_from_supports( + vec![(10, Support { total: 20, voters: vec![(5, 10), (8, 10)] })], + 1, + ); + let page2 = solution_from_supports( + vec![(10, Support { total: 10, voters: vec![(10, 10)] })], + 0, + ); + let paged = PagedRawSolution { + solution_pages: bounded_vec![page0, page1, page2], + score: Default::default(), // score is never checked, so nada + ..Default::default() + }; + + load_mock_signed_and_start(paged); + roll_to_full_verification(); + + // we detect the bound issue in page 2. + assert_eq!( + verifier_events(), + vec![ + Event::Verified(2, 1), + Event::VerificationFailed(1, FeasibilityError::FailedToBoundSupport) + ] + ); + + // our state is fully cleaned. + QueuedSolution::::assert_killed(); + assert_eq!(StatusStorage::::get(), Status::Nothing); + // nothing is verified.. + assert!(::queued_score().is_none()); + // result is reported back. + assert_eq!(MockSignedResults::get(), vec![VerificationResult::Rejected]); + }) + } + + #[test] + fn invalid_solution_bad_bounds_final() { + ExtBuilder::verifier() + .desired_targets(1) + .max_backers_per_winner_final(2) .build_and_execute(|| { roll_to_snapshot_created(); // This is a sneaky custom solution where in each page 10 has 1 backers, so only in - // the last page we can catch the son of the fidge. + // the last page we can catch the mfer. let page0 = solution_from_supports( vec![(10, Support { total: 10, voters: vec![(1, 10)] })], 2, @@ -749,10 +799,14 @@ mod async_verification { Event::Verified(2, 1), Event::Verified(1, 1), Event::Verified(0, 1), - Event::VerificationFailed(0, FeasibilityError::TooManyBackings) + Event::VerificationFailed(0, FeasibilityError::FailedToBoundSupport) ] ); + // our state is fully cleaned. + QueuedSolution::::assert_killed(); + assert_eq!(StatusStorage::::get(), Status::Nothing); + // nothing is verified.. assert!(::queued_score().is_none()); // result is reported back. @@ -990,12 +1044,15 @@ mod sync_verification { MultiBlock::msp(), ) .unwrap_err(), - FeasibilityError::TooManyBackings + FeasibilityError::FailedToBoundSupport ); assert_eq!( verifier_events(), - vec![Event::::VerificationFailed(2, FeasibilityError::TooManyBackings)] + vec![Event::::VerificationFailed( + 2, + FeasibilityError::FailedToBoundSupport + )] ); }); From c0c0632c2efca435e973a1f6788e24235fe0e2a6 Mon Sep 17 00:00:00 2001 From: Clara van Staden Date: Tue, 21 Jan 2025 16:11:50 +0200 Subject: [PATCH 114/169] Snowbridge - Copy Rococo integration tests to Westend (#7108) Copies all the integration tests from Rococo to Westend. Closes: https://github.com/paritytech/polkadot-sdk/issues/6389 --- .../bridges/bridge-hub-westend/src/lib.rs | 3 + .../bridges/bridge-hub-westend/src/lib.rs | 4 +- .../src/tests/snowbridge.rs | 647 +++++++++++++++++- 3 files changed, 646 insertions(+), 8 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs index b548e3b7e64c3..1b6f796518871 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs @@ -18,6 +18,7 @@ pub mod genesis; pub use bridge_hub_westend_runtime::{ self, xcm_config::XcmConfig as BridgeHubWestendXcmConfig, ExistentialDeposit as BridgeHubWestendExistentialDeposit, + RuntimeOrigin as BridgeHubWestendRuntimeOrigin, }; // Substrate @@ -47,6 +48,8 @@ decl_test_parachains! { PolkadotXcm: bridge_hub_westend_runtime::PolkadotXcm, Balances: bridge_hub_westend_runtime::Balances, EthereumSystem: bridge_hub_westend_runtime::EthereumSystem, + EthereumInboundQueue: bridge_hub_westend_runtime::EthereumInboundQueue, + EthereumOutboundQueue: bridge_hub_westend_runtime::EthereumOutboundQueue, } }, } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs index 501ddb84d4259..3d4d4f58e3b54 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs @@ -51,9 +51,11 @@ mod imports { }, bridge_hub_westend_emulated_chain::{ genesis::ED as BRIDGE_HUB_WESTEND_ED, BridgeHubWestendExistentialDeposit, - BridgeHubWestendParaPallet as BridgeHubWestendPallet, BridgeHubWestendXcmConfig, + BridgeHubWestendParaPallet as BridgeHubWestendPallet, BridgeHubWestendRuntimeOrigin, + BridgeHubWestendXcmConfig, }, penpal_emulated_chain::{ + self, penpal_runtime::xcm_config::{ CustomizableAssetFromSystemAssetHub as PenpalCustomizableAssetFromSystemAssetHub, LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub, diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs index ffa60a4f52e74..15ca3a5cf1b85 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs @@ -12,15 +12,18 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use crate::imports::*; +use crate::{imports::*, tests::penpal_emulated_chain::penpal_runtime}; use asset_hub_westend_runtime::xcm_config::bridging::to_ethereum::DefaultBridgeHubEthereumBaseFee; -use bridge_hub_westend_runtime::EthereumInboundQueue; +use bridge_hub_westend_runtime::{ + bridge_to_ethereum_config::EthereumGatewayAddress, EthereumBeaconClient, EthereumInboundQueue, +}; use codec::{Decode, Encode}; -use emulated_integration_tests_common::RESERVABLE_ASSET_ID; +use emulated_integration_tests_common::{PENPAL_B_ID, RESERVABLE_ASSET_ID}; use frame_support::pallet_prelude::TypeInfo; use hex_literal::hex; use rococo_westend_system_emulated_network::asset_hub_westend_emulated_chain::genesis::AssetHubWestendAssetOwner; -use snowbridge_core::{outbound::OperatingMode, AssetMetadata, TokenIdOf}; +use snowbridge_core::{inbound::InboundQueueFixture, AssetMetadata, TokenIdOf}; +use snowbridge_pallet_inbound_queue_fixtures::send_native_eth::make_send_native_eth_message; use snowbridge_router_primitives::inbound::{ Command, Destination, EthereumLocationsConverterFor, MessageV1, VersionedMessage, }; @@ -28,19 +31,20 @@ use sp_core::H256; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; use xcm_executor::traits::ConvertLocation; -const INITIAL_FUND: u128 = 5_000_000_000_000; pub const CHAIN_ID: u64 = 11155111; pub const WETH: [u8; 20] = hex!("87d1f7fdfEe7f651FaBc8bFCB6E086C278b77A7d"); +const INITIAL_FUND: u128 = 5_000_000_000_000; const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EBD0D8D2333700e"); const XCM_FEE: u128 = 100_000_000_000; +const INSUFFICIENT_XCM_FEE: u128 = 1000; const TOKEN_AMOUNT: u128 = 100_000_000_000; +const TREASURY_ACCOUNT: [u8; 32] = + hex!("6d6f646c70792f74727372790000000000000000000000000000000000000000"); #[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] pub enum ControlCall { #[codec(index = 3)] CreateAgent, - #[codec(index = 4)] - CreateChannel { mode: OperatingMode }, } #[allow(clippy::large_enum_variant)] @@ -50,6 +54,75 @@ pub enum SnowbridgeControl { Control(ControlCall), } +pub fn send_inbound_message(fixture: InboundQueueFixture) -> DispatchResult { + EthereumBeaconClient::store_finalized_header( + fixture.finalized_header, + fixture.block_roots_root, + ) + .unwrap(); + EthereumInboundQueue::submit( + BridgeHubWestendRuntimeOrigin::signed(BridgeHubWestendSender::get()), + fixture.message, + ) +} + +/// Create an agent on Ethereum. An agent is a representation of an entity in the Polkadot +/// ecosystem (like a parachain) on Ethereum. +#[test] +#[ignore] +fn create_agent() { + let origin_para: u32 = 1001; + // Fund the origin parachain sovereign account so that it can pay execution fees. + BridgeHubWestend::fund_para_sovereign(origin_para.into(), INITIAL_FUND); + + let sudo_origin = ::RuntimeOrigin::root(); + let destination = Westend::child_location_of(BridgeHubWestend::para_id()).into(); + + let create_agent_call = SnowbridgeControl::Control(ControlCall::CreateAgent {}); + // Construct XCM to create an agent for para 1001 + let remote_xcm = VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + DescendOrigin(Parachain(origin_para).into()), + Transact { + origin_kind: OriginKind::Xcm, + call: create_agent_call.encode().into(), + fallback_max_weight: None, + }, + ])); + + // Westend Global Consensus + // Send XCM message from Relay Chain to Bridge Hub source Parachain + Westend::execute_with(|| { + assert_ok!(::XcmPallet::send( + sudo_origin, + bx!(destination), + bx!(remote_xcm), + )); + + type RuntimeEvent = ::RuntimeEvent; + // Check that the Transact message was sent + assert_expected_events!( + Westend, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that a message was sent to Ethereum to create the agent + assert_expected_events!( + BridgeHubWestend, + vec![ + RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::CreateAgent { + .. + }) => {}, + ] + ); + }); +} + /// Tests the registering of a token as an asset on AssetHub. #[test] fn register_weth_token_from_ethereum_to_asset_hub() { @@ -82,6 +155,566 @@ fn register_weth_token_from_ethereum_to_asset_hub() { }); } +/// Tests the registering of a token as an asset on AssetHub, and then subsequently sending +/// a token from Ethereum to AssetHub. +#[test] +fn send_weth_token_from_ethereum_to_asset_hub() { + let ethereum_network: NetworkId = EthereumNetwork::get().into(); + let origin_location = Location::new(2, ethereum_network); + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::::convert_location(&origin_location).unwrap(); + + BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id().into(), INITIAL_FUND); + + // Fund ethereum sovereign on AssetHub + AssetHubWestend::fund_accounts(vec![ + (AssetHubWestendReceiver::get(), INITIAL_FUND), + (ethereum_sovereign, INITIAL_FUND), + ]); + + // Register the token + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::RegisterToken { token: WETH.into(), fee: XCM_FEE }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert([0; 32].into(), message).unwrap(); + let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Created { .. }) => {},] + ); + }); + + // Send the token + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + type EthereumInboundQueue = + ::EthereumInboundQueue; + let message_id: H256 = [0; 32].into(); + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendToken { + token: WETH.into(), + destination: Destination::AccountId32 { id: AssetHubWestendSender::get().into() }, + amount: 1_000_000, + fee: XCM_FEE, + }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert(message_id, message).unwrap(); + assert_ok!(EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into())); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the token was received and issued as a foreign asset on AssetHub + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + }); +} + +/// Tests sending a token to a 3rd party parachain, called PenPal. The token reserve is +/// still located on AssetHub. +#[test] +fn send_weth_from_ethereum_to_penpal() { + let asset_hub_sovereign = BridgeHubWestend::sovereign_account_id_of(Location::new( + 1, + [Parachain(AssetHubWestend::para_id().into())], + )); + // Fund AssetHub sovereign account so it can pay execution fees for the asset transfer + BridgeHubWestend::fund_accounts(vec![(asset_hub_sovereign.clone(), INITIAL_FUND)]); + + // Fund PenPal receiver (covering ED) + let native_id: Location = Parent.into(); + let receiver: AccountId = [ + 28, 189, 45, 67, 83, 10, 68, 112, 90, 208, 136, 175, 49, 62, 24, 248, 11, 83, 239, 22, 179, + 97, 119, 205, 75, 119, 184, 70, 242, 165, 240, 124, + ] + .into(); + PenpalB::mint_foreign_asset( + ::RuntimeOrigin::signed(PenpalAssetOwner::get()), + native_id, + receiver, + penpal_runtime::EXISTENTIAL_DEPOSIT, + ); + + PenpalB::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(), + Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]).encode(), + )], + )); + }); + + let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); + + // The Weth asset location, identified by the contract address on Ethereum + let weth_asset_location: Location = + (Parent, Parent, ethereum_network_v5, AccountKey20 { network: None, key: WETH }).into(); + + let origin_location = (Parent, Parent, ethereum_network_v5).into(); + + // Fund ethereum sovereign on AssetHub + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::::convert_location(&origin_location).unwrap(); + AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); + + // Create asset on the Penpal parachain. + PenpalB::execute_with(|| { + assert_ok!(::ForeignAssets::force_create( + ::RuntimeOrigin::root(), + weth_asset_location.clone(), + asset_hub_sovereign.into(), + false, + 1000, + )); + + assert!(::ForeignAssets::asset_exists(weth_asset_location)); + }); + + // Register the token + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::RegisterToken { token: WETH.into(), fee: XCM_FEE }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert([0; 32].into(), message).unwrap(); + let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Created { .. }) => {},] + ); + }); + + // Send the token + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + type EthereumInboundQueue = + ::EthereumInboundQueue; + let message_id: H256 = [0; 32].into(); + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendToken { + token: WETH.into(), + destination: Destination::ForeignAccountId32 { + para_id: PENPAL_B_ID, + id: PenpalBReceiver::get().into(), + fee: XCM_FEE, + }, + amount: 1_000_000, + fee: XCM_FEE, + }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert(message_id, message).unwrap(); + assert_ok!(EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into())); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that the assets were issued on AssetHub + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + PenpalB::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that the assets were issued on PenPal + assert_expected_events!( + PenpalB, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + }); +} + +/// Tests the full cycle of eth transfers: +/// - sending a token to AssetHub +/// - returning the token to Ethereum +#[test] +fn send_eth_asset_from_asset_hub_to_ethereum_and_back() { + let ethereum_network: NetworkId = EthereumNetwork::get().into(); + let origin_location = (Parent, Parent, ethereum_network).into(); + + use asset_hub_westend_runtime::xcm_config::bridging::to_ethereum::DefaultBridgeHubEthereumBaseFee; + let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); + let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::::convert_location(&origin_location).unwrap(); + + AssetHubWestend::force_default_xcm_version(Some(XCM_VERSION)); + BridgeHubWestend::force_default_xcm_version(Some(XCM_VERSION)); + AssetHubWestend::force_xcm_version(origin_location.clone(), XCM_VERSION); + + BridgeHubWestend::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]); + AssetHubWestend::fund_accounts(vec![ + (AssetHubWestendReceiver::get(), INITIAL_FUND), + (ethereum_sovereign.clone(), INITIAL_FUND), + ]); + + // Register ETH + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + origin_location.clone(), + ethereum_sovereign.into(), + true, + 1000, + )); + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::ForceCreated { .. }) => {}, + ] + ); + }); + const ETH_AMOUNT: u128 = 1_000_000_000_000_000_000; + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + + // Set the gateway. This is needed because new fixtures use a different gateway address. + assert_ok!(::System::set_storage( + RuntimeOrigin::root(), + vec![( + EthereumGatewayAddress::key().to_vec(), + sp_core::H160(hex!("87d1f7fdfEe7f651FaBc8bFCB6E086C278b77A7d")).encode(), + )], + )); + + // Construct SendToken message and sent to inbound queue + assert_ok!(send_inbound_message(make_send_native_eth_message())); + + // Check that the send token message was sent using xcm + assert_expected_events!( + BridgeHubWestend, + vec![ + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + + let _issued_event = RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { + asset_id: origin_location.clone(), + owner: AssetHubWestendReceiver::get().into(), + amount: ETH_AMOUNT, + }); + // Check that AssetHub has issued the foreign asset + assert_expected_events!( + AssetHubWestend, + vec![ + _issued_event => {}, + ] + ); + let assets = + vec![Asset { id: AssetId(origin_location.clone()), fun: Fungible(ETH_AMOUNT) }]; + let multi_assets = VersionedAssets::from(Assets::from(assets)); + + let destination = origin_location.clone().into(); + + let beneficiary = VersionedLocation::from(Location::new( + 0, + [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }], + )); + + let free_balance_before = + ::Balances::free_balance( + AssetHubWestendReceiver::get(), + ); + // Send the Weth back to Ethereum + ::PolkadotXcm::limited_reserve_transfer_assets( + RuntimeOrigin::signed(AssetHubWestendReceiver::get()), + Box::new(destination), + Box::new(beneficiary), + Box::new(multi_assets), + 0, + Unlimited, + ) + .unwrap(); + + let _burned_event = RuntimeEvent::ForeignAssets(pallet_assets::Event::Burned { + asset_id: origin_location.clone(), + owner: AssetHubWestendReceiver::get().into(), + balance: ETH_AMOUNT, + }); + // Check that AssetHub has issued the foreign asset + let _destination = origin_location.clone(); + assert_expected_events!( + AssetHubWestend, + vec![ + _burned_event => {}, + RuntimeEvent::PolkadotXcm(pallet_xcm::Event::Sent { + destination: _destination, .. + }) => {}, + ] + ); + + let free_balance_after = ::Balances::free_balance( + AssetHubWestendReceiver::get(), + ); + // Assert at least DefaultBridgeHubEthereumBaseFee charged from the sender + let free_balance_diff = free_balance_before - free_balance_after; + assert!(free_balance_diff > DefaultBridgeHubEthereumBaseFee::get()); + }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that the transfer token back to Ethereum message was queue in the Ethereum + // Outbound Queue + assert_expected_events!( + BridgeHubWestend, + vec![ + RuntimeEvent::EthereumOutboundQueue(snowbridge_pallet_outbound_queue::Event::MessageAccepted {..}) => {}, + RuntimeEvent::EthereumOutboundQueue(snowbridge_pallet_outbound_queue::Event::MessageQueued {..}) => {}, + ] + ); + + let events = BridgeHubWestend::events(); + // Check that the local fee was credited to the Snowbridge sovereign account + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount: _ }) + if *who == TREASURY_ACCOUNT.into() + )), + "Snowbridge sovereign takes local fee." + ); + // Check that the remote fee was credited to the AssetHub sovereign account + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount: _ }) + if *who == assethub_sovereign + )), + "AssetHub sovereign takes remote fee." + ); + }); +} + +#[test] +fn register_weth_token_in_asset_hub_fail_for_insufficient_fee() { + BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id().into(), INITIAL_FUND); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type EthereumInboundQueue = + ::EthereumInboundQueue; + let message_id: H256 = [0; 32].into(); + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::RegisterToken { + token: WETH.into(), + // Insufficient fee which should trigger the trap + fee: INSUFFICIENT_XCM_FEE, + }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert(message_id, message).unwrap(); + let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); + + assert_expected_events!( + BridgeHubWestend, + vec![ + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success:false, .. }) => {}, + ] + ); + }); +} + +fn send_weth_from_ethereum_to_asset_hub_with_fee(account_id: [u8; 32], fee: u128) { + let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); + let weth_asset_location: Location = + Location::new(2, [ethereum_network_v5.into(), AccountKey20 { network: None, key: WETH }]); + // Fund asset hub sovereign on bridge hub + let asset_hub_sovereign = BridgeHubWestend::sovereign_account_id_of(Location::new( + 1, + [Parachain(AssetHubWestend::para_id().into())], + )); + BridgeHubWestend::fund_accounts(vec![(asset_hub_sovereign.clone(), INITIAL_FUND)]); + + // Register WETH + AssetHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + weth_asset_location.clone().try_into().unwrap(), + asset_hub_sovereign.into(), + false, + 1, + )); + + assert!(::ForeignAssets::asset_exists( + weth_asset_location.clone().try_into().unwrap(), + )); + }); + + // Send WETH to an existent account on asset hub + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + type EthereumInboundQueue = + ::EthereumInboundQueue; + let message_id: H256 = [0; 32].into(); + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendToken { + token: WETH.into(), + destination: Destination::AccountId32 { id: account_id }, + amount: 1_000_000, + fee, + }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert(message_id, message).unwrap(); + assert_ok!(EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into())); + + // Check that the message was sent + assert_expected_events!( + BridgeHubWestend, + vec![ + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); +} + +#[test] +fn send_weth_from_ethereum_to_existent_account_on_asset_hub() { + send_weth_from_ethereum_to_asset_hub_with_fee(AssetHubWestendSender::get().into(), XCM_FEE); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the token was received and issued as a foreign asset on AssetHub + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + }); +} + +#[test] +fn send_weth_from_ethereum_to_non_existent_account_on_asset_hub() { + send_weth_from_ethereum_to_asset_hub_with_fee([1; 32], XCM_FEE); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the token was received and issued as a foreign asset on AssetHub + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + }); +} + +#[test] +fn send_weth_from_ethereum_to_non_existent_account_on_asset_hub_with_insufficient_fee() { + send_weth_from_ethereum_to_asset_hub_with_fee([1; 32], INSUFFICIENT_XCM_FEE); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the message was not processed successfully due to insufficient fee + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success:false, .. }) => {}, + ] + ); + }); +} + +#[test] +fn send_weth_from_ethereum_to_non_existent_account_on_asset_hub_with_sufficient_fee_but_do_not_satisfy_ed( +) { + // On AH the xcm fee is 26_789_690 and the ED is 3_300_000 + send_weth_from_ethereum_to_asset_hub_with_fee([1; 32], 30_000_000); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the message was not processed successfully due to insufficient ED + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success:false, .. }) => {}, + ] + ); + }); +} + /// Tests the registering of a token as an asset on AssetHub, and then subsequently sending /// a token from Ethereum to AssetHub. #[test] From e09da9b4adc55baae55b9e9c325d8b99923aaed0 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 21 Jan 2025 16:24:27 +0000 Subject: [PATCH 115/169] integrate into the runtime --- Cargo.lock | 1 + substrate/bin/node/runtime/src/constants.rs | 2 +- substrate/bin/node/runtime/src/lib.rs | 94 +++++++++++++- .../election-provider-multi-block/src/lib.rs | 2 +- .../src/signed/mod.rs | 20 ++- .../election-provider-multi-phase/src/lib.rs | 4 +- .../election-provider-support/src/lib.rs | 2 - substrate/frame/staking/src/pallet/impls.rs | 20 +-- umbrella/Cargo.toml | 10 +- umbrella/src/lib.rs | 115 +++++++----------- 10 files changed, 171 insertions(+), 99 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4395368515d73..57a66151ac06e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18775,6 +18775,7 @@ dependencies = [ "pallet-delegated-staking 1.0.0", "pallet-democracy 28.0.0", "pallet-dev-mode 10.0.0", + "pallet-election-provider-multi-block", "pallet-election-provider-multi-phase 27.0.0", "pallet-election-provider-support-benchmarking 27.0.0", "pallet-elections-phragmen 29.0.0", diff --git a/substrate/bin/node/runtime/src/constants.rs b/substrate/bin/node/runtime/src/constants.rs index 42629d53500ce..3a892e2f2b358 100644 --- a/substrate/bin/node/runtime/src/constants.rs +++ b/substrate/bin/node/runtime/src/constants.rs @@ -66,7 +66,7 @@ pub mod time { #[cfg(not(feature = "staking-playground"))] pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 10 * MINUTES; #[cfg(feature = "staking-playground")] - pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 1 * MINUTES; + pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 2 * MINUTES; pub const EPOCH_DURATION_IN_SLOTS: u64 = { const SLOT_FILL_RATE: f64 = MILLISECS_PER_BLOCK as f64 / SLOT_DURATION as f64; diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index b0748935dc1e4..25837720caa25 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -855,7 +855,8 @@ impl pallet_staking::Config for Runtime { type NextNewSession = Session; type MaxExposurePageSize = MaxExposurePageSize; type MaxValidatorSet = MaxActiveValidators; - type ElectionProvider = ElectionProviderMultiPhase; + type ElectionProvider = MultiBlock; + // type ElectionProvider = ElectionProviderMultiPhase; type GenesisElectionProvider = onchain::OnChainExecution; type VoterList = VoterList; type NominationsQuota = pallet_staking::FixedNominationsQuota; @@ -881,6 +882,83 @@ impl pallet_fast_unstake::Config for Runtime { type WeightInfo = (); } +pub(crate) mod multi_block_impls { + use super::*; + use pallet_election_provider_multi_block as multi_block; + use pallet_election_provider_multi_phase as multi_phase; + + parameter_types! { + pub Pages: u32 = 8; + pub VoterSnapshotPerBlock: u32 = 22500 / 8; + pub TargetSnapshotPerBlock: u32 = 1000; + } + impl multi_block::Config for Runtime { + type AdminOrigin = EnsureRoot; + type DataProvider = Staking; + type Fallback = ::Fallback; + // prepare for election 5 blocks ahead of time + type Lookahead = ConstU32<5>; + // split election into 8 pages. + type Pages = Pages; + // allow 2 signed solutions to be verified. + type SignedValidationPhase = ConstU32<16>; + type RuntimeEvent = RuntimeEvent; + // TODO: sanity check that the length of all phases is within reason. + type SignedPhase = ::SignedPhase; + type UnsignedPhase = ::UnsignedPhase; + type WeightInfo = (); + type Solution = NposSolution16; + type TargetSnapshotPerBlock = TargetSnapshotPerBlock; + type VoterSnapshotPerBlock = VoterSnapshotPerBlock; + type Verifier = MultiBlockVerifier; + } + + impl multi_block::verifier::Config for Runtime { + type MaxBackersPerWinner = ::MaxBackersPerWinner; + type MaxWinnersPerPage = ::MaxWinners; + type MaxBackersPerWinnerFinal = ConstU32<{ u32::MAX }>; + type RuntimeEvent = RuntimeEvent; + type SolutionDataProvider = MultiBlockSigned; + type SolutionImprovementThreshold = ::BetterSignedThreshold; + type WeightInfo = (); + } + + parameter_types! { + pub const BailoutGraceRatio: Perbill = Perbill::from_percent(50); + } + + impl multi_block::signed::Config for Runtime { + type BailoutGraceRatio = BailoutGraceRatio; + // TODO: we need an increase factor for this pallet as well. + type DepositBase = SignedFixedDeposit; + type DepositPerPage = SignedDepositByte; + type MaxSubmissions = ConstU32<8>; + type RewardBase = SignedRewardBase; + + type EstimateCallFee = TransactionPayment; + type Currency = Balances; + + type RuntimeEvent = RuntimeEvent; + type RuntimeHoldReason = RuntimeHoldReason; + type WeightInfo = (); + } + + impl multi_block::unsigned::Config for Runtime { + // TODO: split into MinerConfig so the staker miner can easily configure these. + // miner configs. + type MinerMaxLength = MinerMaxLength; + type MinerMaxWeight = MinerMaxWeight; + type OffchainSolver = ::Solver; + + // offchain usage of miner configs + type MinerTxPriority = ::MinerTxPriority; + // TODO: this needs to be an educated number: "estimate mining time per page * pages" + type OffchainRepeat = ConstU32<5>; + + type WeightInfo = (); + } +} + parameter_types! { // phase durations. 1/2 of the last session for each. pub const SignedPhase: u32 = EPOCH_DURATION_IN_BLOCKS / 2; @@ -2844,6 +2922,16 @@ mod runtime { #[runtime::pallet_index(84)] pub type AssetsFreezer = pallet_assets_freezer::Pallet; + + // Order is important! + #[runtime::pallet_index(85)] + pub type MultiBlock = pallet_election_provider_multi_block::Pallet; + #[runtime::pallet_index(86)] + pub type MultiBlockVerifier = pallet_election_provider_multi_block::verifier::Pallet; + #[runtime::pallet_index(87)] + pub type MultiBlockUnsigned = pallet_election_provider_multi_block::unsigned::Pallet; + #[runtime::pallet_index(88)] + pub type MultiBlockSigned = pallet_election_provider_multi_block::signed::Pallet; } impl TryFrom for pallet_revive::Call { @@ -3058,6 +3146,10 @@ mod benches { [pallet_asset_conversion_tx_payment, AssetConversionTxPayment] [pallet_transaction_payment, TransactionPayment] [pallet_election_provider_multi_phase, ElectionProviderMultiPhase] + [palllet_election_provider_multi_block, MultiBlock] + [palllet_election_provider_multi_block::verifier, MultiBlockVerifier] + [palllet_election_provider_multi_block::unsigned, MultiBlockUnsigned] + [palllet_election_provider_multi_block::signed, MultiBlockSigned] [pallet_election_provider_support_benchmarking, EPSBench::] [pallet_elections_phragmen, Elections] [pallet_fast_unstake, FastUnstake] diff --git a/substrate/frame/election-provider-multi-block/src/lib.rs b/substrate/frame/election-provider-multi-block/src/lib.rs index c907f8482e079..a5dbf57cbf6aa 100644 --- a/substrate/frame/election-provider-multi-block/src/lib.rs +++ b/substrate/frame/election-provider-multi-block/src/lib.rs @@ -156,6 +156,7 @@ use scale_info::TypeInfo; use sp_arithmetic::traits::Zero; use sp_npos_elections::VoteWeight; use sp_runtime::SaturatedConversion; +use sp_std::boxed::Box; use verifier::Verifier; #[cfg(test)] @@ -304,7 +305,6 @@ pub mod pallet { type SignedValidationPhase: Get>; /// The number of snapshot voters to fetch per block. - #[pallet::constant] type VoterSnapshotPerBlock: Get; /// The number of snapshot targets to fetch per block. diff --git a/substrate/frame/election-provider-multi-block/src/signed/mod.rs b/substrate/frame/election-provider-multi-block/src/signed/mod.rs index e24cdcc5c36d0..954a0ac32e4d5 100644 --- a/substrate/frame/election-provider-multi-block/src/signed/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/signed/mod.rs @@ -59,11 +59,8 @@ use frame_system::{ensure_signed, pallet_prelude::*}; use scale_info::TypeInfo; use sp_io::MultiRemovalResults; use sp_npos_elections::ElectionScore; -use sp_runtime::{ - traits::{Saturating, Zero}, - DispatchError, Perbill, TryRuntimeError, -}; -use sp_std::{collections::btree_set::BTreeSet, prelude::*}; +use sp_runtime::{traits::Saturating, Perbill}; +use sp_std::prelude::*; /// Exports of this pallet pub use pallet::*; @@ -322,14 +319,14 @@ pub mod pallet { /// /// All mutating functions must be fulled through this bad boy. The round at which the /// mutation happens must be provided - fn mutate_checked R>(round: u32, mutate: F) -> R { + fn mutate_checked R>(_round: u32, mutate: F) -> R { let result = mutate(); #[cfg(debug_assertions)] { - assert!(Self::sanity_check_round(round).is_ok()); - assert!(Self::sanity_check_round(round + 1).is_ok()); - assert!(Self::sanity_check_round(round.saturating_sub(1)).is_ok()); + assert!(Self::sanity_check_round(_round).is_ok()); + assert!(Self::sanity_check_round(_round + 1).is_ok()); + assert!(Self::sanity_check_round(_round.saturating_sub(1)).is_ok()); } result @@ -578,6 +575,7 @@ pub mod pallet { /// Perform all the sanity checks of this storage item group at the given round. pub(crate) fn sanity_check_round(round: u32) -> DispatchResult { + use sp_std::collections::btree_set::BTreeSet; let sorted_scores = SortedScores::::get(round); assert_eq!( sorted_scores.clone().into_iter().map(|(x, _)| x).collect::>().len(), @@ -788,7 +786,7 @@ pub mod pallet { } #[cfg(feature = "try-runtime")] - fn try_state(n: BlockNumberFor) -> Result<(), TryRuntimeError> { + fn try_state(n: BlockNumberFor) -> Result<(), sp_runtime::TryRuntimeError> { Self::do_try_state(n) } } @@ -796,7 +794,7 @@ pub mod pallet { impl Pallet { #[cfg(any(feature = "try-runtime", test))] - pub(crate) fn do_try_state(_n: BlockNumberFor) -> Result<(), TryRuntimeError> { + pub(crate) fn do_try_state(_n: BlockNumberFor) -> Result<(), sp_runtime::TryRuntimeError> { Submissions::::sanity_check_round(Self::current_round()) } diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index cfad31510ba93..a4f092ab4442c 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -599,7 +599,7 @@ pub mod pallet { use sp_runtime::traits::Convert; #[pallet::config] - pub trait Config: frame_system::Config + nherent> { + pub trait Config: frame_system::Config + CreateInherent> { type RuntimeEvent: From> + IsType<::RuntimeEvent> + TryInto>; @@ -768,6 +768,8 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { fn on_initialize(now: BlockNumberFor) -> Weight { + // TODO: a hack for now to prevent this pallet from doing anything. + return Default::default(); let next_election = T::DataProvider::next_election_prediction(now).max(now); let signed_deadline = T::SignedPhase::get() + T::UnsignedPhase::get(); diff --git a/substrate/frame/election-provider-support/src/lib.rs b/substrate/frame/election-provider-support/src/lib.rs index 322e1dc34d7b4..351ec4df1a0c3 100644 --- a/substrate/frame/election-provider-support/src/lib.rs +++ b/substrate/frame/election-provider-support/src/lib.rs @@ -335,7 +335,6 @@ pub trait ElectionDataProvider { /// /// Note that if a notion of self-vote exists, it should be represented here. /// - /// TODO(gpestana): remove self-weighing and return the weight. /// This should be implemented as a self-weighing function. The implementor should register its /// appropriate weight at the end of execution with the system pallet directly. fn electing_voters( @@ -345,7 +344,6 @@ pub trait ElectionDataProvider { /// The number of targets to elect. /// - /// TODO(gpestana): remove self-weighting ?? /// This should be implemented as a self-weighing function. The implementor should register its /// appropriate weight at the end of execution with the system pallet directly. /// diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index d23770332c30b..b0d0e6cac7968 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -18,9 +18,8 @@ //! Implementations for the Staking FRAME Pallet. use frame_election_provider_support::{ - bounds::{CountBound, SizeBound}, - data_provider, BoundedSupportsOf, DataProviderBounds, ElectionDataProvider, ElectionProvider, - PageIndex, ScoreProvider, SortedListProvider, VoteWeight, VoterOf, + bounds::CountBound, data_provider, BoundedSupportsOf, DataProviderBounds, ElectionDataProvider, + ElectionProvider, PageIndex, ScoreProvider, SortedListProvider, VoteWeight, VoterOf, }; use frame_support::{ defensive, @@ -1030,7 +1029,10 @@ impl Pallet { /// `TargetList` not to change while the pages are being processed. /// /// This function is self-weighing as [`DispatchClass::Mandatory`]. - pub fn get_npos_voters(bounds: DataProviderBounds, page: PageIndex) -> Vec> { + pub(crate) fn get_npos_voters( + bounds: DataProviderBounds, + page: PageIndex, + ) -> Vec> { let mut voters_size_tracker: StaticTracker = StaticTracker::default(); let page_len_prediction = { @@ -1166,7 +1168,10 @@ impl Pallet { log!( info, - "generated {} npos voters, {} from validators and {} nominators", + "[page {}, status {:?}, bounds {:?}] generated {} npos voters, {} from validators and {} nominators", + page, + VoterSnapshotStatus::::get(), + bounds, all_voters.len(), validators_taken, nominators_taken @@ -1177,7 +1182,7 @@ impl Pallet { /// Get all the targets associated are eligible for the npos election. /// - /// The target snaphot is *always* single paged. + /// The target snapshot is *always* single paged. /// /// This function is self-weighing as [`DispatchClass::Mandatory`]. pub fn get_npos_targets(bounds: DataProviderBounds) -> Vec { @@ -1205,6 +1210,7 @@ impl Pallet { if targets_size_tracker.try_register_target(target.clone(), &bounds).is_err() { // no more space left for the election snapshot, stop iterating. + log!(warn, "npos targets size exceeded, stopping iteration."); Self::deposit_event(Event::::SnapshotTargetsSizeExceeded { size: targets_size_tracker.size as u32, }); @@ -1217,7 +1223,7 @@ impl Pallet { } Self::register_weight(T::WeightInfo::get_npos_targets(all_targets.len() as u32)); - log!(info, "generated {} npos targets", all_targets.len()); + log!(info, "[bounds {:?}] generated {} npos targets", bounds, all_targets.len()); all_targets } diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index fc0b2d5a140ed..80b72febfb598 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -87,6 +87,7 @@ std = [ "pallet-delegated-staking?/std", "pallet-democracy?/std", "pallet-dev-mode?/std", + "pallet-election-provider-multi-block?/std", "pallet-election-provider-multi-phase?/std", "pallet-election-provider-support-benchmarking?/std", "pallet-elections-phragmen?/std", @@ -281,6 +282,7 @@ runtime-benchmarks = [ "pallet-core-fellowship?/runtime-benchmarks", "pallet-delegated-staking?/runtime-benchmarks", "pallet-democracy?/runtime-benchmarks", + "pallet-election-provider-multi-block?/runtime-benchmarks", "pallet-election-provider-multi-phase?/runtime-benchmarks", "pallet-election-provider-support-benchmarking?/runtime-benchmarks", "pallet-elections-phragmen?/runtime-benchmarks", @@ -417,6 +419,7 @@ try-runtime = [ "pallet-delegated-staking?/try-runtime", "pallet-democracy?/try-runtime", "pallet-dev-mode?/try-runtime", + "pallet-election-provider-multi-block?/try-runtime", "pallet-election-provider-multi-phase?/try-runtime", "pallet-elections-phragmen?/try-runtime", "pallet-fast-unstake?/try-runtime", @@ -546,7 +549,7 @@ with-tracing = [ "sp-tracing?/with-tracing", "sp-tracing?/with-tracing", ] -runtime-full = ["assets-common", "binary-merkle-tree", "bp-header-chain", "bp-messages", "bp-parachains", "bp-polkadot", "bp-polkadot-core", "bp-relayers", "bp-runtime", "bp-test-utils", "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "bridge-hub-common", "bridge-runtime-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-parachain-system-proc-macro", "cumulus-pallet-session-benchmarking", "cumulus-pallet-solo-to-para", "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-ping", "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-primitives-proof-size-hostfunction", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-timestamp", "cumulus-primitives-utility", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-executive", "frame-metadata-hash-extension", "frame-support", "frame-support-procedural", "frame-support-procedural-tools-derive", "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", "pallet-alliance", "pallet-asset-conversion", "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", "pallet-asset-rewards", "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", "pallet-bags-list", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-broker", "pallet-child-bounties", "pallet-collator-selection", "pallet-collective", "pallet-collective-content", "pallet-contracts", "pallet-contracts-proc-macro", "pallet-contracts-uapi", "pallet-conviction-voting", "pallet-core-fellowship", "pallet-delegated-staking", "pallet-democracy", "pallet-dev-mode", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership", "pallet-message-queue", "pallet-migrations", "pallet-mixnet", "pallet-mmr", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-nis", "pallet-node-authorization", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-paged-list", "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", "pallet-revive", "pallet-revive-proc-macro", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", "pallet-salary", "pallet-scheduler", "pallet-scored-pool", "pallet-session", "pallet-session-benchmarking", "pallet-skip-feeless-payment", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", "pallet-staking-reward-fn", "pallet-staking-runtime-api", "pallet-state-trie-migration", "pallet-statement", "pallet-sudo", "pallet-timestamp", "pallet-tips", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-transaction-storage", "pallet-treasury", "pallet-tx-pause", "pallet-uniques", "pallet-utility", "pallet-verify-signature", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "pallet-xcm-bridge-hub-router", "parachains-common", "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", "polkadot-runtime-metrics", "polkadot-runtime-parachains", "polkadot-sdk-frame", "sc-chain-spec-derive", "sc-tracing-proc-macro", "slot-range-helper", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", "snowbridge-outbound-queue-merkle-tree", "snowbridge-outbound-queue-runtime-api", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-ethereum-client-fixtures", "snowbridge-pallet-inbound-queue", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-pallet-outbound-queue", "snowbridge-pallet-system", "snowbridge-router-primitives", "snowbridge-runtime-common", "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", "sp-arithmetic", "sp-authority-discovery", "sp-block-builder", "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", "sp-consensus-pow", "sp-consensus-slots", "sp-core", "sp-crypto-ec-utils", "sp-crypto-hashing", "sp-crypto-hashing-proc-macro", "sp-debug-derive", "sp-externalities", "sp-genesis-builder", "sp-inherents", "sp-io", "sp-keyring", "sp-keystore", "sp-metadata-ir", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-proc-macro", "sp-session", "sp-staking", "sp-state-machine", "sp-statement-store", "sp-std", "sp-storage", "sp-timestamp", "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", "sp-version", "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-bip39", "testnet-parachains-constants", "tracing-gum-proc-macro", "xcm-procedural", "xcm-runtime-apis"] +runtime-full = ["assets-common", "binary-merkle-tree", "bp-header-chain", "bp-messages", "bp-parachains", "bp-polkadot", "bp-polkadot-core", "bp-relayers", "bp-runtime", "bp-test-utils", "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "bridge-hub-common", "bridge-runtime-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-parachain-system-proc-macro", "cumulus-pallet-session-benchmarking", "cumulus-pallet-solo-to-para", "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-ping", "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-primitives-proof-size-hostfunction", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-timestamp", "cumulus-primitives-utility", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-executive", "frame-metadata-hash-extension", "frame-support", "frame-support-procedural", "frame-support-procedural-tools-derive", "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", "pallet-alliance", "pallet-asset-conversion", "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", "pallet-asset-rewards", "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", "pallet-bags-list", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-broker", "pallet-child-bounties", "pallet-collator-selection", "pallet-collective", "pallet-collective-content", "pallet-contracts", "pallet-contracts-proc-macro", "pallet-contracts-uapi", "pallet-conviction-voting", "pallet-core-fellowship", "pallet-delegated-staking", "pallet-democracy", "pallet-dev-mode", "pallet-election-provider-multi-block", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership", "pallet-message-queue", "pallet-migrations", "pallet-mixnet", "pallet-mmr", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-nis", "pallet-node-authorization", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-paged-list", "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", "pallet-revive", "pallet-revive-proc-macro", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", "pallet-salary", "pallet-scheduler", "pallet-scored-pool", "pallet-session", "pallet-session-benchmarking", "pallet-skip-feeless-payment", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", "pallet-staking-reward-fn", "pallet-staking-runtime-api", "pallet-state-trie-migration", "pallet-statement", "pallet-sudo", "pallet-timestamp", "pallet-tips", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-transaction-storage", "pallet-treasury", "pallet-tx-pause", "pallet-uniques", "pallet-utility", "pallet-verify-signature", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "pallet-xcm-bridge-hub-router", "parachains-common", "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", "polkadot-runtime-metrics", "polkadot-runtime-parachains", "polkadot-sdk-frame", "sc-chain-spec-derive", "sc-tracing-proc-macro", "slot-range-helper", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", "snowbridge-outbound-queue-merkle-tree", "snowbridge-outbound-queue-runtime-api", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-ethereum-client-fixtures", "snowbridge-pallet-inbound-queue", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-pallet-outbound-queue", "snowbridge-pallet-system", "snowbridge-router-primitives", "snowbridge-runtime-common", "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", "sp-arithmetic", "sp-authority-discovery", "sp-block-builder", "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", "sp-consensus-pow", "sp-consensus-slots", "sp-core", "sp-crypto-ec-utils", "sp-crypto-hashing", "sp-crypto-hashing-proc-macro", "sp-debug-derive", "sp-externalities", "sp-genesis-builder", "sp-inherents", "sp-io", "sp-keyring", "sp-keystore", "sp-metadata-ir", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-proc-macro", "sp-session", "sp-staking", "sp-state-machine", "sp-statement-store", "sp-std", "sp-storage", "sp-timestamp", "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", "sp-version", "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-bip39", "testnet-parachains-constants", "tracing-gum-proc-macro", "xcm-procedural", "xcm-runtime-apis"] runtime = [ "frame-benchmarking", "frame-benchmarking-pallet-pov", @@ -1028,6 +1031,11 @@ default-features = false optional = true path = "../substrate/frame/examples/dev-mode" +[dependencies.pallet-election-provider-multi-block] +default-features = false +optional = true +path = "../substrate/frame/election-provider-multi-block" + [dependencies.pallet-election-provider-multi-phase] default-features = false optional = true diff --git a/umbrella/src/lib.rs b/umbrella/src/lib.rs index a132f16a2c33f..51849199ae607 100644 --- a/umbrella/src/lib.rs +++ b/umbrella/src/lib.rs @@ -71,8 +71,7 @@ pub use bridge_hub_common; #[cfg(feature = "bridge-hub-test-utils")] pub use bridge_hub_test_utils; -/// Common types and functions that may be used by substrate-based runtimes of all bridged -/// chains. +/// Common types and functions that may be used by substrate-based runtimes of all bridged chains. #[cfg(feature = "bridge-runtime-common")] pub use bridge_runtime_common; @@ -104,8 +103,7 @@ pub use cumulus_client_consensus_relay_chain; #[cfg(feature = "cumulus-client-network")] pub use cumulus_client_network; -/// Inherent that needs to be present in every parachain block. Contains messages and a relay -/// chain storage-proof. +/// Inherent that needs to be present in every parachain block. Contains messages and a relay chain storage-proof. #[cfg(feature = "cumulus-client-parachain-inherent")] pub use cumulus_client_parachain_inherent; @@ -165,8 +163,7 @@ pub use cumulus_primitives_aura; #[cfg(feature = "cumulus-primitives-core")] pub use cumulus_primitives_core; -/// Inherent that needs to be present in every parachain block. Contains messages and a relay -/// chain storage-proof. +/// Inherent that needs to be present in every parachain block. Contains messages and a relay chain storage-proof. #[cfg(feature = "cumulus-primitives-parachain-inherent")] pub use cumulus_primitives_parachain_inherent; @@ -210,8 +207,7 @@ pub use cumulus_test_relay_sproof_builder; #[cfg(feature = "emulated-integration-tests-common")] pub use emulated_integration_tests_common; -/// Utility library for managing tree-like ordered data with logic for pruning the tree while -/// finalizing nodes. +/// Utility library for managing tree-like ordered data with logic for pruning the tree while finalizing nodes. #[cfg(feature = "fork-tree")] pub use fork_tree; @@ -243,8 +239,7 @@ pub use frame_executive; #[cfg(feature = "frame-metadata-hash-extension")] pub use frame_metadata_hash_extension; -/// An externalities provided environment that can load itself from remote nodes or cached -/// files. +/// An externalities provided environment that can load itself from remote nodes or cached files. #[cfg(feature = "frame-remote-externalities")] pub use frame_remote_externalities; @@ -344,8 +339,7 @@ pub use pallet_authority_discovery; #[cfg(feature = "pallet-authorship")] pub use pallet_authorship; -/// Consensus extension module for BABE consensus. Collects on-chain randomness from VRF -/// outputs and manages epoch transitions. +/// Consensus extension module for BABE consensus. Collects on-chain randomness from VRF outputs and manages epoch transitions. #[cfg(feature = "pallet-babe")] pub use pallet_babe; @@ -369,8 +363,7 @@ pub use pallet_beefy_mmr; #[cfg(feature = "pallet-bounties")] pub use pallet_bounties; -/// Module implementing GRANDPA on-chain light client used for bridging consensus of -/// substrate-based chains. +/// Module implementing GRANDPA on-chain light client used for bridging consensus of substrate-based chains. #[cfg(feature = "pallet-bridge-grandpa")] pub use pallet_bridge_grandpa; @@ -398,8 +391,7 @@ pub use pallet_child_bounties; #[cfg(feature = "pallet-collator-selection")] pub use pallet_collator_selection; -/// Collective system: Members of a set of account IDs can make their collective feelings known -/// through dispatched calls from one of two specialized origins. +/// Collective system: Members of a set of account IDs can make their collective feelings known through dispatched calls from one of two specialized origins. #[cfg(feature = "pallet-collective")] pub use pallet_collective; @@ -443,6 +435,10 @@ pub use pallet_democracy; #[cfg(feature = "pallet-dev-mode")] pub use pallet_dev_mode; +/// PALLET multi phase+block election providers. +#[cfg(feature = "pallet-election-provider-multi-block")] +pub use pallet_election_provider_multi_block; + /// PALLET two phase election providers. #[cfg(feature = "pallet-election-provider-multi-phase")] pub use pallet_election_provider_multi_phase; @@ -567,8 +563,7 @@ pub use pallet_preimage; #[cfg(feature = "pallet-proxy")] pub use pallet_proxy; -/// Ranked collective system: Members of a set of account IDs can make their collective -/// feelings known through dispatched calls from one of two specialized origins. +/// Ranked collective system: Members of a set of account IDs can make their collective feelings known through dispatched calls from one of two specialized origins. #[cfg(feature = "pallet-ranked-collective")] pub use pallet_ranked_collective; @@ -636,8 +631,7 @@ pub use pallet_session; #[cfg(feature = "pallet-session-benchmarking")] pub use pallet_session_benchmarking; -/// Pallet to skip payments for calls annotated with `feeless_if` if the respective conditions -/// are satisfied. +/// Pallet to skip payments for calls annotated with `feeless_if` if the respective conditions are satisfied. #[cfg(feature = "pallet-skip-feeless-payment")] pub use pallet_skip_feeless_payment; @@ -749,23 +743,19 @@ pub use parachains_common; #[cfg(feature = "parachains-runtimes-test-utils")] pub use parachains_runtimes_test_utils; -/// Polkadot Approval Distribution subsystem for the distribution of assignments and approvals -/// for approval checks on candidates over the network. +/// Polkadot Approval Distribution subsystem for the distribution of assignments and approvals for approval checks on candidates over the network. #[cfg(feature = "polkadot-approval-distribution")] pub use polkadot_approval_distribution; -/// Polkadot Bitfiled Distribution subsystem, which gossips signed availability bitfields used -/// to compactly determine which backed candidates are available or not based on a 2/3+ quorum. +/// Polkadot Bitfiled Distribution subsystem, which gossips signed availability bitfields used to compactly determine which backed candidates are available or not based on a 2/3+ quorum. #[cfg(feature = "polkadot-availability-bitfield-distribution")] pub use polkadot_availability_bitfield_distribution; -/// The Availability Distribution subsystem. Requests the required availability data. Also -/// distributes availability data and chunks to requesters. +/// The Availability Distribution subsystem. Requests the required availability data. Also distributes availability data and chunks to requesters. #[cfg(feature = "polkadot-availability-distribution")] pub use polkadot_availability_distribution; -/// The Availability Recovery subsystem. Handles requests for recovering the availability data -/// of included candidates. +/// The Availability Recovery subsystem. Handles requests for recovering the availability data of included candidates. #[cfg(feature = "polkadot-availability-recovery")] pub use polkadot_availability_recovery; @@ -773,8 +763,7 @@ pub use polkadot_availability_recovery; #[cfg(feature = "polkadot-cli")] pub use polkadot_cli; -/// Polkadot Collator Protocol subsystem. Allows collators and validators to talk to each -/// other. +/// Polkadot Collator Protocol subsystem. Allows collators and validators to talk to each other. #[cfg(feature = "polkadot-collator-protocol")] pub use polkadot_collator_protocol; @@ -782,8 +771,7 @@ pub use polkadot_collator_protocol; #[cfg(feature = "polkadot-core-primitives")] pub use polkadot_core_primitives; -/// Polkadot Dispute Distribution subsystem, which ensures all concerned validators are aware -/// of a dispute and have the relevant votes. +/// Polkadot Dispute Distribution subsystem, which ensures all concerned validators are aware of a dispute and have the relevant votes. #[cfg(feature = "polkadot-dispute-distribution")] pub use polkadot_dispute_distribution; @@ -791,8 +779,7 @@ pub use polkadot_dispute_distribution; #[cfg(feature = "polkadot-erasure-coding")] pub use polkadot_erasure_coding; -/// Polkadot Gossip Support subsystem. Responsible for keeping track of session changes and -/// issuing a connection request to the relevant validators on every new session. +/// Polkadot Gossip Support subsystem. Responsible for keeping track of session changes and issuing a connection request to the relevant validators on every new session. #[cfg(feature = "polkadot-gossip-support")] pub use polkadot_gossip_support; @@ -812,13 +799,11 @@ pub use polkadot_node_core_approval_voting; #[cfg(feature = "polkadot-node-core-approval-voting-parallel")] pub use polkadot_node_core_approval_voting_parallel; -/// The Availability Store subsystem. Wrapper over the DB that stores availability data and -/// chunks. +/// The Availability Store subsystem. Wrapper over the DB that stores availability data and chunks. #[cfg(feature = "polkadot-node-core-av-store")] pub use polkadot_node_core_av_store; -/// The Candidate Backing Subsystem. Tracks parachain candidates that can be backed, as well as -/// the issuance of statements about candidates. +/// The Candidate Backing Subsystem. Tracks parachain candidates that can be backed, as well as the issuance of statements about candidates. #[cfg(feature = "polkadot-node-core-backing")] pub use polkadot_node_core_backing; @@ -826,13 +811,11 @@ pub use polkadot_node_core_backing; #[cfg(feature = "polkadot-node-core-bitfield-signing")] pub use polkadot_node_core_bitfield_signing; -/// Polkadot crate that implements the Candidate Validation subsystem. Handles requests to -/// validate candidates according to a PVF. +/// Polkadot crate that implements the Candidate Validation subsystem. Handles requests to validate candidates according to a PVF. #[cfg(feature = "polkadot-node-core-candidate-validation")] pub use polkadot_node_core_candidate_validation; -/// The Chain API subsystem provides access to chain related utility functions like block -/// number to hash conversions. +/// The Chain API subsystem provides access to chain related utility functions like block number to hash conversions. #[cfg(feature = "polkadot-node-core-chain-api")] pub use polkadot_node_core_chain_api; @@ -852,33 +835,27 @@ pub use polkadot_node_core_parachains_inherent; #[cfg(feature = "polkadot-node-core-prospective-parachains")] pub use polkadot_node_core_prospective_parachains; -/// Responsible for assembling a relay chain block from a set of available parachain -/// candidates. +/// Responsible for assembling a relay chain block from a set of available parachain candidates. #[cfg(feature = "polkadot-node-core-provisioner")] pub use polkadot_node_core_provisioner; -/// Polkadot crate that implements the PVF validation host. Responsible for coordinating -/// preparation and execution of PVFs. +/// Polkadot crate that implements the PVF validation host. Responsible for coordinating preparation and execution of PVFs. #[cfg(feature = "polkadot-node-core-pvf")] pub use polkadot_node_core_pvf; -/// Polkadot crate that implements the PVF pre-checking subsystem. Responsible for checking and -/// voting for PVFs that are pending approval. +/// Polkadot crate that implements the PVF pre-checking subsystem. Responsible for checking and voting for PVFs that are pending approval. #[cfg(feature = "polkadot-node-core-pvf-checker")] pub use polkadot_node_core_pvf_checker; -/// Polkadot crate that contains functionality related to PVFs that is shared by the PVF host -/// and the PVF workers. +/// Polkadot crate that contains functionality related to PVFs that is shared by the PVF host and the PVF workers. #[cfg(feature = "polkadot-node-core-pvf-common")] pub use polkadot_node_core_pvf_common; -/// Polkadot crate that contains the logic for executing PVFs. Used by the -/// polkadot-execute-worker binary. +/// Polkadot crate that contains the logic for executing PVFs. Used by the polkadot-execute-worker binary. #[cfg(feature = "polkadot-node-core-pvf-execute-worker")] pub use polkadot_node_core_pvf_execute_worker; -/// Polkadot crate that contains the logic for preparing PVFs. Used by the -/// polkadot-prepare-worker binary. +/// Polkadot crate that contains the logic for preparing PVFs. Used by the polkadot-prepare-worker binary. #[cfg(feature = "polkadot-node-core-pvf-prepare-worker")] pub use polkadot_node_core_pvf_prepare_worker; @@ -942,8 +919,7 @@ pub use polkadot_runtime_metrics; #[cfg(feature = "polkadot-runtime-parachains")] pub use polkadot_runtime_parachains; -/// Experimental: The single package to get you started with building frame pallets and -/// runtimes. +/// Experimental: The single package to get you started with building frame pallets and runtimes. #[cfg(feature = "polkadot-sdk-frame")] pub use polkadot_sdk_frame; @@ -1131,8 +1107,7 @@ pub use sc_rpc_spec_v2; #[cfg(feature = "sc-runtime-utilities")] pub use sc_runtime_utilities; -/// Substrate service. Starts a thread that spins up the network, client, and extrinsic pool. -/// Manages communication between them. +/// Substrate service. Starts a thread that spins up the network, client, and extrinsic pool. Manages communication between them. #[cfg(feature = "sc-service")] pub use sc_service; @@ -1308,8 +1283,7 @@ pub use sp_core; #[cfg(feature = "sp-core-hashing")] pub use sp_core_hashing; -/// Procedural macros for calculating static hashes (deprecated in favor of -/// `sp-crypto-hashing-proc-macro`). +/// Procedural macros for calculating static hashes (deprecated in favor of `sp-crypto-hashing-proc-macro`). #[cfg(feature = "sp-core-hashing-proc-macro")] pub use sp_core_hashing_proc_macro; @@ -1397,8 +1371,7 @@ pub use sp_runtime; #[cfg(feature = "sp-runtime-interface")] pub use sp_runtime_interface; -/// This crate provides procedural macros for usage within the context of the Substrate runtime -/// interface. +/// This crate provides procedural macros for usage within the context of the Substrate runtime interface. #[cfg(feature = "sp-runtime-interface-proc-macro")] pub use sp_runtime_interface_proc_macro; @@ -1406,8 +1379,7 @@ pub use sp_runtime_interface_proc_macro; #[cfg(feature = "sp-session")] pub use sp_session; -/// A crate which contains primitives that are useful for implementation that uses staking -/// approaches in general. Definitions related to sessions, slashing, etc go here. +/// A crate which contains primitives that are useful for implementation that uses staking approaches in general. Definitions related to sessions, slashing, etc go here. #[cfg(feature = "sp-staking")] pub use sp_staking; @@ -1419,8 +1391,7 @@ pub use sp_state_machine; #[cfg(feature = "sp-statement-store")] pub use sp_statement_store; -/// Lowest-abstraction level for the Substrate runtime: just exports useful primitives from std -/// or client/alloc to be used with any code that depends on the runtime. +/// Lowest-abstraction level for the Substrate runtime: just exports useful primitives from std or client/alloc to be used with any code that depends on the runtime. #[cfg(feature = "sp-std")] pub use sp_std; @@ -1448,8 +1419,7 @@ pub use sp_transaction_storage_proof; #[cfg(feature = "sp-trie")] pub use sp_trie; -/// Version module for the Substrate runtime; Provides a function that returns the runtime -/// version. +/// Version module for the Substrate runtime; Provides a function that returns the runtime version. #[cfg(feature = "sp-version")] pub use sp_version; @@ -1465,8 +1435,7 @@ pub use sp_wasm_interface; #[cfg(feature = "sp-weights")] pub use sp_weights; -/// Utility for building chain-specification files for Substrate-based runtimes based on -/// `sp-genesis-builder`. +/// Utility for building chain-specification files for Substrate-based runtimes based on `sp-genesis-builder`. #[cfg(feature = "staging-chain-spec-builder")] pub use staging_chain_spec_builder; @@ -1494,8 +1463,7 @@ pub use staging_xcm_builder; #[cfg(feature = "staging-xcm-executor")] pub use staging_xcm_executor; -/// Generate and restore keys for Substrate based chains such as Polkadot, Kusama and a growing -/// number of parachains and Substrate based projects. +/// Generate and restore keys for Substrate based chains such as Polkadot, Kusama and a growing number of parachains and Substrate based projects. #[cfg(feature = "subkey")] pub use subkey; @@ -1539,8 +1507,7 @@ pub use testnet_parachains_constants; #[cfg(feature = "tracing-gum")] pub use tracing_gum; -/// Generate an overseer including builder pattern and message wrapper from a single annotated -/// struct definition. +/// Generate an overseer including builder pattern and message wrapper from a single annotated struct definition. #[cfg(feature = "tracing-gum-proc-macro")] pub use tracing_gum_proc_macro; From ebde96caf5bf24a626d7de247724a599f106284f Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Tue, 21 Jan 2025 17:33:47 +0100 Subject: [PATCH 116/169] Fix link-checker job (#7261) Link-checker job is constantly failing because of these two links. In the browser there is a redirect, apparently our lychee checker can't handle it. --- polkadot/node/gum/proc-macro/src/lib.rs | 2 +- substrate/frame/contracts/src/schedule.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/polkadot/node/gum/proc-macro/src/lib.rs b/polkadot/node/gum/proc-macro/src/lib.rs index e8b6b599172d5..96ff4417a5a27 100644 --- a/polkadot/node/gum/proc-macro/src/lib.rs +++ b/polkadot/node/gum/proc-macro/src/lib.rs @@ -90,7 +90,7 @@ pub(crate) fn gum(item: proc_macro::TokenStream, level: Level) -> proc_macro::To .add_comment("Generated overseer code by `gum::warn!(..)`".to_owned()) // `dry=true` until rust-analyzer can selectively disable features so it's // not all red squiggles. Originally: `!cfg!(feature = "expand")` - // ISSUE: + // ISSUE: https://github.com/rust-lang/rust-analyzer/issues/11777 .dry(true) .verbose(false) .fmt(expander::Edition::_2021) diff --git a/substrate/frame/contracts/src/schedule.rs b/substrate/frame/contracts/src/schedule.rs index 80b8c54b1e1d0..285184280fcba 100644 --- a/substrate/frame/contracts/src/schedule.rs +++ b/substrate/frame/contracts/src/schedule.rs @@ -114,7 +114,7 @@ impl Limits { #[scale_info(skip_type_params(T))] pub struct InstructionWeights { /// Base instruction `ref_time` Weight. - /// Should match to wasmi's `1` fuel (see ). + /// Should match to wasmi's `1` fuel (see ). pub base: u32, /// The type parameter is used in the default implementation. #[codec(skip)] From 9edaef09a69e39b0785f8339f93a3ed6a1f6e023 Mon Sep 17 00:00:00 2001 From: Ludovic_Domingues Date: Tue, 21 Jan 2025 18:36:04 +0100 Subject: [PATCH 117/169] Migrate pallet-paged-list-fuzzer to umbrella crate (#6930) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of #6504 --------- Co-authored-by: Bastian Köcher Co-authored-by: Giuseppe Re --- Cargo.lock | 3 +-- substrate/frame/paged-list/fuzzer/Cargo.toml | 11 ++++++++--- substrate/frame/paged-list/fuzzer/src/paged_list.rs | 7 +++++-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 397d0c7fe823a..55cc1721bddeb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14608,10 +14608,9 @@ name = "pallet-paged-list-fuzzer" version = "0.1.0" dependencies = [ "arbitrary", - "frame-support 28.0.0", "honggfuzz", "pallet-paged-list 0.6.0", - "sp-io 30.0.0", + "polkadot-sdk-frame 0.1.0", ] [[package]] diff --git a/substrate/frame/paged-list/fuzzer/Cargo.toml b/substrate/frame/paged-list/fuzzer/Cargo.toml index 7e6162df09ba6..32535093b5980 100644 --- a/substrate/frame/paged-list/fuzzer/Cargo.toml +++ b/substrate/frame/paged-list/fuzzer/Cargo.toml @@ -18,8 +18,13 @@ path = "src/paged_list.rs" [dependencies] arbitrary = { workspace = true } +frame = { workspace = true, features = ["runtime"] } honggfuzz = { workspace = true } - -frame-support = { features = ["std"], workspace = true } pallet-paged-list = { features = ["std"], workspace = true } -sp-io = { features = ["std"], workspace = true } + +[features] +default = ["std"] +std = [ + "frame/std", + "pallet-paged-list/std", +] diff --git a/substrate/frame/paged-list/fuzzer/src/paged_list.rs b/substrate/frame/paged-list/fuzzer/src/paged_list.rs index 43b797eee6bfb..f0f914de14229 100644 --- a/substrate/frame/paged-list/fuzzer/src/paged_list.rs +++ b/substrate/frame/paged-list/fuzzer/src/paged_list.rs @@ -30,9 +30,12 @@ use arbitrary::Arbitrary; use honggfuzz::fuzz; -use frame_support::{storage::StorageList, StorageNoopGuard}; +use frame::{ + prelude::*, runtime::prelude::storage::storage_noop_guard::StorageNoopGuard, + testing_prelude::TestExternalities, +}; + use pallet_paged_list::mock::{PagedList as List, *}; -use sp_io::TestExternalities; type Meta = MetaOf; fn main() { From 2345eb9a5b5e2145ac1c04fd9cf1fcf12b7278b6 Mon Sep 17 00:00:00 2001 From: Javier Viola <363911+pepoviola@users.noreply.github.com> Date: Tue, 21 Jan 2025 18:24:05 -0300 Subject: [PATCH 118/169] Bump zombienet version to `v1.3.119` (#7283) This version include a fix that make test `zombienet-polkadot-malus-0001-dispute-valid` green again. Thx! --- .gitlab/pipeline/zombienet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/pipeline/zombienet.yml b/.gitlab/pipeline/zombienet.yml index 08bfed2e24ce9..c48bca8af48be 100644 --- a/.gitlab/pipeline/zombienet.yml +++ b/.gitlab/pipeline/zombienet.yml @@ -1,7 +1,7 @@ .zombienet-refs: extends: .build-refs variables: - ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.116" + ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.119" PUSHGATEWAY_URL: "http://zombienet-prometheus-pushgateway.managed-monitoring:9091/metrics/job/zombie-metrics" DEBUG: "zombie,zombie::network-node,zombie::kube::client::logs" ZOMBIE_PROVIDER: "k8s" From 1bdb817f2b140b0c2573396146fd7bbfb936af10 Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Wed, 22 Jan 2025 12:01:28 +0200 Subject: [PATCH 119/169] Enable BEEFY `report_fork_voting()` (#6856) Related to https://github.com/paritytech/polkadot-sdk/issues/4523 Follow-up for: https://github.com/paritytech/polkadot-sdk/pull/5188 Reopening https://github.com/paritytech/polkadot-sdk/pull/6732 as a new PR --------- Co-authored-by: command-bot <> --- Cargo.lock | 14 +++- .../rococo/src/weights/pallet_beefy_mmr.rs | 37 ++++++--- .../westend/src/weights/pallet_beefy_mmr.rs | 37 ++++++--- prdoc/pr_6856.prdoc | 28 +++++++ substrate/frame/beefy-mmr/src/benchmarking.rs | 18 +++++ substrate/frame/beefy-mmr/src/lib.rs | 16 ++++ substrate/frame/beefy-mmr/src/weights.rs | 81 +++++++++++++------ substrate/frame/beefy/src/equivocation.rs | 8 +- substrate/frame/beefy/src/lib.rs | 10 +-- substrate/frame/beefy/src/mock.rs | 9 +++ substrate/frame/beefy/src/tests.rs | 56 ++++++++++++- .../frame/merkle-mountain-range/src/lib.rs | 6 ++ .../merkle-mountain-range/src/mmr/mmr.rs | 18 ++++- .../merkle-mountain-range/src/mmr/mod.rs | 2 +- .../frame/merkle-mountain-range/src/tests.rs | 1 + .../primitives/consensus/beefy/src/lib.rs | 6 ++ .../merkle-mountain-range/Cargo.toml | 2 +- 17 files changed, 278 insertions(+), 71 deletions(-) create mode 100644 prdoc/pr_6856.prdoc diff --git a/Cargo.lock b/Cargo.lock index 55cc1721bddeb..7e41b7e993744 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17263,6 +17263,16 @@ dependencies = [ "itertools 0.10.5", ] +[[package]] +name = "polkadot-ckb-merkle-mountain-range" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "221c71b432b38e494a0fdedb5f720e4cb974edf03a0af09e5b2238dbac7e6947" +dependencies = [ + "cfg-if", + "itertools 0.10.5", +] + [[package]] name = "polkadot-cli" version = "7.0.0" @@ -26958,7 +26968,7 @@ dependencies = [ "array-bytes", "log", "parity-scale-codec", - "polkadot-ckb-merkle-mountain-range", + "polkadot-ckb-merkle-mountain-range 0.8.1", "scale-info", "serde", "sp-api 26.0.0", @@ -26976,7 +26986,7 @@ checksum = "9a12dd76e368f1e48144a84b4735218b712f84b3f976970e2f25a29b30440e10" dependencies = [ "log", "parity-scale-codec", - "polkadot-ckb-merkle-mountain-range", + "polkadot-ckb-merkle-mountain-range 0.7.0", "scale-info", "serde", "sp-api 34.0.0", diff --git a/polkadot/runtime/rococo/src/weights/pallet_beefy_mmr.rs b/polkadot/runtime/rococo/src/weights/pallet_beefy_mmr.rs index 317c9149ec6c5..54989c4f549c5 100644 --- a/polkadot/runtime/rococo/src/weights/pallet_beefy_mmr.rs +++ b/polkadot/runtime/rococo/src/weights/pallet_beefy_mmr.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_beefy_mmr` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-08-13, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-02, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-696hpswk-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-wiukf8gn-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024 // Executed Command: @@ -48,14 +48,25 @@ use core::marker::PhantomData; /// Weight functions for `pallet_beefy_mmr`. pub struct WeightInfo(PhantomData); impl pallet_beefy_mmr::WeightInfo for WeightInfo { + /// The range of component `n` is `[2, 512]`. + fn n_leafs_proof_is_optimal(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 622_000 picoseconds. + Weight::from_parts(1_166_954, 0) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 65 + .saturating_add(Weight::from_parts(1_356, 0).saturating_mul(n.into())) + } /// Storage: `System::BlockHash` (r:1 w:0) /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn extract_validation_context() -> Weight { // Proof Size summary in bytes: - // Measured: `92` + // Measured: `68` // Estimated: `3509` - // Minimum execution time: 7_116_000 picoseconds. - Weight::from_parts(7_343_000, 0) + // Minimum execution time: 6_272_000 picoseconds. + Weight::from_parts(6_452_000, 0) .saturating_add(Weight::from_parts(0, 3509)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -63,10 +74,10 @@ impl pallet_beefy_mmr::WeightInfo for WeightInfo { /// Proof: `Mmr::Nodes` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) fn read_peak() -> Weight { // Proof Size summary in bytes: - // Measured: `234` + // Measured: `254` // Estimated: `3505` - // Minimum execution time: 5_652_000 picoseconds. - Weight::from_parts(5_963_000, 0) + // Minimum execution time: 6_576_000 picoseconds. + Weight::from_parts(6_760_000, 0) .saturating_add(Weight::from_parts(0, 3505)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -77,13 +88,13 @@ impl pallet_beefy_mmr::WeightInfo for WeightInfo { /// The range of component `n` is `[2, 512]`. fn n_items_proof_is_non_canonical(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `226` + // Measured: `246` // Estimated: `1517` - // Minimum execution time: 11_953_000 picoseconds. - Weight::from_parts(15_978_891, 0) + // Minimum execution time: 12_538_000 picoseconds. + Weight::from_parts(24_516_023, 0) .saturating_add(Weight::from_parts(0, 1517)) - // Standard Error: 1_780 - .saturating_add(Weight::from_parts(1_480_582, 0).saturating_mul(n.into())) + // Standard Error: 1_923 + .saturating_add(Weight::from_parts(1_426_781, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(2)) } } diff --git a/polkadot/runtime/westend/src/weights/pallet_beefy_mmr.rs b/polkadot/runtime/westend/src/weights/pallet_beefy_mmr.rs index 5be207e3fcff4..8de9f6ab53e6a 100644 --- a/polkadot/runtime/westend/src/weights/pallet_beefy_mmr.rs +++ b/polkadot/runtime/westend/src/weights/pallet_beefy_mmr.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_beefy_mmr` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-08-13, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-02, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-696hpswk-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-wiukf8gn-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 // Executed Command: @@ -48,14 +48,25 @@ use core::marker::PhantomData; /// Weight functions for `pallet_beefy_mmr`. pub struct WeightInfo(PhantomData); impl pallet_beefy_mmr::WeightInfo for WeightInfo { + /// The range of component `n` is `[2, 512]`. + fn n_leafs_proof_is_optimal(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 628_000 picoseconds. + Weight::from_parts(1_200_102, 0) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 63 + .saturating_add(Weight::from_parts(1_110, 0).saturating_mul(n.into())) + } /// Storage: `System::BlockHash` (r:1 w:0) /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn extract_validation_context() -> Weight { // Proof Size summary in bytes: - // Measured: `92` + // Measured: `68` // Estimated: `3509` - // Minimum execution time: 7_850_000 picoseconds. - Weight::from_parts(8_169_000, 0) + // Minimum execution time: 9_862_000 picoseconds. + Weight::from_parts(10_329_000, 0) .saturating_add(Weight::from_parts(0, 3509)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -63,10 +74,10 @@ impl pallet_beefy_mmr::WeightInfo for WeightInfo { /// Proof: `Mmr::Nodes` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) fn read_peak() -> Weight { // Proof Size summary in bytes: - // Measured: `201` + // Measured: `221` // Estimated: `3505` - // Minimum execution time: 6_852_000 picoseconds. - Weight::from_parts(7_448_000, 0) + // Minimum execution time: 6_396_000 picoseconds. + Weight::from_parts(6_691_000, 0) .saturating_add(Weight::from_parts(0, 3505)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -77,13 +88,13 @@ impl pallet_beefy_mmr::WeightInfo for WeightInfo { /// The range of component `n` is `[2, 512]`. fn n_items_proof_is_non_canonical(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `193` + // Measured: `213` // Estimated: `1517` - // Minimum execution time: 12_860_000 picoseconds. - Weight::from_parts(17_158_162, 0) + // Minimum execution time: 12_553_000 picoseconds. + Weight::from_parts(24_003_920, 0) .saturating_add(Weight::from_parts(0, 1517)) - // Standard Error: 1_732 - .saturating_add(Weight::from_parts(1_489_410, 0).saturating_mul(n.into())) + // Standard Error: 2_023 + .saturating_add(Weight::from_parts(1_390_986, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(2)) } } diff --git a/prdoc/pr_6856.prdoc b/prdoc/pr_6856.prdoc new file mode 100644 index 0000000000000..480c3acea1952 --- /dev/null +++ b/prdoc/pr_6856.prdoc @@ -0,0 +1,28 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Enable report_fork_voting() + +doc: + - audience: + - Runtime Dev + - Runtime User + description: | + This PR enables calling `report_fork_voting`. + In order to do this we needed to also check that the ancestry proof is optimal. + +crates: + - name: pallet-mmr + bump: minor + - name: sp-mmr-primitives + bump: minor + - name: sp-consensus-beefy + bump: minor + - name: rococo-runtime + bump: minor + - name: pallet-beefy + bump: minor + - name: pallet-beefy-mmr + bump: minor + - name: westend-runtime + bump: minor diff --git a/substrate/frame/beefy-mmr/src/benchmarking.rs b/substrate/frame/beefy-mmr/src/benchmarking.rs index fea6a2078f0f1..4fddd1bccf115 100644 --- a/substrate/frame/beefy-mmr/src/benchmarking.rs +++ b/substrate/frame/beefy-mmr/src/benchmarking.rs @@ -49,6 +49,24 @@ fn init_block(block_num: u32) { mod benchmarks { use super::*; + /// Generate ancestry proofs with `n` leafs and benchmark the logic that checks + /// if the proof is optimal. + #[benchmark] + fn n_leafs_proof_is_optimal(n: Linear<2, 512>) { + pallet_mmr::UseLocalStorage::::set(true); + + for block_num in 1..=n { + init_block::(block_num); + } + let proof = Mmr::::generate_mock_ancestry_proof().unwrap(); + assert_eq!(proof.leaf_count, n as u64); + + #[block] + { + as AncestryHelper>>::is_proof_optimal(&proof); + }; + } + #[benchmark] fn extract_validation_context() { pallet_mmr::UseLocalStorage::::set(true); diff --git a/substrate/frame/beefy-mmr/src/lib.rs b/substrate/frame/beefy-mmr/src/lib.rs index ef99bc1e9cf11..c7fcdeff87999 100644 --- a/substrate/frame/beefy-mmr/src/lib.rs +++ b/substrate/frame/beefy-mmr/src/lib.rs @@ -210,6 +210,18 @@ where .ok() } + fn is_proof_optimal(proof: &Self::Proof) -> bool { + let is_proof_optimal = pallet_mmr::Pallet::::is_ancestry_proof_optimal(proof); + + // We don't check the proof size when running benchmarks, since we use mock proofs + // which would cause the test to fail. + if cfg!(feature = "runtime-benchmarks") { + return true + } + + is_proof_optimal + } + fn extract_validation_context(header: HeaderFor) -> Option { // Check if the provided header is canonical. let expected_hash = frame_system::Pallet::::block_hash(header.number()); @@ -292,6 +304,10 @@ impl AncestryHelperWeightInfo> for Pallet where T: pallet_mmr::Config, { + fn is_proof_optimal(proof: &>>::Proof) -> Weight { + ::WeightInfo::n_leafs_proof_is_optimal(proof.leaf_count.saturated_into()) + } + fn extract_validation_context() -> Weight { ::WeightInfo::extract_validation_context() } diff --git a/substrate/frame/beefy-mmr/src/weights.rs b/substrate/frame/beefy-mmr/src/weights.rs index dcfdb560ee949..5f7f7055311cd 100644 --- a/substrate/frame/beefy-mmr/src/weights.rs +++ b/substrate/frame/beefy-mmr/src/weights.rs @@ -51,6 +51,7 @@ use core::marker::PhantomData; /// Weight functions needed for `pallet_beefy_mmr`. pub trait WeightInfo { + fn n_leafs_proof_is_optimal(n: u32, ) -> Weight; fn extract_validation_context() -> Weight; fn read_peak() -> Weight; fn n_items_proof_is_non_canonical(n: u32, ) -> Weight; @@ -59,25 +60,38 @@ pub trait WeightInfo { /// Weights for `pallet_beefy_mmr` using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { + /// The range of component `n` is `[2, 512]`. + fn n_leafs_proof_is_optimal(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 622_000 picoseconds. + Weight::from_parts(1_166_954, 0) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 65 + .saturating_add(Weight::from_parts(1_356, 0).saturating_mul(n.into())) + } /// Storage: `System::BlockHash` (r:1 w:0) /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn extract_validation_context() -> Weight { // Proof Size summary in bytes: // Measured: `68` // Estimated: `3509` - // Minimum execution time: 6_687_000 picoseconds. - Weight::from_parts(6_939_000, 3509) - .saturating_add(T::DbWeight::get().reads(1_u64)) + // Minimum execution time: 6_272_000 picoseconds. + Weight::from_parts(6_452_000, 0) + .saturating_add(Weight::from_parts(0, 3509)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `Mmr::Nodes` (r:1 w:0) /// Proof: `Mmr::Nodes` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) fn read_peak() -> Weight { // Proof Size summary in bytes: - // Measured: `386` + // Measured: `254` // Estimated: `3505` - // Minimum execution time: 10_409_000 picoseconds. - Weight::from_parts(10_795_000, 3505) - .saturating_add(T::DbWeight::get().reads(1_u64)) + // Minimum execution time: 6_576_000 picoseconds. + Weight::from_parts(6_760_000, 0) + .saturating_add(Weight::from_parts(0, 3505)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `Mmr::RootHash` (r:1 w:0) /// Proof: `Mmr::RootHash` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) @@ -86,37 +100,51 @@ impl WeightInfo for SubstrateWeight { /// The range of component `n` is `[2, 512]`. fn n_items_proof_is_non_canonical(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `378` + // Measured: `246` // Estimated: `1517` - // Minimum execution time: 15_459_000 picoseconds. - Weight::from_parts(21_963_366, 1517) - // Standard Error: 1_528 - .saturating_add(Weight::from_parts(984_907, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(2_u64)) + // Minimum execution time: 12_538_000 picoseconds. + Weight::from_parts(24_516_023, 0) + .saturating_add(Weight::from_parts(0, 1517)) + // Standard Error: 1_923 + .saturating_add(Weight::from_parts(1_426_781, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(2)) } } // For backwards compatibility and tests. impl WeightInfo for () { + /// The range of component `n` is `[2, 512]`. + fn n_leafs_proof_is_optimal(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 622_000 picoseconds. + Weight::from_parts(1_166_954, 0) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 65 + .saturating_add(Weight::from_parts(1_356, 0).saturating_mul(n.into())) + } /// Storage: `System::BlockHash` (r:1 w:0) /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn extract_validation_context() -> Weight { // Proof Size summary in bytes: // Measured: `68` // Estimated: `3509` - // Minimum execution time: 6_687_000 picoseconds. - Weight::from_parts(6_939_000, 3509) - .saturating_add(RocksDbWeight::get().reads(1_u64)) + // Minimum execution time: 6_272_000 picoseconds. + Weight::from_parts(6_452_000, 0) + .saturating_add(Weight::from_parts(0, 3509)) + .saturating_add(RocksDbWeight::get().reads(1)) } /// Storage: `Mmr::Nodes` (r:1 w:0) /// Proof: `Mmr::Nodes` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) fn read_peak() -> Weight { // Proof Size summary in bytes: - // Measured: `386` + // Measured: `254` // Estimated: `3505` - // Minimum execution time: 10_409_000 picoseconds. - Weight::from_parts(10_795_000, 3505) - .saturating_add(RocksDbWeight::get().reads(1_u64)) + // Minimum execution time: 6_576_000 picoseconds. + Weight::from_parts(6_760_000, 0) + .saturating_add(Weight::from_parts(0, 3505)) + .saturating_add(RocksDbWeight::get().reads(1)) } /// Storage: `Mmr::RootHash` (r:1 w:0) /// Proof: `Mmr::RootHash` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) @@ -125,12 +153,13 @@ impl WeightInfo for () { /// The range of component `n` is `[2, 512]`. fn n_items_proof_is_non_canonical(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `378` + // Measured: `246` // Estimated: `1517` - // Minimum execution time: 15_459_000 picoseconds. - Weight::from_parts(21_963_366, 1517) - // Standard Error: 1_528 - .saturating_add(Weight::from_parts(984_907, 0).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(2_u64)) + // Minimum execution time: 12_538_000 picoseconds. + Weight::from_parts(24_516_023, 0) + .saturating_add(Weight::from_parts(0, 1517)) + // Standard Error: 1_923 + .saturating_add(Weight::from_parts(1_426_781, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(2)) } } diff --git a/substrate/frame/beefy/src/equivocation.rs b/substrate/frame/beefy/src/equivocation.rs index 3a49b9e169ce9..294d64427ef8a 100644 --- a/substrate/frame/beefy/src/equivocation.rs +++ b/substrate/frame/beefy/src/equivocation.rs @@ -207,11 +207,17 @@ impl EquivocationEvidenceFor { return Err(Error::::InvalidDoubleVotingProof); } - return Ok(()) + Ok(()) }, EquivocationEvidenceFor::ForkVotingProof(equivocation_proof, _) => { let ForkVotingProof { vote, ancestry_proof, header } = equivocation_proof; + if !>>::is_proof_optimal( + &ancestry_proof, + ) { + return Err(Error::::InvalidForkVotingProof); + } + let maybe_validation_context = , >>::extract_validation_context(header); diff --git a/substrate/frame/beefy/src/lib.rs b/substrate/frame/beefy/src/lib.rs index cf690a9df339d..e57fc0e21bc1e 100644 --- a/substrate/frame/beefy/src/lib.rs +++ b/substrate/frame/beefy/src/lib.rs @@ -755,7 +755,8 @@ pub(crate) trait WeightInfoExt: WeightInfo { max_nominators_per_validator: u32, ancestry_proof: &>>::Proof, ) -> Weight { - let _weight = >>::extract_validation_context() + >>::is_proof_optimal(&ancestry_proof) + .saturating_add(>>::extract_validation_context()) .saturating_add( >>::is_non_canonical( ancestry_proof, @@ -765,12 +766,7 @@ pub(crate) trait WeightInfoExt: WeightInfo { 1, validator_count, max_nominators_per_validator, - )); - - // TODO: https://github.com/paritytech/polkadot-sdk/issues/4523 - return `_weight` here. - // We return `Weight::MAX` currently in order to disallow this extrinsic for the moment. - // We need to check that the proof is optimal. - Weight::MAX + )) } fn report_future_block_voting( diff --git a/substrate/frame/beefy/src/mock.rs b/substrate/frame/beefy/src/mock.rs index 38e0cc4cfc266..4b5f1d103b506 100644 --- a/substrate/frame/beefy/src/mock.rs +++ b/substrate/frame/beefy/src/mock.rs @@ -99,6 +99,7 @@ pub struct MockAncestryProofContext { #[derive(Clone, Debug, Decode, Encode, PartialEq, TypeInfo)] pub struct MockAncestryProof { + pub is_optimal: bool, pub is_non_canonical: bool, } @@ -128,6 +129,10 @@ impl AncestryHelper
for MockAncestryHelper { unimplemented!() } + fn is_proof_optimal(proof: &Self::Proof) -> bool { + proof.is_optimal + } + fn extract_validation_context(_header: Header) -> Option { AncestryProofContext::get() } @@ -142,6 +147,10 @@ impl AncestryHelper
for MockAncestryHelper { } impl AncestryHelperWeightInfo
for MockAncestryHelper { + fn is_proof_optimal(_proof: &>>::Proof) -> Weight { + unimplemented!() + } + fn extract_validation_context() -> Weight { unimplemented!() } diff --git a/substrate/frame/beefy/src/tests.rs b/substrate/frame/beefy/src/tests.rs index 89645d21f6baa..1bd0a72b25ecd 100644 --- a/substrate/frame/beefy/src/tests.rs +++ b/substrate/frame/beefy/src/tests.rs @@ -799,7 +799,7 @@ fn report_fork_voting( let payload = Payload::from_single_entry(MMR_ROOT_ID, vec![42]); let equivocation_proof = generate_fork_voting_proof( (block_num, payload, set_id, &equivocation_keyring), - MockAncestryProof { is_non_canonical: true }, + MockAncestryProof { is_optimal: true, is_non_canonical: true }, System::finalize(), ); @@ -835,6 +835,54 @@ fn report_fork_voting_invalid_key_owner_proof() { report_equivocation_invalid_key_owner_proof(report_fork_voting); } +#[test] +fn report_fork_voting_non_optimal_equivocation_proof() { + let authorities = test_authorities(); + + let mut ext = ExtBuilder::default().add_authorities(authorities).build(); + + let mut era = 1; + let (block_num, set_id, equivocation_keyring, key_owner_proof) = ext.execute_with(|| { + start_era(era); + let block_num = System::block_number(); + + let validator_set = Beefy::validator_set().unwrap(); + let authorities = validator_set.validators(); + let set_id = validator_set.id(); + + let equivocation_authority_index = 0; + let equivocation_key = &authorities[equivocation_authority_index]; + let equivocation_keyring = BeefyKeyring::from_public(equivocation_key).unwrap(); + + // generate a key ownership proof at set id in era 1 + let key_owner_proof = Historical::prove((BEEFY_KEY_TYPE, &equivocation_key)).unwrap(); + + era += 1; + start_era(era); + (block_num, set_id, equivocation_keyring, key_owner_proof) + }); + ext.persist_offchain_overlay(); + + ext.execute_with(|| { + let payload = Payload::from_single_entry(MMR_ROOT_ID, vec![42]); + + // Simulate non optimal equivocation proof. + let equivocation_proof = generate_fork_voting_proof( + (block_num + 1, payload.clone(), set_id, &equivocation_keyring), + MockAncestryProof { is_optimal: false, is_non_canonical: true }, + System::finalize(), + ); + assert_err!( + Beefy::report_fork_voting_unsigned( + RuntimeOrigin::none(), + Box::new(equivocation_proof), + key_owner_proof.clone(), + ), + Error::::InvalidForkVotingProof, + ); + }); +} + #[test] fn report_fork_voting_invalid_equivocation_proof() { let authorities = test_authorities(); @@ -869,7 +917,7 @@ fn report_fork_voting_invalid_equivocation_proof() { // vote signed with a key that isn't part of the authority set let equivocation_proof = generate_fork_voting_proof( (block_num, payload.clone(), set_id, &BeefyKeyring::Dave), - MockAncestryProof { is_non_canonical: true }, + MockAncestryProof { is_optimal: true, is_non_canonical: true }, System::finalize(), ); assert_err!( @@ -884,7 +932,7 @@ fn report_fork_voting_invalid_equivocation_proof() { // Simulate InvalidForkVotingProof error. let equivocation_proof = generate_fork_voting_proof( (block_num + 1, payload.clone(), set_id, &equivocation_keyring), - MockAncestryProof { is_non_canonical: false }, + MockAncestryProof { is_optimal: true, is_non_canonical: false }, System::finalize(), ); assert_err!( @@ -945,7 +993,7 @@ fn report_fork_voting_invalid_context() { // different payload than finalized let equivocation_proof = generate_fork_voting_proof( (block_num, payload, set_id, &equivocation_keyring), - MockAncestryProof { is_non_canonical: true }, + MockAncestryProof { is_optimal: true, is_non_canonical: true }, System::finalize(), ); diff --git a/substrate/frame/merkle-mountain-range/src/lib.rs b/substrate/frame/merkle-mountain-range/src/lib.rs index 76d6c2a1ac76f..cc64dfcb7de88 100644 --- a/substrate/frame/merkle-mountain-range/src/lib.rs +++ b/substrate/frame/merkle-mountain-range/src/lib.rs @@ -445,6 +445,12 @@ impl, I: 'static> Pallet { mmr.generate_mock_ancestry_proof() } + pub fn is_ancestry_proof_optimal( + ancestry_proof: &primitives::AncestryProof>, + ) -> bool { + mmr::is_ancestry_proof_optimal::>(ancestry_proof) + } + pub fn verify_ancestry_proof( root: HashOf, ancestry_proof: AncestryProof>, diff --git a/substrate/frame/merkle-mountain-range/src/mmr/mmr.rs b/substrate/frame/merkle-mountain-range/src/mmr/mmr.rs index a9818ba471019..69a08a8b2d6af 100644 --- a/substrate/frame/merkle-mountain-range/src/mmr/mmr.rs +++ b/substrate/frame/merkle-mountain-range/src/mmr/mmr.rs @@ -63,6 +63,18 @@ where .map_err(|e| Error::Verify.log_debug(e)) } +pub fn is_ancestry_proof_optimal(ancestry_proof: &AncestryProof) -> bool +where + H: frame::traits::Hash, +{ + let prev_mmr_size = NodesUtils::new(ancestry_proof.prev_leaf_count).size(); + let mmr_size = NodesUtils::new(ancestry_proof.leaf_count).size(); + + let expected_proof_size = + mmr_lib::ancestry_proof::expected_ancestry_proof_size(prev_mmr_size, mmr_size); + ancestry_proof.items.len() == expected_proof_size +} + pub fn verify_ancestry_proof( root: H::Output, ancestry_proof: AncestryProof, @@ -83,9 +95,9 @@ where ); let raw_ancestry_proof = mmr_lib::AncestryProof::, Hasher> { + prev_mmr_size: mmr_lib::helper::leaf_index_to_mmr_size(ancestry_proof.prev_leaf_count - 1), prev_peaks: ancestry_proof.prev_peaks.into_iter().map(|hash| Node::Hash(hash)).collect(), - prev_size: mmr_lib::helper::leaf_index_to_mmr_size(ancestry_proof.prev_leaf_count - 1), - proof: prev_peaks_proof, + prev_peaks_proof, }; let prev_root = mmr_lib::ancestry_proof::bagging_peaks_hashes::, Hasher>( @@ -248,7 +260,7 @@ where prev_leaf_count, leaf_count: self.leaves, items: raw_ancestry_proof - .proof + .prev_peaks_proof .proof_items() .iter() .map(|(index, item)| (*index, item.hash())) diff --git a/substrate/frame/merkle-mountain-range/src/mmr/mod.rs b/substrate/frame/merkle-mountain-range/src/mmr/mod.rs index 85d00f8a65dee..d3232f23bce1c 100644 --- a/substrate/frame/merkle-mountain-range/src/mmr/mod.rs +++ b/substrate/frame/merkle-mountain-range/src/mmr/mod.rs @@ -18,7 +18,7 @@ mod mmr; pub mod storage; -pub use self::mmr::{verify_ancestry_proof, verify_leaves_proof, Mmr}; +pub use self::mmr::{is_ancestry_proof_optimal, verify_ancestry_proof, verify_leaves_proof, Mmr}; use crate::primitives::{mmr_lib, DataOrHash, FullLeaf}; use frame::traits; diff --git a/substrate/frame/merkle-mountain-range/src/tests.rs b/substrate/frame/merkle-mountain-range/src/tests.rs index ae0c58e91aba4..03b08e51c32ae 100644 --- a/substrate/frame/merkle-mountain-range/src/tests.rs +++ b/substrate/frame/merkle-mountain-range/src/tests.rs @@ -811,6 +811,7 @@ fn generating_and_verifying_ancestry_proofs_works_correctly() { for prev_block_number in 1usize..=500 { let proof = Pallet::::generate_ancestry_proof(prev_block_number as u64, None).unwrap(); + assert!(Pallet::::is_ancestry_proof_optimal(&proof)); assert_eq!( Pallet::::verify_ancestry_proof(root, proof), Ok(prev_roots[prev_block_number - 1]) diff --git a/substrate/primitives/consensus/beefy/src/lib.rs b/substrate/primitives/consensus/beefy/src/lib.rs index e977fb0ea25f6..0f57cdfc81042 100644 --- a/substrate/primitives/consensus/beefy/src/lib.rs +++ b/substrate/primitives/consensus/beefy/src/lib.rs @@ -449,6 +449,9 @@ pub trait AncestryHelper { best_known_block_number: Option, ) -> Option; + /// Check if the proof is optimal. + fn is_proof_optimal(proof: &Self::Proof) -> bool; + /// Extract the validation context from the provided header. fn extract_validation_context(header: Header) -> Option; @@ -463,6 +466,9 @@ pub trait AncestryHelper { /// Weight information for the logic in `AncestryHelper`. pub trait AncestryHelperWeightInfo: AncestryHelper
{ + /// Weight info for the `AncestryHelper::is_proof_optimal()` method. + fn is_proof_optimal(proof: &>::Proof) -> Weight; + /// Weight info for the `AncestryHelper::extract_validation_context()` method. fn extract_validation_context() -> Weight; diff --git a/substrate/primitives/merkle-mountain-range/Cargo.toml b/substrate/primitives/merkle-mountain-range/Cargo.toml index 5f861ca7acf1c..0d8a67da7cad1 100644 --- a/substrate/primitives/merkle-mountain-range/Cargo.toml +++ b/substrate/primitives/merkle-mountain-range/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } log = { workspace = true } -mmr-lib = { package = "polkadot-ckb-merkle-mountain-range", version = "0.7.0", default-features = false } +mmr-lib = { package = "polkadot-ckb-merkle-mountain-range", version = "0.8.1", default-features = false } scale-info = { features = ["derive"], workspace = true } serde = { features = ["alloc", "derive"], optional = true, workspace = true } sp-api = { workspace = true } From 4eb9228840be0abef1c45cf8fa8bc44b5f95200a Mon Sep 17 00:00:00 2001 From: Stephane Gurgenidze <59443568+sw10pa@users.noreply.github.com> Date: Wed, 22 Jan 2025 15:00:50 +0400 Subject: [PATCH 120/169] collation-generation: resolve mismatch between descriptor and commitments core index (#7104) ## Issue [[#7107] Core Index Mismatch in Commitments and Descriptor](https://github.com/paritytech/polkadot-sdk/issues/7107) ## Description This PR resolves a bug where normal (non-malus) undying collators failed to generate and submit collations, resulting in the following error: `ERROR tokio-runtime-worker parachain::collation-generation: Failed to construct and distribute collation: V2 core index check failed: The core index in commitments doesn't match the one in descriptor.` More details about the issue and reproduction steps are described in the [related issue](https://github.com/paritytech/polkadot-sdk/issues/7107). ## Summary of Fix - When core selectors are provided in the UMP signals, core indexes will be chosen using them; - The fix ensures that functionality remains unchanged for parachains not using UMP signals; - Added checks to stop processing if the same core is selected repeatedly. ## TODO - [X] Implement the fix; - [x] Add tests; - [x] Add PRdoc. --- polkadot/node/collation-generation/src/lib.rs | 88 ++++++- .../node/collation-generation/src/tests.rs | 216 ++++++++++++++++-- prdoc/pr_7104.prdoc | 23 ++ 3 files changed, 299 insertions(+), 28 deletions(-) create mode 100644 prdoc/pr_7104.prdoc diff --git a/polkadot/node/collation-generation/src/lib.rs b/polkadot/node/collation-generation/src/lib.rs index b371017a8289a..3c8a216f5f35f 100644 --- a/polkadot/node/collation-generation/src/lib.rs +++ b/polkadot/node/collation-generation/src/lib.rs @@ -53,7 +53,7 @@ use polkadot_primitives::{ node_features::FeatureIndex, vstaging::{ transpose_claim_queue, CandidateDescriptorV2, CandidateReceiptV2 as CandidateReceipt, - CommittedCandidateReceiptV2, TransposedClaimQueue, + ClaimQueueOffset, CommittedCandidateReceiptV2, TransposedClaimQueue, }, CandidateCommitments, CandidateDescriptor, CollatorPair, CoreIndex, Hash, Id as ParaId, NodeFeatures, OccupiedCoreAssumption, PersistedValidationData, SessionIndex, @@ -61,7 +61,7 @@ use polkadot_primitives::{ }; use schnellru::{ByLength, LruMap}; use sp_core::crypto::Pair; -use std::sync::Arc; +use std::{collections::HashSet, sync::Arc}; mod error; @@ -276,13 +276,15 @@ impl CollationGenerationSubsystem { let claim_queue = ClaimQueueSnapshot::from(request_claim_queue(relay_parent, ctx.sender()).await.await??); - let cores_to_build_on = claim_queue - .iter_claims_at_depth(0) - .filter_map(|(core_idx, para_id)| (para_id == config.para_id).then_some(core_idx)) + let assigned_cores = claim_queue + .iter_all_claims() + .filter_map(|(core_idx, para_ids)| { + para_ids.iter().any(|¶_id| para_id == config.para_id).then_some(*core_idx) + }) .collect::>(); - // Nothing to do if no core assigned to us. - if cores_to_build_on.is_empty() { + // Nothing to do if no core is assigned to us at any depth. + if assigned_cores.is_empty() { return Ok(()) } @@ -342,9 +344,13 @@ impl CollationGenerationSubsystem { ctx.spawn( "chained-collation-builder", Box::pin(async move { - let transposed_claim_queue = transpose_claim_queue(claim_queue.0); + let transposed_claim_queue = transpose_claim_queue(claim_queue.0.clone()); - for core_index in cores_to_build_on { + // Track used core indexes not to submit collations on the same core. + let mut used_cores = HashSet::new(); + + for i in 0..assigned_cores.len() { + // Get the collation. let collator_fn = match task_config.collator.as_ref() { Some(x) => x, None => return, @@ -363,6 +369,68 @@ impl CollationGenerationSubsystem { }, }; + // Use the core_selector method from CandidateCommitments to extract + // CoreSelector and ClaimQueueOffset. + let mut commitments = CandidateCommitments::default(); + commitments.upward_messages = collation.upward_messages.clone(); + + let (cs_index, cq_offset) = match commitments.core_selector() { + // Use the CoreSelector's index if provided. + Ok(Some((sel, off))) => (sel.0 as usize, off), + // Fallback to the sequential index if no CoreSelector is provided. + Ok(None) => (i, ClaimQueueOffset(0)), + Err(err) => { + gum::debug!( + target: LOG_TARGET, + ?para_id, + "error processing UMP signals: {}", + err + ); + return + }, + }; + + // Identify the cores to build collations on using the given claim queue offset. + let cores_to_build_on = claim_queue + .iter_claims_at_depth(cq_offset.0 as usize) + .filter_map(|(core_idx, para_id)| { + (para_id == task_config.para_id).then_some(core_idx) + }) + .collect::>(); + + if cores_to_build_on.is_empty() { + gum::debug!( + target: LOG_TARGET, + ?para_id, + "no core is assigned to para at depth {}", + cq_offset.0, + ); + return + } + + let descriptor_core_index = + cores_to_build_on[cs_index % cores_to_build_on.len()]; + + // Ensure the core index has not been used before. + if used_cores.contains(&descriptor_core_index.0) { + gum::warn!( + target: LOG_TARGET, + ?para_id, + "parachain repeatedly selected the same core index: {}", + descriptor_core_index.0, + ); + return + } + + used_cores.insert(descriptor_core_index.0); + gum::trace!( + target: LOG_TARGET, + ?para_id, + "selected core index: {}", + descriptor_core_index.0, + ); + + // Distribute the collation. let parent_head = collation.head_data.clone(); if let Err(err) = construct_and_distribute_receipt( PreparedCollation { @@ -372,7 +440,7 @@ impl CollationGenerationSubsystem { validation_data: validation_data.clone(), validation_code_hash, n_validators, - core_index, + core_index: descriptor_core_index, session_index, }, task_config.key.clone(), diff --git a/polkadot/node/collation-generation/src/tests.rs b/polkadot/node/collation-generation/src/tests.rs index f81c14cdf8f95..dc1d7b3489c11 100644 --- a/polkadot/node/collation-generation/src/tests.rs +++ b/polkadot/node/collation-generation/src/tests.rs @@ -16,11 +16,10 @@ use super::*; use assert_matches::assert_matches; -use futures::{ - task::{Context as FuturesContext, Poll}, - Future, StreamExt, +use futures::{self, Future, StreamExt}; +use polkadot_node_primitives::{ + BlockData, Collation, CollationResult, CollatorFn, MaybeCompressedPoV, PoV, }; -use polkadot_node_primitives::{BlockData, Collation, CollationResult, MaybeCompressedPoV, PoV}; use polkadot_node_subsystem::{ messages::{AllMessages, RuntimeApiMessage, RuntimeApiRequest}, ActivatedLeaf, @@ -28,14 +27,16 @@ use polkadot_node_subsystem::{ use polkadot_node_subsystem_test_helpers::TestSubsystemContextHandle; use polkadot_node_subsystem_util::TimeoutExt; use polkadot_primitives::{ - node_features, vstaging::CandidateDescriptorVersion, CollatorPair, PersistedValidationData, + node_features, + vstaging::{CandidateDescriptorVersion, CoreSelector, UMPSignal, UMP_SEPARATOR}, + CollatorPair, PersistedValidationData, }; use polkadot_primitives_test_helpers::dummy_head_data; use rstest::rstest; use sp_keyring::sr25519::Keyring as Sr25519Keyring; use std::{ collections::{BTreeMap, VecDeque}, - pin::Pin, + sync::Mutex, }; type VirtualOverseer = TestSubsystemContextHandle; @@ -79,17 +80,64 @@ fn test_collation() -> Collation { } } -struct TestCollator; +struct CoreSelectorData { + // The core selector index. + index: u8, + // The increment value for the core selector index. Normally 1, but can be set to 0 or another + // value for testing scenarios where a parachain repeatedly selects the same core index. + increment_index_by: u8, + // The claim queue offset. + cq_offset: u8, +} + +impl CoreSelectorData { + fn new(index: u8, increment_index_by: u8, cq_offset: u8) -> Self { + Self { index, increment_index_by, cq_offset } + } +} -impl Future for TestCollator { - type Output = Option; +struct State { + core_selector_data: Option, +} - fn poll(self: Pin<&mut Self>, _cx: &mut FuturesContext) -> Poll { - Poll::Ready(Some(CollationResult { collation: test_collation(), result_sender: None })) +impl State { + fn new(core_selector_data: Option) -> Self { + Self { core_selector_data } } } -impl Unpin for TestCollator {} +struct TestCollator { + state: Arc>, +} + +impl TestCollator { + fn new(core_selector_data: Option) -> Self { + Self { state: Arc::new(Mutex::new(State::new(core_selector_data))) } + } + + pub fn create_collation_function(&self) -> CollatorFn { + let state = Arc::clone(&self.state); + + Box::new(move |_relay_parent: Hash, _validation_data: &PersistedValidationData| { + let mut collation = test_collation(); + let mut state_guard = state.lock().unwrap(); + + if let Some(core_selector_data) = &mut state_guard.core_selector_data { + collation.upward_messages.force_push(UMP_SEPARATOR); + collation.upward_messages.force_push( + UMPSignal::SelectCore( + CoreSelector(core_selector_data.index), + ClaimQueueOffset(core_selector_data.cq_offset), + ) + .encode(), + ); + core_selector_data.index += core_selector_data.increment_index_by; + } + + async move { Some(CollationResult { collation, result_sender: None }) }.boxed() + }) + } +} const TIMEOUT: std::time::Duration = std::time::Duration::from_millis(2000); @@ -101,10 +149,14 @@ async fn overseer_recv(overseer: &mut VirtualOverseer) -> AllMessages { .expect(&format!("{:?} is long enough to receive messages", TIMEOUT)) } -fn test_config>(para_id: Id) -> CollationGenerationConfig { +fn test_config>( + para_id: Id, + core_selector_data: Option, +) -> CollationGenerationConfig { + let test_collator = TestCollator::new(core_selector_data); CollationGenerationConfig { key: CollatorPair::generate().0, - collator: Some(Box::new(|_: Hash, _vd: &PersistedValidationData| TestCollator.boxed())), + collator: Some(test_collator.create_collation_function()), para_id: para_id.into(), } } @@ -219,7 +271,7 @@ fn distribute_collation_only_for_assigned_para_id_at_offset_0() { .collect::>(); test_harness(|mut virtual_overseer| async move { - helpers::initialize_collator(&mut virtual_overseer, para_id).await; + helpers::initialize_collator(&mut virtual_overseer, para_id, None).await; helpers::activate_new_head(&mut virtual_overseer, activated_hash).await; helpers::handle_runtime_calls_on_new_head_activation( &mut virtual_overseer, @@ -259,7 +311,7 @@ fn distribute_collation_with_elastic_scaling(#[case] total_cores: u32) { .collect::>(); test_harness(|mut virtual_overseer| async move { - helpers::initialize_collator(&mut virtual_overseer, para_id).await; + helpers::initialize_collator(&mut virtual_overseer, para_id, None).await; helpers::activate_new_head(&mut virtual_overseer, activated_hash).await; helpers::handle_runtime_calls_on_new_head_activation( &mut virtual_overseer, @@ -281,6 +333,127 @@ fn distribute_collation_with_elastic_scaling(#[case] total_cores: u32) { }); } +// Tests when submission core indexes need to be selected using the core selectors provided in the +// UMP signals. The core selector index is an increasing number that can start with a non-negative +// value (even greater than the core index), but the collation generation protocol uses the +// remainder to select the core. UMP signals may also contain a claim queue offset, based on which +// we need to select the assigned core indexes for the para from that offset in the claim queue. +#[rstest] +#[case(0, 0, 0, false)] +#[case(1, 0, 0, true)] +#[case(1, 5, 0, false)] +#[case(2, 0, 1, true)] +#[case(4, 2, 2, false)] +fn distribute_collation_with_core_selectors( + #[case] total_cores: u32, + // The core selector index that will be obtained from the first collation. + #[case] init_cs_index: u8, + // Claim queue offset where the assigned cores will be stored. + #[case] cq_offset: u8, + // Enables v2 receipts feature, affecting core selector and claim queue handling. + #[case] v2_receipts: bool, +) { + let activated_hash: Hash = [1; 32].into(); + let para_id = ParaId::from(5); + let other_para_id = ParaId::from(10); + let node_features = + if v2_receipts { node_features_with_v2_enabled() } else { NodeFeatures::EMPTY }; + + let claim_queue = (0..total_cores) + .into_iter() + .map(|idx| { + // Set all cores assigned to para_id 5 at the cq_offset depth. + let mut vec = VecDeque::from(vec![other_para_id; cq_offset as usize]); + vec.push_back(para_id); + (CoreIndex(idx), vec) + }) + .collect::>(); + + test_harness(|mut virtual_overseer| async move { + helpers::initialize_collator( + &mut virtual_overseer, + para_id, + Some(CoreSelectorData::new(init_cs_index, 1, cq_offset)), + ) + .await; + helpers::activate_new_head(&mut virtual_overseer, activated_hash).await; + helpers::handle_runtime_calls_on_new_head_activation( + &mut virtual_overseer, + activated_hash, + claim_queue, + node_features, + ) + .await; + + let mut cores_assigned = (0..total_cores).collect::>(); + if total_cores > 1 && init_cs_index > 0 { + // We need to rotate the list of cores because the first core selector index was + // non-zero, which should change the sequence of submissions. However, collations should + // still be submitted on all cores. + cores_assigned.rotate_left((init_cs_index as u32 % total_cores) as usize); + } + helpers::handle_cores_processing_for_a_leaf( + &mut virtual_overseer, + activated_hash, + para_id, + cores_assigned, + ) + .await; + + virtual_overseer + }); +} + +// Tests the behavior when a parachain repeatedly selects the same core index. +// Ensures that the system handles this behavior correctly while maintaining expected functionality. +#[rstest] +#[case(3, 0, vec![0])] +#[case(3, 1, vec![0, 1, 2])] +#[case(3, 2, vec![0, 2, 1])] +#[case(3, 3, vec![0])] +#[case(3, 4, vec![0, 1, 2])] +fn distribute_collation_with_repeated_core_selector_index( + #[case] total_cores: u32, + #[case] increment_cs_index_by: u8, + #[case] expected_selected_cores: Vec, +) { + let activated_hash: Hash = [1; 32].into(); + let para_id = ParaId::from(5); + let node_features = node_features_with_v2_enabled(); + + let claim_queue = (0..total_cores) + .into_iter() + .map(|idx| (CoreIndex(idx), VecDeque::from([para_id]))) + .collect::>(); + + test_harness(|mut virtual_overseer| async move { + helpers::initialize_collator( + &mut virtual_overseer, + para_id, + Some(CoreSelectorData::new(0, increment_cs_index_by, 0)), + ) + .await; + helpers::activate_new_head(&mut virtual_overseer, activated_hash).await; + helpers::handle_runtime_calls_on_new_head_activation( + &mut virtual_overseer, + activated_hash, + claim_queue, + node_features, + ) + .await; + + helpers::handle_cores_processing_for_a_leaf( + &mut virtual_overseer, + activated_hash, + para_id, + expected_selected_cores, + ) + .await; + + virtual_overseer + }); +} + #[rstest] #[case(true)] #[case(false)] @@ -405,10 +578,17 @@ mod helpers { use std::collections::{BTreeMap, VecDeque}; // Sends `Initialize` with a collator config - pub async fn initialize_collator(virtual_overseer: &mut VirtualOverseer, para_id: ParaId) { + pub async fn initialize_collator( + virtual_overseer: &mut VirtualOverseer, + para_id: ParaId, + core_selector_data: Option, + ) { virtual_overseer .send(FromOrchestra::Communication { - msg: CollationGenerationMessage::Initialize(test_config(para_id)), + msg: CollationGenerationMessage::Initialize(test_config( + para_id, + core_selector_data, + )), }) .await; } diff --git a/prdoc/pr_7104.prdoc b/prdoc/pr_7104.prdoc new file mode 100644 index 0000000000000..bd05e2b60e1ff --- /dev/null +++ b/prdoc/pr_7104.prdoc @@ -0,0 +1,23 @@ +title: "collation-generation: resolve mismatch between descriptor and commitments core index" + +doc: + - audience: Node Dev + description: | + This PR resolves a bug where collators failed to generate and submit collations, + resulting in the following error: + + ``` + ERROR tokio-runtime-worker parachain::collation-generation: Failed to construct and + distribute collation: V2 core index check failed: The core index in commitments doesn't + match the one in descriptor. + ``` + + This issue affects only legacy and test collators that still use the collation function. + It is not a problem for lookahead or slot-based collators. + + This fix ensures the descriptor core index contains the value determined by the core + selector UMP signal when the parachain is using RFC103. + +crates: + - name: polkadot-node-collation-generation + bump: patch From 350a6c4ccc4c2f376b9f5ed259daf3a56d5fed56 Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Wed, 22 Jan 2025 14:02:01 +0200 Subject: [PATCH 121/169] Fix bridge tests image (#7292) Fix bridge tests image --- docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile b/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile index b1f4bffc772ab..f9879fea20822 100644 --- a/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile +++ b/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile @@ -48,8 +48,9 @@ RUN set -eux; \ cd /home/nonroot/bridges-polkadot-sdk/bridges/testing/framework/utils/generate_hex_encoded_call; \ npm install +# use the non-root user +USER node # check if executable works in this container -USER nonroot RUN /usr/local/bin/polkadot --version RUN /usr/local/bin/polkadot-parachain --version RUN /usr/local/bin/substrate-relay --version From 634a17b6f67c71e589f921b0ddd4c23bbed883f1 Mon Sep 17 00:00:00 2001 From: Mrisho Lukamba <69342343+MrishoLukamba@users.noreply.github.com> Date: Wed, 22 Jan 2025 18:06:18 +0300 Subject: [PATCH 122/169] Unify Import verifier usage across parachain template and omninode (#7195) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #7055 @skunert @bkchr --------- Co-authored-by: Bastian Köcher Co-authored-by: command-bot <> Co-authored-by: Sebastian Kunert --- .../aura/src/equivocation_import_queue.rs | 31 +++++++++- .../polkadot-omni-node/lib/src/nodes/aura.rs | 56 +++++++------------ prdoc/pr_7195.prdoc | 7 +++ 3 files changed, 56 insertions(+), 38 deletions(-) create mode 100644 prdoc/pr_7195.prdoc diff --git a/cumulus/client/consensus/aura/src/equivocation_import_queue.rs b/cumulus/client/consensus/aura/src/equivocation_import_queue.rs index dbd9d5ba6a6f9..a3bc90f53c25d 100644 --- a/cumulus/client/consensus/aura/src/equivocation_import_queue.rs +++ b/cumulus/client/consensus/aura/src/equivocation_import_queue.rs @@ -68,7 +68,8 @@ impl NaiveEquivocationDefender { } } -struct Verifier { +/// A parachain block import verifier that checks for equivocation limits within each slot. +pub struct Verifier { client: Arc, create_inherent_data_providers: CIDP, defender: Mutex, @@ -76,6 +77,34 @@ struct Verifier { _phantom: std::marker::PhantomData (Block, P)>, } +impl Verifier +where + P: Pair, + P::Signature: Codec, + P::Public: Codec + Debug, + Block: BlockT, + Client: ProvideRuntimeApi + Send + Sync, + >::Api: BlockBuilderApi + AuraApi, + + CIDP: CreateInherentDataProviders, +{ + /// Creates a new Verifier instance for handling parachain block import verification in Aura + /// consensus. + pub fn new( + client: Arc, + inherent_data_provider: CIDP, + telemetry: Option, + ) -> Self { + Self { + client, + create_inherent_data_providers: inherent_data_provider, + defender: Mutex::new(NaiveEquivocationDefender::default()), + telemetry, + _phantom: std::marker::PhantomData, + } + } +} + #[async_trait::async_trait] impl VerifierT for Verifier where diff --git a/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs b/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs index 816f76117a263..cd0e35d0d0699 100644 --- a/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs +++ b/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs @@ -37,9 +37,12 @@ use cumulus_client_collator::service::{ use cumulus_client_consensus_aura::collators::slot_based::{ self as slot_based, Params as SlotBasedParams, }; -use cumulus_client_consensus_aura::collators::{ - lookahead::{self as aura, Params as AuraParams}, - slot_based::{SlotBasedBlockImport, SlotBasedBlockImportHandle}, +use cumulus_client_consensus_aura::{ + collators::{ + lookahead::{self as aura, Params as AuraParams}, + slot_based::{SlotBasedBlockImport, SlotBasedBlockImportHandle}, + }, + equivocation_import_queue::Verifier as EquivocationVerifier, }; use cumulus_client_consensus_proposer::{Proposer, ProposerInterface}; use cumulus_client_consensus_relay_chain::Verifier as RelayChainVerifier; @@ -118,49 +121,28 @@ where telemetry_handle: Option, task_manager: &TaskManager, ) -> sc_service::error::Result> { - let verifier_client = client.clone(); - - let aura_verifier = cumulus_client_consensus_aura::build_verifier::< - ::Pair, - _, - _, - _, - >(cumulus_client_consensus_aura::BuildVerifierParams { - client: verifier_client.clone(), - create_inherent_data_providers: move |parent_hash, _| { - let cidp_client = verifier_client.clone(); - async move { - let slot_duration = cumulus_client_consensus_aura::slot_duration_at( - &*cidp_client, - parent_hash, - )?; - let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); - - let slot = - sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_slot_duration( - *timestamp, - slot_duration, - ); - - Ok((slot, timestamp)) - } - }, - telemetry: telemetry_handle, - }); + let inherent_data_providers = + move |_, _| async move { Ok(sp_timestamp::InherentDataProvider::from_system_time()) }; + let registry = config.prometheus_registry(); + let spawner = task_manager.spawn_essential_handle(); let relay_chain_verifier = Box::new(RelayChainVerifier::new(client.clone(), |_, _| async { Ok(()) })); + let equivocation_aura_verifier = + EquivocationVerifier::<::Pair, _, _, _>::new( + client.clone(), + inherent_data_providers, + telemetry_handle, + ); + let verifier = Verifier { client, + aura_verifier: Box::new(equivocation_aura_verifier), relay_chain_verifier, - aura_verifier: Box::new(aura_verifier), - _phantom: PhantomData, + _phantom: Default::default(), }; - let registry = config.prometheus_registry(); - let spawner = task_manager.spawn_essential_handle(); - Ok(BasicQueue::new(verifier, Box::new(block_import), None, &spawner, registry)) } } diff --git a/prdoc/pr_7195.prdoc b/prdoc/pr_7195.prdoc new file mode 100644 index 0000000000000..db4f877b156ad --- /dev/null +++ b/prdoc/pr_7195.prdoc @@ -0,0 +1,7 @@ +title: Unify Import verifier usage across parachain template and omninode +doc: +- audience: Node Dev + description: |- + In polkadot-omni-node block import pipeline it uses default aura verifier without checking equivocation, + This Pr replaces the check with full verification with equivocation like in parachain template block import +crates: [] From fd64a1e7768ba6e8676cbbf25c4e821a901c0a7f Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Wed, 22 Jan 2025 18:51:59 +0200 Subject: [PATCH 123/169] net/libp2p: Enforce outbound request-response timeout limits (#7222) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR enforces that outbound requests are finished within the specified protocol timeout. The stable2412 version running libp2p 0.52.4 contains a bug which does not track request timeouts properly: - https://github.com/libp2p/rust-libp2p/pull/5429 The issue has been detected while submitting libp2p -> litep2p requests in kusama. This aims to check that pending outbound requests have not timedout. Although the issue has been fixed in libp2p, there might be other cases where this may happen. For example: - https://github.com/libp2p/rust-libp2p/pull/5417 For more context see: https://github.com/paritytech/polkadot-sdk/issues/7076#issuecomment-2596085096 1. Ideally, the force-timeout mechanism in this PR should never be triggered in production. However, origin/stable2412 occasionally encounters this issue. When this happens, 2 warnings may be generated: - one warning introduced by this PR wrt force timeout terminating the request - possible one warning when the libp2p decides (if at all) to provide the response back to substrate (as mentioned by @alexggh [here](https://github.com/paritytech/polkadot-sdk/pull/7222/files#diff-052aeaf79fef3d9a18c2cfd67006aa306b8d52e848509d9077a6a0f2eb856af7L769) and [here](https://github.com/paritytech/polkadot-sdk/pull/7222/files#diff-052aeaf79fef3d9a18c2cfd67006aa306b8d52e848509d9077a6a0f2eb856af7L842) 2. This implementation does not propagate to the substrate service the `RequestFinished { error: .. }`. That event is only used internally by substrate to increment metrics. However, we don't have the peer information available to propagate the event properly when we force-timeout the request. Considering this should most likely not happen in production (origin/master) and that we'll be able to extract information by warnings, I would say this is a good tradeoff for code simplicity: https://github.com/paritytech/polkadot-sdk/blob/06e3b5c6a7696048d65f1b8729f16b379a16f501/substrate/client/network/src/service.rs#L1543 ### Testing Added a new test to ensure the timeout is reached properly, even if libp2p does not produce a response in due time. I've also transitioned the tests to using `tokio::test` due to a limitation of [CI](https://github.com/paritytech/polkadot-sdk/actions/runs/12832055737/job/35784043867) ``` --- TRY 1 STDERR: sc-network request_responses::tests::max_response_size_exceeded --- thread 'request_responses::tests::max_response_size_exceeded' panicked at /usr/local/cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.40.0/src/time/interval.rs:139:26: there is no reactor running, must be called from the context of a Tokio 1.x runtime ``` cc @paritytech/networking --------- Signed-off-by: Alexandru Vasile Co-authored-by: Bastian Köcher --- prdoc/pr_7222.prdoc | 19 + .../client/network/src/request_responses.rs | 1020 ++++++++++------- 2 files changed, 612 insertions(+), 427 deletions(-) create mode 100644 prdoc/pr_7222.prdoc diff --git a/prdoc/pr_7222.prdoc b/prdoc/pr_7222.prdoc new file mode 100644 index 0000000000000..40b89b0a1820d --- /dev/null +++ b/prdoc/pr_7222.prdoc @@ -0,0 +1,19 @@ +title: Enforce libp2p outbound request-response timeout limits + +doc: + - audience: Node Dev + description: | + This PR enforces that outbound requests are finished within the specified protocol timeout. + The stable2412 version running libp2p 0.52.4 contains a bug which does not track request timeouts properly + https://github.com/libp2p/rust-libp2p/pull/5429. + + The issue has been detected while submitting libp2p to litep2p requests in Kusama. + This aims to check that pending outbound requests have not timed out. + Although the issue has been fixed in libp2p, there might be other cases where this may happen. + For example, https://github.com/libp2p/rust-libp2p/pull/5417. + + For more context see https://github.com/paritytech/polkadot-sdk/issues/7076#issuecomment-2596085096. + +crates: +- name: sc-network + bump: patch diff --git a/substrate/client/network/src/request_responses.rs b/substrate/client/network/src/request_responses.rs index 5fe34c7813786..e21773632ed77 100644 --- a/substrate/client/network/src/request_responses.rs +++ b/substrate/client/network/src/request_responses.rs @@ -64,6 +64,9 @@ use std::{ pub use libp2p::request_response::{Config, InboundRequestId, OutboundRequestId}; +/// Periodically check if requests are taking too long. +const PERIODIC_REQUEST_CHECK: Duration = Duration::from_secs(2); + /// Possible failures occurring in the context of sending an outbound request and receiving the /// response. #[derive(Debug, Clone, thiserror::Error)] @@ -251,8 +254,14 @@ pub struct OutgoingResponse { /// Information stored about a pending request. struct PendingRequest { + /// The time when the request was sent to the libp2p request-response protocol. started_at: Instant, - response_tx: oneshot::Sender, ProtocolName), RequestFailure>>, + /// The channel to send the response back to the caller. + /// + /// This is wrapped in an `Option` to allow for the channel to be taken out + /// on force-detected timeouts. + response_tx: Option, ProtocolName), RequestFailure>>>, + /// Fallback request to send if the primary request fails. fallback_request: Option<(Vec, ProtocolName)>, } @@ -336,16 +345,20 @@ impl From<(ProtocolName, RequestId)> for ProtocolRequestId } } +/// Details of a request-response protocol. +struct ProtocolDetails { + behaviour: Behaviour, + inbound_queue: Option>, + request_timeout: Duration, +} + /// Implementation of `NetworkBehaviour` that provides support for request-response protocols. pub struct RequestResponsesBehaviour { /// The multiple sub-protocols, by name. /// /// Contains the underlying libp2p request-response [`Behaviour`], plus an optional /// "response builder" used to build responses for incoming requests. - protocols: HashMap< - ProtocolName, - (Behaviour, Option>), - >, + protocols: HashMap, /// Pending requests, passed down to a request-response [`Behaviour`], awaiting a reply. pending_requests: HashMap, PendingRequest>, @@ -365,6 +378,14 @@ pub struct RequestResponsesBehaviour { /// Primarily used to get a reputation of a node. peer_store: Arc, + + /// Interval to check that the requests are not taking too long. + /// + /// We had issues in the past where libp2p did not produce a timeout event in due time. + /// + /// For more details, see: + /// - + periodic_request_check: tokio::time::Interval, } /// Generated by the response builder and waiting to be processed. @@ -393,7 +414,7 @@ impl RequestResponsesBehaviour { ProtocolSupport::Outbound }; - let rq_rp = Behaviour::with_codec( + let behaviour = Behaviour::with_codec( GenericCodec { max_request_size: protocol.max_request_size, max_response_size: protocol.max_response_size, @@ -405,7 +426,11 @@ impl RequestResponsesBehaviour { ); match protocols.entry(protocol.name) { - Entry::Vacant(e) => e.insert((rq_rp, protocol.inbound_queue)), + Entry::Vacant(e) => e.insert(ProtocolDetails { + behaviour, + inbound_queue: protocol.inbound_queue, + request_timeout: protocol.request_timeout, + }), Entry::Occupied(e) => return Err(RegisterError::DuplicateProtocol(e.key().clone())), }; } @@ -417,6 +442,7 @@ impl RequestResponsesBehaviour { pending_responses_arrival_time: Default::default(), send_feedback: Default::default(), peer_store, + periodic_request_check: tokio::time::interval(PERIODIC_REQUEST_CHECK), }) } @@ -437,9 +463,11 @@ impl RequestResponsesBehaviour { ) { log::trace!(target: "sub-libp2p", "send request to {target} ({protocol_name:?}), {} bytes", request.len()); - if let Some((protocol, _)) = self.protocols.get_mut(protocol_name.deref()) { + if let Some(ProtocolDetails { behaviour, .. }) = + self.protocols.get_mut(protocol_name.deref()) + { Self::send_request_inner( - protocol, + behaviour, &mut self.pending_requests, target, protocol_name, @@ -474,7 +502,7 @@ impl RequestResponsesBehaviour { (protocol_name.to_string().into(), request_id).into(), PendingRequest { started_at: Instant::now(), - response_tx: pending_response, + response_tx: Some(pending_response), fallback_request, }, ); @@ -521,18 +549,19 @@ impl NetworkBehaviour for RequestResponsesBehaviour { local_addr: &Multiaddr, remote_addr: &Multiaddr, ) -> Result, ConnectionDenied> { - let iter = self.protocols.iter_mut().filter_map(|(p, (r, _))| { - if let Ok(handler) = r.handle_established_inbound_connection( - connection_id, - peer, - local_addr, - remote_addr, - ) { - Some((p.to_string(), handler)) - } else { - None - } - }); + let iter = + self.protocols.iter_mut().filter_map(|(p, ProtocolDetails { behaviour, .. })| { + if let Ok(handler) = behaviour.handle_established_inbound_connection( + connection_id, + peer, + local_addr, + remote_addr, + ) { + Some((p.to_string(), handler)) + } else { + None + } + }); Ok(MultiHandler::try_from_iter(iter).expect( "Protocols are in a HashMap and there can be at most one handler per protocol name, \ @@ -548,19 +577,20 @@ impl NetworkBehaviour for RequestResponsesBehaviour { role_override: Endpoint, port_use: PortUse, ) -> Result, ConnectionDenied> { - let iter = self.protocols.iter_mut().filter_map(|(p, (r, _))| { - if let Ok(handler) = r.handle_established_outbound_connection( - connection_id, - peer, - addr, - role_override, - port_use, - ) { - Some((p.to_string(), handler)) - } else { - None - } - }); + let iter = + self.protocols.iter_mut().filter_map(|(p, ProtocolDetails { behaviour, .. })| { + if let Ok(handler) = behaviour.handle_established_outbound_connection( + connection_id, + peer, + addr, + role_override, + port_use, + ) { + Some((p.to_string(), handler)) + } else { + None + } + }); Ok(MultiHandler::try_from_iter(iter).expect( "Protocols are in a HashMap and there can be at most one handler per protocol name, \ @@ -569,8 +599,8 @@ impl NetworkBehaviour for RequestResponsesBehaviour { } fn on_swarm_event(&mut self, event: FromSwarm) { - for (protocol, _) in self.protocols.values_mut() { - protocol.on_swarm_event(event); + for ProtocolDetails { behaviour, .. } in self.protocols.values_mut() { + behaviour.on_swarm_event(event); } } @@ -581,8 +611,8 @@ impl NetworkBehaviour for RequestResponsesBehaviour { event: THandlerOutEvent, ) { let p_name = event.0; - if let Some((proto, _)) = self.protocols.get_mut(p_name.as_str()) { - return proto.on_connection_handler_event(peer_id, connection_id, event.1) + if let Some(ProtocolDetails { behaviour, .. }) = self.protocols.get_mut(p_name.as_str()) { + return behaviour.on_connection_handler_event(peer_id, connection_id, event.1) } else { log::warn!( target: "sub-libp2p", @@ -594,6 +624,51 @@ impl NetworkBehaviour for RequestResponsesBehaviour { fn poll(&mut self, cx: &mut Context) -> Poll>> { 'poll_all: loop { + // Poll the periodic request check. + if self.periodic_request_check.poll_tick(cx).is_ready() { + self.pending_requests.retain(|id, req| { + let Some(ProtocolDetails { request_timeout, .. }) = + self.protocols.get(&id.protocol) + else { + log::warn!( + target: "sub-libp2p", + "Request {id:?} has no protocol registered.", + ); + + if let Some(response_tx) = req.response_tx.take() { + if response_tx.send(Err(RequestFailure::UnknownProtocol)).is_err() { + log::debug!( + target: "sub-libp2p", + "Request {id:?} has no protocol registered. At the same time local node is no longer interested in the result.", + ); + } + } + return false + }; + + let elapsed = req.started_at.elapsed(); + if elapsed > *request_timeout { + log::debug!( + target: "sub-libp2p", + "Request {id:?} force detected as timeout.", + ); + + if let Some(response_tx) = req.response_tx.take() { + if response_tx.send(Err(RequestFailure::Network(OutboundFailure::Timeout))).is_err() { + log::debug!( + target: "sub-libp2p", + "Request {id:?} force detected as timeout. At the same time local node is no longer interested in the result.", + ); + } + } + + false + } else { + true + } + }); + } + // Poll to see if any response is ready to be sent back. while let Poll::Ready(Some(outcome)) = self.pending_responses.poll_next_unpin(cx) { let RequestProcessingOutcome { @@ -610,10 +685,12 @@ impl NetworkBehaviour for RequestResponsesBehaviour { }; if let Ok(payload) = result { - if let Some((protocol, _)) = self.protocols.get_mut(&*protocol_name) { + if let Some(ProtocolDetails { behaviour, .. }) = + self.protocols.get_mut(&*protocol_name) + { log::trace!(target: "sub-libp2p", "send response to {peer} ({protocol_name:?}), {} bytes", payload.len()); - if protocol.send_response(inner_channel, Ok(payload)).is_err() { + if behaviour.send_response(inner_channel, Ok(payload)).is_err() { // Note: Failure is handled further below when receiving // `InboundFailure` event from request-response [`Behaviour`]. log::debug!( @@ -641,7 +718,8 @@ impl NetworkBehaviour for RequestResponsesBehaviour { let mut fallback_requests = vec![]; // Poll request-responses protocols. - for (protocol, (ref mut behaviour, ref mut resp_builder)) in &mut self.protocols { + for (protocol, ProtocolDetails { behaviour, inbound_queue, .. }) in &mut self.protocols + { 'poll_protocol: while let Poll::Ready(ev) = behaviour.poll(cx) { let ev = match ev { // Main events we are interested in. @@ -696,7 +774,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { // Submit the request to the "response builder" passed by the user at // initialization. - if let Some(resp_builder) = resp_builder { + if let Some(resp_builder) = inbound_queue { // If the response builder is too busy, silently drop `tx`. This // will be reported by the corresponding request-response // [`Behaviour`] through an `InboundFailure::Omission` event. @@ -744,7 +822,11 @@ impl NetworkBehaviour for RequestResponsesBehaviour { .pending_requests .remove(&(protocol.clone(), request_id).into()) { - Some(PendingRequest { started_at, response_tx, .. }) => { + Some(PendingRequest { + started_at, + response_tx: Some(response_tx), + .. + }) => { log::trace!( target: "sub-libp2p", "received response from {peer} ({protocol:?}), {} bytes", @@ -760,13 +842,13 @@ impl NetworkBehaviour for RequestResponsesBehaviour { .map_err(|_| RequestFailure::Obsolete); (started_at, delivered) }, - None => { - log::warn!( + _ => { + log::debug!( target: "sub-libp2p", - "Received `RequestResponseEvent::Message` with unexpected request id {:?}", + "Received `RequestResponseEvent::Message` with unexpected request id {:?} from {:?}", request_id, + peer, ); - debug_assert!(false); continue }, }; @@ -795,7 +877,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { { Some(PendingRequest { started_at, - response_tx, + response_tx: Some(response_tx), fallback_request, }) => { // Try using the fallback request if the protocol was not @@ -833,13 +915,14 @@ impl NetworkBehaviour for RequestResponsesBehaviour { } started_at }, - None => { - log::warn!( + _ => { + log::debug!( target: "sub-libp2p", - "Received `RequestResponseEvent::Message` with unexpected request id {:?}", + "Received `RequestResponseEvent::OutboundFailure` with unexpected request id {:?} error {:?} from {:?}", request_id, + error, + peer ); - debug_assert!(false); continue }, }; @@ -904,7 +987,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { // Send out fallback requests. for (peer, protocol, request, pending_response) in fallback_requests.drain(..) { - if let Some((behaviour, _)) = self.protocols.get_mut(&protocol) { + if let Some(ProtocolDetails { behaviour, .. }) = self.protocols.get_mut(&protocol) { Self::send_request_inner( behaviour, &mut self.pending_requests, @@ -1073,7 +1156,7 @@ mod tests { use crate::mock::MockPeerStore; use assert_matches::assert_matches; - use futures::{channel::oneshot, executor::LocalPool, task::Spawn}; + use futures::channel::oneshot; use libp2p::{ core::{ transport::{MemoryTransport, Transport}, @@ -1086,10 +1169,10 @@ mod tests { }; use std::{iter, time::Duration}; - struct TokioExecutor(tokio::runtime::Runtime); + struct TokioExecutor; impl Executor for TokioExecutor { fn exec(&self, f: Pin + Send>>) { - let _ = self.0.spawn(f); + tokio::spawn(f); } } @@ -1106,13 +1189,11 @@ mod tests { let behaviour = RequestResponsesBehaviour::new(list, Arc::new(MockPeerStore {})).unwrap(); - let runtime = tokio::runtime::Runtime::new().unwrap(); - let mut swarm = Swarm::new( transport, behaviour, keypair.public().to_peer_id(), - SwarmConfig::with_executor(TokioExecutor(runtime)) + SwarmConfig::with_executor(TokioExecutor {}) // This is taken care of by notification protocols in non-test environment // It is very slow in test environment for some reason, hence larger timeout .with_idle_connection_timeout(Duration::from_secs(10)), @@ -1125,34 +1206,27 @@ mod tests { (swarm, listen_addr) } - #[test] - fn basic_request_response_works() { + #[tokio::test] + async fn basic_request_response_works() { let protocol_name = ProtocolName::from("/test/req-resp/1"); - let mut pool = LocalPool::new(); // Build swarms whose behaviour is [`RequestResponsesBehaviour`]. let mut swarms = (0..2) .map(|_| { let (tx, mut rx) = async_channel::bounded::(64); - pool.spawner() - .spawn_obj( - async move { - while let Some(rq) = rx.next().await { - let (fb_tx, fb_rx) = oneshot::channel(); - assert_eq!(rq.payload, b"this is a request"); - let _ = rq.pending_response.send(super::OutgoingResponse { - result: Ok(b"this is a response".to_vec()), - reputation_changes: Vec::new(), - sent_feedback: Some(fb_tx), - }); - fb_rx.await.unwrap(); - } - } - .boxed() - .into(), - ) - .unwrap(); + tokio::spawn(async move { + while let Some(rq) = rx.next().await { + let (fb_tx, fb_rx) = oneshot::channel(); + assert_eq!(rq.payload, b"this is a request"); + let _ = rq.pending_response.send(super::OutgoingResponse { + result: Ok(b"this is a response".to_vec()), + reputation_changes: Vec::new(), + sent_feedback: Some(fb_tx), + }); + fb_rx.await.unwrap(); + } + }); let protocol_config = ProtocolConfig { name: protocol_name.clone(), @@ -1176,84 +1250,69 @@ mod tests { let (mut swarm, _) = swarms.remove(0); // Running `swarm[0]` in the background. - pool.spawner() - .spawn_obj({ - async move { - loop { - match swarm.select_next_some().await { - SwarmEvent::Behaviour(Event::InboundRequest { result, .. }) => { - result.unwrap(); - }, - _ => {}, - } - } - } - .boxed() - .into() - }) - .unwrap(); - - // Remove and run the remaining swarm. - let (mut swarm, _) = swarms.remove(0); - pool.run_until(async move { - let mut response_receiver = None; - + tokio::spawn(async move { loop { match swarm.select_next_some().await { - SwarmEvent::ConnectionEstablished { peer_id, .. } => { - let (sender, receiver) = oneshot::channel(); - swarm.behaviour_mut().send_request( - &peer_id, - protocol_name.clone(), - b"this is a request".to_vec(), - None, - sender, - IfDisconnected::ImmediateError, - ); - assert!(response_receiver.is_none()); - response_receiver = Some(receiver); - }, - SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { + SwarmEvent::Behaviour(Event::InboundRequest { result, .. }) => { result.unwrap(); - break }, _ => {}, } } - - assert_eq!( - response_receiver.unwrap().await.unwrap().unwrap(), - (b"this is a response".to_vec(), protocol_name) - ); }); + + // Remove and run the remaining swarm. + let (mut swarm, _) = swarms.remove(0); + let mut response_receiver = None; + + loop { + match swarm.select_next_some().await { + SwarmEvent::ConnectionEstablished { peer_id, .. } => { + let (sender, receiver) = oneshot::channel(); + swarm.behaviour_mut().send_request( + &peer_id, + protocol_name.clone(), + b"this is a request".to_vec(), + None, + sender, + IfDisconnected::ImmediateError, + ); + assert!(response_receiver.is_none()); + response_receiver = Some(receiver); + }, + SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { + result.unwrap(); + break + }, + _ => {}, + } + } + + assert_eq!( + response_receiver.unwrap().await.unwrap().unwrap(), + (b"this is a response".to_vec(), protocol_name) + ); } - #[test] - fn max_response_size_exceeded() { + #[tokio::test] + async fn max_response_size_exceeded() { let protocol_name = ProtocolName::from("/test/req-resp/1"); - let mut pool = LocalPool::new(); // Build swarms whose behaviour is [`RequestResponsesBehaviour`]. let mut swarms = (0..2) .map(|_| { let (tx, mut rx) = async_channel::bounded::(64); - pool.spawner() - .spawn_obj( - async move { - while let Some(rq) = rx.next().await { - assert_eq!(rq.payload, b"this is a request"); - let _ = rq.pending_response.send(super::OutgoingResponse { - result: Ok(b"this response exceeds the limit".to_vec()), - reputation_changes: Vec::new(), - sent_feedback: None, - }); - } - } - .boxed() - .into(), - ) - .unwrap(); + tokio::spawn(async move { + while let Some(rq) = rx.next().await { + assert_eq!(rq.payload, b"this is a request"); + let _ = rq.pending_response.send(super::OutgoingResponse { + result: Ok(b"this response exceeds the limit".to_vec()), + reputation_changes: Vec::new(), + sent_feedback: None, + }); + } + }); let protocol_config = ProtocolConfig { name: protocol_name.clone(), @@ -1278,59 +1337,52 @@ mod tests { // Running `swarm[0]` in the background until a `InboundRequest` event happens, // which is a hint about the test having ended. let (mut swarm, _) = swarms.remove(0); - pool.spawner() - .spawn_obj({ - async move { - loop { - match swarm.select_next_some().await { - SwarmEvent::Behaviour(Event::InboundRequest { result, .. }) => { - assert!(result.is_ok()); - }, - SwarmEvent::ConnectionClosed { .. } => { - break; - }, - _ => {}, - } - } - } - .boxed() - .into() - }) - .unwrap(); - - // Remove and run the remaining swarm. - let (mut swarm, _) = swarms.remove(0); - pool.run_until(async move { - let mut response_receiver = None; - + tokio::spawn(async move { loop { match swarm.select_next_some().await { - SwarmEvent::ConnectionEstablished { peer_id, .. } => { - let (sender, receiver) = oneshot::channel(); - swarm.behaviour_mut().send_request( - &peer_id, - protocol_name.clone(), - b"this is a request".to_vec(), - None, - sender, - IfDisconnected::ImmediateError, - ); - assert!(response_receiver.is_none()); - response_receiver = Some(receiver); + SwarmEvent::Behaviour(Event::InboundRequest { result, .. }) => { + assert!(result.is_ok()); }, - SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { - assert!(result.is_err()); - break + SwarmEvent::ConnectionClosed { .. } => { + break; }, _ => {}, } } + }); + + // Remove and run the remaining swarm. + let (mut swarm, _) = swarms.remove(0); + + let mut response_receiver = None; - match response_receiver.unwrap().await.unwrap().unwrap_err() { - RequestFailure::Network(OutboundFailure::Io(_)) => {}, - request_failure => panic!("Unexpected failure: {request_failure:?}"), + loop { + match swarm.select_next_some().await { + SwarmEvent::ConnectionEstablished { peer_id, .. } => { + let (sender, receiver) = oneshot::channel(); + swarm.behaviour_mut().send_request( + &peer_id, + protocol_name.clone(), + b"this is a request".to_vec(), + None, + sender, + IfDisconnected::ImmediateError, + ); + assert!(response_receiver.is_none()); + response_receiver = Some(receiver); + }, + SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { + assert!(result.is_err()); + break + }, + _ => {}, } - }); + } + + match response_receiver.unwrap().await.unwrap().unwrap_err() { + RequestFailure::Network(OutboundFailure::Io(_)) => {}, + request_failure => panic!("Unexpected failure: {request_failure:?}"), + } } /// A `RequestId` is a unique identifier among either all inbound or all outbound requests for @@ -1343,11 +1395,10 @@ mod tests { /// without a `RequestId` collision. /// /// See [`ProtocolRequestId`] for additional information. - #[test] - fn request_id_collision() { + #[tokio::test] + async fn request_id_collision() { let protocol_name_1 = ProtocolName::from("/test/req-resp-1/1"); let protocol_name_2 = ProtocolName::from("/test/req-resp-2/1"); - let mut pool = LocalPool::new(); let mut swarm_1 = { let protocol_configs = vec![ @@ -1405,114 +1456,100 @@ mod tests { swarm_1.dial(listen_add_2).unwrap(); // Run swarm 2 in the background, receiving two requests. - pool.spawner() - .spawn_obj( - async move { - loop { - match swarm_2.select_next_some().await { - SwarmEvent::Behaviour(Event::InboundRequest { result, .. }) => { - result.unwrap(); - }, - _ => {}, - } - } + tokio::spawn(async move { + loop { + match swarm_2.select_next_some().await { + SwarmEvent::Behaviour(Event::InboundRequest { result, .. }) => { + result.unwrap(); + }, + _ => {}, } - .boxed() - .into(), - ) - .unwrap(); + } + }); // Handle both requests sent by swarm 1 to swarm 2 in the background. // // Make sure both requests overlap, by answering the first only after receiving the // second. - pool.spawner() - .spawn_obj( - async move { - let protocol_1_request = swarm_2_handler_1.next().await; - let protocol_2_request = swarm_2_handler_2.next().await; - - protocol_1_request - .unwrap() - .pending_response - .send(OutgoingResponse { - result: Ok(b"this is a response".to_vec()), - reputation_changes: Vec::new(), - sent_feedback: None, - }) - .unwrap(); - protocol_2_request - .unwrap() - .pending_response - .send(OutgoingResponse { - result: Ok(b"this is a response".to_vec()), - reputation_changes: Vec::new(), - sent_feedback: None, - }) - .unwrap(); - } - .boxed() - .into(), - ) - .unwrap(); + tokio::spawn(async move { + let protocol_1_request = swarm_2_handler_1.next().await; + let protocol_2_request = swarm_2_handler_2.next().await; + + protocol_1_request + .unwrap() + .pending_response + .send(OutgoingResponse { + result: Ok(b"this is a response".to_vec()), + reputation_changes: Vec::new(), + sent_feedback: None, + }) + .unwrap(); + protocol_2_request + .unwrap() + .pending_response + .send(OutgoingResponse { + result: Ok(b"this is a response".to_vec()), + reputation_changes: Vec::new(), + sent_feedback: None, + }) + .unwrap(); + }); // Have swarm 1 send two requests to swarm 2 and await responses. - pool.run_until(async move { - let mut response_receivers = None; - let mut num_responses = 0; - loop { - match swarm_1.select_next_some().await { - SwarmEvent::ConnectionEstablished { peer_id, .. } => { - let (sender_1, receiver_1) = oneshot::channel(); - let (sender_2, receiver_2) = oneshot::channel(); - swarm_1.behaviour_mut().send_request( - &peer_id, - protocol_name_1.clone(), - b"this is a request".to_vec(), - None, - sender_1, - IfDisconnected::ImmediateError, - ); - swarm_1.behaviour_mut().send_request( - &peer_id, - protocol_name_2.clone(), - b"this is a request".to_vec(), - None, - sender_2, - IfDisconnected::ImmediateError, - ); - assert!(response_receivers.is_none()); - response_receivers = Some((receiver_1, receiver_2)); - }, - SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { - num_responses += 1; - result.unwrap(); - if num_responses == 2 { - break - } - }, - _ => {}, - } + let mut response_receivers = None; + let mut num_responses = 0; + + loop { + match swarm_1.select_next_some().await { + SwarmEvent::ConnectionEstablished { peer_id, .. } => { + let (sender_1, receiver_1) = oneshot::channel(); + let (sender_2, receiver_2) = oneshot::channel(); + swarm_1.behaviour_mut().send_request( + &peer_id, + protocol_name_1.clone(), + b"this is a request".to_vec(), + None, + sender_1, + IfDisconnected::ImmediateError, + ); + swarm_1.behaviour_mut().send_request( + &peer_id, + protocol_name_2.clone(), + b"this is a request".to_vec(), + None, + sender_2, + IfDisconnected::ImmediateError, + ); + assert!(response_receivers.is_none()); + response_receivers = Some((receiver_1, receiver_2)); + }, + SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { + num_responses += 1; + result.unwrap(); + if num_responses == 2 { + break + } + }, + _ => {}, } - let (response_receiver_1, response_receiver_2) = response_receivers.unwrap(); - assert_eq!( - response_receiver_1.await.unwrap().unwrap(), - (b"this is a response".to_vec(), protocol_name_1) - ); - assert_eq!( - response_receiver_2.await.unwrap().unwrap(), - (b"this is a response".to_vec(), protocol_name_2) - ); - }); + } + let (response_receiver_1, response_receiver_2) = response_receivers.unwrap(); + assert_eq!( + response_receiver_1.await.unwrap().unwrap(), + (b"this is a response".to_vec(), protocol_name_1) + ); + assert_eq!( + response_receiver_2.await.unwrap().unwrap(), + (b"this is a response".to_vec(), protocol_name_2) + ); } - #[test] - fn request_fallback() { + #[tokio::test] + async fn request_fallback() { let protocol_name_1 = ProtocolName::from("/test/req-resp/2"); let protocol_name_1_fallback = ProtocolName::from("/test/req-resp/1"); let protocol_name_2 = ProtocolName::from("/test/another"); - let mut pool = LocalPool::new(); let protocol_config_1 = ProtocolConfig { name: protocol_name_1.clone(), @@ -1550,39 +1587,31 @@ mod tests { let mut protocol_config_2 = protocol_config_2.clone(); protocol_config_2.inbound_queue = Some(tx_2); - pool.spawner() - .spawn_obj( - async move { - for _ in 0..2 { - if let Some(rq) = rx_1.next().await { - let (fb_tx, fb_rx) = oneshot::channel(); - assert_eq!(rq.payload, b"request on protocol /test/req-resp/1"); - let _ = rq.pending_response.send(super::OutgoingResponse { - result: Ok( - b"this is a response on protocol /test/req-resp/1".to_vec() - ), - reputation_changes: Vec::new(), - sent_feedback: Some(fb_tx), - }); - fb_rx.await.unwrap(); - } - } - - if let Some(rq) = rx_2.next().await { - let (fb_tx, fb_rx) = oneshot::channel(); - assert_eq!(rq.payload, b"request on protocol /test/other"); - let _ = rq.pending_response.send(super::OutgoingResponse { - result: Ok(b"this is a response on protocol /test/other".to_vec()), - reputation_changes: Vec::new(), - sent_feedback: Some(fb_tx), - }); - fb_rx.await.unwrap(); - } + tokio::spawn(async move { + for _ in 0..2 { + if let Some(rq) = rx_1.next().await { + let (fb_tx, fb_rx) = oneshot::channel(); + assert_eq!(rq.payload, b"request on protocol /test/req-resp/1"); + let _ = rq.pending_response.send(super::OutgoingResponse { + result: Ok(b"this is a response on protocol /test/req-resp/1".to_vec()), + reputation_changes: Vec::new(), + sent_feedback: Some(fb_tx), + }); + fb_rx.await.unwrap(); } - .boxed() - .into(), - ) - .unwrap(); + } + + if let Some(rq) = rx_2.next().await { + let (fb_tx, fb_rx) = oneshot::channel(); + assert_eq!(rq.payload, b"request on protocol /test/other"); + let _ = rq.pending_response.send(super::OutgoingResponse { + result: Ok(b"this is a response on protocol /test/other".to_vec()), + reputation_changes: Vec::new(), + sent_feedback: Some(fb_tx), + }); + fb_rx.await.unwrap(); + } + }); build_swarm(vec![protocol_config_1_fallback, protocol_config_2].into_iter()) }; @@ -1603,132 +1632,269 @@ mod tests { } // Running `older_swarm`` in the background. - pool.spawner() - .spawn_obj({ - async move { - loop { - _ = older_swarm.0.select_next_some().await; - } - } - .boxed() - .into() - }) - .unwrap(); + tokio::spawn(async move { + loop { + _ = older_swarm.0.select_next_some().await; + } + }); // Run the newer swarm. Attempt to make requests on all protocols. let (mut swarm, _) = new_swarm; let mut older_peer_id = None; - pool.run_until(async move { - let mut response_receiver = None; - // Try the new protocol with a fallback. - loop { - match swarm.select_next_some().await { - SwarmEvent::ConnectionEstablished { peer_id, .. } => { - older_peer_id = Some(peer_id); - let (sender, receiver) = oneshot::channel(); - swarm.behaviour_mut().send_request( - &peer_id, - protocol_name_1.clone(), - b"request on protocol /test/req-resp/2".to_vec(), - Some(( - b"request on protocol /test/req-resp/1".to_vec(), - protocol_config_1_fallback.name.clone(), - )), - sender, - IfDisconnected::ImmediateError, - ); - response_receiver = Some(receiver); - }, - SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { - result.unwrap(); - break - }, - _ => {}, - } + let mut response_receiver = None; + // Try the new protocol with a fallback. + loop { + match swarm.select_next_some().await { + SwarmEvent::ConnectionEstablished { peer_id, .. } => { + older_peer_id = Some(peer_id); + let (sender, receiver) = oneshot::channel(); + swarm.behaviour_mut().send_request( + &peer_id, + protocol_name_1.clone(), + b"request on protocol /test/req-resp/2".to_vec(), + Some(( + b"request on protocol /test/req-resp/1".to_vec(), + protocol_config_1_fallback.name.clone(), + )), + sender, + IfDisconnected::ImmediateError, + ); + response_receiver = Some(receiver); + }, + SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { + result.unwrap(); + break + }, + _ => {}, } - assert_eq!( - response_receiver.unwrap().await.unwrap().unwrap(), - ( - b"this is a response on protocol /test/req-resp/1".to_vec(), - protocol_name_1_fallback.clone() - ) - ); - // Try the old protocol with a useless fallback. - let (sender, response_receiver) = oneshot::channel(); - swarm.behaviour_mut().send_request( - older_peer_id.as_ref().unwrap(), - protocol_name_1_fallback.clone(), - b"request on protocol /test/req-resp/1".to_vec(), - Some(( - b"dummy request, will fail if processed".to_vec(), - protocol_config_1_fallback.name.clone(), - )), - sender, - IfDisconnected::ImmediateError, - ); - loop { - match swarm.select_next_some().await { - SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { - result.unwrap(); - break - }, - _ => {}, - } + } + assert_eq!( + response_receiver.unwrap().await.unwrap().unwrap(), + ( + b"this is a response on protocol /test/req-resp/1".to_vec(), + protocol_name_1_fallback.clone() + ) + ); + // Try the old protocol with a useless fallback. + let (sender, response_receiver) = oneshot::channel(); + swarm.behaviour_mut().send_request( + older_peer_id.as_ref().unwrap(), + protocol_name_1_fallback.clone(), + b"request on protocol /test/req-resp/1".to_vec(), + Some(( + b"dummy request, will fail if processed".to_vec(), + protocol_config_1_fallback.name.clone(), + )), + sender, + IfDisconnected::ImmediateError, + ); + loop { + match swarm.select_next_some().await { + SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { + result.unwrap(); + break + }, + _ => {}, } - assert_eq!( - response_receiver.await.unwrap().unwrap(), - ( - b"this is a response on protocol /test/req-resp/1".to_vec(), - protocol_name_1_fallback.clone() - ) - ); - // Try the new protocol with no fallback. Should fail. - let (sender, response_receiver) = oneshot::channel(); - swarm.behaviour_mut().send_request( - older_peer_id.as_ref().unwrap(), - protocol_name_1.clone(), - b"request on protocol /test/req-resp-2".to_vec(), - None, - sender, - IfDisconnected::ImmediateError, - ); - loop { - match swarm.select_next_some().await { - SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { - assert_matches!( - result.unwrap_err(), - RequestFailure::Network(OutboundFailure::UnsupportedProtocols) - ); - break - }, - _ => {}, - } + } + assert_eq!( + response_receiver.await.unwrap().unwrap(), + ( + b"this is a response on protocol /test/req-resp/1".to_vec(), + protocol_name_1_fallback.clone() + ) + ); + // Try the new protocol with no fallback. Should fail. + let (sender, response_receiver) = oneshot::channel(); + swarm.behaviour_mut().send_request( + older_peer_id.as_ref().unwrap(), + protocol_name_1.clone(), + b"request on protocol /test/req-resp-2".to_vec(), + None, + sender, + IfDisconnected::ImmediateError, + ); + loop { + match swarm.select_next_some().await { + SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { + assert_matches!( + result.unwrap_err(), + RequestFailure::Network(OutboundFailure::UnsupportedProtocols) + ); + break + }, + _ => {}, } - assert!(response_receiver.await.unwrap().is_err()); - // Try the other protocol with no fallback. - let (sender, response_receiver) = oneshot::channel(); - swarm.behaviour_mut().send_request( - older_peer_id.as_ref().unwrap(), - protocol_name_2.clone(), - b"request on protocol /test/other".to_vec(), - None, - sender, - IfDisconnected::ImmediateError, - ); + } + assert!(response_receiver.await.unwrap().is_err()); + // Try the other protocol with no fallback. + let (sender, response_receiver) = oneshot::channel(); + swarm.behaviour_mut().send_request( + older_peer_id.as_ref().unwrap(), + protocol_name_2.clone(), + b"request on protocol /test/other".to_vec(), + None, + sender, + IfDisconnected::ImmediateError, + ); + loop { + match swarm.select_next_some().await { + SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { + result.unwrap(); + break + }, + _ => {}, + } + } + assert_eq!( + response_receiver.await.unwrap().unwrap(), + (b"this is a response on protocol /test/other".to_vec(), protocol_name_2.clone()) + ); + } + + /// This test ensures the `RequestResponsesBehaviour` propagates back the Request::Timeout error + /// even if the libp2p component hangs. + /// + /// For testing purposes, the communication happens on the `/test/req-resp/1` protocol. + /// + /// This is achieved by: + /// - Two swarms are connected, the first one is slow to respond and has the timeout set to 10 + /// seconds. The second swarm is configured with a timeout of 10 seconds in libp2p, however in + /// substrate this is set to 1 second. + /// + /// - The first swarm introduces a delay of 2 seconds before responding to the request. + /// + /// - The second swarm must enforce the 1 second timeout. + #[tokio::test] + async fn enforce_outbound_timeouts() { + const REQUEST_TIMEOUT: Duration = Duration::from_secs(10); + const REQUEST_TIMEOUT_SHORT: Duration = Duration::from_secs(1); + + // These swarms only speaks protocol_name. + let protocol_name = ProtocolName::from("/test/req-resp/1"); + + let protocol_config = ProtocolConfig { + name: protocol_name.clone(), + fallback_names: Vec::new(), + max_request_size: 1024, + max_response_size: 1024 * 1024, + request_timeout: REQUEST_TIMEOUT, // <-- important for the test + inbound_queue: None, + }; + + // Build swarms whose behaviour is [`RequestResponsesBehaviour`]. + let (mut first_swarm, _) = { + let (tx, mut rx) = async_channel::bounded::(64); + + tokio::spawn(async move { + if let Some(rq) = rx.next().await { + assert_eq!(rq.payload, b"this is a request"); + + // Sleep for more than `REQUEST_TIMEOUT_SHORT` and less than + // `REQUEST_TIMEOUT`. + tokio::time::sleep(REQUEST_TIMEOUT_SHORT * 2).await; + + // By the time the response is sent back, the second swarm + // received Timeout. + let _ = rq.pending_response.send(super::OutgoingResponse { + result: Ok(b"Second swarm already timedout".to_vec()), + reputation_changes: Vec::new(), + sent_feedback: None, + }); + } + }); + + let mut protocol_config = protocol_config.clone(); + protocol_config.inbound_queue = Some(tx); + + build_swarm(iter::once(protocol_config)) + }; + + let (mut second_swarm, second_address) = { + let (tx, mut rx) = async_channel::bounded::(64); + + tokio::spawn(async move { + while let Some(rq) = rx.next().await { + let _ = rq.pending_response.send(super::OutgoingResponse { + result: Ok(b"This is the response".to_vec()), + reputation_changes: Vec::new(), + sent_feedback: None, + }); + } + }); + let mut protocol_config = protocol_config.clone(); + protocol_config.inbound_queue = Some(tx); + + build_swarm(iter::once(protocol_config.clone())) + }; + // Modify the second swarm to have a shorter timeout. + second_swarm + .behaviour_mut() + .protocols + .get_mut(&protocol_name) + .unwrap() + .request_timeout = REQUEST_TIMEOUT_SHORT; + + // Ask first swarm to dial the second swarm. + { + Swarm::dial(&mut first_swarm, second_address).unwrap(); + } + + // Running the first swarm in the background until a `InboundRequest` event happens, + // which is a hint about the test having ended. + tokio::spawn(async move { loop { - match swarm.select_next_some().await { - SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { - result.unwrap(); - break + let event = first_swarm.select_next_some().await; + match event { + SwarmEvent::Behaviour(Event::InboundRequest { result, .. }) => { + assert!(result.is_ok()); + break; + }, + SwarmEvent::ConnectionClosed { .. } => { + break; }, _ => {}, } } - assert_eq!( - response_receiver.await.unwrap().unwrap(), - (b"this is a response on protocol /test/other".to_vec(), protocol_name_2.clone()) - ); }); + + // Run the second swarm. + // - on connection established send the request to the first swarm + // - expect to receive a timeout + let mut response_receiver = None; + loop { + let event = second_swarm.select_next_some().await; + + match event { + SwarmEvent::ConnectionEstablished { peer_id, .. } => { + let (sender, receiver) = oneshot::channel(); + second_swarm.behaviour_mut().send_request( + &peer_id, + protocol_name.clone(), + b"this is a request".to_vec(), + None, + sender, + IfDisconnected::ImmediateError, + ); + assert!(response_receiver.is_none()); + response_receiver = Some(receiver); + }, + SwarmEvent::ConnectionClosed { .. } => { + break; + }, + SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { + assert!(result.is_err()); + break + }, + _ => {}, + } + } + + // Expect the timeout. + match response_receiver.unwrap().await.unwrap().unwrap_err() { + RequestFailure::Network(OutboundFailure::Timeout) => {}, + request_failure => panic!("Unexpected failure: {request_failure:?}"), + } } } From 89b022842c7ab922de5bf026cd45e43b9cd8c654 Mon Sep 17 00:00:00 2001 From: FereMouSiopi Date: Wed, 22 Jan 2025 10:08:59 -0800 Subject: [PATCH 124/169] Migrate `pallet-insecure-randomness-collective-flip` to umbrella crate (#6738) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of https://github.com/paritytech/polkadot-sdk/issues/6504 --------- Co-authored-by: command-bot <> Co-authored-by: Bastian Köcher --- Cargo.lock | 6 +--- .../Cargo.toml | 18 ++---------- .../src/lib.rs | 28 ++++++------------- 3 files changed, 13 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e41b7e993744..a10def370be72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13925,14 +13925,10 @@ dependencies = [ name = "pallet-insecure-randomness-collective-flip" version = "16.0.0" dependencies = [ - "frame-support 28.0.0", - "frame-system 28.0.0", "parity-scale-codec", + "polkadot-sdk-frame 0.1.0", "safe-mix", "scale-info", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-runtime 31.0.1", ] [[package]] diff --git a/substrate/frame/insecure-randomness-collective-flip/Cargo.toml b/substrate/frame/insecure-randomness-collective-flip/Cargo.toml index 1682b52dfbf4d..789f130423a4b 100644 --- a/substrate/frame/insecure-randomness-collective-flip/Cargo.toml +++ b/substrate/frame/insecure-randomness-collective-flip/Cargo.toml @@ -17,30 +17,18 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } +frame = { workspace = true, features = ["runtime"] } safe-mix = { workspace = true } scale-info = { features = ["derive"], workspace = true } -sp-runtime = { workspace = true } - -[dev-dependencies] -sp-core = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } [features] default = ["std"] std = [ "codec/std", - "frame-support/std", - "frame-system/std", + "frame/std", "safe-mix/std", "scale-info/std", - "sp-core/std", - "sp-io/std", - "sp-runtime/std", ] try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", - "sp-runtime/try-runtime", + "frame/try-runtime", ] diff --git a/substrate/frame/insecure-randomness-collective-flip/src/lib.rs b/substrate/frame/insecure-randomness-collective-flip/src/lib.rs index b605b4d08582b..0e7e8001d5df7 100644 --- a/substrate/frame/insecure-randomness-collective-flip/src/lib.rs +++ b/substrate/frame/insecure-randomness-collective-flip/src/lib.rs @@ -42,13 +42,11 @@ //! ### Example - Get random seed for the current block //! //! ``` -//! use frame_support::traits::Randomness; +//! use frame::{prelude::*, traits::Randomness}; //! -//! #[frame_support::pallet] +//! #[frame::pallet] //! pub mod pallet { //! use super::*; -//! use frame_support::pallet_prelude::*; -//! use frame_system::pallet_prelude::*; //! //! #[pallet::pallet] //! pub struct Pallet(_); @@ -73,9 +71,7 @@ use safe_mix::TripletMix; use codec::Encode; -use frame_support::{pallet_prelude::Weight, traits::Randomness}; -use frame_system::pallet_prelude::BlockNumberFor; -use sp_runtime::traits::{Hash, Saturating}; +use frame::{prelude::*, traits::Randomness}; const RANDOM_MATERIAL_LEN: u32 = 81; @@ -87,10 +83,9 @@ fn block_number_to_index(block_number: BlockNumberFor) -> usize { pub use pallet::*; -#[frame_support::pallet] +#[frame::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; #[pallet::pallet] pub struct Pallet(_); @@ -167,19 +162,14 @@ impl Randomness> for Pallet { mod tests { use super::*; use crate as pallet_insecure_randomness_collective_flip; - - use sp_core::H256; - use sp_runtime::{traits::Header as _, BuildStorage}; - - use frame_support::{ - derive_impl, parameter_types, - traits::{OnInitialize, Randomness}, + use frame::{ + testing_prelude::{frame_system::limits, *}, + traits::Header as _, }; - use frame_system::limits; type Block = frame_system::mocking::MockBlock; - frame_support::construct_runtime!( + construct_runtime!( pub enum Test { System: frame_system, @@ -199,7 +189,7 @@ mod tests { impl pallet_insecure_randomness_collective_flip::Config for Test {} - fn new_test_ext() -> sp_io::TestExternalities { + fn new_test_ext() -> TestExternalities { let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); t.into() } From dbea5a8f7c643d580a57267d54655b71603ed9a7 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 23 Jan 2025 09:45:22 +0000 Subject: [PATCH 125/169] make multi page election graceful, onchain supports multi page, better instant election traits --- .../assets/asset-hub-rococo/src/lib.rs | 3 +- substrate/bin/node/cli/src/chain_spec.rs | 2 +- substrate/bin/node/runtime/src/lib.rs | 17 +- .../election-provider-multi-block/src/lib.rs | 1012 ++++++++++------- .../src/mock/mod.rs | 61 +- .../src/mock/signed.rs | 2 +- .../src/mock/staking.rs | 21 +- .../src/signed/mod.rs | 2 +- .../src/types.rs | 1 + .../src/unsigned/miner.rs | 39 +- .../src/unsigned/mod.rs | 42 + .../src/verifier/impls.rs | 34 +- .../src/verifier/mod.rs | 4 + .../src/verifier/tests.rs | 11 +- .../election-provider-multi-phase/src/lib.rs | 105 +- .../election-provider-multi-phase/src/mock.rs | 16 +- .../src/signed.rs | 4 +- .../election-provider-support/src/lib.rs | 21 +- .../election-provider-support/src/onchain.rs | 60 +- substrate/frame/staking/src/mock.rs | 48 +- substrate/frame/staking/src/pallet/impls.rs | 73 +- substrate/frame/staking/src/pallet/mod.rs | 7 +- substrate/frame/staking/src/tests.rs | 12 +- .../frame/staking/src/tests_paged_election.rs | 200 +++- 24 files changed, 1146 insertions(+), 651 deletions(-) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index 43b7bf0ba1184..264e6408d211b 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -610,8 +610,7 @@ impl InstanceFilter for ProxyType { RuntimeCall::Utility { .. } | RuntimeCall::Multisig { .. } | RuntimeCall::NftFractionalization { .. } | - RuntimeCall::Nfts { .. } | - RuntimeCall::Uniques { .. } + RuntimeCall::Nfts { .. } | RuntimeCall::Uniques { .. } ) }, ProxyType::AssetOwner => matches!( diff --git a/substrate/bin/node/cli/src/chain_spec.rs b/substrate/bin/node/cli/src/chain_spec.rs index 400c6e1fdd6fc..a28ab5784eb86 100644 --- a/substrate/bin/node/cli/src/chain_spec.rs +++ b/substrate/bin/node/cli/src/chain_spec.rs @@ -383,7 +383,7 @@ pub fn testnet_genesis( }, "staking": { "validatorCount": std::option_env!("VAL_COUNT").map(|v| v.parse::().unwrap()).unwrap_or((initial_authorities.len()/2usize) as u32), - "minimumValidatorCount": 4, + "minimumValidatorCount": 10, "invulnerables": initial_authorities.iter().map(|x| x.0.clone()).collect::>(), "slashRewardFraction": Perbill::from_percent(10), "stakers": stakers.clone(), diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 25837720caa25..684fad82a0989 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -888,29 +888,32 @@ pub(crate) mod multi_block_impls { use pallet_election_provider_multi_phase as multi_phase; parameter_types! { - pub Pages: u32 = 8; - pub VoterSnapshotPerBlock: u32 = 22500 / 8; + pub Pages: u32 = 4; + pub VoterSnapshotPerBlock: u32 = 22500 / 4; pub TargetSnapshotPerBlock: u32 = 1000; + pub SignedPhase: u32 = EPOCH_DURATION_IN_BLOCKS / 4; + pub UnsignedPhase: u32 = EPOCH_DURATION_IN_BLOCKS / 4; } + impl multi_block::Config for Runtime { type AdminOrigin = EnsureRoot; + type RuntimeEvent = RuntimeEvent; type DataProvider = Staking; - type Fallback = ::Fallback; + type Fallback = multi_block::Continue; // prepare for election 5 blocks ahead of time type Lookahead = ConstU32<5>; // split election into 8 pages. type Pages = Pages; // allow 2 signed solutions to be verified. type SignedValidationPhase = ConstU32<16>; - type RuntimeEvent = RuntimeEvent; // TODO: sanity check that the length of all phases is within reason. - type SignedPhase = ::SignedPhase; - type UnsignedPhase = ::UnsignedPhase; - type WeightInfo = (); + type SignedPhase = SignedPhase; + type UnsignedPhase = UnsignedPhase; type Solution = NposSolution16; type TargetSnapshotPerBlock = TargetSnapshotPerBlock; type VoterSnapshotPerBlock = VoterSnapshotPerBlock; type Verifier = MultiBlockVerifier; + type WeightInfo = (); } impl multi_block::verifier::Config for Runtime { diff --git a/substrate/frame/election-provider-multi-block/src/lib.rs b/substrate/frame/election-provider-multi-block/src/lib.rs index a5dbf57cbf6aa..6ba447726da67 100644 --- a/substrate/frame/election-provider-multi-block/src/lib.rs +++ b/substrate/frame/election-provider-multi-block/src/lib.rs @@ -107,14 +107,25 @@ //! //! ### Emergency Phase and Fallback //! -//! Works similar to the multi-phase pallet: +//! * [`Config::Fallback`] is called on each page. It typically may decide to: //! -//! * [`Config::Fallback`] is used, only in page 0, in case no queued solution is present. -//! * [`Phase::Emergency`] is entered if also the fallback fails. At this point, only -//! [`AdminOperation::SetSolution`] can be used to recover. +//! 1. Do nothing, +//! 2. Force us into the emergency phase +//! 3. computer an onchain from the give page of snapshot. Note that this will be sub-optimal, +//! because the proper pagination size of snapshot and fallback will likely differ a lot. //! -//! TODO: test that multi-page seq-phragmen with fallback works. +//! Note that configuring the fallback to be onchain computation is not recommended, unless for +//! test-nets for a number of reasons: //! +//! 1. The solution score of fallback is never checked to be match the "minimum" score. That being +//! said, the computation happens onchain so we can trust it. +//! 2. The onchain fallback runs on the same number of voters and targets that reside on a single +//! page of a snapshot, which will very likely be too much for actual onchain computation. Yet, +//! we don't have another choice as we cannot request another smaller snapshot from the data +//! provider mid-election without more bookkeeping on the staking side. +//! +//! If onchain solution is to be seriously considered, an improvement to this pallet should +//! re-request a smaller set of voters from `T::DataProvider` in a stateless manner. //! //! ### Signed Phase //! @@ -142,21 +153,29 @@ #![cfg_attr(not(feature = "std"), no_std)] +use crate::types::*; use codec::{Decode, Encode, MaxEncodedLen}; use frame_election_provider_support::{ onchain, BoundedSupportsOf, DataProviderBounds, ElectionDataProvider, ElectionProvider, + InstantElectionProvider, }; use frame_support::{ - ensure, - traits::{ConstU32, Defensive, Get}, - BoundedVec, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, + pallet_prelude::*, + traits::{Defensive, EnsureOrigin}, + Twox64Concat, }; -use frame_system::pallet_prelude::BlockNumberFor; +use frame_system::pallet_prelude::*; use scale_info::TypeInfo; -use sp_arithmetic::traits::Zero; +use sp_arithmetic::{ + traits::{CheckedAdd, Zero}, + PerThing, UpperOf, +}; use sp_npos_elections::VoteWeight; -use sp_runtime::SaturatedConversion; -use sp_std::boxed::Box; +use sp_runtime::{ + traits::{Hash, Saturating}, + SaturatedConversion, +}; +use sp_std::{borrow::ToOwned, boxed::Box, prelude::*}; use verifier::Verifier; #[cfg(test)] @@ -166,6 +185,13 @@ pub mod helpers; const LOG_PREFIX: &'static str = "runtime::multiblock-election"; +macro_rules! clear_paged_map { + ($map: ty) => {{ + let __r = <$map>::clear(u32::MAX, None); + debug_assert!(__r.unique <= T::Pages::get(), "clearing map caused too many removals") + }}; +} + // pub mod signed; pub mod signed; pub mod types; @@ -179,18 +205,17 @@ pub use weights::WeightInfo; /// A fallback implementation that transitions the pallet to the emergency phase. pub struct InitiateEmergencyPhase(sp_std::marker::PhantomData); -impl ElectionProvider for InitiateEmergencyPhase { +impl ElectionProvider for InitiateEmergencyPhase { type AccountId = T::AccountId; type BlockNumber = BlockNumberFor; type DataProvider = T::DataProvider; type Error = &'static str; - type Pages = ConstU32<1>; - type MaxBackersPerWinner = T::MaxBackersPerWinner; - type MaxWinnersPerPage = T::MaxWinnersPerPage; + type Pages = T::Pages; + type MaxBackersPerWinner = ::MaxBackersPerWinner; + type MaxWinnersPerPage = ::MaxWinnersPerPage; - fn elect(remaining: PageIndex) -> Result, Self::Error> { - ensure!(remaining == 0, "fallback should only have 1 page"); - log!(warn, "Entering emergency phase."); + fn elect(_page: PageIndex) -> Result, Self::Error> { + Pallet::::phase_transition(Phase::Emergency); Err("Emergency phase started.") } @@ -199,6 +224,54 @@ impl ElectionProvider for InitiateEmergencyPhase { } } +impl InstantElectionProvider for InitiateEmergencyPhase { + fn instant_elect( + _voters: Vec>, + _targets: Vec, + _desired_targets: u32, + ) -> Result, Self::Error> { + Self::elect(0) + } + + fn bother() -> bool { + false + } +} + +pub struct Continue(sp_std::marker::PhantomData); +impl ElectionProvider for Continue { + type AccountId = T::AccountId; + type BlockNumber = BlockNumberFor; + type DataProvider = T::DataProvider; + type Error = &'static str; + type Pages = T::Pages; + type MaxBackersPerWinner = ::MaxBackersPerWinner; + type MaxWinnersPerPage = ::MaxWinnersPerPage; + + fn elect(_page: PageIndex) -> Result, Self::Error> { + log!(warn, "'Continue' fallback will do nothing"); + Err("'Continue' fallback will do nothing") + } + + fn ongoing() -> bool { + false + } +} + +impl InstantElectionProvider for Continue { + fn instant_elect( + _voters: Vec>, + _targets: Vec, + _desired_targets: u32, + ) -> Result, Self::Error> { + Self::elect(0) + } + + fn bother() -> bool { + false + } +} + /// Internal errors of the pallet. /// /// Note that this is different from [`pallet::Error`]. @@ -218,6 +291,8 @@ pub enum ElectionError { SupportPageNotAvailable, /// The election is not ongoing and therefore no results may be queried. NotOngoing, + /// Other misc error + Other(&'static str), } impl From for ElectionError { @@ -271,20 +346,7 @@ pub enum AdminOperation { #[frame_support::pallet] pub mod pallet { - use crate::{ - types::*, - verifier::{self}, - AdminOperation, WeightInfo, - }; - use frame_election_provider_support::{ - ElectionDataProvider, ElectionProvider, NposSolution, PageIndex, - }; - use frame_support::{pallet_prelude::*, traits::EnsureOrigin, Twox64Concat}; - use frame_system::pallet_prelude::*; - use sp_arithmetic::{traits::CheckedAdd, PerThing, UpperOf}; - use sp_runtime::traits::{Hash, Saturating, Zero}; - use sp_std::{borrow::ToOwned, prelude::*}; - + use super::*; #[pallet::config] pub trait Config: frame_system::Config { type RuntimeEvent: From> @@ -342,13 +404,12 @@ pub mod pallet { /// /// This type is only used on the last page of the election, therefore it may at most have /// 1 pages. - type Fallback: ElectionProvider< + type Fallback: InstantElectionProvider< AccountId = Self::AccountId, BlockNumber = BlockNumberFor, DataProvider = Self::DataProvider, MaxBackersPerWinner = ::MaxBackersPerWinner, MaxWinnersPerPage = ::MaxWinnersPerPage, - Pages = ConstU32<1>, >; /// The verifier pallet's interface. @@ -377,7 +438,21 @@ pub mod pallet { match op { AdminOperation::EmergencyFallback => { ensure!(Self::current_phase() == Phase::Emergency, Error::::UnexpectedPhase); - let fallback = T::Fallback::elect(0).map_err(|_| Error::::Fallback)?; + // note: for now we run this on the msp, but we can make it configurable if need + // be. + let voters = Snapshot::::voters(Self::msp()).ok_or(Error::::Snapshot)?; + let targets = Snapshot::::targets().ok_or(Error::::Snapshot)?; + let desired_targets = + Snapshot::::desired_targets().ok_or(Error::::Snapshot)?; + let fallback = T::Fallback::instant_elect( + voters.into_inner(), + targets.into_inner(), + desired_targets, + ) + .map_err(|e| { + log!(warn, "Fallback failed: {:?}", e); + Error::::Fallback + })?; let score = fallback.evaluate(); T::Verifier::force_set_single_page_valid(fallback, 0, score); Ok(().into()) @@ -419,19 +494,19 @@ pub mod pallet { let next_election = T::DataProvider::next_election_prediction(now) .saturating_sub(T::Lookahead::get()) .max(now); - let remaining_blocks = next_election - now; + let remaining_blocks = next_election.saturating_sub(now); let current_phase = Self::current_phase(); log!( trace, - "current phase {:?}, next election {:?}, remaining: {:?}, deadlines: [unsigned {:?} signed_validation {:?}, signed {:?}, snapshot {:?}]", + "current phase {:?}, next election {:?}, remaining: {:?}, deadlines: [snapshot {:?}, signed {:?}, signed_validation {:?}, unsigned {:?}]", current_phase, next_election, remaining_blocks, - unsigned_deadline, - signed_validation_deadline, - signed_deadline, snapshot_deadline, + signed_deadline, + signed_validation_deadline, + unsigned_deadline, ); match current_phase { @@ -442,8 +517,8 @@ pub mod pallet { => { let remaining_pages = Self::msp(); - let count = Self::create_targets_snapshot().unwrap(); - let count = Self::create_voters_snapshot_paged(remaining_pages).unwrap(); + Self::create_targets_snapshot().defensive_unwrap_or_default(); + Self::create_voters_snapshot_paged(remaining_pages).defensive_unwrap_or_default(); Self::phase_transition(Phase::Snapshot(remaining_pages)); todo_weight }, @@ -576,6 +651,8 @@ pub mod pallet { Fallback, /// Unexpected phase UnexpectedPhase, + /// Snapshot was unavailable. + Snapshot, } impl PartialEq for Error { @@ -644,10 +721,10 @@ pub mod pallet { pub(crate) fn set_targets(targets: BoundedVec) { let hash = Self::write_storage_with_pre_allocate( - &PagedTargetSnapshot::::hashed_key_for(Pallet::::lsp()), + &PagedTargetSnapshot::::hashed_key_for(Pallet::::msp()), targets, ); - PagedTargetSnapshotHash::::insert(Pallet::::lsp(), hash); + PagedTargetSnapshotHash::::insert(Pallet::::msp(), hash); } pub(crate) fn set_voters(page: PageIndex, voters: VoterPageOf) { @@ -663,10 +740,10 @@ pub mod pallet { /// Should be called only once we transition to [`Phase::Off`]. pub(crate) fn kill() { DesiredTargets::::kill(); - PagedVoterSnapshot::::clear(u32::MAX, None); - PagedVoterSnapshotHash::::clear(u32::MAX, None); - PagedTargetSnapshot::::clear(u32::MAX, None); - PagedTargetSnapshotHash::::clear(u32::MAX, None); + clear_paged_map!(PagedVoterSnapshot::); + clear_paged_map!(PagedVoterSnapshotHash::); + clear_paged_map!(PagedTargetSnapshot::); + clear_paged_map!(PagedTargetSnapshotHash::); } // ----------- non-mutables @@ -687,16 +764,12 @@ pub mod pallet { } pub(crate) fn targets_decode_len() -> Option { - PagedVoterSnapshot::::decode_len(Pallet::::msp()) + PagedTargetSnapshot::::decode_len(Pallet::::msp()) } pub(crate) fn targets() -> Option> { // NOTE: targets always have one index, which is 0, aka lsp. - PagedTargetSnapshot::::get(Pallet::::lsp()) - } - - pub(crate) fn targets_hash() -> Option { - PagedTargetSnapshotHash::::get(Pallet::::lsp()) + PagedTargetSnapshot::::get(Pallet::::msp()) } /// Get a fingerprint of the snapshot, from all the hashes that are stored for each page of @@ -707,10 +780,7 @@ pub mod pallet { /// voters, from `msp` to `lsp`. pub fn fingerprint() -> T::Hash { let mut hashed_target_and_voters = - PagedTargetSnapshotHash::::get(Pallet::::lsp()) - .unwrap_or_default() - .as_ref() - .to_vec(); + Self::targets_hash().unwrap_or_default().as_ref().to_vec(); let hashed_voters = (Pallet::::msp()..=Pallet::::lsp()) .map(|i| PagedVoterSnapshotHash::::get(i).unwrap_or_default()) .map(|hash| >::as_ref(&hash).to_owned()) @@ -736,6 +806,10 @@ pub mod pallet { hash } + pub(crate) fn targets_hash() -> Option { + PagedTargetSnapshotHash::::get(Pallet::::msp()) + } + #[cfg(any(test, debug_assertions))] pub(crate) fn ensure_snapshot( exists: bool, @@ -757,7 +831,7 @@ pub mod pallet { ensure!(hash == T::Hashing::hash(&targets.encode()), "targets hash mismatch"); } - // ensure that pages that should exist, indeed to exist.. + // ensure that voter pages that should exist, indeed to exist.. let mut sum_existing_voters = 0; for p in (crate::Pallet::::lsp()..=crate::Pallet::::msp()) .rev() @@ -842,19 +916,21 @@ pub mod pallet { DesiredTargets::::kill(); } - pub(crate) fn remove_target_page(page: PageIndex) { - PagedTargetSnapshot::::remove(page); + pub(crate) fn remove_target_page() { + PagedTargetSnapshot::::remove(Pallet::::msp()); } pub(crate) fn remove_target(at: usize) { - PagedTargetSnapshot::::mutate(crate::Pallet::::lsp(), |maybe_targets| { + PagedTargetSnapshot::::mutate(crate::Pallet::::msp(), |maybe_targets| { if let Some(targets) = maybe_targets { targets.remove(at); // and update the hash. PagedTargetSnapshotHash::::insert( - crate::Pallet::::lsp(), + crate::Pallet::::msp(), T::Hashing::hash(&targets.encode()), ) + } else { + unreachable!(); } }) } @@ -969,7 +1045,7 @@ impl Pallet { /// Creates the target snapshot. Writes new data to: /// /// Returns `Ok(num_created)` if operation is okay. - pub fn create_targets_snapshot() -> Result> { + pub fn create_targets_snapshot() -> Result<(), ElectionError> { // if requested, get the targets as well. Snapshot::::set_desired_targets( T::DataProvider::desired_targets().map_err(ElectionError::DataProvider)?, @@ -986,13 +1062,13 @@ impl Pallet { log!(debug, "created target snapshot with {} targets.", count); Snapshot::::set_targets(targets); - Ok(count) + Ok(()) } /// Creates the voter snapshot. Writes new data to: /// /// Returns `Ok(num_created)` if operation is okay. - pub fn create_voters_snapshot_paged(remaining: PageIndex) -> Result> { + pub fn create_voters_snapshot_paged(remaining: PageIndex) -> Result<(), ElectionError> { let count = T::VoterSnapshotPerBlock::get(); let bounds = DataProviderBounds { count: Some(count.into()), size: None }; let voters: BoundedVec<_, T::VoterSnapshotPerBlock> = @@ -1004,7 +1080,7 @@ impl Pallet { Snapshot::::set_voters(remaining, voters); log!(debug, "created voter snapshot with {} voters, {} remaining.", count, remaining); - Ok(count) + Ok(()) } /// Perform the tasks to be done after a new `elect` has been triggered: @@ -1026,21 +1102,28 @@ impl Pallet { Snapshot::::kill(); } + fn fallback_for_page(page: PageIndex) -> Result, ElectionError> { + use frame_election_provider_support::InstantElectionProvider; + let (voters, targets, desired_targets) = if T::Fallback::bother() { + ( + Snapshot::::voters(page).ok_or(ElectionError::Other("snapshot!"))?, + Snapshot::::targets().ok_or(ElectionError::Other("snapshot!"))?, + Snapshot::::desired_targets().ok_or(ElectionError::Other("snapshot!"))?, + ) + } else { + (Default::default(), Default::default(), Default::default()) + }; + T::Fallback::instant_elect(voters.into_inner(), targets.into_inner(), desired_targets) + .map_err(|fe| ElectionError::Fallback(fe)) + } + #[cfg(any(test, debug_assertions))] pub(crate) fn sanity_check() -> Result<(), &'static str> { Snapshot::::sanity_check() } } -impl ElectionProvider for Pallet -where - T::Fallback: ElectionProvider< - AccountId = T::AccountId, - BlockNumber = BlockNumberFor, - MaxBackersPerWinner = ::MaxBackersPerWinner, - MaxWinnersPerPage = ::MaxWinnersPerPage, - >, -{ +impl ElectionProvider for Pallet { type AccountId = T::AccountId; type BlockNumber = BlockNumberFor; type Error = ElectionError; @@ -1054,41 +1137,43 @@ where return Err(ElectionError::NotOngoing); } - T::Verifier::get_queued_solution_page(remaining) + let result = T::Verifier::get_queued_solution_page(remaining) .ok_or(ElectionError::SupportPageNotAvailable) - .or_else(|err| { - // if this is the last page, we might use the fallback to recover something. - if remaining.is_zero() { - log!( - error, - "primary election provider failed due to: {:?}, trying fallback", - err - ); - T::Fallback::elect(0).map_err(|fe| ElectionError::::Fallback(fe)) - } else { - Err(err) - } - }) - .map(|supports| { - // if either of `Verifier` or `Fallback` was okay, and if this is the last page, - // then clear everything. - if remaining.is_zero() { - log!(info, "receiving last call to elect(0), rotating round"); - Self::rotate_round() - } else { - Self::phase_transition(Phase::Export(remaining)) - } - supports.into() + .or_else(|err: ElectionError| { + log!( + warn, + "primary election for page {} failed due to: {:?}, trying fallback", + remaining, + err, + ); + Self::fallback_for_page(remaining) }) .map_err(|err| { // if any pages returns an error, we go into the emergency phase and don't do // anything else anymore. This will prevent any new submissions to signed and // unsigned pallet, and thus the verifier will also be almost stuck, except for the // submission of emergency solutions. - log!(error, "fetching page {} failed. entering emergency mode.", remaining); - Self::phase_transition(Phase::Emergency); + log!(warn, "primary and fallback ({:?}) failed for page {:?}", err, remaining); err }) + .map(|supports| { + // convert to bounded + supports.into() + }); + + // if fallback has possibly put us into the emergency phase, don't do anything else. + if CurrentPhase::::get().is_emergency() && result.is_err() { + log!(error, "Emergency phase triggered, halting the election."); + } else { + if remaining.is_zero() { + log!(info, "receiving last call to elect(0), rotating round"); + Self::rotate_round() + } else { + Self::phase_transition(Phase::Export(remaining)) + } + } + + result } fn ongoing() -> bool { @@ -1113,356 +1198,365 @@ mod phase_rotation { #[test] fn single_page() { - ExtBuilder::full().pages(1).onchain_fallback(true).build_and_execute(|| { - // 0 -------- 14 15 --------- 20 ------------- 25 ---------- 30 - // | | | | | - // Snapshot Signed SignedValidation Unsigned elect() + ExtBuilder::full() + .pages(1) + .fallback_mode(FallbackModes::Onchain) + .build_and_execute(|| { + // 0 -------- 14 15 --------- 20 ------------- 25 ---------- 30 + // | | | | | + // Snapshot Signed SignedValidation Unsigned elect() - assert_eq!(System::block_number(), 0); - assert_eq!(MultiBlock::current_phase(), Phase::Off); - assert_ok!(Snapshot::::ensure_snapshot(false, 1)); - assert_eq!(MultiBlock::round(), 0); + assert_eq!(System::block_number(), 0); + assert_eq!(MultiBlock::current_phase(), Phase::Off); + assert_ok!(Snapshot::::ensure_snapshot(false, 1)); + assert_eq!(MultiBlock::round(), 0); - roll_to(4); - assert_eq!(MultiBlock::current_phase(), Phase::Off); - assert_eq!(MultiBlock::round(), 0); + roll_to(4); + assert_eq!(MultiBlock::current_phase(), Phase::Off); + assert_eq!(MultiBlock::round(), 0); - roll_to(13); - assert_eq!(MultiBlock::current_phase(), Phase::Off); + roll_to(13); + assert_eq!(MultiBlock::current_phase(), Phase::Off); - roll_to(14); - assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0)); + roll_to(14); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0)); - roll_to(15); - assert_eq!(MultiBlock::current_phase(), Phase::Signed); - assert_eq!( - multi_block_events(), - vec![ - Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(0) }, - Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed } - ] - ); - assert_ok!(Snapshot::::ensure_snapshot(true, 1)); - assert_eq!(MultiBlock::round(), 0); + roll_to(15); + assert_eq!(MultiBlock::current_phase(), Phase::Signed); + assert_eq!( + multi_block_events(), + vec![ + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(0) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed } + ] + ); + assert_ok!(Snapshot::::ensure_snapshot(true, 1)); + assert_eq!(MultiBlock::round(), 0); - roll_to(19); - assert_eq!(MultiBlock::current_phase(), Phase::Signed); - assert_ok!(Snapshot::::ensure_snapshot(true, 1)); - assert_eq!(MultiBlock::round(), 0); + roll_to(19); + assert_eq!(MultiBlock::current_phase(), Phase::Signed); + assert_ok!(Snapshot::::ensure_snapshot(true, 1)); + assert_eq!(MultiBlock::round(), 0); - roll_to(20); - assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(20)); - assert_eq!( - multi_block_events(), - vec![ - Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(0) }, - Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, - Event::PhaseTransitioned { - from: Phase::Signed, - to: Phase::SignedValidation(20) - } - ], - ); - assert_ok!(Snapshot::::ensure_snapshot(true, 1)); + roll_to(20); + assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(20)); + assert_eq!( + multi_block_events(), + vec![ + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(0) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, + Event::PhaseTransitioned { + from: Phase::Signed, + to: Phase::SignedValidation(20) + } + ], + ); + assert_ok!(Snapshot::::ensure_snapshot(true, 1)); - roll_to(24); - assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(20)); - assert_ok!(Snapshot::::ensure_snapshot(true, 1)); - assert_eq!(MultiBlock::round(), 0); + roll_to(24); + assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(20)); + assert_ok!(Snapshot::::ensure_snapshot(true, 1)); + assert_eq!(MultiBlock::round(), 0); - roll_to(25); - assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); - assert_eq!( - multi_block_events(), - vec![ - Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(0) }, - Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, - Event::PhaseTransitioned { - from: Phase::Signed, - to: Phase::SignedValidation(20) - }, - Event::PhaseTransitioned { - from: Phase::SignedValidation(20), - to: Phase::Unsigned(25) - } - ], - ); - assert_ok!(Snapshot::::ensure_snapshot(true, 1)); + roll_to(25); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + assert_eq!( + multi_block_events(), + vec![ + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(0) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, + Event::PhaseTransitioned { + from: Phase::Signed, + to: Phase::SignedValidation(20) + }, + Event::PhaseTransitioned { + from: Phase::SignedValidation(20), + to: Phase::Unsigned(25) + } + ], + ); + assert_ok!(Snapshot::::ensure_snapshot(true, 1)); - roll_to(30); - assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); - assert_ok!(Snapshot::::ensure_snapshot(true, 1)); + roll_to(30); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + assert_ok!(Snapshot::::ensure_snapshot(true, 1)); - // We close when upstream tells us to elect. - roll_to(32); - assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); - assert_ok!(Snapshot::::ensure_snapshot(true, 1)); + // We close when upstream tells us to elect. + roll_to(32); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + assert_ok!(Snapshot::::ensure_snapshot(true, 1)); - MultiBlock::elect(0).unwrap(); + MultiBlock::elect(0).unwrap(); - assert!(MultiBlock::current_phase().is_off()); - assert_ok!(Snapshot::::ensure_snapshot(false, 1)); - assert_eq!(MultiBlock::round(), 1); + assert!(MultiBlock::current_phase().is_off()); + assert_ok!(Snapshot::::ensure_snapshot(false, 1)); + assert_eq!(MultiBlock::round(), 1); - roll_to(43); - assert_eq!(MultiBlock::current_phase(), Phase::Off); + roll_to(43); + assert_eq!(MultiBlock::current_phase(), Phase::Off); - roll_to(44); - assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0)); + roll_to(44); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0)); - roll_to(45); - assert!(MultiBlock::current_phase().is_signed()); + roll_to(45); + assert!(MultiBlock::current_phase().is_signed()); - roll_to(50); - assert!(MultiBlock::current_phase().is_signed_validation_open_at(50)); + roll_to(50); + assert!(MultiBlock::current_phase().is_signed_validation_open_at(50)); - roll_to(55); - assert!(MultiBlock::current_phase().is_unsigned_open_at(55)); - }) + roll_to(55); + assert!(MultiBlock::current_phase().is_unsigned_open_at(55)); + }) } #[test] fn multi_page_2() { - ExtBuilder::full().pages(2).onchain_fallback(true).build_and_execute(|| { - // 0 -------13 14 15 ------- 20 ---- 25 ------- 30 - // | | | | | - // Snapshot Signed SigValid Unsigned Elect + ExtBuilder::full() + .pages(2) + .fallback_mode(FallbackModes::Onchain) + .build_and_execute(|| { + // 0 -------13 14 15 ------- 20 ---- 25 ------- 30 + // | | | | | + // Snapshot Signed SigValid Unsigned Elect - assert_eq!(System::block_number(), 0); - assert_eq!(MultiBlock::current_phase(), Phase::Off); - assert_ok!(Snapshot::::ensure_snapshot(false, 2)); - assert_eq!(MultiBlock::round(), 0); + assert_eq!(System::block_number(), 0); + assert_eq!(MultiBlock::current_phase(), Phase::Off); + assert_ok!(Snapshot::::ensure_snapshot(false, 2)); + assert_eq!(MultiBlock::round(), 0); - roll_to(4); - assert_eq!(MultiBlock::current_phase(), Phase::Off); - assert_eq!(MultiBlock::round(), 0); + roll_to(4); + assert_eq!(MultiBlock::current_phase(), Phase::Off); + assert_eq!(MultiBlock::round(), 0); - roll_to(12); - assert_eq!(MultiBlock::current_phase(), Phase::Off); + roll_to(12); + assert_eq!(MultiBlock::current_phase(), Phase::Off); - roll_to(13); - assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1)); - assert_ok!(Snapshot::::ensure_snapshot(true, 1)); + roll_to(13); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1)); + assert_ok!(Snapshot::::ensure_snapshot(true, 1)); - roll_to(14); - assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0)); - assert_ok!(Snapshot::::ensure_snapshot(true, 2)); + roll_to(14); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0)); + assert_ok!(Snapshot::::ensure_snapshot(true, 2)); - roll_to(15); - assert_eq!(MultiBlock::current_phase(), Phase::Signed); - assert_eq!( - multi_block_events(), - vec![ - Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(1) }, - Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed } - ] - ); - assert_ok!(Snapshot::::ensure_snapshot(true, 2)); - assert_eq!(MultiBlock::round(), 0); + roll_to(15); + assert_eq!(MultiBlock::current_phase(), Phase::Signed); + assert_eq!( + multi_block_events(), + vec![ + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(1) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed } + ] + ); + assert_ok!(Snapshot::::ensure_snapshot(true, 2)); + assert_eq!(MultiBlock::round(), 0); - roll_to(19); - assert_eq!(MultiBlock::current_phase(), Phase::Signed); - assert_ok!(Snapshot::::ensure_snapshot(true, 2)); - assert_eq!(MultiBlock::round(), 0); + roll_to(19); + assert_eq!(MultiBlock::current_phase(), Phase::Signed); + assert_ok!(Snapshot::::ensure_snapshot(true, 2)); + assert_eq!(MultiBlock::round(), 0); - roll_to(20); - assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(20)); - assert_eq!( - multi_block_events(), - vec![ - Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(1) }, - Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, - Event::PhaseTransitioned { - from: Phase::Signed, - to: Phase::SignedValidation(20) - } - ], - ); - assert_ok!(Snapshot::::ensure_snapshot(true, 2)); + roll_to(20); + assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(20)); + assert_eq!( + multi_block_events(), + vec![ + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(1) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, + Event::PhaseTransitioned { + from: Phase::Signed, + to: Phase::SignedValidation(20) + } + ], + ); + assert_ok!(Snapshot::::ensure_snapshot(true, 2)); - roll_to(24); - assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(20)); - assert_ok!(Snapshot::::ensure_snapshot(true, 2)); - assert_eq!(MultiBlock::round(), 0); + roll_to(24); + assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(20)); + assert_ok!(Snapshot::::ensure_snapshot(true, 2)); + assert_eq!(MultiBlock::round(), 0); - roll_to(25); - assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); - assert_eq!( - multi_block_events(), - vec![ - Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(1) }, - Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, - Event::PhaseTransitioned { - from: Phase::Signed, - to: Phase::SignedValidation(20) - }, - Event::PhaseTransitioned { - from: Phase::SignedValidation(20), - to: Phase::Unsigned(25) - } - ], - ); - assert_ok!(Snapshot::::ensure_snapshot(true, 2)); + roll_to(25); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + assert_eq!( + multi_block_events(), + vec![ + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(1) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, + Event::PhaseTransitioned { + from: Phase::Signed, + to: Phase::SignedValidation(20) + }, + Event::PhaseTransitioned { + from: Phase::SignedValidation(20), + to: Phase::Unsigned(25) + } + ], + ); + assert_ok!(Snapshot::::ensure_snapshot(true, 2)); - roll_to(29); - assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); - assert_ok!(Snapshot::::ensure_snapshot(true, 2)); + roll_to(29); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + assert_ok!(Snapshot::::ensure_snapshot(true, 2)); - roll_to(30); - assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); - assert_ok!(Snapshot::::ensure_snapshot(true, 2)); + roll_to(30); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + assert_ok!(Snapshot::::ensure_snapshot(true, 2)); - // We close when upstream tells us to elect. - roll_to(32); - assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + // We close when upstream tells us to elect. + roll_to(32); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); - MultiBlock::elect(0).unwrap(); // and even this one's coming from the fallback. - assert!(MultiBlock::current_phase().is_off()); + MultiBlock::elect(0).unwrap(); // and even this one's coming from the fallback. + assert!(MultiBlock::current_phase().is_off()); - // all snapshots are gone. - assert_ok!(Snapshot::::ensure_snapshot(false, 2)); - assert_eq!(MultiBlock::round(), 1); + // all snapshots are gone. + assert_ok!(Snapshot::::ensure_snapshot(false, 2)); + assert_eq!(MultiBlock::round(), 1); - roll_to(42); - assert_eq!(MultiBlock::current_phase(), Phase::Off); + roll_to(42); + assert_eq!(MultiBlock::current_phase(), Phase::Off); - roll_to(43); - assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1)); + roll_to(43); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1)); - roll_to(44); - assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0)); + roll_to(44); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0)); - roll_to(45); - assert!(MultiBlock::current_phase().is_signed()); + roll_to(45); + assert!(MultiBlock::current_phase().is_signed()); - roll_to(50); - assert!(MultiBlock::current_phase().is_signed_validation_open_at(50)); + roll_to(50); + assert!(MultiBlock::current_phase().is_signed_validation_open_at(50)); - roll_to(55); - assert!(MultiBlock::current_phase().is_unsigned_open_at(55)); - }) + roll_to(55); + assert!(MultiBlock::current_phase().is_unsigned_open_at(55)); + }) } #[test] fn multi_page_3() { - ExtBuilder::full().pages(3).onchain_fallback(true).build_and_execute(|| { - // 0 ------- 12 13 14 15 ----------- 20 ---------25 ------- 30 - // | | | | | - // Snapshot Signed SignedValidation Unsigned Elect + ExtBuilder::full() + .pages(3) + .fallback_mode(FallbackModes::Onchain) + .build_and_execute(|| { + // 0 ------- 12 13 14 15 ----------- 20 ---------25 ------- 30 + // | | | | | + // Snapshot Signed SignedValidation Unsigned Elect - assert_eq!(System::block_number(), 0); - assert_eq!(MultiBlock::current_phase(), Phase::Off); - assert_ok!(Snapshot::::ensure_snapshot(false, 3)); - assert_eq!(MultiBlock::round(), 0); + assert_eq!(System::block_number(), 0); + assert_eq!(MultiBlock::current_phase(), Phase::Off); + assert_ok!(Snapshot::::ensure_snapshot(false, 3)); + assert_eq!(MultiBlock::round(), 0); - roll_to(4); - assert_eq!(MultiBlock::current_phase(), Phase::Off); - assert_eq!(MultiBlock::round(), 0); + roll_to(4); + assert_eq!(MultiBlock::current_phase(), Phase::Off); + assert_eq!(MultiBlock::round(), 0); - roll_to(11); - assert_eq!(MultiBlock::current_phase(), Phase::Off); + roll_to(11); + assert_eq!(MultiBlock::current_phase(), Phase::Off); - roll_to(12); - assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(2)); - assert_ok!(Snapshot::::ensure_snapshot(true, 1)); + roll_to(12); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(2)); + assert_ok!(Snapshot::::ensure_snapshot(true, 1)); - roll_to(13); - assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1)); - assert_ok!(Snapshot::::ensure_snapshot(true, 2)); + roll_to(13); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1)); + assert_ok!(Snapshot::::ensure_snapshot(true, 2)); - roll_to(14); - assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0)); - assert_ok!(Snapshot::::ensure_snapshot(true, 3)); + roll_to(14); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0)); + assert_ok!(Snapshot::::ensure_snapshot(true, 3)); - roll_to(15); - assert_eq!(MultiBlock::current_phase(), Phase::Signed); - assert_eq!( - multi_block_events(), - vec![ - Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(2) }, - Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed } - ] - ); - assert_eq!(MultiBlock::round(), 0); + roll_to(15); + assert_eq!(MultiBlock::current_phase(), Phase::Signed); + assert_eq!( + multi_block_events(), + vec![ + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(2) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed } + ] + ); + assert_eq!(MultiBlock::round(), 0); - roll_to(19); - assert_eq!(MultiBlock::current_phase(), Phase::Signed); - assert_eq!(MultiBlock::round(), 0); + roll_to(19); + assert_eq!(MultiBlock::current_phase(), Phase::Signed); + assert_eq!(MultiBlock::round(), 0); - roll_to(20); - assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(20)); - assert_eq!( - multi_block_events(), - vec![ - Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(2) }, - Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, - Event::PhaseTransitioned { - from: Phase::Signed, - to: Phase::SignedValidation(20) - } - ] - ); + roll_to(20); + assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(20)); + assert_eq!( + multi_block_events(), + vec![ + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(2) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, + Event::PhaseTransitioned { + from: Phase::Signed, + to: Phase::SignedValidation(20) + } + ] + ); - roll_to(24); - assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(20)); - assert_eq!(MultiBlock::round(), 0); + roll_to(24); + assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(20)); + assert_eq!(MultiBlock::round(), 0); - roll_to(25); - assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); - assert_eq!( - multi_block_events(), - vec![ - Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(2) }, - Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, - Event::PhaseTransitioned { - from: Phase::Signed, - to: Phase::SignedValidation(20) - }, - Event::PhaseTransitioned { - from: Phase::SignedValidation(20), - to: Phase::Unsigned(25) - } - ] - ); + roll_to(25); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + assert_eq!( + multi_block_events(), + vec![ + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(2) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, + Event::PhaseTransitioned { + from: Phase::Signed, + to: Phase::SignedValidation(20) + }, + Event::PhaseTransitioned { + from: Phase::SignedValidation(20), + to: Phase::Unsigned(25) + } + ] + ); - roll_to(29); - assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + roll_to(29); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); - roll_to(30); - assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + roll_to(30); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); - // We close when upstream tells us to elect. - roll_to(32); - assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); + // We close when upstream tells us to elect. + roll_to(32); + assert_eq!(MultiBlock::current_phase(), Phase::Unsigned(25)); - MultiBlock::elect(0).unwrap(); - assert!(MultiBlock::current_phase().is_off()); + MultiBlock::elect(0).unwrap(); + assert!(MultiBlock::current_phase().is_off()); - // all snapshots are gone. - assert_none_snapshot(); - assert_eq!(MultiBlock::round(), 1); + // all snapshots are gone. + assert_none_snapshot(); + assert_eq!(MultiBlock::round(), 1); - roll_to(41); - assert_eq!(MultiBlock::current_phase(), Phase::Off); + roll_to(41); + assert_eq!(MultiBlock::current_phase(), Phase::Off); - roll_to(42); - assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(2)); + roll_to(42); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(2)); - roll_to(43); - assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1)); + roll_to(43); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(1)); - roll_to(44); - assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0)); + roll_to(44); + assert_eq!(MultiBlock::current_phase(), Phase::Snapshot(0)); - roll_to(45); - assert!(MultiBlock::current_phase().is_signed()); + roll_to(45); + assert!(MultiBlock::current_phase().is_signed()); - roll_to(50); - assert!(MultiBlock::current_phase().is_signed_validation_open_at(50)); + roll_to(50); + assert!(MultiBlock::current_phase().is_signed_validation_open_at(50)); - roll_to(55); - assert!(MultiBlock::current_phase().is_unsigned_open_at(55)); - }) + roll_to(55); + assert!(MultiBlock::current_phase().is_unsigned_open_at(55)); + }) } #[test] @@ -1470,7 +1564,7 @@ mod phase_rotation { ExtBuilder::full() .pages(3) .lookahead(2) - .onchain_fallback(true) + .fallback_mode(FallbackModes::Onchain) .build_and_execute(|| { // 0 ------- 10 11 12 13 ----------- 17 ---------22 ------- 27 // | | | | | @@ -1598,7 +1692,7 @@ mod phase_rotation { ExtBuilder::full() .pages(3) .unsigned_phase(0) - .onchain_fallback(true) + .fallback_mode(FallbackModes::Onchain) .build_and_execute(|| { // 0 --------------------- 17 ------ 20 ---------25 ------- 30 // | | | | | @@ -1662,7 +1756,7 @@ mod phase_rotation { ExtBuilder::full() .pages(3) .signed_phase(0, 0) - .onchain_fallback(true) + .fallback_mode(FallbackModes::Onchain) .build_and_execute(|| { // 0 ------------------------- 22 ------ 25 ------- 30 // | | | @@ -1738,8 +1832,10 @@ mod phase_rotation { mod election_provider { use super::*; use crate::{mock::*, unsigned::miner::BaseMiner, verifier::Verifier, Phase}; - use frame_election_provider_support::ElectionProvider; - use frame_support::{assert_storage_noop, unsigned::ValidateUnsigned}; + use frame_election_provider_support::{BoundedSupport, BoundedSupports, ElectionProvider}; + use frame_support::{ + assert_storage_noop, testing_prelude::bounded_vec, unsigned::ValidateUnsigned, + }; // This is probably the most important test of all, a basic, correct scenario. This test should // be studied in detail, and all of the branches of how it can go wrong or diverge from the @@ -2026,11 +2122,11 @@ mod election_provider { // try submit one signed page: assert_noop!( SignedPallet::submit_page(RuntimeOrigin::signed(999), 0, Default::default()), - "phase not signed" + crate::signed::Error::::PhaseNotSigned, ); assert_noop!( SignedPallet::register(RuntimeOrigin::signed(999), Default::default()), - "phase not signed" + crate::signed::Error::::PhaseNotSigned, ); assert_storage_noop!(assert!(::pre_dispatch( &unsigned::Call::submit_unsigned { paged_solution: Default::default() } @@ -2041,17 +2137,65 @@ mod election_provider { #[test] fn multi_page_elect_fallback_works() { - ExtBuilder::full().onchain_fallback(true).build_and_execute(|| { + ExtBuilder::full().fallback_mode(FallbackModes::Onchain).build_and_execute(|| { roll_to_signed_open(); - // but then we immediately call `elect`. - assert_eq!(MultiBlock::elect(2), Err(ElectionError::SupportPageNotAvailable)); + // same targets, but voters from page 2 (1, 2, 3, 4, see `mock/staking`). + assert_eq!( + MultiBlock::elect(2).unwrap(), + BoundedSupports(bounded_vec![ + (10, BoundedSupport { total: 15, voters: bounded_vec![(1, 10), (4, 5)] }), + ( + 40, + BoundedSupport { + total: 25, + voters: bounded_vec![(2, 10), (3, 10), (4, 5)] + } + ) + ]) + ); + // page 1 of voters + assert_eq!( + MultiBlock::elect(1).unwrap(), + BoundedSupports(bounded_vec![ + (10, BoundedSupport { total: 15, voters: bounded_vec![(5, 5), (8, 10)] }), + ( + 30, + BoundedSupport { + total: 25, + voters: bounded_vec![(5, 5), (6, 10), (7, 10)] + } + ) + ]) + ); + // self votes + assert_eq!( + MultiBlock::elect(0).unwrap(), + BoundedSupports(bounded_vec![ + (30, BoundedSupport { total: 30, voters: bounded_vec![(30, 30)] }), + (40, BoundedSupport { total: 40, voters: bounded_vec![(40, 40)] }) + ]) + ); + + assert_eq!( + multi_block_events(), + vec![ + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(2) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, + Event::PhaseTransitioned { from: Phase::Signed, to: Phase::Export(2) }, + Event::PhaseTransitioned { from: Phase::Export(1), to: Phase::Off } + ] + ); + assert_eq!(verifier_events(), vec![]); // This will set us to emergency phase, because we don't know wtf to do. - assert_eq!(MultiBlock::current_phase(), Phase::Emergency); + assert_eq!(MultiBlock::current_phase(), Phase::Off); }); + } - ExtBuilder::full().onchain_fallback(true).build_and_execute(|| { + #[test] + fn multi_page_fallback_shortcut_to_msp_works() { + ExtBuilder::full().fallback_mode(FallbackModes::Onchain).build_and_execute(|| { roll_to_signed_open(); // but then we immediately call `elect`, this will work @@ -2073,12 +2217,12 @@ mod election_provider { #[test] fn elect_call_when_not_ongoing() { - ExtBuilder::full().onchain_fallback(true).build_and_execute(|| { + ExtBuilder::full().fallback_mode(FallbackModes::Onchain).build_and_execute(|| { roll_to_snapshot_created(); assert_eq!(MultiBlock::ongoing(), true); assert!(MultiBlock::elect(0).is_ok()); }); - ExtBuilder::full().onchain_fallback(true).build_and_execute(|| { + ExtBuilder::full().fallback_mode(FallbackModes::Onchain).build_and_execute(|| { roll_to(10); assert_eq!(MultiBlock::ongoing(), false); assert_eq!(MultiBlock::elect(0), Err(ElectionError::NotOngoing)); @@ -2100,7 +2244,7 @@ mod admin_ops { // we get a call to elect(0). this will cause emergency, since no fallback is allowed. assert_eq!( MultiBlock::elect(0), - Err(ElectionError::Fallback("Emergency phase started.")) + Err(ElectionError::Fallback("Emergency phase started.".to_string())) ); assert_eq!(MultiBlock::current_phase(), Phase::Emergency); @@ -2136,50 +2280,54 @@ mod admin_ops { #[test] fn trigger_fallback_works() { - ExtBuilder::full().build_and_execute(|| { - roll_to_signed_open(); + ExtBuilder::full() + .fallback_mode(FallbackModes::Emergency) + .build_and_execute(|| { + roll_to_signed_open(); - // we get a call to elect(0). this will cause emergency, since no fallback is allowed. - assert_eq!( - MultiBlock::elect(0), - Err(ElectionError::Fallback("Emergency phase started.")) - ); - assert_eq!(MultiBlock::current_phase(), Phase::Emergency); + // we get a call to elect(0). this will cause emergency, since no fallback is + // allowed. + assert_eq!( + MultiBlock::elect(0), + Err(ElectionError::Fallback("Emergency phase started.".to_string())) + ); + assert_eq!(MultiBlock::current_phase(), Phase::Emergency); - // we can now set the solution to emergency. - OnChianFallback::set(true); - assert_ok!(MultiBlock::manage( - RuntimeOrigin::root(), - AdminOperation::EmergencyFallback - )); + // we can now set the solution to emergency, assuming fallback is set to onchain + FallbackMode::set(FallbackModes::Onchain); + assert_ok!(MultiBlock::manage( + RuntimeOrigin::root(), + AdminOperation::EmergencyFallback + )); - assert_eq!(MultiBlock::current_phase(), Phase::Emergency); - assert_ok!(MultiBlock::elect(0)); - assert_eq!(MultiBlock::current_phase(), Phase::Off); + assert_eq!(MultiBlock::current_phase(), Phase::Emergency); + assert_ok!(MultiBlock::elect(0)); + assert_eq!(MultiBlock::current_phase(), Phase::Off); - assert_eq!( - multi_block_events(), - vec![ - Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(2) }, - Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, - Event::PhaseTransitioned { from: Phase::Signed, to: Phase::Emergency }, - Event::PhaseTransitioned { from: Phase::Emergency, to: Phase::Off } - ] - ); - assert_eq!( - verifier_events(), - vec![verifier::Event::Queued( - ElectionScore { minimal_stake: 55, sum_stake: 130, sum_stake_squared: 8650 }, - None - )] - ); - }) + assert_eq!( + multi_block_events(), + vec![ + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(2) }, + Event::PhaseTransitioned { from: Phase::Snapshot(0), to: Phase::Signed }, + Event::PhaseTransitioned { from: Phase::Signed, to: Phase::Emergency }, + Event::PhaseTransitioned { from: Phase::Emergency, to: Phase::Off } + ] + ); + assert_eq!( + verifier_events(), + vec![verifier::Event::Queued( + ElectionScore { minimal_stake: 15, sum_stake: 40, sum_stake_squared: 850 }, + None + )] + ); + }) } #[test] fn force_rotate_round() { // clears the snapshot and verifier data. // leaves the signed data as is since we bump the round. + todo!(); } #[test] diff --git a/substrate/frame/election-provider-multi-block/src/mock/mod.rs b/substrate/frame/election-provider-multi-block/src/mock/mod.rs index 3a23f76855fb4..9111d4babb1c8 100644 --- a/substrate/frame/election-provider-multi-block/src/mock/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/mock/mod.rs @@ -31,7 +31,7 @@ use crate::{ use codec::{Decode, Encode, MaxEncodedLen}; use frame_election_provider_support::{ bounds::{ElectionBounds, ElectionBoundsBuilder}, - NposSolution, SequentialPhragmen, + InstantElectionProvider, NposSolution, SequentialPhragmen, }; pub use frame_support::{assert_noop, assert_ok}; use frame_support::{ @@ -41,7 +41,7 @@ use frame_support::{ traits::{fungible::InspectHold, Hooks}, weights::{constants, Weight}, }; -use frame_system::{pallet_prelude::*, EnsureRoot}; +use frame_system::EnsureRoot; use parking_lot::RwLock; pub use signed::*; use sp_core::{ @@ -54,7 +54,6 @@ use sp_core::{ use sp_npos_elections::EvaluateSupport; use sp_runtime::{ bounded_vec, - testing::Header, traits::{BlakeTwo256, IdentityLookup}, BuildStorage, PerU16, Perbill, }; @@ -122,13 +121,20 @@ impl pallet_balances::Config for Runtime { type WeightInfo = (); } +#[derive(Clone)] +pub enum FallbackModes { + Continue, + Emergency, + Onchain, +} + parameter_types! { pub static Pages: PageIndex = 3; pub static UnsignedPhase: BlockNumber = 5; pub static SignedPhase: BlockNumber = 5; pub static SignedValidationPhase: BlockNumber = 5; - pub static OnChianFallback: bool = false; + pub static FallbackMode: FallbackModes = FallbackModes::Emergency; pub static MinerTxPriority: u64 = 100; pub static SolutionImprovementThreshold: Perbill = Perbill::zero(); pub static OffchainRepeat: BlockNumber = 5; @@ -215,22 +221,14 @@ pub struct MockFallback; impl ElectionProvider for MockFallback { type AccountId = AccountId; type BlockNumber = u64; - type Error = &'static str; + type Error = String; type DataProvider = staking::MockStaking; type Pages = ConstU32<1>; type MaxBackersPerWinner = MaxBackersPerWinner; type MaxWinnersPerPage = MaxWinnersPerPage; - fn elect(remaining: PageIndex) -> Result, Self::Error> { - if OnChianFallback::get() { - onchain::OnChainExecution::::elect(remaining) - .map_err(|_| "onchain::OnChainExecution failed") - } else { - // NOTE: this pesky little trick here is to avoid a clash of type, since `Ok` of our - // election provider and our fallback is not the same - let err = InitiateEmergencyPhase::::elect(remaining).unwrap_err(); - Err(err) - } + fn elect(_remaining: PageIndex) -> Result, Self::Error> { + unreachable!() } fn ongoing() -> bool { @@ -238,6 +236,35 @@ impl ElectionProvider for MockFallback { } } +impl InstantElectionProvider for MockFallback { + fn instant_elect( + voters: Vec>, + targets: Vec, + desired_targets: u32, + ) -> Result, Self::Error> { + match FallbackMode::get() { + FallbackModes::Continue => + crate::Continue::::instant_elect(voters, targets, desired_targets) + .map_err(|x| x.to_string()), + FallbackModes::Emergency => crate::InitiateEmergencyPhase::::instant_elect( + voters, + targets, + desired_targets, + ) + .map_err(|x| x.to_string()), + FallbackModes::Onchain => onchain::OnChainExecution::::instant_elect( + voters, + targets, + desired_targets, + ) + .map_err(|e| format!("onchain fallback failed: {:?}", e)), + } + } + fn bother() -> bool { + matches!(FallbackMode::get(), FallbackModes::Onchain) + } +} + impl frame_system::offchain::CreateTransactionBase for Runtime where RuntimeCall: From, @@ -340,8 +367,8 @@ impl ExtBuilder { staking::VOTERS.with(|v| v.borrow_mut().push((who, stake, targets.try_into().unwrap()))); self } - pub(crate) fn onchain_fallback(self, enable: bool) -> Self { - OnChianFallback::set(enable); + pub(crate) fn fallback_mode(self, mode: FallbackModes) -> Self { + FallbackMode::set(mode); self } pub(crate) fn build_unchecked(self) -> sp_io::TestExternalities { diff --git a/substrate/frame/election-provider-multi-block/src/mock/signed.rs b/substrate/frame/election-provider-multi-block/src/mock/signed.rs index d83c65183f316..c59648d722b5b 100644 --- a/substrate/frame/election-provider-multi-block/src/mock/signed.rs +++ b/substrate/frame/election-provider-multi-block/src/mock/signed.rs @@ -190,7 +190,7 @@ pub fn load_signed_for_verification_and_start( pub fn load_signed_for_verification_and_start_and_roll_to_verified( who: AccountId, paged: PagedRawSolution, - round: u32, + _round: u32, ) { load_signed_for_verification(who, paged.clone()); diff --git a/substrate/frame/election-provider-multi-block/src/mock/staking.rs b/substrate/frame/election-provider-multi-block/src/mock/staking.rs index b92c385402098..b1b2706a5e733 100644 --- a/substrate/frame/election-provider-multi-block/src/mock/staking.rs +++ b/substrate/frame/election-provider-multi-block/src/mock/staking.rs @@ -20,11 +20,12 @@ use crate::VoterOf; use frame_election_provider_support::{ data_provider, DataProviderBounds, ElectionDataProvider, PageIndex, VoteWeight, }; -use frame_support::{pallet_prelude::*, BoundedVec}; -use frame_system::pallet_prelude::*; +use frame_support::pallet_prelude::*; use sp_core::bounded_vec; use sp_std::prelude::*; +pub type T = Runtime; + frame_support::parameter_types! { pub static Targets: Vec = vec![10, 20, 30, 40]; pub static Voters: Vec> = vec![ @@ -63,7 +64,10 @@ impl ElectionDataProvider for MockStaking { let targets = Targets::get(); if remaining != 0 { - return Err("targets shall not have more than a single page") + crate::log!( + warn, + "requesting targets for non-zero page, we will return the same page in any case" + ); } if bounds.slice_exhausted(&targets) { return Err("Targets too big") @@ -167,14 +171,15 @@ mod tests { ExtBuilder::full().build_and_execute(|| { assert_eq!(Targets::get().len(), 4); - // any non-zero page is error - assert!(MockStaking::electable_targets(bound_by_count(None), 1).is_err()); - assert!(MockStaking::electable_targets(bound_by_count(None), 2).is_err()); + // any non-zero page returns page zero. + assert_eq!(MockStaking::electable_targets(bound_by_count(None), 2).unwrap().len(), 4); + assert_eq!(MockStaking::electable_targets(bound_by_count(None), 1).unwrap().len(), 4); - // but 0 is fine. + // 0 is also fine. assert_eq!(MockStaking::electable_targets(bound_by_count(None), 0).unwrap().len(), 4); - // fetch less targets is error. + // fetch less targets is error, because targets cannot be sorted (both by MockStaking, + // and the real staking). assert!(MockStaking::electable_targets(bound_by_count(Some(2)), 0).is_err()); // more targets is fine. diff --git a/substrate/frame/election-provider-multi-block/src/signed/mod.rs b/substrate/frame/election-provider-multi-block/src/signed/mod.rs index 954a0ac32e4d5..0953957087fab 100644 --- a/substrate/frame/election-provider-multi-block/src/signed/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/signed/mod.rs @@ -48,7 +48,7 @@ use frame_support::{ pallet_prelude::{StorageDoubleMap, ValueQuery, *}, traits::{ tokens::{ - fungible::{Inspect, InspectHold, Mutate, MutateHold}, + fungible::{Inspect, Mutate, MutateHold}, Fortitude, Precision, }, Defensive, DefensiveSaturating, EstimateCallFee, TryCollect, diff --git a/substrate/frame/election-provider-multi-block/src/types.rs b/substrate/frame/election-provider-multi-block/src/types.rs index 859774be247c8..6a4e77843dd6b 100644 --- a/substrate/frame/election-provider-multi-block/src/types.rs +++ b/substrate/frame/election-provider-multi-block/src/types.rs @@ -203,6 +203,7 @@ pub struct SolutionOrSnapshotSize { pub targets: u32, } +// TODO: we are not using this anywhere. /// The type of `Computation` that provided this election data. #[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] pub enum ElectionCompute { diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs b/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs index e696de2414a25..b06482b1ba8a9 100644 --- a/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs +++ b/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs @@ -680,7 +680,6 @@ impl OffchainWorkerMiner { } fn submit_call(call: Call) -> Result<(), OffchainMinerError> { - // TODO: need to pagify the unsigned solution as well, maybe sublog!( debug, "unsigned::ocw-miner", @@ -1539,7 +1538,7 @@ mod base_miner { assert_eq!( paged.solution_pages, vec![ - // this can be 'pagified" to snapshot at index 1, which contains 5, 6, 7, 8 + // this can be "pagified" to snapshot at index 1, which contains 5, 6, 7, 8 // in which: // 6 (index:1) votes for 40 (index:3) // 8 (index:1) votes for 10 (index:0) @@ -1966,6 +1965,42 @@ mod offchain_worker_miner { }) } + #[test] + fn multi_page_ocw_e2e_submits_and_queued_msp_only() { + let (mut ext, pool) = ExtBuilder::unsigned().build_offchainify(); + ext.execute_with_sanity_checks(|| { + assert!(VerifierPallet::queued_score().is_none()); + + roll_to_with_ocw(25 + 1, Some(pool.clone())); + + assert_eq!( + multi_block_events(), + vec![ + crate::Event::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(2) }, + crate::Event::PhaseTransitioned { + from: Phase::Snapshot(0), + to: Phase::Unsigned(25) + } + ] + ); + assert_eq!( + verifier_events(), + vec![ + crate::verifier::Event::Verified(2, 2), + crate::verifier::Event::Queued( + ElectionScore { minimal_stake: 15, sum_stake: 40, sum_stake_squared: 850 }, + None + ) + ] + ); + + assert!(VerifierPallet::queued_score().is_some()); + + // pool is empty + assert_eq!(pool.read().transactions.len(), 0); + }) + } + #[test] fn will_not_mine_if_not_enough_winners() { // also see `trim_weight_too_much_makes_solution_invalid`. diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs b/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs index 932a513129e0a..471ee54f942c8 100644 --- a/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs @@ -16,6 +16,45 @@ // limitations under the License. //! The unsigned phase, and its miner. +//! +//! TODO: the following is the idea of how to implement multi-page unsigned, which we don't have. +//! +//! ## Multi-block unsigned submission +//! +//! The process of allowing validators to coordinate to submit a multi-page solution is new to this +//! pallet, and non-existent in the multi-phase pallet. The process is as follows: +//! +//! All validators will run their miners and compute the full paginated solution. They submit all +//! pages as individual unsigned transactions to their local tx-pool. +//! +//! Upon validation, if any page is now present the corresponding transaction is dropped. +//! +//! At each block, the first page that may be valid is included as a high priority operational +//! transaction. This page is validated on the fly to be correct. Since this transaction is sourced +//! from a validator, we can panic if they submit an invalid transaction. +//! +//! Then, once the final page is submitted, some extra checks are done, as explained in +//! [`crate::verifier`]: +//! +//! 1. bounds +//! 2. total score +//! +//! These checks might still fail. If they do, the solution is dropped. At this point, we don't know +//! which validator may have submitted a slightly-faulty solution. +//! +//! In order to prevent this, the validation process always includes a check to ensure all of the +//! previous pages that have been submitted match what the local validator has computed. If they +//! match, the validator knows that they are putting skin in a game that is valid. +//! +//! If any bad paged are detected, the next validator can bail. This process means: +//! +//! * As long as all validators are honest, and run the same miner code, a correct solution is +//! found. +//! * As little as one malicious validator can stall the process, but no one is accidentally +//! slashed, and no panic happens. +//! +//! A future improvement should keep track of submitters, and report a slash if it occurs. Or, if +//! the signed process is bullet-proof, we can be okay with the status quo. /// Exports of this pallet pub use pallet::*; @@ -99,6 +138,9 @@ mod pallet { /// This works very much like an inherent, as only the validators are permitted to submit /// anything. By default validators will compute this call in their `offchain_worker` hook /// and try and submit it back. + /// + /// This is different from signed page submission mainly in that the solution page is + /// verified on the fly. #[pallet::weight((0, DispatchClass::Operational))] #[pallet::call_index(0)] pub fn submit_unsigned( diff --git a/substrate/frame/election-provider-multi-block/src/verifier/impls.rs b/substrate/frame/election-provider-multi-block/src/verifier/impls.rs index b60882f64c8e0..ccb923acf16b7 100644 --- a/substrate/frame/election-provider-multi-block/src/verifier/impls.rs +++ b/substrate/frame/election-provider-multi-block/src/verifier/impls.rs @@ -233,10 +233,10 @@ pub(crate) mod pallet { // TODO: safe wrapper around this that clears exactly pages keys, and ensures none is // left. match Self::invalid() { - ValidSolution::X => QueuedSolutionX::::clear(u32::MAX, None), - ValidSolution::Y => QueuedSolutionY::::clear(u32::MAX, None), + ValidSolution::X => clear_paged_map!(QueuedSolutionX::), + ValidSolution::Y => clear_paged_map!(QueuedSolutionY::), }; - let _ = QueuedSolutionBackings::::clear(u32::MAX, None); + clear_paged_map!(QueuedSolutionBackings::); } /// Write a single page of a valid solution into the `invalid` variant of the storage. @@ -278,8 +278,8 @@ pub(crate) mod pallet { Self::mutate_checked(|| { // clear everything about valid solutions. match Self::valid() { - ValidSolution::X => QueuedSolutionX::::clear(u32::MAX, None), - ValidSolution::Y => QueuedSolutionY::::clear(u32::MAX, None), + ValidSolution::X => clear_paged_map!(QueuedSolutionX::), + ValidSolution::Y => clear_paged_map!(QueuedSolutionY::), }; QueuedSolutionScore::::kill(); @@ -299,10 +299,10 @@ pub(crate) mod pallet { /// Should only be called once everything is done. pub(crate) fn kill() { Self::mutate_checked(|| { - QueuedSolutionX::::clear(u32::MAX, None); - QueuedSolutionY::::clear(u32::MAX, None); + clear_paged_map!(QueuedSolutionX::); + clear_paged_map!(QueuedSolutionY::); QueuedValidVariant::::kill(); - QueuedSolutionBackings::::clear(u32::MAX, None); + clear_paged_map!(QueuedSolutionBackings::); QueuedSolutionScore::::kill(); }) } @@ -621,7 +621,7 @@ impl Pallet { ensure!(truth_score == claimed_score, FeasibilityError::InvalidScore); // and finally queue the solution. - QueuedSolution::::force_set_single_page_valid(0, supports.clone(), truth_score); + QueuedSolution::::force_set_single_page_valid(page, supports.clone(), truth_score); Ok(supports) } @@ -803,13 +803,25 @@ impl Verifier for Pallet { let maybe_current_score = Self::queued_score(); match Self::do_verify_synchronous(partial_solution, claimed_score, page) { Ok(supports) => { - sublog!(info, "verifier", "queued a sync solution with score {:?}.", claimed_score); + sublog!( + info, + "verifier", + "queued a sync solution with score {:?} for page {}", + claimed_score, + page + ); Self::deposit_event(Event::::Verified(page, supports.len() as u32)); Self::deposit_event(Event::::Queued(claimed_score, maybe_current_score)); Ok(supports) }, Err(fe) => { - sublog!(warn, "verifier", "sync verification failed due to {:?}.", fe); + sublog!( + warn, + "verifier", + "sync verification of page {} failed due to {:?}.", + page, + fe + ); Self::deposit_event(Event::::VerificationFailed(page, fe.clone())); Err(fe) }, diff --git a/substrate/frame/election-provider-multi-block/src/verifier/mod.rs b/substrate/frame/election-provider-multi-block/src/verifier/mod.rs index f2f332fe707e5..014371070cbda 100644 --- a/substrate/frame/election-provider-multi-block/src/verifier/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/verifier/mod.rs @@ -191,6 +191,10 @@ pub trait Verifier { page: PageIndex, ) -> Result, FeasibilityError>; + /// Force set a single page solution as the valid one. + /// + /// Will erase any previous solution. Should only be used in case of emergency fallbacks and + /// similar. fn force_set_single_page_valid( partial_supports: SupportsOf, page: PageIndex, diff --git a/substrate/frame/election-provider-multi-block/src/verifier/tests.rs b/substrate/frame/election-provider-multi-block/src/verifier/tests.rs index 17014547111c5..a9b340f6a598a 100644 --- a/substrate/frame/election-provider-multi-block/src/verifier/tests.rs +++ b/substrate/frame/election-provider-multi-block/src/verifier/tests.rs @@ -37,7 +37,7 @@ mod feasibility_check { let paged = mine_full_solution().unwrap(); // ..remove the only page of the target snapshot. - crate::Snapshot::::remove_target_page(0); + crate::Snapshot::::remove_target_page(); assert_noop!( VerifierPallet::feasibility_check_page_inner(paged.solution_pages[0].clone(), 0), @@ -89,7 +89,7 @@ mod feasibility_check { let paged = mine_full_solution().unwrap(); // `DesiredTargets` is not checked here. - crate::Snapshot::::remove_target_page(0); + crate::Snapshot::::remove_target_page(); assert_noop!( VerifierPallet::feasibility_check_page_inner(paged.solution_pages[1].clone(), 0), @@ -1071,12 +1071,15 @@ mod sync_verification { MultiBlock::msp(), ) .unwrap_err(), - FeasibilityError::WrongWinnerCount + FeasibilityError::FailedToBoundSupport ); assert_eq!( verifier_events(), - vec![Event::::VerificationFailed(2, FeasibilityError::WrongWinnerCount)] + vec![Event::::VerificationFailed( + 2, + FeasibilityError::FailedToBoundSupport + )] ); }); } diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index a4f092ab4442c..dc988369efc82 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -246,8 +246,8 @@ extern crate alloc; use alloc::{boxed::Box, vec::Vec}; use codec::{Decode, Encode}; use frame_election_provider_support::{ - bounds::{CountBound, ElectionBounds, ElectionBoundsBuilder, SizeBound}, - BoundedSupports, BoundedSupportsOf, DataProviderBounds, ElectionDataProvider, ElectionProvider, + bounds::{CountBound, ElectionBounds, SizeBound}, + BoundedSupports, BoundedSupportsOf, ElectionDataProvider, ElectionProvider, InstantElectionProvider, NposSolution, PageIndex, }; use frame_support::{ @@ -768,8 +768,6 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { fn on_initialize(now: BlockNumberFor) -> Weight { - // TODO: a hack for now to prevent this pallet from doing anything. - return Default::default(); let next_election = T::DataProvider::next_election_prediction(now).max(now); let signed_deadline = T::SignedPhase::get() + T::UnsignedPhase::get(); @@ -1114,24 +1112,17 @@ pub mod pallet { /// 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, - maybe_max_voters: Option, - maybe_max_targets: Option, - ) -> DispatchResult { + pub fn governance_fallback(origin: OriginFor) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; ensure!(CurrentPhase::::get().is_emergency(), Error::::CallNotAllowed); - let election_bounds = ElectionBoundsBuilder::default() - .voters_count(maybe_max_voters.unwrap_or(u32::MAX).into()) - .targets_count(maybe_max_targets.unwrap_or(u32::MAX).into()) - .build(); + let RoundSnapshot { voters, targets } = + Snapshot::::get().ok_or(Error::::MissingSnapshotMetadata)?; + let desired_targets = + DesiredTargets::::get().ok_or(Error::::MissingSnapshotMetadata)?; - let supports = T::GovernanceFallback::instant_elect( - election_bounds.voters, - election_bounds.targets, - ) - .map_err(|e| { + let supports = T::GovernanceFallback::instant_elect(voters, targets, desired_targets) + .map_err(|e| { log!(error, "GovernanceFallback failed: {:?}", e); Error::::FallbackFailed })?; @@ -1656,21 +1647,26 @@ impl Pallet { .ok_or(ElectionError::::NothingQueued) .or_else(|_| { log!(warn, "No solution queued, falling back to instant fallback.",); - // default data provider bounds are unbounded. calling `instant_elect` with - // unbounded data provider bounds means that the on-chain `T:Bounds` configs will - // *not* be overwritten. - T::Fallback::instant_elect( - DataProviderBounds::default(), - DataProviderBounds::default(), - ) - .map_err(|fe| ElectionError::Fallback(fe)) - .and_then(|supports| { - Ok(ReadySolution { - supports, - score: Default::default(), - compute: ElectionCompute::Fallback, + let (voters, targets, desired_targets) = if T::Fallback::bother() { + let RoundSnapshot { voters, targets } = Snapshot::::get().ok_or( + ElectionError::::Feasibility(FeasibilityError::SnapshotUnavailable), + )?; + let desired_targets = DesiredTargets::::get().ok_or( + ElectionError::::Feasibility(FeasibilityError::SnapshotUnavailable), + )?; + (voters, targets, desired_targets) + } else { + (Default::default(), Default::default(), Default::default()) + }; + T::Fallback::instant_elect(voters, targets, desired_targets) + .map_err(|fe| ElectionError::Fallback(fe)) + .and_then(|supports| { + Ok(ReadySolution { + supports, + score: Default::default(), + compute: ElectionCompute::Fallback, + }) }) - }) }) .map(|ReadySolution { compute, score, supports }| { Self::deposit_event(Event::ElectionFinalized { compute, score }); @@ -2035,6 +2031,7 @@ mod tests { }, Phase, }; + use frame_election_provider_support::bounds::ElectionBoundsBuilder; use frame_support::{assert_noop, assert_ok}; use sp_npos_elections::{BalancingConfig, Support}; @@ -2245,23 +2242,20 @@ mod tests { roll_to(30); assert!(CurrentPhase::::get().is_off()); - // This module is now only capable of doing on-chain backup. - assert_ok!(MultiPhase::elect(SINGLE_PAGE)); + // This module is now cannot even do onchain fallback, as no snapshot is there + assert_eq!( + MultiPhase::elect(SINGLE_PAGE), + Err(ElectionError::::Feasibility(FeasibilityError::SnapshotUnavailable)) + ); - assert!(CurrentPhase::::get().is_off()); + // this puts us in emergency now. + assert!(CurrentPhase::::get().is_emergency()); assert_eq!( multi_phase_events(), 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 }, + Event::ElectionFailed, + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Emergency, round: 1 } ] ); }); @@ -2624,12 +2618,12 @@ mod tests { // no single account can trigger this assert_noop!( - MultiPhase::governance_fallback(RuntimeOrigin::signed(99), None, None), + MultiPhase::governance_fallback(RuntimeOrigin::signed(99)), DispatchError::BadOrigin ); // only root can - assert_ok!(MultiPhase::governance_fallback(RuntimeOrigin::root(), None, None)); + assert_ok!(MultiPhase::governance_fallback(RuntimeOrigin::root())); // something is queued now assert!(QueuedSolution::::get().is_some()); // next election call with fix everything.; @@ -2684,22 +2678,17 @@ mod tests { roll_to(25); assert_eq!(CurrentPhase::::get(), Phase::Off); - // On-chain backup works though. - let supports = MultiPhase::elect(SINGLE_PAGE).unwrap(); - assert!(supports.len() > 0); + // On-chain backup will fail similarly. + assert_eq!( + MultiPhase::elect(SINGLE_PAGE).unwrap_err(), + ElectionError::::Feasibility(FeasibilityError::SnapshotUnavailable) + ); assert_eq!( multi_phase_events(), 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 }, + Event::ElectionFailed, + Event::PhaseTransitioned { from: Phase::Off, to: Phase::Emergency, round: 1 }, ] ); }); diff --git a/substrate/frame/election-provider-multi-phase/src/mock.rs b/substrate/frame/election-provider-multi-phase/src/mock.rs index c408296e48346..5f27ed23e487a 100644 --- a/substrate/frame/election-provider-multi-phase/src/mock.rs +++ b/substrate/frame/election-provider-multi-phase/src/mock.rs @@ -18,7 +18,7 @@ use super::*; use crate::{self as multi_phase, signed::GeometricDepositBase, unsigned::MinerConfig}; use frame_election_provider_support::{ - bounds::{DataProviderBounds, ElectionBounds}, + bounds::{DataProviderBounds, ElectionBounds, ElectionBoundsBuilder}, data_provider, onchain, ElectionDataProvider, NposSolution, SequentialPhragmen, }; pub use frame_support::derive_impl; @@ -338,19 +338,25 @@ impl ElectionProvider for MockFallback { impl InstantElectionProvider for MockFallback { fn instant_elect( - voters_bounds: DataProviderBounds, - targets_bounds: DataProviderBounds, + voters: Vec>, + targets: Vec, + desired_targets: u32, ) -> Result, Self::Error> { if OnChainFallback::get() { onchain::OnChainExecution::::instant_elect( - voters_bounds, - targets_bounds, + voters, + targets, + desired_targets, ) .map_err(|_| "onchain::OnChainExecution failed.") } else { Err("NoFallback.") } } + + fn bother() -> bool { + OnChainFallback::get() + } } parameter_types! { diff --git a/substrate/frame/election-provider-multi-phase/src/signed.rs b/substrate/frame/election-provider-multi-phase/src/signed.rs index 5b8b22e6119b7..5efe848c0e626 100644 --- a/substrate/frame/election-provider-multi-phase/src/signed.rs +++ b/substrate/frame/election-provider-multi-phase/src/signed.rs @@ -566,9 +566,9 @@ impl Pallet { mod tests { use super::*; use crate::{ - mock::*, CurrentPhase, ElectionBoundsBuilder, ElectionCompute, ElectionError, Error, Event, - Perbill, Phase, Round, + mock::*, CurrentPhase, ElectionCompute, ElectionError, Error, Event, Perbill, Phase, Round, }; + use frame_election_provider_support::bounds::ElectionBoundsBuilder; use frame_support::{assert_noop, assert_ok, assert_storage_noop}; use sp_runtime::Percent; diff --git a/substrate/frame/election-provider-support/src/lib.rs b/substrate/frame/election-provider-support/src/lib.rs index 351ec4df1a0c3..e9055d456c458 100644 --- a/substrate/frame/election-provider-support/src/lib.rs +++ b/substrate/frame/election-provider-support/src/lib.rs @@ -478,9 +478,15 @@ pub trait ElectionProvider { /// data provider at runtime via `forced_input_voters_bound` and `forced_input_target_bound`. pub trait InstantElectionProvider: ElectionProvider { fn instant_elect( - forced_input_voters_bound: DataProviderBounds, - forced_input_target_bound: DataProviderBounds, + voters: Vec>, + targets: Vec, + desired_targets: u32, ) -> Result, Self::Error>; + + // Sine many instant election provider, like [`NoElection`] are meant to do nothing, this is a + // hint for the caller to call before, and if `false` is returned, not bother with passing all + // the info to `instant_elect`. + fn bother() -> bool; } /// An election provider that does nothing whatsoever. @@ -519,11 +525,16 @@ where MaxBackersPerWinner: Get, { fn instant_elect( - _: DataProviderBounds, - _: DataProviderBounds, + _: Vec>, + _: Vec, + _: u32, ) -> Result, Self::Error> { Err("`NoElection` cannot do anything.") } + + fn bother() -> bool { + false + } } /// A utility trait for something to implement `ElectionDataProvider` in a sensible way. @@ -723,7 +734,7 @@ pub type VoterOf = Voter<::AccountId, ::MaxVotesPerVoter>; /// A bounded vector of supports. Bounded equivalent to [`sp_npos_elections::Supports`]. -#[derive(Default, RuntimeDebug, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)] +#[derive(Default, Debug, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)] #[codec(mel_bound(AccountId: MaxEncodedLen, Bound: Get))] #[scale_info(skip_type_params(Bound))] pub struct BoundedSupport> { diff --git a/substrate/frame/election-provider-support/src/onchain.rs b/substrate/frame/election-provider-support/src/onchain.rs index c18f8e1d54bda..7dd4e98f04a94 100644 --- a/substrate/frame/election-provider-support/src/onchain.rs +++ b/substrate/frame/election-provider-support/src/onchain.rs @@ -20,9 +20,9 @@ //! careful when using it onchain. use crate::{ - bounds::{DataProviderBounds, ElectionBounds, ElectionBoundsBuilder}, + bounds::{ElectionBounds, ElectionBoundsBuilder}, BoundedSupportsOf, Debug, ElectionDataProvider, ElectionProvider, InstantElectionProvider, - NposSolver, PageIndex, WeightInfo, Zero, + NposSolver, PageIndex, VoterOf, WeightInfo, }; use alloc::collections::btree_map::BTreeMap; use core::marker::PhantomData; @@ -33,7 +33,7 @@ use sp_npos_elections::{ }; /// Errors of the on-chain election. -#[derive(Eq, PartialEq, Debug)] +#[derive(Eq, PartialEq, Debug, Clone)] pub enum Error { /// An internal error in the NPoS elections crate. NposElections(sp_npos_elections::Error), @@ -41,8 +41,6 @@ pub enum Error { DataProvider(&'static str), /// Results failed to meet the bounds. FailedToBound, - /// Election page index not supported. - UnsupportedPageIndex, } impl From for Error { @@ -106,18 +104,11 @@ pub trait Config { } impl OnChainExecution { - fn elect_with( - bounds: ElectionBounds, - page: PageIndex, + fn elect_with_snapshot( + voters: Vec>, + targets: Vec<::AccountId>, + desired_targets: u32, ) -> Result, Error> { - let (voters, targets) = T::DataProvider::electing_voters(bounds.voters, page) - .and_then(|voters| { - Ok((voters, T::DataProvider::electable_targets(bounds.targets, page)?)) - }) - .map_err(Error::DataProvider)?; - - let desired_targets = T::DataProvider::desired_targets().map_err(Error::DataProvider)?; - if (desired_targets > T::MaxWinnersPerPage::get()) && !T::Sort::get() { // early exit what will fail in the last line anyways. return Err(Error::FailedToBound) @@ -158,20 +149,32 @@ impl OnChainExecution { }; Ok(bounded) } + + fn elect_with( + bounds: ElectionBounds, + page: PageIndex, + ) -> Result, Error> { + let (voters, targets) = T::DataProvider::electing_voters(bounds.voters, page) + .and_then(|voters| { + Ok((voters, T::DataProvider::electable_targets(bounds.targets, page)?)) + }) + .map_err(Error::DataProvider)?; + let desired_targets = T::DataProvider::desired_targets().map_err(Error::DataProvider)?; + Self::elect_with_snapshot(voters, targets, desired_targets) + } } impl InstantElectionProvider for OnChainExecution { fn instant_elect( - forced_input_voters_bounds: DataProviderBounds, - forced_input_targets_bounds: DataProviderBounds, + voters: Vec>, + targets: Vec<::AccountId>, + desired_targets: u32, ) -> Result, Self::Error> { - let elections_bounds = ElectionBoundsBuilder::from(T::Bounds::get()) - .voters_or_lower(forced_input_voters_bounds) - .targets_or_lower(forced_input_targets_bounds) - .build(); + Self::elect_with_snapshot(voters, targets, desired_targets) + } - // NOTE: instant provider is *always* single page. - Self::elect_with(elections_bounds, Zero::zero()) + fn bother() -> bool { + true } } @@ -181,16 +184,13 @@ impl ElectionProvider for OnChainExecution { type Error = Error; type MaxWinnersPerPage = T::MaxWinnersPerPage; type MaxBackersPerWinner = T::MaxBackersPerWinner; - type Pages = sp_core::ConstU32<1>; + // can support any number of pages, as this is meant to be called "instantly". + type Pages = sp_core::ConstU32<{ u32::MAX }>; type DataProvider = T::DataProvider; fn elect(page: PageIndex) -> Result, Self::Error> { - if page > 0 { - return Err(Error::UnsupportedPageIndex) - } - let election_bounds = ElectionBoundsBuilder::from(T::Bounds::get()).build(); - Self::elect_with(election_bounds, Zero::zero()) + Self::elect_with(election_bounds, page) } fn ongoing() -> bool { diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index 1ca018fcbcce4..a5edd59864251 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -227,6 +227,9 @@ parameter_types! { // default is single page EP. pub static Pages: PageIndex = 1; pub static MaxBackersPerWinner: u32 = 10_000; + // If set, the `SingleOrMultipageElectionProvider` will return these exact values, per page + // index. If not, it will behave is per the code. + pub static CustomElectionSupports: Option::ElectionProvider>, onchain::Error>>> = None; } // An election provider wrapper that allows testing with single and multi page modes. @@ -250,26 +253,32 @@ impl< type Error = onchain::Error; fn elect(page: PageIndex) -> Result, Self::Error> { - if Pages::get() == 1 { - SP::elect(page) + if let Some(maybe_paged_supports) = CustomElectionSupports::get() { + maybe_paged_supports[page as usize].clone() } else { - // will take first `MaxWinnersPerPage` in the validator set as winners. in this mock - // impl, we return an arbitratily but deterministic nominator exposure per winner/page. - let supports: Vec<(AccountId, Support)> = Validators::::iter_keys() - .filter(|x| Staking::status(x) == Ok(StakerStatus::Validator)) - .take(Self::MaxWinnersPerPage::get() as usize) - .map(|v| { - ( - v, - Support { - total: (100 + page).into(), - voters: vec![((page + 1) as AccountId, (100 + page).into())], - }, - ) - }) - .collect::>(); - - Ok(to_bounded_supports(supports)) + if Pages::get() == 1 { + SP::elect(page) + } else { + // will take first `MaxWinnersPerPage` in the validator set as winners. in this mock + // impl, we return an arbitrarily but deterministic nominator exposure per + // winner/page. + let supports: Vec<(AccountId, Support)> = + Validators::::iter_keys() + .filter(|x| Staking::status(x) == Ok(StakerStatus::Validator)) + .take(Self::MaxWinnersPerPage::get() as usize) + .map(|v| { + ( + v, + Support { + total: (100 + page).into(), + voters: vec![((page + 1) as AccountId, (100 + page).into())], + }, + ) + }) + .collect::>(); + + Ok(to_bounded_supports(supports)) + } } } fn msp() -> PageIndex { @@ -346,7 +355,6 @@ impl crate::pallet::pallet::Config for Test { type ElectionProvider = SingleOrMultipageElectionProvider>; type GenesisElectionProvider = onchain::OnChainExecution; - // NOTE: consider a macro and use `UseNominatorsAndValidatorsMap` as well. type VoterList = VoterBagsList; type TargetList = UseValidatorsMap; type NominationsQuota = WeightedNominationsQuota<16>; diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index b0d0e6cac7968..14362993fe1ff 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -739,33 +739,35 @@ impl Pallet { /// If any new election winner does not fit in the electable stashes storage, it truncates the /// result of the election. We ensure that only the winners that are part of the electable /// stashes have exposures collected for the next era. + /// + /// If `T::ElectionProvider::elect(_)`, we don't raise an error just yet and continue until + /// `elect(0)`. IFF `elect(0)` is called, yet we have not collected enough validators (as per + /// `MinimumValidatorCount` storage), an error is raised in the next era rotation. pub(crate) fn do_elect_paged(page: PageIndex) -> Weight { - let paged_result = match T::ElectionProvider::elect(page) { - Ok(result) => result, + match T::ElectionProvider::elect(page) { + Ok(supports) => { + let inner_processing_results = Self::do_elect_paged_inner(supports); + if let Err(not_included) = inner_processing_results { + defensive!( + "electable stashes exceeded limit, unexpected but election proceeds.\ + {} stashes from election result discarded", + not_included + ); + }; + + Self::deposit_event(Event::PagedElectionProceeded { + page, + result: inner_processing_results.map(|x| x as u32).map_err(|x| x as u32), + }); + T::WeightInfo::do_elect_paged(T::MaxValidatorSet::get()) + }, Err(e) => { log!(warn, "election provider page failed due to {:?} (page: {})", e, page); - // election failed, clear election prep metadata. - Self::clear_election_metadata(); - Self::deposit_event(Event::StakingElectionFailed); - - return T::WeightInfo::clear_election_metadata(); + Self::deposit_event(Event::PagedElectionProceeded { page, result: Err(0) }); + // no-op -- no need to raise an error for now. + Default::default() }, - }; - - let inner_processing_results = Self::do_elect_paged_inner(paged_result); - if let Err(not_included) = inner_processing_results { - defensive!( - "electable stashes exceeded limit, unexpected but election proceeds.\ - {} stashes from election result discarded", - not_included - ); - }; - - Self::deposit_event(Event::PagedElectionProceeded { - page, - result: inner_processing_results.map_err(|x| x as u32), - }); - T::WeightInfo::do_elect_paged(T::MaxValidatorSet::get()) + } } /// Inner implementation of [`Self::do_elect_paged`]. @@ -775,16 +777,16 @@ impl Pallet { /// included. pub(crate) fn do_elect_paged_inner( mut supports: BoundedSupportsOf, - ) -> Result<(), usize> { + ) -> Result { // preparing the next era. Note: we expect `do_elect_paged` to be called *only* during a // non-genesis era, thus current era should be set by now. let planning_era = CurrentEra::::get().defensive_unwrap_or_default().saturating_add(1); match Self::add_electables(supports.iter().map(|(s, _)| s.clone())) { - Ok(_) => { + Ok(added) => { let exposures = Self::collect_exposures(supports); let _ = Self::store_stakers_info(exposures, planning_era); - Ok(()) + Ok(added) }, Err(not_included_idx) => { let not_included = supports.len().saturating_sub(not_included_idx); @@ -902,18 +904,23 @@ impl Pallet { /// Adds a new set of stashes to the electable stashes. /// - /// Deduplicates stashes in place and returns an error if the bounds are exceeded. In case of - /// error, it returns the iter index of the element that failed to add. + /// Returns: + /// + /// `Ok(newly_added)` if all stashes were added successfully. + /// `Err(first_un_included)` if some stashes cannot be added due to bounds. pub(crate) fn add_electables( new_stashes: impl Iterator, - ) -> Result<(), usize> { + ) -> Result { ElectableStashes::::mutate(|electable| { + let pre_size = electable.len(); + for (idx, stash) in new_stashes.enumerate() { if electable.try_insert(stash).is_err() { return Err(idx); } } - Ok(()) + + Ok(electable.len() - pre_size) }) } @@ -2283,10 +2290,10 @@ impl Pallet { let next_election = Self::next_election_prediction(now); let pages = Self::election_pages().saturated_into::>(); let election_prep_start = next_election - pages; - - if now >= election_prep_start && now < next_election { + let is_mid_election = now >= election_prep_start && now < next_election; + if !is_mid_election { ensure!( - !ElectableStashes::::get().is_empty(), + ElectableStashes::::get().is_empty(), "ElectableStashes should not be empty mid election" ); } diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index 70c3cd54215c7..09cac208bd027 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -154,7 +154,6 @@ pub mod pallet { AccountId = Self::AccountId, BlockNumber = BlockNumberFor, DataProvider = Pallet, - Pages = ConstU32<1>, MaxWinnersPerPage = ::MaxWinnersPerPage, MaxBackersPerWinner = ::MaxBackersPerWinner, >; @@ -969,11 +968,15 @@ pub mod pallet { /// A page from a multi-page election was fetched. A number of these are followed by /// `StakersElected`. /// + /// `Ok(count)` indicates the give number of stashes were added. + /// `Err(index)` indicates that the stashes after index were dropped. + /// `Err(0)` indicates that an error happened but no stashes were dropped nor added. + /// /// The error indicates that a number of validators were dropped due to excess size, but /// the overall election will continue. PagedElectionProceeded { page: PageIndex, - result: Result<(), u32>, + result: Result, }, } diff --git a/substrate/frame/staking/src/tests.rs b/substrate/frame/staking/src/tests.rs index 7ae0a8607d67a..200d4e2fd8dde 100644 --- a/substrate/frame/staking/src/tests.rs +++ b/substrate/frame/staking/src/tests.rs @@ -3149,7 +3149,7 @@ fn deferred_slashes_are_deferred() { staking_events_since_last_call().as_slice(), &[ Event::SlashReported { validator: 11, slash_era: 1, .. }, - Event::PagedElectionProceeded { page: 0, result: Ok(()) }, + Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::StakersElected, .., Event::Slashed { staker: 11, amount: 100 }, @@ -3486,7 +3486,7 @@ fn slash_kicks_validators_not_nominators_and_disables_nominator_for_kicked_valid assert_eq!( staking_events_since_last_call(), vec![ - Event::PagedElectionProceeded { page: 0, result: Ok(()) }, + Event::PagedElectionProceeded { page: 0, result: Ok(7) }, Event::StakersElected, Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 }, Event::SlashReported { @@ -3560,7 +3560,7 @@ fn non_slashable_offence_disables_validator() { assert_eq!( staking_events_since_last_call(), vec![ - Event::PagedElectionProceeded { page: 0, result: Ok(()) }, + Event::PagedElectionProceeded { page: 0, result: Ok(7) }, Event::StakersElected, Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 }, Event::SlashReported { @@ -3641,7 +3641,7 @@ fn slashing_independent_of_disabling_validator() { assert_eq!( staking_events_since_last_call(), vec![ - Event::PagedElectionProceeded { page: 0, result: Ok(()) }, + Event::PagedElectionProceeded { page: 0, result: Ok(5) }, Event::StakersElected, Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 }, Event::SlashReported { @@ -8710,7 +8710,7 @@ fn reenable_lower_offenders_mock() { assert_eq!( staking_events_since_last_call(), vec![ - Event::PagedElectionProceeded { page: 0, result: Ok(()) }, + Event::PagedElectionProceeded { page: 0, result: Ok(7) }, Event::StakersElected, Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 }, Event::SlashReported { @@ -8787,7 +8787,7 @@ fn do_not_reenable_higher_offenders_mock() { assert_eq!( staking_events_since_last_call(), vec![ - Event::PagedElectionProceeded { page: 0, result: Ok(()) }, + Event::PagedElectionProceeded { page: 0, result: Ok(7) }, Event::StakersElected, Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 }, Event::SlashReported { diff --git a/substrate/frame/staking/src/tests_paged_election.rs b/substrate/frame/staking/src/tests_paged_election.rs index 72e17bf53c090..3b4950f1f18de 100644 --- a/substrate/frame/staking/src/tests_paged_election.rs +++ b/substrate/frame/staking/src/tests_paged_election.rs @@ -113,6 +113,7 @@ mod electable_stashes { mod paged_on_initialize { use super::*; + use frame_election_provider_support::onchain; #[test] fn single_page_election_works() { @@ -378,7 +379,7 @@ mod paged_on_initialize { ElectableStashes::::get().into_iter().collect::>(), expected_elected ); - // election cursor reamins unchanged during intermediate pages. + // election cursor moves along. assert_eq!(NextElectionPage::::get(), Some(0)); // exposures have been collected for all validators in the page. for s in expected_elected.iter() { @@ -401,9 +402,9 @@ mod paged_on_initialize { } assert_eq!(NextElectionPage::::get(), None); assert_eq!(staking_events_since_last_call(), vec![ - Event::PagedElectionProceeded { page: 2, result: Ok(()) }, - Event::PagedElectionProceeded { page: 1, result: Ok(()) }, - Event::PagedElectionProceeded { page: 0, result: Ok(()) } + Event::PagedElectionProceeded { page: 2, result: Ok(5) }, + Event::PagedElectionProceeded { page: 1, result: Ok(0) }, + Event::PagedElectionProceeded { page: 0, result: Ok(0) } ]); // upon fetching page 0, the electing started will remain in storage until the @@ -500,6 +501,192 @@ mod paged_on_initialize { ); }) } + + #[test] + fn multi_page_election_is_graceful() { + // demonstrate that in a multi-page election, in some of the `elect(_)` calls fail we won't + // bail right away. + ExtBuilder::default().multi_page_election_provider(3).build_and_execute(|| { + // load some exact data into the election provider, some of which are error or empty. + let correct_results = ::GenesisElectionProvider::elect(0); + CustomElectionSupports::set(Some(vec![ + // page 0. + correct_results.clone(), + // page 1. + Err(onchain::Error::FailedToBound), + // page 2. + Ok(Default::default()), + ])); + + // genesis era. + assert_eq!(current_era(), 0); + + let next_election = + ::next_election_prediction(System::block_number()); + assert_eq!(next_election, 10); + + // try-state sanity check. + assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); + + // 1. election prep hasn't started yet, election cursor and electable stashes are + // not set yet. + run_to_block(6); + assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); + assert_eq!(NextElectionPage::::get(), None); + assert!(ElectableStashes::::get().is_empty()); + + // 2. starts preparing election at the (election_prediction - n_pages) block. + // fetches lsp (i.e. 2). + run_to_block(7); + assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); + + // electing started at cursor is set once the election starts to be prepared. + assert_eq!(NextElectionPage::::get(), Some(1)); + // in elect(2) we won't collect any stashes yet. + assert!(ElectableStashes::::get().is_empty()); + + // 3. progress one block to fetch page 1. + run_to_block(8); + assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); + + // in elect(1) we won't collect any stashes yet. + assert!(ElectableStashes::::get().is_empty()); + // election cursor is updated + assert_eq!(NextElectionPage::::get(), Some(0)); + + // 4. progress one block to fetch mps (i.e. 0). + run_to_block(9); + assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); + + // some stashes come in. + assert_eq!( + ElectableStashes::::get().into_iter().collect::>(), + vec![11 as AccountId, 21] + ); + // cursor is now none + assert_eq!(NextElectionPage::::get(), None); + + // events thus far + assert_eq!( + staking_events_since_last_call(), + vec![ + Event::PagedElectionProceeded { page: 2, result: Ok(0) }, + Event::PagedElectionProceeded { page: 1, result: Err(0) }, + Event::PagedElectionProceeded { page: 0, result: Ok(2) } + ] + ); + + // upon fetching page 0, the electing started will remain in storage until the + // era rotates. + assert_eq!(current_era(), 0); + + // Next block the era will rotate. + run_to_block(10); + assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); + + // and all the metadata has been cleared up and ready for the next election. + assert!(NextElectionPage::::get().is_none()); + assert!(ElectableStashes::::get().is_empty()); + + // and the overall staking worked fine. + assert_eq!(staking_events_since_last_call(), vec![Event::StakersElected]); + }) + } + + #[test] + fn multi_page_election_fails_if_not_enough_validators() { + // a graceful multi-page election still fails if not enough validators are provided. + ExtBuilder::default() + .multi_page_election_provider(3) + .minimum_validator_count(3) + .build_and_execute(|| { + // load some exact data into the election provider, some of which are error or + // empty. + let correct_results = ::GenesisElectionProvider::elect(0); + CustomElectionSupports::set(Some(vec![ + // page 0. + correct_results.clone(), + // page 1. + Err(onchain::Error::FailedToBound), + // page 2. + Ok(Default::default()), + ])); + + // genesis era. + assert_eq!(current_era(), 0); + + let next_election = ::next_election_prediction( + System::block_number(), + ); + assert_eq!(next_election, 10); + + // try-state sanity check. + assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); + + // 1. election prep hasn't started yet, election cursor and electable stashes are + // not set yet. + run_to_block(6); + assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); + assert_eq!(NextElectionPage::::get(), None); + assert!(ElectableStashes::::get().is_empty()); + + // 2. starts preparing election at the (election_prediction - n_pages) block. + // fetches lsp (i.e. 2). + run_to_block(7); + assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); + + // electing started at cursor is set once the election starts to be prepared. + assert_eq!(NextElectionPage::::get(), Some(1)); + // in elect(2) we won't collect any stashes yet. + assert!(ElectableStashes::::get().is_empty()); + + // 3. progress one block to fetch page 1. + run_to_block(8); + assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); + + // in elect(1) we won't collect any stashes yet. + assert!(ElectableStashes::::get().is_empty()); + // election cursor is updated + assert_eq!(NextElectionPage::::get(), Some(0)); + + // 4. progress one block to fetch mps (i.e. 0). + run_to_block(9); + assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); + + // some stashes come in. + assert_eq!( + ElectableStashes::::get().into_iter().collect::>(), + vec![11 as AccountId, 21] + ); + // cursor is now none + assert_eq!(NextElectionPage::::get(), None); + + // events thus far + assert_eq!( + staking_events_since_last_call(), + vec![ + Event::PagedElectionProceeded { page: 2, result: Ok(0) }, + Event::PagedElectionProceeded { page: 1, result: Err(0) }, + Event::PagedElectionProceeded { page: 0, result: Ok(2) } + ] + ); + + // upon fetching page 0, the electing started will remain in storage until the + // era rotates. + assert_eq!(current_era(), 0); + + // Next block the era will rotate. + run_to_block(10); + assert_ok!(Staking::ensure_snapshot_metadata_state(System::block_number())); + + // and all the metadata has been cleared up and ready for the next election. + assert!(NextElectionPage::::get().is_none()); + assert!(ElectableStashes::::get().is_empty()); + + // and the overall staking worked fine. + assert_eq!(staking_events_since_last_call(), vec![Event::StakingElectionFailed]); + }) + } } mod paged_snapshot { @@ -628,6 +815,11 @@ mod paged_snapshot { assert_eq!(all_voters, single_page_voters); }) } + + #[test] + fn voter_snapshot_starts_from_msp_to_lsp() { + todo!(); + } } mod paged_exposures { From 340c563ba3b3da78578e9a9d5d562303b93e3445 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 23 Jan 2025 09:46:55 +0000 Subject: [PATCH 126/169] node biuld --- substrate/frame/election-provider-support/src/onchain.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/election-provider-support/src/onchain.rs b/substrate/frame/election-provider-support/src/onchain.rs index 7dd4e98f04a94..65f5bc05cf6e3 100644 --- a/substrate/frame/election-provider-support/src/onchain.rs +++ b/substrate/frame/election-provider-support/src/onchain.rs @@ -24,7 +24,7 @@ use crate::{ BoundedSupportsOf, Debug, ElectionDataProvider, ElectionProvider, InstantElectionProvider, NposSolver, PageIndex, VoterOf, WeightInfo, }; -use alloc::collections::btree_map::BTreeMap; +use alloc::{collections::btree_map::BTreeMap, vec::Vec}; use core::marker::PhantomData; use frame_support::{dispatch::DispatchClass, traits::Get}; use frame_system::pallet_prelude::BlockNumberFor; From 5772b9dbde8f88718ec5c6409f444d6e5b4e4e03 Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Thu, 23 Jan 2025 10:57:06 +0100 Subject: [PATCH 127/169] [pallet-revive] fee estimation fixes (#7281) - Fix the EVM fee cost estimation. The estimation shown in EVM wallet was using Native instead of EVM decimals - Remove the precise code length estimation in dry run call. Over-estimating is fine, since extra gas is refunded anyway. - Ensure that the estimated fee calculated from gas_price x gas use the encoded weight & deposit limit instead of the exact one calculated by the dry-run. Else we can end up with a fee that is lower than the actual fee paid by the user --------- Co-authored-by: command-bot <> --- .../assets/asset-hub-westend/src/lib.rs | 6 +- prdoc/pr_7281.prdoc | 13 ++ substrate/bin/node/runtime/src/lib.rs | 4 + .../rpc/examples/js/src/build-contracts.ts | 2 +- .../rpc/examples/js/src/geth-diff.test.ts | 162 +++++++++--------- .../frame/revive/rpc/examples/js/src/util.ts | 11 +- .../frame/revive/rpc/revive_chain.metadata | Bin 661585 -> 671115 bytes substrate/frame/revive/rpc/src/client.rs | 17 +- .../frame/revive/src/benchmarking/mod.rs | 6 +- substrate/frame/revive/src/evm/gas_encoder.rs | 11 ++ substrate/frame/revive/src/evm/runtime.rs | 64 +++---- substrate/frame/revive/src/exec.rs | 6 +- substrate/frame/revive/src/lib.rs | 129 ++++++++------ substrate/frame/revive/src/primitives.rs | 8 + 14 files changed, 249 insertions(+), 190 deletions(-) create mode 100644 prdoc/pr_7281.prdoc diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index f56c4568f2d1f..ecbe1fb0e62af 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -129,7 +129,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("westmint"), impl_name: alloc::borrow::Cow::Borrowed("westmint"), authoring_version: 1, - spec_version: 1_017_005, + spec_version: 1_017_006, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 16, @@ -2189,6 +2189,10 @@ impl_runtime_apis! { Revive::evm_balance(&address) } + fn block_gas_limit() -> U256 { + Revive::evm_block_gas_limit() + } + fn nonce(address: H160) -> Nonce { let account = ::AddressMapper::to_account_id(&address); System::account_nonce(account) diff --git a/prdoc/pr_7281.prdoc b/prdoc/pr_7281.prdoc new file mode 100644 index 0000000000000..33e04c419bad9 --- /dev/null +++ b/prdoc/pr_7281.prdoc @@ -0,0 +1,13 @@ +title: '[pallet-revive] fix eth fee estimation' +doc: +- audience: Runtime Dev + description: |- + Fix EVM fee cost estimation. + The current estimation was shown in Native and not EVM decimal currency. +crates: +- name: asset-hub-westend-runtime + bump: minor +- name: pallet-revive-eth-rpc + bump: minor +- name: pallet-revive + bump: minor diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 26f4dacf9a1e3..220929fdfd838 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -3301,6 +3301,10 @@ impl_runtime_apis! { Revive::evm_balance(&address) } + fn block_gas_limit() -> U256 { + Revive::evm_block_gas_limit() + } + fn nonce(address: H160) -> Nonce { let account = ::AddressMapper::to_account_id(&address); System::account_nonce(account) diff --git a/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts b/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts index f26f275ec3d53..b162b8be0adfe 100644 --- a/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts +++ b/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts @@ -55,7 +55,7 @@ for (const file of input) { } console.log('Compiling with revive...') - const reviveOut = await compile(input, { bin: 'resolc' }) + const reviveOut = await compile(input) for (const contracts of Object.values(reviveOut.contracts)) { for (const [name, contract] of Object.entries(contracts)) { diff --git a/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts index 86b8ec50bd638..2a4ff2edcdf52 100644 --- a/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts +++ b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts @@ -12,62 +12,64 @@ import { ErrorsAbi } from '../abi/Errors' import { FlipperCallerAbi } from '../abi/FlipperCaller' import { FlipperAbi } from '../abi/Flipper' import { Subprocess, spawn } from 'bun' +import { fail } from 'node:assert' const procs: Subprocess[] = [] -beforeAll(async () => { - if (!process.env.USE_LIVE_SERVERS) { - procs.push( - // Run geth on port 8546 - await (async () => { - killProcessOnPort(8546) - const proc = spawn( - 'geth --http --http.api web3,eth,debug,personal,net --http.port 8546 --dev --verbosity 0'.split( - ' ' - ), - { stdout: Bun.file('/tmp/geth.out.log'), stderr: Bun.file('/tmp/geth.err.log') } - ) +if (!process.env.USE_LIVE_SERVERS) { + procs.push( + // Run geth on port 8546 + await (async () => { + killProcessOnPort(8546) + console.log('Starting geth') + const proc = spawn( + 'geth --http --http.api web3,eth,debug,personal,net --http.port 8546 --dev --verbosity 0'.split( + ' ' + ), + { stdout: Bun.file('/tmp/geth.out.log'), stderr: Bun.file('/tmp/geth.err.log') } + ) - await waitForHealth('http://localhost:8546').catch() - return proc - })(), - //Run the substate node - (() => { - killProcessOnPort(9944) - return spawn( - [ - './target/debug/substrate-node', - '--dev', - '-l=error,evm=debug,sc_rpc_server=info,runtime::revive=debug', - ], - { - stdout: Bun.file('/tmp/kitchensink.out.log'), - stderr: Bun.file('/tmp/kitchensink.err.log'), - cwd: polkadotSdkPath, - } - ) - })(), - // Run eth-rpc on 8545 - await (async () => { - killProcessOnPort(8545) - const proc = spawn( - [ - './target/debug/eth-rpc', - '--dev', - '--node-rpc-url=ws://localhost:9944', - '-l=rpc-metrics=debug,eth-rpc=debug', - ], - { - stdout: Bun.file('/tmp/eth-rpc.out.log'), - stderr: Bun.file('/tmp/eth-rpc.err.log'), - cwd: polkadotSdkPath, - } - ) - await waitForHealth('http://localhost:8545').catch() - return proc - })() - ) - } -}) + await waitForHealth('http://localhost:8546').catch() + return proc + })(), + //Run the substate node + (() => { + killProcessOnPort(9944) + console.log('Starting substrate node') + return spawn( + [ + './target/debug/substrate-node', + '--dev', + '-l=error,evm=debug,sc_rpc_server=info,runtime::revive=debug', + ], + { + stdout: Bun.file('/tmp/kitchensink.out.log'), + stderr: Bun.file('/tmp/kitchensink.err.log'), + cwd: polkadotSdkPath, + } + ) + })(), + // Run eth-rpc on 8545 + await (async () => { + killProcessOnPort(8545) + console.log('Starting eth-rpc') + const proc = spawn( + [ + './target/debug/eth-rpc', + '--dev', + '--node-rpc-url=ws://localhost:9944', + '-l=rpc-metrics=debug,eth-rpc=debug', + ], + { + stdout: Bun.file('/tmp/eth-rpc.out.log'), + stderr: Bun.file('/tmp/eth-rpc.err.log'), + cwd: polkadotSdkPath, + } + ) + await waitForHealth('http://localhost:8545').catch() + return proc + })() + ) +} afterEach(() => { jsonRpcErrors.length = 0 @@ -88,7 +90,7 @@ for (const env of envs) { { const hash = await env.serverWallet.deployContract({ abi: ErrorsAbi, - bytecode: getByteCode('errors', env.evm), + bytecode: getByteCode('Errors', env.evm), }) const deployReceipt = await env.serverWallet.waitForTransactionReceipt({ hash }) if (!deployReceipt.contractAddress) @@ -99,7 +101,7 @@ for (const env of envs) { { const hash = await env.serverWallet.deployContract({ abi: FlipperAbi, - bytecode: getByteCode('flipper', env.evm), + bytecode: getByteCode('Flipper', env.evm), }) const deployReceipt = await env.serverWallet.waitForTransactionReceipt({ hash }) if (!deployReceipt.contractAddress) @@ -111,7 +113,7 @@ for (const env of envs) { const hash = await env.serverWallet.deployContract({ abi: FlipperCallerAbi, args: [flipperAddr], - bytecode: getByteCode('flipperCaller', env.evm), + bytecode: getByteCode('FlipperCaller', env.evm), }) const deployReceipt = await env.serverWallet.waitForTransactionReceipt({ hash }) if (!deployReceipt.contractAddress) @@ -121,13 +123,13 @@ for (const env of envs) { }) test('triggerAssertError', async () => { - expect.assertions(3) try { await env.accountWallet.readContract({ address: errorsAddr, abi: ErrorsAbi, functionName: 'triggerAssertError', }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(3) @@ -139,13 +141,13 @@ for (const env of envs) { }) test('triggerRevertError', async () => { - expect.assertions(3) try { await env.accountWallet.readContract({ address: errorsAddr, abi: ErrorsAbi, functionName: 'triggerRevertError', }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(3) @@ -157,13 +159,13 @@ for (const env of envs) { }) test('triggerDivisionByZero', async () => { - expect.assertions(3) try { await env.accountWallet.readContract({ address: errorsAddr, abi: ErrorsAbi, functionName: 'triggerDivisionByZero', }) + expect.assertions(3) } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(3) @@ -177,13 +179,13 @@ for (const env of envs) { }) test('triggerOutOfBoundsError', async () => { - expect.assertions(3) try { await env.accountWallet.readContract({ address: errorsAddr, abi: ErrorsAbi, functionName: 'triggerOutOfBoundsError', }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(3) @@ -197,13 +199,13 @@ for (const env of envs) { }) test('triggerCustomError', async () => { - expect.assertions(3) try { await env.accountWallet.readContract({ address: errorsAddr, abi: ErrorsAbi, functionName: 'triggerCustomError', }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(3) @@ -215,15 +217,15 @@ for (const env of envs) { }) test('eth_call (not enough funds)', async () => { - expect.assertions(3) try { - await env.accountWallet.simulateContract({ + await env.emptyWallet.simulateContract({ address: errorsAddr, abi: ErrorsAbi, functionName: 'valueMatch', value: parseEther('10'), args: [parseEther('10')], }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(-32000) @@ -233,12 +235,15 @@ for (const env of envs) { }) test('eth_call transfer (not enough funds)', async () => { - expect.assertions(3) + const value = parseEther('10') + const balance = await env.emptyWallet.getBalance(env.emptyWallet.account) + expect(balance, 'Balance should be less than 10').toBeLessThan(value) try { - await env.accountWallet.sendTransaction({ + await env.emptyWallet.sendTransaction({ to: '0x75E480dB528101a381Ce68544611C169Ad7EB342', - value: parseEther('10'), + value, }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(-32000) @@ -248,15 +253,15 @@ for (const env of envs) { }) test('eth_estimate (not enough funds)', async () => { - expect.assertions(3) try { - await env.accountWallet.estimateContractGas({ + await env.emptyWallet.estimateContractGas({ address: errorsAddr, abi: ErrorsAbi, functionName: 'valueMatch', value: parseEther('10'), args: [parseEther('10')], }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(-32000) @@ -266,15 +271,15 @@ for (const env of envs) { }) test('eth_estimate call caller (not enough funds)', async () => { - expect.assertions(3) try { - await env.accountWallet.estimateContractGas({ + await env.emptyWallet.estimateContractGas({ address: errorsAddr, abi: ErrorsAbi, functionName: 'valueMatch', value: parseEther('10'), args: [parseEther('10')], }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(-32000) @@ -284,7 +289,6 @@ for (const env of envs) { }) test('eth_estimate (revert)', async () => { - expect.assertions(3) try { await env.serverWallet.estimateContractGas({ address: errorsAddr, @@ -293,6 +297,7 @@ for (const env of envs) { value: parseEther('11'), args: [parseEther('10')], }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(3) @@ -313,17 +318,16 @@ for (const env of envs) { }) test('eth_estimate (not enough funds to cover gas specified)', async () => { - expect.assertions(4) + let balance = await env.serverWallet.getBalance(env.emptyWallet.account) + expect(balance).toBe(0n) try { - let balance = await env.serverWallet.getBalance(env.accountWallet.account) - expect(balance).toBe(0n) - - await env.accountWallet.estimateContractGas({ + await env.emptyWallet.estimateContractGas({ address: errorsAddr, abi: ErrorsAbi, functionName: 'setState', args: [true], }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(-32000) @@ -333,7 +337,7 @@ for (const env of envs) { }) test('eth_estimate (no gas specified)', async () => { - let balance = await env.serverWallet.getBalance(env.accountWallet.account) + let balance = await env.serverWallet.getBalance(env.emptyWallet.account) expect(balance).toBe(0n) const data = encodeFunctionData({ @@ -342,12 +346,12 @@ for (const env of envs) { args: [true], }) - await env.accountWallet.request({ + await env.emptyWallet.request({ method: 'eth_estimateGas', params: [ { data, - from: env.accountWallet.account.address, + from: env.emptyWallet.account.address, to: errorsAddr, }, ], diff --git a/substrate/frame/revive/rpc/examples/js/src/util.ts b/substrate/frame/revive/rpc/examples/js/src/util.ts index bdc64eea1ef58..2991bdfe6367b 100644 --- a/substrate/frame/revive/rpc/examples/js/src/util.ts +++ b/substrate/frame/revive/rpc/examples/js/src/util.ts @@ -85,7 +85,16 @@ export async function createEnv(name: 'geth' | 'kitchensink') { chain, }).extend(publicActions) - return { serverWallet, accountWallet, evm: name == 'geth' } + const emptyWallet = createWalletClient({ + account: privateKeyToAccount( + '0x4450c571bae82da0528ecf76fcf7079e12ecc46dc873c9cacb6db8b75ed22f41', + { nonceManager } + ), + transport, + chain, + }).extend(publicActions) + + return { serverWallet, emptyWallet, accountWallet, evm: name == 'geth' } } export function wait(ms: number) { diff --git a/substrate/frame/revive/rpc/revive_chain.metadata b/substrate/frame/revive/rpc/revive_chain.metadata index a03c95b4944f663225642b1678ef66aaccec3fb5..ff365892a265e1fd9b59f6811ea1c59642b65d91 100644 GIT binary patch delta 25763 zcmbWg4SZC^)jxjc?%g~0?n`z92_%rf28j@bB~ieD0SOQ!%1Z(PMTyCh-H?@JH`(0? zQ8Ca;3o2^d!ia)`6_r*Lo|axwQBb4OM_OY=#frdFQBhITih>sXf9Kx2$%f$b^#Aj- z=Dy9GnK^HB=A1J_$ByB5{xF<21Y$myGj%8Z+(L5V|FX;?*?*WUeW{WK@q&~BQW>vF zDI&i3=9KYbfU+xyk{<6&$q`#qR9izS}j3tct(qnorio zN4Y1Fb@4{`<>EHV+!iGx-tHbuw#SdV^QB#s{E_`sW6h3_P0c2|;}xm-;vUMLwxEFr zQ?p2W{F&4VO{KCDkZ>5O07r--QWb$wrTBsHGh_kt0V zrUvQ*i+! zN9@n!t@lA8jdSkav3P%PLGm?+E@M(Z`B(;|Vu&4Gv{WGf__B-f2r z)f(U8#m%BoL};nHgLh@x@rd57vRLg|*>GO`vLNFjnV}_?Bjn{K>w04r_L8QN=_dO_@i` z@p~2-A3P0u^?=_~8wodf>ccflqMlgTQx~rHM@M)#B#$rD(p5ZG=Zkr2!p-%5PbeJo zEDCt)0`>kG!KepK+iL=P4c<08w%2;WAKoeY;WaW(8~Ks7h>&?~pF+lI(`AyDcwp-( ziI^YQrcbQ58TSt*%5G4}3Xznv0SAkid}Cb0{;Dvd%nm11n1>avy^zBRt*Y z(Cnkg$WnT%dr>&#S6HfD%+Rh`MxHI|Gszr#RgH;CDef-p-#RGkJp-qKsHZUyX$VH6 z!EmTP5RKNiczhlZCt7ztYS!Htg?b!vQ}B#e*Hp)CqTW1c`QP#^Q4;Rc^S zkRYDNhv()RP!FRBV+f1t171%Bx`gief+5Jj?FQhk9veKs_Kp>;S@i+`;y}bJyuxeL z1_B)XQeW^UFiMcsKVi*gmt11K^4HhwBKTj#{}%i&Y1izs{?gW3mdxx4LCG*)x~E`F zl2jp(p(6|wR5gaf^=YI??~T98kglMAGbx%JURG2we!MPDrT&QmDKdoae=SoHxkzV% zDo9133IwcL#JM_y3{HU@5S~1s|vj z*L#H#Wxj|H$}kX#j(?t&zaXWzP6>xT||YJzgi5 zJ~U1sh>cOg6ExU@H*@*dsCdo*&r{JKjK(6t)Mq)2ksL+)45J19?$jFQ-je)U#zCC0-6$Ztp!4>D?K;)B0(Q|7P!e*-^_J1 z#6ZVaU|FE1IR@eLaMkLqZlif{Cw1zM3T-AMg9L?q+Jj z4UL#e(2Op()w9B(K=p_f9=!yYXW$GXgo%UG%opI*YRdG^Z8^+fUS5B zt&BKgRd_23$@JY3;ptq>h2ApxMmozp4_yjt2bR zqB>s$TEELMU|a0gblL;UmW*4c8->u@ntC6Y+R!jvqVoTSzyAzy8)Gd6 z=?wnpi4)8;o~3m`Oe?@nqK2t~k{SPe2pYVMn@yZt=9F-bhSm|*>WA_@|M#96TuQ5( z8TJQj`IM})fGMP0oV~(c!#av##_pDNUDQ38MD@^DOT|P0R zws0E&!)^c|uxJ=hx;F6^c8x|as`uVHl=nWK^J@AHR!3RS{uu(FXy-p}rlPAELm3;h znckXxIBzX!B5Re?R?LNlSy~t9o>n~3I^A5;%k@T$QQc7bJ#b?vQZV*^-sNl)H5L8< zOpahe6sioN)BD_|=2~gmQOu#RorADq10Kw-bzxnDo0ZSey1@`QE}@T1L(8lKvT)0Y zPm492;vs19Xw(;J@eDV`-7r|W`ib~L(b@nk=T<)^KInU$MZ%m*#u1&58Uf1Z_eTIC zZ-fWaqOM>g;hTbfNGlhBnxt+=!{CunGgP!c5DDI-vj(4H0S`Lan4N;KnT#$*0!__m z)bGu&anuJ;uThr=iLHFk+P787D4Jrt?HcGZ!>H(?KJ{Lj+*D2yHKMU_#J3o_GJ?en zpX0dI4ud0H6V#_v^E)~ZMQqG$;HQD@YkNO4u&cI-iy|u z;ylWZd>{>A8GEg>#RYWz>ry|~y4SjjR5I&6>lV>xeAiyuXT4tJYs1a+=|DFA73)S4 zWbeLWEzS>8DPv9`+FTz~qzckNr4rIiiK5J2A_)ytz<-XC2o_JxxF>?pz^1oj4bj9_ zwOey>`8~fhvyOJF*Nx%|848YzRRu$};Rfb<)tWR?;O zSTnSSx2%Jm;ws9HJtt-Enn6}cWR1B(S(Cg%F&>N+ig~ZEQ1ttUh*=gZl$oMs4OQ5_ zVrwR={M>rAxR$cL#nv1)<5g>lcIb0!nDlR>Wcd7WEHEcf6OQ-=k`{wa(6UCWI%(}g zENzMEYrnJ>kag_EFRiU)gLcJN)~O<{w|83C^XtHGtZjsB(zc(nK269Lt@1l-JFX1Z zyV=e&)~RFz`}T}=uWbX6CMlaY3!B*9@2#WyY{9d*l?wRr_Zh-Ajr?HUD~j7Gn^X;B z{^(ijJ!GfW_@niZB<`fF?KP=CTV#=MBfHo!i|iA3V|l-h4q!J(awp4G<$>%*tNa1k zr(G$_ABtojE3?W2*d$fHfV4BeDqly4FO5$FsR<4W!dF^#CZ^p773pv;Sgm_G3x+rfkR#_lf&A+ zKJqg%ImTuUkSDTdGUavTn1=fzCpoU&IZXaUAt$tGzD%r+WBTA#s#4)NTQNo+mUaSf z#FJFO4`?BD0}(pefk&+a8KB$UQr5giKNlVcaV!b6oMN40;He;-Oqj9-{eLGfO zO-`}a%j81&3?+w15i5SodI39nnLMAIVeZGRBbmQYo^#RJ9z!NduOSmfXLO>lqlNM~ z*{YCZNx){aug1z&nahP0i`d7lBiQTX$7wG9^{VnzV4x2o)QO1gF*?#;=c8n?7p|G zeebL9f2kndI;}b6$EqRWGx03qQ?U6Mj(9zD39=+oUR2&ZDfSRv%oKQv`EP*T~}K z!-w?GR2=79-Li6roK~5k+wJJOY4r&tpD$mHX`s|E-CDtwX~MkZo)q22;>$S9@=-6z z(ygPQ-%T>gU?-u{ zK5wSR$H>#9uUERaci)1GLzwaODU;jeSlT28#Z@jr$Y>yt2rcJmD|6>(}F)Th!UP#8VulZ#Hn|Y;tA=@`y9#vSZ2$*|9d|36x3gJ%H5nT(`u2(~` zV1Zc^jXRN^hd1(6G~rt_cJe*Tp6ZLWxoOzUgvW zcBw)t$^wyuTW<ocT)Si;N+rJIK%=TxL}v z_*L~mr?ZzzVbIKD&a32!dGopvuz(`~e+2!HSg8p3!CqS_xdorXT%$<3w&p7NHx?3L zgJ#JgF{m(~ou)JQI(g{u21T07?Zzf}yygdLco;-z(CaT3^YIqg$78czC7%tREz_cw zq@pdnfFvwek`30Cy{)&ED2HXrN>*7eXR+I6%l*kJwtluejI3oZ%$7%xHum{!d|6m7 zXS1PY^1{Ar6p}wbQacy3tPd+O7{6;+yiC3Z6?7Q1T%LzoxLyAq$3ovF1}$X5_?XA;`dcbNe9 zBmvyTR?L?(*oE`u87aFJk~IhG%y1|w2)p@*@wPp@on4Bs&*)Toy9r}|5{z~OM$R?z zLfd{`zFiRx80GtQnB`N|Zc=q9Z1xRue&2&img&1E8CdNpLH|MKzE&O}9#q(h%j9%6 z=rP#ErPs=i%L=o&8K%eh4k@J0lvr*N;obQ0(-CbkdM?<7tSRsx*97Y&;V|=E3zPjA zzZ9{%&rq3tf30jM$Cre?i#iv`L!~nc z*|?rb1KH^7WDgby98#Z4CkjP^XRfc7&l*_Sz+(UHb@D}`Rb_v@P9D^Hp2(!Cc7+;E z<1^sbBa`R$zgLjbiEvr6hDyXw0;(cr5AuxDxNE$z*NU%Ts}ycdnO512^`Ca)H#TI2z-{4zT({)H$WFonJu~Q|^-} zdxXC^qp%;>OGDY$3*{l=*(8v_Ou1FgWZM?XDXg_p?vr9w9iz?RL;^RhzaC`%W2Jm` ztE&FY5(Wq|!%RI#G}YvY?nMC~n^QfnEgwBEh9xk?;opoo25Bd3uG z>-}<>vsi@~XWRsrNF-XyD*f^${1w+r`6a4QVpe$bIk~T`lsAWuGmV$S%1uPiOG0$H zUU!GvEzIMk@wk9Z3dpa+A%)9u_VP1wsy4Y+rV{b7t8SD#qyWdP?=JA>=p}L=F<`d% z<6X2bd{lQWkw-`+D$)>U?jV`$;1ck80~=5eiEdKK2y;?w9y=P-NfWE9mve2+ybFq3 z5Ly_kmsi@C>s@jSZp;c7G|0DOLU^J0SAP?A?4cdsG2Gj)?;nVV{22t`-XI><=yS#bm#Bq(!bF;sJwV zmv)%!a4?A-I(Q|AdyFUu2RTGMAJX9*Rx^adNz4U>|Maa;I?}E3BDTh7BKzBY@+Ep5Tb@y8wJ_57%XC=J=R&i?xr~VqqN_)E=A*e)Hp!ECr$|?m(b9YvBa*NN=+Xa( zYi$C4AkXLwp^g2|7C8WCZdc=Q&)xM1tOqz{ACU*cZ+r9+c>td9GK=;9t^6cj>Gwx} zD-UCl$K*6N@KJdX*~Z2{3JY>OTl^?Yke%$FN9Bp|%l_q2plLVjyH)m*J#6w;eA~vp z#Kp>W^mgMm1GhVk+tIk)rQgcsSeU?S(+lV0c8_rj-6iZ}(Z}SA#daI!_kr4TkICLt zalehZE5tPRlLo);0Rv0ixXvC&j47|fmSnMUW*J9>gY4;NquNc3WXB}I^Dua1CN}kA#&ko_U9d9p8n{AdRaL*A=(HOk((T>D!Mh|^b*jCW-!>Hx zVfm-pS>v;i(Hozam#FD@CpzsyrqPDZdD&)x@q6UCVvhOn*dF;xlFPo_Bli@Jyq^xiooj~_E_*>Pp`?Jl&@PvgG0gp{oR>GIN597P?$+&e6^KrdtW}IZsQGXu|uqX|97a-otpatc^MIR+2ecf&1AnlEFXp{Soxv67OKE? zL@vQ)-VtboeQfy=dCY)zz#lO$#N&@hQ3&{9dc}e@O9HX|?2RMPCI^`PsC<>!Ztpoq zT)LlQRuS6m!hs(B-QT;vyeRtHkwl(@THZ1Fe$jqNhjQ4CsaOA+e#}J3@g#&CV|_l7 z+xi~o13hLJPUv+`qRvU9PN!MtR8pNz_V176+tgFMPN!Wsqy6%@Tq@en>UE)+iG#iS ziTpS@%vwLi(!=VAU&jV$FMo=ON`yhIoRqgqP6xSn{T1{=(dp2Roy5G3`LpwkJVi`* zXiZE36DoPni3CL zgn3#4r62LRwx5kYgt>N?jc&I4ct;d>z=0W62eI4K>%k-tK@MU!5gN3o?DQ1^+2cji z&!-%kjZZBOLr|6{L1|$>Iq5a(at^G;0Wqvfp|8O*#wDE!djeli-PN!=ym+JRR`q5tw-l`XL zzwRT#HXURKChqO}UB;8bPR-e$&XO>t7G%>j);N&vf-vd##cXVWe^C zfsi@jdiZPh7-eEvv;cC3@&nnvEIKf?-7&Pdu@M;qzWONH?+_ep=P5aT#86-2<$gH7 zx=KL(ni&u64w5z1$eduF!5BwLHmyVe6?pE)V3l0Rj$~7N`T<98)8qjADVz51-+>_s z{Ev9hA>hYl_jTcr(a+*Rv<{M>kL!g&w7+=F!Kwz)44A-Q52BagqJO$@&g;6} zuOB8IL^z=XIz&!7Ad7mLj=}Us%)*I|xtPyG2lY7xQPAxpr^m&nVQHh9pmg4p#rB8-}&u8_EsUi&o%~C#R8`= z&e`qWh`;fzs#~4f#&PsX5z<^*M7Kb2KQE$-loDPeUnphsCeVC{>aQlyDqKFEKz){S zbZhV=+K*LFqztp*>50^9nU{EZ#TE20S)gT4qEiVZ_Ske7;I|ahSt$7DVmbozlj(3=woIm1^mrP^o^GL;=e&Aw3Mi9YXBd;F;+4;6VC+;{W;Ebok58rdNC79Q zXPviz$LE#kD`Ye?oVjkLDrV#dOK2WSx0lc%u(m%fp%=mzt9NwxGN7!W+mMCp@zHAl|zY11exYT}fYr5%A)4+Rw4pNz#yGb`#XVT3+byliHlbGcC9* z;4d9JS|FZfRpHuNM0L-3-EzLySf#c(5H=5aRHgh1Unf-b*8=#~Q@MiGXp@Y-<%%yi($U*Jl z`E(t@+JbjfU$lycop4lUX?Oox{%;YgjDN^3mklvKo&@?h2Usp+CBO4}$jM1waw^wl zyh6~da4M<7DPAEc!??J8A^lv22HWR{$M=pJ+A5M1X7|$~e$^umH~4A6rPh?*`q`S& zbLr?dmyV^dR0K7JHPt|<|L&(lL}v>7$xo+=nJMhb0L|ym_&9*Y19Y;On!?@<(7|GQ z3fp#2oX>AM0p;tUBJPqXVsZ zDTEN_0-FdpzuZl*{6$XYY*l1|E%yb z0d3zjn!$91r#J8)E4(#H3U8Rf0{QHPTd9(gn-uYwnW86}X|u1QyDXOS6f;rmg%#A3 zK2JvoL|0f~-np?BDr=)tL|=-LLw47%ql!}(+=vQ&I zHJ#16hX%21dgdNldhyDXWWBvArI+4bgWUNK*r$KRx(eXj2 zOxfI;(mf|{Mf>6@S=eTj>9gHr(Vbmo*iRcFSv4DIA>GLfZ%<+OZUVWV-9Vp*k$2=l z+D}{i04*obV0fs2-s11rRU7H#{_QDb(v(CDIFvB(+l>;tH`3{>=3y8U8JlR>en2On z8?@};-}|#|o9NZ#pi$QT5a!=QEcYQgWayz3axD+~1^iRO5xAG4OC|?nJPZ{>Tn0d_ zcC3>0KCFC*ZWNChLrXty4(&wO&;+Eh>E&*In9fsA@X;Ml5l(9FJxr(CpwY5_M{f~N zrLd0i@`c*o-@!J3CB}IRDs1GF^fw}LvGS+T)AygGr$O?+ZKs8z74O<0`KtcSCpul) zAD^Nh^(jns{d~+NE!NT)hz{?LuJHXmu$XxU7u!tbi^d4)|7Z0P#XQ+;hQ9AeukU85 zYpofrMX@k)CA;_Nm6CIIwULUhWcse1Z?L?3mmAI9)(8<*>Dgnwz6+V>>tieTV|T}u z(vXxIj`04G{Nc00v0`KA3G~_%-07W2HZeDA{aos}=`#mManJ%m>O%-SSFp1OkA~+Q zX~w#zzO`rmxtWMnT^hpbB8WgH-^4N%YkD(mgkbgC#!FAqTN4&fd5PZ|^?iRiI~L^NUL zSlX}ENZAAQZfqm&fCoK}=JjwYan=&7(hA%o`MDd_=VBor1xyTtBZ4epzXvJ+l{yxX z`)#)D^-T8pxk3mCtyEgglU9MBNTgX$YUR7Skgpzy@U1)GAM8jdoEO6KuO2BvdVZsz zNFQ{)LC1Bz^yqwvTp%NpJ!!K6kF}Ux)sa#MHmL|3kXlvg$;2?~K-dsRL^3>J26Q)D z_ZD6gG&YA$^98ZVPq0Zlj5|)B#KuuG9nVOwlY=qZ@7$7aMtJ5VELx|^Qj=Ee$|r&Z0;ciACLTu;Gj72q#O^j07<42@{y z=0;cy<>35s3?QKrt|B4I zoQTkxuS08g3A&ys=8MGw4UJ$;E-~2184iuq)hg;331Cx`pcs6|H74M+{v05_gOOCo2h$t3JFutp>>n%Tf3;P?}~}=J;(Z71Eu#zOf8T>o~6c> z7oUN+Hohqk(dP@FnI36u&Ga@Vr{`hY-Q+-N2mq!v>+Th+krmk^$Bu8#OXW(!+>KhR z7iGR*%4w~#v>6-Hn`>)>HSnLu%miuZ3ci&Rn>=Hf^^6^X-l0arQ<`G}mG0-$VUJcY zpYb;Iy;ohEi$)uh70;^V^%Q9@uV*kY->-E8GCl)lYN)lRc}s02iB(YZpE zc5n!d;1H-@zMqgQMV(@aEs@xiXtsc80D{h~1+Je->@zb#j7cB+R0OFMY*$zj4$s8) z8Y4jhE??h;vN?fI`a@Rc90+-JfbiqBm_itFA{be6KM-zIY;qx?;+}Wt zRfy@l{|?QYztWYwDp~I8wJKSTYJ63Ka&x+N7~PHyMr%-3-(Yk*fSdb^))4lcHF2cxr@B}~eE_DoS10v>ymW0AfLlu1Ym|=nO2G(k*15MV(>t4X9aCbX*m5x2Mm{GeA?N`>&?ynBew>ATKF@=HTp z=0mZmz8HwN&FJ0@@6)mQ+k1>_rz?42J2~8*Bim_I{rG*l10L?C4`>d1=I^v1e|HZs zdpMYV3Hi7@nSger%<_2u^dGiDK{r0o(KcKmJ2YQgDy*F9f4L}`i z`e7OcFFb#k)?$Hl!H3juJWXSN`H)h}ItA#TgWRY?F4pG@c-{`P0Y_*NoI1;o(n0Li zPvCw$&dQI|2VBQpM4H1l^YbG(qNiBu$21$?azDYs^8_zm$owDET=xD^Iu0u$z5d@n zrZdg_9iCo$rgulr7#-you?&eAdbRz>=;b1Dv)hi+Y@-iW_WmbylcQG$Mp@m-jHkMR z**cT)R37ay=~JiURW(7D{)2kisZZf2zv~|~bCk0OjMQWpsl8yNnlPq(iIpq;h)yj? z?@|4Pj4oul%|4vacM%!8>oNfg#LYATZ1{-I6|>#!%a7=QfjMsCxgHr#mwQH6B7qDg z^(|QWLav+b`xuVKiN|S5A4Qr&^4tk?26mg9)gPzDVu71IeVh(dGa|YOj&ZY-c(e$4 zdgTG^!J~A5IMU6uqtwH%t3Rff@k)SPGipqa?&l?sKCjp4$C(ftKA{uD3Fr2$`2X%( zNuqDXCM3|JKl|)cY8TPl?>?nNyd^z4RGi$Q;$9spWjnusi=pZS;zBc7+X*`Pss(Or z|AWe#+=3%NaFB(krVeLtU=O>`P4Z1OJMWS)WAVw&H@F1cq{w_-Z0tqF7sp~>)W@m>kOb-SzCx14DcD?y*=o62?%9*{5?cYBeRKItwwQ(gMf+zZWPMc< zd_&e(nbjbt6$NX0bWIo0wTZ4Ji|AUTYirHA5Yc|@^p{YJZEVL^n06+AMH?{f;QlI1 zJ3o9yFUi}`14)~DBWaTXbCUs~z*EU@{{Z2_o}n6N8xtP5?w8{1ME3X@K=(u-`(LB_TyGO;1&+Jm8sFjs)mM0 zRbwRB5M=HD!Zg^yzWNvD#u9enH?-J$utzJalN(vxtBpf!-8Zzf?_tQ}Tx>gsY>^Sf8$pli4Cw2KG?jdFTm3YF95Xb$ez#Au-{4KqvwG%+PW%N{U zdYv+A^NV=KE#QZN7q%l6u}AZKEMR%VkV+a0-=c!XNosO4yr7{91_+70y=FmcD%!!l znrdV~+rOhO2Qnh(wqS|ZAPGK}`yHn70Gsn27-uWHjbFC2-+f1Mo)dfjJH(hOS@vlJ zZ+5Xor>PfqNZV;1FtNoa$?5F9)0kg!Y&eLlA6rY5y!g}DEHJYBnWy-t!kXTEhL(sW zwzv;sb@mKJIwBkUJ(_4>wckU3H?jM_rVTS2LiP%67*%~S+v{02K|VFYgsA3>}0VYAxl;#+wvpkPNy?| zg(HXk@FQH!*)07hI$F$g#&eJ$R-WkO$l|8M8I6}W!xTsPKumJZ5HobuN5 zNH@%a+P6Ffk>qMFn{tCl^0bgcsUyfMde^C3PR3|`QzLn0$p zsj`$SrR?TZC5QO6jj2i+Q39vuy>m4FQm6K0AEkg`()_TmvH((3n5JA!g4*qA%I__> z=4U7m5(I|c$i&y>+Jys@EJjx_jN@T0{ z-35xZ=u*4`I$Mr5%wjEn~iN1VhnIU!#PHrCDv!Zd{-|EaHuAp^|5@ zELT@*W2=;OKHM4A%6IrM)u%j8$SUoNMT(yXp2};LDF}eBt5x0rfKAN5SXqnG|6Hs@ zkT(>lQ^s1`)UE0YmSa^s+TJ>4HX++I_l?Ri2ic|l^CqP~A-gr(GUYD>I5^azltDFI zbhC0BP_gZ1Wj;Cdr(1w6>oHY=AG7lg)ytl^Rher!slLZSj@3plSC$jI)g}q<1;Bq@ zfNgo5WU#kyQ;v(OO?$3Yc|n0DW4EkUJYuSiZCb4?7t?JlYYoPetu0)u3?Xdnok}xQ z9NT;?^xaE$Dmh}VSu!uVBqPvN-`uTSfE0#|dz2eMfu;8-X`*GE?RMUq9PQ~gWuFXP z*mS>gnOuxcNw2e&cPMG>rTdkIV8Fo}lx*mw2^*AgpwrR~$}JXBuKjDHk}4v5+xoE5 zk60GiBv$aSvXlLFvvP-}GV$bmL|LQwILJ3Wb6MjfN;(_$h@!IR9|5s9v8vw!SwZbj zzg65K)Z1%Ym19u3+a813XtuG7UsSKeIUa)^SLRFB|q7tzU z`S$zNJ6L!-I>BCdtSK|m&4C1GqB>kXpa)Zg7V=#${koxUi5-;3OdR2qVf&t z)YA7V9}W?NrYu0dY?*_V~u38ecM?aIF?8L71%P!13f zVdh_z>%sTi{tEuZV`c|{&t#8wfXgb`Hyz5QmViCTE_w^X(!g5YQf87S?X|Z65-d9S zZ6yqr4YIbkl`Da{LvJgWSfENB?GHyllmsblr#Gl(lU6hZxQ} z_U8|kZ=quM9f6wJ#Lga37DJ)~N0pF>jMBINN115FjMev3jEsWNWV z30sF9$8V5qD_Khfa?n0RNGpqkm$eY%qGa)>U?56}WEBpv|M^sT1?O%&enL4RVS;G; zQn^q%YbOrvxi6LbL_Pt|?o@8%DVWc7D&xge2m7v584H>B{!i zyCT>aCHh}Q{|g74b#$gfd-+>sn1q?`^Y1a2Ee#Cf86;r~(Ya`8{H8nS@Z!>T$MG`UAr8^G~rRkg?pP9N`5W6)6SX}222WpJwc zky7aZZ$IfVRj8*A8u4jY_EEkcPORrGhA!oE%lhk2W#F}p^Q46@0vSTpm&-Rw6!$sApeO98{2o`mxrm5G$k2&i~ zwGfwIU#Z>%Lk5{v>Oh{nUl=)L|bbus9Ocb8%Yz2s_jI)S0X z%5qS!ZH77wg0_1GPzR;wnWdIs8m*nB4u^KTXO_AbZIsPcZzUz#f!S&>g_}WPXSz1? z2K8?gi<6vM^=fdV7OGWmuzs&6h_Pmhhr6bL>7_VkuqfZsz$ZYy2ZKGX=s18r%`dlrD z&Xa8EGW9rKSGK6X040vLsFzF2X<8ZEQAN|VOK(f<7^k5Yf3 z-i%r0u3xAFVTEW<{8GKf;>3AL9wRfe)F0FwzgDMOfXnhb(Dh<=@(y(hDb)(@RDB|j zQqmj^>OG=~8OY7Hd(}Syoz-pX<*=tWwt;~bF#COKyVR_L|+w8qltdt7U|&)3*Ia&4oT-e_f~kLk-$IjV+^m(HeGknp!M^dwyK6 z4uwT<0aHH^$+q?;&*_ zQkb>+ht*l!f&1KMbu&n?a0>*XgWbAC{Tt+=>=CsJig3px>Rsr{gx{(%FFN&6^(C@L zqg&N-sn3R#X~Iy%k+5Qc2NFYR*KXXV&ajX}n)amn2JiaqPpOL_f1f<1*1?J@ds-a@ z&?}x+X9M(`PpgY8ds6nXF+0>dU_$KOf$kh&uAM3pZ`sM6>h)lT(*IJIfTX+rOMM2E z4*fxW-+7p(CDCTyA5|oAon-!9oaovoyVPPuI)y`(*oYU@f!Uqd>@!1{K6gp5aasV@ za5QS}p+a|6=6^wb(CSnKY@?B01hUVti(XWxi)T~V;K!9Bw&O)LohORC_M$o!$sw9^ zuUbry9im~@-AXK}u<-7BN{MJUzN~&BwK`p8LeYF2gjU;*wqWtdN>>nGKcF~P7YIf?)#ghqnXA1*D{R?Pehw!mhHoH!J2#@<65-qxr`y*<_DLLrQEDf#he)yQ25#k%q%02yH>*LIpK@kTVL0-z9YQW3xP!XYj>`Nrz3x-?^bjwPwkPU(f~`sCj36AX zJATbGG; zj0d)h_#K%B_KYPu4q7pfF6w5&5xjk!YxH9kjW9EB0)@@I1RQka6($&fx?k}w7*%)* z0OZu)+T2YHy>mc^F5yN}L?Y(RpTK@zGSYSK3GSrlf$j>nlJSFgVeIOxmySiAlukUw zx-!vOUk8mN(RsO6pw~>kIVUJ=WEL#OVYeZVoL%tY1@vesfPrLNJ#6X0tC<*wBu^e5wtnR%|&Wm9kp*5ZJUl!|5MbhW^>da7P* zCjM0d?vbC4{p&v0N;2B0hocqwk-9XF3n=2=UQEDT)cy%fsX^!;iPZ17e4VQIFo z7xt@vz3S0J(dI4MTkvagAe(WRlURc{GJWad$ zxcZqWAMOe3Bw=$uQ!8)+;uD|2KCzbCrBcFXr`nLh`-R$sRdmxyoEmZH3)KOu;o~o0 zU+Av}xz8GdDs)GE^|3;PH1~F@rR>v_FsYnwcJY_$FT`}W_Vkx(HJR=yGKPxCQ&l~1 z8xykKQp__az|Rgf)P`Qbg90ACfH!NA@h=NVS>`FKr4$jx+^^MVllvqhi1^7D>Ke0C zspH&6kVWs*N`f#RdUc(p)ZNUo|Eb<0uGD*Wt5Qn&QH;_WZP35e+sMUDE}Wwpa<{pK zso2>VX{o|d^+*+sA-656IGUHm%mVA&0y}bA9i$!qh8uue*sSkhC%3WDr(yZ7)1E%9 z&VvETtUsu~vVdsX-9M-|iWxiIMQE%Fk$>cwVMABARww8LPS6or(U0nbCW$MJH!87V zFMv+s2Z(l5vb7NDfJ-T1h|SX4biZLg;6cc-RPw@sE15qO4`I&9f&h{JXP2t3VUy9O)nha9$5 z5)A*LskYl%drX?rbgn+D5*o0IPbfl$<95%LI51dOBYY~u^kd9K@D5X;`11xbAJiflk31FQ(WL6;MtQgZJ!EX#{cOUV~kWVpi z#PC43Wl#v|b`KwvOwPG)8j-GLwe6*BboXZw-pj3$?}ioFVL?!QsT>ju<@T z#W4TYC^GNG&arO_m>xr8UR*ip=eJ3&Qby=NA816H|rjv$PIhR3i5etQ5Ck=DuJO-QI)d#}R_YDmsAD(^RuwO3l z0_s5XFC;Fe&mt9!a6Wg9 zUUD%B|CzHYEA>(Z)`^5o?$JTPKtDDNAtkUZpZde3j}3lUpZBpLj#R+fj}4=Oup=i! z%gd^&m%2(*Sz4IV}}sEq!DEH^Mnc^DZa|74g&wkmUeGVCQm+Zc&o zR95Yr?<_2M&)1qhUqeC zY)hbzV4=J&)18DgDf9L85FvZu9Y!Bic2Ihg_u)g*2)j9b-PDMj)AN1%q$UQZkB~^% zEU10(9&DBOGYS9Hx53f@2K)B0NToy2{j%K5;CP`S0vgP8J84k@EVNrsTHsqhB;Lwk z!%D+2Xcn&5ln9* z9k4u@=3vntLIa>9nEI0w@M$pp6X}8#A#_Z_3Dk+X<<+jTQa$QKC#r*ePFblFb-})% zti-98Iz7tJsUU_xV(G$1ogQE7CA78+>kOgFl~B5pl5S;13_U_g4@AY&k37XBB_)Ag zF_T^;c|83$Cufx4$&?t(JsKkf6HMxbxaoAb{~2tN&oT-BkVS+w5$PN(n@&>=eT=-7 z4m+MP_=7!*hQQm?X#%+nebeb`Pca#GXVEQkAA{BzBmi!nL1W1!D40Pv%9j{q&ZLPT zZlUu>Tplpf{u^i7kF-(k`@M)By@d`X1diWA@4;fqOuCL3;K)q+sw_B|pEC@FN3-cj zl>A?_=~uD^E&2@>?5=ahhGnA-%uE@dERpEKveN2GM`88VCigqedGPhERJh|;jON!` zSFGqU*k_JO9q*pCGQo1SS(5cHO>-Mv;qE6_Mk}XprAdV7z&e{IxSv`T>h18(s>JJf zbgnrb><(9|_xM^n(cW0~~r*(O;eswarj_LI^ zH68>nW)_bm0oR5^cy-y;2RE4z>TWJs^M8CB;+|K!91h+}{S0>JdS{{gucc#AM6WKQ zc_M_P#OsI@0ilcO?IZ^F-%bM}r5LU^Pb*ueDq|c+CBq*{`!UE;0z9*rE+I*W9J$|W-iE-kApaTKR=qF*SH#IkZ{r9)NnYfVE-9Hm8$ z>axmO4~&%R!D`5-=}{$&qQNL8)EOElM=G8&p$_+N7%oV2Epl*G;EsQ17G3;U!p=`P~g6fqK4n#C~6Q6&8Oy( z78_=&m?wglTNlv zjp}Uhsfoj~TZzB&K_T5s%zM<=W+^yY+RtImBIMFtn_svBp-Fa5IP2+S2hmX#zS?+9 z1wG28b}wj$7xc0ZXb1GJqA9^Ac=%LDv7@xmNrEvBb63#`{LLYFBOczZsCx%Ze zt0^sX6{9bQ{Vuu)T|XA-Fm5%9;}pzTjeeyYs#nwD=(QhPO~;a6_+T}ira!~UF6f_) z5hrC09jZUe$z$&FpTeMe4UHz}G$ez@PLk54=mm}y&YZF$3>SSI{<(&ZkS}qt71Kx= zX~vY$X-HEo5~jr8HhX!`+!b4gn~o zG4fgNF8ygJd{K&?{hS96lELT~0!zziv`VYKj20U&t9BCcycf%bBm zGRh!+%Qu5i`6iKIM+vg4I+$DqkR$^{SI|U%efpfT5*I2d`jL5MWyMu`Nx-rSI#MSI zp(QpDUrFuoLj@fyECRq7A9!g?A9=CyPTiGay# z=~6jDsFs@yAFQSRx|nMa>u7={PM~mu*J3tPS(OA+*U>R*ld7`G2|`NHI!-etsWh=Y z#e-;gs(1bqeCD63bvtagOA}ORe9VCJ>uEgXtf%`(rh@e;dUR8x>u4AHB(f1@ne83e z_JedVCbbnCX(CC0$2ZbtnDPF)k){vI6C}~=Fl)w-Lno65Ih$ylF<+&|?UGag8#d8; zlS89!m+X=g_&szpaly8G=zL5mPv1l1ZYmM_=&1`Uoz5IbIoHo3l711fqx-7BZrYDr zBP9GooQR?pD{`GccadICj~MH<_P2ti2DnmBA0b=SA`}kYOQ-s8Q$cqM3IEjbGzzIv zKuRZOv?2GQ0yPOBuVA*8e;+lG4T|$V`aO5w`)h2(9<|?oA>p4&vr{J>&}z*hO=^ZI z+c5sOD8<`oA(2}>Od8wfW%^Mcrnjk`%=T1Sl8&kv@%fmBr(Fa~?H=i2j+VXyE#nV= zVm8>fou*)-_ThHw!UEgd0y+grTc~ER>0X0JE_ZsmMD3)v%3Z?!{LCP@cPG6Cjrwmp z=^QlUS0AEL@)-d=57Agy@(^7*uq{qLC7>ddrNns$-QzRpZf%72e@Q*+knyS4L#@h9 zryx#^+Iwy~HSnwkm@1!^&Uw1D3#m^)SH^?zYm|~SQhhA=P_-_dm7au1-^Nj7LioQeTJrD zfph}3UEp#DTivD?w^|{LE=jnW%oM&{06@@E%TPY>Da8psbf88{Q z6hN**2Sfi0^pC{hsqcD`Ucp5hyo9N{(=)>4IyNkN9NI&$$EUfBB<-rHq|Zu>UW9aA zkl99qf=ZA_lKLZ87$y8e%^epk)p$_f+lJ;=d!2fn+8hq4FJru}hn$yb6ls8xmuVXoG0Uu8YnH7A7)%US-)@SCO;Q z$FB(Z@_o!(p6#M@Ef$mNyCl0XNq#2iDv*Pf$PZ~E!xVE?H_as>u&0|QB!rlJ#}{F` zVSEvY2gery-*(fRG4mbqDVkp#Ec=v3_{Et>JQ`40O?4G!shICW#rrg6Sc1vJ#U#^p zaVDt4;r3uD2`e%_rD(7Lsb1I&AJ|kF`5CSeCcv7{X*4|a86AZ61E0|nk)d`@HA$He z_BoyFff$cb?}^Vb#mIt=&*^MqwnhoHF9*VUaLBnDAqWW^Ek0%li-BVoarL|({&5kl{{R@jLt$gU{Ei0W&PM)s=oJ;z9K~VoHBnIBV!+D>12>Flf=xbDyJ5A zYH*Pu0AB<`o{=RJi&_tdCyXpF%+E~5Wfm5y$*dZp5R?R0YAcff7-V9TiJzzADicdV zW7d!yHnH8N5L9W*R#5XJJgvoMwg@h8RO4kvOk+lGJk#6YXn7boD9 z8`S!v(OWe@`#TOIZPRFU;qq>$Rtr8THJYLSGn{3&g;|x?N3eK3#?ZrKm_PK7Vr^)M zTK(^<^_GzRX4Fbf(x#Q+*b3E^%lxupTw?F{z>s8={Q*z?mSh&|*K8h|SzeA2!BJdA zTFjCeMvrCzusxX><66w*`b&v~7ddg)*HztYCgIaPRSA<~jrNDhtN;x?IfdOat<`*E zrqXJ@G0}eqN7{x{)PCfnW(ogPm++2s%!Bk~3R?wZl5se*#;_o{!wlb~uwcxa>&CD# zSRm#4kP~VryZxOW(nX|B4MR8SGGolv26pFIwi?r899SHjAIl;Kb)z|HdDH@DwYELd z4UyvzYmWzh);QJ$hsGdHm7Ve#GfYWk0pyJ5?dnvPDW5efEvYQoB%d?Gms8lw`aZK~ zA*-}yvM3^7GQ%fRS(kj-49(NnGP&Ojrs*t9E#s%N$I!q#rn5Mm-eBRkL3=n41pQ1_ zY!eoB_7>SsmMj*t_sxz?dZs)!lP%TBeiqFZf&wfa-3_r^x2gc#jr?UcZVoS<%^os_ zASOA$B1KqmNgby|=dd;z1Mnwv(LCZ{{XDjuC#WFFQWE@Q9(s`!7@W(NVo{aL9GD`X z%4LTzMmEf6qp)~sKDwt23mkk}AF1FrNgP2#{dOT+i4k&f9!tddxjB#BgXwSPViu1D zS{Hz9_TSb-s>1{?(}aL|CaxMr=fxJNm|qOSKqORcpKvkN<81vf~{ zQk_M?!<>^kx!&SApK5ksC~45fEa7hcRybY6qM>X#n}e%?*Os#^^gH@OG~8{TH;W4y z9{s_tLN*gUSZ^Vk&VC?8Y^c1`!<^xbUQKWE(ey@*fIXE;O<0rhagT~F44R7A3`BtS zP&>y!#zLijWhRy zl=WFR=2bcj9J$J=Ri{-3>FgRi%fldM{R2&g080O7NH zoT4n-!QR)QZ%=-hO$?ZzF#@er%JA0gxHP)wVKz<9vclY-3_~DnH#IBYKg_nv`fTgf z)6&O*-K@{K_K1h4Liq()8AHdrcONFD_wL0+F%O>J%ch|d{(3JgzjoosaRRQZolpLm<>*ST zK8<~nZNTNz;U`%l`0rz5v}Ih45_5nx>u@{d-DlWGxMteh%mQV(9>sv?n%WTneEuAB z$qk-iMqcvT=vM3XSi2SJ%9p6L&CBkcUQKNDf!PTuEy#@v2U(hERN;48r6#rQL-a@L zD<$-KmP^oIX>j)*Vw2EmwI5=)1vOjAlxbxp<(OGw^5#wCG0oBdXBs}*DIF?gYs#l{}9lH1hN7H844vP#@)ty(kHRjr;8 zR7=t^L|`z%@k3~vwinoLxx+iH6W(cc`b_Ht^u55=h)#7zC#+JJ;(U=!H)4AA>@jw? z+-=oPNtIczu@MCGDOH_t`|*|I>|yzgRhjoXvk{c;vUZk6&U%#XoE5M+2rjj=Tjf5h zf+wo~8WeF!omBi~YrmD)3^rl&vjx~fY%#V3n{?R@*vRPNV%x zbQ;B{*>LnLTK)Ocml0~`!I_M@YF|L zWYM_CG4~={hWm_%FQRLTfOjrp))NEgFS1FdI2*C6M<4301vW&(;_t9Wf~WUG-=RV$ zX(Zsi?~r^7e2Yb3s*R+nOQ5Xv)s?Q&Dpw)y-lcfJCVtQ64#Fwxr8IO^sW#+oiVY7I zANw9xiW%_l?^(j4Oq;JUWY}&nh79b6#*l?@*Dri*)@PAygw^hQY)0hX>mJ!2;B4so zfyKg>OPEyTz@bYlHZ0dhX6BWyDJ@%9y0FqwT7?JMsDvu!92Iy7*nVKWXzN(7Kr=7= z53(g6w*H5WNXfSi3^(<9^wMcKs{*8^{m2fRgn#IP>-A~4zWUdH*!;nHHj`bD)hHmBXIN`~WaE=d2F$0z<~yMZ74N_qRY2E|Xks<6@h9Ao=(x;Ml4}QGs8cc2 zUu8+14~BZI<236$U2pk`jmKpgqPY`Yf6q^tS#AaPGmA^uHb4>$H%g+xgJ>rt|AOl? z*Uy+&JK@C77(*J>rXtVNjM>3>Q(%Q_}ClHJJ7D(W^Yt&z6wZ#{E3QcECpT^KnKxF&%eQ~m4gK*%Q=R)2!f#JLHK5~JicIUis#a&|b;a!VOt#z8^(mPzkb7+Kv&=)o zdTk{74m>=Y7(K2)l6nzPyJnLtov}f^9uE$$$?&Yohezd0Hn372h9@?@@{`YM;GvpK z;EgM|&dt{I@p7NmT&UiRxq_L?WiM2cfoI75J`|{`CBzQUk9Tkk(52(yzoCGxwcd#1 znL&9hY$iMa>L|C3GT4264YB*^4epOzGfj`)2$&~$FnmaP3bBBm@u{I9cD$~IE^%sY zwX+Iy%4p0faUJ6-ih*Ug+Y=gRzm|Ar78O-Gt5hKKN=H=;>|i`BBnCHJy?JLIUZla7 z{;tyUn(732i}B&;t-oPB1`8|aBYqFKvZ}8H7$QN`nY9R;Mr2xDCIBgFJZWG)N-*dh|gsAT#?6?z$ z3>z!Q*irQAn(v^%IVIrrGCn*ZX#ky+8|kEY=%lDPB5t;;!$!BOUKgmr7?V0ca1&I) zWgreMN=(3>SDk8x9n1zE1CJT`?Ysx0p2}wEx`He3439=6nfRFWI7~Aas8MaDI=9RL z$g_O4BFhK2M=P>CXkIq)g~{0iI%*b`b9Gd{7M0_fTaKqIT2vscG4o+^t{pc!0^myv zw?Lbjm&kcO4Kpmry?iLO@G)3?j7*dZuD!0f!!dxN&Knu(^bl1GJr53+Tmw3k+}NSS z)1kx;z3T4faVsB*x%byt$Q4@Kbc`znHa;7RXKg%gRLy|tk3*T%UY$Lzxvz?()`PYd z`fdExsdWSTB>VPBzM)V30N@5yFtv85H}Sz?UaSq;?6%r>+NG^nmABcYZIIx{({Po( z+>eh(^=tCunWGv9VA|qKcFPT9n}84E*@O3>5iLLyR&P?Qa#W!@?t!8~yew`10AvT$ zVYOVH_yHWn)tR?o9o0*1lAGQ7V{PXlup zAB2bVSkJ<-IKz-$E<6;502OddEgIp!!}v(cSu-iVy2ZW%a)bF`_fT^vF}o+5W1uXY zhaldq;e4EIvA7SL!{F0!9!DY`iQwsSlEppR5&-W+ax*NA;OlYw()~9}D0~;e!*K%k zNE~uKOo_xe*=BL?w?;r?B)&NcFGTX2a8u_hZxcM%*n41N6u(IyVfEf)hwV{33RC+- zQG5|DnBB?t2vrhhNRH-#a*owqVGjo-f?M6Y?RL4q>Tazk@s|YS*h9&ZeBtco8$hQ;Z^Tj0oCi3~^QT#Bf)ymO)sV<9$z=_cq z9hWIzkLLbFF{PmJP!%SO;rXbS?lF88DNw!~!=KY(`KR%GH^GyT;c57`La9&V;d;!6 zA5Z6ZVG)wSA0yk8moxZoJeE-APvv$Uu5t^rcn#UFbZ7A#J!YZ9Z{Nl`w(oj*ZHhO%jiniOR!yPe#KH<^^DT%77ij?z-hJ8)w{sjB99<2+wku$Gq* zUA`z#I@a;MGPXGH;t4vPLpYV4bv!`5r1A7d{vUj?cN2daXRO>(&x_RUf`gm+G#tQ% z&HOz?P!Go&_%?*j`2(*+2mbmW_;^E&*edRUiXl8&3Hu|TOOPdNw(wpvvLvUG2jRtW z#rX(7Nst%gcJX;=X$`yhM$DS^O?(mF=f0x}`GwnvU61kxyxRl44~m*f7rtPWnF}EvpSI&#dw&vQ}vonW)Ozi zf4-=r>B(Jn1Cvp_OH@IC!mk|3F??s)|cc2H! zhvW`EQ!X&V`#mCEdAWm!=!rx5-~=xvxZa!mF5i!0>U|egy-qps9yfE;%ojf9Um?4j zPVp6_`H@dhge}VSPxv|+x&G?k`70>sb=|1aX!l*8qQxBp{b%S3k3rgJ7??Yt;WJ*1 zx3PZujNeZ>q2Y6W32pQ49{xQ!rIdfczaa7%Bg{L^-<8i9A)=R`*7Y07Bmdxw@$B}t zfAGx&zXa*|C;x>JKjqV}c_(23CgffWQP~#&Z=T~*P@VPXQ7usf)!!nS6!`U9RGv(j zdx4MDWt(!K;R4z~E_{4}=a4)lwhyr*H}1N~%TN<@;G2v57UbEu@Aw!rRap50kHEwQfuj)u`6y!>XkuLfUANdHv~`(c)#4;nHL=75&i@$>L^$XHh*V0uRZQJI9C` z5|L|8l+1HnRYd)m|3 zBUT`uT{)sp747{CL=lSi+XW&TEy2D}G@^t}cZgbu$`gaI>9IS+AK*xyuo&vh$Yz+l zM5My6c_IJ<&Xpx13dSuGq3FiuE)pYjTg}^G!y+*b#oxL}{8@*=IPZ33K$8#V9Ui2Q zA(QvO#3iECaNs)Pe=b2}$5h0r=;#sWFtld;cf#E1MGR1Y3^@fK7Kl$skJ5an7;eCQ z!mdJ*f@iCc8H2a6!-_;aJEvj|AaG$3I-5I-L?3Qb&2x&|a5>cM6lqxe!ztFveP+0Q zg$Ps6UDvG;_BD1pB=iU-k`X`#ZE_QO*xvoY|pf+?&bg`TagX8 zery$^b&V`rd9hW@BdE0c7X_YAH^GRP#6MA8tZm{}Eb`mLP|O52wu#SW89&C_o`w4v z>y9923V`B8!Q4;B83SS6DZrQ%hQM`#JtOa2AWYZSrI z_^Plg-yah(YWN8}F7}~#l;db9ZSY^mQFg8H-0NZ~KAGCZ1IX50?E-Is*1+C3#2a{& zxcp6#s~=Qnogod?u9K?wN^xH8O2^w`whrUVpm)W41Qp}U_r!9v&8eMY6&{>E*C|Hh zZbI}oxJSmpz_FE6Yj+5ekOen(M7juyC>i0zt5}x^ixC0gC$q&RDR0DgLID%?$ zqDy>g>1O^uQak*iz%#clIR25i7YR@OSY-0_KGu(u)z6MzfM3iwi=xXNl@9z86mO%C zB1w##!>gOKrI`!XxXNcZoyEAY^InI=IT+yh=VG@(aEWxo(jE~f_u#o*2ZpI$IN2lS z<5>X2ej%pIXRXSrFGMD$HCCANF27$%J+11NMak_I%M5XS)_GF;B4?#*MXk@LBA6=X zO8PzW1i4Rt$r=xRU$Vg~E?c)unYPMVxMo4Qvk<$e%glbOJxyy{xZc}BZ1!ZWlu=i1y7zs7oG&Y=g@skfY9^e zMO6Be=f!k=rp^8&be$K0O5(TTVWLa1Wx<;l#QVBDTPig6iPgp=n_Z=sXLEwMD9-u2 zY`l7XiYw{znZEI#1I9T4S`!a;FXAgNz&StFAT9+O$(Pr6tI?h>RRMA3tVW>&z^7g@-Tqd;+5n zcX)ONvssT#pF*PNDdsZzQN66*%f2J2C%89?L%Cfi?5Xfv zpz&_>=EH-Gn~^&QgNz%=$P}Bo*jZ}NwM)}0YMhm|OIJ9ZON&q!T*Xz~T(iE~3y^1* zpd`W=sZ@m+(T6zT^DyHXOyu4OH{OMbTtPI@Vx zzc?$G;IMO&C zbG?p{#?|Ofk`s-8HEp%~Yp8eH8x@jd+(HJlJUq&HMD?IK$;PR;E3qTlI1P)BlZ}hf xAtt668}#U7UPv`=gwMwthmd10-(>6~a=TsmF3otSPVTTP3(}3LM0dh2{Vy%9#fAU? diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 7a72f8e26b0b4..440972c7a681b 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -18,7 +18,6 @@ //! and is used by the rpc server to query and send transactions to the substrate chain. use crate::{ extract_receipts_from_block, - runtime::gas_from_fee, subxt_client::{ revive::calls::types::EthTransact, runtime_types::pallet_revive::storage::ContractInfo, }, @@ -649,8 +648,7 @@ impl Client { hydrated_transactions: bool, ) -> Result { let runtime_api = self.api.runtime_api().at(block.hash()); - let max_fee = Self::weight_to_fee(&runtime_api, self.max_block_weight()).await?; - let gas_limit = gas_from_fee(max_fee); + let gas_limit = Self::block_gas_limit(&runtime_api).await?; let header = block.header(); let timestamp = extract_block_timestamp(&block).await.unwrap_or_default(); @@ -695,16 +693,13 @@ impl Client { } /// Convert a weight to a fee. - async fn weight_to_fee( + async fn block_gas_limit( runtime_api: &subxt::runtime_api::RuntimeApi>, - weight: Weight, - ) -> Result { - let payload = subxt_client::apis() - .transaction_payment_api() - .query_weight_to_fee(weight.into()); + ) -> Result { + let payload = subxt_client::apis().revive_api().block_gas_limit(); - let fee = runtime_api.call(payload).await?; - Ok(fee) + let gas_limit = runtime_api.call(payload).await?; + Ok(*gas_limit) } /// Get the chain ID. diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index 16bdd6d1a18a0..a19ed28dd9b09 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -27,7 +27,7 @@ use crate::{ exec::{Key, MomentOf}, limits, storage::WriteOutcome, - Pallet as Contracts, *, + ConversionPrecision, Pallet as Contracts, *, }; use alloc::{vec, vec::Vec}; use codec::{Encode, MaxEncodedLen}; @@ -1771,7 +1771,9 @@ mod benchmarks { assert!(ContractInfoOf::::get(&addr).is_some()); assert_eq!( T::Currency::balance(&account_id), - Pallet::::min_balance() + Pallet::::convert_evm_to_native(value.into()).unwrap() + Pallet::::min_balance() + + Pallet::::convert_evm_to_native(value.into(), ConversionPrecision::Exact) + .unwrap() ); Ok(()) } diff --git a/substrate/frame/revive/src/evm/gas_encoder.rs b/substrate/frame/revive/src/evm/gas_encoder.rs index ffdf8b13c0439..8853e77e958eb 100644 --- a/substrate/frame/revive/src/evm/gas_encoder.rs +++ b/substrate/frame/revive/src/evm/gas_encoder.rs @@ -72,6 +72,12 @@ pub trait GasEncoder: private::Sealed { /// Decodes the weight and deposit from the encoded gas value. /// Returns `None` if the gas value is invalid fn decode(gas: U256) -> Option<(Weight, Balance)>; + + /// Returns the encoded values of the specified weight and deposit. + fn as_encoded_values(weight: Weight, deposit: Balance) -> (Weight, Balance) { + let encoded = Self::encode(U256::zero(), weight, deposit); + Self::decode(encoded).expect("encoded values should be decodable; qed") + } } impl GasEncoder for () @@ -148,6 +154,11 @@ mod test { assert!(decoded_deposit >= deposit); assert!(deposit * 2 >= decoded_deposit); + + assert_eq!( + (decoded_weight, decoded_deposit), + <() as GasEncoder>::as_encoded_values(weight, deposit) + ); } #[test] diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index 0e5fc3da545b5..09bfbf380c61c 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -20,7 +20,8 @@ use crate::{ api::{GenericTransaction, TransactionSigned}, GasEncoder, }, - AccountIdOf, AddressMapper, BalanceOf, Config, MomentOf, Weight, LOG_TARGET, + AccountIdOf, AddressMapper, BalanceOf, Config, ConversionPrecision, MomentOf, Pallet, + LOG_TARGET, }; use alloc::vec::Vec; use codec::{Decode, Encode}; @@ -34,8 +35,8 @@ use sp_core::{Get, H256, U256}; use sp_runtime::{ generic::{self, CheckedExtrinsic, ExtrinsicFormat}, traits::{ - self, AtLeast32BitUnsigned, Checkable, Dispatchable, ExtrinsicLike, ExtrinsicMetadata, - IdentifyAccount, Member, TransactionExtension, + self, Checkable, Dispatchable, ExtrinsicLike, ExtrinsicMetadata, IdentifyAccount, Member, + TransactionExtension, }, transaction_validity::{InvalidTransaction, TransactionValidityError}, OpaqueExtrinsic, RuntimeDebug, Saturating, @@ -56,34 +57,6 @@ type CallOf = ::RuntimeCall; /// - Not too low, enabling users to adjust the gas price to define a tip. pub const GAS_PRICE: u32 = 1_000u32; -/// Convert a `Balance` into a gas value, using the fixed `GAS_PRICE`. -/// The gas is calculated as `balance / GAS_PRICE`, rounded up to the nearest integer. -pub fn gas_from_fee(fee: Balance) -> U256 -where - u32: Into, - Balance: Into + AtLeast32BitUnsigned + Copy, -{ - let gas_price = GAS_PRICE.into(); - let remainder = fee % gas_price; - if remainder.is_zero() { - (fee / gas_price).into() - } else { - (fee.saturating_add(gas_price) / gas_price).into() - } -} - -/// Convert a `Weight` into a gas value, using the fixed `GAS_PRICE`. -/// and the `Config::WeightPrice` to compute the fee. -/// The gas is calculated as `fee / GAS_PRICE`, rounded up to the nearest integer. -pub fn gas_from_weight(weight: Weight) -> U256 -where - BalanceOf: Into, -{ - use sp_runtime::traits::Convert; - let fee: BalanceOf = T::WeightPrice::convert(weight); - gas_from_fee(fee) -} - /// Wraps [`generic::UncheckedExtrinsic`] to support checking unsigned /// [`crate::Call::eth_transact`] extrinsic. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] @@ -346,11 +319,14 @@ pub trait EthExtra { return Err(InvalidTransaction::Call); } - let value = crate::Pallet::::convert_evm_to_native(value.unwrap_or_default()) - .map_err(|err| { - log::debug!(target: LOG_TARGET, "Failed to convert value to native: {err:?}"); - InvalidTransaction::Call - })?; + let value = crate::Pallet::::convert_evm_to_native( + value.unwrap_or_default(), + ConversionPrecision::Exact, + ) + .map_err(|err| { + log::debug!(target: LOG_TARGET, "Failed to convert value to native: {err:?}"); + InvalidTransaction::Call + })?; let data = input.unwrap_or_default().0; @@ -393,17 +369,21 @@ pub trait EthExtra { let nonce = nonce.unwrap_or_default().try_into().map_err(|_| InvalidTransaction::Call)?; // Fees calculated with the fixed `GAS_PRICE` - // When we dry-run the transaction, we set the gas to `Fee / GAS_PRICE` + // When we dry-run the transaction, we set the gas to `fee / GAS_PRICE` let eth_fee_no_tip = U256::from(GAS_PRICE) .saturating_mul(gas) .try_into() .map_err(|_| InvalidTransaction::Call)?; - // Fees with the actual gas_price from the transaction. - let eth_fee: BalanceOf = U256::from(gas_price.unwrap_or_default()) - .saturating_mul(gas) - .try_into() - .map_err(|_| InvalidTransaction::Call)?; + // Fees calculated from the gas and gas_price of the transaction. + let eth_fee = Pallet::::convert_evm_to_native( + U256::from(gas_price.unwrap_or_default()).saturating_mul(gas), + ConversionPrecision::RoundUp, + ) + .map_err(|err| { + log::debug!(target: LOG_TARGET, "Failed to compute eth_fee: {err:?}"); + InvalidTransaction::Call + })?; let info = call.get_dispatch_info(); let function: CallOf = call.into(); diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index d2ef6c9c7ba6c..14ab917c0d4f9 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -24,8 +24,8 @@ use crate::{ storage::{self, meter::Diff, WriteOutcome}, tracing::if_tracing, transient_storage::TransientStorage, - BalanceOf, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf, Error, Event, - ImmutableData, ImmutableDataOf, Pallet as Contracts, + BalanceOf, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf, ConversionPrecision, + Error, Event, ImmutableData, ImmutableDataOf, Pallet as Contracts, }; use alloc::vec::Vec; use core::{fmt::Debug, marker::PhantomData, mem}; @@ -1273,7 +1273,7 @@ where to: &T::AccountId, value: U256, ) -> ExecResult { - let value = crate::Pallet::::convert_evm_to_native(value)?; + let value = crate::Pallet::::convert_evm_to_native(value, ConversionPrecision::Exact)?; if value.is_zero() { return Ok(Default::default()); } diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index c36cb3f47caed..7f4565a9f0884 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -41,10 +41,7 @@ pub mod tracing; pub mod weights; use crate::{ - evm::{ - runtime::{gas_from_fee, GAS_PRICE}, - GasEncoder, GenericTransaction, - }, + evm::{runtime::GAS_PRICE, GasEncoder, GenericTransaction}, exec::{AccountIdOf, ExecError, Executable, Ext, Key, Stack as ExecStack}, gas::GasMeter, storage::{meter::Meter as StorageMeter, ContractInfo, DeletionQueueManager}, @@ -1140,16 +1137,20 @@ where if tx.nonce.is_none() { tx.nonce = Some(>::account_nonce(&origin).into()); } + if tx.chain_id.is_none() { + tx.chain_id = Some(T::ChainId::get().into()); + } if tx.gas_price.is_none() { tx.gas_price = Some(GAS_PRICE.into()); } - if tx.chain_id.is_none() { - tx.chain_id = Some(T::ChainId::get().into()); + if tx.gas.is_none() { + tx.gas = Some(Self::evm_block_gas_limit()); } // Convert the value to the native balance type. let evm_value = tx.value.unwrap_or_default(); - let native_value = match Self::convert_evm_to_native(evm_value) { + let native_value = match Self::convert_evm_to_native(evm_value, ConversionPrecision::Exact) + { Ok(v) => v, Err(_) => return Err(EthTransactError::Message("Failed to convert value".into())), }; @@ -1206,12 +1207,16 @@ where data, eth_gas: Default::default(), }; - // Get the dispatch info of the call. + + let (gas_limit, storage_deposit_limit) = T::EthGasEncoder::as_encoded_values( + result.gas_required, + result.storage_deposit, + ); let dispatch_call: ::RuntimeCall = crate::Call::::call { dest, value: native_value, - gas_limit: result.gas_required, - storage_deposit_limit: result.storage_deposit, + gas_limit, + storage_deposit_limit, data: input.clone(), } .into(); @@ -1264,11 +1269,15 @@ where }; // Get the dispatch info of the call. + let (gas_limit, storage_deposit_limit) = T::EthGasEncoder::as_encoded_values( + result.gas_required, + result.storage_deposit, + ); let dispatch_call: ::RuntimeCall = crate::Call::::instantiate_with_code { value: native_value, - gas_limit: result.gas_required, - storage_deposit_limit: result.storage_deposit, + gas_limit, + storage_deposit_limit, code: code.to_vec(), data: data.to_vec(), salt: None, @@ -1278,38 +1287,26 @@ where }, }; - // The transaction fees depend on the extrinsic's length, which in turn is influenced by - // the encoded length of the gas limit specified in the transaction (tx.gas). - // We iteratively compute the fee by adjusting tx.gas until the fee stabilizes. - // with a maximum of 3 iterations to avoid an infinite loop. - for _ in 0..3 { - let Ok(unsigned_tx) = tx.clone().try_into_unsigned() else { - log::debug!(target: LOG_TARGET, "Failed to convert to unsigned"); - return Err(EthTransactError::Message("Invalid transaction".into())); - }; - - let eth_dispatch_call = - crate::Call::::eth_transact { payload: unsigned_tx.dummy_signed_payload() }; - let encoded_len = utx_encoded_size(eth_dispatch_call); - let fee = pallet_transaction_payment::Pallet::::compute_fee( - encoded_len, - &dispatch_info, - 0u32.into(), - ) - .into(); - let eth_gas = gas_from_fee(fee); - let eth_gas = - T::EthGasEncoder::encode(eth_gas, result.gas_required, result.storage_deposit); - - if eth_gas == result.eth_gas { - log::trace!(target: LOG_TARGET, "bare_eth_call: encoded_len: {encoded_len:?} eth_gas: {eth_gas:?}"); - break; - } - result.eth_gas = eth_gas; - tx.gas = Some(eth_gas.into()); - log::debug!(target: LOG_TARGET, "Adjusting Eth gas to: {eth_gas:?}"); - } + let Ok(unsigned_tx) = tx.clone().try_into_unsigned() else { + return Err(EthTransactError::Message("Invalid transaction".into())); + }; + let eth_dispatch_call = + crate::Call::::eth_transact { payload: unsigned_tx.dummy_signed_payload() }; + + let encoded_len = utx_encoded_size(eth_dispatch_call); + let fee = pallet_transaction_payment::Pallet::::compute_fee( + encoded_len, + &dispatch_info, + 0u32.into(), + ) + .into(); + let eth_gas = Self::evm_fee_to_gas(fee); + let eth_gas = + T::EthGasEncoder::encode(eth_gas, result.gas_required, result.storage_deposit); + + log::trace!(target: LOG_TARGET, "bare_eth_call: encoded_len: {encoded_len:?} eth_gas: {eth_gas:?}"); + result.eth_gas = eth_gas; Ok(result) } @@ -1319,6 +1316,29 @@ where Self::convert_native_to_evm(T::Currency::reducible_balance(&account, Preserve, Polite)) } + /// Convert an EVM fee into a gas value, using the fixed `GAS_PRICE`. + /// The gas is calculated as `fee / GAS_PRICE`, rounded up to the nearest integer. + pub fn evm_fee_to_gas(fee: BalanceOf) -> U256 { + let fee = Self::convert_native_to_evm(fee); + let gas_price = GAS_PRICE.into(); + let (quotient, remainder) = fee.div_mod(gas_price); + if remainder.is_zero() { + quotient + } else { + quotient + U256::one() + } + } + + pub fn evm_block_gas_limit() -> U256 { + let max_block_weight = T::BlockWeights::get() + .get(DispatchClass::Normal) + .max_total + .unwrap_or_else(|| T::BlockWeights::get().max_block); + + let fee = T::WeightPrice::convert(max_block_weight); + Self::evm_fee_to_gas(fee) + } + /// A generalized version of [`Self::upload_code`]. /// /// It is identical to [`Self::upload_code`] and only differs in the information it returns. @@ -1379,16 +1399,22 @@ where } /// Convert an EVM balance to a native balance. - fn convert_evm_to_native(value: U256) -> Result, Error> { + fn convert_evm_to_native( + value: U256, + precision: ConversionPrecision, + ) -> Result, Error> { if value.is_zero() { return Ok(Zero::zero()) } - let ratio = T::NativeToEthRatio::get().into(); - let res = value.checked_div(ratio).expect("divisor is non-zero; qed"); - if res.saturating_mul(ratio) == value { - res.try_into().map_err(|_| Error::::BalanceConversionFailed) - } else { - Err(Error::::DecimalPrecisionLoss) + + let (quotient, remainder) = value.div_mod(T::NativeToEthRatio::get().into()); + match (precision, remainder.is_zero()) { + (ConversionPrecision::Exact, false) => Err(Error::::DecimalPrecisionLoss), + (_, true) => quotient.try_into().map_err(|_| Error::::BalanceConversionFailed), + (_, false) => quotient + .saturating_add(U256::one()) + .try_into() + .map_err(|_| Error::::BalanceConversionFailed), } } } @@ -1417,6 +1443,9 @@ sp_api::decl_runtime_apis! { Nonce: Codec, BlockNumber: Codec, { + /// Returns the block gas limit. + fn block_gas_limit() -> U256; + /// Returns the free balance of the given `[H160]` address, using EVM decimals. fn balance(address: H160) -> U256; diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index 9c149c7cc3890..e2900bd027b69 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -108,6 +108,14 @@ pub enum EthTransactError { Message(String), } +/// Precision used for converting between Native and EVM balances. +pub enum ConversionPrecision { + /// Exact conversion without any rounding. + Exact, + /// Conversion that rounds up to the nearest whole number. + RoundUp, +} + /// Result type of a `bare_code_upload` call. pub type CodeUploadResult = Result, DispatchError>; From fb2e414f44d4bfa2d47aba4a868fb3f8932f7930 Mon Sep 17 00:00:00 2001 From: Egor_P Date: Thu, 23 Jan 2025 11:27:39 +0100 Subject: [PATCH 128/169] [Release|CI/CD] Download only linux artefacts for deb package build (#7271) This PR contains a fix for the rc-build release pipeline. The problem was, that for the deb package build were all the artefacts downloaded and merged together, what could lead to the issue, that the polkadot linux binary was overwritten with the macos one. --- .github/workflows/release-reusable-rc-buid.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-reusable-rc-buid.yml b/.github/workflows/release-reusable-rc-buid.yml index 035b547603e1f..b79f7fa617506 100644 --- a/.github/workflows/release-reusable-rc-buid.yml +++ b/.github/workflows/release-reusable-rc-buid.yml @@ -263,9 +263,24 @@ jobs: ref: ${{ inputs.release_tag }} fetch-depth: 0 - - name: Download artifacts + - name: Download polkadot_x86_64-unknown-linux-gnu artifacts uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: + name: polkadot_x86_64-unknown-linux-gnu + path: target/production + merge-multiple: true + + - name: Download polkadot-execute-worker_x86_64-unknown-linux-gnu artifacts + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: polkadot-execute-worker_x86_64-unknown-linux-gnu + path: target/production + merge-multiple: true + + - name: Download polkadot-prepare-worker_x86_64-unknown-linux-gnu artifacts + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: polkadot-prepare-worker_x86_64-unknown-linux-gnu path: target/production merge-multiple: true From fa9ef69629d016c1b3ce3f5d285435429ad74b49 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 23 Jan 2025 10:37:01 +0000 Subject: [PATCH 129/169] add a few comments for niklas --- Cargo.lock | 4538 +++++++++-------- substrate/bin/node/runtime/src/lib.rs | 5 +- .../election-provider-multi-phase/src/lib.rs | 1 + 3 files changed, 2440 insertions(+), 2104 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 57a66151ac06e..a3ed271668588 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,7 +27,7 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ - "gimli 0.28.0", + "gimli 0.28.1", ] [[package]] @@ -36,6 +36,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "adler32" version = "1.2.0" @@ -54,9 +60,9 @@ dependencies = [ [[package]] name = "aes" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher 0.4.4", @@ -74,7 +80,7 @@ dependencies = [ "cipher 0.4.4", "ctr", "ghash", - "subtle 2.5.0", + "subtle 2.6.1", ] [[package]] @@ -112,18 +118,18 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.4" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "allocator-api2" -version = "0.2.16" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-core" @@ -152,7 +158,7 @@ dependencies = [ "itoa", "serde", "serde_json", - "winnow 0.6.18", + "winnow 0.6.20", ] [[package]] @@ -177,7 +183,7 @@ dependencies = [ "bytes", "cfg-if", "const-hex", - "derive_more 0.99.17", + "derive_more 0.99.18", "hex-literal", "itoa", "proptest", @@ -209,7 +215,7 @@ dependencies = [ "proptest", "rand", "ruint", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "serde", "sha3 0.10.8", "tiny-keccak", @@ -217,13 +223,12 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.3" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc0fac0fc16baf1f63f78b47c3d24718f3619b0714076f6a02957d808d52cbef" +checksum = "da0822426598f95e45dd1ea32a738dac057529a709ee645fcc516ffa4cbde08f" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "bytes", - "smol_str", ] [[package]] @@ -236,68 +241,68 @@ dependencies = [ "dunce", "heck 0.4.1", "proc-macro-error", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", "syn-solidity 0.4.2", "tiny-keccak", ] [[package]] name = "alloy-sol-macro" -version = "0.8.15" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9d64f851d95619233f74b310f12bcf16e0cbc27ee3762b6115c14a84809280a" +checksum = "8d039d267aa5cbb7732fa6ce1fd9b5e9e29368f580f80ba9d7a8450c794de4b2" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", "proc-macro-error2", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "alloy-sol-macro-expander" -version = "0.8.15" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf7ed1574b699f48bf17caab4e6e54c6d12bc3c006ab33d58b1e227c1c3559f" +checksum = "620ae5eee30ee7216a38027dec34e0585c55099f827f92f50d11e3d2d3a4a954" dependencies = [ "alloy-sol-macro-input", "const-hex", "heck 0.5.0", "indexmap 2.7.0", "proc-macro-error2", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", - "syn-solidity 0.8.15", + "syn 2.0.90", + "syn-solidity 0.8.19", "tiny-keccak", ] [[package]] name = "alloy-sol-macro-input" -version = "0.8.15" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c02997ccef5f34f9c099277d4145f183b422938ed5322dc57a089fe9b9ad9ee" +checksum = "ad9f7d057e00f8c5994e4ff4492b76532c51ead39353aa2ed63f8c50c0f4d52e" dependencies = [ "const-hex", "dunce", "heck 0.5.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", - "syn-solidity 0.8.15", + "syn 2.0.90", + "syn-solidity 0.8.19", ] [[package]] name = "alloy-sol-type-parser" -version = "0.8.15" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce13ff37285b0870d0a0746992a4ae48efaf34b766ae4c2640fa15e5305f8e73" +checksum = "74e60b084fe1aef8acecda2743ff2d93c18ff3eb67a2d3b12f62582a1e66ef5e" dependencies = [ "serde", - "winnow 0.6.18", + "winnow 0.6.20", ] [[package]] @@ -320,7 +325,7 @@ checksum = "1174cafd6c6d810711b4e00383037bdb458efc4fe3dbafafa16567e0320c54d8" dependencies = [ "alloy-json-abi", "alloy-primitives 0.8.15", - "alloy-sol-macro 0.8.15", + "alloy-sol-macro 0.8.19", "const-hex", "serde", ] @@ -363,57 +368,58 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.11" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.1" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.1" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" [[package]] name = "approx" @@ -433,16 +439,16 @@ dependencies = [ "include_dir", "itertools 0.10.5", "proc-macro-error", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "arbitrary" -version = "1.3.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" dependencies = [ "derive_arbitrary", ] @@ -453,7 +459,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb00293ba84f51ce3bd026bd0de55899c4e68f0a39a5728cebae3a73ffdc0a4f" dependencies = [ - "ark-ec", + "ark-ec 0.4.2", "ark-ff 0.4.2", "ark-std 0.4.0", ] @@ -465,7 +471,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20c7021f180a0cbea0380eba97c2af3c57074cdaffe0eef7e840e1c9f2841e55" dependencies = [ "ark-bls12-377", - "ark-ec", + "ark-ec 0.4.2", "ark-models-ext", "ark-std 0.4.0", ] @@ -476,7 +482,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" dependencies = [ - "ark-ec", + "ark-ec 0.4.2", "ark-ff 0.4.2", "ark-serialize 0.4.2", "ark-std 0.4.0", @@ -489,7 +495,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1dc4b3d08f19e8ec06e949712f95b8361e43f1391d94f65e4234df03480631c" dependencies = [ "ark-bls12-381", - "ark-ec", + "ark-ec 0.4.2", "ark-ff 0.4.2", "ark-models-ext", "ark-serialize 0.4.2", @@ -503,7 +509,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e0605daf0cc5aa2034b78d008aaf159f56901d92a52ee4f6ecdfdac4f426700" dependencies = [ "ark-bls12-377", - "ark-ec", + "ark-ec 0.4.2", "ark-ff 0.4.2", "ark-std 0.4.0", ] @@ -515,7 +521,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccee5fba47266f460067588ee1bf070a9c760bf2050c1c509982c5719aadb4f2" dependencies = [ "ark-bw6-761", - "ark-ec", + "ark-ec 0.4.2", "ark-ff 0.4.2", "ark-models-ext", "ark-std 0.4.0", @@ -528,7 +534,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" dependencies = [ "ark-ff 0.4.2", - "ark-poly", + "ark-poly 0.4.2", "ark-serialize 0.4.2", "ark-std 0.4.0", "derivative", @@ -539,6 +545,27 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" +dependencies = [ + "ahash 0.8.11", + "ark-ff 0.5.0", + "ark-poly 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.2", + "itertools 0.13.0", + "num-bigint", + "num-integer", + "num-traits", + "zeroize", +] + [[package]] name = "ark-ed-on-bls12-377" version = "0.4.0" @@ -546,7 +573,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b10d901b9ac4b38f9c32beacedfadcdd64e46f8d7f8e88c1ae1060022cf6f6c6" dependencies = [ "ark-bls12-377", - "ark-ec", + "ark-ec 0.4.2", "ark-ff 0.4.2", "ark-std 0.4.0", ] @@ -557,7 +584,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524a4fb7540df2e1a8c2e67a83ba1d1e6c3947f4f9342cc2359fc2e789ad731d" dependencies = [ - "ark-ec", + "ark-ec 0.4.2", "ark-ed-on-bls12-377", "ark-ff 0.4.2", "ark-models-ext", @@ -571,7 +598,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9cde0f2aa063a2a5c28d39b47761aa102bda7c13c84fc118a61b87c7b2f785c" dependencies = [ "ark-bls12-381", - "ark-ec", + "ark-ec 0.4.2", "ark-ff 0.4.2", "ark-std 0.4.0", ] @@ -582,7 +609,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d15185f1acb49a07ff8cbe5f11a1adc5a93b19e211e325d826ae98e98e124346" dependencies = [ - "ark-ec", + "ark-ec 0.4.2", "ark-ed-on-bls12-381-bandersnatch", "ark-ff 0.4.2", "ark-models-ext", @@ -623,7 +650,27 @@ dependencies = [ "num-bigint", "num-traits", "paste", - "rustc_version 0.4.0", + "rustc_version 0.4.1", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec 0.7.6", + "digest 0.10.7", + "educe", + "itertools 0.13.0", + "num-bigint", + "num-traits", + "paste", "zeroize", ] @@ -647,6 +694,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote 1.0.37", + "syn 2.0.90", +] + [[package]] name = "ark-ff-macros" version = "0.3.0" @@ -667,18 +724,31 @@ checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" dependencies = [ "num-bigint", "num-traits", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "syn 1.0.109", ] +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", +] + [[package]] name = "ark-models-ext" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e9eab5d4b5ff2f228b763d38442adc9b084b0a465409b059fac5c2308835ec2" dependencies = [ - "ark-ec", + "ark-ec 0.4.2", "ark-ff 0.4.2", "ark-serialize 0.4.2", "ark-std 0.4.0", @@ -699,17 +769,18 @@ dependencies = [ ] [[package]] -name = "ark-scale" -version = "0.0.11" +name = "ark-poly" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51bd73bb6ddb72630987d37fa963e99196896c0d0ea81b7c894567e74a2f83af" +checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" dependencies = [ - "ark-ec", - "ark-ff 0.4.2", - "ark-serialize 0.4.2", - "ark-std 0.4.0", - "parity-scale-codec", - "scale-info", + "ahash 0.8.11", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.2", ] [[package]] @@ -718,7 +789,7 @@ version = "0.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f69c00b3b529be29528a6f2fd5fa7b1790f8bed81b9cdca17e326538545a179" dependencies = [ - "ark-ec", + "ark-ec 0.4.2", "ark-ff 0.4.2", "ark-serialize 0.4.2", "ark-std 0.4.0", @@ -731,7 +802,7 @@ name = "ark-secret-scalar" version = "0.0.2" source = "git+https://github.com/w3f/ring-vrf?rev=0fef826#0fef8266d851932ad25d6b41bc4b34d834d1e11d" dependencies = [ - "ark-ec", + "ark-ec 0.4.2", "ark-ff 0.4.2", "ark-serialize 0.4.2", "ark-std 0.4.0", @@ -757,23 +828,47 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ - "ark-serialize-derive", + "ark-serialize-derive 0.4.2", "ark-std 0.4.0", "digest 0.10.7", "num-bigint", ] +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-serialize-derive 0.5.0", + "ark-std 0.5.0", + "arrayvec 0.7.6", + "digest 0.10.7", + "num-bigint", +] + [[package]] name = "ark-serialize-derive" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "syn 1.0.109", ] +[[package]] +name = "ark-serialize-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" +dependencies = [ + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", +] + [[package]] name = "ark-std" version = "0.3.0" @@ -795,6 +890,16 @@ dependencies = [ "rayon", ] +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand", +] + [[package]] name = "ark-transcript" version = "0.0.2" @@ -810,15 +915,15 @@ dependencies = [ [[package]] name = "array-bytes" -version = "6.2.2" +version = "6.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f840fb7195bcfc5e17ea40c26e5ce6d5b9ce5d584466e17703209657e459ae0" +checksum = "5d5dde061bd34119e902bbb2d9b90c5692635cf59fb91d582c2b68043f1b8293" [[package]] name = "arrayref" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" @@ -837,15 +942,15 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "asn1-rs" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ad1373757efa0f70ec53939aabc7152e1591cb485208052993070ac8d2429d" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" dependencies = [ "asn1-rs-derive", "asn1-rs-impl", @@ -853,19 +958,19 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror", + "thiserror 1.0.69", "time", ] [[package]] name = "asn1-rs-derive" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7378575ff571966e99a744addeff0bff98b8ada0dedf1956d59e634db95eaac1" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", "synstructure 0.13.1", ] @@ -875,21 +980,22 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "assert_cmd" -version = "2.0.14" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed72493ac66d5804837f480ab3766c72bdfab91a65e565fc54fa9e42db0073a8" +checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d" dependencies = [ "anstyle", "bstr", "doc-comment", - "predicates 3.0.3", + "libc", + "predicates 3.1.2", "predicates-core", "predicates-tree", "wait-timeout", @@ -1222,7 +1328,7 @@ dependencies = [ "pallet-collator-selection 19.0.0", "pallet-session 38.0.0", "pallet-timestamp 37.0.0", - "pallet-xcm 17.0.0", + "pallet-xcm 17.0.1", "pallet-xcm-bridge-hub-router 0.15.1", "parachains-common 18.0.0", "parachains-runtimes-test-utils 17.0.0", @@ -1260,9 +1366,9 @@ dependencies = [ [[package]] name = "assets-common" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4556e56f9206b129c3f96249cd907b76e8d7ad5265fe368c228c708789a451a3" +checksum = "93438e31a4449fbeab87210931edc8cd156292354f1fc15f17d819ecded6bf25" dependencies = [ "cumulus-primitives-core 0.16.0", "frame-support 38.0.0", @@ -1270,7 +1376,7 @@ dependencies = [ "log", "pallet-asset-conversion 20.0.0", "pallet-assets 40.0.0", - "pallet-xcm 17.0.0", + "pallet-xcm 17.0.1", "parachains-common 18.0.0", "parity-scale-codec", "scale-info", @@ -1305,12 +1411,11 @@ dependencies = [ [[package]] name = "async-channel" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f2776ead772134d55b62dd45e59a79e21612d85d0af729b8b7d3967d601a62a" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" dependencies = [ "concurrent-queue", - "event-listener 5.3.1", "event-listener-strategy", "futures-core", "pin-project-lite", @@ -1318,15 +1423,14 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.5.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" dependencies = [ - "async-lock 2.8.0", "async-task", "concurrent-queue", - "fastrand 1.9.0", - "futures-lite 1.13.0", + "fastrand 2.2.0", + "futures-lite 2.5.0", "slab", ] @@ -1350,21 +1454,21 @@ checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" dependencies = [ "async-lock 3.4.0", "blocking", - "futures-lite 2.3.0", + "futures-lite 2.5.0", ] [[package]] name = "async-global-executor" -version = "2.3.1" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ - "async-channel 1.9.0", + "async-channel 2.3.1", "async-executor", - "async-io 1.13.0", - "async-lock 2.8.0", + "async-io 2.4.0", + "async-lock 3.4.0", "blocking", - "futures-lite 1.13.0", + "futures-lite 2.5.0", "once_cell", ] @@ -1382,29 +1486,29 @@ dependencies = [ "log", "parking", "polling 2.8.0", - "rustix 0.37.23", + "rustix 0.37.27", "slab", - "socket2 0.4.9", + "socket2 0.4.10", "waker-fn", ] [[package]] name = "async-io" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964" +checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" dependencies = [ "async-lock 3.4.0", "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "parking", - "polling 3.4.0", - "rustix 0.38.42", + "polling 3.7.4", + "rustix 0.38.41", "slab", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1429,12 +1533,11 @@ dependencies = [ [[package]] name = "async-net" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4051e67316bc7eff608fe723df5d32ed639946adcd69e07df41fd42a7b411f1f" +checksum = "0434b1ed18ce1cf5769b8ac540e33f01fa9471058b5e89da9e06f3c882a8c12f" dependencies = [ "async-io 1.13.0", - "autocfg", "blocking", "futures-lite 1.13.0", ] @@ -1445,26 +1548,25 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" dependencies = [ - "async-io 2.3.3", + "async-io 2.4.0", "blocking", - "futures-lite 2.3.0", + "futures-lite 2.5.0", ] [[package]] name = "async-process" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" +checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" dependencies = [ "async-io 1.13.0", "async-lock 2.8.0", - "autocfg", + "async-signal", "blocking", "cfg-if", - "event-listener 2.5.3", + "event-listener 3.1.0", "futures-lite 1.13.0", - "rustix 0.37.23", - "signal-hook", + "rustix 0.38.41", "windows-sys 0.48.0", ] @@ -1474,54 +1576,54 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" dependencies = [ - "async-channel 2.3.0", - "async-io 2.3.3", + "async-channel 2.3.1", + "async-io 2.4.0", "async-lock 3.4.0", "async-signal", "async-task", "blocking", "cfg-if", "event-listener 5.3.1", - "futures-lite 2.3.0", - "rustix 0.38.42", + "futures-lite 2.5.0", + "rustix 0.38.41", "tracing", ] [[package]] name = "async-signal" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb3634b73397aa844481f814fad23bbf07fdb0eabec10f2eb95e58944b1ec32" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" dependencies = [ - "async-io 2.3.3", + "async-io 2.4.0", "async-lock 3.4.0", "atomic-waker", "cfg-if", "futures-core", "futures-io", - "rustix 0.38.42", + "rustix 0.38.41", "signal-hook-registry", "slab", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "async-std" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615" dependencies = [ "async-attributes", "async-channel 1.9.0", "async-global-executor", - "async-io 1.13.0", - "async-lock 2.8.0", + "async-io 2.4.0", + "async-lock 3.4.0", "crossbeam-utils", "futures-channel", "futures-core", "futures-io", - "futures-lite 1.13.0", - "gloo-timers", + "futures-lite 2.5.0", + "gloo-timers 0.3.0", "kv-log-macro", "log", "memchr", @@ -1534,9 +1636,9 @@ dependencies = [ [[package]] name = "async-stream" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" dependencies = [ "async-stream-impl", "futures-core", @@ -1545,13 +1647,13 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1566,9 +1668,9 @@ version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1614,9 +1716,9 @@ checksum = "a8ab6b55fe97976e46f91ddbed8d147d966475dc29b2032757ba47e02376fbc3" [[package]] name = "atomic-waker" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "attohttpc" @@ -1624,7 +1726,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d9a9bf8b79a749ee0b911b91b671cc2b6c670bdbc7e3dfd537576ddc94bb2a2" dependencies = [ - "http 0.2.9", + "http 0.2.12", "log", "url", ] @@ -1642,21 +1744,20 @@ dependencies = [ [[package]] name = "auto_impl" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fee3da8ef1276b0bee5dd1c7258010d8fffd31801447323115a25560e1327b89" +checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ - "proc-macro-error", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 1.0.109", + "syn 2.0.90", ] [[package]] name = "autocfg" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backoff" @@ -1679,7 +1780,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.7.4", "object 0.32.2", "rustc-demangle", ] @@ -1690,7 +1791,7 @@ version = "0.0.4" source = "git+https://github.com/w3f/ring-vrf?rev=0fef826#0fef8266d851932ad25d6b41bc4b34d834d1e11d" dependencies = [ "ark-bls12-381", - "ark-ec", + "ark-ec 0.4.2", "ark-ed-on-bls12-381-bandersnatch", "ark-ff 0.4.2", "ark-serialize 0.4.2", @@ -1747,15 +1848,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" -[[package]] -name = "basic-toml" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2db21524cad41c5591204d22d75e1970a2d1f71060214ca931dc7d5afe2c14e5" -dependencies = [ - "serde", -] - [[package]] name = "binary-merkle-tree" version = "13.0.0" @@ -1801,12 +1893,12 @@ dependencies = [ "lazycell", "peeking_take_while", "prettyplease", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1821,7 +1913,7 @@ dependencies = [ "rand_core 0.6.4", "ripemd", "sha2 0.10.8", - "subtle 2.5.0", + "subtle 2.6.1", "zeroize", ] @@ -1870,7 +1962,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" dependencies = [ "bitcoin-internals", - "hex-conservative 0.1.1", + "hex-conservative 0.1.2", ] [[package]] @@ -1960,8 +2052,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" dependencies = [ "arrayref", - "arrayvec 0.7.4", - "constant_time_eq 0.3.0", + "arrayvec 0.7.6", + "constant_time_eq 0.3.1", ] [[package]] @@ -1977,26 +2069,26 @@ dependencies = [ [[package]] name = "blake2s_simd" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6637f448b9e61dfadbdcbae9a885fadee1f3eaffb1f8d3c1965d3ade8bdfd44f" +checksum = "94230421e395b9920d23df13ea5d77a20e1725331f90fbbf6df6040b33f756ae" dependencies = [ "arrayref", - "arrayvec 0.7.4", - "constant_time_eq 0.2.6", + "arrayvec 0.7.6", + "constant_time_eq 0.3.1", ] [[package]] name = "blake3" -version = "1.5.4" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" +checksum = "b8ee0c1824c4dea5b5f81736aff91bae041d2c07ee1192bec91054e10e3e601e" dependencies = [ "arrayref", - "arrayvec 0.7.4", + "arrayvec 0.7.6", "cc", "cfg-if", - "constant_time_eq 0.3.0", + "constant_time_eq 0.3.1", ] [[package]] @@ -2026,17 +2118,15 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "blocking" -version = "1.3.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" dependencies = [ - "async-channel 1.9.0", - "async-lock 2.8.0", + "async-channel 2.3.1", "async-task", - "atomic-waker", - "fastrand 1.9.0", - "futures-lite 1.13.0", - "log", + "futures-io", + "futures-lite 2.5.0", + "piper", ] [[package]] @@ -2058,7 +2148,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68534a48cbf63a4b1323c433cf21238c9ec23711e0df13b08c33e5c2082663ce" dependencies = [ - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2599,7 +2689,7 @@ checksum = "c31b53c53d627e2da38f8910807944bf3121e154b5c0ac9e122995af9dfb13ed" dependencies = [ "cumulus-primitives-core 0.16.0", "frame-support 38.0.0", - "pallet-message-queue 41.0.1", + "pallet-message-queue 41.0.2", "parity-scale-codec", "scale-info", "snowbridge-core 0.10.0", @@ -2832,7 +2922,7 @@ dependencies = [ "pallet-bridge-relayers 0.18.0", "pallet-timestamp 37.0.0", "pallet-utility 38.0.0", - "pallet-xcm 17.0.0", + "pallet-xcm 17.0.1", "pallet-xcm-bridge-hub 0.13.0", "parachains-common 18.0.0", "parachains-runtimes-test-utils 17.0.0", @@ -3079,12 +3169,12 @@ dependencies = [ [[package]] name = "bstr" -version = "1.6.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" +checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" dependencies = [ "memchr", - "regex-automata 0.3.6", + "regex-automata 0.4.9", "serde", ] @@ -3099,9 +3189,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.13.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byte-slice-cast" @@ -3117,9 +3207,9 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" [[package]] name = "bytemuck" -version = "1.13.1" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" +checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" [[package]] name = "byteorder" @@ -3129,9 +3219,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" dependencies = [ "serde", ] @@ -3178,18 +3268,18 @@ dependencies = [ [[package]] name = "camino" -version = "1.1.6" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" dependencies = [ "serde", ] [[package]] name = "cargo-platform" -version = "0.1.3" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cfa25e60aea747ec7e1124f238816749faa93759c6ff5b31f1ccdda137f4479" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" dependencies = [ "serde", ] @@ -3202,10 +3292,10 @@ checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" dependencies = [ "camino", "cargo-platform", - "semver 1.0.18", + "semver 1.0.23", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -3222,9 +3312,9 @@ checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" [[package]] name = "cc" -version = "1.1.24" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" +checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" dependencies = [ "jobserver", "libc", @@ -3248,9 +3338,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.15.5" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03915af431787e6ffdcc74c645077518c6b6e01f80b761e0fbbfa288536311b3" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" dependencies = [ "smallvec", ] @@ -3336,9 +3426,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", @@ -3346,14 +3436,14 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] name = "ciborium" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", @@ -3362,15 +3452,15 @@ dependencies = [ [[package]] name = "ciborium-io" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", @@ -3444,9 +3534,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.6.1" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", @@ -3463,108 +3553,69 @@ dependencies = [ "atty", "bitflags 1.3.2", "strsim 0.8.0", - "textwrap 0.11.0", - "unicode-width", + "textwrap", + "unicode-width 0.1.14", "vec_map", ] [[package]] name = "clap" -version = "3.2.25" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" -dependencies = [ - "atty", - "bitflags 1.3.2", - "clap_derive 3.2.25", - "clap_lex 0.2.4", - "indexmap 1.9.3", - "once_cell", - "strsim 0.10.0", - "termcolor", - "textwrap 0.16.0", -] - -[[package]] -name = "clap" -version = "4.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" dependencies = [ "clap_builder", - "clap_derive 4.5.13", + "clap_derive", ] [[package]] name = "clap-num" -version = "1.0.2" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488557e97528174edaa2ee268b23a809e0c598213a4bbcb4f34575a46fda147e" +checksum = "0e063d263364859dc54fb064cedb7c122740cd4733644b14b176c097f51e8ab7" dependencies = [ "num-traits", ] [[package]] name = "clap_builder" -version = "4.5.13" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" dependencies = [ "anstream", "anstyle", - "clap_lex 0.7.0", + "clap_lex", "strsim 0.11.1", "terminal_size", ] [[package]] name = "clap_complete" -version = "4.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa3c596da3cf0983427b0df0dba359df9182c13bd5b519b585a482b0c351f4e8" -dependencies = [ - "clap 4.5.13", -] - -[[package]] -name = "clap_derive" -version = "3.2.25" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" +checksum = "d9647a559c112175f17cf724dc72d3645680a883c58481332779192b0d8e7a01" dependencies = [ - "heck 0.4.1", - "proc-macro-error", - "proc-macro2 1.0.86", - "quote 1.0.37", - "syn 1.0.109", + "clap 4.5.21", ] [[package]] name = "clap_derive" -version = "4.5.13" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck 0.5.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", -] - -[[package]] -name = "clap_lex" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", + "syn 2.0.90", ] [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "cmd_lib" @@ -3573,7 +3624,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "371c15a3c178d0117091bd84414545309ca979555b1aad573ef591ad58818d41" dependencies = [ "cmd_lib_macros", - "env_logger 0.10.1", + "env_logger 0.10.2", "faccess", "lazy_static", "log", @@ -3587,20 +3638,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb844bd05be34d91eb67101329aeba9d3337094c04fd8507d821db7ebb488eaf" dependencies = [ "proc-macro-error2", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "coarsetime" -version = "0.1.23" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90d114103adbc625300f346d4d09dfb4ab1c4a8df6868435dd903392ecf4354" +checksum = "13b3839cf01bb7960114be3ccf2340f541b6d0c81f8690b007b2b39f750f7e5d" dependencies = [ "libc", - "once_cell", - "wasi", + "wasix", "wasm-bindgen", ] @@ -3611,7 +3661,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" dependencies = [ "termcolor", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] @@ -3750,47 +3800,46 @@ dependencies = [ [[package]] name = "color-print" -version = "0.3.4" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2a5e6504ed8648554968650feecea00557a3476bc040d0ffc33080e66b646d0" +checksum = "3aa954171903797d5623e047d9ab69d91b493657917bdfb8c2c80ecaf9cdb6f4" dependencies = [ "color-print-proc-macro", ] [[package]] name = "color-print-proc-macro" -version = "0.3.4" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51beaa537d73d2d1ff34ee70bc095f170420ab2ec5d687ecd3ec2b0d092514b" +checksum = "692186b5ebe54007e45a59aea47ece9eb4108e141326c304cdc91699a7118a22" dependencies = [ "nom", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 1.0.109", + "syn 2.0.90", ] [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "colored" -version = "2.0.4" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" dependencies = [ - "is-terminal", "lazy_static", "windows-sys 0.48.0", ] [[package]] name = "combine" -version = "4.6.6" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ "bytes", "memchr", @@ -3798,13 +3847,13 @@ dependencies = [ [[package]] name = "comfy-table" -version = "7.1.0" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c64043d6c7b7a4c58e39e7efccfdea7b93d885a795d0c054a69dbbf4dd52686" +checksum = "24f165e7b643266ea80cb858aed492ad9280e3e05ce24d4a99d7d7b889b6a4d9" dependencies = [ - "strum 0.25.0", - "strum_macros 0.25.3", - "unicode-width", + "strum 0.26.3", + "strum_macros 0.26.4", + "unicode-width 0.2.0", ] [[package]] @@ -3812,9 +3861,9 @@ name = "common" version = "0.1.0" source = "git+https://github.com/w3f/ring-proof?rev=665f5f5#665f5f51af5734c7b6d90b985dd6861d4c5b4752" dependencies = [ - "ark-ec", + "ark-ec 0.4.2", "ark-ff 0.4.2", - "ark-poly", + "ark-poly 0.4.2", "ark-serialize 0.4.2", "ark-std 0.4.0", "fflonk", @@ -3848,7 +3897,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a54b9c40054eb8999c5d1d36fdc90e4e5f7ff0d1d9621706f360b3cbc8beb828" dependencies = [ "convert_case 0.4.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "syn 1.0.109", ] @@ -3860,7 +3909,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5437e327e861081c91270becff184859f706e3e50f5301a9d4dc8eb50752c3" dependencies = [ "convert_case 0.6.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "syn 1.0.109", ] @@ -3883,20 +3932,10 @@ dependencies = [ "encode_unicode", "lazy_static", "libc", - "unicode-width", + "unicode-width 0.1.14", "windows-sys 0.52.0", ] -[[package]] -name = "console_error_panic_hook" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" -dependencies = [ - "cfg-if", - "wasm-bindgen", -] - [[package]] name = "const-hex" version = "1.14.0" @@ -3912,29 +3951,27 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const-random" -version = "0.1.15" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368a7a772ead6ce7e1de82bfb04c485f3db8ec744f72925af5735e29a22cc18e" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" dependencies = [ "const-random-macro", - "proc-macro-hack", ] [[package]] name = "const-random-macro" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d7d6ab3c3a2282db210df5f02c4dab6e0a7057af0fb7ebd4070f30fe05c0ddb" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ "getrandom", "once_cell", - "proc-macro-hack", "tiny-keccak", ] @@ -3946,21 +3983,15 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "constant_time_eq" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a53c0a4d288377e7415b53dcfc3c04da5cdc2cc95c8d5ac178b58f0b861ad6" - -[[package]] -name = "constant_time_eq" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "constcat" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f272d0c4cf831b4fa80ee529c7707f76585986e910e1fbce1d7921970bc1a241" +checksum = "cd7e35aee659887cbfb97aaf227ac12cad1a9d7c71e55ff3376839ed4e282d08" [[package]] name = "contracts-rococo-runtime" @@ -4057,11 +4088,21 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core2" @@ -4284,9 +4325,9 @@ dependencies = [ [[package]] name = "cpp_demangle" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8227005286ec39567949b33df9896bcadfa6051bccca2488129f108ca23119" +checksum = "96e58d342ad113c2b878f16d5d034c03be492ae460cdbc02b7f0f2284d310c7d" dependencies = [ "cfg-if", ] @@ -4303,9 +4344,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -4404,7 +4445,7 @@ dependencies = [ "itertools 0.10.5", "log", "smallvec", - "wasmparser", + "wasmparser 0.102.0", "wasmtime-types", ] @@ -4425,9 +4466,9 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] @@ -4441,7 +4482,7 @@ dependencies = [ "anes", "cast", "ciborium", - "clap 4.5.13", + "clap 4.5.21", "criterion-plot", "futures", "is-terminal", @@ -4472,26 +4513,21 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.15" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset 0.9.0", - "scopeguard", ] [[package]] @@ -4517,13 +4553,13 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.5.2" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array 0.14.7", "rand_core 0.6.4", - "subtle 2.5.0", + "subtle 2.6.1", "zeroize", ] @@ -4555,7 +4591,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ "generic-array 0.14.7", - "subtle 2.5.0", + "subtle 2.6.1", ] [[package]] @@ -4569,7 +4605,7 @@ dependencies = [ "generic-array 0.14.7", "poly1305", "salsa20", - "subtle 2.5.0", + "subtle 2.6.1", "zeroize", ] @@ -4586,7 +4622,7 @@ dependencies = [ name = "cumulus-client-cli" version = "0.7.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.21", "parity-scale-codec", "sc-chain-spec", "sc-cli", @@ -4718,7 +4754,7 @@ dependencies = [ "sp-inherents 26.0.0", "sp-runtime 31.0.1", "sp-state-machine 0.35.0", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -5013,7 +5049,7 @@ dependencies = [ "frame-system 38.0.0", "impl-trait-for-tuples", "log", - "pallet-message-queue 41.0.1", + "pallet-message-queue 41.0.2", "parity-scale-codec", "polkadot-parachain-primitives 14.0.0", "polkadot-runtime-common 17.0.0", @@ -5037,10 +5073,10 @@ dependencies = [ name = "cumulus-pallet-parachain-system-proc-macro" version = "0.6.0" dependencies = [ - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -5049,10 +5085,10 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "befbaf3a1ce23ac8476481484fef5f4d500cbd15b4dad6380ce1d28134b0c1f7" dependencies = [ - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -5199,7 +5235,7 @@ dependencies = [ "frame-support 38.0.0", "frame-system 38.0.0", "log", - "pallet-message-queue 41.0.1", + "pallet-message-queue 41.0.2", "parity-scale-codec", "polkadot-runtime-common 17.0.0", "polkadot-runtime-parachains 17.0.1", @@ -5247,7 +5283,7 @@ name = "cumulus-pov-validator" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.5.13", + "clap 4.5.21", "parity-scale-codec", "polkadot-node-primitives", "polkadot-parachain-primitives 6.0.0", @@ -5257,7 +5293,7 @@ dependencies = [ "sp-io 30.0.0", "sp-maybe-compressed-blob 11.0.0", "tracing", - "tracing-subscriber 0.3.18", + "tracing-subscriber", ] [[package]] @@ -5498,7 +5534,7 @@ dependencies = [ "sp-blockchain", "sp-state-machine 0.35.0", "sp-version 29.0.0", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -5569,7 +5605,7 @@ dependencies = [ "sp-storage 19.0.0", "sp-version 29.0.0", "substrate-prometheus-endpoint", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-util", "tracing", @@ -5687,7 +5723,7 @@ name = "cumulus-test-service" version = "0.1.0" dependencies = [ "async-trait", - "clap 4.5.13", + "clap 4.5.21", "criterion", "cumulus-client-cli", "cumulus-client-collator", @@ -5766,24 +5802,24 @@ dependencies = [ [[package]] name = "curl" -version = "0.4.46" +version = "0.4.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e2161dd6eba090ff1594084e95fd67aeccf04382ffea77999ea94ed42ec67b6" +checksum = "d9fb4d13a1be2b58f14d60adba57c9834b78c62fd86c3e76a148f732686e9265" dependencies = [ "curl-sys", "libc", "openssl-probe", "openssl-sys", "schannel", - "socket2 0.5.7", + "socket2 0.5.8", "windows-sys 0.52.0", ] [[package]] name = "curl-sys" -version = "0.4.72+curl-8.6.0" +version = "0.4.78+curl-8.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29cbdc8314c447d11e8fd156dcdd031d9e02a7a976163e396b548c03153bc9ea" +checksum = "8eec768341c5c7789611ae51cf6c459099f22e64a5d5d0ce4892434e33821eaf" dependencies = [ "cc", "libc", @@ -5804,7 +5840,7 @@ dependencies = [ "byteorder", "digest 0.9.0", "rand_core 0.5.1", - "subtle 2.5.0", + "subtle 2.6.1", "zeroize", ] @@ -5819,20 +5855,20 @@ dependencies = [ "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", - "rustc_version 0.4.0", - "subtle 2.5.0", + "rustc_version 0.4.1", + "subtle 2.6.1", "zeroize", ] [[package]] name = "curve25519-dalek-derive" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -5850,46 +5886,61 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.106" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28403c86fc49e3401fdf45499ba37fad6493d9329449d6449d7f0e10f4654d28" +checksum = "05e1ec88093d2abd9cf1b09ffd979136b8e922bf31cad966a8fe0d73233112ef" dependencies = [ "cc", + "cxxbridge-cmd", "cxxbridge-flags", "cxxbridge-macro", + "foldhash", "link-cplusplus", ] [[package]] name = "cxx-build" -version = "1.0.106" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78da94fef01786dc3e0c76eafcd187abcaa9972c78e05ff4041e24fdf059c285" +checksum = "9afa390d956ee7ccb41aeed7ed7856ab3ffb4fc587e7216be7e0f83e949b4e6c" dependencies = [ "cc", "codespan-reporting", - "once_cell", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "scratch", - "syn 2.0.87", + "syn 2.0.90", +] + +[[package]] +name = "cxxbridge-cmd" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c23bfff654d6227cbc83de8e059d2f8678ede5fc3a6c5a35d5c379983cc61e6" +dependencies = [ + "clap 4.5.21", + "codespan-reporting", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] name = "cxxbridge-flags" -version = "1.0.106" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2a6f5e1dfb4b34292ad4ea1facbfdaa1824705b231610087b00b17008641809" +checksum = "f7c01b36e22051bc6928a78583f1621abaaf7621561c2ada1b00f7878fbe2caa" [[package]] name = "cxxbridge-macro" -version = "1.0.106" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c49547d73ba8dcfd4ad7325d64c6d5391ff4224d498fc39a6f3f49825a530d" +checksum = "f6e14013136fac689345d17b9a6df55977251f11d333c0a571e8d963b55e1f95" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "rustversion", + "syn 2.0.90", ] [[package]] @@ -5910,10 +5961,10 @@ checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "strsim 0.11.1", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -5924,20 +5975,20 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "dashmap" -version = "5.5.1" +version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd72493923899c6f10c641bdbdeddc7183d6396641d99c1a0d1597f37f92e28" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", "hashbrown 0.14.5", "lock_api", "once_cell", - "parking_lot_core 0.9.8", + "parking_lot_core 0.9.10", ] [[package]] @@ -5948,9 +5999,9 @@ checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "data-encoding-macro" -version = "0.1.13" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c904b33cc60130e1aeea4956ab803d08a3f4a0ca82d64ed757afac3891f2bb99" +checksum = "f1559b6cba622276d6d63706db152618eeb15b89b3e4041446b05876e352e639" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -5958,9 +6009,9 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.11" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fdf3fce3ce863539ec1d7fd1b6dcc3c645663376b43ed376bbf887733e4f772" +checksum = "332d754c0af53bc87c108fed664d121ecf59207ec4196041f04d6ab9002ad33f" dependencies = [ "data-encoding", "syn 1.0.109", @@ -5977,9 +6028,9 @@ dependencies = [ [[package]] name = "der" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "pem-rfc7468", @@ -6015,7 +6066,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "syn 1.0.109", ] @@ -6026,9 +6077,9 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -6037,33 +6088,33 @@ version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "derive_arbitrary" -version = "1.3.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "derive_more" -version = "0.99.17" +version = "0.99.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ "convert_case 0.4.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "rustc_version 0.4.0", - "syn 1.0.109", + "rustc_version 0.4.1", + "syn 2.0.90", ] [[package]] @@ -6081,10 +6132,10 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", - "unicode-xid 0.2.4", + "syn 2.0.90", + "unicode-xid 0.2.6", ] [[package]] @@ -6126,7 +6177,7 @@ dependencies = [ "block-buffer 0.10.4", "const-oid", "crypto-common", - "subtle 2.5.0", + "subtle 2.6.1", ] [[package]] @@ -6182,44 +6233,46 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "dissimilar" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86e3bdc80eee6e16b2b6b0f87fbc98c04bee3455e35174c0de1a125d0688c632" +checksum = "59f8e79d1fbf76bdfbde321e902714bf6c49df88a7dda6fc682fc2979226962d" [[package]] name = "dleq_vrf" version = "0.0.2" source = "git+https://github.com/w3f/ring-vrf?rev=0fef826#0fef8266d851932ad25d6b41bc4b34d834d1e11d" dependencies = [ - "ark-ec", + "ark-ec 0.4.2", "ark-ff 0.4.2", - "ark-scale 0.0.12", + "ark-scale", "ark-secret-scalar", "ark-serialize 0.4.2", "ark-std 0.4.0", "ark-transcript", - "arrayvec 0.7.4", + "arrayvec 0.7.6", "zeroize", ] [[package]] name = "dlmalloc" -version = "0.2.4" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "203540e710bfadb90e5e29930baf5d10270cec1f43ab34f46f78b147b2de715a" +checksum = "d9b5e0d321d61de16390ed273b647ce51605b575916d3c25e6ddf27a1e140035" dependencies = [ + "cfg-if", "libc", + "windows-sys 0.59.0", ] [[package]] @@ -6246,10 +6299,10 @@ dependencies = [ "common-path", "derive-syn-parse", "once_cell", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "regex", - "syn 2.0.87", + "syn 2.0.90", "termcolor", "toml 0.8.19", "walkdir", @@ -6278,9 +6331,9 @@ checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" [[package]] name = "downcast-rs" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "dtoa" @@ -6290,9 +6343,9 @@ checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" [[package]] name = "dunce" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "dyn-clonable" @@ -6310,22 +6363,22 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "558e40ea573c374cf53507fd240b7ee2f5477df7cfebdb97323ec61c719399c5" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "syn 1.0.109", ] [[package]] name = "dyn-clone" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "ecdsa" -version = "0.16.8" +version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", "digest 0.10.7", @@ -6338,9 +6391,9 @@ dependencies = [ [[package]] name = "ed25519" -version = "2.2.2" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ "pkcs8", "signature", @@ -6357,7 +6410,7 @@ dependencies = [ "rand_core 0.6.4", "serde", "sha2 0.10.8", - "subtle 2.5.0", + "subtle 2.6.1", "zeroize", ] @@ -6390,6 +6443,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", +] + [[package]] name = "either" version = "1.13.0" @@ -6415,7 +6480,7 @@ dependencies = [ "rand_core 0.6.4", "sec1", "serdect", - "subtle 2.5.0", + "subtle 2.6.1", "zeroize", ] @@ -6462,61 +6527,81 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] [[package]] name = "enum-as-inner" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" dependencies = [ - "heck 0.4.1", - "proc-macro2 1.0.86", + "heck 0.5.0", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", +] + +[[package]] +name = "enum-ordinalize" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" +dependencies = [ + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "enumflags2" -version = "0.7.7" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c041f5090df68b32bcd905365fd51769c8b9d553fe87fde0b683534f10c01bd2" +checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" dependencies = [ "enumflags2_derive", ] [[package]] name = "enumflags2_derive" -version = "0.7.7" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" +checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "enumn" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fd000fd6988e73bbe993ea3db9b1aa64906ab88766d654973924340c8cddb42" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "env_filter" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" dependencies = [ "log", "regex", @@ -6534,9 +6619,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" dependencies = [ "humantime", "is-terminal", @@ -6547,9 +6632,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.3" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" dependencies = [ "anstream", "anstyle", @@ -6587,11 +6672,12 @@ dependencies = [ [[package]] name = "erased-serde" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b73807008a3c7f171cc40312f37d95ef0396e048b5848d775f54b1a4dd4a0d3" +checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" dependencies = [ "serde", + "typeid", ] [[package]] @@ -6638,7 +6724,7 @@ dependencies = [ "serde", "serde_json", "sha3 0.10.8", - "thiserror", + "thiserror 1.0.69", "uint 0.9.5", ] @@ -6730,6 +6816,17 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "event-listener" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + [[package]] name = "event-listener" version = "5.3.1" @@ -6743,9 +6840,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" dependencies = [ "event-listener 5.3.1", "pin-project-lite", @@ -6770,16 +6867,16 @@ dependencies = [ "file-guard", "fs-err", "prettyplease", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "eyre" -version = "0.6.8" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" dependencies = [ "indenter", "once_cell", @@ -6819,9 +6916,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "fastrlp" @@ -6829,7 +6926,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "auto_impl", "bytes", ] @@ -6841,7 +6938,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec6f82451ff7f0568c6181287189126d492b5654e30a788add08027b6363d019" dependencies = [ "fatality-proc-macro", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -6852,10 +6949,10 @@ checksum = "eb42427514b063d97ce21d5199f36c0c307d981434a6be32582bc79fe5bd2303" dependencies = [ "expander", "indexmap 2.7.0", - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -6865,7 +6962,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e182f7dbc2ef73d9ef67351c5fbbea084729c48362d3ce9dd44c28e32e277fe5" dependencies = [ "libc", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -6891,27 +6988,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ "rand_core 0.6.4", - "subtle 2.5.0", + "subtle 2.6.1", ] [[package]] name = "fflonk" -version = "0.1.0" -source = "git+https://github.com/w3f/fflonk#1e854f35e9a65d08b11a86291405cdc95baa0a35" +version = "0.1.1" +source = "git+https://github.com/w3f/fflonk#1c97672e8d09321babda3fea795352f982e9c2d5" dependencies = [ - "ark-ec", - "ark-ff 0.4.2", - "ark-poly", - "ark-serialize 0.4.2", - "ark-std 0.4.0", + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-poly 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", "merlin", ] [[package]] name = "fiat-crypto" -version = "0.2.5" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "file-guard" @@ -6929,20 +7026,20 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84f2e425d9790201ba4af4630191feac6dcc98765b118d4d18e91d23c2353866" dependencies = [ - "env_logger 0.10.1", + "env_logger 0.10.2", "log", ] [[package]] name = "filetime" -version = "0.2.22" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", - "windows-sys 0.48.0", + "libredox", + "windows-sys 0.59.0", ] [[package]] @@ -7019,12 +7116,12 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.27" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.8.0", ] [[package]] @@ -7097,7 +7194,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9" dependencies = [ "nonempty", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -7177,7 +7274,7 @@ dependencies = [ "Inflector", "array-bytes", "chrono", - "clap 4.5.13", + "clap 4.5.21", "comfy-table", "cumulus-client-parachain-inherent", "cumulus-primitives-proof-size-hostfunction 0.2.0", @@ -7230,7 +7327,7 @@ dependencies = [ "substrate-test-runtime", "subxt", "subxt-signer", - "thiserror", + "thiserror 1.0.69", "thousands", "westend-runtime", ] @@ -7284,12 +7381,12 @@ dependencies = [ "frame-election-provider-support 28.0.0", "frame-support 28.0.0", "parity-scale-codec", - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.92", "quote 1.0.37", "scale-info", "sp-arithmetic 23.0.0", - "syn 2.0.87", + "syn 2.0.90", "trybuild", ] @@ -7299,10 +7396,10 @@ version = "14.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8156f209055d352994ecd49e19658c6b469d7c6de923bd79868957d0dcfb6f71" dependencies = [ - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -7344,7 +7441,7 @@ dependencies = [ name = "frame-election-solution-type-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 4.5.13", + "clap 4.5.21", "frame-election-provider-solution-type 13.0.0", "frame-election-provider-support 28.0.0", "frame-support 28.0.0", @@ -7477,7 +7574,7 @@ name = "frame-omni-bencher" version = "0.1.0" dependencies = [ "assert_cmd", - "clap 4.5.13", + "clap 4.5.21", "cumulus-primitives-proof-size-hostfunction 0.2.0", "cumulus-test-runtime", "frame-benchmarking-cli", @@ -7489,7 +7586,7 @@ dependencies = [ "sp-statement-store 10.0.0", "sp-tracing 16.0.0", "tempfile", - "tracing-subscriber 0.3.18", + "tracing-subscriber", ] [[package]] @@ -7621,7 +7718,7 @@ dependencies = [ "parity-scale-codec", "pretty_assertions", "proc-macro-warning", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "regex", "scale-info", @@ -7631,7 +7728,7 @@ dependencies = [ "sp-metadata-ir 0.6.0", "sp-runtime 31.0.1", "static_assertions", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -7648,10 +7745,10 @@ dependencies = [ "itertools 0.11.0", "macro_magic", "proc-macro-warning", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -7659,10 +7756,10 @@ name = "frame-support-procedural-tools" version = "10.0.0" dependencies = [ "frame-support-procedural-tools-derive 11.0.0", - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -7672,19 +7769,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bead15a320be1764cdd50458c4cfacb23e0cee65f64f500f8e34136a94c7eeca" dependencies = [ "frame-support-procedural-tools-derive 12.0.0", - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "frame-support-procedural-tools-derive" version = "11.0.0" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -7693,9 +7790,9 @@ version = "12.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed971c6435503a099bdac99fe4c5bea08981709e5b5a0a8535a1856f48561191" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -7876,9 +7973,12 @@ dependencies = [ [[package]] name = "fs-err" -version = "2.9.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0845fa252299212f0389d64ba26f34fa32cfe41588355f21ed507c59a0f64541" +checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" +dependencies = [ + "autocfg", +] [[package]] name = "fs2" @@ -7896,7 +7996,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29f9df8a11882c4e3335eb2d18a0137c505d9ca927470b0cac9c6f0ae07d28f7" dependencies = [ - "rustix 0.38.42", + "rustix 0.38.41", "windows-sys 0.48.0", ] @@ -7999,11 +8099,11 @@ dependencies = [ [[package]] name = "futures-lite" -version = "2.3.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" dependencies = [ - "fastrand 2.3.0", + "fastrand 2.2.0", "futures-core", "futures-io", "parking", @@ -8016,9 +8116,9 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -8028,7 +8128,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb" dependencies = [ "futures-io", - "rustls 0.23.18", + "rustls 0.23.19", "rustls-pki-types", ] @@ -8050,7 +8150,7 @@ version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" dependencies = [ - "gloo-timers", + "gloo-timers 0.2.6", "send_wrapper", ] @@ -8126,13 +8226,15 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -8147,11 +8249,11 @@ dependencies = [ [[package]] name = "ghash" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" dependencies = [ - "opaque-debug 0.3.0", + "opaque-debug 0.3.1", "polyval", ] @@ -8168,9 +8270,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" dependencies = [ "fallible-iterator 0.3.0", "stable_deref_trait", @@ -8213,7 +8315,7 @@ dependencies = [ "pin-project", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -8231,6 +8333,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "gloo-utils" version = "0.2.0" @@ -8291,9 +8405,9 @@ dependencies = [ [[package]] name = "governor" -version = "0.6.0" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "821239e5672ff23e2a7060901fa622950bbd80b649cdaadd78d1c1767ed14eb4" +checksum = "68a7f542ee6b35af73b06abc0dad1c1bae89964e4e253bc4b587b91c9637867b" dependencies = [ "cfg-if", "dashmap", @@ -8302,9 +8416,11 @@ dependencies = [ "no-std-compat", "nonzero_ext", "parking_lot 0.12.3", + "portable-atomic", "quanta", "rand", "smallvec", + "spinning_top", ] [[package]] @@ -8315,7 +8431,7 @@ checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", "rand_core 0.6.4", - "subtle 2.5.0", + "subtle 2.6.1", ] [[package]] @@ -8329,7 +8445,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http 0.2.9", + "http 0.2.12", "indexmap 2.7.0", "slab", "tokio", @@ -8339,9 +8455,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" dependencies = [ "atomic-waker", "bytes", @@ -8358,22 +8474,26 @@ dependencies = [ [[package]] name = "half" -version = "1.8.2" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] [[package]] name = "handlebars" -version = "5.1.0" +version = "5.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab283476b99e66691dee3f1640fea91487a8d81f50fb5ecc75538f8f8879a1e4" +checksum = "d08485b96a0e6393e9e4d1b8d48cf74ad6c063cd905eb33f42c1ce3f0377539b" dependencies = [ "log", "pest", "pest_derive", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -8426,6 +8546,8 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ + "allocator-api2", + "equivalent", "foldhash", "serde", ] @@ -8441,11 +8563,11 @@ dependencies = [ [[package]] name = "hashlink" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.14.5", + "hashbrown 0.15.2", ] [[package]] @@ -8484,6 +8606,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "hex" version = "0.4.3" @@ -8495,9 +8623,9 @@ dependencies = [ [[package]] name = "hex-conservative" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ed443af458ccb6d81c1e7e661545f94d3176752fb1df2f543b902a1e0f51e2" +checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" [[package]] name = "hex-conservative" @@ -8505,7 +8633,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", ] [[package]] @@ -8531,8 +8659,8 @@ dependencies = [ "ipnet", "once_cell", "rand", - "socket2 0.5.7", - "thiserror", + "socket2 0.5.8", + "thiserror 1.0.69", "tinyvec", "tokio", "tracing", @@ -8555,7 +8683,7 @@ dependencies = [ "rand", "resolv-conf", "smallvec", - "thiserror", + "thiserror 1.0.69", "tokio", "tracing", ] @@ -8610,14 +8738,14 @@ dependencies = [ [[package]] name = "honggfuzz" -version = "0.5.55" +version = "0.5.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "848e9c511092e0daa0a35a63e8e6e475a3e8f870741448b9f6028d69b142f18e" +checksum = "7c76b6234c13c9ea73946d1379d33186151148e0da231506b964b44f3d023505" dependencies = [ "arbitrary", "lazy_static", - "memmap2 0.5.10", - "rustc_version 0.4.0", + "memmap2 0.9.5", + "rustc_version 0.4.1", ] [[package]] @@ -8633,9 +8761,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.9" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -8655,20 +8783,20 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http 0.2.9", + "http 0.2.12", "pin-project-lite", ] [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http 1.1.0", @@ -8683,7 +8811,7 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "pin-project-lite", ] @@ -8695,9 +8823,9 @@ checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -8713,22 +8841,22 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.29" +version = "0.14.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" +checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", "h2 0.3.26", - "http 0.2.9", - "http-body 0.4.5", + "http 0.2.12", + "http-body 0.4.6", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.7", + "socket2 0.5.8", "tokio", "tower-service", "tracing", @@ -8737,16 +8865,16 @@ dependencies = [ [[package]] name = "hyper" -version = "1.3.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.5", + "h2 0.4.7", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "httparse", "httpdate", "itoa", @@ -8763,10 +8891,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", - "http 0.2.9", - "hyper 0.14.29", + "http 0.2.12", + "hyper 0.14.31", "log", - "rustls 0.21.7", + "rustls 0.21.12", "rustls-native-certs 0.6.3", "tokio", "tokio-rustls 0.24.1", @@ -8780,16 +8908,16 @@ checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.3.1", + "hyper 1.5.1", "hyper-util", "log", - "rustls 0.23.18", - "rustls-native-certs 0.8.0", + "rustls 0.23.19", + "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", "tokio-rustls 0.26.0", "tower-service", - "webpki-roots 0.26.3", + "webpki-roots 0.26.7", ] [[package]] @@ -8798,7 +8926,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.29", + "hyper 0.14.31", "pin-project-lite", "tokio", "tokio-io-timeout", @@ -8811,7 +8939,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper 0.14.29", + "hyper 0.14.31", "native-tls", "tokio", "tokio-native-tls", @@ -8819,36 +8947,35 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.5" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-channel", "futures-util", "http 1.1.0", - "http-body 1.0.0", - "hyper 1.3.1", + "http-body 1.0.1", + "hyper 1.5.1", "pin-project-lite", - "socket2 0.5.7", + "socket2 0.5.8", "tokio", - "tower", "tower-service", "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.57" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows 0.48.0", + "windows-core 0.52.0", ] [[package]] @@ -8861,91 +8988,224 @@ dependencies = [ ] [[package]] -name = "ident_case" -version = "1.0.1" +name = "icu_collections" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] [[package]] -name = "idna" -version = "0.4.0" +name = "icu_locid" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", ] [[package]] -name = "idna" -version = "0.5.0" +name = "icu_locid_transform" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", ] [[package]] -name = "if-addrs" -version = "0.10.2" +name = "icu_locid_transform_data" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cabb0019d51a643781ff15c9c8a3e5dedc365c47211270f4e8f82812fedd8f0a" -dependencies = [ - "libc", - "windows-sys 0.48.0", -] +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" [[package]] -name = "if-watch" -version = "3.2.0" +name = "icu_normalizer" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b0422c86d7ce0e97169cc42e04ae643caf278874a7a3c87b8150a220dc7e1e" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" dependencies = [ - "async-io 2.3.3", - "core-foundation", - "fnv", - "futures", - "if-addrs", - "ipnet", - "log", - "rtnetlink", - "system-configuration", - "tokio", - "windows 0.51.1", + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", ] [[package]] -name = "igd-next" -version = "0.14.3" +name = "icu_normalizer_data" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "064d90fec10d541084e7b39ead8875a5a80d9114a2b18791565253bae25f49e4" -dependencies = [ - "async-trait", - "attohttpc", - "bytes", - "futures", - "http 0.2.9", - "hyper 0.14.29", - "log", - "rand", - "tokio", - "url", - "xmltree", -] +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" [[package]] -name = "impl-codec" -version = "0.6.0" +name = "icu_properties" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ - "parity-scale-codec", + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", ] [[package]] -name = "impl-codec" -version = "0.7.0" +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", +] + +[[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.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "if-addrs" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabb0019d51a643781ff15c9c8a3e5dedc365c47211270f4e8f82812fedd8f0a" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "if-watch" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf9d64cfcf380606e64f9a0bcf493616b65331199f984151a6fa11a7b3cde38" +dependencies = [ + "async-io 2.4.0", + "core-foundation 0.9.4", + "fnv", + "futures", + "if-addrs", + "ipnet", + "log", + "netlink-packet-core", + "netlink-packet-route", + "netlink-proto", + "netlink-sys", + "rtnetlink", + "system-configuration 0.6.1", + "tokio", + "windows 0.53.0", +] + +[[package]] +name = "igd-next" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "064d90fec10d541084e7b39ead8875a5a80d9114a2b18791565253bae25f49e4" +dependencies = [ + "async-trait", + "attohttpc", + "bytes", + "futures", + "http 0.2.12", + "hyper 0.14.31", + "log", + "rand", + "tokio", + "url", + "xmltree", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-codec" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b67aa010c1e3da95bf151bd8b4c059b2ed7e75387cdb969b4f8f2723a43f9941" dependencies = [ @@ -9012,31 +9272,31 @@ dependencies = [ [[package]] name = "impl-trait-for-tuples" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 1.0.109", + "syn 2.0.90", ] [[package]] name = "include_dir" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" dependencies = [ "include_dir_macros", ] [[package]] name = "include_dir_macros" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", ] @@ -9076,15 +9336,15 @@ checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" [[package]] name = "indicatif" -version = "0.17.7" +version = "0.17.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" +checksum = "cbf675b85ed934d3c67b5c5469701eec7db22689d0a2139d856e0925fa28b281" dependencies = [ "console", - "instant", "number_prefix", "portable-atomic", - "unicode-width", + "unicode-width 0.2.0", + "web-time", ] [[package]] @@ -9137,7 +9397,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2 0.5.7", + "socket2 0.5.8", "widestring", "windows-sys 0.48.0", "winreg", @@ -9149,7 +9409,7 @@ version = "0.21.3" source = "git+https://github.com/chevdor/subwasm?rev=v0.21.3#aa8acb6fdfb34144ac51ab95618a9b37fa251295" dependencies = [ "ipfs-unixfs", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -9168,30 +9428,36 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.8.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "is-terminal" -version = "0.4.9" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ - "hermit-abi 0.3.9", - "rustix 0.38.42", - "windows-sys 0.48.0", + "hermit-abi 0.4.0", + "libc", + "windows-sys 0.52.0", ] [[package]] name = "is_executable" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa9acdc6d67b75e626ad644734e8bc6df893d9cd2a834129065d3dd6158ea9c8" +checksum = "d4a1b5bad6f9072935961dfbf1cced2f3d129963d091b6f69f007fe04e758ae2" dependencies = [ "winapi", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "isahc" version = "1.7.2" @@ -9206,7 +9472,7 @@ dependencies = [ "encoding_rs", "event-listener 2.5.3", "futures-lite 1.13.0", - "http 0.2.9", + "http 0.2.12", "log", "mime", "once_cell", @@ -9257,9 +9523,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jemalloc_pprof" @@ -9288,7 +9554,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.69", "walkdir", ] @@ -9309,19 +9575,14 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" dependencies = [ + "once_cell", "wasm-bindgen", ] -[[package]] -name = "json" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" - [[package]] name = "json-patch" version = "1.4.0" @@ -9330,7 +9591,7 @@ checksum = "ec9ad60d674508f3ca8f380a928cfe7b096bc729c4e2dbfe3852bc45da3ab30b" dependencies = [ "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -9343,7 +9604,7 @@ dependencies = [ "pest_derive", "regex", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -9388,11 +9649,11 @@ dependencies = [ "http 1.1.0", "jsonrpsee-core", "pin-project", - "rustls 0.23.18", + "rustls 0.23.19", "rustls-pki-types", "rustls-platform-verifier", - "soketto 0.8.0", - "thiserror", + "soketto 0.8.1", + "thiserror 1.0.69", "tokio", "tokio-rustls 0.26.0", "tokio-util", @@ -9411,16 +9672,16 @@ dependencies = [ "futures-timer", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", "jsonrpsee-types", "parking_lot 0.12.3", "pin-project", "rand", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", "tracing", @@ -9435,17 +9696,17 @@ checksum = "b3638bc4617f96675973253b3a45006933bde93c2fd8a6170b33c777cc389e5b" dependencies = [ "async-trait", "base64 0.22.1", - "http-body 1.0.0", - "hyper 1.3.1", + "http-body 1.0.1", + "hyper 1.5.1", "hyper-rustls 0.27.3", "hyper-util", "jsonrpsee-core", "jsonrpsee-types", - "rustls 0.23.18", + "rustls 0.23.19", "rustls-platform-verifier", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "tokio", "tower", "tracing", @@ -9459,10 +9720,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06c01ae0007548e73412c08e2285ffe5d723195bf268bce67b1b77c3bb2a14d" dependencies = [ "heck 0.5.0", - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -9473,9 +9734,9 @@ checksum = "82ad8ddc14be1d4290cd68046e7d1d37acd408efed6d3ca08aefcc3ad6da069c" dependencies = [ "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", - "hyper 1.3.1", + "hyper 1.5.1", "hyper-util", "jsonrpsee-core", "jsonrpsee-types", @@ -9483,8 +9744,8 @@ dependencies = [ "route-recognizer", "serde", "serde_json", - "soketto 0.8.0", - "thiserror", + "soketto 0.8.1", + "thiserror 1.0.69", "tokio", "tokio-stream", "tokio-util", @@ -9501,7 +9762,7 @@ dependencies = [ "http 1.1.0", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -9558,9 +9819,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ "cpufeatures", ] @@ -9644,9 +9905,9 @@ dependencies = [ "either", "futures", "home", - "http 0.2.9", - "http-body 0.4.5", - "hyper 0.14.29", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.31", "hyper-rustls 0.24.2", "hyper-timeout", "jsonpath-rust", @@ -9655,13 +9916,13 @@ dependencies = [ "pem 3.0.4", "pin-project", "rand", - "rustls 0.21.7", - "rustls-pemfile 1.0.3", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", "secrecy 0.8.0", "serde", "serde_json", "serde_yaml", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-tungstenite", "tokio-util", @@ -9678,13 +9939,13 @@ checksum = "b5bba93d054786eba7994d03ce522f368ef7d48c88a1826faa28478d85fb63ae" dependencies = [ "chrono", "form_urlencoded", - "http 0.2.9", + "http 0.2.12", "json-patch", "k8s-openapi", "once_cell", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -9707,7 +9968,7 @@ dependencies = [ "serde", "serde_json", "smallvec", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-util", "tracing", @@ -9766,13 +10027,13 @@ dependencies = [ [[package]] name = "landlock" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1530c5b973eeed4ac216af7e24baf5737645a6272e361f1fb95710678b67d9cc" +checksum = "9baa9eeb6e315942429397e617a190f4fdc696ef1ee0342939d641029cbb4ea7" dependencies = [ "enumflags2", "libc", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -9798,9 +10059,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.169" +version = "0.2.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" [[package]] name = "libflate" @@ -9824,36 +10085,35 @@ dependencies = [ [[package]] name = "libfuzzer-sys" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +checksum = "9b9569d2f74e257076d8c6bfa73fb505b46b851e51ddaecc825944aa3bed17fa" dependencies = [ "arbitrary", "cc", - "once_cell", ] [[package]] name = "libloading" -version = "0.7.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "winapi", + "windows-targets 0.52.6", ] [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libnghttp2-sys" -version = "0.1.9+1.58.0" +version = "0.1.10+1.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b57e858af2798e167e709b9d969325b6d8e9d50232fcbc494d7d54f976854a64" +checksum = "959c25552127d2e1fa72f0e52548ec04fc386e827ba71a7bd01db46a447dc135" dependencies = [ "cc", "libc", @@ -9888,10 +10148,10 @@ dependencies = [ "libp2p-upnp", "libp2p-websocket", "libp2p-yamux", - "multiaddr 0.18.1", + "multiaddr 0.18.2", "pin-project", "rw-stream-sink", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -9929,8 +10189,8 @@ dependencies = [ "futures", "futures-timer", "libp2p-identity", - "multiaddr 0.18.1", - "multihash 0.19.1", + "multiaddr 0.18.2", + "multihash 0.19.2", "multistream-select", "once_cell", "parking_lot 0.12.3", @@ -9939,7 +10199,7 @@ dependencies = [ "rand", "rw-stream-sink", "smallvec", - "thiserror", + "thiserror 1.0.69", "tracing", "unsigned-varint 0.8.0", "void", @@ -9976,29 +10236,29 @@ dependencies = [ "libp2p-core", "libp2p-identity", "libp2p-swarm", - "lru 0.12.3", + "lru 0.12.5", "quick-protobuf 0.8.1", "quick-protobuf-codec", "smallvec", - "thiserror", + "thiserror 1.0.69", "tracing", "void", ] [[package]] name = "libp2p-identity" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cca1eb2bc1fd29f099f3daaab7effd01e1a54b7c577d0ed082521034d912e8" +checksum = "257b5621d159b32282eac446bed6670c39c7dc68a200a992d8f056afa0066f6d" dependencies = [ "bs58", "ed25519-dalek", "hkdf", - "multihash 0.19.1", + "multihash 0.19.2", "quick-protobuf 0.8.1", "rand", "sha2 0.10.8", - "thiserror", + "thiserror 1.0.69", "tracing", "zeroize", ] @@ -10009,7 +10269,7 @@ version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced237d0bd84bbebb7c2cad4c073160dacb4fe40534963c32ed6d4c6bb7702a3" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "asynchronous-codec 0.7.0", "bytes", "either", @@ -10025,7 +10285,7 @@ dependencies = [ "rand", "sha2 0.10.8", "smallvec", - "thiserror", + "thiserror 1.0.69", "tracing", "uint 0.9.5", "void", @@ -10047,7 +10307,7 @@ dependencies = [ "libp2p-swarm", "rand", "smallvec", - "socket2 0.5.7", + "socket2 0.5.8", "tokio", "tracing", "void", @@ -10083,15 +10343,15 @@ dependencies = [ "futures", "libp2p-core", "libp2p-identity", - "multiaddr 0.18.1", - "multihash 0.19.1", + "multiaddr 0.18.2", + "multihash 0.19.2", "once_cell", "quick-protobuf 0.8.1", "rand", "sha2 0.10.8", "snow", "static_assertions", - "thiserror", + "thiserror 1.0.69", "tracing", "x25519-dalek", "zeroize", @@ -10132,9 +10392,9 @@ dependencies = [ "quinn", "rand", "ring 0.17.8", - "rustls 0.23.18", - "socket2 0.5.7", - "thiserror", + "rustls 0.23.19", + "socket2 0.5.8", + "thiserror 1.0.69", "tokio", "tracing", ] @@ -10172,7 +10432,7 @@ dependencies = [ "libp2p-core", "libp2p-identity", "libp2p-swarm-derive", - "lru 0.12.3", + "lru 0.12.5", "multistream-select", "once_cell", "rand", @@ -10190,9 +10450,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "206e0aa0ebe004d778d79fb0966aa0de996c19894e2c0605ba2f8524dd4443d8" dependencies = [ "heck 0.5.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -10207,7 +10467,7 @@ dependencies = [ "libc", "libp2p-core", "libp2p-identity", - "socket2 0.5.7", + "socket2 0.5.8", "tokio", "tracing", ] @@ -10224,9 +10484,9 @@ dependencies = [ "libp2p-identity", "rcgen 0.11.3", "ring 0.17.8", - "rustls 0.23.18", - "rustls-webpki 0.101.4", - "thiserror", + "rustls 0.23.19", + "rustls-webpki 0.101.7", + "thiserror 1.0.69", "x509-parser", "yasna", ] @@ -10261,11 +10521,11 @@ dependencies = [ "parking_lot 0.12.3", "pin-project-lite", "rw-stream-sink", - "soketto 0.8.0", - "thiserror", + "soketto 0.8.1", + "thiserror 1.0.69", "tracing", "url", - "webpki-roots 0.25.2", + "webpki-roots 0.25.4", ] [[package]] @@ -10277,10 +10537,21 @@ dependencies = [ "either", "futures", "libp2p-core", - "thiserror", + "thiserror 1.0.69", "tracing", "yamux 0.12.1", - "yamux 0.13.3", + "yamux 0.13.4", +] + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", + "redox_syscall 0.5.7", ] [[package]] @@ -10325,7 +10596,7 @@ checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" dependencies = [ "crunchy", "digest 0.9.0", - "subtle 2.5.0", + "subtle 2.6.1", ] [[package]] @@ -10359,9 +10630,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.12" +version = "1.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" +checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" dependencies = [ "cc", "libc", @@ -10395,9 +10666,9 @@ dependencies = [ [[package]] name = "linregress" -version = "0.5.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de0b5f52a9f84544d268f5fabb71b38962d6aa3c6600b8bcd27d44ccf9c9c45" +checksum = "a9eda9dcf4f2a99787827661f312ac3219292549c2ee992bf9a6248ffb066bf7" dependencies = [ "nalgebra", ] @@ -10450,6 +10721,12 @@ dependencies = [ "paste", ] +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + [[package]] name = "litep2p" version = "0.9.0" @@ -10467,7 +10744,7 @@ dependencies = [ "hickory-resolver", "indexmap 2.7.0", "libc", - "mockall 0.13.0", + "mockall 0.13.1", "multiaddr 0.17.1", "multihash 0.17.0", "network-interface", @@ -10485,9 +10762,9 @@ dependencies = [ "simple-dns", "smallvec", "snow", - "socket2 0.5.7", + "socket2 0.5.8", "static_assertions", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", "tokio-tungstenite", @@ -10510,9 +10787,9 @@ checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -10539,17 +10816,17 @@ dependencies = [ [[package]] name = "lru" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eedb2bdbad7e0634f83989bf596f497b070130daaa398ab22d84c39e266deec5" +checksum = "a4a83fb7698b3643a0e34f9ae6f2e8f0178c0fd42f8b59d493aa271ff3a5bf21" [[package]] name = "lru" -version = "0.12.3" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.14.5", + "hashbrown 0.15.2", ] [[package]] @@ -10563,19 +10840,18 @@ dependencies = [ [[package]] name = "lz4" -version = "1.24.0" +version = "1.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9e2dd86df36ce760a60f6ff6ad526f7ba1f14ba0356f8254fb6905e6494df1" +checksum = "4d1febb2b4a79ddd1980eede06a8f7902197960aa0383ffcfdd62fe723036725" dependencies = [ - "libc", "lz4-sys", ] [[package]] name = "lz4-sys" -version = "1.9.4" +version = "1.11.1+lz4-1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900" +checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" dependencies = [ "cc", "libc", @@ -10590,15 +10866,6 @@ dependencies = [ "libc", ] -[[package]] -name = "mach2" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" -dependencies = [ - "libc", -] - [[package]] name = "macro_magic" version = "0.5.1" @@ -10608,7 +10875,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -10620,9 +10887,9 @@ dependencies = [ "const-random", "derive-syn-parse", "macro_magic_core_macros", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -10631,9 +10898,9 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -10644,7 +10911,7 @@ checksum = "73ea28ee64b88876bf45277ed9a5817c1817df061a74f2b988971a12570e5869" dependencies = [ "macro_magic_core", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -10672,15 +10939,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" -[[package]] -name = "matchers" -version = "0.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" -dependencies = [ - "regex-automata 0.1.10", -] - [[package]] name = "matchers" version = "0.1.0" @@ -10692,9 +10950,9 @@ dependencies = [ [[package]] name = "matrixmultiply" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090126dc04f95dc0d1c1c91f61bdd474b3930ca064c1edc8a849da2c6cbe1e77" +checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" dependencies = [ "autocfg", "rawpointer", @@ -10718,11 +10976,11 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memfd" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc89ccdc6e10d6907450f753537ebc5c5d3460d2e4e62ea74bd571db62c0f9e" +checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" dependencies = [ - "rustix 0.37.23", + "rustix 0.38.41", ] [[package]] @@ -10736,9 +10994,9 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45fd3a57831bf88bc63f8cebc0cf956116276e97fef3966103e96416209f7c92" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" dependencies = [ "libc", ] @@ -10752,15 +11010,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - [[package]] name = "memory-db" version = "0.32.0" @@ -10820,6 +11069,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minicov" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" +dependencies = [ + "cc", + "walkdir", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -10830,7 +11089,7 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" name = "minimal-template-node" version = "0.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.21", "docify", "futures", "futures-timer", @@ -10853,20 +11112,28 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + [[package]] name = "mio" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi 0.3.9", "libc", "wasi", "windows-sys 0.52.0", @@ -10879,7 +11146,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daa3eb39495d8e2e2947a1d862852c90cc6a4a8845f8b41c8829cb9fcc047f4a" dependencies = [ "arrayref", - "arrayvec 0.7.4", + "arrayvec 0.7.6", "bitflags 1.3.2", "blake2 0.10.6", "c2-chacha", @@ -10892,8 +11159,8 @@ dependencies = [ "rand", "rand_chacha", "rand_distr", - "subtle 2.5.0", - "thiserror", + "subtle 2.6.1", + "thiserror 1.0.69", "zeroize", ] @@ -10952,15 +11219,15 @@ dependencies = [ [[package]] name = "mockall" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c28b3fb6d753d28c20e826cd46ee611fda1cf3cde03a443a974043247c065a" +checksum = "39a6bfcc6c8c7eed5ee98b9c3e33adc726054389233e201c95dab2d41a3839d2" dependencies = [ "cfg-if", "downcast", "fragile", - "mockall_derive 0.13.0", - "predicates 3.0.3", + "mockall_derive 0.13.1", + "predicates 3.1.2", "predicates-tree", ] @@ -10971,21 +11238,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ce75669015c4f47b289fd4d4f56e894e4c96003ffdf3ac51313126f94c6cbb" dependencies = [ "cfg-if", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "syn 1.0.109", ] [[package]] name = "mockall_derive" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "341014e7f530314e9a1fdbc7400b244efea7122662c96bfa248c31da5bfb2020" +checksum = "25ca3004c2efe9011bd4e461bd8256445052b9615405b4f7ea43fc8ca5c20898" dependencies = [ "cfg-if", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -11015,20 +11282,20 @@ dependencies = [ [[package]] name = "multiaddr" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b852bc02a2da5feed68cd14fa50d0774b92790a5bdbfa932a813926c8472070" +checksum = "fe6351f60b488e04c1d21bc69e56b89cb3f5e8f5d22557d6e8031bdfd79b6961" dependencies = [ "arrayref", "byteorder", "data-encoding", "libp2p-identity", "multibase 0.9.1", - "multihash 0.19.1", + "multihash 0.19.2", "percent-encoding", "serde", "static_assertions", - "unsigned-varint 0.7.2", + "unsigned-varint 0.8.0", "url", ] @@ -11076,7 +11343,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835d6ff01d610179fbce3de1694d007e500bf33a7f29689838941d6bf783ae40" dependencies = [ "blake2b_simd 1.0.2", - "blake2s_simd 1.0.1", + "blake2s_simd 1.0.2", "blake3", "core2", "digest 0.10.7", @@ -11093,7 +11360,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfd8a792c1694c6da4f68db0a9d707c72bd260994da179e6030a5dcee00bb815" dependencies = [ "blake2b_simd 1.0.2", - "blake2s_simd 1.0.1", + "blake2s_simd 1.0.2", "blake3", "core2", "digest 0.10.7", @@ -11105,23 +11372,23 @@ dependencies = [ [[package]] name = "multihash" -version = "0.19.1" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076d548d76a0e2a0d4ab471d0b1c36c577786dfc4471242035d97a12a735c492" +checksum = "cc41f430805af9d1cf4adae4ed2149c759b877b01d909a1f40256188d09345d2" dependencies = [ "core2", - "unsigned-varint 0.7.2", + "unsigned-varint 0.8.0", ] [[package]] name = "multihash-derive" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc076939022111618a5026d3be019fd8b366e76314538ff9a1b59ffbcbf98bcd" +checksum = "1d6d4752e6230d8ef7adf7bd5d8c4b1f6561c1014c5ba9a37445ccefe18aa1db" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 1.1.3", "proc-macro-error", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "syn 1.0.109", "synstructure 0.12.6", @@ -11129,9 +11396,9 @@ dependencies = [ [[package]] name = "multimap" -version = "0.8.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" [[package]] name = "multistream-select" @@ -11149,13 +11416,12 @@ dependencies = [ [[package]] name = "nalgebra" -version = "0.32.3" +version = "0.33.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307ed9b18cc2423f29e83f84fd23a8e73628727990181f18641a8b5dc2ab1caa" +checksum = "26aecdf64b707efd1310e3544d709c5c0ac61c13756046aaaba41be5c4f66a3b" dependencies = [ "approx", "matrixmultiply", - "nalgebra-macros", "num-complex", "num-rational", "num-traits", @@ -11163,24 +11429,12 @@ dependencies = [ "typenum", ] -[[package]] -name = "nalgebra-macros" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91761aed67d03ad966ef783ae962ef9bbaca728d2dd7ceb7939ec110fffad998" -dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.37", - "syn 1.0.109", -] - [[package]] name = "names" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bddcd3bf5144b6392de80e04c347cd7fab2508f6df16a85fc496ecd5cec39bc" dependencies = [ - "clap 3.2.25", "rand", ] @@ -11202,28 +11456,27 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework", + "security-framework 2.11.1", "security-framework-sys", "tempfile", ] [[package]] name = "netlink-packet-core" -version = "0.4.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345b8ab5bd4e71a2986663e88c56856699d060e78e152e6e9d7966fcd5491297" +checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" dependencies = [ "anyhow", "byteorder", - "libc", "netlink-packet-utils", ] [[package]] name = "netlink-packet-route" -version = "0.12.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9ea4302b9759a7a88242299225ea3688e63c85ea136371bb6cf94fd674efaab" +checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" dependencies = [ "anyhow", "bitflags 1.3.2", @@ -11242,29 +11495,29 @@ dependencies = [ "anyhow", "byteorder", "paste", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "netlink-proto" -version = "0.10.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65b4b14489ab424703c092062176d52ba55485a89c076b4f9db05092b7223aa6" +checksum = "86b33524dc0968bfad349684447bfce6db937a9ac3332a1fe60c0c5a5ce63f21" dependencies = [ "bytes", "futures", "log", "netlink-packet-core", "netlink-sys", - "thiserror", + "thiserror 1.0.69", "tokio", ] [[package]] name = "netlink-sys" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6471bf08e7ac0135876a9581bf3217ef0333c191c128d34878079f42ee150411" +checksum = "416060d346fbaf1f23f9512963e3e878f1a78e707cb699ba9215761754244307" dependencies = [ "bytes", "futures", @@ -11275,21 +11528,21 @@ dependencies = [ [[package]] name = "network-interface" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae72fd9dbd7f55dda80c00d66acc3b2130436fcba9ea89118fc508eaae48dfb0" +checksum = "a4a43439bf756eed340bdf8feba761e2d50c7d47175d87545cd5cbe4a137c4d1" dependencies = [ "cc", "libc", - "thiserror", + "thiserror 1.0.69", "winapi", ] [[package]] name = "nix" -version = "0.24.3" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ "bitflags 1.3.2", "cfg-if", @@ -11298,11 +11551,11 @@ dependencies = [ [[package]] name = "nix" -version = "0.26.4" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "cfg-if", "libc", ] @@ -11337,8 +11590,8 @@ version = "0.9.0-dev" dependencies = [ "array-bytes", "async-trait", - "clap 4.5.13", - "derive_more 0.99.17", + "clap 4.5.21", + "derive_more 0.99.18", "fs_extra", "futures", "hash-db", @@ -11413,7 +11666,7 @@ dependencies = [ name = "node-runtime-generate-bags" version = "3.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.21", "generate-bags", "kitchensink-runtime", ] @@ -11422,7 +11675,7 @@ dependencies = [ name = "node-template-release" version = "3.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.21", "flate2", "fs_extra", "glob", @@ -11533,9 +11786,9 @@ dependencies = [ [[package]] name = "num" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ "num-bigint", "num-complex", @@ -11547,11 +11800,10 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -11575,9 +11827,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", ] @@ -11594,9 +11846,9 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -11605,25 +11857,24 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "itoa", ] [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.43" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", @@ -11632,11 +11883,10 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ - "autocfg", "num-bigint", "num-integer", "num-traits", @@ -11700,33 +11950,33 @@ dependencies = [ [[package]] name = "object" -version = "0.36.1" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "oid-registry" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c958dd45046245b9c3c2547369bb634eb461670b2e7e0de552905801a648d1d" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" dependencies = [ "asn1-rs", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "oorandom" -version = "11.1.3" +version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] name = "opaque-debug" @@ -11736,15 +11986,15 @@ checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.64" +version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -11761,9 +12011,9 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -11774,9 +12024,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.102" +version = "0.9.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" dependencies = [ "cc", "libc", @@ -11803,7 +12053,7 @@ dependencies = [ "orchestra-proc-macro", "pin-project", "prioritized-metered-channel", - "thiserror", + "thiserror 1.0.69", "tracing", ] @@ -11817,8 +12067,8 @@ dependencies = [ "indexmap 2.7.0", "itertools 0.11.0", "petgraph", - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.92", "quote 1.0.37", "syn 1.0.109", ] @@ -11842,12 +12092,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "os_str_bytes" -version = "6.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" - [[package]] name = "overload" version = "0.1.1" @@ -13104,11 +13348,11 @@ dependencies = [ "pallet-contracts-proc-macro 23.0.1", "pallet-contracts-uapi 12.0.0", "pallet-insecure-randomness-collective-flip 26.0.0", - "pallet-message-queue 41.0.1", + "pallet-message-queue 41.0.2", "pallet-proxy 38.0.0", "pallet-timestamp 37.0.0", "pallet-utility 38.0.0", - "pallet-xcm 17.0.0", + "pallet-xcm 17.0.1", "parity-scale-codec", "polkadot-parachain-primitives 14.0.0", "polkadot-primitives 16.0.0", @@ -13130,9 +13374,9 @@ dependencies = [ name = "pallet-contracts-proc-macro" version = "18.0.0" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -13141,9 +13385,9 @@ version = "23.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94226cbd48516b7c310eb5dae8d50798c1ce73a7421dc0977c55b7fc2237a283" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -14029,9 +14273,9 @@ dependencies = [ [[package]] name = "pallet-message-queue" -version = "41.0.1" +version = "41.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0faa48b29bf5a178580c164ef00de87319a37da7547a9cd6472dfd160092811a" +checksum = "983f7d1be18e9a089a3e23670918f5085705b4403acd3fdde31878d57b76a1a8" dependencies = [ "environmental", "frame-benchmarking 38.0.0", @@ -14861,7 +15105,7 @@ version = "0.1.0" dependencies = [ "array-bytes", "assert_matches", - "derive_more 0.99.17", + "derive_more 0.99.18", "environmental", "ethereum-types 0.15.1", "frame-benchmarking 28.0.0", @@ -14936,8 +15180,8 @@ name = "pallet-revive-eth-rpc" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.5.13", - "env_logger 0.11.3", + "clap 4.5.21", + "env_logger 0.11.5", "ethabi", "futures", "hex", @@ -14961,7 +15205,7 @@ dependencies = [ "substrate-prometheus-endpoint", "subxt", "subxt-signer", - "thiserror", + "thiserror 1.0.69", "tokio", ] @@ -15032,14 +15276,14 @@ dependencies = [ "frame-system 38.0.0", "pallet-assets 40.0.0", "pallet-balances 39.0.0", - "pallet-message-queue 41.0.1", + "pallet-message-queue 41.0.2", "pallet-proxy 38.0.0", "pallet-revive 0.2.0", "pallet-revive-proc-macro 0.1.1", "pallet-revive-uapi 0.1.1", "pallet-timestamp 37.0.0", "pallet-utility 38.0.0", - "pallet-xcm 17.0.0", + "pallet-xcm 17.0.1", "parity-scale-codec", "polkadot-parachain-primitives 14.0.0", "polkadot-primitives 16.0.0", @@ -15061,9 +15305,9 @@ dependencies = [ name = "pallet-revive-proc-macro" version = "0.1.0" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -15072,9 +15316,9 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc16d1f7cee6a1ee6e8cd710e16230d59fb4935316c1704cf770e4d2335f8d4" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -15515,11 +15759,11 @@ dependencies = [ name = "pallet-staking-reward-curve" version = "11.0.0" dependencies = [ - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.92", "quote 1.0.37", "sp-runtime 31.0.1", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -16119,9 +16363,9 @@ dependencies = [ [[package]] name = "pallet-xcm" -version = "17.0.0" +version = "17.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b1760b6589e53f4ad82216c72c0e38fcb4df149c37224ab3301dc240c85d1d4" +checksum = "989676964dbda5f5275650fbdcd3894fe7fac626d113abf89d572b4952adcc36" dependencies = [ "bounded-collections", "frame-benchmarking 38.0.0", @@ -16138,6 +16382,7 @@ dependencies = [ "staging-xcm 14.2.0", "staging-xcm-builder 17.0.1", "staging-xcm-executor 17.0.0", + "tracing", "xcm-runtime-apis 0.4.0", ] @@ -16276,7 +16521,7 @@ dependencies = [ name = "parachain-template-node" version = "0.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.21", "color-print", "docify", "futures", @@ -16353,8 +16598,8 @@ dependencies = [ "pallet-authorship 38.0.0", "pallet-balances 39.0.0", "pallet-collator-selection 19.0.0", - "pallet-message-queue 41.0.1", - "pallet-xcm 17.0.0", + "pallet-message-queue 41.0.2", + "pallet-xcm 17.0.1", "parity-scale-codec", "polkadot-primitives 16.0.0", "scale-info", @@ -16432,7 +16677,7 @@ dependencies = [ "pallet-collator-selection 19.0.0", "pallet-session 38.0.0", "pallet-timestamp 37.0.0", - "pallet-xcm 17.0.0", + "pallet-xcm 17.0.1", "parity-scale-codec", "polkadot-parachain-primitives 14.0.0", "sp-consensus-aura 0.40.0", @@ -16467,9 +16712,9 @@ checksum = "16b56e3a2420138bdb970f84dfb9c774aea80fa0e7371549eedec0d80c209c67" [[package]] name = "parity-db" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59e9ab494af9e6e813c72170f0d3c1de1500990d62c97cc05cc7576f91aa402f" +checksum = "592a28a24b09c9dc20ac8afaa6839abc417c720afe42c12e1e4a9d6aa2508d2e" dependencies = [ "blake2 0.10.6", "crc32fast", @@ -16483,6 +16728,7 @@ dependencies = [ "rand", "siphasher 0.3.11", "snap", + "winapi", ] [[package]] @@ -16491,7 +16737,7 @@ version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "bitvec", "byte-slice-cast", "bytes", @@ -16506,8 +16752,8 @@ version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" dependencies = [ - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.92", "quote 1.0.37", "syn 1.0.109", ] @@ -16536,7 +16782,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f557c32c6d268a07c921471619c0295f5efad3a0e76d4f97a05c091a51d110b2" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "syn 1.0.109", "synstructure 0.12.6", ] @@ -16571,7 +16817,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", - "parking_lot_core 0.9.8", + "parking_lot_core 0.9.10", ] [[package]] @@ -16590,15 +16836,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", + "redox_syscall 0.5.7", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -16615,7 +16861,7 @@ checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", "rand_core 0.6.4", - "subtle 2.5.0", + "subtle 2.6.1", ] [[package]] @@ -16959,19 +17205,20 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.2" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a" +checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" dependencies = [ - "thiserror", + "memchr", + "thiserror 1.0.69", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.2" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666d00490d4ac815001da55838c500eafb0320019bbaa44444137c48b443a853" +checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" dependencies = [ "pest", "pest_generator", @@ -16979,22 +17226,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.2" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ca01446f50dbda87c1786af8770d535423fa8a53aec03b8f4e3d7eb10e0929" +checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" dependencies = [ "pest", "pest_meta", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "pest_meta" -version = "2.7.2" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56af0a30af74d0445c0bf6d9d051c979b516a1a5af790d251daee76005420a48" +checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" dependencies = [ "once_cell", "pest", @@ -17003,9 +17250,9 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", "indexmap 2.7.0", @@ -17026,16 +17273,16 @@ version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -17043,6 +17290,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand 2.2.0", + "futures-io", +] + [[package]] name = "pkcs1" version = "0.7.5" @@ -17066,21 +17324,21 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "platforms" -version = "3.4.1" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4c7666f2019727f9e8e14bf14456e99c707d780922869f1ba473eee101fa49" +checksum = "d43467300237085a4f9e864b937cf0bc012cef7740be12be1a48b10d2c8a3701" [[package]] name = "plotters" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits", "plotters-backend", @@ -17091,15 +17349,15 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] @@ -17186,7 +17444,7 @@ name = "polkadot-availability-distribution" version = "7.0.0" dependencies = [ "assert_matches", - "derive_more 0.99.17", + "derive_more 0.99.18", "fatality", "futures", "futures-timer", @@ -17208,7 +17466,7 @@ dependencies = [ "sp-keyring 31.0.0", "sp-keystore 0.34.0", "sp-tracing 16.0.0", - "thiserror", + "thiserror 1.0.69", "tracing-gum", ] @@ -17240,7 +17498,7 @@ dependencies = [ "sp-core 28.0.0", "sp-keyring 31.0.0", "sp-tracing 16.0.0", - "thiserror", + "thiserror 1.0.69", "tokio", "tracing-gum", ] @@ -17260,7 +17518,7 @@ name = "polkadot-cli" version = "7.0.0" dependencies = [ "cfg-if", - "clap 4.5.13", + "clap 4.5.21", "frame-benchmarking-cli", "futures", "log", @@ -17281,7 +17539,7 @@ dependencies = [ "sp-maybe-compressed-blob 11.0.0", "sp-runtime 31.0.1", "substrate-build-script-utils", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -17310,7 +17568,7 @@ dependencies = [ "sp-keystore 0.34.0", "sp-runtime 31.0.1", "sp-tracing 16.0.0", - "thiserror", + "thiserror 1.0.69", "tokio-util", "tracing-gum", ] @@ -17344,7 +17602,7 @@ dependencies = [ "assert_matches", "async-channel 1.9.0", "async-trait", - "derive_more 0.99.17", + "derive_more 0.99.18", "fatality", "futures", "futures-timer", @@ -17365,7 +17623,7 @@ dependencies = [ "sp-keyring 31.0.0", "sp-keystore 0.34.0", "sp-tracing 16.0.0", - "thiserror", + "thiserror 1.0.69", "tracing-gum", ] @@ -17381,7 +17639,7 @@ dependencies = [ "reed-solomon-novelpoly", "sp-core 28.0.0", "sp-trie 29.0.0", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -17439,7 +17697,7 @@ dependencies = [ "sp-consensus", "sp-core 28.0.0", "sp-keyring 31.0.0", - "thiserror", + "thiserror 1.0.69", "tracing-gum", ] @@ -17462,7 +17720,7 @@ dependencies = [ "sp-core 28.0.0", "sp-keyring 31.0.0", "sp-maybe-compressed-blob 11.0.0", - "thiserror", + "thiserror 1.0.69", "tracing-gum", ] @@ -17473,7 +17731,7 @@ dependencies = [ "assert_matches", "async-trait", "bitvec", - "derive_more 0.99.17", + "derive_more 0.99.18", "futures", "futures-timer", "itertools 0.11.0", @@ -17506,7 +17764,7 @@ dependencies = [ "sp-keystore 0.34.0", "sp-runtime 31.0.1", "sp-tracing 16.0.0", - "thiserror", + "thiserror 1.0.69", "tracing-gum", ] @@ -17548,7 +17806,7 @@ dependencies = [ "sp-keystore 0.34.0", "sp-runtime 31.0.1", "sp-tracing 16.0.0", - "thiserror", + "thiserror 1.0.69", "tracing-gum", ] @@ -17577,7 +17835,7 @@ dependencies = [ "sp-core 28.0.0", "sp-keyring 31.0.0", "sp-tracing 16.0.0", - "thiserror", + "thiserror 1.0.69", "tracing-gum", ] @@ -17606,7 +17864,7 @@ dependencies = [ "sp-keyring 31.0.0", "sp-keystore 0.34.0", "sp-tracing 16.0.0", - "thiserror", + "thiserror 1.0.69", "tracing-gum", ] @@ -17621,7 +17879,7 @@ dependencies = [ "polkadot-primitives 7.0.0", "polkadot-primitives-test-helpers", "sp-keystore 0.34.0", - "thiserror", + "thiserror 1.0.69", "tracing-gum", "wasm-timer", ] @@ -17691,7 +17949,7 @@ dependencies = [ "polkadot-node-subsystem-util", "polkadot-primitives 7.0.0", "sp-core 28.0.0", - "thiserror", + "thiserror 1.0.69", "tracing-gum", ] @@ -17719,7 +17977,7 @@ dependencies = [ "sp-keyring 31.0.0", "sp-keystore 0.34.0", "sp-tracing 16.0.0", - "thiserror", + "thiserror 1.0.69", "tracing-gum", ] @@ -17735,7 +17993,7 @@ dependencies = [ "polkadot-primitives 7.0.0", "sp-blockchain", "sp-inherents 26.0.0", - "thiserror", + "thiserror 1.0.69", "tracing-gum", ] @@ -17755,7 +18013,7 @@ dependencies = [ "rstest", "sp-core 28.0.0", "sp-tracing 16.0.0", - "thiserror", + "thiserror 1.0.69", "tracing-gum", ] @@ -17777,7 +18035,7 @@ dependencies = [ "schnellru", "sp-application-crypto 30.0.0", "sp-keystore 0.34.0", - "thiserror", + "thiserror 1.0.69", "tracing-gum", ] @@ -17822,7 +18080,7 @@ dependencies = [ "tempfile", "test-parachain-adder", "test-parachain-halt", - "thiserror", + "thiserror 1.0.69", "tokio", "tracing-gum", ] @@ -17846,7 +18104,7 @@ dependencies = [ "sp-keyring 31.0.0", "sp-keystore 0.34.0", "sp-runtime 31.0.1", - "thiserror", + "thiserror 1.0.69", "tracing-gum", ] @@ -17873,7 +18131,7 @@ dependencies = [ "sp-io 30.0.0", "sp-tracing 16.0.0", "tempfile", - "thiserror", + "thiserror 1.0.69", "tracing-gum", ] @@ -17948,7 +18206,7 @@ dependencies = [ "futures", "futures-timer", "http-body-util", - "hyper 1.3.1", + "hyper 1.5.1", "hyper-util", "log", "parity-scale-codec", @@ -17974,7 +18232,7 @@ dependencies = [ "async-channel 1.9.0", "async-trait", "bitvec", - "derive_more 0.99.17", + "derive_more 0.99.18", "fatality", "futures", "hex", @@ -17988,7 +18246,7 @@ dependencies = [ "sc-network-types", "sp-runtime 31.0.1", "strum 0.26.3", - "thiserror", + "thiserror 1.0.69", "tracing-gum", ] @@ -18014,7 +18272,7 @@ dependencies = [ "sp-keystore 0.34.0", "sp-maybe-compressed-blob 11.0.0", "sp-runtime 31.0.1", - "thiserror", + "thiserror 1.0.69", "zstd 0.12.4", ] @@ -18053,7 +18311,7 @@ version = "7.0.0" dependencies = [ "async-trait", "bitvec", - "derive_more 0.99.17", + "derive_more 0.99.18", "fatality", "futures", "orchestra", @@ -18072,7 +18330,7 @@ dependencies = [ "sp-consensus-babe 0.32.0", "sp-runtime 31.0.1", "substrate-prometheus-endpoint", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -18081,7 +18339,7 @@ version = "7.0.0" dependencies = [ "assert_matches", "async-trait", - "derive_more 0.99.17", + "derive_more 0.99.18", "fatality", "futures", "futures-channel", @@ -18112,7 +18370,7 @@ dependencies = [ "sp-core 28.0.0", "sp-keystore 0.34.0", "tempfile", - "thiserror", + "thiserror 1.0.69", "tracing-gum", ] @@ -18131,7 +18389,7 @@ version = "0.1.0" dependencies = [ "assert_cmd", "async-trait", - "clap 4.5.13", + "clap 4.5.21", "color-print", "cumulus-client-cli", "cumulus-client-collator", @@ -18272,7 +18530,7 @@ name = "polkadot-parachain-primitives" version = "6.0.0" dependencies = [ "bounded-collections", - "derive_more 0.99.17", + "derive_more 0.99.18", "parity-scale-codec", "polkadot-core-primitives 7.0.0", "scale-info", @@ -18289,7 +18547,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52b5648a2e8ce1f9a0f8c41c38def670cefd91932cd793468e1a5b0b0b4e4af1" dependencies = [ "bounded-collections", - "derive_more 0.99.17", + "derive_more 0.99.18", "parity-scale-codec", "polkadot-core-primitives 15.0.0", "scale-info", @@ -18324,7 +18582,7 @@ dependencies = [ "sp-runtime 31.0.1", "sp-staking 26.0.0", "sp-std 14.0.0", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -18563,7 +18821,7 @@ dependencies = [ "assert_matches", "bitflags 1.3.2", "bitvec", - "derive_more 0.99.17", + "derive_more 0.99.18", "frame-benchmarking 28.0.0", "frame-support 28.0.0", "frame-support-test", @@ -18624,7 +18882,7 @@ checksum = "bd58e3a17e5df678f5737b018cbfec603af2c93bec56bbb9f8fb8b2b017b54b1" dependencies = [ "bitflags 1.3.2", "bitvec", - "derive_more 0.99.17", + "derive_more 0.99.18", "frame-benchmarking 38.0.0", "frame-support 38.0.0", "frame-system 38.0.0", @@ -18635,7 +18893,7 @@ dependencies = [ "pallet-babe 38.0.0", "pallet-balances 39.0.0", "pallet-broker 0.17.0", - "pallet-message-queue 41.0.1", + "pallet-message-queue 41.0.2", "pallet-mmr 38.0.0", "pallet-session 38.0.0", "pallet-staking 38.0.0", @@ -19057,7 +19315,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb819108697967452fa6d8d96ab4c0d48cbaa423b3156499dcb24f1cf95d6775" dependencies = [ "asset-test-utils 18.0.0", - "assets-common 0.18.0", + "assets-common 0.18.1", "binary-merkle-tree 15.0.1", "bp-header-chain 0.18.1", "bp-messages 0.18.0", @@ -19146,7 +19404,7 @@ dependencies = [ "pallet-insecure-randomness-collective-flip 26.0.0", "pallet-lottery 38.0.0", "pallet-membership 38.0.0", - "pallet-message-queue 41.0.1", + "pallet-message-queue 41.0.2", "pallet-migrations 8.0.0", "pallet-mixnet 0.14.0", "pallet-mmr 38.0.0", @@ -19199,7 +19457,7 @@ dependencies = [ "pallet-utility 38.0.0", "pallet-vesting 38.0.0", "pallet-whitelist 37.0.0", - "pallet-xcm 17.0.0", + "pallet-xcm 17.0.1", "pallet-xcm-benchmarks 17.0.0", "pallet-xcm-bridge-hub 0.13.0", "pallet-xcm-bridge-hub-router 0.15.1", @@ -19589,7 +19847,7 @@ dependencies = [ "staging-xcm 7.0.0", "substrate-prometheus-endpoint", "tempfile", - "thiserror", + "thiserror 1.0.69", "tracing-gum", "westend-runtime", "westend-runtime-constants 7.0.0", @@ -19600,7 +19858,7 @@ dependencies = [ name = "polkadot-statement-distribution" version = "7.0.0" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "assert_matches", "async-channel 1.9.0", "bitvec", @@ -19628,7 +19886,7 @@ dependencies = [ "sp-keystore 0.34.0", "sp-staking 26.0.0", "sp-tracing 16.0.0", - "thiserror", + "thiserror 1.0.69", "tracing-gum", ] @@ -19650,7 +19908,7 @@ dependencies = [ "async-trait", "bincode", "bitvec", - "clap 4.5.13", + "clap 4.5.21", "clap-num", "color-eyre", "colored", @@ -19752,7 +20010,7 @@ version = "1.0.0" dependencies = [ "assert_matches", "async-trait", - "clap 4.5.13", + "clap 4.5.21", "color-eyre", "futures", "futures-timer", @@ -19894,7 +20152,7 @@ dependencies = [ name = "polkadot-voter-bags" version = "7.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.21", "generate-bags", "sp-io 30.0.0", "westend-runtime", @@ -19905,7 +20163,7 @@ name = "polkadot-zombienet-sdk-tests" version = "0.1.0" dependencies = [ "anyhow", - "env_logger 0.11.3", + "env_logger 0.11.5", "log", "parity-scale-codec", "polkadot-primitives 7.0.0", @@ -20048,9 +20306,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c4fdfc49717fb9a196e74a5d28e0bc764eb394a2c803eb11133a31ac996c60c" dependencies = [ "polkavm-common 0.9.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -20060,21 +20318,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7855353a5a783dd5d09e3b915474bddf66575f5a3cf45dec8d1c5e051ba320dc" dependencies = [ "polkavm-common 0.10.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "polkavm-derive-impl" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12d2840cc62a0550156b1676fed8392271ddf2fab4a00661db56231424674624" +checksum = "2f2116a92e6e96220a398930f4c8a6cda1264206f3e2034fc9982bfd93f261f7" dependencies = [ "polkavm-common 0.18.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -20084,7 +20342,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ "polkavm-derive-impl 0.9.0", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -20094,7 +20352,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9324fe036de37c17829af233b46ef6b5562d4a0c09bb7fdb9f8378856dee30cf" dependencies = [ "polkavm-derive-impl 0.10.0", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -20103,8 +20361,8 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c16669ddc7433e34c1007d31080b80901e3e8e523cb9d4b441c3910cf9294b" dependencies = [ - "polkavm-derive-impl 0.18.0", - "syn 2.0.87", + "polkavm-derive-impl 0.18.1", + "syn 2.0.90", ] [[package]] @@ -20113,7 +20371,7 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c7be503e60cf56c0eb785f90aaba4b583b36bff00e93997d93fef97f9553c39" dependencies = [ - "gimli 0.28.0", + "gimli 0.28.1", "hashbrown 0.14.5", "log", "object 0.32.2", @@ -20128,10 +20386,10 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d704edfe7bdcc876784f19436d53d515b65eb07bc9a0fae77085d552c2dbbb5" dependencies = [ - "gimli 0.28.0", + "gimli 0.28.1", "hashbrown 0.14.5", "log", - "object 0.36.1", + "object 0.36.5", "polkavm-common 0.10.0", "regalloc2 0.9.3", "rustc-demangle", @@ -20147,7 +20405,7 @@ dependencies = [ "gimli 0.31.1", "hashbrown 0.14.5", "log", - "object 0.36.1", + "object 0.36.5", "polkavm-common 0.18.0", "regalloc2 0.9.3", "rustc-demangle", @@ -20189,16 +20447,17 @@ dependencies = [ [[package]] name = "polling" -version = "3.4.0" +version = "3.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30054e72317ab98eddd8561db0f6524df3367636884b7b21b703e4b280a84a14" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" dependencies = [ "cfg-if", "concurrent-queue", + "hermit-abi 0.4.0", "pin-project-lite", - "rustix 0.38.42", + "rustix 0.38.41", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -20208,27 +20467,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ "cpufeatures", - "opaque-debug 0.3.0", + "opaque-debug 0.3.1", "universal-hash", ] [[package]] name = "polyval" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", "cpufeatures", - "opaque-debug 0.3.0", + "opaque-debug 0.3.1", "universal-hash", ] [[package]] name = "portable-atomic" -version = "1.4.2" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f32154ba0af3a075eefa1eda8bb414ee928f62303a54ea85b8d6638ff1a6ee9e" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" [[package]] name = "portpicker" @@ -20246,23 +20505,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] -name = "pprof" -version = "0.12.1" +name = "pprof2" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978385d59daf9269189d052ca8a84c1acfd0715c0599a5d5188d4acc078ca46a" +checksum = "8961ed0a916b512e565f8070eb0dfa05773dd140160b45ac9a5ad339b557adeb" dependencies = [ "backtrace", "cfg-if", "findshlibs", "libc", "log", - "nix 0.26.4", + "nix 0.27.1", "once_cell", "parking_lot 0.12.3", "smallvec", "symbolic-demangle", "tempfile", - "thiserror", + "thiserror 2.0.4", ] [[package]] @@ -20280,9 +20539,12 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "predicates" @@ -20300,27 +20562,26 @@ dependencies = [ [[package]] name = "predicates" -version = "3.0.3" +version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09963355b9f467184c04017ced4a2ba2d75cbcb4e7462690d388233253d4b1a9" +checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" dependencies = [ "anstyle", "difflib", - "itertools 0.10.5", "predicates-core", ] [[package]] name = "predicates-core" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" +checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" [[package]] name = "predicates-tree" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" dependencies = [ "predicates-core", "termtree", @@ -20328,9 +20589,9 @@ dependencies = [ [[package]] name = "pretty_assertions" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ "diff", "yansi", @@ -20338,12 +20599,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.12" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ - "proc-macro2 1.0.86", - "syn 2.0.87", + "proc-macro2 1.0.92", + "syn 2.0.90", ] [[package]] @@ -20384,31 +20645,31 @@ checksum = "a172e6cc603231f2cf004232eabcecccc0da53ba576ab286ef7baa0cfc7927ad" dependencies = [ "coarsetime", "crossbeam-queue", - "derive_more 0.99.17", + "derive_more 0.99.18", "futures", "futures-timer", "nanorand", - "thiserror", + "thiserror 1.0.69", "tracing", ] [[package]] name = "proc-macro-crate" -version = "1.3.1" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" dependencies = [ - "once_cell", - "toml_edit 0.19.15", + "thiserror 1.0.69", + "toml 0.5.11", ] [[package]] name = "proc-macro-crate" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_edit 0.21.0", + "toml_edit 0.22.22", ] [[package]] @@ -20418,7 +20679,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "syn 1.0.109", "version_check", @@ -20430,7 +20691,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "version_check", ] @@ -20441,7 +20702,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", ] @@ -20452,26 +20713,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" dependencies = [ "proc-macro-error-attr2", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - [[package]] name = "proc-macro-warning" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b698b0b09d40e9b7c1a47b132d66a8b54bcd20583d9b6d06e4535e383b4405c" +checksum = "834da187cfe638ae8abb0203f0b33e5ccdb02a28e7199f2f47b3e2754f50edca" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -20485,9 +20740,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -20504,7 +20759,7 @@ dependencies = [ "hex", "lazy_static", "procfs-core", - "rustix 0.38.42", + "rustix 0.38.41", ] [[package]] @@ -20520,16 +20775,16 @@ dependencies = [ [[package]] name = "prometheus" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c" +checksum = "3d33c28a30771f7f96db69893f78b857f7450d7e0237e9c8fc6427a81bae7ed1" dependencies = [ "cfg-if", "fnv", "lazy_static", "memchr", "parking_lot 0.12.3", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -20550,28 +20805,28 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "prometheus-parse" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2aa5feb83bf4b2c8919eaf563f51dbab41183de73ba2353c0e03cd7b6bd892" +checksum = "811031bea65e5a401fb2e1f37d802cca6601e204ac463809a3189352d13b78a5" dependencies = [ "chrono", - "itertools 0.10.5", + "itertools 0.12.1", "once_cell", "regex", ] [[package]] name = "proptest" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" +checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" dependencies = [ "bit-set", "bit-vec", @@ -20609,19 +20864,19 @@ dependencies = [ [[package]] name = "prost" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2ecbe40f08db5c006b5764a2645f7f3f141ce756412ac9e1dd6087e6d32995" +checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f" dependencies = [ "bytes", - "prost-derive 0.13.2", + "prost-derive 0.13.3", ] [[package]] name = "prost-build" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8650aabb6c35b860610e9cff5dc1af886c9e25073b7b1712a68972af4281302" +checksum = "0c1318b19085f08681016926435853bbf7858f9c082d0999b80550ff5d9abe15" dependencies = [ "bytes", "heck 0.5.0", @@ -20631,10 +20886,10 @@ dependencies = [ "once_cell", "petgraph", "prettyplease", - "prost 0.13.2", + "prost 0.13.3", "prost-types", "regex", - "syn 2.0.87", + "syn 2.0.90", "tempfile", ] @@ -20646,7 +20901,7 @@ checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", "itertools 0.10.5", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "syn 1.0.109", ] @@ -20659,81 +20914,80 @@ checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", "itertools 0.12.1", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "prost-derive" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" +checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" dependencies = [ "anyhow", "itertools 0.13.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "prost-types" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60caa6738c7369b940c3d49246a8d1749323674c65cb13010134f5c9bad5b519" +checksum = "4759aa0d3a6232fb8dbdb97b61de2c20047c68aca932c7ed76da9d788508d670" dependencies = [ - "prost 0.13.2", + "prost 0.13.3", ] [[package]] name = "psm" -version = "0.1.21" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810" dependencies = [ "cc", ] [[package]] name = "pyroscope" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac8a53ce01af1087eaeee6ce7c4fbf50ea4040ab1825c0115c4bafa039644ba9" +checksum = "d3a5f63b0d2727095db59045e6a0ef3259b28b90d481ae88f0e3d866d0234ce8" dependencies = [ - "json", "libc", "libflate", "log", "names", "prost 0.11.9", - "reqwest 0.11.27", - "thiserror", + "reqwest 0.12.9", + "serde_json", + "thiserror 1.0.69", "url", "winapi", ] [[package]] name = "pyroscope_pprofrs" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f010b2a981a7f8449a650f25f309e520b5206ea2d89512dcb146aaa5518ff4" +checksum = "614a25777053da6bdca9d84a67892490b5a57590248dbdee3d7bf0716252af70" dependencies = [ "log", - "pprof", + "pprof2", "pyroscope", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "quanta" -version = "0.11.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17e662a7a8291a865152364c20c7abc5e60486ab2001e8ec10b24862de0b9ab" +checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5" dependencies = [ "crossbeam-utils", "libc", - "mach2", "once_cell", "raw-cpuid", "wasi", @@ -20774,7 +21028,7 @@ dependencies = [ "asynchronous-codec 0.7.0", "bytes", "quick-protobuf 0.8.1", - "thiserror", + "thiserror 1.0.69", "unsigned-varint 0.8.0", ] @@ -20802,51 +21056,55 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" +checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" dependencies = [ "bytes", "futures-io", "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.0.0", - "rustls 0.23.18", - "socket2 0.5.7", - "thiserror", + "rustc-hash 2.1.0", + "rustls 0.23.19", + "socket2 0.5.8", + "thiserror 2.0.4", "tokio", "tracing", ] [[package]] name = "quinn-proto" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" +checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" dependencies = [ "bytes", + "getrandom", "rand", "ring 0.17.8", - "rustc-hash 2.0.0", - "rustls 0.23.18", + "rustc-hash 2.1.0", + "rustls 0.23.19", + "rustls-pki-types", "slab", - "thiserror", + "thiserror 2.0.4", "tinyvec", "tracing", + "web-time", ] [[package]] name = "quinn-udp" -version = "0.5.4" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" +checksum = "7d5a626c6807713b15cac82a6acaccd6043c9a5408c24baae07611fec3f243da" dependencies = [ + "cfg_aliases 0.2.1", "libc", "once_cell", - "socket2 0.5.7", + "socket2 0.5.8", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -20864,7 +21122,7 @@ version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", ] [[package]] @@ -20940,11 +21198,11 @@ dependencies = [ [[package]] name = "raw-cpuid" -version = "10.7.0" +version = "11.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" +checksum = "1ab240315c661615f2ee9f0f2cd32d5a7343a84d5ebcccb99d46e6637565e7b0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", ] [[package]] @@ -21028,31 +21286,22 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.8" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", - "redox_syscall 0.2.16", - "thiserror", + "libredox", + "thiserror 1.0.69", ] [[package]] @@ -21061,10 +21310,10 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87413ebb313323d431e85d0afc5a68222aaed972843537cbfe5f061cf1b4bcab" dependencies = [ - "derive_more 0.99.17", + "derive_more 0.99.18", "fs-err", "static_init", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -21082,9 +21331,9 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -21120,7 +21369,7 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "regex-syntax 0.8.5", ] @@ -21135,15 +21384,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" - -[[package]] -name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -21164,9 +21407,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "relative-path" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e898588f33fdd5b9420719948f9f2a32c922a246964576f71ba7f24f80610fbc" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "relay-substrate-client" @@ -21204,7 +21447,7 @@ dependencies = [ "sp-trie 29.0.0", "sp-version 29.0.0", "staging-xcm 7.0.0", - "thiserror", + "thiserror 1.0.69", "tokio", ] @@ -21229,7 +21472,7 @@ dependencies = [ "sp-tracing 16.0.0", "substrate-prometheus-endpoint", "sysinfo", - "thiserror", + "thiserror 1.0.69", "time", "tokio", ] @@ -21238,7 +21481,7 @@ dependencies = [ name = "remote-ext-tests-bags-list" version = "1.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.21", "frame-system 28.0.0", "log", "pallet-bags-list-remote-tests", @@ -21261,10 +21504,9 @@ dependencies = [ "futures-core", "futures-util", "h2 0.3.26", - "http 0.2.9", - "http-body 0.4.5", - "hyper 0.14.29", - "hyper-rustls 0.24.2", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.31", "hyper-tls", "ipnet", "js-sys", @@ -21274,22 +21516,19 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.7", - "rustls-pemfile 1.0.3", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", "sync_wrapper 0.1.2", - "system-configuration", + "system-configuration 0.5.1", "tokio", "tokio-native-tls", - "tokio-rustls 0.24.1", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.25.2", "winreg", ] @@ -21305,9 +21544,9 @@ dependencies = [ "futures-core", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", - "hyper 1.3.1", + "hyper 1.5.1", "hyper-rustls 0.27.3", "hyper-util", "ipnet", @@ -21318,13 +21557,13 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.18", - "rustls-pemfile 2.0.0", + "rustls 0.23.19", + "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.1", + "sync_wrapper 1.0.2", "tokio", "tokio-rustls 0.26.0", "tower-service", @@ -21332,7 +21571,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.26.3", + "webpki-roots 0.26.7", "windows-registry", ] @@ -21353,7 +21592,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ "hmac 0.12.1", - "subtle 2.5.0", + "subtle 2.6.1", ] [[package]] @@ -21361,12 +21600,12 @@ name = "ring" version = "0.1.0" source = "git+https://github.com/w3f/ring-proof?rev=665f5f5#665f5f51af5734c7b6d90b985dd6861d4c5b4752" dependencies = [ - "ark-ec", + "ark-ec 0.4.2", "ark-ff 0.4.2", - "ark-poly", + "ark-poly 0.4.2", "ark-serialize 0.4.2", "ark-std 0.4.0", - "arrayvec 0.7.4", + "arrayvec 0.7.6", "blake2 0.10.6", "common", "fflonk", @@ -21690,20 +21929,20 @@ checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" [[package]] name = "rpassword" -version = "7.2.0" +version = "7.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6678cf63ab3491898c0d021b493c94c9b221d91295294a2a5746eacbe5928322" +checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f" dependencies = [ "libc", "rtoolbox", - "winapi", + "windows-sys 0.48.0", ] [[package]] name = "rsa" -version = "0.9.5" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af6c4b23d99685a1408194da11270ef8e9809aff951cc70ec9b17350b087e474" +checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" dependencies = [ "const-oid", "digest 0.10.7", @@ -21715,7 +21954,7 @@ dependencies = [ "rand_core 0.6.4", "signature", "spki", - "subtle 2.5.0", + "subtle 2.6.1", "zeroize", ] @@ -21728,7 +21967,7 @@ dependencies = [ "futures", "futures-timer", "rstest_macros", - "rustc_version 0.4.0", + "rustc_version 0.4.1", ] [[package]] @@ -21739,38 +21978,41 @@ checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" dependencies = [ "cfg-if", "glob", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "regex", "relative-path", - "rustc_version 0.4.0", - "syn 2.0.87", + "rustc_version 0.4.1", + "syn 2.0.90", "unicode-ident", ] [[package]] name = "rtnetlink" -version = "0.10.1" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322c53fd76a18698f1c27381d58091de3a043d356aa5bd0d510608b565f469a0" +checksum = "7a552eb82d19f38c3beed3f786bd23aa434ceb9ac43ab44419ca6d67a7e186c0" dependencies = [ "futures", "log", + "netlink-packet-core", "netlink-packet-route", + "netlink-packet-utils", "netlink-proto", - "nix 0.24.3", - "thiserror", + "netlink-sys", + "nix 0.26.4", + "thiserror 1.0.69", "tokio", ] [[package]] name = "rtoolbox" -version = "0.0.1" +version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034e22c514f5c0cb8a10ff341b9b048b5ceb21591f31c8f44c43b960f9b3524a" +checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e" dependencies = [ "libc", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -21805,9 +22047,9 @@ checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" @@ -21817,9 +22059,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" [[package]] name = "rustc-hex" @@ -21847,11 +22089,11 @@ dependencies = [ [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.18", + "semver 1.0.23", ] [[package]] @@ -21865,9 +22107,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.15" +version = "0.36.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c37f1bd5ef1b5422177b7646cba67430579cfe2ace80f284fee876bca52ad941" +checksum = "305efbd14fde4139eb501df5f136994bb520b033fa9fbdce287507dc23b8c7ed" dependencies = [ "bitflags 1.3.2", "errno", @@ -21879,9 +22121,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.23" +version = "0.37.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" dependencies = [ "bitflags 1.3.2", "errno", @@ -21893,15 +22135,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.42" +version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ "bitflags 2.6.0", "errno", "libc", "linux-raw-sys 0.4.14", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -21917,13 +22159,13 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.7" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring 0.16.20", - "rustls-webpki 0.101.4", + "ring 0.17.8", + "rustls-webpki 0.101.7", "sct", ] @@ -21937,22 +22179,22 @@ dependencies = [ "ring 0.17.8", "rustls-pki-types", "rustls-webpki 0.102.8", - "subtle 2.5.0", + "subtle 2.6.1", "zeroize", ] [[package]] name = "rustls" -version = "0.23.18" +version = "0.23.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f" +checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" dependencies = [ "log", "once_cell", "ring 0.17.8", "rustls-pki-types", "rustls-webpki 0.102.8", - "subtle 2.5.0", + "subtle 2.6.1", "zeroize", ] @@ -21963,53 +22205,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", - "rustls-pemfile 1.0.3", + "rustls-pemfile 1.0.4", "schannel", - "security-framework", + "security-framework 2.11.1", ] [[package]] name = "rustls-native-certs" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" dependencies = [ "openssl-probe", - "rustls-pemfile 2.0.0", + "rustls-pemfile 2.2.0", "rustls-pki-types", "schannel", - "security-framework", + "security-framework 2.11.1", ] [[package]] name = "rustls-native-certs" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" dependencies = [ "openssl-probe", - "rustls-pemfile 2.0.0", "rustls-pki-types", "schannel", - "security-framework", + "security-framework 3.0.1", ] [[package]] name = "rustls-pemfile" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ "base64 0.21.7", ] [[package]] name = "rustls-pemfile" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e4980fa29e4c4b212ffb3db068a564cbf560e51d3944b7c88bd8bf5bec64f4" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64 0.21.7", "rustls-pki-types", ] @@ -22018,42 +22258,45 @@ name = "rustls-pki-types" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +dependencies = [ + "web-time", +] [[package]] name = "rustls-platform-verifier" -version = "0.3.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5f0d26fa1ce3c790f9590868f0109289a044acb954525f933e2aa3b871c157d" +checksum = "afbb878bdfdf63a336a5e63561b1835e7a8c91524f51621db870169eac84b490" dependencies = [ - "core-foundation", + "core-foundation 0.9.4", "core-foundation-sys", "jni", "log", "once_cell", - "rustls 0.23.18", - "rustls-native-certs 0.7.0", + "rustls 0.23.19", + "rustls-native-certs 0.7.3", "rustls-platform-verifier-android", "rustls-webpki 0.102.8", - "security-framework", + "security-framework 2.11.1", "security-framework-sys", - "webpki-roots 0.26.3", + "webpki-roots 0.26.7", "winapi", ] [[package]] name = "rustls-platform-verifier-android" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84e217e7fdc8466b5b35d30f8c0a30febd29173df4a3a0c2115d306b9c4117ad" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.101.4" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.16.20", - "untrusted 0.7.1", + "ring 0.17.8", + "untrusted 0.9.0", ] [[package]] @@ -22069,9 +22312,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] name = "rusty-fork" @@ -22103,7 +22346,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5174a470eeb535a721ae9fdd6e291c2411a906b96592182d05217591d5c5cf7b" dependencies = [ "byteorder", - "derive_more 0.99.17", + "derive_more 0.99.18", ] [[package]] @@ -22119,9 +22362,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "safe-mix" @@ -22134,9 +22377,9 @@ dependencies = [ [[package]] name = "safe_arch" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f398075ce1e6a179b46f51bd88d0598b92b00d3551f1a2d4ac49e771b56ac354" +checksum = "c3460605018fdc9612bce72735cba0d27efbcd9904780d44c7e3a9948f96148a" dependencies = [ "bytemuck", ] @@ -22166,7 +22409,7 @@ dependencies = [ "log", "sp-core 28.0.0", "sp-wasm-interface 20.0.0", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -22178,7 +22421,7 @@ dependencies = [ "log", "sp-core 33.0.1", "sp-wasm-interface 21.0.1", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -22190,7 +22433,7 @@ dependencies = [ "log", "sp-core 34.0.0", "sp-wasm-interface 21.0.1", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -22203,7 +22446,7 @@ dependencies = [ "ip_network", "linked_hash_set", "log", - "multihash 0.19.1", + "multihash 0.19.2", "parity-scale-codec", "prost 0.12.6", "prost-build", @@ -22221,7 +22464,7 @@ dependencies = [ "sp-tracing 16.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime-client", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -22270,10 +22513,10 @@ name = "sc-chain-spec" version = "28.0.0" dependencies = [ "array-bytes", - "clap 4.5.13", + "clap 4.5.21", "docify", "log", - "memmap2 0.9.3", + "memmap2 0.9.5", "parity-scale-codec", "regex", "sc-chain-spec-derive", @@ -22301,10 +22544,10 @@ dependencies = [ name = "sc-chain-spec-derive" version = "11.0.0" dependencies = [ - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -22313,7 +22556,7 @@ version = "0.36.0" dependencies = [ "array-bytes", "chrono", - "clap 4.5.13", + "clap 4.5.21", "fdlimit", "futures", "futures-timer", @@ -22347,7 +22590,7 @@ dependencies = [ "sp-tracing 16.0.0", "sp-version 29.0.0", "tempfile", - "thiserror", + "thiserror 1.0.69", "tokio", ] @@ -22377,7 +22620,7 @@ dependencies = [ "sp-trie 29.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -22434,7 +22677,7 @@ dependencies = [ "sp-state-machine 0.35.0", "sp-test-primitives", "substrate-prometheus-endpoint", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -22471,7 +22714,7 @@ dependencies = [ "substrate-prometheus-endpoint", "substrate-test-runtime-client", "tempfile", - "thiserror", + "thiserror 1.0.69", "tokio", ] @@ -22513,7 +22756,7 @@ dependencies = [ "sp-tracing 16.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime-client", - "thiserror", + "thiserror 1.0.69", "tokio", ] @@ -22541,7 +22784,7 @@ dependencies = [ "sp-keystore 0.34.0", "sp-runtime 31.0.1", "substrate-test-runtime-client", - "thiserror", + "thiserror 1.0.69", "tokio", ] @@ -22584,7 +22827,7 @@ dependencies = [ "substrate-prometheus-endpoint", "substrate-test-runtime-client", "tempfile", - "thiserror", + "thiserror 1.0.69", "tokio", "wasm-timer", ] @@ -22607,7 +22850,7 @@ dependencies = [ "sp-core 28.0.0", "sp-runtime 31.0.1", "substrate-test-runtime-client", - "thiserror", + "thiserror 1.0.69", "tokio", ] @@ -22669,7 +22912,7 @@ dependencies = [ "sp-tracing 16.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime-client", - "thiserror", + "thiserror 1.0.69", "tokio", ] @@ -22693,7 +22936,7 @@ dependencies = [ "sp-keyring 31.0.0", "sp-runtime 31.0.1", "substrate-test-runtime-client", - "thiserror", + "thiserror 1.0.69", "tokio", ] @@ -22731,7 +22974,7 @@ dependencies = [ "substrate-prometheus-endpoint", "substrate-test-runtime-client", "substrate-test-runtime-transaction-pool", - "thiserror", + "thiserror 1.0.69", "tokio", ] @@ -22756,7 +22999,7 @@ dependencies = [ "sp-inherents 26.0.0", "sp-runtime 31.0.1", "substrate-prometheus-endpoint", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -22817,7 +23060,7 @@ dependencies = [ "substrate-test-runtime", "tempfile", "tracing", - "tracing-subscriber 0.3.18", + "tracing-subscriber", "wat", ] @@ -22877,7 +23120,7 @@ dependencies = [ "sc-allocator 23.0.0", "sp-maybe-compressed-blob 11.0.0", "sp-wasm-interface 20.0.0", - "thiserror", + "thiserror 1.0.69", "wasm-instrument", ] @@ -22891,7 +23134,7 @@ dependencies = [ "sc-allocator 28.0.0", "sp-maybe-compressed-blob 11.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-wasm-interface 21.0.1", - "thiserror", + "thiserror 1.0.69", "wasm-instrument", ] @@ -22905,7 +23148,7 @@ dependencies = [ "sc-allocator 29.0.0", "sp-maybe-compressed-blob 11.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-wasm-interface 21.0.1", - "thiserror", + "thiserror 1.0.69", "wasm-instrument", ] @@ -22955,7 +23198,7 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", "paste", - "rustix 0.36.15", + "rustix 0.36.17", "sc-allocator 23.0.0", "sc-executor-common 0.29.0", "sc-runtime-test", @@ -22978,7 +23221,7 @@ dependencies = [ "libc", "log", "parking_lot 0.12.3", - "rustix 0.36.15", + "rustix 0.36.17", "sc-allocator 28.0.0", "sc-executor-common 0.34.0", "sp-runtime-interface 27.0.0", @@ -22997,7 +23240,7 @@ dependencies = [ "libc", "log", "parking_lot 0.12.3", - "rustix 0.36.15", + "rustix 0.36.17", "sc-allocator 29.0.0", "sc-executor-common 0.35.0", "sp-runtime-interface 28.0.0", @@ -23032,7 +23275,7 @@ dependencies = [ "sp-core 28.0.0", "sp-keystore 0.34.0", "tempfile", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -23040,14 +23283,14 @@ name = "sc-mixnet" version = "0.4.0" dependencies = [ "array-bytes", - "arrayvec 0.7.4", + "arrayvec 0.7.6", "blake2 0.10.6", "bytes", "futures", "futures-timer", "log", "mixnet", - "multiaddr 0.18.1", + "multiaddr 0.18.2", "parity-scale-codec", "parking_lot 0.12.3", "sc-client-api", @@ -23060,7 +23303,7 @@ dependencies = [ "sp-keystore 0.34.0", "sp-mixnet 0.4.0", "sp-runtime 31.0.1", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -23118,7 +23361,7 @@ dependencies = [ "substrate-test-runtime", "substrate-test-runtime-client", "tempfile", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", "tokio-test", @@ -23187,7 +23430,7 @@ dependencies = [ "sp-blockchain", "sp-core 28.0.0", "sp-runtime 31.0.1", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -23244,7 +23487,7 @@ dependencies = [ "sp-tracing 16.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime-client", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", ] @@ -23309,11 +23552,11 @@ dependencies = [ "libp2p-kad", "litep2p", "log", - "multiaddr 0.18.1", - "multihash 0.19.1", + "multiaddr 0.18.2", + "multihash 0.19.2", "quickcheck", "rand", - "thiserror", + "thiserror 1.0.69", "zeroize", ] @@ -23328,7 +23571,7 @@ dependencies = [ "futures", "futures-timer", "http-body-util", - "hyper 1.3.1", + "hyper 1.5.1", "hyper-rustls 0.27.3", "hyper-util", "log", @@ -23337,7 +23580,7 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", "rand", - "rustls 0.23.18", + "rustls 0.23.19", "sc-block-builder", "sc-client-api", "sc-client-db", @@ -23425,7 +23668,7 @@ dependencies = [ "sp-rpc", "sp-runtime 31.0.1", "sp-version 29.0.0", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -23438,7 +23681,7 @@ dependencies = [ "governor", "http 1.1.0", "http-body-util", - "hyper 1.3.1", + "hyper 1.5.1", "ip_network", "jsonrpsee", "log", @@ -23491,7 +23734,7 @@ dependencies = [ "substrate-test-runtime", "substrate-test-runtime-client", "substrate-test-runtime-transaction-pool", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", ] @@ -23523,7 +23766,7 @@ dependencies = [ "sp-version 29.0.0", "sp-wasm-interface 20.0.0", "subxt", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -23585,7 +23828,7 @@ dependencies = [ "substrate-test-runtime", "substrate-test-runtime-client", "tempfile", - "thiserror", + "thiserror 1.0.69", "tokio", "tracing", "tracing-futures", @@ -23661,11 +23904,11 @@ dependencies = [ name = "sc-storage-monitor" version = "0.16.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.21", "fs4", "log", "sp-core 28.0.0", - "thiserror", + "thiserror 1.0.69", "tokio", ] @@ -23684,14 +23927,14 @@ dependencies = [ "serde_json", "sp-blockchain", "sp-runtime 31.0.1", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "sc-sysinfo" version = "27.0.0" dependencies = [ - "derive_more 0.99.17", + "derive_more 0.99.18", "futures", "libc", "log", @@ -23722,7 +23965,7 @@ dependencies = [ "sc-utils", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "wasm-timer", ] @@ -23749,20 +23992,20 @@ dependencies = [ "sp-rpc", "sp-runtime 31.0.1", "sp-tracing 16.0.0", - "thiserror", + "thiserror 1.0.69", "tracing", - "tracing-log 0.2.0", - "tracing-subscriber 0.3.18", + "tracing-log", + "tracing-subscriber", ] [[package]] name = "sc-tracing-proc-macro" version = "11.0.0" dependencies = [ - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -23798,7 +24041,7 @@ dependencies = [ "substrate-test-runtime", "substrate-test-runtime-client", "substrate-test-runtime-transaction-pool", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", ] @@ -23816,7 +24059,7 @@ dependencies = [ "sp-blockchain", "sp-core 28.0.0", "sp-runtime 31.0.1", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -23851,7 +24094,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e98f3262c250d90e700bb802eb704e1f841e03331c2eb815e46516c4edbf5b27" dependencies = [ - "derive_more 0.99.17", + "derive_more 0.99.18", "parity-scale-codec", "scale-bits", "scale-type-resolver", @@ -23880,9 +24123,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ed9401effa946b493f9f84dc03714cca98119b230497df6f3df6b84a2b03648" dependencies = [ "darling", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -23907,10 +24150,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "102fbc6236de6c53906c0b262f12c7aa69c2bdc604862c12728f5f4d370bc137" dependencies = [ "darling", - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -23933,10 +24176,10 @@ version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6630024bf739e2179b91fb424b28898baf819414262c5d376677dbff1fe7ebf" dependencies = [ - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -23955,11 +24198,11 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc4c70c7fea2eef1740f0081d3fe385d8bee1eef11e9272d3bec7dc8e5438e0" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "scale-info", - "syn 2.0.87", - "thiserror", + "syn 2.0.90", + "thiserror 1.0.69", ] [[package]] @@ -23984,18 +24227,18 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "schemars" -version = "0.8.13" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763f8cd0d4c71ed8389c90cb8100cba87e763bd01a8e614d4f0af97bcd50a161" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" dependencies = [ "dyn-clone", "schemars_derive", @@ -24005,14 +24248,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.13" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0f696e21e10fa546b7ffb1c9672c6de8fbc7a81acf59524386d8639bf12737" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "serde_derive_internals", - "syn 1.0.109", + "syn 2.0.90", ] [[package]] @@ -24033,7 +24276,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "844b7645371e6ecdf61ff246ba1958c29e802881a749ae3fb1993675d210d28d" dependencies = [ "arrayref", - "arrayvec 0.7.4", + "arrayvec 0.7.6", "curve25519-dalek-ng", "merlin", "rand_core 0.6.4", @@ -24050,14 +24293,14 @@ checksum = "8de18f6d8ba0aad7045f5feae07ec29899c1112584a38509a84ad7b04451eaa0" dependencies = [ "aead", "arrayref", - "arrayvec 0.7.4", + "arrayvec 0.7.6", "curve25519-dalek 4.1.3", "getrandom_or_panic", "merlin", "rand_core 0.6.4", "serde_bytes", "sha2 0.10.8", - "subtle 2.5.0", + "subtle 2.6.1", "zeroize", ] @@ -24093,12 +24336,12 @@ dependencies = [ [[package]] name = "sct" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.16.20", - "untrusted 0.7.1", + "ring 0.17.8", + "untrusted 0.9.0", ] [[package]] @@ -24112,7 +24355,7 @@ dependencies = [ "generic-array 0.14.7", "pkcs8", "serdect", - "subtle 2.5.0", + "subtle 2.6.1", "zeroize", ] @@ -24184,23 +24427,36 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.6.0", - "core-foundation", + "core-foundation 0.9.4", "core-foundation-sys", "libc", "num-bigint", "security-framework-sys", ] +[[package]] +name = "security-framework" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1415a607e92bec364ea2cf9264646dcce0f91e6d65281bd6f2819cca3bf39c8" +dependencies = [ + "bitflags 2.6.0", + "core-foundation 0.10.0", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + [[package]] name = "security-framework-sys" -version = "2.11.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" dependencies = [ "core-foundation-sys", "libc", @@ -24230,14 +24486,14 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" dependencies = [ - "semver-parser 0.10.2", + "semver-parser 0.10.3", ] [[package]] name = "semver" -version = "1.0.18" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" dependencies = [ "serde", ] @@ -24250,9 +24506,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "semver-parser" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" dependencies = [ "pest", ] @@ -24271,9 +24527,9 @@ checksum = "f97841a747eef040fcd2e7b3b9a220a7205926e60488e673d9e4926d27772ce5" [[package]] name = "serde" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] @@ -24299,33 +24555,33 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.12" +version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "serde_derive_internals" -version = "0.26.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 1.0.109", + "syn 2.0.90", ] [[package]] @@ -24339,9 +24595,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "indexmap 2.7.0", "itoa", @@ -24352,9 +24608,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -24404,7 +24660,7 @@ dependencies = [ "cfg-if", "cpufeatures", "digest 0.9.0", - "opaque-debug 0.3.0", + "opaque-debug 0.3.1", ] [[package]] @@ -24428,7 +24684,7 @@ dependencies = [ "cfg-if", "cpufeatures", "digest 0.9.0", - "opaque-debug 0.3.0", + "opaque-debug 0.3.1", ] [[package]] @@ -24451,7 +24707,7 @@ dependencies = [ "block-buffer 0.9.0", "digest 0.9.0", "keccak", - "opaque-debug 0.3.0", + "opaque-debug 0.3.1", ] [[package]] @@ -24476,9 +24732,9 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] @@ -24489,30 +24745,20 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "signal-hook" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" -dependencies = [ - "libc", - "signal-hook-registry", -] - [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] [[package]] name = "signature" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest 0.10.7", "rand_core 0.6.4", @@ -24520,9 +24766,9 @@ dependencies = [ [[package]] name = "simba" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae" +checksum = "b3a386a501cd104797982c15ae17aafe8b9261315b5d07e3ec803f2ea26be0fa" dependencies = [ "approx", "num-complex", @@ -24602,9 +24848,9 @@ dependencies = [ [[package]] name = "slotmap" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" dependencies = [ "version_check", ] @@ -24640,8 +24886,8 @@ dependencies = [ "async-fs 1.6.0", "async-io 1.13.0", "async-lock 2.8.0", - "async-net 1.7.0", - "async-process 1.7.0", + "async-net 1.8.0", + "async-process 1.8.1", "blocking", "futures-lite 1.13.0", ] @@ -24652,24 +24898,15 @@ version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f" dependencies = [ - "async-channel 2.3.0", + "async-channel 2.3.1", "async-executor", "async-fs 2.1.2", - "async-io 2.3.3", + "async-io 2.4.0", "async-lock 3.4.0", "async-net 2.0.0", "async-process 2.3.0", "blocking", - "futures-lite 2.3.0", -] - -[[package]] -name = "smol_str" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74212e6bbe9a4352329b2f68ba3130c15a3f26fe88ff22dbdc6cdd58fa85e99c" -dependencies = [ - "serde", + "futures-lite 2.5.0", ] [[package]] @@ -24678,7 +24915,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0bb30cf57b7b5f6109ce17c3164445e2d6f270af2cb48f6e4d31c2967c9a9f5" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "async-lock 2.8.0", "atomic-take", "base64 0.21.7", @@ -24687,7 +24924,7 @@ dependencies = [ "bs58", "chacha20", "crossbeam-queue", - "derive_more 0.99.17", + "derive_more 0.99.18", "ed25519-zebra 4.0.3", "either", "event-listener 2.5.3", @@ -24732,7 +24969,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "966e72d77a3b2171bb7461d0cb91f43670c63558c62d7cf42809cae6c8b6b818" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "async-lock 3.4.0", "atomic-take", "base64 0.22.1", @@ -24741,12 +24978,12 @@ dependencies = [ "bs58", "chacha20", "crossbeam-queue", - "derive_more 0.99.17", + "derive_more 0.99.18", "ed25519-zebra 4.0.3", "either", "event-listener 5.3.1", "fnv", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "futures-util", "hashbrown 0.14.5", "hex", @@ -24773,7 +25010,7 @@ dependencies = [ "siphasher 1.0.1", "slab", "smallvec", - "soketto 0.8.0", + "soketto 0.8.1", "twox-hash", "wasmi 0.32.3", "x25519-dalek", @@ -24790,7 +25027,7 @@ dependencies = [ "async-lock 2.8.0", "base64 0.21.7", "blake2-rfc", - "derive_more 0.99.17", + "derive_more 0.99.18", "either", "event-listener 2.5.3", "fnv", @@ -24801,7 +25038,7 @@ dependencies = [ "hex", "itertools 0.11.0", "log", - "lru 0.11.0", + "lru 0.11.1", "no-std-net", "parking_lot 0.12.3", "pin-project", @@ -24822,23 +25059,23 @@ version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a33b06891f687909632ce6a4e3fd7677b24df930365af3d0bcb078310129f3f" dependencies = [ - "async-channel 2.3.0", + "async-channel 2.3.1", "async-lock 3.4.0", "base64 0.22.1", "blake2-rfc", "bs58", - "derive_more 0.99.17", + "derive_more 0.99.18", "either", "event-listener 5.3.1", "fnv", "futures-channel", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "futures-util", "hashbrown 0.14.5", "hex", "itertools 0.13.0", "log", - "lru 0.12.3", + "lru 0.12.5", "parking_lot 0.12.3", "pin-project", "rand", @@ -24854,9 +25091,9 @@ dependencies = [ [[package]] name = "snap" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e9f0ab6ef7eb7353d9119c170a436d1bf248eea575ac42d19d12f4e34130831" +checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" [[package]] name = "snow" @@ -24870,9 +25107,9 @@ dependencies = [ "curve25519-dalek 4.1.3", "rand_core 0.6.4", "ring 0.17.8", - "rustc_version 0.4.0", + "rustc_version 0.4.1", "sha2 0.10.8", - "subtle 2.5.0", + "subtle 2.6.1", ] [[package]] @@ -25449,11 +25686,11 @@ dependencies = [ "frame-system 38.0.0", "pallet-balances 39.0.0", "pallet-collator-selection 19.0.0", - "pallet-message-queue 41.0.1", + "pallet-message-queue 41.0.2", "pallet-session 38.0.0", "pallet-timestamp 37.0.0", "pallet-utility 38.0.0", - "pallet-xcm 17.0.0", + "pallet-xcm 17.0.1", "parachains-runtimes-test-utils 17.0.0", "parity-scale-codec", "snowbridge-core 0.10.0", @@ -25496,9 +25733,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ "libc", "winapi", @@ -25506,9 +25743,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -25531,9 +25768,9 @@ dependencies = [ [[package]] name = "soketto" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37468c595637c10857701c990f93a40ce0e357cedb0953d1c26c8d8027f9bb53" +checksum = "2e859df029d160cb88608f5d7df7fb4753fd20fdfb4de5644f3d8b8440841721" dependencies = [ "base64 0.22.1", "bytes", @@ -25549,7 +25786,7 @@ dependencies = [ name = "solochain-template-node" version = "0.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.21", "frame-benchmarking-cli", "frame-metadata-hash-extension 0.1.0", "frame-system 28.0.0", @@ -25647,7 +25884,7 @@ dependencies = [ "sp-test-primitives", "sp-trie 29.0.0", "sp-version 29.0.0", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -25670,7 +25907,7 @@ dependencies = [ "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-trie 35.0.0", "sp-version 35.0.0", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -25693,7 +25930,7 @@ dependencies = [ "sp-state-machine 0.43.0", "sp-trie 37.0.0", "sp-version 37.0.0", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -25704,10 +25941,10 @@ dependencies = [ "assert_matches", "blake2 0.10.6", "expander", - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -25719,10 +25956,10 @@ dependencies = [ "Inflector", "blake2 0.10.6", "expander", - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -25734,10 +25971,10 @@ dependencies = [ "Inflector", "blake2 0.10.6", "expander", - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -25877,7 +26114,7 @@ version = "0.4.2" source = "git+https://github.com/paritytech/arkworks-substrate#caa2eed74beb885dd07c7db5f916f2281dad818f" dependencies = [ "ark-bls12-381-ext", - "sp-crypto-ec-utils 0.4.1", + "sp-crypto-ec-utils 0.10.0 (git+https://github.com/paritytech/polkadot-sdk)", ] [[package]] @@ -25886,7 +26123,7 @@ version = "0.4.2" source = "git+https://github.com/paritytech/arkworks-substrate#caa2eed74beb885dd07c7db5f916f2281dad818f" dependencies = [ "ark-ed-on-bls12-381-bandersnatch-ext", - "sp-crypto-ec-utils 0.4.1", + "sp-crypto-ec-utils 0.10.0 (git+https://github.com/paritytech/polkadot-sdk)", ] [[package]] @@ -25947,7 +26184,7 @@ dependencies = [ "sp-database", "sp-runtime 31.0.1", "sp-state-machine 0.35.0", - "thiserror", + "thiserror 1.0.69", "tracing", ] @@ -25963,7 +26200,7 @@ dependencies = [ "sp-runtime 31.0.1", "sp-state-machine 0.35.0", "sp-test-primitives", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -26212,7 +26449,7 @@ dependencies = [ "sp-storage 19.0.0", "ss58-registry", "substrate-bip39 0.4.7", - "thiserror", + "thiserror 1.0.69", "tracing", "w3f-bls", "zeroize", @@ -26259,7 +26496,7 @@ dependencies = [ "sp-storage 21.0.0", "ss58-registry", "substrate-bip39 0.6.0", - "thiserror", + "thiserror 1.0.69", "tracing", "w3f-bls", "zeroize", @@ -26306,7 +26543,7 @@ dependencies = [ "sp-storage 21.0.0", "ss58-registry", "substrate-bip39 0.6.0", - "thiserror", + "thiserror 1.0.69", "tracing", "w3f-bls", "zeroize", @@ -26353,7 +26590,7 @@ dependencies = [ "sp-storage 21.0.0", "ss58-registry", "substrate-bip39 0.6.0", - "thiserror", + "thiserror 1.0.69", "tracing", "w3f-bls", "zeroize", @@ -26393,8 +26630,7 @@ dependencies = [ [[package]] name = "sp-crypto-ec-utils" -version = "0.4.1" -source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" +version = "0.10.0" dependencies = [ "ark-bls12-377", "ark-bls12-377-ext", @@ -26402,19 +26638,19 @@ dependencies = [ "ark-bls12-381-ext", "ark-bw6-761", "ark-bw6-761-ext", - "ark-ec", + "ark-ec 0.4.2", "ark-ed-on-bls12-377", "ark-ed-on-bls12-377-ext", "ark-ed-on-bls12-381-bandersnatch", "ark-ed-on-bls12-381-bandersnatch-ext", - "ark-scale 0.0.11", - "sp-runtime-interface 17.0.0", - "sp-std 8.0.0", + "ark-scale", + "sp-runtime-interface 24.0.0", ] [[package]] name = "sp-crypto-ec-utils" version = "0.10.0" +source = "git+https://github.com/paritytech/polkadot-sdk#896c81440c1dd169bd2f5e65aba46eca228609f8" dependencies = [ "ark-bls12-377", "ark-bls12-377-ext", @@ -26422,13 +26658,13 @@ dependencies = [ "ark-bls12-381-ext", "ark-bw6-761", "ark-bw6-761-ext", - "ark-ec", + "ark-ec 0.4.2", "ark-ed-on-bls12-377", "ark-ed-on-bls12-377-ext", "ark-ed-on-bls12-381-bandersnatch", "ark-ed-on-bls12-381-bandersnatch-ext", - "ark-scale 0.0.12", - "sp-runtime-interface 24.0.0", + "ark-scale", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk)", ] [[package]] @@ -26443,12 +26679,12 @@ dependencies = [ "ark-bls12-381-ext", "ark-bw6-761", "ark-bw6-761-ext", - "ark-ec", + "ark-ec 0.4.2", "ark-ed-on-bls12-377", "ark-ed-on-bls12-377-ext", "ark-ed-on-bls12-381-bandersnatch", "ark-ed-on-bls12-381-bandersnatch-ext", - "ark-scale 0.0.12", + "ark-scale", "sp-runtime-interface 28.0.0", ] @@ -26486,7 +26722,7 @@ version = "0.1.0" dependencies = [ "quote 1.0.37", "sp-crypto-hashing 0.1.0", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -26497,7 +26733,7 @@ checksum = "b85d0f1f1e44bd8617eb2a48203ee854981229e3e79e6f468c7175d5fd37489b" dependencies = [ "quote 1.0.37", "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -26510,52 +26746,51 @@ dependencies = [ [[package]] name = "sp-debug-derive" -version = "8.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" +version = "14.0.0" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "sp-debug-derive" version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d09fa0a5f7299fb81ee25ae3853d26200f7a348148aed6de76be905c007dbe" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "sp-debug-derive" version = "14.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d09fa0a5f7299fb81ee25ae3853d26200f7a348148aed6de76be905c007dbe" +source = "git+https://github.com/paritytech/polkadot-sdk#896c81440c1dd169bd2f5e65aba46eca228609f8" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "sp-externalities" -version = "0.19.0" -source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" +version = "0.25.0" dependencies = [ "environmental", "parity-scale-codec", - "sp-std 8.0.0", - "sp-storage 13.0.0", + "sp-storage 19.0.0", ] [[package]] name = "sp-externalities" version = "0.25.0" +source = "git+https://github.com/paritytech/polkadot-sdk#896c81440c1dd169bd2f5e65aba46eca228609f8" dependencies = [ "environmental", "parity-scale-codec", - "sp-storage 19.0.0", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk)", ] [[package]] @@ -26614,7 +26849,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-runtime 31.0.1", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -26628,7 +26863,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-runtime 39.0.2", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -26809,7 +27044,7 @@ dependencies = [ name = "sp-maybe-compressed-blob" version = "11.0.0" dependencies = [ - "thiserror", + "thiserror 1.0.69", "zstd 0.12.4", ] @@ -26819,7 +27054,7 @@ version = "11.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0c768c11afbe698a090386876911da4236af199cd38a5866748df4d8628aeff" dependencies = [ - "thiserror", + "thiserror 1.0.69", "zstd 0.12.4", ] @@ -26879,7 +27114,7 @@ dependencies = [ "sp-core 28.0.0", "sp-debug-derive 14.0.0", "sp-runtime 31.0.1", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -26897,7 +27132,7 @@ dependencies = [ "sp-core 34.0.0", "sp-debug-derive 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-runtime 39.0.2", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -26932,7 +27167,7 @@ dependencies = [ name = "sp-npos-elections-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 4.5.13", + "clap 4.5.21", "honggfuzz", "rand", "sp-npos-elections 26.0.0", @@ -27100,24 +27335,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "sp-runtime-interface" -version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" -dependencies = [ - "bytes", - "impl-trait-for-tuples", - "parity-scale-codec", - "primitive-types 0.12.2", - "sp-externalities 0.19.0", - "sp-runtime-interface-proc-macro 11.0.0", - "sp-std 8.0.0", - "sp-storage 13.0.0", - "sp-tracing 10.0.0", - "sp-wasm-interface 14.0.0", - "static_assertions", -] - [[package]] name = "sp-runtime-interface" version = "24.0.0" @@ -27142,6 +27359,25 @@ dependencies = [ "trybuild", ] +[[package]] +name = "sp-runtime-interface" +version = "24.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#896c81440c1dd169bd2f5e65aba46eca228609f8" +dependencies = [ + "bytes", + "impl-trait-for-tuples", + "parity-scale-codec", + "polkavm-derive 0.9.1", + "primitive-types 0.13.1", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk)", + "sp-runtime-interface-proc-macro 17.0.0 (git+https://github.com/paritytech/polkadot-sdk)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk)", + "static_assertions", +] + [[package]] name = "sp-runtime-interface" version = "27.0.0" @@ -27184,26 +27420,27 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" -version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" +version = "17.0.0" dependencies = [ "Inflector", - "proc-macro-crate 1.3.1", - "proc-macro2 1.0.86", + "expander", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "sp-runtime-interface-proc-macro" version = "17.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#896c81440c1dd169bd2f5e65aba46eca228609f8" dependencies = [ "Inflector", "expander", - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -27214,10 +27451,10 @@ checksum = "0195f32c628fee3ce1dfbbf2e7e52a30ea85f3589da9fe62a8b816d70fc06294" dependencies = [ "Inflector", "expander", - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -27344,7 +27581,7 @@ dependencies = [ "sp-panic-handler 13.0.0", "sp-runtime 31.0.1", "sp-trie 29.0.0", - "thiserror", + "thiserror 1.0.69", "tracing", "trie-db", ] @@ -27365,7 +27602,7 @@ dependencies = [ "sp-externalities 0.28.0", "sp-panic-handler 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-trie 34.0.0", - "thiserror", + "thiserror 1.0.69", "tracing", "trie-db", ] @@ -27386,7 +27623,7 @@ dependencies = [ "sp-externalities 0.28.0", "sp-panic-handler 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-trie 35.0.0", - "thiserror", + "thiserror 1.0.69", "tracing", "trie-db", ] @@ -27407,7 +27644,7 @@ dependencies = [ "sp-externalities 0.29.0", "sp-panic-handler 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-trie 37.0.0", - "thiserror", + "thiserror 1.0.69", "tracing", "trie-db", ] @@ -27431,7 +27668,7 @@ dependencies = [ "sp-externalities 0.25.0", "sp-runtime 31.0.1", "sp-runtime-interface 24.0.0", - "thiserror", + "thiserror 1.0.69", "x25519-dalek", ] @@ -27456,47 +27693,46 @@ dependencies = [ "sp-externalities 0.29.0", "sp-runtime 39.0.2", "sp-runtime-interface 28.0.0", - "thiserror", + "thiserror 1.0.69", "x25519-dalek", ] [[package]] name = "sp-std" -version = "8.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" +version = "14.0.0" [[package]] name = "sp-std" version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f8ee986414b0a9ad741776762f4083cd3a5128449b982a3919c4df36874834" [[package]] name = "sp-std" version = "14.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f8ee986414b0a9ad741776762f4083cd3a5128449b982a3919c4df36874834" +source = "git+https://github.com/paritytech/polkadot-sdk#896c81440c1dd169bd2f5e65aba46eca228609f8" [[package]] name = "sp-storage" -version = "13.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" +version = "19.0.0" dependencies = [ - "impl-serde 0.4.0", + "impl-serde 0.5.0", "parity-scale-codec", "ref-cast", "serde", - "sp-debug-derive 8.0.0", - "sp-std 8.0.0", + "sp-debug-derive 14.0.0", ] [[package]] name = "sp-storage" version = "19.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#896c81440c1dd169bd2f5e65aba46eca228609f8" dependencies = [ "impl-serde 0.5.0", "parity-scale-codec", "ref-cast", "serde", - "sp-debug-derive 14.0.0", + "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk)", ] [[package]] @@ -27532,7 +27768,7 @@ dependencies = [ "parity-scale-codec", "sp-inherents 26.0.0", "sp-runtime 31.0.1", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -27545,29 +27781,28 @@ dependencies = [ "parity-scale-codec", "sp-inherents 34.0.0", "sp-runtime 39.0.2", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "sp-tracing" -version = "10.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" +version = "16.0.0" dependencies = [ "parity-scale-codec", - "sp-std 8.0.0", "tracing", "tracing-core", - "tracing-subscriber 0.2.25", + "tracing-subscriber", ] [[package]] name = "sp-tracing" version = "16.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#896c81440c1dd169bd2f5e65aba46eca228609f8" dependencies = [ "parity-scale-codec", "tracing", "tracing-core", - "tracing-subscriber 0.3.18", + "tracing-subscriber", ] [[package]] @@ -27579,7 +27814,7 @@ dependencies = [ "parity-scale-codec", "tracing", "tracing-core", - "tracing-subscriber 0.3.18", + "tracing-subscriber", ] [[package]] @@ -27646,7 +27881,7 @@ dependencies = [ "sp-core 28.0.0", "sp-externalities 0.25.0", "sp-runtime 31.0.1", - "thiserror", + "thiserror 1.0.69", "tracing", "trie-bench", "trie-db", @@ -27672,7 +27907,7 @@ dependencies = [ "schnellru", "sp-core 32.0.0", "sp-externalities 0.28.0", - "thiserror", + "thiserror 1.0.69", "tracing", "trie-db", "trie-root", @@ -27696,7 +27931,7 @@ dependencies = [ "schnellru", "sp-core 33.0.1", "sp-externalities 0.28.0", - "thiserror", + "thiserror 1.0.69", "tracing", "trie-db", "trie-root", @@ -27720,7 +27955,7 @@ dependencies = [ "schnellru", "sp-core 34.0.0", "sp-externalities 0.29.0", - "thiserror", + "thiserror 1.0.69", "tracing", "trie-db", "trie-root", @@ -27739,7 +27974,7 @@ dependencies = [ "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-version-proc-macro 13.0.0", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -27757,7 +27992,7 @@ dependencies = [ "sp-runtime 37.0.0", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-version-proc-macro 14.0.0", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -27775,7 +28010,7 @@ dependencies = [ "sp-runtime 39.0.2", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-version-proc-macro 14.0.0", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -27784,10 +28019,10 @@ version = "13.0.0" dependencies = [ "parity-scale-codec", "proc-macro-warning", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "sp-version 29.0.0", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -27797,33 +28032,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aee8f6730641a65fcf0c8f9b1e448af4b3bb083d08058b47528188bccc7b7a7" dependencies = [ "parity-scale-codec", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "sp-wasm-interface" -version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" +version = "20.0.0" dependencies = [ "anyhow", "impl-trait-for-tuples", "log", "parity-scale-codec", - "sp-std 8.0.0", "wasmtime", ] [[package]] name = "sp-wasm-interface" version = "20.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#896c81440c1dd169bd2f5e65aba46eca228609f8" dependencies = [ "anyhow", "impl-trait-for-tuples", "log", "parity-scale-codec", - "wasmtime", ] [[package]] @@ -27895,30 +28128,29 @@ dependencies = [ ] [[package]] -name = "spki" -version = "0.7.2" +name = "spinning_top" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300" dependencies = [ - "base64ct", - "der", + "lock_api", ] [[package]] -name = "sqlformat" -version = "0.2.6" +name = "spki" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ - "nom", - "unicode_categories", + "base64ct", + "der", ] [[package]] name = "sqlx" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e" +checksum = "4410e73b3c0d8442c5f99b425d7a435b5ee0ae4167b3196771dd3f7a01be745f" dependencies = [ "sqlx-core", "sqlx-macros", @@ -27929,37 +28161,31 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e" +checksum = "6a007b6936676aa9ab40207cde35daab0a04b823be8ae004368c0793b96a61e0" dependencies = [ - "atoi", - "byteorder", "bytes", "crc", "crossbeam-queue", "either", "event-listener 5.3.1", - "futures-channel", "futures-core", "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.14.5", - "hashlink 0.9.1", - "hex", + "hashbrown 0.15.2", + "hashlink 0.10.0", "indexmap 2.7.0", "log", "memchr", "once_cell", - "paste", "percent-encoding", "serde", "serde_json", "sha2 0.10.8", "smallvec", - "sqlformat", - "thiserror", + "thiserror 2.0.4", "tokio", "tokio-stream", "tracing", @@ -27968,29 +28194,29 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657" +checksum = "3112e2ad78643fef903618d78cf0aec1cb3134b019730edb039b69eaf531f310" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "sqlx-core", "sqlx-macros-core", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "sqlx-macros-core" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5" +checksum = "4e9f90acc5ab146a99bf5061a7eb4976b573f560bc898ef3bf8435448dd5e7ad" dependencies = [ "dotenvy", "either", "heck 0.5.0", "hex", "once_cell", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "serde", "serde_json", @@ -27999,7 +28225,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.87", + "syn 2.0.90", "tempfile", "tokio", "url", @@ -28007,9 +28233,9 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" +checksum = "4560278f0e00ce64938540546f59f590d60beee33fffbd3b9cd47851e5fff233" dependencies = [ "atoi", "base64 0.22.1", @@ -28042,16 +28268,16 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.4", "tracing", "whoami", ] [[package]] name = "sqlx-postgres" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" +checksum = "c5b98a57f363ed6764d5b3a12bfedf62f07aa16e1856a7ddc2a0bb190a959613" dependencies = [ "atoi", "base64 0.22.1", @@ -28062,7 +28288,6 @@ dependencies = [ "etcetera", "futures-channel", "futures-core", - "futures-io", "futures-util", "hex", "hkdf", @@ -28080,16 +28305,16 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.4", "tracing", "whoami", ] [[package]] name = "sqlx-sqlite" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680" +checksum = "f85ca71d3a5b24e64e1d08dd8fe36c6c95c339a896cc33068148906784620540" dependencies = [ "atoi", "flume", @@ -28110,17 +28335,17 @@ dependencies = [ [[package]] name = "ss58-registry" -version = "1.43.0" +version = "1.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6915280e2d0db8911e5032a5c275571af6bdded2916abd691a659be25d3439" +checksum = "19409f13998e55816d1c728395af0b52ec066206341d939e22e7766df9b494b8" dependencies = [ "Inflector", "num-format", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "serde", "serde_json", - "unicode-xid 0.2.4", + "unicode-xid 0.2.6", ] [[package]] @@ -28141,7 +28366,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f07d54c4d01a1713eb363b55ba51595da15f6f1211435b71466460da022aa140" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "syn 1.0.109", ] @@ -28156,7 +28381,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" name = "staging-chain-spec-builder" version = "1.6.1" dependencies = [ - "clap 4.5.13", + "clap 4.5.21", "cmd_lib", "docify", "log", @@ -28173,7 +28398,7 @@ version = "3.0.0-dev" dependencies = [ "array-bytes", "assert_cmd", - "clap 4.5.13", + "clap 4.5.21", "clap_complete", "criterion", "futures", @@ -28194,7 +28419,7 @@ dependencies = [ "scale-info", "serde", "serde_json", - "soketto 0.8.0", + "soketto 0.8.1", "sp-keyring 31.0.0", "staging-node-inspect", "substrate-cli-test-utils", @@ -28210,7 +28435,7 @@ dependencies = [ name = "staging-node-inspect" version = "0.12.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.21", "parity-scale-codec", "sc-cli", "sc-client-api", @@ -28220,7 +28445,7 @@ dependencies = [ "sp-io 30.0.0", "sp-runtime 31.0.1", "sp-statement-store 10.0.0", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -28419,7 +28644,7 @@ checksum = "70a2595fc3aa78f2d0e45dd425b22282dd863273761cc77780914b2cf3003acf" dependencies = [ "cfg_aliases 0.1.1", "memchr", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "syn 1.0.109", ] @@ -28452,12 +28677,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "strsim" version = "0.11.1" @@ -28483,7 +28702,7 @@ checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" dependencies = [ "heck 0.3.3", "proc-macro-error", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "syn 1.0.109", ] @@ -28497,12 +28716,6 @@ dependencies = [ "strum_macros 0.24.3", ] -[[package]] -name = "strum" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" - [[package]] name = "strum" version = "0.26.3" @@ -28519,25 +28732,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "rustversion", "syn 1.0.109", ] -[[package]] -name = "strum_macros" -version = "0.25.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" -dependencies = [ - "heck 0.4.1", - "proc-macro2 1.0.86", - "quote 1.0.37", - "rustversion", - "syn 2.0.87", -] - [[package]] name = "strum_macros" version = "0.26.4" @@ -28545,17 +28745,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ "heck 0.5.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "rustversion", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "subkey" version = "9.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.21", "sc-cli", ] @@ -28630,7 +28830,7 @@ dependencies = [ "scale-info", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "wasm-testbed", ] @@ -28680,11 +28880,11 @@ name = "substrate-prometheus-endpoint" version = "0.17.0" dependencies = [ "http-body-util", - "hyper 1.3.1", + "hyper 1.5.1", "hyper-util", "log", "prometheus", - "thiserror", + "thiserror 1.0.69", "tokio", ] @@ -28728,7 +28928,7 @@ dependencies = [ "sp-trie 29.0.0", "structopt", "strum 0.26.3", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -28758,7 +28958,7 @@ dependencies = [ "sp-io 35.0.0", "sp-runtime 36.0.0", "sp-wasm-interface 21.0.1", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -28886,7 +29086,7 @@ dependencies = [ "sp-blockchain", "sp-runtime 31.0.1", "substrate-test-runtime-client", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -28957,9 +29157,9 @@ checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "subtle-ng" @@ -28981,12 +29181,12 @@ dependencies = [ "rand", "reqwest 0.12.9", "scale-info", - "semver 1.0.18", + "semver 1.0.23", "serde", "serde_json", "sp-version 35.0.0", "substrate-differ", - "thiserror", + "thiserror 1.0.69", "url", "uuid", "wasm-loader", @@ -29022,7 +29222,7 @@ dependencies = [ "subxt-lightclient", "subxt-macro", "subxt-metadata", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-util", "tracing", @@ -29039,13 +29239,13 @@ checksum = "3cfcfb7d9589f3df0ac87c4988661cf3fb370761fcb19f2fd33104cc59daf22a" dependencies = [ "heck 0.5.0", "parity-scale-codec", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "scale-info", "scale-typegen", "subxt-metadata", - "syn 2.0.87", - "thiserror", + "syn 2.0.90", + "thiserror 1.0.69", ] [[package]] @@ -29088,7 +29288,7 @@ dependencies = [ "serde", "serde_json", "smoldot-light 0.16.2", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", "tracing", @@ -29107,7 +29307,7 @@ dependencies = [ "scale-typegen", "subxt-codegen", "subxt-utils-fetchmetadata", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -29161,20 +29361,20 @@ checksum = "3082b17a86e3c3fe45d858d94d68f6b5247caace193dad6201688f24db8ba9bb" dependencies = [ "hex", "parity-scale-codec", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "sval" -version = "2.6.1" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b031320a434d3e9477ccf9b5756d57d4272937b8d22cb88af80b7633a1b78b1" +checksum = "f6dc0f9830c49db20e73273ffae9b5240f63c42e515af1da1fceefb69fceafd8" [[package]] name = "sval_buffer" -version = "2.6.1" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf7e9412af26b342f3f2cc5cc4122b0105e9d16eb76046cd14ed10106cf6028" +checksum = "429922f7ad43c0ef8fd7309e14d750e38899e32eb7e8da656ea169dd28ee212f" dependencies = [ "sval", "sval_ref", @@ -29182,18 +29382,18 @@ dependencies = [ [[package]] name = "sval_dynamic" -version = "2.6.1" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0ef628e8a77a46ed3338db8d1b08af77495123cc229453084e47cd716d403cf" +checksum = "68f16ff5d839396c11a30019b659b0976348f3803db0626f736764c473b50ff4" dependencies = [ "sval", ] [[package]] name = "sval_fmt" -version = "2.6.1" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dc09e9364c2045ab5fa38f7b04d077b3359d30c4c2b3ec4bae67a358bd64326" +checksum = "c01c27a80b6151b0557f9ccbe89c11db571dc5f68113690c1e028d7e974bae94" dependencies = [ "itoa", "ryu", @@ -29202,55 +29402,65 @@ dependencies = [ [[package]] name = "sval_json" -version = "2.6.1" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ada6f627e38cbb8860283649509d87bc4a5771141daa41c78fd31f2b9485888d" +checksum = "0deef63c70da622b2a8069d8600cf4b05396459e665862e7bdb290fd6cf3f155" dependencies = [ "itoa", "ryu", "sval", ] +[[package]] +name = "sval_nested" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a39ce5976ae1feb814c35d290cf7cf8cd4f045782fe1548d6bc32e21f6156e9f" +dependencies = [ + "sval", + "sval_buffer", + "sval_ref", +] + [[package]] name = "sval_ref" -version = "2.6.1" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703ca1942a984bd0d9b5a4c0a65ab8b4b794038d080af4eb303c71bc6bf22d7c" +checksum = "bb7c6ee3751795a728bc9316a092023529ffea1783499afbc5c66f5fabebb1fa" dependencies = [ "sval", ] [[package]] name = "sval_serde" -version = "2.6.1" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830926cd0581f7c3e5d51efae4d35c6b6fc4db583842652891ba2f1bed8db046" +checksum = "2a5572d0321b68109a343634e3a5d576bf131b82180c6c442dee06349dfc652a" dependencies = [ "serde", "sval", - "sval_buffer", - "sval_fmt", + "sval_nested", ] [[package]] name = "symbolic-common" -version = "12.3.0" +version = "12.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167a4ffd7c35c143fd1030aa3c2caf76ba42220bd5a6b5f4781896434723b8c3" +checksum = "e5ba5365997a4e375660bed52f5b42766475d5bc8ceb1bb13fea09c469ea0f49" dependencies = [ "debugid", - "memmap2 0.5.10", + "memmap2 0.9.5", "stable_deref_trait", "uuid", ] [[package]] name = "symbolic-demangle" -version = "12.3.0" +version = "12.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e378c50e80686c1c5c205674e1f86a2858bec3d2a7dfdd690331a8a19330f293" +checksum = "beff338b2788519120f38c59ff4bb15174f52a183e547bac3d6072c2c0aa48aa" dependencies = [ - "cpp_demangle 0.4.3", + "cpp_demangle 0.4.4", "rustc-demangle", "symbolic-common", ] @@ -29272,18 +29482,18 @@ version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.87" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "unicode-ident", ] @@ -29295,21 +29505,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b837ef12ab88835251726eb12237655e61ec8dc8a280085d1961cdc3dfd047" dependencies = [ "paste", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "syn-solidity" -version = "0.8.15" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219389c1ebe89f8333df8bdfb871f6631c552ff399c23cac02480b6088aad8f0" +checksum = "b84e4d83a0a6704561302b917a932484e1cae2d8c6354c64be8b7bac1c1fe057" dependencies = [ "paste", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -29320,9 +29530,9 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "sync_wrapper" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] @@ -29333,10 +29543,10 @@ version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "syn 1.0.109", - "unicode-xid 0.2.4", + "unicode-xid 0.2.6", ] [[package]] @@ -29345,16 +29555,16 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "sysinfo" -version = "0.30.5" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb4f3438c8f6389c864e61221cbc97e9bca98b4daf39a5beb7bea660f528bb2" +checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3" dependencies = [ "cfg-if", "core-foundation-sys", @@ -29372,8 +29582,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys", + "core-foundation 0.9.4", + "system-configuration-sys 0.5.0", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.6.0", + "core-foundation 0.9.4", + "system-configuration-sys 0.6.0", ] [[package]] @@ -29386,6 +29607,16 @@ dependencies = [ "libc", ] +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tap" version = "1.0.1" @@ -29394,9 +29625,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tar" -version = "0.4.40" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" +checksum = "c65998313f8e17d0d553d28f91a0df93e4dbbbf770279c7bc21ca0f09ea1a1f6" dependencies = [ "filetime", "libc", @@ -29405,9 +29636,15 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.11" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "target-triple" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" +checksum = "42a4d50cdb458045afc8131fd91b64904da29548bcb63c7236e0844936c13078" [[package]] name = "tempfile" @@ -29416,9 +29653,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", - "fastrand 2.3.0", + "fastrand 2.2.0", "once_cell", - "rustix 0.38.42", + "rustix 0.38.41", "windows-sys 0.59.0", ] @@ -29427,7 +29664,7 @@ name = "template-zombienet-tests" version = "0.0.0" dependencies = [ "anyhow", - "env_logger 0.11.3", + "env_logger 0.11.5", "log", "tokio", "zombienet-sdk", @@ -29435,21 +29672,21 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.2.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] [[package]] name = "terminal_size" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" dependencies = [ - "rustix 0.38.42", - "windows-sys 0.48.0", + "rustix 0.38.41", + "windows-sys 0.59.0", ] [[package]] @@ -29464,9 +29701,9 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dffced63c2b5c7be278154d76b479f9f9920ed34e7574201407f0b14e2bbb93" dependencies = [ - "env_logger 0.11.3", + "env_logger 0.11.5", "test-log-macros", - "tracing-subscriber 0.3.18", + "tracing-subscriber", ] [[package]] @@ -29475,9 +29712,9 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -29496,7 +29733,7 @@ dependencies = [ name = "test-parachain-adder-collator" version = "1.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.21", "futures", "futures-timer", "log", @@ -29543,7 +29780,7 @@ dependencies = [ name = "test-parachain-undying-collator" version = "1.0.0" dependencies = [ - "clap 4.5.13", + "clap 4.5.21", "futures", "futures-timer", "log", @@ -29622,53 +29859,67 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ - "unicode-width", + "unicode-width 0.1.14", ] [[package]] -name = "textwrap" -version = "0.16.0" +name = "thiserror" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] [[package]] name = "thiserror" -version = "1.0.65" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" +checksum = "2f49a1853cf82743e3b7950f77e0f4d622ca36cf4317cba00c767838bac8d490" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.4", ] [[package]] name = "thiserror-core" -version = "1.0.38" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d97345f6437bb2004cd58819d8a9ef8e36cdd7661c2abc4bbde0a7c40d9f497" +checksum = "c001ee18b7e5e3f62cbf58c7fe220119e68d902bb7443179c0c8aef30090e999" dependencies = [ "thiserror-core-impl", ] [[package]] name = "thiserror-core-impl" -version = "1.0.38" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10ac1c5050e43014d16b2f94d0d2ce79e65ffdd8b38d8048f9c8f6a8a6da62ac" +checksum = "e4c60d69f36615a077cc7663b9cb8e42275722d23e58a7fa3d2c7f2915d09d04" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 1.0.109", + "syn 2.0.90", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", ] [[package]] name = "thiserror-impl" -version = "1.0.65" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" +checksum = "8381894bb3efe0c4acac3ded651301ceee58a15d47c2e34885ed1908ad667061" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -29679,9 +29930,9 @@ checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", @@ -29729,9 +29980,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -29752,9 +30003,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", @@ -29769,6 +30020,16 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -29781,9 +30042,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -29796,9 +30057,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.40.0" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", "bytes", @@ -29807,7 +30068,7 @@ dependencies = [ "parking_lot 0.12.3", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.7", + "socket2 0.5.8", "tokio-macros", "windows-sys 0.52.0", ] @@ -29828,9 +30089,9 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -29860,7 +30121,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.7", + "rustls 0.21.12", "tokio", ] @@ -29870,7 +30131,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.18", + "rustls 0.23.19", "rustls-pki-types", "tokio", ] @@ -29908,7 +30169,7 @@ checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", "log", - "rustls 0.21.7", + "rustls 0.21.12", "rustls-native-certs 0.6.3", "tokio", "tokio-rustls 0.24.1", @@ -29968,18 +30229,7 @@ checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap 2.7.0", "toml_datetime", - "winnow 0.5.15", -] - -[[package]] -name = "toml_edit" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" -dependencies = [ - "indexmap 2.7.0", - "toml_datetime", - "winnow 0.5.15", + "winnow 0.5.40", ] [[package]] @@ -29992,7 +30242,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.18", + "winnow 0.6.20", ] [[package]] @@ -30023,8 +30273,8 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http 0.2.9", - "http-body 0.4.5", + "http 0.2.12", + "http-body 0.4.6", "http-range-header", "mime", "pin-project-lite", @@ -30042,7 +30292,7 @@ dependencies = [ "bitflags 2.6.0", "bytes", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", "pin-project-lite", "tower-layer", @@ -30051,21 +30301,21 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -30075,20 +30325,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -30120,21 +30370,10 @@ version = "5.0.0" dependencies = [ "assert_matches", "expander", - "proc-macro-crate 3.1.0", - "proc-macro2 1.0.86", + "proc-macro-crate 3.2.0", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", -] - -[[package]] -name = "tracing-log" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" -dependencies = [ - "lazy_static", - "log", - "tracing-core", + "syn 2.0.90", ] [[package]] @@ -30148,46 +30387,14 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-serde" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" -dependencies = [ - "serde", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" -dependencies = [ - "ansi_term", - "chrono", - "lazy_static", - "matchers 0.0.1", - "regex", - "serde", - "serde_json", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log 0.1.3", - "tracing-serde", -] - [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "chrono", - "matchers 0.1.0", + "matchers", "nu-ansi-term", "once_cell", "parking_lot 0.12.3", @@ -30198,7 +30405,7 @@ dependencies = [ "time", "tracing", "tracing-core", - "tracing-log 0.2.0", + "tracing-log", ] [[package]] @@ -30250,24 +30457,24 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "trybuild" -version = "1.0.89" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a9d3ba662913483d6722303f619e75ea10b7855b0f8e0d72799cf8621bb488f" +checksum = "8dcd332a5496c026f1e14b7f3d2b7bd98e509660c04239c58b0ba38a12daded4" dependencies = [ - "basic-toml", "dissimilar", "glob", - "once_cell", "serde", "serde_derive", "serde_json", + "target-triple", "termcolor", + "toml 0.8.19", ] [[package]] @@ -30285,13 +30492,13 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 0.2.9", + "http 0.2.12", "httparse", "log", "rand", - "rustls 0.21.7", + "rustls 0.21.12", "sha1", - "thiserror", + "thiserror 1.0.69", "url", "utf-8", ] @@ -30310,10 +30517,10 @@ dependencies = [ "log", "rand", "rustls 0.22.4", - "rustls-native-certs 0.7.0", + "rustls-native-certs 0.7.3", "rustls-pki-types", "sha1", - "thiserror", + "thiserror 1.0.69", "url", "utf-8", ] @@ -30336,17 +30543,23 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "typeid" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e" + [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "uint" @@ -30380,15 +30593,15 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-normalization" @@ -30407,33 +30620,33 @@ checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.1.10" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] -name = "unicode-xid" -version = "0.1.0" +name = "unicode-width" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" [[package]] -name = "unicode_categories" -version = "0.1.1" +name = "unicode-xid" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "universal-hash" @@ -30442,7 +30655,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ "crypto-common", - "subtle 2.5.0", + "subtle 2.6.1", ] [[package]] @@ -30507,22 +30720,22 @@ dependencies = [ "flate2", "log", "once_cell", - "rustls 0.23.18", + "rustls 0.23.19", "rustls-pki-types", "serde", "serde_json", "url", - "webpki-roots 0.26.3", + "webpki-roots 0.26.7", ] [[package]] name = "url" -version = "2.5.2" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", - "idna 0.5.0", + "idna 1.0.3", "percent-encoding", "serde", ] @@ -30533,17 +30746,29 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.4.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ "getrandom", ] @@ -30556,9 +30781,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec26a25bd6fca441cdd0f769fd7f891bae119f996de31f86a5eddccef54c1d" +checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" dependencies = [ "value-bag-serde1", "value-bag-sval2", @@ -30566,9 +30791,9 @@ dependencies = [ [[package]] name = "value-bag-serde1" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ead5b693d906686203f19a49e88c477fb8c15798b68cf72f60b4b5521b4ad891" +checksum = "4bb773bd36fd59c7ca6e336c94454d9c66386416734817927ac93d81cb3c5b0b" dependencies = [ "erased-serde", "serde", @@ -30577,9 +30802,9 @@ dependencies = [ [[package]] name = "value-bag-sval2" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9d0f4a816370c3a0d7d82d603b62198af17675b12fe5e91de6b47ceb505882" +checksum = "53a916a702cac43a88694c97657d449775667bcd14b70419441d05b7fea4a83a" dependencies = [ "sval", "sval_buffer", @@ -30604,9 +30829,9 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "void" @@ -30616,16 +30841,16 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "w3f-bls" -version = "0.1.3" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7335e4c132c28cc43caef6adb339789e599e39adbe78da0c4d547fad48cbc331" +checksum = "70a3028804c8bbae2a97a15b71ffc0e308c4b01a520994aafa77d56e94e19024" dependencies = [ "ark-bls12-377", "ark-bls12-381", - "ark-ec", + "ark-ec 0.4.2", "ark-ff 0.4.2", "ark-serialize 0.4.2", - "ark-serialize-derive", + "ark-serialize-derive 0.4.2", "arrayref", "constcat", "digest 0.10.7", @@ -30634,7 +30859,7 @@ dependencies = [ "rand_core 0.6.4", "sha2 0.10.8", "sha3 0.10.8", - "thiserror", + "thiserror 1.0.69", "zeroize", ] @@ -30649,9 +30874,9 @@ dependencies = [ [[package]] name = "waker-fn" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" [[package]] name = "walkdir" @@ -30684,11 +30909,20 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" +[[package]] +name = "wasix" +version = "0.12.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d" +dependencies = [ + "wasi", +] + [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" dependencies = [ "cfg-if", "once_cell", @@ -30699,36 +30933,37 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.45" +version = "0.4.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +checksum = "9dfaf8f50e5f293737ee323940c7d8b08a66a95a419223d9f41610ca08b0833d" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" dependencies = [ "quote 1.0.37", "wasm-bindgen-macro-support", @@ -30736,31 +30971,32 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" [[package]] name = "wasm-bindgen-test" -version = "0.3.37" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e6e302a7ea94f83a6d09e78e7dc7d9ca7b186bc2829c24a22d0753efd680671" +checksum = "3d919bb60ebcecb9160afee6c71b43a58a4f0517a2de0054cd050d02cec08201" dependencies = [ - "console_error_panic_hook", "js-sys", + "minicov", + "once_cell", "scoped-tls", "wasm-bindgen", "wasm-bindgen-futures", @@ -30769,21 +31005,23 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.37" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecb993dd8c836930ed130e020e77d9b2e65dd0fbab1b67c790b0f5d80b11a575" +checksum = "222ebde6ea87fbfa6bdd2e9f1fd8a91d60aee5db68792632176c4e16a74fc7d8" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", + "syn 2.0.90", ] [[package]] name = "wasm-encoder" -version = "0.31.1" +version = "0.221.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41763f20eafed1399fff1afb466496d3a959f58241436cfdc17e3f5ca954de16" +checksum = "c17a3bd88f2155da63a1f2fcb8a56377a24f0b6dfed12733bb5f544e86f690c5" dependencies = [ "leb128", + "wasmparser 0.221.2", ] [[package]] @@ -30803,12 +31041,12 @@ dependencies = [ "array-bytes", "log", "multibase 0.9.1", - "multihash 0.19.1", + "multihash 0.19.2", "serde", "serde_json", "sp-maybe-compressed-blob 11.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "subrpcer", - "thiserror", + "thiserror 1.0.69", "tungstenite 0.21.0", "ureq", "url", @@ -30816,16 +31054,16 @@ dependencies = [ [[package]] name = "wasm-opt" -version = "0.116.0" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc942673e7684671f0c5708fc18993569d184265fd5223bb51fc8e5b9b6cfd52" +checksum = "2fd87a4c135535ffed86123b6fb0f0a5a0bc89e50416c942c5f0662c645f679c" dependencies = [ "anyhow", "libc", "strum 0.24.1", "strum_macros 0.24.3", "tempfile", - "thiserror", + "thiserror 1.0.69", "wasm-opt-cxx-sys", "wasm-opt-sys", ] @@ -30873,7 +31111,7 @@ dependencies = [ "sp-version 35.0.0", "sp-wasm-interface 21.0.1", "substrate-runtime-proposal-hash", - "thiserror", + "thiserror 1.0.69", "wasm-loader", ] @@ -30911,7 +31149,7 @@ version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50386c99b9c32bd2ed71a55b6dd4040af2580530fae8bdb9a6576571a80d0cca" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "multi-stash", "num-derive", "num-traits", @@ -30973,6 +31211,17 @@ dependencies = [ "url", ] +[[package]] +name = "wasmparser" +version = "0.221.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9845c470a2e10b61dd42c385839cdd6496363ed63b5c9e420b5488b77bd22083" +dependencies = [ + "bitflags 2.6.0", + "indexmap 2.7.0", + "semver 1.0.23", +] + [[package]] name = "wasmparser-nostd" version = "0.100.2" @@ -31001,7 +31250,7 @@ dependencies = [ "rayon", "serde", "target-lexicon", - "wasmparser", + "wasmparser 0.102.0", "wasmtime-cache", "wasmtime-cranelift", "wasmtime-environ", @@ -31031,7 +31280,7 @@ dependencies = [ "directories-next", "file-per-thread-logger", "log", - "rustix 0.36.15", + "rustix 0.36.17", "serde", "sha2 0.10.8", "toml 0.5.11", @@ -31055,8 +31304,8 @@ dependencies = [ "log", "object 0.30.4", "target-lexicon", - "thiserror", - "wasmparser", + "thiserror 1.0.69", + "wasmparser 0.102.0", "wasmtime-cranelift-shared", "wasmtime-environ", ] @@ -31090,8 +31339,8 @@ dependencies = [ "object 0.30.4", "serde", "target-lexicon", - "thiserror", - "wasmparser", + "thiserror 1.0.69", + "wasmparser 0.102.0", "wasmtime-types", ] @@ -31127,7 +31376,7 @@ checksum = "6e0554b84c15a27d76281d06838aed94e13a77d7bf604bbbaf548aa20eb93846" dependencies = [ "object 0.30.4", "once_cell", - "rustix 0.36.15", + "rustix 0.36.17", ] [[package]] @@ -31155,10 +31404,10 @@ dependencies = [ "log", "mach", "memfd", - "memoffset 0.8.0", + "memoffset", "paste", "rand", - "rustix 0.36.15", + "rustix 0.36.17", "wasmtime-asm-macros", "wasmtime-environ", "wasmtime-jit-debug", @@ -31173,36 +31422,37 @@ checksum = "a4f6fffd2a1011887d57f07654dd112791e872e3ff4a2e626aee8059ee17f06f" dependencies = [ "cranelift-entity", "serde", - "thiserror", - "wasmparser", + "thiserror 1.0.69", + "wasmparser 0.102.0", ] [[package]] name = "wast" -version = "63.0.0" +version = "221.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2560471f60a48b77fccefaf40796fda61c97ce1e790b59dfcec9dc3995c9f63a" +checksum = "fcc4470b9de917ba199157d1f0ae104f2ae362be728c43e68c571c7715bd629e" dependencies = [ + "bumpalo", "leb128", "memchr", - "unicode-width", + "unicode-width 0.2.0", "wasm-encoder", ] [[package]] name = "wat" -version = "1.0.70" +version = "1.221.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdc306c2c4c2f2bf2ba69e083731d0d2a77437fc6a350a19db139636e7e416c" +checksum = "6b1f3c6d82af47286494c6caea1d332037f5cbeeac82bbf5ef59cb8c201c466e" dependencies = [ "wast", ] [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c" dependencies = [ "js-sys", "wasm-bindgen", @@ -31230,15 +31480,15 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.2" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "0.26.3" +version = "0.26.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" dependencies = [ "rustls-pki-types", ] @@ -31429,15 +31679,15 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" dependencies = [ - "redox_syscall 0.5.8", + "redox_syscall 0.5.7", "wasite", ] [[package]] name = "wide" -version = "0.7.11" +version = "0.7.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa469ffa65ef7e0ba0f164183697b89b854253fd31aeb92358b7b6155177d62f" +checksum = "58e6db2670d2be78525979e9a5f9c69d296fd7d670549fe9ebf70f8708cb5019" dependencies = [ "bytemuck", "safe_arch", @@ -31445,9 +31695,9 @@ dependencies = [ [[package]] name = "widestring" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" [[package]] name = "winapi" @@ -31467,11 +31717,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "winapi", + "windows-sys 0.59.0", ] [[package]] @@ -31482,59 +31732,60 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.48.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ - "windows-targets 0.48.5", + "windows-core 0.52.0", + "windows-targets 0.52.6", ] [[package]] name = "windows" -version = "0.51.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" +checksum = "efc5cf48f83140dcaab716eeaea345f9e93d0018fb81162753a3f76c3397b538" dependencies = [ - "windows-core 0.51.1", - "windows-targets 0.48.5", + "windows-core 0.53.0", + "windows-targets 0.52.6", ] [[package]] -name = "windows" +name = "windows-core" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-core 0.52.0", "windows-targets 0.52.6", ] [[package]] name = "windows-core" -version = "0.51.1" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +checksum = "9dcc5b895a6377f1ab9fa55acedab1fd5ac0db66ad1e6c7f47e28a22e446a5dd" dependencies = [ - "windows-targets 0.48.5", + "windows-result 0.1.2", + "windows-targets 0.52.6", ] [[package]] -name = "windows-core" -version = "0.52.0" +name = "windows-registry" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ + "windows-result 0.2.0", + "windows-strings", "windows-targets 0.52.6", ] [[package]] -name = "windows-registry" -version = "0.2.0" +name = "windows-result" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" dependencies = [ - "windows-result", - "windows-strings", "windows-targets 0.52.6", ] @@ -31553,7 +31804,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ - "windows-result", + "windows-result 0.2.0", "windows-targets 0.52.6", ] @@ -31773,18 +32024,18 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.5.15" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" dependencies = [ "memchr", ] [[package]] name = "winnow" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] @@ -31799,6 +32050,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "wyz" version = "0.5.1" @@ -31810,9 +32073,9 @@ dependencies = [ [[package]] name = "x25519-dalek" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ "curve25519-dalek 4.1.3", "rand_core 0.6.4", @@ -31833,17 +32096,19 @@ dependencies = [ "nom", "oid-registry", "rusticata-macros", - "thiserror", + "thiserror 1.0.69", "time", ] [[package]] name = "xattr" -version = "1.0.1" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" dependencies = [ "libc", + "linux-raw-sys 0.4.14", + "rustix 0.38.41", ] [[package]] @@ -31935,10 +32200,10 @@ version = "7.0.0" dependencies = [ "Inflector", "frame-support 28.0.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", "staging-xcm 7.0.0", - "syn 2.0.87", + "syn 2.0.90", "trybuild", ] @@ -31949,9 +32214,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87fb4f14094d65c500a59bcf540cf42b99ee82c706edd6226a92e769ad60563e" dependencies = [ "Inflector", - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -32093,9 +32358,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.20" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" +checksum = "ea8b391c9a790b496184c29f7f93b9ed5b16abb306c05415b68bcc16e4d06432" [[package]] name = "xmltree" @@ -32123,9 +32388,9 @@ dependencies = [ [[package]] name = "yamux" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31b5e376a8b012bee9c423acdbb835fc34d45001cfa3106236a624e4b738028" +checksum = "17610762a1207ee816c6fadc29220904753648aba0a9ed61c7b8336e80a559c4" dependencies = [ "futures", "log", @@ -32139,9 +32404,9 @@ dependencies = [ [[package]] name = "yansi" -version = "0.5.1" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yap" @@ -32158,24 +32423,70 @@ dependencies = [ "time", ] +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", + "synstructure 0.13.1", +] + [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", + "synstructure 0.13.1", ] [[package]] @@ -32193,9 +32504,31 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2 1.0.92", "quote 1.0.37", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -32207,7 +32540,7 @@ dependencies = [ "reqwest 0.12.9", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-tungstenite", "tracing-gum", @@ -32222,12 +32555,12 @@ checksum = "5ced2fca1322821431f03d06dcf2ea74d3a7369760b6c587b372de6eada3ce43" dependencies = [ "anyhow", "lazy_static", - "multiaddr 0.18.1", + "multiaddr 0.18.2", "regex", "reqwest 0.11.27", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "tokio", "toml 0.8.19", "url", @@ -32247,7 +32580,7 @@ dependencies = [ "hex", "libp2p", "libsecp256k1", - "multiaddr 0.18.1", + "multiaddr 0.18.2", "rand", "regex", "reqwest 0.11.27", @@ -32257,7 +32590,7 @@ dependencies = [ "sp-core 34.0.0", "subxt", "subxt-signer", - "thiserror", + "thiserror 1.0.69", "tokio", "tracing", "uuid", @@ -32275,7 +32608,7 @@ checksum = "23702db0819a050c8a0130a769b105695137020a64207b4597aa021f06924552" dependencies = [ "pest", "pest_derive", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -32299,7 +32632,7 @@ dependencies = [ "serde_yaml", "sha2 0.10.8", "tar", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-util", "tracing", @@ -32340,7 +32673,7 @@ dependencies = [ "rand", "regex", "reqwest 0.11.27", - "thiserror", + "thiserror 1.0.69", "tokio", "tracing", "uuid", @@ -32386,11 +32719,10 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.8+zstd.1.5.5" +version = "2.0.13+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" +checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" dependencies = [ "cc", - "libc", "pkg-config", ] diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 684fad82a0989..8f9c2d2e2957e 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -889,9 +889,12 @@ pub(crate) mod multi_block_impls { parameter_types! { pub Pages: u32 = 4; + // nominators snapshot size pub VoterSnapshotPerBlock: u32 = 22500 / 4; + // validator snapshot size pub TargetSnapshotPerBlock: u32 = 1000; pub SignedPhase: u32 = EPOCH_DURATION_IN_BLOCKS / 4; + pub SignedValidation: u32 = 8; pub UnsignedPhase: u32 = EPOCH_DURATION_IN_BLOCKS / 4; } @@ -905,7 +908,7 @@ pub(crate) mod multi_block_impls { // split election into 8 pages. type Pages = Pages; // allow 2 signed solutions to be verified. - type SignedValidationPhase = ConstU32<16>; + type SignedValidationPhase = SignedValidation; // TODO: sanity check that the length of all phases is within reason. type SignedPhase = SignedPhase; type UnsignedPhase = UnsignedPhase; diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index dc988369efc82..0f4a593419697 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -768,6 +768,7 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { fn on_initialize(now: BlockNumberFor) -> Weight { + return Default::default(); let next_election = T::DataProvider::next_election_prediction(now).max(now); let signed_deadline = T::SignedPhase::get() + T::UnsignedPhase::get(); From 04847d515ef56da4d0801c9b89a4241dfa827b33 Mon Sep 17 00:00:00 2001 From: runcomet Date: Thu, 23 Jan 2025 02:38:15 -0800 Subject: [PATCH 130/169] Balances: Configurable Number of Genesis Accounts with Specified Balances for Benchmarking (#6267) # Derived Dev Accounts Resolves https://github.com/paritytech/polkadot-sdk/issues/6040 ## Description This update introduces support for creating an arbitrary number of developer accounts at the genesis block based on a specified derivation path. This functionality is gated by the runtime-benchmarks feature, ensuring it is only enabled during benchmarking scenarios. ### Key Features - Arbitrary Dev Accounts at Genesis: Developers can now specify any number of accounts to be generated at genesis using a hard derivation path. - Default Derivation Path: If no derivation path is provided (i.e., when `Option` is set to `Some` at genesis), the system will default to the path `//Sender//{}`. - No Impact on Total Token Issuance: Developer accounts are excluded from the total issuance of the token supply at genesis, ensuring they do not affect the overall balance or token distribution. polkadot address: 14SRqZTC1d8rfxL8W1tBTnfUBPU23ACFVPzp61FyGf4ftUFg --------- Co-authored-by: Sebastian Kunert --- bridges/modules/messages/src/tests/mock.rs | 9 +- .../pallets/collator-selection/src/mock.rs | 2 +- .../assets/asset-hub-rococo/src/genesis.rs | 1 + .../assets/asset-hub-westend/src/genesis.rs | 1 + .../bridges/bridge-hub-rococo/src/genesis.rs | 1 + .../bridges/bridge-hub-westend/src/genesis.rs | 1 + .../collectives-westend/src/genesis.rs | 1 + .../coretime/coretime-rococo/src/genesis.rs | 1 + .../coretime/coretime-westend/src/genesis.rs | 1 + .../people/people-rococo/src/genesis.rs | 1 + .../people/people-westend/src/genesis.rs | 1 + .../parachains/testing/penpal/src/genesis.rs | 1 + .../chains/relays/rococo/src/genesis.rs | 1 + .../chains/relays/westend/src/genesis.rs | 1 + .../parachains/runtimes/test-utils/src/lib.rs | 2 +- .../runtime/common/src/assigned_slots/mod.rs | 1 + polkadot/runtime/common/src/auctions/mock.rs | 1 + polkadot/runtime/common/src/crowdloan/mod.rs | 1 + .../common/src/paras_registrar/mock.rs | 1 + polkadot/runtime/common/src/slots/mod.rs | 1 + .../relay_token_transactor/network.rs | 9 +- polkadot/xcm/pallet-xcm/src/mock.rs | 2 +- .../single_asset_adapter/mock.rs | 1 + polkadot/xcm/xcm-builder/tests/mock/mod.rs | 2 +- polkadot/xcm/xcm-runtime-apis/tests/mock.rs | 4 +- polkadot/xcm/xcm-simulator/example/src/lib.rs | 2 + polkadot/xcm/xcm-simulator/fuzzer/src/fuzz.rs | 3 +- prdoc/pr_6267.prdoc | 171 ++++++++++++++++++ .../cli/tests/res/default_genesis_config.json | 3 +- substrate/bin/node/testing/src/genesis.rs | 2 +- .../tests/expected/create_default.json | 3 +- .../tests/expected/create_parachain.json | 3 +- .../tests/expected/create_with_params.json | 3 +- .../tests/expected/doc/create_default.json | 3 +- .../tests/expected/doc/display_preset.json | 2 +- .../chain-spec/src/genesis_config_builder.rs | 2 +- substrate/frame/alliance/src/mock.rs | 1 + .../frame/asset-conversion/ops/src/mock.rs | 1 + substrate/frame/asset-conversion/src/mock.rs | 1 + substrate/frame/asset-rewards/src/mock.rs | 1 + substrate/frame/atomic-swap/src/tests.rs | 5 +- substrate/frame/babe/src/mock.rs | 2 +- substrate/frame/balances/Cargo.toml | 6 +- substrate/frame/balances/src/lib.rs | 64 ++++++- .../balances/src/tests/currency_tests.rs | 11 +- substrate/frame/balances/src/tests/mod.rs | 40 +++- substrate/frame/beefy/src/mock.rs | 2 +- substrate/frame/bounties/src/tests.rs | 15 +- substrate/frame/child-bounties/src/tests.rs | 1 + substrate/frame/collective/src/tests.rs | 5 +- .../frame/contracts/mock-network/src/lib.rs | 2 + substrate/frame/contracts/src/tests.rs | 2 +- .../frame/conviction-voting/src/tests.rs | 1 + substrate/frame/delegated-staking/src/mock.rs | 1 + substrate/frame/democracy/src/tests.rs | 1 + .../election-provider-multi-phase/src/mock.rs | 1 + .../test-staking-e2e/src/mock.rs | 1 + substrate/frame/elections-phragmen/src/lib.rs | 1 + substrate/frame/executive/src/tests.rs | 20 +- substrate/frame/fast-unstake/src/mock.rs | 1 + substrate/frame/grandpa/src/mock.rs | 2 +- substrate/frame/identity/src/tests.rs | 1 + substrate/frame/indices/src/mock.rs | 1 + substrate/frame/lottery/src/mock.rs | 1 + substrate/frame/multisig/src/tests.rs | 1 + substrate/frame/nis/src/mock.rs | 1 + .../test-delegate-stake/src/mock.rs | 1 + substrate/frame/preimage/src/mock.rs | 1 + substrate/frame/proxy/src/tests.rs | 1 + substrate/frame/recovery/src/mock.rs | 1 + substrate/frame/referenda/src/mock.rs | 2 +- .../frame/revive/mock-network/src/lib.rs | 2 + substrate/frame/revive/src/tests.rs | 2 +- substrate/frame/root-offences/src/mock.rs | 1 + substrate/frame/safe-mode/src/mock.rs | 1 + substrate/frame/scored-pool/src/mock.rs | 2 +- substrate/frame/society/src/mock.rs | 2 +- substrate/frame/staking/src/mock.rs | 1 + .../frame/state-trie-migration/src/lib.rs | 9 +- substrate/frame/statement/src/mock.rs | 1 + substrate/frame/tips/src/tests.rs | 6 +- .../asset-conversion-tx-payment/src/tests.rs | 1 + .../asset-tx-payment/src/tests.rs | 1 + .../frame/transaction-payment/src/tests.rs | 1 + .../frame/transaction-storage/src/mock.rs | 1 + substrate/frame/treasury/src/tests.rs | 11 +- substrate/frame/tx-pause/src/mock.rs | 1 + substrate/frame/utility/src/tests.rs | 1 + substrate/frame/vesting/src/mock.rs | 1 + .../test-utils/runtime/src/genesismap.rs | 5 +- substrate/test-utils/runtime/src/lib.rs | 2 +- 91 files changed, 434 insertions(+), 62 deletions(-) create mode 100644 prdoc/pr_6267.prdoc diff --git a/bridges/modules/messages/src/tests/mock.rs b/bridges/modules/messages/src/tests/mock.rs index 2935ebd69610f..8eebdf3a50817 100644 --- a/bridges/modules/messages/src/tests/mock.rs +++ b/bridges/modules/messages/src/tests/mock.rs @@ -461,9 +461,12 @@ pub fn inbound_unrewarded_relayers_state(lane: TestLaneIdType) -> UnrewardedRela /// Return test externalities to use in tests. pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { balances: vec![(ENDOWED_ACCOUNT, 1_000_000)] } - .assimilate_storage(&mut t) - .unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(ENDOWED_ACCOUNT, 1_000_000)], + ..Default::default() + } + .assimilate_storage(&mut t) + .unwrap(); sp_io::TestExternalities::new(t) } diff --git a/cumulus/pallets/collator-selection/src/mock.rs b/cumulus/pallets/collator-selection/src/mock.rs index d13f9e9d8c44d..6a97525c4f576 100644 --- a/cumulus/pallets/collator-selection/src/mock.rs +++ b/cumulus/pallets/collator-selection/src/mock.rs @@ -188,7 +188,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { invulnerables, }; let session = pallet_session::GenesisConfig:: { keys, ..Default::default() }; - pallet_balances::GenesisConfig:: { balances } + pallet_balances::GenesisConfig:: { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); // collator selection must be initialized before session. diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/genesis.rs index 3ffb9a704b464..4a10a1e10c733 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/genesis.rs @@ -42,6 +42,7 @@ pub fn genesis() -> Storage { .cloned() .map(|k| (k, ED * 4096 * 4096)) .collect(), + ..Default::default() }, parachain_info: asset_hub_rococo_runtime::ParachainInfoConfig { parachain_id: PARA_ID.into(), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/genesis.rs index ef7997322da73..0473686081e7e 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/genesis.rs @@ -39,6 +39,7 @@ pub fn genesis() -> Storage { system: asset_hub_westend_runtime::SystemConfig::default(), balances: asset_hub_westend_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + ..Default::default() }, parachain_info: asset_hub_westend_runtime::ParachainInfoConfig { parachain_id: PARA_ID.into(), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs index 575017f88bb59..62b2e4eed9e73 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs @@ -33,6 +33,7 @@ pub fn genesis() -> Storage { system: bridge_hub_rococo_runtime::SystemConfig::default(), balances: bridge_hub_rococo_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + ..Default::default() }, parachain_info: bridge_hub_rococo_runtime::ParachainInfoConfig { parachain_id: PARA_ID.into(), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs index eb4623084f85e..5286110bcab9a 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs @@ -33,6 +33,7 @@ pub fn genesis() -> Storage { system: bridge_hub_westend_runtime::SystemConfig::default(), balances: bridge_hub_westend_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + ..Default::default() }, parachain_info: bridge_hub_westend_runtime::ParachainInfoConfig { parachain_id: PARA_ID.into(), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/src/genesis.rs index d4ef184ea392d..51e065a4ae55d 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/src/genesis.rs @@ -30,6 +30,7 @@ pub fn genesis() -> Storage { system: collectives_westend_runtime::SystemConfig::default(), balances: collectives_westend_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + ..Default::default() }, parachain_info: collectives_westend_runtime::ParachainInfoConfig { parachain_id: PARA_ID.into(), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-rococo/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-rococo/src/genesis.rs index e0f035c368e39..f2035c8654d08 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-rococo/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-rococo/src/genesis.rs @@ -30,6 +30,7 @@ pub fn genesis() -> Storage { system: coretime_rococo_runtime::SystemConfig::default(), balances: coretime_rococo_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + ..Default::default() }, parachain_info: coretime_rococo_runtime::ParachainInfoConfig { parachain_id: PARA_ID.into(), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-westend/src/genesis.rs index 239ad3760c112..29894222eff7d 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-westend/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-westend/src/genesis.rs @@ -30,6 +30,7 @@ pub fn genesis() -> Storage { system: coretime_westend_runtime::SystemConfig::default(), balances: coretime_westend_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + ..Default::default() }, parachain_info: coretime_westend_runtime::ParachainInfoConfig { parachain_id: PARA_ID.into(), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-rococo/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-rococo/src/genesis.rs index 36a701d24c27e..9772a64d23b34 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-rococo/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-rococo/src/genesis.rs @@ -31,6 +31,7 @@ pub fn genesis() -> Storage { system: people_rococo_runtime::SystemConfig::default(), balances: people_rococo_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + ..Default::default() }, parachain_info: people_rococo_runtime::ParachainInfoConfig { parachain_id: ParaId::from(PARA_ID), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend/src/genesis.rs index 942ec1b31d2b4..377babc59f65f 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend/src/genesis.rs @@ -31,6 +31,7 @@ pub fn genesis() -> Storage { system: people_westend_runtime::SystemConfig::default(), balances: people_westend_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + ..Default::default() }, parachain_info: people_westend_runtime::ParachainInfoConfig { parachain_id: ParaId::from(PARA_ID), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs index 63510d233d2c4..e514d0cb74773 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs @@ -40,6 +40,7 @@ pub fn genesis(para_id: u32) -> Storage { system: penpal_runtime::SystemConfig::default(), balances: penpal_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + ..Default::default() }, parachain_info: penpal_runtime::ParachainInfoConfig { parachain_id: para_id.into(), diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/genesis.rs index 3d8b5b1a500f2..db9fe19dbdd73 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/genesis.rs @@ -57,6 +57,7 @@ pub fn genesis() -> Storage { system: rococo_runtime::SystemConfig::default(), balances: rococo_runtime::BalancesConfig { balances: accounts::init_balances().iter().map(|k| (k.clone(), ENDOWMENT)).collect(), + ..Default::default() }, session: rococo_runtime::SessionConfig { keys: validators::initial_authorities() diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs index f8d43cf4648db..2f02ca5f1932f 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs @@ -58,6 +58,7 @@ pub fn genesis() -> Storage { system: westend_runtime::SystemConfig::default(), balances: westend_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ENDOWMENT)).collect(), + ..Default::default() }, session: westend_runtime::SessionConfig { keys: validators::initial_authorities() diff --git a/cumulus/parachains/runtimes/test-utils/src/lib.rs b/cumulus/parachains/runtimes/test-utils/src/lib.rs index 5c33809ba67b1..b46a68312aa17 100644 --- a/cumulus/parachains/runtimes/test-utils/src/lib.rs +++ b/cumulus/parachains/runtimes/test-utils/src/lib.rs @@ -230,7 +230,7 @@ impl ExtBuilder { .unwrap(); } - pallet_balances::GenesisConfig:: { balances: self.balances } + pallet_balances::GenesisConfig:: { balances: self.balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/polkadot/runtime/common/src/assigned_slots/mod.rs b/polkadot/runtime/common/src/assigned_slots/mod.rs index dea29f53cad4f..81e2986ab6b3c 100644 --- a/polkadot/runtime/common/src/assigned_slots/mod.rs +++ b/polkadot/runtime/common/src/assigned_slots/mod.rs @@ -773,6 +773,7 @@ mod tests { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/polkadot/runtime/common/src/auctions/mock.rs b/polkadot/runtime/common/src/auctions/mock.rs index e0365d363ca23..191608f8c8783 100644 --- a/polkadot/runtime/common/src/auctions/mock.rs +++ b/polkadot/runtime/common/src/auctions/mock.rs @@ -210,6 +210,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/polkadot/runtime/common/src/crowdloan/mod.rs b/polkadot/runtime/common/src/crowdloan/mod.rs index f8b3169407e83..1b40f248bfb11 100644 --- a/polkadot/runtime/common/src/crowdloan/mod.rs +++ b/polkadot/runtime/common/src/crowdloan/mod.rs @@ -1082,6 +1082,7 @@ mod tests { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 1000), (2, 2000), (3, 3000), (4, 4000)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/polkadot/runtime/common/src/paras_registrar/mock.rs b/polkadot/runtime/common/src/paras_registrar/mock.rs index 07b8fbca5189f..bb3728f0e12a0 100644 --- a/polkadot/runtime/common/src/paras_registrar/mock.rs +++ b/polkadot/runtime/common/src/paras_registrar/mock.rs @@ -166,6 +166,7 @@ pub fn new_test_ext() -> TestExternalities { pallet_balances::GenesisConfig:: { balances: vec![(1, 10_000_000), (2, 10_000_000), (3, 10_000_000)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/polkadot/runtime/common/src/slots/mod.rs b/polkadot/runtime/common/src/slots/mod.rs index 59a1f1870b2dc..131a75f3d743c 100644 --- a/polkadot/runtime/common/src/slots/mod.rs +++ b/polkadot/runtime/common/src/slots/mod.rs @@ -578,6 +578,7 @@ mod tests { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/polkadot/xcm/docs/src/cookbook/relay_token_transactor/network.rs b/polkadot/xcm/docs/src/cookbook/relay_token_transactor/network.rs index 46ac0e5df6372..71c14f6b241b6 100644 --- a/polkadot/xcm/docs/src/cookbook/relay_token_transactor/network.rs +++ b/polkadot/xcm/docs/src/cookbook/relay_token_transactor/network.rs @@ -78,9 +78,12 @@ pub fn relay_ext() -> TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { balances: vec![(ALICE, INITIAL_BALANCE)] } - .assimilate_storage(&mut t) - .unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(ALICE, INITIAL_BALANCE)], + ..Default::default() + } + .assimilate_storage(&mut t) + .unwrap(); let mut ext = TestExternalities::new(t); ext.execute_with(|| { diff --git a/polkadot/xcm/pallet-xcm/src/mock.rs b/polkadot/xcm/pallet-xcm/src/mock.rs index 8d0476b0e70d7..58b4226ccf191 100644 --- a/polkadot/xcm/pallet-xcm/src/mock.rs +++ b/polkadot/xcm/pallet-xcm/src/mock.rs @@ -700,7 +700,7 @@ pub(crate) fn new_test_ext_with_balances_and_xcm_version( ) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { balances } + pallet_balances::GenesisConfig:: { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/mock.rs b/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/mock.rs index e6fe8e45c2651..55a924dbaa63e 100644 --- a/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/mock.rs +++ b/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/mock.rs @@ -339,6 +339,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pallet_balances::GenesisConfig:: { balances: vec![(0, INITIAL_BALANCE), (1, INITIAL_BALANCE), (2, INITIAL_BALANCE)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/polkadot/xcm/xcm-builder/tests/mock/mod.rs b/polkadot/xcm/xcm-builder/tests/mock/mod.rs index 0468b0a5410c4..c3e5328450824 100644 --- a/polkadot/xcm/xcm-builder/tests/mock/mod.rs +++ b/polkadot/xcm/xcm-builder/tests/mock/mod.rs @@ -243,7 +243,7 @@ construct_runtime!( pub fn kusama_like_with_balances(balances: Vec<(AccountId, Balance)>) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { balances } + pallet_balances::GenesisConfig:: { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/polkadot/xcm/xcm-runtime-apis/tests/mock.rs b/polkadot/xcm/xcm-runtime-apis/tests/mock.rs index 56a77094f1774..18d9dce9245a3 100644 --- a/polkadot/xcm/xcm-runtime-apis/tests/mock.rs +++ b/polkadot/xcm/xcm-runtime-apis/tests/mock.rs @@ -365,7 +365,7 @@ impl pallet_xcm::Config for TestRuntime { pub fn new_test_ext_with_balances(balances: Vec<(AccountId, Balance)>) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { balances } + pallet_balances::GenesisConfig:: { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); @@ -381,7 +381,7 @@ pub fn new_test_ext_with_balances_and_assets( ) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { balances } + pallet_balances::GenesisConfig:: { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/polkadot/xcm/xcm-simulator/example/src/lib.rs b/polkadot/xcm/xcm-simulator/example/src/lib.rs index 6fb9a69770ea8..8a05569831b5c 100644 --- a/polkadot/xcm/xcm-simulator/example/src/lib.rs +++ b/polkadot/xcm/xcm-simulator/example/src/lib.rs @@ -101,6 +101,7 @@ pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { pallet_balances::GenesisConfig:: { balances: vec![(ALICE, INITIAL_BALANCE), (parent_account_id(), INITIAL_BALANCE)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); @@ -125,6 +126,7 @@ pub fn relay_ext() -> sp_io::TestExternalities { (child_account_id(1), INITIAL_BALANCE), (child_account_id(2), INITIAL_BALANCE), ], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/polkadot/xcm/xcm-simulator/fuzzer/src/fuzz.rs b/polkadot/xcm/xcm-simulator/fuzzer/src/fuzz.rs index adf6cacd278b9..8ea5e033f3ad7 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/src/fuzz.rs +++ b/polkadot/xcm/xcm-simulator/fuzzer/src/fuzz.rs @@ -117,6 +117,7 @@ pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { pallet_balances::GenesisConfig:: { balances: (0..6).map(|i| ([i; 32].into(), INITIAL_BALANCE)).collect(), + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); @@ -138,7 +139,7 @@ pub fn relay_ext() -> sp_io::TestExternalities { balances.append(&mut (1..=3).map(|i| (para_account_id(i), INITIAL_BALANCE)).collect()); balances.append(&mut (0..6).map(|i| ([i; 32].into(), INITIAL_BALANCE)).collect()); - pallet_balances::GenesisConfig:: { balances } + pallet_balances::GenesisConfig:: { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/prdoc/pr_6267.prdoc b/prdoc/pr_6267.prdoc new file mode 100644 index 0000000000000..30ada44562592 --- /dev/null +++ b/prdoc/pr_6267.prdoc @@ -0,0 +1,171 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Allow configurable number of genesis accounts with specified balances for benchmarking. + +doc: + - audience: Runtime Dev + description: | + This pull request adds an additional field `dev_accounts` to the `GenesisConfig` + of the balances pallet, feature gated by `runtime-benchmarks`. + + Bringing about an abitrary number of derived dev accounts when building the genesis + state. Runtime developers should supply a derivation path that includes an index placeholder + (i.e. "//Sender/{}") to generate multiple accounts from the same root in a consistent + manner. + +crates: + - name: substrate-test-runtime + bump: minor + - name: pallet-vesting + bump: patch + - name: pallet-utility + bump: patch + - name: pallet-tx-pause + bump: patch + - name: pallet-treasury + bump: patch + - name: pallet-transaction-storage + bump: patch + - name: pallet-transaction-payment + bump: patch + - name: pallet-asset-tx-payment + bump: patch + - name: pallet-asset-conversion-tx-payment + bump: patch + - name: pallet-tips + bump: patch + - name: pallet-state-trie-migration + bump: patch + - name: pallet-staking + bump: patch + - name: pallet-society + bump: patch + - name: pallet-safe-mode + bump: patch + - name: pallet-scored-pool + bump: patch + - name: pallet-statement + bump: patch + - name: pallet-root-offences + bump: patch + - name: pallet-revive + bump: patch + - name: pallet-revive-mock-network + bump: patch + - name: pallet-referenda + bump: patch + - name: pallet-recovery + bump: patch + - name: pallet-proxy + bump: patch + - name: pallet-preimage + bump: patch + - name: pallet-nis + bump: patch + - name: pallet-nomination-pools-test-delegate-stake + bump: minor + - name: pallet-multisig + bump: patch + - name: pallet-lottery + bump: patch + - name: pallet-indices + bump: patch + - name: pallet-identity + bump: patch + - name: pallet-grandpa + bump: patch + - name: pallet-fast-unstake + bump: patch + - name: frame-executive + bump: patch + - name: pallet-elections-phragmen + bump: patch + - name: pallet-election-provider-e2e-test + bump: minor + - name: pallet-election-provider-multi-phase + bump: patch + - name: pallet-democracy + bump: patch + - name: pallet-delegated-staking + bump: patch + - name: pallet-conviction-voting + bump: patch + - name: pallet-contracts + bump: patch + - name: pallet-contracts-mock-network + bump: patch + - name: pallet-collective + bump: patch + - name: pallet-child-bounties + bump: patch + - name: pallet-bounties + bump: patch + - name: pallet-beefy + bump: patch + - name: pallet-balances + bump: major + - name: pallet-babe + bump: patch + - name: pallet-asset-conversion + bump: patch + - name: pallet-asset-conversion-ops + bump: patch + - name: pallet-asset-rewards + bump: patch + - name: pallet-atomic-swap + bump: patch + - name: pallet-alliance + bump: patch + - name: node-testing + bump: minor + - name: sc-chain-spec + bump: patch + - name: staging-chain-spec-builder + bump: patch + - name: xcm-simulator-fuzzer + bump: minor + - name: xcm-simulator-fuzzer + bump: minor + - name: xcm-simulator-example + bump: patch + - name: xcm-runtime-apis + bump: patch + - name: staging-xcm-builder + bump: patch + - name: pallet-xcm + bump: patch + - name: xcm-docs + bump: minor + - name: polkadot-runtime-common + bump: patch + - name: parachains-runtimes-test-utils + bump: patch + - name: westend-emulated-chain + bump: minor + - name: rococo-emulated-chain + bump: minor + - name: penpal-emulated-chain + bump: minor + - name: people-westend-emulated-chain + bump: minor + - name: people-rococo-emulated-chain + bump: minor + - name: coretime-westend-emulated-chain + bump: minor + - name: coretime-rococo-emulated-chain + bump: minor + - name: collectives-westend-emulated-chain + bump: minor + - name: bridge-hub-westend-emulated-chain + bump: minor + - name: bridge-hub-rococo-emulated-chain + bump: minor + - name: asset-hub-westend-emulated-chain + bump: minor + - name: asset-hub-rococo-emulated-chain + bump: minor + - name: pallet-collator-selection + bump: patch + - name: pallet-bridge-messages + bump: patch diff --git a/substrate/bin/node/cli/tests/res/default_genesis_config.json b/substrate/bin/node/cli/tests/res/default_genesis_config.json index a2e52837d8822..8ad2428f78554 100644 --- a/substrate/bin/node/cli/tests/res/default_genesis_config.json +++ b/substrate/bin/node/cli/tests/res/default_genesis_config.json @@ -14,7 +14,8 @@ "indices": [] }, "balances": { - "balances": [] + "balances": [], + "devAccounts": null }, "broker": {}, "transactionPayment": { diff --git a/substrate/bin/node/testing/src/genesis.rs b/substrate/bin/node/testing/src/genesis.rs index 0394f6cd7394d..624b00b4d6c23 100644 --- a/substrate/bin/node/testing/src/genesis.rs +++ b/substrate/bin/node/testing/src/genesis.rs @@ -47,7 +47,7 @@ pub fn config_endowed(extra_endowed: Vec) -> RuntimeGenesisConfig { RuntimeGenesisConfig { indices: IndicesConfig { indices: vec![] }, - balances: BalancesConfig { balances: endowed }, + balances: BalancesConfig { balances: endowed, ..Default::default() }, session: SessionConfig { keys: vec![ (alice(), dave(), session_keys_from_seed(Ed25519Keyring::Alice.into())), diff --git a/substrate/bin/utils/chain-spec-builder/tests/expected/create_default.json b/substrate/bin/utils/chain-spec-builder/tests/expected/create_default.json index ac67aef93345f..77891ac93ead1 100644 --- a/substrate/bin/utils/chain-spec-builder/tests/expected/create_default.json +++ b/substrate/bin/utils/chain-spec-builder/tests/expected/create_default.json @@ -25,7 +25,8 @@ } }, "balances": { - "balances": [] + "balances": [], + "devAccounts": null }, "substrateTest": { "authorities": [] diff --git a/substrate/bin/utils/chain-spec-builder/tests/expected/create_parachain.json b/substrate/bin/utils/chain-spec-builder/tests/expected/create_parachain.json index 7106b4b50dc59..22b0ca6571b40 100644 --- a/substrate/bin/utils/chain-spec-builder/tests/expected/create_parachain.json +++ b/substrate/bin/utils/chain-spec-builder/tests/expected/create_parachain.json @@ -27,7 +27,8 @@ } }, "balances": { - "balances": [] + "balances": [], + "devAccounts": null }, "substrateTest": { "authorities": [] diff --git a/substrate/bin/utils/chain-spec-builder/tests/expected/create_with_params.json b/substrate/bin/utils/chain-spec-builder/tests/expected/create_with_params.json index 5aedd5b5c18ba..641df669e1888 100644 --- a/substrate/bin/utils/chain-spec-builder/tests/expected/create_with_params.json +++ b/substrate/bin/utils/chain-spec-builder/tests/expected/create_with_params.json @@ -25,7 +25,8 @@ } }, "balances": { - "balances": [] + "balances": [], + "devAccounts": null }, "substrateTest": { "authorities": [] diff --git a/substrate/bin/utils/chain-spec-builder/tests/expected/doc/create_default.json b/substrate/bin/utils/chain-spec-builder/tests/expected/doc/create_default.json index 203b6716cb268..e5957624ead2d 100644 --- a/substrate/bin/utils/chain-spec-builder/tests/expected/doc/create_default.json +++ b/substrate/bin/utils/chain-spec-builder/tests/expected/doc/create_default.json @@ -24,7 +24,8 @@ } }, "balances": { - "balances": [] + "balances": [], + "devAccounts": null }, "substrateTest": { "authorities": [] diff --git a/substrate/bin/utils/chain-spec-builder/tests/expected/doc/display_preset.json b/substrate/bin/utils/chain-spec-builder/tests/expected/doc/display_preset.json index 6aa6799af771d..6bbb475d35c78 100644 --- a/substrate/bin/utils/chain-spec-builder/tests/expected/doc/display_preset.json +++ b/substrate/bin/utils/chain-spec-builder/tests/expected/doc/display_preset.json @@ -1 +1 @@ -{"babe":{"authorities":[],"epochConfig":{"allowed_slots":"PrimaryAndSecondaryVRFSlots","c":[1,4]}},"balances":{"balances":[]},"substrateTest":{"authorities":[]},"system":{}} +{"babe":{"authorities":[],"epochConfig":{"allowed_slots":"PrimaryAndSecondaryVRFSlots","c":[1,4]}},"balances":{"balances":[], "devAccounts": null},"substrateTest":{"authorities":[]},"system":{}} diff --git a/substrate/client/chain-spec/src/genesis_config_builder.rs b/substrate/client/chain-spec/src/genesis_config_builder.rs index 5fe8f9dc053c1..c7b5ae4bf1680 100644 --- a/substrate/client/chain-spec/src/genesis_config_builder.rs +++ b/substrate/client/chain-spec/src/genesis_config_builder.rs @@ -196,7 +196,7 @@ mod tests { ::new(substrate_test_runtime::wasm_binary_unwrap()) .get_default_config() .unwrap(); - let expected = r#"{"babe": {"authorities": [], "epochConfig": {"allowed_slots": "PrimaryAndSecondaryVRFSlots", "c": [1, 4]}}, "balances": {"balances": []}, "substrateTest": {"authorities": []}, "system": {}}"#; + let expected = r#"{"babe": {"authorities": [], "epochConfig": {"allowed_slots": "PrimaryAndSecondaryVRFSlots", "c": [1, 4]}}, "balances": {"balances": [], "devAccounts": null}, "substrateTest": {"authorities": []}, "system": {}}"#; assert_eq!(from_str::(expected).unwrap(), config); } diff --git a/substrate/frame/alliance/src/mock.rs b/substrate/frame/alliance/src/mock.rs index 625cabf3457f3..069c29a88d38c 100644 --- a/substrate/frame/alliance/src/mock.rs +++ b/substrate/frame/alliance/src/mock.rs @@ -283,6 +283,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { (8, 1000), (9, 1000), ], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/asset-conversion/ops/src/mock.rs b/substrate/frame/asset-conversion/ops/src/mock.rs index 5c05faa6aa88d..576b266b39c17 100644 --- a/substrate/frame/asset-conversion/ops/src/mock.rs +++ b/substrate/frame/asset-conversion/ops/src/mock.rs @@ -135,6 +135,7 @@ pub(crate) fn new_test_ext() -> sp_io::TestExternalities { pallet_balances::GenesisConfig:: { balances: vec![(1, 10000), (2, 20000), (3, 30000), (4, 40000)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/asset-conversion/src/mock.rs b/substrate/frame/asset-conversion/src/mock.rs index d8832d70488af..313d9f9857e49 100644 --- a/substrate/frame/asset-conversion/src/mock.rs +++ b/substrate/frame/asset-conversion/src/mock.rs @@ -162,6 +162,7 @@ pub(crate) fn new_test_ext() -> sp_io::TestExternalities { pallet_balances::GenesisConfig:: { balances: vec![(1, 10000), (2, 20000), (3, 30000), (4, 40000)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/asset-rewards/src/mock.rs b/substrate/frame/asset-rewards/src/mock.rs index 87c8a8a0dea0b..1e9b41104d4cd 100644 --- a/substrate/frame/asset-rewards/src/mock.rs +++ b/substrate/frame/asset-rewards/src/mock.rs @@ -211,6 +211,7 @@ pub(crate) fn new_test_ext() -> sp_io::TestExternalities { (20, 40000), (pool_zero_account_id, 100_000), // Top up the default pool account id ], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/atomic-swap/src/tests.rs b/substrate/frame/atomic-swap/src/tests.rs index 6fcc5571a5232..d6384fab343d6 100644 --- a/substrate/frame/atomic-swap/src/tests.rs +++ b/substrate/frame/atomic-swap/src/tests.rs @@ -54,7 +54,10 @@ const B: u64 = 2; pub fn new_test_ext() -> TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - let genesis = pallet_balances::GenesisConfig:: { balances: vec![(A, 100), (B, 200)] }; + let genesis = pallet_balances::GenesisConfig:: { + balances: vec![(A, 100), (B, 200)], + ..Default::default() + }; genesis.assimilate_storage(&mut t).unwrap(); t.into() } diff --git a/substrate/frame/babe/src/mock.rs b/substrate/frame/babe/src/mock.rs index 8d00509e800b6..6f9f54cc7efcb 100644 --- a/substrate/frame/babe/src/mock.rs +++ b/substrate/frame/babe/src/mock.rs @@ -314,7 +314,7 @@ pub fn new_test_ext_raw_authorities(authorities: Vec) -> sp_io::Tes let balances: Vec<_> = (0..authorities.len()).map(|i| (i as u64, 10_000_000)).collect(); - pallet_balances::GenesisConfig:: { balances } + pallet_balances::GenesisConfig:: { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/balances/Cargo.toml b/substrate/frame/balances/Cargo.toml index 03bc7fcb3fcc0..4255ed4143601 100644 --- a/substrate/frame/balances/Cargo.toml +++ b/substrate/frame/balances/Cargo.toml @@ -23,13 +23,15 @@ frame-support = { workspace = true } frame-system = { workspace = true } log = { workspace = true } scale-info = { features = ["derive"], workspace = true } +sp-core = { workspace = true } sp-runtime = { workspace = true } [dev-dependencies] -frame-support = { features = ["experimental"], workspace = true, default-features = true } +frame-support = { features = [ + "experimental", +], workspace = true, default-features = true } pallet-transaction-payment = { workspace = true, default-features = true } paste = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } [features] diff --git a/substrate/frame/balances/src/lib.rs b/substrate/frame/balances/src/lib.rs index 9d74014521010..e994f05a77c51 100644 --- a/substrate/frame/balances/src/lib.rs +++ b/substrate/frame/balances/src/lib.rs @@ -152,7 +152,11 @@ pub mod weights; extern crate alloc; -use alloc::vec::Vec; +use alloc::{ + format, + string::{String, ToString}, + vec::Vec, +}; use codec::{Codec, MaxEncodedLen}; use core::{cmp, fmt::Debug, mem, result}; use frame_support::{ @@ -173,6 +177,7 @@ use frame_support::{ use frame_system as system; pub use impl_currency::{NegativeImbalance, PositiveImbalance}; use scale_info::TypeInfo; +use sp_core::{sr25519::Pair as SrPair, Pair}; use sp_runtime::{ traits::{ AtLeast32BitUnsigned, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Saturating, @@ -180,6 +185,7 @@ use sp_runtime::{ }, ArithmeticError, DispatchError, FixedPointOperand, Perbill, RuntimeDebug, TokenError, }; + pub use types::{ AccountData, AdjustmentDirection, BalanceLock, DustCleaner, ExtraFlags, Reasons, ReserveData, }; @@ -189,6 +195,9 @@ pub use pallet::*; const LOG_TARGET: &str = "runtime::balances"; +// Default derivation(hard) for development accounts. +const DEFAULT_ADDRESS_URI: &str = "//Sender//{}"; + type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; #[frame_support::pallet] @@ -505,11 +514,18 @@ pub mod pallet { #[pallet::genesis_config] pub struct GenesisConfig, I: 'static = ()> { pub balances: Vec<(T::AccountId, T::Balance)>, + /// Derived development accounts(Optional): + /// - `u32`: The number of development accounts to generate. + /// - `T::Balance`: The initial balance assigned to each development account. + /// - `String`: An optional derivation(hard) string template. + /// - Must include `{}` as a placeholder for account indices. + /// - Defaults to `"//Sender//{}`" if `None`. + pub dev_accounts: Option<(u32, T::Balance, Option)>, } impl, I: 'static> Default for GenesisConfig { fn default() -> Self { - Self { balances: Default::default() } + Self { balances: Default::default(), dev_accounts: None } } } @@ -540,6 +556,15 @@ pub mod pallet { "duplicate balances in genesis." ); + // Generate additional dev accounts. + if let Some((num_accounts, balance, ref derivation)) = self.dev_accounts { + // Using the provided derivation string or default to `"//Sender//{}`". + Pallet::::derive_dev_account( + num_accounts, + balance, + derivation.as_deref().unwrap_or(DEFAULT_ADDRESS_URI), + ); + } for &(ref who, free) in self.balances.iter() { frame_system::Pallet::::inc_providers(who); assert!(T::AccountStore::insert(who, AccountData { free, ..Default::default() }) @@ -1248,5 +1273,40 @@ pub mod pallet { }); Ok(actual) } + + /// Generate dev account from derivation(hard) string. + pub fn derive_dev_account(num_accounts: u32, balance: T::Balance, derivation: &str) { + // Ensure that the number of accounts is not zero. + assert!(num_accounts > 0, "num_accounts must be greater than zero"); + + assert!( + balance >= >::ExistentialDeposit::get(), + "the balance of any account should always be at least the existential deposit.", + ); + + assert!( + derivation.contains("{}"), + "Invalid derivation, expected `{{}}` as part of the derivation" + ); + + for index in 0..num_accounts { + // Replace "{}" in the derivation string with the index. + let derivation_string = derivation.replace("{}", &index.to_string()); + + // Generate the key pair from the derivation string using sr25519. + let pair: SrPair = Pair::from_string(&derivation_string, None) + .expect(&format!("Failed to parse derivation string: {derivation_string}")); + + // Convert the public key to AccountId. + let who = T::AccountId::decode(&mut &pair.public().encode()[..]) + .expect(&format!("Failed to decode public key from pair: {:?}", pair.public())); + + // Set the balance for the generated account. + Self::mutate_account_handling_dust(&who, |account| { + account.free = balance; + }) + .expect(&format!("Failed to add account to keystore: {:?}", who)); + } + } } } diff --git a/substrate/frame/balances/src/tests/currency_tests.rs b/substrate/frame/balances/src/tests/currency_tests.rs index 5ad818e5bfa21..a6377c3ad72e8 100644 --- a/substrate/frame/balances/src/tests/currency_tests.rs +++ b/substrate/frame/balances/src/tests/currency_tests.rs @@ -721,7 +721,7 @@ fn burn_must_work() { fn cannot_set_genesis_value_below_ed() { EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = 11); let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - let _ = crate::GenesisConfig:: { balances: vec![(1, 10)] } + let _ = crate::GenesisConfig:: { balances: vec![(1, 10)], ..Default::default() } .assimilate_storage(&mut t) .unwrap(); } @@ -730,9 +730,12 @@ fn cannot_set_genesis_value_below_ed() { #[should_panic = "duplicate balances in genesis."] fn cannot_set_genesis_value_twice() { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - let _ = crate::GenesisConfig:: { balances: vec![(1, 10), (2, 20), (1, 15)] } - .assimilate_storage(&mut t) - .unwrap(); + let _ = crate::GenesisConfig:: { + balances: vec![(1, 10), (2, 20), (1, 15)], + ..Default::default() + } + .assimilate_storage(&mut t) + .unwrap(); } #[test] diff --git a/substrate/frame/balances/src/tests/mod.rs b/substrate/frame/balances/src/tests/mod.rs index bf49ad9f0a1f0..ceb8e8134f0a2 100644 --- a/substrate/frame/balances/src/tests/mod.rs +++ b/substrate/frame/balances/src/tests/mod.rs @@ -19,7 +19,10 @@ #![cfg(test)] -use crate::{self as pallet_balances, AccountData, Config, CreditOf, Error, Pallet, TotalIssuance}; +use crate::{ + self as pallet_balances, AccountData, Config, CreditOf, Error, Pallet, TotalIssuance, + DEFAULT_ADDRESS_URI, +}; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ assert_err, assert_noop, assert_ok, assert_storage_noop, derive_impl, @@ -34,7 +37,7 @@ use frame_support::{ use frame_system::{self as system, RawOrigin}; use pallet_transaction_payment::{ChargeTransactionPayment, FungibleAdapter, Multiplier}; use scale_info::TypeInfo; -use sp_core::hexdisplay::HexDisplay; +use sp_core::{hexdisplay::HexDisplay, sr25519::Pair as SrPair, Pair}; use sp_io; use sp_runtime::{ traits::{BadOrigin, Zero}, @@ -169,6 +172,11 @@ impl ExtBuilder { } else { vec![] }, + dev_accounts: Some(( + 1000, + self.existential_deposit, + Some(DEFAULT_ADDRESS_URI.to_string()), + )), } .assimilate_storage(&mut t) .unwrap(); @@ -281,7 +289,32 @@ pub fn info_from_weight(w: Weight) -> DispatchInfo { pub fn ensure_ti_valid() { let mut sum = 0; + // Fetch the dev accounts from Account Storage. + let dev_accounts = (1000, EXISTENTIAL_DEPOSIT, DEFAULT_ADDRESS_URI.to_string()); + let (num_accounts, _balance, ref derivation) = dev_accounts; + + // Generate the dev account public keys. + let dev_account_ids: Vec<_> = (0..num_accounts) + .map(|index| { + let derivation_string = derivation.replace("{}", &index.to_string()); + let pair: SrPair = + Pair::from_string(&derivation_string, None).expect("Invalid derivation string"); + ::AccountId::decode( + &mut &pair.public().encode()[..], + ) + .unwrap() + }) + .collect(); + + // Iterate over all account keys (i.e., the account IDs). for acc in frame_system::Account::::iter_keys() { + // Skip dev accounts by checking if the account is in the dev_account_ids list. + // This also proves dev_accounts exists in storage. + if dev_account_ids.contains(&acc) { + continue; + } + + // Check if we are using the system pallet or some other custom storage for accounts. if UseSystem::get() { let data = frame_system::Pallet::::account(acc); sum += data.data.total(); @@ -291,7 +324,8 @@ pub fn ensure_ti_valid() { } } - assert_eq!(TotalIssuance::::get(), sum, "Total Issuance wrong"); + // Ensure the total issuance matches the sum of the account balances + assert_eq!(TotalIssuance::::get(), sum, "Total Issuance is incorrect"); } #[test] diff --git a/substrate/frame/beefy/src/mock.rs b/substrate/frame/beefy/src/mock.rs index 4b5f1d103b506..fc731e3bc50e9 100644 --- a/substrate/frame/beefy/src/mock.rs +++ b/substrate/frame/beefy/src/mock.rs @@ -282,7 +282,7 @@ impl ExtBuilder { let balances: Vec<_> = (0..self.authorities.len()).map(|i| (i as u64, 10_000_000)).collect(); - pallet_balances::GenesisConfig:: { balances } + pallet_balances::GenesisConfig:: { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/bounties/src/tests.rs b/substrate/frame/bounties/src/tests.rs index 447d0edb4122d..c9f6c1319ed1e 100644 --- a/substrate/frame/bounties/src/tests.rs +++ b/substrate/frame/bounties/src/tests.rs @@ -187,7 +187,10 @@ impl ExtBuilder { pub fn build(self) -> sp_io::TestExternalities { let mut ext: sp_io::TestExternalities = RuntimeGenesisConfig { system: frame_system::GenesisConfig::default(), - balances: pallet_balances::GenesisConfig { balances: vec![(0, 100), (1, 98), (2, 1)] }, + balances: pallet_balances::GenesisConfig { + balances: vec![(0, 100), (1, 98), (2, 1)], + ..Default::default() + }, treasury: Default::default(), treasury_1: Default::default(), } @@ -338,9 +341,12 @@ fn treasury_account_doesnt_get_deleted() { #[allow(deprecated)] fn inexistent_account_works() { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { balances: vec![(0, 100), (1, 99), (2, 1)] } - .assimilate_storage(&mut t) - .unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(0, 100), (1, 99), (2, 1)], + ..Default::default() + } + .assimilate_storage(&mut t) + .unwrap(); // Treasury genesis config is not build thus treasury account does not exist let mut t: sp_io::TestExternalities = t.into(); @@ -977,6 +983,7 @@ fn genesis_funding_works() { pallet_balances::GenesisConfig:: { // Total issuance will be 200 with treasury account initialized with 100. balances: vec![(0, 100), (Treasury::account_id(), initial_funding)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/child-bounties/src/tests.rs b/substrate/frame/child-bounties/src/tests.rs index 939983054f667..50c8adb453e5e 100644 --- a/substrate/frame/child-bounties/src/tests.rs +++ b/substrate/frame/child-bounties/src/tests.rs @@ -148,6 +148,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pallet_balances::GenesisConfig:: { // Total issuance will be 200 with treasury account initialized at ED. balances: vec![(account_id(0), 100), (account_id(1), 98), (account_id(2), 1)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/collective/src/tests.rs b/substrate/frame/collective/src/tests.rs index c4ed17821ae89..300d5ad3772a9 100644 --- a/substrate/frame/collective/src/tests.rs +++ b/substrate/frame/collective/src/tests.rs @@ -203,7 +203,10 @@ impl ExtBuilder { let mut ext: sp_io::TestExternalities = RuntimeGenesisConfig { system: frame_system::GenesisConfig::default(), // balances: pallet_balances::GenesisConfig::default(), - balances: pallet_balances::GenesisConfig { balances: vec![(1, 100), (2, 200)] }, + balances: pallet_balances::GenesisConfig { + balances: vec![(1, 100), (2, 200)], + ..Default::default() + }, collective: pallet_collective::GenesisConfig { members: self.collective_members, phantom: Default::default(), diff --git a/substrate/frame/contracts/mock-network/src/lib.rs b/substrate/frame/contracts/mock-network/src/lib.rs index cb9e22439b76d..c918cd39ed915 100644 --- a/substrate/frame/contracts/mock-network/src/lib.rs +++ b/substrate/frame/contracts/mock-network/src/lib.rs @@ -99,6 +99,7 @@ pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { (relay_sovereign_account_id(), INITIAL_BALANCE), (BOB, INITIAL_BALANCE), ], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); @@ -137,6 +138,7 @@ pub fn relay_ext() -> sp_io::TestExternalities { (parachain_sovereign_account_id(1), INITIAL_BALANCE), (parachain_account_sovereign_account_id(1, ALICE), INITIAL_BALANCE), ], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/contracts/src/tests.rs b/substrate/frame/contracts/src/tests.rs index b01d0aa4fa48a..9bba55f82b4e1 100644 --- a/substrate/frame/contracts/src/tests.rs +++ b/substrate/frame/contracts/src/tests.rs @@ -553,7 +553,7 @@ impl ExtBuilder { sp_tracing::try_init_simple(); self.set_associated_consts(); let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { balances: vec![] } + pallet_balances::GenesisConfig:: { balances: vec![], ..Default::default() } .assimilate_storage(&mut t) .unwrap(); let mut ext = sp_io::TestExternalities::new(t); diff --git a/substrate/frame/conviction-voting/src/tests.rs b/substrate/frame/conviction-voting/src/tests.rs index dd9ee33ee1835..b1b1fab5ae50e 100644 --- a/substrate/frame/conviction-voting/src/tests.rs +++ b/substrate/frame/conviction-voting/src/tests.rs @@ -160,6 +160,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/delegated-staking/src/mock.rs b/substrate/frame/delegated-staking/src/mock.rs index 875279864f7ab..a4546e57dab5e 100644 --- a/substrate/frame/delegated-staking/src/mock.rs +++ b/substrate/frame/delegated-staking/src/mock.rs @@ -189,6 +189,7 @@ impl ExtBuilder { (GENESIS_NOMINATOR_ONE, 1000), (GENESIS_NOMINATOR_TWO, 2000), ], + ..Default::default() } .assimilate_storage(&mut storage); diff --git a/substrate/frame/democracy/src/tests.rs b/substrate/frame/democracy/src/tests.rs index 10e5ee75611d5..7777448006848 100644 --- a/substrate/frame/democracy/src/tests.rs +++ b/substrate/frame/democracy/src/tests.rs @@ -169,6 +169,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/election-provider-multi-phase/src/mock.rs b/substrate/frame/election-provider-multi-phase/src/mock.rs index 2e5ac2527203f..d0797e100fcdf 100644 --- a/substrate/frame/election-provider-multi-phase/src/mock.rs +++ b/substrate/frame/election-provider-multi-phase/src/mock.rs @@ -600,6 +600,7 @@ impl ExtBuilder { (999, 100), (9999, 100), ], + ..Default::default() } .assimilate_storage(&mut storage); diff --git a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs index bcb25f8287b35..3a64964361870 100644 --- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs +++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs @@ -567,6 +567,7 @@ impl ExtBuilder { let _ = pallet_balances::GenesisConfig:: { balances: self.balances_builder.balances.clone(), + ..Default::default() } .assimilate_storage(&mut storage); diff --git a/substrate/frame/elections-phragmen/src/lib.rs b/substrate/frame/elections-phragmen/src/lib.rs index fa1c48ee65eda..4a40d44e40776 100644 --- a/substrate/frame/elections-phragmen/src/lib.rs +++ b/substrate/frame/elections-phragmen/src/lib.rs @@ -1476,6 +1476,7 @@ mod tests { (5, 50 * self.balance_factor), (6, 60 * self.balance_factor), ], + ..Default::default() }, elections: elections_phragmen::GenesisConfig:: { members: self.genesis_members, diff --git a/substrate/frame/executive/src/tests.rs b/substrate/frame/executive/src/tests.rs index 882d875f3d804..dd12a85a1114c 100644 --- a/substrate/frame/executive/src/tests.rs +++ b/substrate/frame/executive/src/tests.rs @@ -576,7 +576,7 @@ fn call_transfer(dest: u64, value: u64) -> RuntimeCall { #[test] fn balance_transfer_dispatch_works() { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { balances: vec![(1, 211)] } + pallet_balances::GenesisConfig:: { balances: vec![(1, 211)], ..Default::default() } .assimilate_storage(&mut t) .unwrap(); let xt = UncheckedXt::new_signed(call_transfer(2, 69), 1, 1.into(), tx_ext(0, 0)); @@ -598,9 +598,12 @@ fn balance_transfer_dispatch_works() { fn new_test_ext(balance_factor: Balance) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { balances: vec![(1, 111 * balance_factor)] } - .assimilate_storage(&mut t) - .unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(1, 111 * balance_factor)], + ..Default::default() + } + .assimilate_storage(&mut t) + .unwrap(); let mut ext: sp_io::TestExternalities = t.into(); ext.execute_with(|| { SystemCallbacksCalled::set(0); @@ -610,9 +613,12 @@ fn new_test_ext(balance_factor: Balance) -> sp_io::TestExternalities { fn new_test_ext_v0(balance_factor: Balance) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { balances: vec![(1, 111 * balance_factor)] } - .assimilate_storage(&mut t) - .unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(1, 111 * balance_factor)], + ..Default::default() + } + .assimilate_storage(&mut t) + .unwrap(); (t, sp_runtime::StateVersion::V0).into() } diff --git a/substrate/frame/fast-unstake/src/mock.rs b/substrate/frame/fast-unstake/src/mock.rs index cf4f5f49240e9..67f7ee21e6175 100644 --- a/substrate/frame/fast-unstake/src/mock.rs +++ b/substrate/frame/fast-unstake/src/mock.rs @@ -228,6 +228,7 @@ impl ExtBuilder { .chain(validators_range.clone().map(|x| (x, 7 + 1 + 100))) .chain(nominators_range.clone().map(|x| (x, 7 + 1 + 100))) .collect::>(), + ..Default::default() } .assimilate_storage(&mut storage); diff --git a/substrate/frame/grandpa/src/mock.rs b/substrate/frame/grandpa/src/mock.rs index 0a85d9ffd2b08..cb754fb7955b5 100644 --- a/substrate/frame/grandpa/src/mock.rs +++ b/substrate/frame/grandpa/src/mock.rs @@ -226,7 +226,7 @@ pub fn new_test_ext_raw_authorities(authorities: AuthorityList) -> sp_io::TestEx let balances: Vec<_> = (0..authorities.len()).map(|i| (i as u64, 10_000_000)).collect(); - pallet_balances::GenesisConfig:: { balances } + pallet_balances::GenesisConfig:: { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/identity/src/tests.rs b/substrate/frame/identity/src/tests.rs index 01bc312723aa5..c4c02a2834ac4 100644 --- a/substrate/frame/identity/src/tests.rs +++ b/substrate/frame/identity/src/tests.rs @@ -105,6 +105,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { (account(20), 1000), (account(30), 1000), ], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/indices/src/mock.rs b/substrate/frame/indices/src/mock.rs index 72bbc6dab4a42..80d0a88881f97 100644 --- a/substrate/frame/indices/src/mock.rs +++ b/substrate/frame/indices/src/mock.rs @@ -59,6 +59,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/lottery/src/mock.rs b/substrate/frame/lottery/src/mock.rs index b771ed0849f69..ea3f69b6cfc5d 100644 --- a/substrate/frame/lottery/src/mock.rs +++ b/substrate/frame/lottery/src/mock.rs @@ -75,6 +75,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/multisig/src/tests.rs b/substrate/frame/multisig/src/tests.rs index 4065ce73f9055..8a389314256be 100644 --- a/substrate/frame/multisig/src/tests.rs +++ b/substrate/frame/multisig/src/tests.rs @@ -75,6 +75,7 @@ pub fn new_test_ext() -> TestState { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10), (2, 10), (3, 10), (4, 10), (5, 2)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/nis/src/mock.rs b/substrate/frame/nis/src/mock.rs index 08e69ef0de054..82b9f55b919be 100644 --- a/substrate/frame/nis/src/mock.rs +++ b/substrate/frame/nis/src/mock.rs @@ -133,6 +133,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 100), (2, 100), (3, 100), (4, 100)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs index d943ba6f53333..7aa8019b9c42c 100644 --- a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs +++ b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs @@ -314,6 +314,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let _ = pallet_balances::GenesisConfig:: { balances: vec![(10, 100), (20, 100), (21, 100), (22, 100)], + ..Default::default() } .assimilate_storage(&mut storage) .unwrap(); diff --git a/substrate/frame/preimage/src/mock.rs b/substrate/frame/preimage/src/mock.rs index 9c72d09cae146..dec590c6a197b 100644 --- a/substrate/frame/preimage/src/mock.rs +++ b/substrate/frame/preimage/src/mock.rs @@ -81,6 +81,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); let balances = pallet_balances::GenesisConfig:: { balances: vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100)], + ..Default::default() }; balances.assimilate_storage(&mut t).unwrap(); t.into() diff --git a/substrate/frame/proxy/src/tests.rs b/substrate/frame/proxy/src/tests.rs index afc668188e6cb..14389b03ac7e2 100644 --- a/substrate/frame/proxy/src/tests.rs +++ b/substrate/frame/proxy/src/tests.rs @@ -133,6 +133,7 @@ pub fn new_test_ext() -> TestState { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10), (2, 10), (3, 10), (4, 10), (5, 3)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/recovery/src/mock.rs b/substrate/frame/recovery/src/mock.rs index 86f13b0da4f76..446d507a414c5 100644 --- a/substrate/frame/recovery/src/mock.rs +++ b/substrate/frame/recovery/src/mock.rs @@ -78,6 +78,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/referenda/src/mock.rs b/substrate/frame/referenda/src/mock.rs index c96a50af86581..5d36ce137d46d 100644 --- a/substrate/frame/referenda/src/mock.rs +++ b/substrate/frame/referenda/src/mock.rs @@ -219,7 +219,7 @@ impl ExtBuilder { pub fn build(self) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); let balances = vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100), (6, 100)]; - pallet_balances::GenesisConfig:: { balances } + pallet_balances::GenesisConfig:: { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); let mut ext = sp_io::TestExternalities::new(t); diff --git a/substrate/frame/revive/mock-network/src/lib.rs b/substrate/frame/revive/mock-network/src/lib.rs index adfd0016b4dd9..b8c9bc13aa796 100644 --- a/substrate/frame/revive/mock-network/src/lib.rs +++ b/substrate/frame/revive/mock-network/src/lib.rs @@ -99,6 +99,7 @@ pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { (relay_sovereign_account_id(), INITIAL_BALANCE), (BOB, INITIAL_BALANCE), ], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); @@ -137,6 +138,7 @@ pub fn relay_ext() -> sp_io::TestExternalities { (parachain_sovereign_account_id(1), INITIAL_BALANCE), (parachain_account_sovereign_account_id(1, ALICE), INITIAL_BALANCE), ], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 90b9f053a03fb..d8b60e38da5ef 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -566,7 +566,7 @@ impl ExtBuilder { sp_tracing::try_init_simple(); self.set_associated_consts(); let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { balances: vec![] } + pallet_balances::GenesisConfig:: { balances: vec![], ..Default::default() } .assimilate_storage(&mut t) .unwrap(); let mut ext = sp_io::TestExternalities::new(t); diff --git a/substrate/frame/root-offences/src/mock.rs b/substrate/frame/root-offences/src/mock.rs index 3f14dc00b5606..9b319cabb09ed 100644 --- a/substrate/frame/root-offences/src/mock.rs +++ b/substrate/frame/root-offences/src/mock.rs @@ -212,6 +212,7 @@ impl ExtBuilder { (31, self.balance_factor * 1000), (41, self.balance_factor * 2000), ], + ..Default::default() } .assimilate_storage(&mut storage) .unwrap(); diff --git a/substrate/frame/safe-mode/src/mock.rs b/substrate/frame/safe-mode/src/mock.rs index aaf3456272fa0..2980f86abc281 100644 --- a/substrate/frame/safe-mode/src/mock.rs +++ b/substrate/frame/safe-mode/src/mock.rs @@ -233,6 +233,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pallet_balances::GenesisConfig:: { // The 0 account is NOT a special origin, the rest may be. balances: vec![(0, BAL_ACC0), (1, BAL_ACC1), (2, 5678), (3, 5678), (4, 5678)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/scored-pool/src/mock.rs b/substrate/frame/scored-pool/src/mock.rs index 7708c06e56bd8..5eb9df5289240 100644 --- a/substrate/frame/scored-pool/src/mock.rs +++ b/substrate/frame/scored-pool/src/mock.rs @@ -109,7 +109,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { balances.push((40, 500_000)); balances.push((99, 1)); - pallet_balances::GenesisConfig:: { balances } + pallet_balances::GenesisConfig:: { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); pallet_scored_pool::GenesisConfig:: { diff --git a/substrate/frame/society/src/mock.rs b/substrate/frame/society/src/mock.rs index 8cb5dc823753b..63fc5059279b1 100644 --- a/substrate/frame/society/src/mock.rs +++ b/substrate/frame/society/src/mock.rs @@ -115,7 +115,7 @@ impl EnvBuilder { pub fn execute R>(mut self, f: F) -> R { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); self.balances.push((Society::account_id(), self.balance.max(self.pot))); - pallet_balances::GenesisConfig:: { balances: self.balances } + pallet_balances::GenesisConfig:: { balances: self.balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); pallet_society::GenesisConfig:: { pot: self.pot } diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index 6346949576fa7..41fb3a31d52ed 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -471,6 +471,7 @@ impl ExtBuilder { // This allows us to have a total_payout different from 0. (999, 1_000_000_000_000), ], + ..Default::default() } .assimilate_storage(&mut storage); diff --git a/substrate/frame/state-trie-migration/src/lib.rs b/substrate/frame/state-trie-migration/src/lib.rs index 1dc1a3928f2b8..6e475b7067e16 100644 --- a/substrate/frame/state-trie-migration/src/lib.rs +++ b/substrate/frame/state-trie-migration/src/lib.rs @@ -1297,9 +1297,12 @@ mod mock { frame_system::GenesisConfig::::default() .assimilate_storage(&mut custom_storage) .unwrap(); - pallet_balances::GenesisConfig:: { balances: vec![(1, 1000)] } - .assimilate_storage(&mut custom_storage) - .unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(1, 1000)], + ..Default::default() + } + .assimilate_storage(&mut custom_storage) + .unwrap(); } sp_tracing::try_init_simple(); diff --git a/substrate/frame/statement/src/mock.rs b/substrate/frame/statement/src/mock.rs index 34afd332c083d..db9d19dbbab73 100644 --- a/substrate/frame/statement/src/mock.rs +++ b/substrate/frame/statement/src/mock.rs @@ -82,6 +82,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { 500000, ), ], + ..Default::default() }; balances.assimilate_storage(&mut t).unwrap(); t.into() diff --git a/substrate/frame/tips/src/tests.rs b/substrate/frame/tips/src/tests.rs index 530efb708e414..b769ea5b3e753 100644 --- a/substrate/frame/tips/src/tests.rs +++ b/substrate/frame/tips/src/tests.rs @@ -180,7 +180,10 @@ impl Config for Test { pub fn new_test_ext() -> sp_io::TestExternalities { let mut ext: sp_io::TestExternalities = RuntimeGenesisConfig { system: frame_system::GenesisConfig::default(), - balances: pallet_balances::GenesisConfig { balances: vec![(0, 100), (1, 98), (2, 1)] }, + balances: pallet_balances::GenesisConfig { + balances: vec![(0, 100), (1, 98), (2, 1)], + ..Default::default() + }, treasury: Default::default(), treasury_1: Default::default(), } @@ -583,6 +586,7 @@ fn genesis_funding_works() { pallet_balances::GenesisConfig:: { // Total issuance will be 200 with treasury account initialized with 100. balances: vec![(0, 100), (Treasury::account_id(), initial_funding)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs index 6ce4652fd42f5..76d46aa164713 100644 --- a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs +++ b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs @@ -86,6 +86,7 @@ impl ExtBuilder { } else { vec![] }, + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/transaction-payment/asset-tx-payment/src/tests.rs b/substrate/frame/transaction-payment/asset-tx-payment/src/tests.rs index 6de2e8e7da55c..2aa5d8ec7beed 100644 --- a/substrate/frame/transaction-payment/asset-tx-payment/src/tests.rs +++ b/substrate/frame/transaction-payment/asset-tx-payment/src/tests.rs @@ -81,6 +81,7 @@ impl ExtBuilder { } else { vec![] }, + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/transaction-payment/src/tests.rs b/substrate/frame/transaction-payment/src/tests.rs index bde1bf64728e4..8349df306675e 100644 --- a/substrate/frame/transaction-payment/src/tests.rs +++ b/substrate/frame/transaction-payment/src/tests.rs @@ -99,6 +99,7 @@ impl ExtBuilder { } else { vec![] }, + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/transaction-storage/src/mock.rs b/substrate/frame/transaction-storage/src/mock.rs index 84a77043d577c..25f44b953bfb2 100644 --- a/substrate/frame/transaction-storage/src/mock.rs +++ b/substrate/frame/transaction-storage/src/mock.rs @@ -65,6 +65,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { system: Default::default(), balances: pallet_balances::GenesisConfig:: { balances: vec![(1, 1000000000), (2, 100), (3, 100), (4, 100)], + ..Default::default() }, transaction_storage: pallet_transaction_storage::GenesisConfig:: { storage_period: 10, diff --git a/substrate/frame/treasury/src/tests.rs b/substrate/frame/treasury/src/tests.rs index e9efb7c0956f1..2c2ceac586249 100644 --- a/substrate/frame/treasury/src/tests.rs +++ b/substrate/frame/treasury/src/tests.rs @@ -221,6 +221,7 @@ impl ExtBuilder { pallet_balances::GenesisConfig:: { // Total issuance will be 200 with treasury account initialized at ED. balances: vec![(0, 100), (1, 98), (2, 1)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); @@ -406,9 +407,12 @@ fn treasury_account_doesnt_get_deleted() { #[test] fn inexistent_account_works() { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { balances: vec![(0, 100), (1, 99), (2, 1)] } - .assimilate_storage(&mut t) - .unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(0, 100), (1, 99), (2, 1)], + ..Default::default() + } + .assimilate_storage(&mut t) + .unwrap(); // Treasury genesis config is not build thus treasury account does not exist let mut t: sp_io::TestExternalities = t.into(); @@ -445,6 +449,7 @@ fn genesis_funding_works() { pallet_balances::GenesisConfig:: { // Total issuance will be 200 with treasury account initialized with 100. balances: vec![(0, 100), (Treasury::account_id(), initial_funding)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/tx-pause/src/mock.rs b/substrate/frame/tx-pause/src/mock.rs index fd9b3b552ccdf..d543f447ca7a5 100644 --- a/substrate/frame/tx-pause/src/mock.rs +++ b/substrate/frame/tx-pause/src/mock.rs @@ -157,6 +157,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pallet_balances::GenesisConfig:: { // The 0 account is NOT a special origin. The rest may be: balances: vec![(0, 1234), (1, 5678), (2, 5678), (3, 5678), (4, 5678)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/utility/src/tests.rs b/substrate/frame/utility/src/tests.rs index 274a90d77cf06..d075ec1ff82e3 100644 --- a/substrate/frame/utility/src/tests.rs +++ b/substrate/frame/utility/src/tests.rs @@ -237,6 +237,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10), (2, 10), (3, 10), (4, 10), (5, 2)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/vesting/src/mock.rs b/substrate/frame/vesting/src/mock.rs index f0954a5b989c8..8fae9bbf74974 100644 --- a/substrate/frame/vesting/src/mock.rs +++ b/substrate/frame/vesting/src/mock.rs @@ -94,6 +94,7 @@ impl ExtBuilder { (12, 10 * self.existential_deposit), (13, 9999 * self.existential_deposit), ], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/test-utils/runtime/src/genesismap.rs b/substrate/test-utils/runtime/src/genesismap.rs index 5c0c146d45a5a..e9a0e4815a2bf 100644 --- a/substrate/test-utils/runtime/src/genesismap.rs +++ b/substrate/test-utils/runtime/src/genesismap.rs @@ -130,7 +130,10 @@ impl GenesisStorageBuilder { authorities: authorities_sr25519.clone(), ..Default::default() }, - balances: pallet_balances::GenesisConfig { balances: self.balances.clone() }, + balances: pallet_balances::GenesisConfig { + balances: self.balances.clone(), + ..Default::default() + }, } } diff --git a/substrate/test-utils/runtime/src/lib.rs b/substrate/test-utils/runtime/src/lib.rs index 4d24354f99a7e..7c092f2851663 100644 --- a/substrate/test-utils/runtime/src/lib.rs +++ b/substrate/test-utils/runtime/src/lib.rs @@ -1329,7 +1329,7 @@ mod tests { .expect("default config is there"); let json = String::from_utf8(r.into()).expect("returned value is json. qed."); - let expected = r#"{"system":{},"babe":{"authorities":[],"epochConfig":{"c":[1,4],"allowed_slots":"PrimaryAndSecondaryVRFSlots"}},"substrateTest":{"authorities":[]},"balances":{"balances":[]}}"#; + let expected = r#"{"system":{},"babe":{"authorities":[],"epochConfig":{"c":[1,4],"allowed_slots":"PrimaryAndSecondaryVRFSlots"}},"substrateTest":{"authorities":[]},"balances":{"balances":[],"devAccounts":null}}"#; assert_eq!(expected.to_string(), json); } From 66bd26d35c21ad260120129776c86870ff1dd220 Mon Sep 17 00:00:00 2001 From: "Alisher A. Khassanov" Date: Thu, 23 Jan 2025 16:01:55 +0500 Subject: [PATCH 131/169] Add `offchain_localStorageClear` RPC method (#7266) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Closes https://github.com/paritytech/polkadot-sdk/issues/7265. ## Integration Requires changes in `https://github.com/polkadot-js/api/packages/{rpc-augment,types-support,types}` to be visible in Polkadot\Substrate Portal and in other libraries where we should explicitly state RPC methods. Accompany PR to `polkadot-js/api`: https://github.com/polkadot-js/api/pull/6070. ## Review Notes Please put the right label on my PR. --------- Co-authored-by: command-bot <> Co-authored-by: Bastian Köcher --- prdoc/pr_7266.prdoc | 13 +++++++++++++ substrate/client/offchain/src/api.rs | 8 +++++++- substrate/client/rpc-api/src/offchain/mod.rs | 4 ++++ substrate/client/rpc/src/offchain/mod.rs | 17 +++++++++++++++++ substrate/client/rpc/src/offchain/tests.rs | 13 ++++++++++++- 5 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 prdoc/pr_7266.prdoc diff --git a/prdoc/pr_7266.prdoc b/prdoc/pr_7266.prdoc new file mode 100644 index 0000000000000..4fa7ddb7b41a5 --- /dev/null +++ b/prdoc/pr_7266.prdoc @@ -0,0 +1,13 @@ +title: Add `offchain_localStorageClear` RPC method +doc: +- audience: Node Operator + description: |- + Adds RPC method `offchain_localStorageClear` to clear the offchain local storage. +crates: +- name: sc-offchain + bump: minor +- name: sc-rpc-api + bump: minor + validate: false +- name: sc-rpc + bump: minor diff --git a/substrate/client/offchain/src/api.rs b/substrate/client/offchain/src/api.rs index a5981f14c093c..7d5c07deca4fb 100644 --- a/substrate/client/offchain/src/api.rs +++ b/substrate/client/offchain/src/api.rs @@ -375,7 +375,7 @@ mod tests { } #[test] - fn should_set_and_get_local_storage() { + fn should_set_get_and_clear_local_storage() { // given let kind = StorageKind::PERSISTENT; let mut api = offchain_db(); @@ -387,6 +387,12 @@ mod tests { // then assert_eq!(api.local_storage_get(kind, key), Some(b"value".to_vec())); + + // when + api.local_storage_clear(kind, key); + + // then + assert_eq!(api.local_storage_get(kind, key), None); } #[test] diff --git a/substrate/client/rpc-api/src/offchain/mod.rs b/substrate/client/rpc-api/src/offchain/mod.rs index 4dd5b066d49fe..606d441231b05 100644 --- a/substrate/client/rpc-api/src/offchain/mod.rs +++ b/substrate/client/rpc-api/src/offchain/mod.rs @@ -31,6 +31,10 @@ pub trait OffchainApi { #[method(name = "offchain_localStorageSet", with_extensions)] fn set_local_storage(&self, kind: StorageKind, key: Bytes, value: Bytes) -> Result<(), Error>; + /// Clear offchain local storage under given key and prefix. + #[method(name = "offchain_localStorageClear", with_extensions)] + fn clear_local_storage(&self, kind: StorageKind, key: Bytes) -> Result<(), Error>; + /// Get offchain local storage under given key and prefix. #[method(name = "offchain_localStorageGet", with_extensions)] fn get_local_storage(&self, kind: StorageKind, key: Bytes) -> Result, Error>; diff --git a/substrate/client/rpc/src/offchain/mod.rs b/substrate/client/rpc/src/offchain/mod.rs index af6bc1ba58c8f..f5b1b35be1063 100644 --- a/substrate/client/rpc/src/offchain/mod.rs +++ b/substrate/client/rpc/src/offchain/mod.rs @@ -66,6 +66,23 @@ impl OffchainApiServer for Offchain { Ok(()) } + fn clear_local_storage( + &self, + ext: &Extensions, + kind: StorageKind, + key: Bytes, + ) -> Result<(), Error> { + check_if_safe(ext)?; + + let prefix = match kind { + StorageKind::PERSISTENT => sp_offchain::STORAGE_PREFIX, + StorageKind::LOCAL => return Err(Error::UnavailableStorageKind), + }; + self.storage.write().remove(prefix, &key); + + Ok(()) + } + fn get_local_storage( &self, ext: &Extensions, diff --git a/substrate/client/rpc/src/offchain/tests.rs b/substrate/client/rpc/src/offchain/tests.rs index 41f22c2dc9642..6b8225a7b5eb2 100644 --- a/substrate/client/rpc/src/offchain/tests.rs +++ b/substrate/client/rpc/src/offchain/tests.rs @@ -35,9 +35,14 @@ fn local_storage_should_work() { Ok(()) ); assert_matches!( - offchain.get_local_storage(&ext, StorageKind::PERSISTENT, key), + offchain.get_local_storage(&ext, StorageKind::PERSISTENT, key.clone()), Ok(Some(ref v)) if *v == value ); + assert_matches!( + offchain.clear_local_storage(&ext, StorageKind::PERSISTENT, key.clone()), + Ok(()) + ); + assert_matches!(offchain.get_local_storage(&ext, StorageKind::PERSISTENT, key), Ok(None)); } #[test] @@ -55,6 +60,12 @@ fn offchain_calls_considered_unsafe() { assert_eq!(e.to_string(), "RPC call is unsafe to be called externally") } ); + assert_matches!( + offchain.clear_local_storage(&ext, StorageKind::PERSISTENT, key.clone()), + Err(Error::UnsafeRpcCalled(e)) => { + assert_eq!(e.to_string(), "RPC call is unsafe to be called externally") + } + ); assert_matches!( offchain.get_local_storage(&ext, StorageKind::PERSISTENT, key), Err(Error::UnsafeRpcCalled(e)) => { From 085da479dee8e09ad3de83dbc59b304bd36b4ebe Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Thu, 23 Jan 2025 12:55:14 +0100 Subject: [PATCH 132/169] Bridges small nits/improvements (#7307) This PR contains small fixes identified during work on the larger PR: https://github.com/paritytech/polkadot-sdk/issues/6906. --------- Co-authored-by: command-bot <> --- Cargo.lock | 3 --- bridges/bin/runtime-common/Cargo.toml | 2 -- bridges/bin/runtime-common/src/integrity.rs | 9 +++++---- .../integration-tests/emulated/common/Cargo.toml | 1 - .../bridge-hubs/bridge-hub-rococo/Cargo.toml | 2 -- .../src/bridge_to_bulletin_config.rs | 1 + .../src/bridge_to_westend_config.rs | 1 + .../src/bridge_to_rococo_config.rs | 1 + polkadot/xcm/xcm-builder/src/barriers.rs | 2 +- prdoc/pr_7307.prdoc | 16 ++++++++++++++++ 10 files changed, 25 insertions(+), 13 deletions(-) create mode 100644 prdoc/pr_7307.prdoc diff --git a/Cargo.lock b/Cargo.lock index a10def370be72..5cc898714d31e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2671,7 +2671,6 @@ version = "0.5.0" dependencies = [ "bp-asset-hub-rococo", "bp-asset-hub-westend", - "bp-bridge-hub-polkadot", "bp-bridge-hub-rococo", "bp-bridge-hub-westend", "bp-header-chain 0.7.0", @@ -3018,7 +3017,6 @@ dependencies = [ "bp-relayers 0.7.0", "bp-runtime 0.7.0", "bp-test-utils 0.7.0", - "bp-xcm-bridge-hub 0.2.0", "frame-support 28.0.0", "frame-system 28.0.0", "log", @@ -6436,7 +6434,6 @@ dependencies = [ "asset-test-utils 7.0.0", "bp-messages 0.7.0", "bp-xcm-bridge-hub 0.2.0", - "bridge-runtime-common 0.7.0", "cumulus-pallet-parachain-system 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-primitives-core 0.7.0", diff --git a/bridges/bin/runtime-common/Cargo.toml b/bridges/bin/runtime-common/Cargo.toml index 49cd086fd3eb5..b5ec37a24a8d4 100644 --- a/bridges/bin/runtime-common/Cargo.toml +++ b/bridges/bin/runtime-common/Cargo.toml @@ -24,7 +24,6 @@ bp-parachains = { workspace = true } bp-polkadot-core = { workspace = true } bp-relayers = { workspace = true } bp-runtime = { workspace = true } -bp-xcm-bridge-hub = { workspace = true } pallet-bridge-grandpa = { workspace = true } pallet-bridge-messages = { workspace = true } pallet-bridge-parachains = { workspace = true } @@ -63,7 +62,6 @@ std = [ "bp-relayers/std", "bp-runtime/std", "bp-test-utils/std", - "bp-xcm-bridge-hub/std", "codec/std", "frame-support/std", "frame-system/std", diff --git a/bridges/bin/runtime-common/src/integrity.rs b/bridges/bin/runtime-common/src/integrity.rs index 61dbf09109acc..0fc377090cfe1 100644 --- a/bridges/bin/runtime-common/src/integrity.rs +++ b/bridges/bin/runtime-common/src/integrity.rs @@ -30,7 +30,6 @@ use pallet_bridge_messages::{ThisChainOf, WeightInfoExt as _}; // Re-export to avoid include all dependencies everywhere. #[doc(hidden)] pub mod __private { - pub use bp_xcm_bridge_hub; pub use static_assertions; } @@ -66,9 +65,9 @@ macro_rules! assert_bridge_messages_pallet_types( with_bridged_chain_messages_instance: $i:path, this_chain: $this:path, bridged_chain: $bridged:path, + expected_payload_type: $payload:path, ) => { { - use $crate::integrity::__private::bp_xcm_bridge_hub::XcmAsPlainPayload; use $crate::integrity::__private::static_assertions::assert_type_eq_all; use bp_messages::ChainWithMessages; use bp_runtime::Chain; @@ -81,8 +80,8 @@ macro_rules! assert_bridge_messages_pallet_types( assert_type_eq_all!(<$r as BridgeMessagesConfig<$i>>::ThisChain, $this); assert_type_eq_all!(<$r as BridgeMessagesConfig<$i>>::BridgedChain, $bridged); - assert_type_eq_all!(<$r as BridgeMessagesConfig<$i>>::OutboundPayload, XcmAsPlainPayload); - assert_type_eq_all!(<$r as BridgeMessagesConfig<$i>>::InboundPayload, XcmAsPlainPayload); + assert_type_eq_all!(<$r as BridgeMessagesConfig<$i>>::OutboundPayload, $payload); + assert_type_eq_all!(<$r as BridgeMessagesConfig<$i>>::InboundPayload, $payload); } } ); @@ -97,6 +96,7 @@ macro_rules! assert_complete_bridge_types( with_bridged_chain_messages_instance: $mi:path, this_chain: $this:path, bridged_chain: $bridged:path, + expected_payload_type: $payload:path, ) => { $crate::assert_chain_types!(runtime: $r, this_chain: $this); $crate::assert_bridge_messages_pallet_types!( @@ -104,6 +104,7 @@ macro_rules! assert_complete_bridge_types( with_bridged_chain_messages_instance: $mi, this_chain: $this, bridged_chain: $bridged, + expected_payload_type: $payload, ); } ); diff --git a/cumulus/parachains/integration-tests/emulated/common/Cargo.toml b/cumulus/parachains/integration-tests/emulated/common/Cargo.toml index e921deb9c628e..4bd45ef1a87c6 100644 --- a/cumulus/parachains/integration-tests/emulated/common/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/common/Cargo.toml @@ -46,6 +46,5 @@ xcm-emulator = { workspace = true, default-features = true } # Bridges bp-messages = { workspace = true, default-features = true } bp-xcm-bridge-hub = { workspace = true, default-features = true } -bridge-runtime-common = { workspace = true, default-features = true } pallet-bridge-messages = { workspace = true, default-features = true } pallet-xcm-bridge-hub = { workspace = true, default-features = true } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index 3dba65ae99f18..b3d48adfedc5f 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -86,7 +86,6 @@ testnet-parachains-constants = { features = ["rococo"], workspace = true } # Bridges bp-asset-hub-rococo = { workspace = true } bp-asset-hub-westend = { workspace = true } -bp-bridge-hub-polkadot = { workspace = true } bp-bridge-hub-rococo = { workspace = true } bp-bridge-hub-westend = { workspace = true } bp-header-chain = { workspace = true } @@ -132,7 +131,6 @@ default = ["std"] std = [ "bp-asset-hub-rococo/std", "bp-asset-hub-westend/std", - "bp-bridge-hub-polkadot/std", "bp-bridge-hub-rococo/std", "bp-bridge-hub-westend/std", "bp-header-chain/std", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs index 1e733503f43b6..1f58e9c2f2bad 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs @@ -203,6 +203,7 @@ mod tests { with_bridged_chain_messages_instance: WithRococoBulletinMessagesInstance, this_chain: bp_bridge_hub_rococo::BridgeHubRococo, bridged_chain: bp_polkadot_bulletin::PolkadotBulletin, + expected_payload_type: XcmAsPlainPayload, ); // we can't use `assert_complete_bridge_constants` here, because there's a trick with diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs index a14101eb454bd..d394af73e7478 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs @@ -295,6 +295,7 @@ mod tests { with_bridged_chain_messages_instance: WithBridgeHubWestendMessagesInstance, this_chain: bp_bridge_hub_rococo::BridgeHubRococo, bridged_chain: bp_bridge_hub_westend::BridgeHubWestend, + expected_payload_type: XcmAsPlainPayload, ); assert_complete_with_parachain_bridge_constants::< diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs index 24e5482b7b097..a5fb33cf504d5 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs @@ -323,6 +323,7 @@ mod tests { with_bridged_chain_messages_instance: WithBridgeHubRococoMessagesInstance, this_chain: bp_bridge_hub_westend::BridgeHubWestend, bridged_chain: bp_bridge_hub_rococo::BridgeHubRococo, + expected_payload_type: XcmAsPlainPayload, ); assert_complete_with_parachain_bridge_constants::< diff --git a/polkadot/xcm/xcm-builder/src/barriers.rs b/polkadot/xcm/xcm-builder/src/barriers.rs index adba9a3ef79f1..27153a3f441da 100644 --- a/polkadot/xcm/xcm-builder/src/barriers.rs +++ b/polkadot/xcm/xcm-builder/src/barriers.rs @@ -490,7 +490,7 @@ impl ShouldExecute for DenyReserveTransferToRelayChain { if matches!(origin, Location { parents: 1, interior: Here }) => { log::warn!( - target: "xcm::barrier", + target: "xcm::barriers", "Unexpected ReserveAssetDeposited from the Relay Chain", ); Ok(ControlFlow::Continue(())) diff --git a/prdoc/pr_7307.prdoc b/prdoc/pr_7307.prdoc new file mode 100644 index 0000000000000..b27aace0bd139 --- /dev/null +++ b/prdoc/pr_7307.prdoc @@ -0,0 +1,16 @@ +title: Bridges small nits/improvements +doc: +- audience: Runtime Dev + description: | + This PR introduces a new `expected_payload_type` parameter to the Bridges `assert_complete_bridge_types` macro. +crates: +- name: bridge-runtime-common + bump: patch +- name: bridge-hub-rococo-runtime + bump: patch +- name: bridge-hub-westend-runtime + bump: patch +- name: staging-xcm-builder + bump: patch +- name: emulated-integration-tests-common + bump: patch From cfc5b6f59a1fa46aa55144bff5eb7fca14e27e2b Mon Sep 17 00:00:00 2001 From: Alin Dima Date: Thu, 23 Jan 2025 15:00:31 +0200 Subject: [PATCH 133/169] bump lookahead to 3 for testnet genesis (#7252) This is the right value after https://github.com/paritytech/polkadot-sdk/pull/4880, which corresponds to an allowedAncestryLen of 2 (which is the default) WIll fix https://github.com/paritytech/polkadot-sdk/issues/7105 --- polkadot/runtime/rococo/src/genesis_config_presets.rs | 2 +- polkadot/runtime/westend/src/genesis_config_presets.rs | 2 +- .../tests/elastic_scaling/doesnt_break_parachains.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/polkadot/runtime/rococo/src/genesis_config_presets.rs b/polkadot/runtime/rococo/src/genesis_config_presets.rs index a96a509b0e4da..83bd1fbbc8faf 100644 --- a/polkadot/runtime/rococo/src/genesis_config_presets.rs +++ b/polkadot/runtime/rococo/src/genesis_config_presets.rs @@ -134,7 +134,7 @@ fn default_parachains_host_configuration( 1u8 << (FeatureIndex::CandidateReceiptV2 as usize), ), scheduler_params: SchedulerParams { - lookahead: 2, + lookahead: 3, group_rotation_frequency: 20, paras_availability_period: 4, ..Default::default() diff --git a/polkadot/runtime/westend/src/genesis_config_presets.rs b/polkadot/runtime/westend/src/genesis_config_presets.rs index ea5aff554e8c5..729df20b3c65e 100644 --- a/polkadot/runtime/westend/src/genesis_config_presets.rs +++ b/polkadot/runtime/westend/src/genesis_config_presets.rs @@ -137,7 +137,7 @@ fn default_parachains_host_configuration( 1u8 << (FeatureIndex::CandidateReceiptV2 as usize), ), scheduler_params: SchedulerParams { - lookahead: 2, + lookahead: 3, group_rotation_frequency: 20, paras_availability_period: 4, ..Default::default() diff --git a/polkadot/zombienet-sdk-tests/tests/elastic_scaling/doesnt_break_parachains.rs b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/doesnt_break_parachains.rs index f83400d2b22ab..e65029d7095cb 100644 --- a/polkadot/zombienet-sdk-tests/tests/elastic_scaling/doesnt_break_parachains.rs +++ b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/doesnt_break_parachains.rs @@ -120,8 +120,8 @@ async fn doesnt_break_parachains_test() -> Result<(), anyhow::Error> { assert_eq!( cq, [ - (CoreIndex(0), [para_id, para_id].into_iter().collect()), - (CoreIndex(1), [para_id, para_id].into_iter().collect()), + (CoreIndex(0), std::iter::repeat(para_id).take(3).collect()), + (CoreIndex(1), std::iter::repeat(para_id).take(3).collect()), ] .into_iter() .collect() From 6091330ae6d799bcf34d366acda7aff91c609ab1 Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Thu, 23 Jan 2025 13:30:26 +0000 Subject: [PATCH 134/169] Refactor command bot and drop rejecting non paritytech members (#7231) Aims to - close #7049 - close https://github.com/paritytech/opstooling/issues/449 - close https://github.com/paritytech/opstooling/issues/463 What's changed: - removed is paritytech member check as required prerequisite to run a command - run the cmd.py script taking it from master, if someone who run this is not a member of paritytech, and from current branch, if is a member. That keeps the developer experience & easy testing if paritytech members are contributing to cmd.py - isolate the cmd job from being able to access GH App token or PR token- currently the fmt/bench/prdoc are being run with limited permissions scope, just to generate output, which then is uploaded to artifacts, and then the other job which doesn't run any files from repo, does a commit/push more securely --- .github/workflows/cmd.yml | 428 +++++++++++++++++++++----------------- 1 file changed, 240 insertions(+), 188 deletions(-) diff --git a/.github/workflows/cmd.yml b/.github/workflows/cmd.yml index 42b2eab3b9e4e..50e71f2699d5e 100644 --- a/.github/workflows/cmd.yml +++ b/.github/workflows/cmd.yml @@ -5,7 +5,7 @@ on: types: [created] permissions: # allow the action to comment on the PR - contents: write + contents: read issues: write pull-requests: write actions: read @@ -55,38 +55,9 @@ jobs: return 'false'; - reject-non-members: - needs: is-org-member - if: ${{ startsWith(github.event.comment.body, '/cmd') && needs.is-org-member.outputs.member != 'true' }} - runs-on: ubuntu-latest - steps: - - name: Add reaction to rejected comment - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - github.rest.reactions.createForIssueComment({ - comment_id: ${{ github.event.comment.id }}, - owner: context.repo.owner, - repo: context.repo.repo, - content: 'confused' - }) - - - name: Comment PR (Rejected) - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: `Sorry, only members of the organization ${{ github.event.repository.owner.login }} members can run commands.` - }) acknowledge: - needs: is-org-member - if: ${{ startsWith(github.event.comment.body, '/cmd') && needs.is-org-member.outputs.member == 'true' }} + if: ${{ startsWith(github.event.comment.body, '/cmd') }} runs-on: ubuntu-latest steps: - name: Add reaction to triggered comment @@ -102,12 +73,11 @@ jobs: }) clean: - needs: is-org-member runs-on: ubuntu-latest steps: - name: Clean previous comments - if: ${{ startsWith(github.event.comment.body, '/cmd') && contains(github.event.comment.body, '--clean') && needs.is-org-member.outputs.member == 'true' }} uses: actions/github-script@v7 + if: ${{ startsWith(github.event.comment.body, '/cmd') && contains(github.event.comment.body, '--clean') }} with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | @@ -139,25 +109,72 @@ jobs: } } }) - help: - needs: [clean, is-org-member] - if: ${{ startsWith(github.event.comment.body, '/cmd') && contains(github.event.comment.body, '--help') && needs.is-org-member.outputs.member == 'true' }} + + get-pr-info: + if: ${{ startsWith(github.event.comment.body, '/cmd') }} runs-on: ubuntu-latest + outputs: + CMD: ${{ steps.get-comment.outputs.group2 }} + pr-branch: ${{ steps.get-pr.outputs.pr_branch }} + repo: ${{ steps.get-pr.outputs.repo }} steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Get command uses: actions-ecosystem/action-regex-match@v2 - id: get-pr-comment + id: get-comment with: text: ${{ github.event.comment.body }} regex: "^(\\/cmd )([-\\/\\s\\w.=:]+)$" # see explanation in docs/contributor/commands-readme.md#examples + + # Get PR branch name, because the issue_comment event does not contain the PR branch name + - name: Check if the issue is a PR + id: check-pr + run: | + if [ -n "${{ github.event.issue.pull_request.url }}" ]; then + echo "This is a pull request comment" + else + echo "This is not a pull request comment" + exit 1 + fi + + - name: Get PR Branch Name and Repo + if: steps.check-pr.outcome == 'success' + id: get-pr + uses: actions/github-script@v7 + with: + script: | + const pr = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number, + }); + const prBranch = pr.data.head.ref; + const repo = pr.data.head.repo.full_name; + console.log(prBranch, repo) + core.setOutput('pr_branch', prBranch); + core.setOutput('repo', repo); + + - name: Use PR Branch Name and Repo + env: + PR_BRANCH: ${{ steps.get-pr.outputs.pr_branch }} + REPO: ${{ steps.get-pr.outputs.repo }} + CMD: ${{ steps.get-comment.outputs.group2 }} + run: | + echo "The PR branch is $PR_BRANCH" + echo "The repository is $REPO" + echo "The CMD is $CMD" + + help: + needs: [clean, get-pr-info] + if: ${{ startsWith(github.event.comment.body, '/cmd') && contains(github.event.comment.body, '--help') }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 - name: Save output of help id: help env: - CMD: ${{ steps.get-pr-comment.outputs.group2 }} # to avoid "" around the command + CMD: ${{ needs.get-pr-info.outputs.CMD }} # to avoid "" around the command run: | python3 -m pip install -r .github/scripts/generate-prdoc.requirements.txt echo 'help<> $GITHUB_OUTPUT @@ -209,9 +226,11 @@ jobs: }) set-image: - needs: [clean, is-org-member] - if: ${{ startsWith(github.event.comment.body, '/cmd') && !contains(github.event.comment.body, '--help') && needs.is-org-member.outputs.member == 'true' }} + needs: [clean, get-pr-info] + if: ${{ startsWith(github.event.comment.body, '/cmd') && !contains(github.event.comment.body, '--help') }} runs-on: ubuntu-latest + env: + CMD: ${{ needs.get-pr-info.outputs.CMD }} outputs: IMAGE: ${{ steps.set-image.outputs.IMAGE }} RUNNER: ${{ steps.set-image.outputs.RUNNER }} @@ -221,7 +240,7 @@ jobs: - id: set-image run: | - BODY=$(echo "${{ github.event.comment.body }}" | xargs) + BODY=$(echo "$CMD" | xargs) # remove whitespace IMAGE_OVERRIDE=$(echo $BODY | grep -oe 'docker.io/paritytech/ci-unified:.*\s' | xargs) cat .github/env >> $GITHUB_OUTPUT @@ -243,87 +262,17 @@ jobs: echo "RUNNER=${{ steps.set-image.outputs.RUNNER }}" echo "IMAGE=${{ steps.set-image.outputs.IMAGE }}" - # Get PR branch name, because the issue_comment event does not contain the PR branch name - get-pr-branch: - needs: [set-image] + before-cmd: + needs: [set-image, get-pr-info] runs-on: ubuntu-latest - outputs: - pr-branch: ${{ steps.get-pr.outputs.pr_branch }} - repo: ${{ steps.get-pr.outputs.repo }} - steps: - - name: Check if the issue is a PR - id: check-pr - run: | - if [ -n "${{ github.event.issue.pull_request.url }}" ]; then - echo "This is a pull request comment" - else - echo "This is not a pull request comment" - exit 1 - fi - - - name: Get PR Branch Name and Repo - if: steps.check-pr.outcome == 'success' - id: get-pr - uses: actions/github-script@v7 - with: - script: | - const pr = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.issue.number, - }); - const prBranch = pr.data.head.ref; - const repo = pr.data.head.repo.full_name; - console.log(prBranch, repo) - core.setOutput('pr_branch', prBranch); - core.setOutput('repo', repo); - - - name: Use PR Branch Name and Repo - run: | - echo "The PR branch is ${{ steps.get-pr.outputs.pr_branch }}" - echo "The repository is ${{ steps.get-pr.outputs.repo }}" - - cmd: - needs: [set-image, get-pr-branch] env: JOB_NAME: "cmd" - runs-on: ${{ needs.set-image.outputs.RUNNER }} - container: - image: ${{ needs.set-image.outputs.IMAGE }} - timeout-minutes: 1440 # 24 hours per runtime + CMD: ${{ needs.get-pr-info.outputs.CMD }} + PR_BRANCH: ${{ needs.get-pr-info.outputs.pr-branch }} + outputs: + job_url: ${{ steps.build-link.outputs.job_url }} + run_url: ${{ steps.build-link.outputs.run_url }} steps: - - name: Generate token - uses: actions/create-github-app-token@v1 - id: generate_token - with: - app-id: ${{ secrets.CMD_BOT_APP_ID }} - private-key: ${{ secrets.CMD_BOT_APP_KEY }} - - - name: Checkout - uses: actions/checkout@v4 - with: - token: ${{ steps.generate_token.outputs.token }} - repository: ${{ needs.get-pr-branch.outputs.repo }} - ref: ${{ needs.get-pr-branch.outputs.pr-branch }} - - - name: Get command - uses: actions-ecosystem/action-regex-match@v2 - id: get-pr-comment - with: - text: ${{ github.event.comment.body }} - regex: "^(\\/cmd )([-\\/\\s\\w.=:]+)$" # see explanation in docs/contributor/commands-readme.md#examples - - # In order to run prdoc without specifying the PR number, we need to add the PR number as an argument automatically - - name: Prepare PR Number argument - id: pr-arg - run: | - CMD="${{ steps.get-pr-comment.outputs.group2 }}" - if echo "$CMD" | grep -q "prdoc" && ! echo "$CMD" | grep -qE "\-\-pr[[:space:]=][0-9]+"; then - echo "arg=--pr ${{ github.event.issue.number }}" >> $GITHUB_OUTPUT - else - echo "arg=" >> $GITHUB_OUTPUT - fi - - name: Build workflow link if: ${{ !contains(github.event.comment.body, '--quiet') }} id: build-link @@ -346,40 +295,90 @@ jobs: - name: Comment PR (Start) # No need to comment on prdoc start or if --quiet - if: ${{ !contains(github.event.comment.body, '--quiet') && !contains(github.event.comment.body, 'prdoc') }} + if: ${{ !contains(github.event.comment.body, '--quiet') && !startsWith(needs.get-pr-info.outputs.CMD, 'prdoc') && !startsWith(needs.get-pr-info.outputs.CMD, 'fmt')}} uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | let job_url = ${{ steps.build-link.outputs.job_url }} - + let cmd = process.env.CMD; github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - body: `Command "${{ steps.get-pr-comment.outputs.group2 }}" has started 🚀 [See logs here](${job_url})` + body: `Command "${cmd}" has started 🚀 [See logs here](${job_url})` }) + + cmd: + needs: [before-cmd, set-image, get-pr-info, is-org-member] + env: + CMD: ${{ needs.get-pr-info.outputs.CMD }} + PR_BRANCH: ${{ needs.get-pr-info.outputs.pr-branch }} + runs-on: ${{ needs.set-image.outputs.RUNNER }} + container: + image: ${{ needs.set-image.outputs.IMAGE }} + timeout-minutes: 1440 # 24 hours per runtime + # lowerdown permissions to separate permissions context for executable parts by contributors + permissions: + contents: read + pull-requests: none + actions: none + issues: none + outputs: + cmd_output: ${{ steps.cmd.outputs.cmd_output }} + subweight: ${{ steps.subweight.outputs.result }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + repository: ${{ needs.get-pr-info.outputs.repo }} + ref: ${{ needs.get-pr-info.outputs.pr-branch }} + + # In order to run prdoc without specifying the PR number, we need to add the PR number as an argument automatically + - name: Prepare PR Number argument + id: pr-arg + run: | + CMD="${{ needs.get-pr-info.outputs.CMD }}" + if echo "$CMD" | grep -q "prdoc" && ! echo "$CMD" | grep -qE "\-\-pr[[:space:]=][0-9]+"; then + echo "arg=--pr ${{ github.event.issue.number }}" >> $GITHUB_OUTPUT + else + echo "arg=" >> $GITHUB_OUTPUT + fi - name: Install dependencies for bench - if: startsWith(steps.get-pr-comment.outputs.group2, 'bench') + if: startsWith(needs.get-pr-info.outputs.CMD, 'bench') run: | - cargo install subweight --locked cargo install --path substrate/utils/frame/omni-bencher --locked - name: Run cmd id: cmd env: - CMD: ${{ steps.get-pr-comment.outputs.group2 }} # to avoid "" around the command PR_ARG: ${{ steps.pr-arg.outputs.arg }} + IS_ORG_MEMBER: ${{ needs.is-org-member.outputs.member }} run: | echo "Running command: '$CMD $PR_ARG' on '${{ needs.set-image.outputs.RUNNER }}' runner, container: '${{ needs.set-image.outputs.IMAGE }}'" echo "RUST_NIGHTLY_VERSION: $RUST_NIGHTLY_VERSION" - # Fixes "detected dubious ownership" error in the ci - git config --global --add safe.directory '*' - git remote -v - cat /proc/cpuinfo - python3 -m pip install -r .github/scripts/generate-prdoc.requirements.txt - python3 .github/scripts/cmd/cmd.py $CMD $PR_ARG + echo "IS_ORG_MEMBER: $IS_ORG_MEMBER" + + git config --global --add safe.directory $GITHUB_WORKSPACE + git config user.name "cmd[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + + # if the user is not an org member, we need to use the bot's path from master to avoid unwanted modifications + if [ "$IS_ORG_MEMBER" = "true" ]; then + # safe to run commands from current branch + BOT_PATH=.github + else + # going to run commands from master + TMP_DIR=/tmp/polkadot-sdk + git clone --depth 1 --branch master https://github.com/paritytech/polkadot-sdk $TMP_DIR + BOT_PATH=$TMP_DIR/.github + fi + + # install deps and run a command from master + python3 -m pip install -r $BOT_PATH/scripts/generate-prdoc.requirements.txt + python3 $BOT_PATH/scripts/cmd/cmd.py $CMD $PR_ARG git status git diff @@ -393,6 +392,11 @@ jobs: echo 'EOF' >> $GITHUB_OUTPUT fi + git add -A + git diff HEAD > /tmp/cmd/command_diff.patch -U0 + git commit -m "tmp cmd: $CMD" || true + # without push, as we're saving the diff to an artifact and subweight will compare the local branch with the remote branch + - name: Upload command output if: ${{ always() }} uses: actions/upload-artifact@v4 @@ -400,38 +404,100 @@ jobs: name: command-output path: /tmp/cmd/command_output.log - # Generate token for commit, as the earlier token expires after 1 hour, while cmd can take longer - - name: Generate token for commit - uses: actions/create-github-app-token@v1 - id: generate_token_commit + - name: Upload command diff + uses: actions/upload-artifact@v4 + with: + name: command-diff + path: /tmp/cmd/command_diff.patch + + - name: Install subweight for bench + if: startsWith(needs.get-pr-info.outputs.CMD, 'bench') + run: cargo install subweight + + - name: Run Subweight for bench + id: subweight + if: startsWith(needs.get-pr-info.outputs.CMD, 'bench') + shell: bash + run: | + git fetch + git remote -v + echo $(git log -n 2 --oneline) + + result=$(subweight compare commits \ + --path-pattern "./**/weights/**/*.rs,./**/weights.rs" \ + --method asymptotic \ + --format markdown \ + --no-color \ + --change added changed \ + --ignore-errors \ + refs/remotes/origin/master $PR_BRANCH) + + # Save the multiline result to the output + { + echo "result<> $GITHUB_OUTPUT + + after-cmd: + needs: [cmd, get-pr-info, before-cmd] + env: + CMD: ${{ needs.get-pr-info.outputs.CMD }} + PR_BRANCH: ${{ needs.get-pr-info.outputs.pr-branch }} + runs-on: ubuntu-latest + steps: + # needs to be able to trigger CI, as default token does not retrigger + - uses: actions/create-github-app-token@v1 + id: generate_token with: app-id: ${{ secrets.CMD_BOT_APP_ID }} private-key: ${{ secrets.CMD_BOT_APP_KEY }} - - name: Commit changes + - name: Checkout + uses: actions/checkout@v4 + with: + token: ${{ steps.generate_token.outputs.token }} + repository: ${{ needs.get-pr-info.outputs.repo }} + ref: ${{ needs.get-pr-info.outputs.pr-branch }} + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + name: command-diff + path: command-diff + + - name: Apply & Commit changes run: | + ls -lsa . + + git config --global --add safe.directory $GITHUB_WORKSPACE + git config user.name "cmd[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config --global pull.rebase false + + echo "Applying $file" + git apply "command-diff/command_diff.patch" --unidiff-zero --allow-empty + + rm -rf command-diff + + git status + if [ -n "$(git status --porcelain)" ]; then - git config --global user.name command-bot - git config --global user.email "<>" - git config --global pull.rebase false - # Push the results to the target branch - git remote add \ - github \ - "https://x-access-token:${{ steps.generate_token_commit.outputs.token }}@github.com/${{ needs.get-pr-branch.outputs.repo }}.git" || : + git remote -v push_changes() { - git push github "HEAD:${{ needs.get-pr-branch.outputs.pr-branch }}" + git push origin "HEAD:$PR_BRANCH" } git add . git restore --staged Cargo.lock # ignore changes in Cargo.lock - git commit -m "Update from ${{ github.actor }} running command '${{ steps.get-pr-comment.outputs.group2 }}'" || true + git commit -m "Update from ${{ github.actor }} running command '$CMD'" || true # Attempt to push changes if ! push_changes; then echo "Push failed, trying to rebase..." - git pull --rebase github "${{ needs.get-pr-branch.outputs.pr-branch }}" + git pull --rebase origin $PR_BRANCH # After successful rebase, try pushing again push_changes fi @@ -439,41 +505,20 @@ jobs: echo "Nothing to commit"; fi - - name: Run Subweight - id: subweight - if: startsWith(steps.get-pr-comment.outputs.group2, 'bench') - shell: bash - run: | - git fetch - result=$(subweight compare commits \ - --path-pattern "./**/weights/**/*.rs,./**/weights.rs" \ - --method asymptotic \ - --format markdown \ - --no-color \ - --change added changed \ - --ignore-errors \ - refs/remotes/origin/master refs/heads/${{ needs.get-pr-branch.outputs.pr-branch }}) - - # Save the multiline result to the output - { - echo "result<> $GITHUB_OUTPUT - - name: Comment PR (End) # No need to comment on prdoc success or --quiet - if: ${{ !failure() && !contains(github.event.comment.body, '--quiet') && !contains(github.event.comment.body, 'prdoc') }} + if: ${{ needs.cmd.result == 'success' && !contains(github.event.comment.body, '--quiet') && !startsWith(needs.get-pr-info.outputs.CMD, 'prdoc') && !startsWith(needs.get-pr-info.outputs.CMD, 'fmt') }} uses: actions/github-script@v7 env: - SUBWEIGHT: "${{ steps.subweight.outputs.result }}" - CMD_OUTPUT: "${{ steps.cmd.outputs.cmd_output }}" + SUBWEIGHT: "${{ needs.cmd.outputs.subweight }}" + CMD_OUTPUT: "${{ needs.cmd.outputs.cmd_output }}" with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - let runUrl = ${{ steps.build-link.outputs.run_url }} - let subweight = process.env.SUBWEIGHT; - let cmdOutput = process.env.CMD_OUTPUT; + let runUrl = ${{ needs.before-cmd.outputs.run_url }} + let subweight = process.env.SUBWEIGHT || ''; + let cmdOutput = process.env.CMD_OUTPUT || ''; + let cmd = process.env.CMD; console.log(cmdOutput); let subweightCollapsed = subweight.trim() !== '' @@ -488,34 +533,41 @@ jobs: issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - body: `Command "${{ steps.get-pr-comment.outputs.group2 }}" has finished ✅ [See logs here](${runUrl})${subweightCollapsed}${cmdOutputCollapsed}` + body: `Command "${cmd}" has finished ✅ [See logs here](${runUrl})${subweightCollapsed}${cmdOutputCollapsed}` }) + finish: + needs: [get-pr-info, before-cmd, after-cmd, cmd] + if: ${{ always() }} + runs-on: ubuntu-latest + env: + CMD_OUTPUT: "${{ needs.cmd.outputs.cmd_output }}" + CMD: ${{ needs.get-pr-info.outputs.CMD }} + steps: - name: Comment PR (Failure) - if: ${{ failure() && !contains(github.event.comment.body, '--quiet') }} + if: ${{ needs.cmd.result == 'failure' || needs.after-cmd.result == 'failure' }} uses: actions/github-script@v7 - env: - CMD_OUTPUT: "${{ steps.cmd.outputs.cmd_output }}" with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - let jobUrl = ${{ steps.build-link.outputs.job_url }} + let jobUrl = ${{ needs.before-cmd.outputs.job_url }} let cmdOutput = process.env.CMD_OUTPUT; - - let cmdOutputCollapsed = cmdOutput.trim() !== '' - ? `
\n\nCommand output:\n\n${cmdOutput}\n\n
` - : ''; + let cmd = process.env.CMD; + let cmdOutputCollapsed = ''; + if (cmdOutput && cmdOutput.trim() !== '') { + cmdOutputCollapsed = `
\n\nCommand output:\n\n${cmdOutput}\n\n
` + } github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - body: `Command "${{ steps.get-pr-comment.outputs.group2 }}" has failed ❌! [See logs here](${jobUrl})${cmdOutputCollapsed}` + body: `Command "${cmd}" has failed ❌! [See logs here](${jobUrl})${cmdOutputCollapsed}` }) - name: Add 😕 reaction on failure + if: ${{ needs.cmd.result == 'failure' || needs.after-cmd.result == 'failure' }} uses: actions/github-script@v7 - if: ${{ failure() }} with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | @@ -527,8 +579,8 @@ jobs: }) - name: Add 👍 reaction on success + if: ${{ needs.cmd.result == 'success' && needs.after-cmd.result == 'success' }} uses: actions/github-script@v7 - if: ${{ !failure() }} with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | From 0e2446cf248b69b99e091a7766da67d465449205 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 23 Jan 2025 13:47:40 +0000 Subject: [PATCH 135/169] add a few comments for niklas --- substrate/frame/staking/src/pallet/impls.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index 14362993fe1ff..c9032aa273ac8 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -1518,6 +1518,15 @@ impl ElectionDataProvider for Pallet { .into(), }; + // TODO: this is somewhat temp hack to fix this issue: + // in the new multi-block staking model, we finish the election one block before the session + // ends. In this very last block, we don't want to tell EP that the next election is in one + // blocks, but rather in a whole era from now. + + if until_this_session_end == One::one() && sessions_left.is_zero() { + return now.saturating_add(T::SessionsPerEra::get().into() * session_length) + } + now.saturating_add( until_this_session_end.saturating_add(sessions_left.saturating_mul(session_length)), ) From e10e8bf6b10f97e55ecac57b798f76538026897a Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 23 Jan 2025 13:48:32 +0000 Subject: [PATCH 136/169] add a few comments for niklas --- substrate/frame/staking/src/pallet/impls.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index c9032aa273ac8..8b0a460e3153e 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -1524,7 +1524,9 @@ impl ElectionDataProvider for Pallet { // blocks, but rather in a whole era from now. if until_this_session_end == One::one() && sessions_left.is_zero() { - return now.saturating_add(T::SessionsPerEra::get().into() * session_length) + return now.saturating_add( + BlockNumberFor::::from(T::SessionsPerEra::get()) * session_length, + ) } now.saturating_add( From 31f4245849175bf4c27de400f0a7b160c6ac95dd Mon Sep 17 00:00:00 2001 From: kianenigma Date: Thu, 23 Jan 2025 14:03:31 +0000 Subject: [PATCH 137/169] works continously now --- substrate/frame/staking/src/pallet/impls.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index 8b0a460e3153e..963363623349f 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -1521,9 +1521,12 @@ impl ElectionDataProvider for Pallet { // TODO: this is somewhat temp hack to fix this issue: // in the new multi-block staking model, we finish the election one block before the session // ends. In this very last block, we don't want to tell EP that the next election is in one - // blocks, but rather in a whole era from now. - - if until_this_session_end == One::one() && sessions_left.is_zero() { + // blocks, but rather in a whole era from now. For simplification, while we are + // mid-election,we always point to one era later. + // + // This whole code path has to change when we move to the rc-client model. + if !ElectableStashes::::get().is_empty() { + log!(debug, "we are mid-election, pointing to next era as election prediction."); return now.saturating_add( BlockNumberFor::::from(T::SessionsPerEra::get()) * session_length, ) From 3a7f3c0af63b1a7566ca29c59fa4ac274bd911f1 Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Thu, 23 Jan 2025 16:08:32 +0000 Subject: [PATCH 138/169] Fix setting the image properly (#7315) Fixed condition which sets weights/large images --- .github/workflows/cmd.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cmd.yml b/.github/workflows/cmd.yml index 50e71f2699d5e..3d4779064a44f 100644 --- a/.github/workflows/cmd.yml +++ b/.github/workflows/cmd.yml @@ -250,9 +250,9 @@ jobs: echo "IMAGE=$IMAGE" >> $GITHUB_OUTPUT fi - if [[ $BODY == "/cmd bench"* ]]; then + if [[ $BODY == "bench"* ]]; then echo "RUNNER=parity-weights" >> $GITHUB_OUTPUT - elif [[ $BODY == "/cmd update-ui"* ]]; then + elif [[ $BODY == "update-ui"* ]]; then echo "RUNNER=parity-large" >> $GITHUB_OUTPUT else echo "RUNNER=ubuntu-latest" >> $GITHUB_OUTPUT From e9393a9afc3b33cc2d01b7820a8f186434196758 Mon Sep 17 00:00:00 2001 From: Andrei Sandu <54316454+sandreim@users.noreply.github.com> Date: Thu, 23 Jan 2025 18:53:27 +0200 Subject: [PATCH 139/169] Deprecate ParaBackingState API (#6867) Currently the `para_backing_state` API is used only by the prospective parachains subsystems and returns 2 things: the constraints for parachain blocks and the candidates pending availability. This PR deprecates `para_backing_state` and introduces a new `backing_constraints` API that can be used together with `candidates_pending_availability` to get the same information provided by `para_backing_state`. TODO: - [x] PRDoc --------- Signed-off-by: Andrei Sandu Co-authored-by: command-bot <> --- .../src/blockchain_rpc_client.rs | 12 +- .../src/rpc_client.rs | 13 +- .../emulated/chains/relays/rococo/src/lib.rs | 2 +- .../emulated/chains/relays/westend/src/lib.rs | 2 +- .../src/fragment_chain/mod.rs | 25 +- .../src/fragment_chain/tests.rs | 1 + .../core/prospective-parachains/src/lib.rs | 89 ++++- .../core/prospective-parachains/src/tests.rs | 369 +++++++++++++++--- polkadot/node/core/runtime-api/src/cache.rs | 24 +- polkadot/node/core/runtime-api/src/lib.rs | 13 + polkadot/node/core/runtime-api/src/tests.rs | 12 +- polkadot/node/subsystem-types/src/messages.rs | 10 +- .../subsystem-types/src/runtime_client.rs | 23 +- .../src/inclusion_emulator/mod.rs | 139 +++++-- polkadot/node/subsystem-util/src/lib.rs | 7 +- polkadot/primitives/src/runtime_api.rs | 10 +- .../primitives/src/vstaging/async_backing.rs | 40 +- polkadot/primitives/src/vstaging/mod.rs | 9 +- .../node/backing/prospective-parachains.md | 3 + .../parachains/src/runtime_api_impl/v11.rs | 19 +- .../src/runtime_api_impl/vstaging.rs | 30 ++ polkadot/runtime/rococo/src/lib.rs | 15 +- polkadot/runtime/test-runtime/src/lib.rs | 1 + polkadot/runtime/westend/src/lib.rs | 15 +- prdoc/pr_6867.prdoc | 30 ++ 25 files changed, 758 insertions(+), 155 deletions(-) create mode 100644 prdoc/pr_6867.prdoc diff --git a/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs b/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs index 1086e3a52ec01..862cf6af97956 100644 --- a/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs +++ b/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs @@ -26,7 +26,9 @@ use futures::{Stream, StreamExt}; use polkadot_core_primitives::{Block, BlockNumber, Hash, Header}; use polkadot_overseer::{ChainApiBackend, RuntimeApiSubsystemClient}; use polkadot_primitives::{ - async_backing::AsyncBackingParams, slashing, vstaging::async_backing::BackingState, + async_backing::AsyncBackingParams, + slashing, + vstaging::async_backing::{BackingState, Constraints}, ApprovalVotingParams, CoreIndex, NodeFeatures, }; use sc_authority_discovery::{AuthorityDiscovery, Error as AuthorityDiscoveryError}; @@ -454,6 +456,14 @@ impl RuntimeApiSubsystemClient for BlockChainRpcClient { .parachain_host_candidates_pending_availability(at, para_id) .await?) } + + async fn backing_constraints( + &self, + at: Hash, + para_id: ParaId, + ) -> Result, ApiError> { + Ok(self.rpc_client.parachain_host_backing_constraints(at, para_id).await?) + } } #[async_trait::async_trait] diff --git a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs index d7785d92c73a5..0467b7085ca02 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs @@ -35,8 +35,8 @@ use cumulus_primitives_core::{ async_backing::AsyncBackingParams, slashing, vstaging::{ - async_backing::BackingState, CandidateEvent, - CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, + async_backing::{BackingState, Constraints}, + CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, ApprovalVotingParams, BlockNumber, CandidateCommitments, CandidateHash, CoreIndex, @@ -720,6 +720,15 @@ impl RelayChainRpcClient { .await } + pub async fn parachain_host_backing_constraints( + &self, + at: RelayHash, + para_id: ParaId, + ) -> Result, RelayChainError> { + self.call_remote_runtime_function("ParachainHost_backing_constraints", at, Some(para_id)) + .await + } + fn send_register_message_to_worker( &self, message: RpcDispatcherMessage, diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/lib.rs index bd637a5f7965b..240c0931ae5af 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/lib.rs @@ -25,7 +25,7 @@ use emulated_integration_tests_common::{ // Rococo declaration decl_test_relay_chains! { - #[api_version(11)] + #[api_version(12)] pub struct Rococo { genesis = genesis::genesis(), on_init = (), diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/lib.rs index ce9fafcd5bda8..729bb3ad63d16 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/lib.rs @@ -25,7 +25,7 @@ use emulated_integration_tests_common::{ // Westend declaration decl_test_relay_chains! { - #[api_version(11)] + #[api_version(12)] pub struct Westend { genesis = genesis::genesis(), on_init = (), diff --git a/polkadot/node/core/prospective-parachains/src/fragment_chain/mod.rs b/polkadot/node/core/prospective-parachains/src/fragment_chain/mod.rs index ded0a3ab73b2d..72a76537160d7 100644 --- a/polkadot/node/core/prospective-parachains/src/fragment_chain/mod.rs +++ b/polkadot/node/core/prospective-parachains/src/fragment_chain/mod.rs @@ -132,8 +132,8 @@ use std::{ use super::LOG_TARGET; use polkadot_node_subsystem::messages::Ancestors; use polkadot_node_subsystem_util::inclusion_emulator::{ - self, ConstraintModifications, Constraints, Fragment, HypotheticalOrConcreteCandidate, - ProspectiveCandidate, RelayChainBlockInfo, + self, validate_commitments, ConstraintModifications, Constraints, Fragment, + HypotheticalOrConcreteCandidate, ProspectiveCandidate, RelayChainBlockInfo, }; use polkadot_primitives::{ vstaging::CommittedCandidateReceiptV2 as CommittedCandidateReceipt, BlockNumber, @@ -1052,7 +1052,7 @@ impl FragmentChain { // Try seeing if the parent candidate is in the current chain or if it is the latest // included candidate. If so, get the constraints the candidate must satisfy. - let (constraints, maybe_min_relay_parent_number) = + let (is_unconnected, constraints, maybe_min_relay_parent_number) = if let Some(parent_candidate) = self.best_chain.by_output_head.get(&parent_head_hash) { let Some(parent_candidate) = self.best_chain.chain.iter().find(|c| &c.candidate_hash == parent_candidate) @@ -1062,6 +1062,7 @@ impl FragmentChain { }; ( + false, self.scope .base_constraints .apply_modifications(&parent_candidate.cumulative_modifications) @@ -1070,11 +1071,10 @@ impl FragmentChain { ) } else if self.scope.base_constraints.required_parent.hash() == parent_head_hash { // It builds on the latest included candidate. - (self.scope.base_constraints.clone(), None) + (false, self.scope.base_constraints.clone(), None) } else { - // If the parent is not yet part of the chain, there's nothing else we can check for - // now. - return Ok(()) + // The parent is not yet part of the chain + (true, self.scope.base_constraints.clone(), None) }; // Check for cycles or invalid tree transitions. @@ -1088,6 +1088,17 @@ impl FragmentChain { candidate.persisted_validation_data(), candidate.validation_code_hash(), ) { + if is_unconnected { + // If the parent is not yet part of the chain, we can check the commitments only + // if we have the full candidate. + return validate_commitments( + &self.scope.base_constraints, + &relay_parent, + commitments, + &validation_code_hash, + ) + .map_err(Error::CheckAgainstConstraints) + } Fragment::check_against_constraints( &relay_parent, &constraints, diff --git a/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs b/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs index 624dd74132c19..9e7e570bd16f9 100644 --- a/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs +++ b/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs @@ -34,6 +34,7 @@ fn make_constraints( min_relay_parent_number, max_pov_size: 1_000_000, max_code_size: 1_000_000, + max_head_data_size: 20480, ump_remaining: 10, ump_remaining_bytes: 1_000, max_ump_num_per_candidate: 10, diff --git a/polkadot/node/core/prospective-parachains/src/lib.rs b/polkadot/node/core/prospective-parachains/src/lib.rs index 92aea8509f8c4..7416c97f3cd02 100644 --- a/polkadot/node/core/prospective-parachains/src/lib.rs +++ b/polkadot/node/core/prospective-parachains/src/lib.rs @@ -45,15 +45,13 @@ use polkadot_node_subsystem::{ use polkadot_node_subsystem_util::{ backing_implicit_view::{BlockInfoProspectiveParachains as BlockInfo, View as ImplicitView}, inclusion_emulator::{Constraints, RelayChainBlockInfo}, + request_backing_constraints, request_candidates_pending_availability, request_session_index_for_child, runtime::{fetch_claim_queue, prospective_parachains_mode, ProspectiveParachainsMode}, }; use polkadot_primitives::{ - vstaging::{ - async_backing::CandidatePendingAvailability, - CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - }, - BlockNumber, CandidateHash, Hash, HeadData, Header, Id as ParaId, PersistedValidationData, + vstaging::{CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState}, + BlockNumber, CandidateHash, Hash, Header, Id as ParaId, PersistedValidationData, }; use crate::{ @@ -257,8 +255,9 @@ async fn handle_active_leaves_update( let mut fragment_chains = HashMap::new(); for para in scheduled_paras { // Find constraints and pending availability candidates. - let backing_state = fetch_backing_state(ctx, hash, para).await?; - let Some((constraints, pending_availability)) = backing_state else { + let Some((constraints, pending_availability)) = + fetch_backing_constraints_and_candidates(ctx, hash, para).await? + else { // This indicates a runtime conflict of some kind. gum::debug!( target: LOG_TARGET, @@ -273,7 +272,7 @@ async fn handle_active_leaves_update( let pending_availability = preprocess_candidates_pending_availability( ctx, &mut temp_header_cache, - constraints.required_parent.clone(), + &constraints, pending_availability, ) .await?; @@ -445,22 +444,23 @@ struct ImportablePendingAvailability { async fn preprocess_candidates_pending_availability( ctx: &mut Context, cache: &mut HashMap, - required_parent: HeadData, - pending_availability: Vec, + constraints: &Constraints, + pending_availability: Vec, ) -> JfyiErrorResult> { - let mut required_parent = required_parent; + let mut required_parent = constraints.required_parent.clone(); let mut importable = Vec::new(); let expected_count = pending_availability.len(); for (i, pending) in pending_availability.into_iter().enumerate() { + let candidate_hash = pending.hash(); let Some(relay_parent) = fetch_block_info(ctx, cache, pending.descriptor.relay_parent()).await? else { let para_id = pending.descriptor.para_id(); gum::debug!( target: LOG_TARGET, - ?pending.candidate_hash, + ?candidate_hash, ?para_id, index = ?i, ?expected_count, @@ -478,12 +478,12 @@ async fn preprocess_candidates_pending_availability( }, persisted_validation_data: PersistedValidationData { parent_head: required_parent, - max_pov_size: pending.max_pov_size, + max_pov_size: constraints.max_pov_size as _, relay_parent_number: relay_parent.number, relay_parent_storage_root: relay_parent.storage_root, }, compact: fragment_chain::PendingAvailability { - candidate_hash: pending.candidate_hash, + candidate_hash, relay_parent: relay_parent.into(), }, }); @@ -883,7 +883,7 @@ async fn fetch_backing_state( ctx: &mut Context, relay_parent: Hash, para_id: ParaId, -) -> JfyiErrorResult)>> { +) -> JfyiErrorResult)>> { let (tx, rx) = oneshot::channel(); ctx.send_message(RuntimeApiMessage::Request( relay_parent, @@ -891,10 +891,63 @@ async fn fetch_backing_state( )) .await; - Ok(rx + Ok(rx.await.map_err(JfyiError::RuntimeApiRequestCanceled)??.map(|s| { + ( + From::from(s.constraints), + s.pending_availability + .into_iter() + .map(|c| CommittedCandidateReceipt { + descriptor: c.descriptor, + commitments: c.commitments, + }) + .collect(), + ) + })) +} + +#[overseer::contextbounds(ProspectiveParachains, prefix = self::overseer)] +async fn fetch_backing_constraints_and_candidates( + ctx: &mut Context, + relay_parent: Hash, + para_id: ParaId, +) -> JfyiErrorResult)>> { + match fetch_backing_constraints_and_candidates_inner(ctx, relay_parent, para_id).await { + Err(error) => { + gum::debug!( + target: LOG_TARGET, + ?para_id, + ?relay_parent, + ?error, + "Failed to get constraints and candidates pending availability." + ); + + // Fallback to backing state. + fetch_backing_state(ctx, relay_parent, para_id).await + }, + Ok(maybe_constraints_and_candidatest) => Ok(maybe_constraints_and_candidatest), + } +} + +#[overseer::contextbounds(ProspectiveParachains, prefix = self::overseer)] +async fn fetch_backing_constraints_and_candidates_inner( + ctx: &mut Context, + relay_parent: Hash, + para_id: ParaId, +) -> JfyiErrorResult)>> { + let maybe_constraints = request_backing_constraints(relay_parent, para_id, ctx.sender()) + .await .await - .map_err(JfyiError::RuntimeApiRequestCanceled)?? - .map(|s| (From::from(s.constraints), s.pending_availability))) + .map_err(JfyiError::RuntimeApiRequestCanceled)??; + + let Some(constraints) = maybe_constraints else { return Ok(None) }; + + let pending_availability = + request_candidates_pending_availability(relay_parent, para_id, ctx.sender()) + .await + .await + .map_err(JfyiError::RuntimeApiRequestCanceled)??; + + Ok(Some((From::from(constraints), pending_availability))) } #[overseer::contextbounds(ProspectiveParachains, prefix = self::overseer)] diff --git a/polkadot/node/core/prospective-parachains/src/tests.rs b/polkadot/node/core/prospective-parachains/src/tests.rs index 3f1eaa4e41ed8..5d1ef2f2f51cb 100644 --- a/polkadot/node/core/prospective-parachains/src/tests.rs +++ b/polkadot/node/core/prospective-parachains/src/tests.rs @@ -27,8 +27,8 @@ use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_primitives::{ async_backing::{AsyncBackingParams, Constraints, InboundHrmpLimitations}, vstaging::{ - async_backing::BackingState, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, - MutateDescriptorV2, + async_backing::{BackingState, CandidatePendingAvailability, Constraints as ConstraintsV2}, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, MutateDescriptorV2, }, CoreIndex, HeadData, Header, PersistedValidationData, ScheduledCore, ValidationCodeHash, }; @@ -44,7 +44,7 @@ const ALLOWED_ANCESTRY_LEN: u32 = 3; const ASYNC_BACKING_PARAMETERS: AsyncBackingParams = AsyncBackingParams { max_candidate_depth: 4, allowed_ancestry_len: ALLOWED_ANCESTRY_LEN }; -const ASYNC_BACKING_DISABLED_ERROR: RuntimeApiError = +const RUNTIME_API_NOT_SUPPORTED: RuntimeApiError = RuntimeApiError::NotSupported { runtime_api_name: "test-runtime" }; const MAX_POV_SIZE: u32 = 1_000_000; @@ -76,6 +76,31 @@ fn dummy_constraints( } } +fn dummy_constraints_v2( + min_relay_parent_number: BlockNumber, + valid_watermarks: Vec, + required_parent: HeadData, + validation_code_hash: ValidationCodeHash, +) -> ConstraintsV2 { + ConstraintsV2 { + min_relay_parent_number, + max_pov_size: MAX_POV_SIZE, + max_head_data_size: 20480, + max_code_size: 1_000_000, + ump_remaining: 10, + ump_remaining_bytes: 1_000, + max_ump_num_per_candidate: 10, + dmp_remaining_messages: vec![], + hrmp_inbound: InboundHrmpLimitations { valid_watermarks }, + hrmp_channels_out: vec![], + max_hrmp_num_per_candidate: 0, + required_parent, + validation_code_hash, + upgrade_restriction: None, + future_validation_code: None, + } +} + struct TestState { claim_queue: BTreeMap>, runtime_api_version: u32, @@ -364,47 +389,93 @@ async fn handle_leaf_activation( let paras: HashSet<_> = test_state.claim_queue.values().flatten().collect(); - for _ in 0..paras.len() { + // We expect two messages per parachain block. + for _ in 0..paras.len() * 2 { let message = virtual_overseer.recv().await; - // Get the para we are working with since the order is not deterministic. - let para_id = match &message { + let para_id = match message { + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + parent, + RuntimeApiRequest::ParaBackingState(p_id, tx), + )) if parent == *hash => { + let PerParaData { min_relay_parent, head_data, pending_availability } = + leaf.para_data(p_id); + + let constraints = dummy_constraints( + *min_relay_parent, + vec![*number], + head_data.clone(), + test_state.validation_code_hash, + ); + + tx.send(Ok(Some(BackingState { + constraints, + pending_availability: pending_availability.clone(), + }))) + .unwrap(); + Some(p_id) + }, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + parent, + RuntimeApiRequest::BackingConstraints(p_id, tx), + )) if parent == *hash && + test_state.runtime_api_version >= + RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT => + { + let PerParaData { min_relay_parent, head_data, pending_availability: _ } = + leaf.para_data(p_id); + let constraints = dummy_constraints_v2( + *min_relay_parent, + vec![*number], + head_data.clone(), + test_state.validation_code_hash, + ); + + tx.send(Ok(Some(constraints))).unwrap(); + None + }, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + parent, + RuntimeApiRequest::BackingConstraints(_p_id, tx), + )) if parent == *hash && + test_state.runtime_api_version < + RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT => + { + tx.send(Err(RUNTIME_API_NOT_SUPPORTED)).unwrap(); + None + }, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _, - RuntimeApiRequest::ParaBackingState(p_id, _), - )) => *p_id, + parent, + RuntimeApiRequest::CandidatesPendingAvailability(p_id, tx), + )) if parent == *hash => { + tx.send(Ok(leaf + .para_data(p_id) + .pending_availability + .clone() + .into_iter() + .map(|c| CommittedCandidateReceipt { + descriptor: c.descriptor, + commitments: c.commitments, + }) + .collect())) + .unwrap(); + Some(p_id) + }, _ => panic!("received unexpected message {:?}", message), }; - let PerParaData { min_relay_parent, head_data, pending_availability } = - leaf.para_data(para_id); - let constraints = dummy_constraints( - *min_relay_parent, - vec![*number], - head_data.clone(), - test_state.validation_code_hash, - ); - let backing_state = - BackingState { constraints, pending_availability: pending_availability.clone() }; - - assert_matches!( - message, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::ParaBackingState(p_id, tx)) - ) if parent == *hash && p_id == para_id => { - tx.send(Ok(Some(backing_state))).unwrap(); - } - ); - - for pending in pending_availability { - if !used_relay_parents.contains(&pending.descriptor.relay_parent()) { - send_block_header( - virtual_overseer, - pending.descriptor.relay_parent(), - pending.relay_parent_number, - ) - .await; - - used_relay_parents.insert(pending.descriptor.relay_parent()); + if let Some(para_id) = para_id { + for pending in leaf.para_data(para_id).pending_availability.clone() { + if !used_relay_parents.contains(&pending.descriptor.relay_parent()) { + send_block_header( + virtual_overseer, + pending.descriptor.relay_parent(), + pending.relay_parent_number, + ) + .await; + + used_relay_parents.insert(pending.descriptor.relay_parent()); + } } } } @@ -416,7 +487,9 @@ async fn handle_leaf_activation( msg: ProspectiveParachainsMessage::GetMinimumRelayParents(*hash, tx), }) .await; + let mut resp = rx.await.unwrap(); + resp.sort(); let mrp_response: Vec<(ParaId, BlockNumber)> = para_data .iter() @@ -597,7 +670,7 @@ fn should_do_no_work_if_async_backing_disabled_for_leaf() { AllMessages::RuntimeApi( RuntimeApiMessage::Request(parent, RuntimeApiRequest::AsyncBackingParams(tx)) ) if parent == hash => { - tx.send(Err(ASYNC_BACKING_DISABLED_ERROR)).unwrap(); + tx.send(Err(RUNTIME_API_NOT_SUPPORTED)).unwrap(); } ); } @@ -616,9 +689,12 @@ fn should_do_no_work_if_async_backing_disabled_for_leaf() { // - One for leaf B on parachain 1 // - One for leaf C on parachain 2 // Also tests a claim queue size larger than 1. -#[test] -fn introduce_candidates_basic() { +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn introduce_candidates_basic(#[case] runtime_api_version: u32) { let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); let chain_a = ParaId::from(1); let chain_b = ParaId::from(2); @@ -786,9 +862,129 @@ fn introduce_candidates_basic() { assert_eq!(view.active_leaves.len(), 3); } -#[test] -fn introduce_candidate_multiple_times() { - let test_state = TestState::default(); +// Check if candidates are not backed if they fail constraint checks +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn introduce_candidates_error(#[case] runtime_api_version: u32) { + let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); + + let view = test_harness(|mut virtual_overseer| async move { + // Leaf A + let leaf_a = TestLeaf { + number: 100, + hash: Default::default(), + para_data: vec![ + (1.into(), PerParaData::new(98, HeadData(vec![1, 2, 3]))), + (2.into(), PerParaData::new(100, HeadData(vec![2, 3, 4]))), + ], + }; + + // Activate leaves. + activate_leaf_with_params( + &mut virtual_overseer, + &leaf_a, + &test_state, + AsyncBackingParams { allowed_ancestry_len: 3, max_candidate_depth: 1 }, + ) + .await; + + // Candidate A. + let (candidate_a, pvd_a) = make_candidate( + leaf_a.hash, + leaf_a.number, + 1.into(), + HeadData(vec![1, 2, 3]), + HeadData(vec![1]), + test_state.validation_code_hash, + ); + + // Candidate B. + let (candidate_b, pvd_b) = make_candidate( + leaf_a.hash, + leaf_a.number, + 1.into(), + HeadData(vec![1]), + HeadData(vec![1; 20480]), + test_state.validation_code_hash, + ); + + // Candidate C commits to oversized head data. + let (candidate_c, pvd_c) = make_candidate( + leaf_a.hash, + leaf_a.number, + 1.into(), + HeadData(vec![1; 20480]), + HeadData(vec![0; 20485]), + test_state.validation_code_hash, + ); + + // Get hypothetical membership of candidates before adding candidate A. + // Candidate A can be added directly, candidates B and C are potential candidates. + for (candidate, pvd) in + [(candidate_a.clone(), pvd_a.clone()), (candidate_b.clone(), pvd_b.clone())] + { + get_hypothetical_membership( + &mut virtual_overseer, + candidate.hash(), + candidate, + pvd, + vec![leaf_a.hash], + ) + .await; + } + + // Fails constraints check + get_hypothetical_membership( + &mut virtual_overseer, + candidate_c.hash(), + candidate_c.clone(), + pvd_c.clone(), + Vec::new(), + ) + .await; + + // Add candidates + introduce_seconded_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a.clone()) + .await; + introduce_seconded_candidate(&mut virtual_overseer, candidate_b.clone(), pvd_b.clone()) + .await; + // Fails constraints check + introduce_seconded_candidate_failed( + &mut virtual_overseer, + candidate_c.clone(), + pvd_c.clone(), + ) + .await; + + back_candidate(&mut virtual_overseer, &candidate_a, candidate_a.hash()).await; + back_candidate(&mut virtual_overseer, &candidate_b, candidate_b.hash()).await; + // This one will not be backed. + back_candidate(&mut virtual_overseer, &candidate_c, candidate_c.hash()).await; + + // Expect only A and B to be backable + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + Ancestors::default(), + 5, + vec![(candidate_a.hash(), leaf_a.hash), (candidate_b.hash(), leaf_a.hash)], + ) + .await; + virtual_overseer + }); + + assert_eq!(view.active_leaves.len(), 1); +} + +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn introduce_candidate_multiple_times(#[case] runtime_api_version: u32) { + let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); let view = test_harness(|mut virtual_overseer| async move { // Leaf A let leaf_a = TestLeaf { @@ -1172,9 +1368,12 @@ fn introduce_candidate_parent_leaving_view() { } // Introduce a candidate to multiple forks, see how the membership is returned. -#[test] -fn introduce_candidate_on_multiple_forks() { - let test_state = TestState::default(); +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn introduce_candidate_on_multiple_forks(#[case] runtime_api_version: u32) { + let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); let view = test_harness(|mut virtual_overseer| async move { // Leaf B let leaf_b = TestLeaf { @@ -1241,11 +1440,14 @@ fn introduce_candidate_on_multiple_forks() { assert_eq!(view.active_leaves.len(), 2); } -#[test] -fn unconnected_candidates_become_connected() { +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn unconnected_candidates_become_connected(#[case] runtime_api_version: u32) { // This doesn't test all the complicated cases with many unconnected candidates, as it's more // extensively tested in the `fragment_chain::tests` module. - let test_state = TestState::default(); + let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); let view = test_harness(|mut virtual_overseer| async move { // Leaf A let leaf_a = TestLeaf { @@ -1483,9 +1685,14 @@ fn check_backable_query_single_candidate() { } // Backs some candidates and tests `GetBackableCandidates` when requesting a multiple candidates. -#[test] -fn check_backable_query_multiple_candidates() { - let test_state = TestState::default(); +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn check_backable_query_multiple_candidates(#[case] runtime_api_version: u32) { + // This doesn't test all the complicated cases with many unconnected candidates, as it's more + // extensively tested in the `fragment_chain::tests` module. + let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); let view = test_harness(|mut virtual_overseer| async move { // Leaf A let leaf_a = TestLeaf { @@ -1755,9 +1962,13 @@ fn check_backable_query_multiple_candidates() { } // Test hypothetical membership query. -#[test] -fn check_hypothetical_membership_query() { - let test_state = TestState::default(); +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn check_hypothetical_membership_query(#[case] runtime_api_version: u32) { + let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); + let view = test_harness(|mut virtual_overseer| async move { // Leaf B let leaf_b = TestLeaf { @@ -1894,6 +2105,17 @@ fn check_hypothetical_membership_query() { ); introduce_seconded_candidate_failed(&mut virtual_overseer, candidate_d, pvd_d).await; + // Candidate E has invalid head data. + let (candidate_e, pvd_e) = make_candidate( + leaf_a.hash, + leaf_a.number, + 1.into(), + HeadData(vec![2]), + HeadData(vec![0; 20481]), + test_state.validation_code_hash, + ); + introduce_seconded_candidate_failed(&mut virtual_overseer, candidate_e, pvd_e).await; + // Add candidate B and back it. introduce_seconded_candidate(&mut virtual_overseer, candidate_b.clone(), pvd_b.clone()) .await; @@ -1921,9 +2143,14 @@ fn check_hypothetical_membership_query() { assert_eq!(view.active_leaves.len(), 2); } -#[test] -fn check_pvd_query() { - let test_state = TestState::default(); +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn check_pvd_query(#[case] runtime_api_version: u32) { + // This doesn't test all the complicated cases with many unconnected candidates, as it's more + // extensively tested in the `fragment_chain::tests` module. + let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); let view = test_harness(|mut virtual_overseer| async move { // Leaf A let leaf_a = TestLeaf { @@ -2061,6 +2288,7 @@ fn check_pvd_query() { // This test is parametrised with the runtime api version. For versions that don't support the claim // queue API, we check that av-cores are used. #[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] #[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] #[case(8)] fn correctly_updates_leaves(#[case] runtime_api_version: u32) { @@ -2098,6 +2326,7 @@ fn correctly_updates_leaves(#[case] runtime_api_version: u32) { // Activate leaves. activate_leaf(&mut virtual_overseer, &leaf_a, &test_state).await; + activate_leaf(&mut virtual_overseer, &leaf_b, &test_state).await; // Try activating a duplicate leaf. @@ -2161,10 +2390,15 @@ fn correctly_updates_leaves(#[case] runtime_api_version: u32) { assert_eq!(view.active_leaves.len(), 0); } -#[test] -fn handle_active_leaves_update_gets_candidates_from_parent() { - let para_id = ParaId::from(1); +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn handle_active_leaves_update_gets_candidates_from_parent(#[case] runtime_api_version: u32) { + // This doesn't test all the complicated cases with many unconnected candidates, as it's more + // extensively tested in the `fragment_chain::tests` module. let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); + let para_id = ParaId::from(1); test_state.claim_queue = test_state .claim_queue .into_iter() @@ -2477,9 +2711,14 @@ fn handle_active_leaves_update_bounded_implicit_view() { ); } -#[test] -fn persists_pending_availability_candidate() { +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn persists_pending_availability_candidate(#[case] runtime_api_version: u32) { + // This doesn't test all the complicated cases with many unconnected candidates, as it's more + // extensively tested in the `fragment_chain::tests` module. let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); let para_id = ParaId::from(1); test_state.claim_queue = test_state .claim_queue diff --git a/polkadot/node/core/runtime-api/src/cache.rs b/polkadot/node/core/runtime-api/src/cache.rs index 7246010711e40..8a885ea9cc92f 100644 --- a/polkadot/node/core/runtime-api/src/cache.rs +++ b/polkadot/node/core/runtime-api/src/cache.rs @@ -20,10 +20,10 @@ use schnellru::{ByLength, LruMap}; use sp_consensus_babe::Epoch; use polkadot_primitives::{ - async_backing, slashing, vstaging, + async_backing, slashing, vstaging::{ - CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - ScrapedOnChainVotes, + self, async_backing::Constraints, CandidateEvent, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, ApprovalVotingParams, AuthorityDiscoveryId, BlockNumber, CandidateCommitments, CandidateHash, CoreIndex, DisputeState, ExecutorParams, GroupRotationInfo, Hash, Id as ParaId, @@ -75,6 +75,7 @@ pub(crate) struct RequestResultCache { node_features: LruMap, approval_voting_params: LruMap, claim_queue: LruMap>>, + backing_constraints: LruMap<(Hash, ParaId), Option>, } impl Default for RequestResultCache { @@ -112,6 +113,7 @@ impl Default for RequestResultCache { async_backing_params: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), node_features: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), claim_queue: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), + backing_constraints: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), } } } @@ -559,6 +561,21 @@ impl RequestResultCache { ) { self.claim_queue.insert(relay_parent, value); } + + pub(crate) fn backing_constraints( + &mut self, + key: (Hash, ParaId), + ) -> Option<&Option> { + self.backing_constraints.get(&key).map(|v| &*v) + } + + pub(crate) fn cache_backing_constraints( + &mut self, + key: (Hash, ParaId), + value: Option, + ) { + self.backing_constraints.insert(key, value); + } } pub(crate) enum RequestResult { @@ -610,4 +627,5 @@ pub(crate) enum RequestResult { NodeFeatures(SessionIndex, NodeFeatures), ClaimQueue(Hash, BTreeMap>), CandidatesPendingAvailability(Hash, ParaId, Vec), + BackingConstraints(Hash, ParaId, Option), } diff --git a/polkadot/node/core/runtime-api/src/lib.rs b/polkadot/node/core/runtime-api/src/lib.rs index c8b1d61e7be72..4889822b46a9b 100644 --- a/polkadot/node/core/runtime-api/src/lib.rs +++ b/polkadot/node/core/runtime-api/src/lib.rs @@ -183,6 +183,9 @@ where ClaimQueue(relay_parent, sender) => { self.requests_cache.cache_claim_queue(relay_parent, sender); }, + BackingConstraints(relay_parent, para_id, constraints) => self + .requests_cache + .cache_backing_constraints((relay_parent, para_id), constraints), } } @@ -340,6 +343,8 @@ where }, Request::ClaimQueue(sender) => query!(claim_queue(), sender).map(|sender| Request::ClaimQueue(sender)), + Request::BackingConstraints(para, sender) => query!(backing_constraints(para), sender) + .map(|sender| Request::BackingConstraints(para, sender)), } } @@ -652,5 +657,13 @@ where ver = Request::CLAIM_QUEUE_RUNTIME_REQUIREMENT, sender ), + Request::BackingConstraints(para, sender) => { + query!( + BackingConstraints, + backing_constraints(para), + ver = Request::CONSTRAINTS_RUNTIME_REQUIREMENT, + sender + ) + }, } } diff --git a/polkadot/node/core/runtime-api/src/tests.rs b/polkadot/node/core/runtime-api/src/tests.rs index d4fa07323886d..56c6087695786 100644 --- a/polkadot/node/core/runtime-api/src/tests.rs +++ b/polkadot/node/core/runtime-api/src/tests.rs @@ -22,8 +22,8 @@ use polkadot_node_subsystem_test_helpers::make_subsystem_context; use polkadot_primitives::{ async_backing, slashing, vstaging, vstaging::{ - CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - ScrapedOnChainVotes, + async_backing::Constraints, CandidateEvent, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, ApprovalVotingParams, AuthorityDiscoveryId, BlockNumber, CandidateCommitments, CandidateHash, CoreIndex, DisputeState, ExecutorParams, GroupRotationInfo, Id as ParaId, @@ -307,6 +307,14 @@ impl RuntimeApiSubsystemClient for MockSubsystemClient { ) -> Result>, ApiError> { todo!("Not required for tests") } + + async fn backing_constraints( + &self, + _at: Hash, + _para_id: ParaId, + ) -> Result, ApiError> { + todo!("Not required for tests") + } } #[test] diff --git a/polkadot/node/subsystem-types/src/messages.rs b/polkadot/node/subsystem-types/src/messages.rs index b541f95192193..8a3b91b3ec741 100644 --- a/polkadot/node/subsystem-types/src/messages.rs +++ b/polkadot/node/subsystem-types/src/messages.rs @@ -42,9 +42,9 @@ use polkadot_node_primitives::{ ValidationResult, }; use polkadot_primitives::{ - async_backing, slashing, vstaging, + async_backing, slashing, vstaging::{ - BackedCandidate, CandidateReceiptV2 as CandidateReceipt, + self, async_backing::Constraints, BackedCandidate, CandidateReceiptV2 as CandidateReceipt, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, }, ApprovalVotingParams, AuthorityDiscoveryId, BlockNumber, CandidateCommitments, CandidateHash, @@ -772,6 +772,9 @@ pub enum RuntimeApiRequest { /// Get the candidates pending availability for a particular parachain /// `V11` CandidatesPendingAvailability(ParaId, RuntimeApiSender>), + /// Get the backing constraints for a particular parachain. + /// `V12` + BackingConstraints(ParaId, RuntimeApiSender>), } impl RuntimeApiRequest { @@ -812,6 +815,9 @@ impl RuntimeApiRequest { /// `candidates_pending_availability` pub const CANDIDATES_PENDING_AVAILABILITY_RUNTIME_REQUIREMENT: u32 = 11; + + /// `backing_constraints` + pub const CONSTRAINTS_RUNTIME_REQUIREMENT: u32 = 12; } /// A message to the Runtime API subsystem. diff --git a/polkadot/node/subsystem-types/src/runtime_client.rs b/polkadot/node/subsystem-types/src/runtime_client.rs index 4b96009f44bf8..018b52bedcd24 100644 --- a/polkadot/node/subsystem-types/src/runtime_client.rs +++ b/polkadot/node/subsystem-types/src/runtime_client.rs @@ -18,10 +18,10 @@ use async_trait::async_trait; use polkadot_primitives::{ async_backing, runtime_api::ParachainHost, - slashing, vstaging, + slashing, vstaging::{ - CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - ScrapedOnChainVotes, + self, async_backing::Constraints, CandidateEvent, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, ApprovalVotingParams, Block, BlockNumber, CandidateCommitments, CandidateHash, CoreIndex, DisputeState, ExecutorParams, GroupRotationInfo, Hash, Header, Id, InboundDownwardMessage, @@ -347,6 +347,15 @@ pub trait RuntimeApiSubsystemClient { at: Hash, para_id: Id, ) -> Result>, ApiError>; + + // == v12 == + /// Get the constraints on the actions that can be taken by a new parachain + /// block. + async fn backing_constraints( + &self, + at: Hash, + para_id: Id, + ) -> Result, ApiError>; } /// Default implementation of [`RuntimeApiSubsystemClient`] using the client. @@ -624,6 +633,14 @@ where async fn claim_queue(&self, at: Hash) -> Result>, ApiError> { self.client.runtime_api().claim_queue(at) } + + async fn backing_constraints( + &self, + at: Hash, + para_id: Id, + ) -> Result, ApiError> { + self.client.runtime_api().backing_constraints(at, para_id) + } } impl HeaderBackend for DefaultSubsystemClient diff --git a/polkadot/node/subsystem-util/src/inclusion_emulator/mod.rs b/polkadot/node/subsystem-util/src/inclusion_emulator/mod.rs index 48d3f27b1fa6d..8a620db4ab0c9 100644 --- a/polkadot/node/subsystem-util/src/inclusion_emulator/mod.rs +++ b/polkadot/node/subsystem-util/src/inclusion_emulator/mod.rs @@ -82,9 +82,10 @@ /// in practice at most once every few weeks. use polkadot_node_subsystem::messages::HypotheticalCandidate; use polkadot_primitives::{ - async_backing::Constraints as PrimitiveConstraints, vstaging::skip_ump_signals, BlockNumber, - CandidateCommitments, CandidateHash, Hash, HeadData, Id as ParaId, PersistedValidationData, - UpgradeRestriction, ValidationCodeHash, + async_backing::Constraints as OldPrimitiveConstraints, + vstaging::{async_backing::Constraints as PrimitiveConstraints, skip_ump_signals}, + BlockNumber, CandidateCommitments, CandidateHash, Hash, HeadData, Id as ParaId, + PersistedValidationData, UpgradeRestriction, ValidationCodeHash, }; use std::{collections::HashMap, sync::Arc}; @@ -115,6 +116,8 @@ pub struct Constraints { pub max_pov_size: usize, /// The maximum new validation code size allowed, in bytes. pub max_code_size: usize, + /// The maximum head-data size, in bytes. + pub max_head_data_size: usize, /// The amount of UMP messages remaining. pub ump_remaining: usize, /// The amount of UMP bytes remaining. @@ -146,6 +149,44 @@ impl From for Constraints { min_relay_parent_number: c.min_relay_parent_number, max_pov_size: c.max_pov_size as _, max_code_size: c.max_code_size as _, + max_head_data_size: c.max_head_data_size as _, + ump_remaining: c.ump_remaining as _, + ump_remaining_bytes: c.ump_remaining_bytes as _, + max_ump_num_per_candidate: c.max_ump_num_per_candidate as _, + dmp_remaining_messages: c.dmp_remaining_messages, + hrmp_inbound: InboundHrmpLimitations { + valid_watermarks: c.hrmp_inbound.valid_watermarks, + }, + hrmp_channels_out: c + .hrmp_channels_out + .into_iter() + .map(|(para_id, limits)| { + ( + para_id, + OutboundHrmpChannelLimitations { + bytes_remaining: limits.bytes_remaining as _, + messages_remaining: limits.messages_remaining as _, + }, + ) + }) + .collect(), + max_hrmp_num_per_candidate: c.max_hrmp_num_per_candidate as _, + required_parent: c.required_parent, + validation_code_hash: c.validation_code_hash, + upgrade_restriction: c.upgrade_restriction, + future_validation_code: c.future_validation_code, + } + } +} + +impl From for Constraints { + fn from(c: OldPrimitiveConstraints) -> Self { + Constraints { + min_relay_parent_number: c.min_relay_parent_number, + max_pov_size: c.max_pov_size as _, + max_code_size: c.max_code_size as _, + // Equal to Polkadot/Kusama config. + max_head_data_size: 20480, ump_remaining: c.ump_remaining as _, ump_remaining_bytes: c.ump_remaining_bytes as _, max_ump_num_per_candidate: c.max_ump_num_per_candidate as _, @@ -520,6 +561,10 @@ pub enum FragmentValidityError { /// /// Max allowed, new. CodeSizeTooLarge(usize, usize), + /// Head data size too big. + /// + /// Max allowed, new. + HeadDataTooLarge(usize, usize), /// Relay parent too old. /// /// Min allowed, current. @@ -686,28 +731,13 @@ impl Fragment { } } -fn validate_against_constraints( +/// Validates if the candidate commitments are obeying the constraints. +pub fn validate_commitments( constraints: &Constraints, relay_parent: &RelayChainBlockInfo, commitments: &CandidateCommitments, - persisted_validation_data: &PersistedValidationData, validation_code_hash: &ValidationCodeHash, - modifications: &ConstraintModifications, ) -> Result<(), FragmentValidityError> { - let expected_pvd = PersistedValidationData { - parent_head: constraints.required_parent.clone(), - relay_parent_number: relay_parent.number, - relay_parent_storage_root: relay_parent.storage_root, - max_pov_size: constraints.max_pov_size as u32, - }; - - if expected_pvd != *persisted_validation_data { - return Err(FragmentValidityError::PersistedValidationDataMismatch( - expected_pvd, - persisted_validation_data.clone(), - )) - } - if constraints.validation_code_hash != *validation_code_hash { return Err(FragmentValidityError::ValidationCodeMismatch( constraints.validation_code_hash, @@ -715,6 +745,13 @@ fn validate_against_constraints( )) } + if commitments.head_data.0.len() > constraints.max_head_data_size { + return Err(FragmentValidityError::HeadDataTooLarge( + constraints.max_head_data_size, + commitments.head_data.0.len(), + )) + } + if relay_parent.number < constraints.min_relay_parent_number { return Err(FragmentValidityError::RelayParentTooOld( constraints.min_relay_parent_number, @@ -740,6 +777,39 @@ fn validate_against_constraints( )) } + if commitments.horizontal_messages.len() > constraints.max_hrmp_num_per_candidate { + return Err(FragmentValidityError::HrmpMessagesPerCandidateOverflow { + messages_allowed: constraints.max_hrmp_num_per_candidate, + messages_submitted: commitments.horizontal_messages.len(), + }) + } + + Ok(()) +} + +fn validate_against_constraints( + constraints: &Constraints, + relay_parent: &RelayChainBlockInfo, + commitments: &CandidateCommitments, + persisted_validation_data: &PersistedValidationData, + validation_code_hash: &ValidationCodeHash, + modifications: &ConstraintModifications, +) -> Result<(), FragmentValidityError> { + validate_commitments(constraints, relay_parent, commitments, validation_code_hash)?; + + let expected_pvd = PersistedValidationData { + parent_head: constraints.required_parent.clone(), + relay_parent_number: relay_parent.number, + relay_parent_storage_root: relay_parent.storage_root, + max_pov_size: constraints.max_pov_size as u32, + }; + + if expected_pvd != *persisted_validation_data { + return Err(FragmentValidityError::PersistedValidationDataMismatch( + expected_pvd, + persisted_validation_data.clone(), + )) + } if modifications.dmp_messages_processed == 0 { if constraints .dmp_remaining_messages @@ -750,20 +820,12 @@ fn validate_against_constraints( } } - if commitments.horizontal_messages.len() > constraints.max_hrmp_num_per_candidate { - return Err(FragmentValidityError::HrmpMessagesPerCandidateOverflow { - messages_allowed: constraints.max_hrmp_num_per_candidate, - messages_submitted: commitments.horizontal_messages.len(), - }) - } - if modifications.ump_messages_sent > constraints.max_ump_num_per_candidate { return Err(FragmentValidityError::UmpMessagesPerCandidateOverflow { messages_allowed: constraints.max_ump_num_per_candidate, messages_submitted: commitments.upward_messages.len(), }) } - constraints .check_modifications(&modifications) .map_err(FragmentValidityError::OutputsInvalid) @@ -971,6 +1033,7 @@ mod tests { validation_code_hash: ValidationCode(vec![4, 5, 6]).hash(), upgrade_restriction: None, future_validation_code: None, + max_head_data_size: 1024, } } @@ -1478,4 +1541,24 @@ mod tests { Err(FragmentValidityError::HrmpMessagesDescendingOrDuplicate(1)), ); } + + #[test] + fn head_data_size_too_large() { + let relay_parent = RelayChainBlockInfo { + number: 6, + hash: Hash::repeat_byte(0xcc), + storage_root: Hash::repeat_byte(0xff), + }; + + let constraints = make_constraints(); + let mut candidate = make_candidate(&constraints, &relay_parent); + + let head_data_size = constraints.max_head_data_size; + candidate.commitments.head_data = vec![0; head_data_size + 1].into(); + + assert_eq!( + Fragment::new(relay_parent, constraints, Arc::new(candidate.clone())), + Err(FragmentValidityError::HeadDataTooLarge(head_data_size, head_data_size + 1)), + ); + } } diff --git a/polkadot/node/subsystem-util/src/lib.rs b/polkadot/node/subsystem-util/src/lib.rs index 3bed185589419..6b069ee86113f 100644 --- a/polkadot/node/subsystem-util/src/lib.rs +++ b/polkadot/node/subsystem-util/src/lib.rs @@ -43,8 +43,9 @@ use futures::channel::{mpsc, oneshot}; use polkadot_primitives::{ slashing, vstaging::{ - async_backing::BackingState, CandidateEvent, - CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, + async_backing::{BackingState, Constraints}, + CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, + ScrapedOnChainVotes, }, AsyncBackingParams, AuthorityDiscoveryId, CandidateHash, CoreIndex, EncodeAs, ExecutorParams, GroupIndex, GroupRotationInfo, Hash, Id as ParaId, OccupiedCoreAssumption, @@ -313,6 +314,8 @@ specialize_requests! { fn request_async_backing_params() -> AsyncBackingParams; AsyncBackingParams; fn request_claim_queue() -> BTreeMap>; ClaimQueue; fn request_para_backing_state(para_id: ParaId) -> Option; ParaBackingState; + fn request_backing_constraints(para_id: ParaId) -> Option; BackingConstraints; + } /// Requests executor parameters from the runtime effective at given relay-parent. First obtains diff --git a/polkadot/primitives/src/runtime_api.rs b/polkadot/primitives/src/runtime_api.rs index 3c90c050baed1..df1dfbac40013 100644 --- a/polkadot/primitives/src/runtime_api.rs +++ b/polkadot/primitives/src/runtime_api.rs @@ -116,8 +116,8 @@ use crate::{ slashing, vstaging::{ - self, CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - ScrapedOnChainVotes, + self, async_backing::Constraints, CandidateEvent, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, ApprovalVotingParams, AsyncBackingParams, BlockNumber, CandidateCommitments, CandidateHash, CoreIndex, DisputeState, ExecutorParams, GroupRotationInfo, Hash, NodeFeatures, @@ -297,5 +297,11 @@ sp_api::decl_runtime_apis! { /// Elastic scaling support #[api_version(11)] fn candidates_pending_availability(para_id: ppp::Id) -> Vec>; + + /***** Added in v12 *****/ + /// Returns the constraints on the actions that can be taken by a new parachain + /// block. + #[api_version(12)] + fn backing_constraints(para_id: ppp::Id) -> Option; } } diff --git a/polkadot/primitives/src/vstaging/async_backing.rs b/polkadot/primitives/src/vstaging/async_backing.rs index 8706214b5a010..ce9954538056f 100644 --- a/polkadot/primitives/src/vstaging/async_backing.rs +++ b/polkadot/primitives/src/vstaging/async_backing.rs @@ -50,12 +50,50 @@ impl From> } } +/// Constraints on the actions that can be taken by a new parachain +/// block. These limitations are implicitly associated with some particular +/// parachain, which should be apparent from usage. +#[derive(RuntimeDebug, Clone, PartialEq, Encode, Decode, TypeInfo)] +pub struct Constraints { + /// The minimum relay-parent number accepted under these constraints. + pub min_relay_parent_number: N, + /// The maximum Proof-of-Validity size allowed, in bytes. + pub max_pov_size: u32, + /// The maximum new validation code size allowed, in bytes. + pub max_code_size: u32, + /// The maximum head-data size, in bytes. + pub max_head_data_size: u32, + /// The amount of UMP messages remaining. + pub ump_remaining: u32, + /// The amount of UMP bytes remaining. + pub ump_remaining_bytes: u32, + /// The maximum number of UMP messages allowed per candidate. + pub max_ump_num_per_candidate: u32, + /// Remaining DMP queue. Only includes sent-at block numbers. + pub dmp_remaining_messages: Vec, + /// The limitations of all registered inbound HRMP channels. + pub hrmp_inbound: InboundHrmpLimitations, + /// The limitations of all registered outbound HRMP channels. + pub hrmp_channels_out: Vec<(Id, OutboundHrmpChannelLimitations)>, + /// The maximum number of HRMP messages allowed per candidate. + pub max_hrmp_num_per_candidate: u32, + /// The required parent head-data of the parachain. + pub required_parent: HeadData, + /// The expected validation-code-hash of this parachain. + pub validation_code_hash: ValidationCodeHash, + /// The code upgrade restriction signal as-of this parachain. + pub upgrade_restriction: Option, + /// The future validation code hash, if any, and at what relay-parent + /// number the upgrade would be minimally applied. + pub future_validation_code: Option<(N, ValidationCodeHash)>, +} + /// The per-parachain state of the backing system, including /// state-machine constraints and candidates pending availability. #[derive(RuntimeDebug, Clone, PartialEq, Encode, Decode, TypeInfo)] pub struct BackingState { /// The state-machine constraints of the parachain. - pub constraints: Constraints, + pub constraints: crate::async_backing::Constraints, /// The candidates pending availability. These should be ordered, i.e. they should form /// a sub-chain, where the first candidate builds on top of the required parent of the /// constraints and each subsequent builds on top of the previous head-data. diff --git a/polkadot/primitives/src/vstaging/mod.rs b/polkadot/primitives/src/vstaging/mod.rs index c52f3539c3e53..5da4595af6580 100644 --- a/polkadot/primitives/src/vstaging/mod.rs +++ b/polkadot/primitives/src/vstaging/mod.rs @@ -19,10 +19,11 @@ use crate::{ValidatorIndex, ValidityAttestation}; // Put any primitives used by staging APIs functions here use super::{ - async_backing::Constraints, BlakeTwo256, BlockNumber, CandidateCommitments, - CandidateDescriptor, CandidateHash, CollatorId, CollatorSignature, CoreIndex, GroupIndex, Hash, - HashT, HeadData, Header, Id, Id as ParaId, MultiDisputeStatementSet, ScheduledCore, - UncheckedSignedAvailabilityBitfields, ValidationCodeHash, + async_backing::{InboundHrmpLimitations, OutboundHrmpChannelLimitations}, + BlakeTwo256, BlockNumber, CandidateCommitments, CandidateDescriptor, CandidateHash, CollatorId, + CollatorSignature, CoreIndex, GroupIndex, Hash, HashT, HeadData, Header, Id, Id as ParaId, + MultiDisputeStatementSet, ScheduledCore, UncheckedSignedAvailabilityBitfields, + UpgradeRestriction, ValidationCodeHash, }; use alloc::{ collections::{BTreeMap, BTreeSet, VecDeque}, diff --git a/polkadot/roadmap/implementers-guide/src/node/backing/prospective-parachains.md b/polkadot/roadmap/implementers-guide/src/node/backing/prospective-parachains.md index 61278621cf565..0f210a0786400 100644 --- a/polkadot/roadmap/implementers-guide/src/node/backing/prospective-parachains.md +++ b/polkadot/roadmap/implementers-guide/src/node/backing/prospective-parachains.md @@ -126,6 +126,9 @@ prospective validation data. This is unlikely to change. - `RuntimeApiRequest::ParaBackingState` - Gets the backing state of the given para (the constraints of the para and candidates pending availability). +- `RuntimeApiRequest::BackingConstraints` + - Gets the constraints on the actions that can be taken by a new parachain + block. - `RuntimeApiRequest::AvailabilityCores` - Gets information on all availability cores. - `ChainApiMessage::Ancestors` diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/v11.rs b/polkadot/runtime/parachains/src/runtime_api_impl/v11.rs index e9327bc7641a3..3f2cb57710987 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/v11.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/v11.rs @@ -401,10 +401,10 @@ pub fn minimum_backing_votes() -> u32 { configuration::ActiveConfig::::get().minimum_backing_votes } -/// Implementation for `ParaBackingState` function from the runtime API -pub fn backing_state( +// Helper function that returns the backing constraints given a parachain id. +pub(crate) fn backing_constraints( para_id: ParaId, -) -> Option>> { +) -> Option>> { let config = configuration::ActiveConfig::::get(); // Async backing is only expected to be enabled with a tracker capacity of 1. // Subsequent configuration update gets applied on new session, which always @@ -458,7 +458,7 @@ pub fn backing_state( }) .collect(); - let constraints = Constraints { + Some(Constraints { min_relay_parent_number, max_pov_size: config.max_pov_size, max_code_size: config.max_code_size, @@ -473,7 +473,16 @@ pub fn backing_state( validation_code_hash, upgrade_restriction, future_validation_code, - }; + }) +} + +/// Implementation for `ParaBackingState` function from the runtime API +#[deprecated(note = "`backing_state` will be removed. Use `backing_constraints` and + `candidates_pending_availability` instead.")] +pub fn backing_state( + para_id: ParaId, +) -> Option>> { + let constraints = backing_constraints::(para_id)?; let pending_availability = { crate::inclusion::PendingAvailability::::get(¶_id) diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs b/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs index d01b543630c31..52a9a9e122889 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs @@ -15,3 +15,33 @@ // along with Polkadot. If not, see . //! Put implementations of functions from staging APIs here. + +use crate::{configuration, initializer}; +use frame_system::pallet_prelude::*; +use polkadot_primitives::{vstaging::async_backing::Constraints, Id as ParaId}; + +/// Implementation for `constraints` function from the runtime API +pub fn backing_constraints( + para_id: ParaId, +) -> Option>> { + let config = configuration::ActiveConfig::::get(); + let constraints_v11 = super::v11::backing_constraints::(para_id)?; + + Some(Constraints { + min_relay_parent_number: constraints_v11.min_relay_parent_number, + max_pov_size: constraints_v11.max_pov_size, + max_code_size: constraints_v11.max_code_size, + max_head_data_size: config.max_head_data_size, + ump_remaining: constraints_v11.ump_remaining, + ump_remaining_bytes: constraints_v11.ump_remaining_bytes, + max_ump_num_per_candidate: constraints_v11.max_ump_num_per_candidate, + dmp_remaining_messages: constraints_v11.dmp_remaining_messages, + hrmp_inbound: constraints_v11.hrmp_inbound, + hrmp_channels_out: constraints_v11.hrmp_channels_out, + max_hrmp_num_per_candidate: constraints_v11.max_hrmp_num_per_candidate, + required_parent: constraints_v11.required_parent, + validation_code_hash: constraints_v11.validation_code_hash, + upgrade_restriction: constraints_v11.upgrade_restriction, + future_validation_code: constraints_v11.future_validation_code, + }) +} diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index c2c3d35ee5b42..f165091beda47 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -49,8 +49,8 @@ use pallet_nis::WithMaximumOf; use polkadot_primitives::{ slashing, vstaging::{ - CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - ScrapedOnChainVotes, + async_backing::Constraints, CandidateEvent, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, AccountId, AccountIndex, ApprovalVotingParams, Balance, BlockNumber, CandidateHash, CoreIndex, DisputeState, ExecutorParams, GroupRotationInfo, Hash, Id as ParaId, InboundDownwardMessage, @@ -78,7 +78,9 @@ use polkadot_runtime_parachains::{ initializer as parachains_initializer, on_demand as parachains_on_demand, origin as parachains_origin, paras as parachains_paras, paras_inherent as parachains_paras_inherent, - runtime_api_impl::v11 as parachains_runtime_api_impl, + runtime_api_impl::{ + v11 as parachains_runtime_api_impl, vstaging as parachains_runtime_vstaging_api_impl, + }, scheduler as parachains_scheduler, session_info as parachains_session_info, shared as parachains_shared, }; @@ -1984,7 +1986,7 @@ sp_api::impl_runtime_apis! { } } - #[api_version(11)] + #[api_version(12)] impl polkadot_primitives::runtime_api::ParachainHost for Runtime { fn validators() -> Vec { parachains_runtime_api_impl::validators::() @@ -2122,6 +2124,7 @@ sp_api::impl_runtime_apis! { } fn para_backing_state(para_id: ParaId) -> Option { + #[allow(deprecated)] parachains_runtime_api_impl::backing_state::(para_id) } @@ -2148,6 +2151,10 @@ sp_api::impl_runtime_apis! { fn candidates_pending_availability(para_id: ParaId) -> Vec> { parachains_runtime_api_impl::candidates_pending_availability::(para_id) } + + fn backing_constraints(para_id: ParaId) -> Option { + parachains_runtime_vstaging_api_impl::backing_constraints::(para_id) + } } #[api_version(5)] diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs index cdf6fa92da2f5..4126193388cae 100644 --- a/polkadot/runtime/test-runtime/src/lib.rs +++ b/polkadot/runtime/test-runtime/src/lib.rs @@ -1067,6 +1067,7 @@ sp_api::impl_runtime_apis! { } fn para_backing_state(para_id: ParaId) -> Option { + #[allow(deprecated)] runtime_impl::backing_state::(para_id) } diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index a9ba0778fe0ef..935b62c23388e 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -52,8 +52,8 @@ use pallet_transaction_payment::{FeeDetails, FungibleAdapter, RuntimeDispatchInf use polkadot_primitives::{ slashing, vstaging::{ - CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - ScrapedOnChainVotes, + async_backing::Constraints, CandidateEvent, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, AccountId, AccountIndex, ApprovalVotingParams, Balance, BlockNumber, CandidateHash, CoreIndex, DisputeState, ExecutorParams, GroupRotationInfo, Hash, Id as ParaId, InboundDownwardMessage, @@ -84,7 +84,9 @@ use polkadot_runtime_parachains::{ initializer as parachains_initializer, on_demand as parachains_on_demand, origin as parachains_origin, paras as parachains_paras, paras_inherent as parachains_paras_inherent, reward_points as parachains_reward_points, - runtime_api_impl::v11 as parachains_runtime_api_impl, + runtime_api_impl::{ + v11 as parachains_runtime_api_impl, vstaging as parachains_runtime_vstaging_api_impl, + }, scheduler as parachains_scheduler, session_info as parachains_session_info, shared as parachains_shared, }; @@ -2010,7 +2012,7 @@ sp_api::impl_runtime_apis! { } } - #[api_version(11)] + #[api_version(12)] impl polkadot_primitives::runtime_api::ParachainHost for Runtime { fn validators() -> Vec { parachains_runtime_api_impl::validators::() @@ -2148,6 +2150,7 @@ sp_api::impl_runtime_apis! { } fn para_backing_state(para_id: ParaId) -> Option { + #[allow(deprecated)] parachains_runtime_api_impl::backing_state::(para_id) } @@ -2174,6 +2177,10 @@ sp_api::impl_runtime_apis! { fn candidates_pending_availability(para_id: ParaId) -> Vec> { parachains_runtime_api_impl::candidates_pending_availability::(para_id) } + + fn backing_constraints(para_id: ParaId) -> Option { + parachains_runtime_vstaging_api_impl::backing_constraints::(para_id) + } } #[api_version(5)] diff --git a/prdoc/pr_6867.prdoc b/prdoc/pr_6867.prdoc new file mode 100644 index 0000000000000..afa35533d4639 --- /dev/null +++ b/prdoc/pr_6867.prdoc @@ -0,0 +1,30 @@ +title: Deprecate ParaBackingState API +doc: +- audience: [ Runtime Dev, Node Dev ] + description: |- + Deprecates the `para_backing_state` API. Introduces and new `backing_constraints` API that can be used + together with existing `candidates_pending_availability` to retrieve the same information provided by + `para_backing_state`. + +crates: +- name: polkadot-primitives + bump: minor +- name: polkadot-runtime-parachains + bump: minor +- name: rococo-runtime + bump: minor +- name: westend-runtime + bump: minor +- name: cumulus-relay-chain-rpc-interface + bump: minor +- name: polkadot-node-core-prospective-parachains + bump: patch +- name: polkadot-node-core-runtime-api + bump: minor +- name: polkadot-node-subsystem-types + bump: major +- name: polkadot-node-subsystem-util + bump: major +- name: cumulus-relay-chain-minimal-node + bump: minor + From f845a9f42614120c98582f598d45d6d831455305 Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Fri, 24 Jan 2025 09:36:16 +0000 Subject: [PATCH 140/169] bench all weekly - and fix for pallet_multisig lib (#6789) Closes #6196 Closes #7204 Example of PR: https://github.com/paritytech/polkadot-sdk/pull/6816 Every sunday 01:00 AM it's going to start to benchmark (with /cmd bench) all runtimes and all pallets Then diff total will be pushed to a branch and PR open,. I assume review-bot is going assign required reviewers per changed files I afraid each weeks will be too much to review & merge, but we can adjust later Bonus: fix for pallet_multisig lib and substrate/.maintain/frame-weight-template.hbs , which didn't let to compile new weights --------- Signed-off-by: Oliver Tale-Yazdi Co-authored-by: command-bot <> Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Oliver Tale-Yazdi --- .github/workflows/bench-all-runtimes.yml | 165 ++++++++++++++++ .../src/weights/pallet_multisig.rs | 109 +++++----- .../src/weights/pallet_multisig.rs | 134 +++++++------ .../src/weights/pallet_multisig.rs | 134 +++++++------ .../src/weights/pallet_multisig.rs | 111 ++++++----- .../src/weights/pallet_multisig.rs | 126 ++++++------ .../src/weights/pallet_multisig.rs | 99 +++++----- .../src/weights/pallet_multisig.rs | 97 ++++----- .../src/weights/pallet_multisig.rs | 150 +++++++------- .../src/weights/pallet_multisig.rs | 150 +++++++------- .../rococo/src/weights/pallet_multisig.rs | 89 ++++----- .../westend/src/weights/pallet_multisig.rs | 125 ++++++------ substrate/.maintain/frame-weight-template.hbs | 3 +- substrate/frame/multisig/src/benchmarking.rs | 22 +-- substrate/frame/multisig/src/weights.rs | 186 +++++++++--------- 15 files changed, 949 insertions(+), 751 deletions(-) create mode 100644 .github/workflows/bench-all-runtimes.yml diff --git a/.github/workflows/bench-all-runtimes.yml b/.github/workflows/bench-all-runtimes.yml new file mode 100644 index 0000000000000..a24a7095d9801 --- /dev/null +++ b/.github/workflows/bench-all-runtimes.yml @@ -0,0 +1,165 @@ +name: Bench all runtimes + +on: + # schedule: + # - cron: '0 1 * * 0' # weekly on Sunday night 01:00 UTC + workflow_dispatch: + # pull_request: + +permissions: # allow the action to create a PR + contents: write + issues: write + pull-requests: write + actions: read + +jobs: + preflight: + uses: ./.github/workflows/reusable-preflight.yml + + runtime-matrix: + runs-on: ubuntu-latest + needs: [preflight] + timeout-minutes: 30 + outputs: + runtime: ${{ steps.runtime.outputs.runtime }} + container: + image: ${{ needs.preflight.outputs.IMAGE }} + name: Extract runtimes from matrix + steps: + - uses: actions/checkout@v4 + - id: runtime + run: | + RUNTIMES=$(jq '[.[] | select(.package != null)]' .github/workflows/runtimes-matrix.json) + + RUNTIMES=$(echo $RUNTIMES | jq -c .) + echo "runtime=$RUNTIMES" + echo "runtime=$RUNTIMES" >> $GITHUB_OUTPUT + + run-frame-omni-bencher: + needs: [preflight, runtime-matrix] + runs-on: ${{ needs.preflight.outputs.RUNNER_WEIGHTS }} + # 24 hours per runtime. + # Max it takes 14hr for westend to recalculate, but due to limited runners, + # sometimes it can take longer. + timeout-minutes: 1440 + strategy: + fail-fast: false # keep running other workflows even if one fails, to see the logs of all possible failures + matrix: + runtime: ${{ fromJSON(needs.runtime-matrix.outputs.runtime) }} + container: + image: ${{ needs.preflight.outputs.IMAGE }} + env: + PACKAGE_NAME: ${{ matrix.runtime.package }} + FLAGS: ${{ matrix.runtime.bench_flags }} + RUST_LOG: "frame_omni_bencher=info,polkadot_sdk_frame=info" + steps: + + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: master + + - name: script + id: required + run: | + # Fixes "detected dubious ownership" error in the ci + git config --global --add safe.directory $GITHUB_WORKSPACE + git remote -v + python3 -m pip install -r .github/scripts/generate-prdoc.requirements.txt + python3 .github/scripts/cmd/cmd.py bench --runtime ${{ matrix.runtime.name }} + git add . + git status + + if [ -f /tmp/cmd/command_output.log ]; then + CMD_OUTPUT=$(cat /tmp/cmd/command_output.log) + # export to summary to display in the PR + echo "$CMD_OUTPUT" >> $GITHUB_STEP_SUMMARY + # should be multiline, otherwise it captures the first line only + echo 'cmd_output<> $GITHUB_OUTPUT + echo "$CMD_OUTPUT" >> $GITHUB_OUTPUT + echo 'EOF' >> $GITHUB_OUTPUT + fi + + # Create patch that includes both modifications and new files + git add -A + git diff --staged > diff-${{ matrix.runtime.name }}.patch -U0 + git reset + + - name: Upload diff + uses: actions/upload-artifact@v4 + with: + name: diff-${{ matrix.runtime.name }} + path: diff-${{ matrix.runtime.name }}.patch + + apply-diff-commit: + runs-on: ubuntu-latest + needs: [run-frame-omni-bencher] + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: master + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: patches + + - name: Install subweight + run: cargo install subweight + + # needs to be able to trigger CI + - uses: actions/create-github-app-token@v1 + id: generate_token + with: + app-id: ${{ secrets.CMD_BOT_APP_ID }} + private-key: ${{ secrets.CMD_BOT_APP_KEY }} + + - name: Apply diff and create PR + env: + GH_TOKEN: ${{ steps.generate_token.outputs.token }} + run: | + DATE=$(date +'%Y-%m-%d-%s') + BRANCH="update-weights-weekly-$DATE" + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + git switch -c "$BRANCH" + + for file in patches/diff-*/diff-*.patch; do + if [ -f "$file" ] && [ -s "$file" ]; then + echo "Applying $file" + git apply "$file" --unidiff-zero --allow-empty || echo "Failed to apply $file" + else + echo "Skipping empty or non-existent patch file: $file" + fi + done + rm -rf patches + + git add . + git commit -m "Update all weights weekly for $DATE" + git push --set-upstream origin "$BRANCH" + + PR_TITLE="Auto-update of all weights for $DATE" + gh pr create \ + --title "$PR_TITLE" \ + --head "$BRANCH" \ + --base "master" \ + --reviewer paritytech/ci \ + --reviewer paritytech/release-engineering \ + --draft \ + --label "R0-silent" \ + --body "$PR_TITLE" + + subweight compare commits \ + --path-pattern "./**/weights/**/*.rs,./**/weights.rs" \ + --method asymptotic \ + --format markdown \ + --no-color \ + --change added changed \ + --ignore-errors \ + --threshold 2 \ + origin/master $BRANCH \ No newline at end of file diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_multisig.rs index cf9c523f6571f..1192478c90ac4 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_multisig.rs @@ -16,28 +16,28 @@ //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-rococo-dev")`, DB CACHE: 1024 +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet +// --extrinsic=* // --chain=asset-hub-rococo-dev -// --wasm-execution=compiled // --pallet=pallet_multisig -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-rococo/src/weights/ +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,11 +55,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_714_000 picoseconds. - Weight::from_parts(14_440_231, 0) + // Minimum execution time: 16_059_000 picoseconds. + Weight::from_parts(17_033_878, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 5 - .saturating_add(Weight::from_parts(598, 0).saturating_mul(z.into())) + // Standard Error: 8 + .saturating_add(Weight::from_parts(489, 0).saturating_mul(z.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) @@ -67,15 +67,15 @@ impl pallet_multisig::WeightInfo for WeightInfo { /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `262 + s * (2 ±0)` + // Measured: `295 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 44_768_000 picoseconds. - Weight::from_parts(33_662_218, 0) + // Minimum execution time: 46_128_000 picoseconds. + Weight::from_parts(33_704_180, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_633 - .saturating_add(Weight::from_parts(128_927, 0).saturating_mul(s.into())) - // Standard Error: 16 - .saturating_add(Weight::from_parts(1_543, 0).saturating_mul(z.into())) + // Standard Error: 1_456 + .saturating_add(Weight::from_parts(147_148, 0).saturating_mul(s.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(2_037, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -85,15 +85,15 @@ impl pallet_multisig::WeightInfo for WeightInfo { /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `282` + // Measured: `315` // Estimated: `6811` - // Minimum execution time: 29_745_000 picoseconds. - Weight::from_parts(20_559_891, 0) + // Minimum execution time: 32_218_000 picoseconds. + Weight::from_parts(21_320_145, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 914 - .saturating_add(Weight::from_parts(103_601, 0).saturating_mul(s.into())) - // Standard Error: 8 - .saturating_add(Weight::from_parts(1_504, 0).saturating_mul(z.into())) + // Standard Error: 1_922 + .saturating_add(Weight::from_parts(131_349, 0).saturating_mul(s.into())) + // Standard Error: 18 + .saturating_add(Weight::from_parts(1_829, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -105,60 +105,63 @@ impl pallet_multisig::WeightInfo for WeightInfo { /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `385 + s * (33 ±0)` + // Measured: `418 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 51_506_000 picoseconds. - Weight::from_parts(36_510_777, 0) + // Minimum execution time: 53_641_000 picoseconds. + Weight::from_parts(32_057_363, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 2_183 - .saturating_add(Weight::from_parts(183_764, 0).saturating_mul(s.into())) - // Standard Error: 21 - .saturating_add(Weight::from_parts(1_653, 0).saturating_mul(z.into())) + // Standard Error: 2_897 + .saturating_add(Weight::from_parts(254_035, 0).saturating_mul(s.into())) + // Standard Error: 28 + .saturating_add(Weight::from_parts(2_432, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `263 + s * (2 ±0)` + // Measured: `295 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 31_072_000 picoseconds. - Weight::from_parts(32_408_621, 0) + // Minimum execution time: 30_302_000 picoseconds. + Weight::from_parts(33_367_363, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 913 - .saturating_add(Weight::from_parts(121_410, 0).saturating_mul(s.into())) + // Standard Error: 1_389 + .saturating_add(Weight::from_parts(150_845, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `282` + // Measured: `315` // Estimated: `6811` - // Minimum execution time: 18_301_000 picoseconds. - Weight::from_parts(18_223_547, 0) + // Minimum execution time: 17_008_000 picoseconds. + Weight::from_parts(18_452_875, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 747 - .saturating_add(Weight::from_parts(114_584, 0).saturating_mul(s.into())) + // Standard Error: 949 + .saturating_add(Weight::from_parts(130_051, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `454 + s * (1 ±0)` + // Measured: `482 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 32_107_000 picoseconds. - Weight::from_parts(33_674_827, 0) + // Minimum execution time: 30_645_000 picoseconds. + Weight::from_parts(33_864_517, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_220 - .saturating_add(Weight::from_parts(122_011, 0).saturating_mul(s.into())) + // Standard Error: 1_511 + .saturating_add(Weight::from_parts(138_628, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_multisig.rs index 27687e10751b3..737ee0f54df0c 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_multisig.rs @@ -1,42 +1,43 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 +// This file is part of Cumulus. -// 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. +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-westend-dev")`, DB CACHE: 1024 +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet +// --extrinsic=* // --chain=asset-hub-westend-dev -// --wasm-execution=compiled // --pallet=pallet_multisig -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-westend/src/weights/ +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -54,11 +55,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 14_098_000 picoseconds. - Weight::from_parts(14_915_657, 0) + // Minimum execution time: 16_032_000 picoseconds. + Weight::from_parts(16_636_014, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 6 - .saturating_add(Weight::from_parts(454, 0).saturating_mul(z.into())) + // Standard Error: 11 + .saturating_add(Weight::from_parts(632, 0).saturating_mul(z.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) @@ -66,15 +67,15 @@ impl pallet_multisig::WeightInfo for WeightInfo { /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `262 + s * (2 ±0)` + // Measured: `295 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 44_573_000 picoseconds. - Weight::from_parts(32_633_219, 0) + // Minimum execution time: 47_519_000 picoseconds. + Weight::from_parts(33_881_382, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_256 - .saturating_add(Weight::from_parts(131_767, 0).saturating_mul(s.into())) - // Standard Error: 12 - .saturating_add(Weight::from_parts(1_512, 0).saturating_mul(z.into())) + // Standard Error: 1_770 + .saturating_add(Weight::from_parts(159_560, 0).saturating_mul(s.into())) + // Standard Error: 17 + .saturating_add(Weight::from_parts(2_031, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -84,15 +85,15 @@ impl pallet_multisig::WeightInfo for WeightInfo { /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `282` + // Measured: `315` // Estimated: `6811` - // Minimum execution time: 30_035_000 picoseconds. - Weight::from_parts(20_179_371, 0) + // Minimum execution time: 31_369_000 picoseconds. + Weight::from_parts(18_862_672, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 827 - .saturating_add(Weight::from_parts(110_520, 0).saturating_mul(s.into())) - // Standard Error: 8 - .saturating_add(Weight::from_parts(1_419, 0).saturating_mul(z.into())) + // Standard Error: 1_519 + .saturating_add(Weight::from_parts(141_546, 0).saturating_mul(s.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(2_057, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -104,60 +105,63 @@ impl pallet_multisig::WeightInfo for WeightInfo { /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `385 + s * (33 ±0)` + // Measured: `418 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 50_444_000 picoseconds. - Weight::from_parts(36_060_265, 0) + // Minimum execution time: 55_421_000 picoseconds. + Weight::from_parts(33_628_199, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_604 - .saturating_add(Weight::from_parts(187_796, 0).saturating_mul(s.into())) - // Standard Error: 15 - .saturating_add(Weight::from_parts(1_506, 0).saturating_mul(z.into())) + // Standard Error: 2_430 + .saturating_add(Weight::from_parts(247_959, 0).saturating_mul(s.into())) + // Standard Error: 23 + .saturating_add(Weight::from_parts(2_339, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `263 + s * (2 ±0)` + // Measured: `295 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 30_298_000 picoseconds. - Weight::from_parts(31_284_628, 0) + // Minimum execution time: 30_380_000 picoseconds. + Weight::from_parts(32_147_463, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 924 - .saturating_add(Weight::from_parts(132_724, 0).saturating_mul(s.into())) + // Standard Error: 1_530 + .saturating_add(Weight::from_parts(156_234, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `282` + // Measured: `315` // Estimated: `6811` - // Minimum execution time: 17_486_000 picoseconds. - Weight::from_parts(18_518_530, 0) + // Minimum execution time: 17_016_000 picoseconds. + Weight::from_parts(17_777_791, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_274 - .saturating_add(Weight::from_parts(103_767, 0).saturating_mul(s.into())) + // Standard Error: 1_216 + .saturating_add(Weight::from_parts(137_967, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `454 + s * (1 ±0)` + // Measured: `482 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 31_236_000 picoseconds. - Weight::from_parts(32_663_816, 0) + // Minimum execution time: 31_594_000 picoseconds. + Weight::from_parts(31_850_574, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_445 - .saturating_add(Weight::from_parts(131_060, 0).saturating_mul(s.into())) + // Standard Error: 2_031 + .saturating_add(Weight::from_parts(159_513, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_multisig.rs index 832380d3876bc..4ee6f6725409b 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_multisig.rs @@ -1,42 +1,43 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 +// This file is part of Cumulus. -// 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. +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet +// --extrinsic=* // --chain=bridge-hub-rococo-dev -// --wasm-execution=compiled // --pallet=pallet_multisig -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/ +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -54,11 +55,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_958_000 picoseconds. - Weight::from_parts(14_501_711, 0) + // Minimum execution time: 16_890_000 picoseconds. + Weight::from_parts(17_493_920, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 4 - .saturating_add(Weight::from_parts(626, 0).saturating_mul(z.into())) + // Standard Error: 11 + .saturating_add(Weight::from_parts(559, 0).saturating_mul(z.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) @@ -66,15 +67,15 @@ impl pallet_multisig::WeightInfo for WeightInfo { /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `263 + s * (2 ±0)` + // Measured: `191 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 44_067_000 picoseconds. - Weight::from_parts(33_432_998, 0) + // Minimum execution time: 46_099_000 picoseconds. + Weight::from_parts(34_431_293, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_250 - .saturating_add(Weight::from_parts(131_851, 0).saturating_mul(s.into())) - // Standard Error: 12 - .saturating_add(Weight::from_parts(1_459, 0).saturating_mul(z.into())) + // Standard Error: 2_489 + .saturating_add(Weight::from_parts(151_886, 0).saturating_mul(s.into())) + // Standard Error: 24 + .saturating_add(Weight::from_parts(1_900, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -84,15 +85,15 @@ impl pallet_multisig::WeightInfo for WeightInfo { /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `282` + // Measured: `210` // Estimated: `6811` - // Minimum execution time: 29_373_000 picoseconds. - Weight::from_parts(19_409_201, 0) + // Minimum execution time: 31_133_000 picoseconds. + Weight::from_parts(19_877_758, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 725 - .saturating_add(Weight::from_parts(110_824, 0).saturating_mul(s.into())) - // Standard Error: 7 - .saturating_add(Weight::from_parts(1_502, 0).saturating_mul(z.into())) + // Standard Error: 1_220 + .saturating_add(Weight::from_parts(132_155, 0).saturating_mul(s.into())) + // Standard Error: 11 + .saturating_add(Weight::from_parts(1_916, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -104,60 +105,63 @@ impl pallet_multisig::WeightInfo for WeightInfo { /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `388 + s * (33 ±0)` + // Measured: `316 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 49_724_000 picoseconds. - Weight::from_parts(34_153_321, 0) + // Minimum execution time: 58_414_000 picoseconds. + Weight::from_parts(32_980_753, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_376 - .saturating_add(Weight::from_parts(174_634, 0).saturating_mul(s.into())) - // Standard Error: 13 - .saturating_add(Weight::from_parts(1_753, 0).saturating_mul(z.into())) + // Standard Error: 3_838 + .saturating_add(Weight::from_parts(302_359, 0).saturating_mul(s.into())) + // Standard Error: 37 + .saturating_add(Weight::from_parts(2_629, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `263 + s * (2 ±0)` + // Measured: `191 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 31_081_000 picoseconds. - Weight::from_parts(31_552_702, 0) + // Minimum execution time: 29_917_000 picoseconds. + Weight::from_parts(33_459_806, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_066 - .saturating_add(Weight::from_parts(135_081, 0).saturating_mul(s.into())) + // Standard Error: 1_607 + .saturating_add(Weight::from_parts(150_128, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `282` + // Measured: `210` // Estimated: `6811` - // Minimum execution time: 17_807_000 picoseconds. - Weight::from_parts(18_241_044, 0) + // Minimum execution time: 16_739_000 picoseconds. + Weight::from_parts(16_757_542, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 768 - .saturating_add(Weight::from_parts(112_957, 0).saturating_mul(s.into())) + // Standard Error: 909 + .saturating_add(Weight::from_parts(138_791, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `454 + s * (1 ±0)` + // Measured: `382 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 32_421_000 picoseconds. - Weight::from_parts(32_554_061, 0) + // Minimum execution time: 35_004_000 picoseconds. + Weight::from_parts(35_434_253, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_157 - .saturating_add(Weight::from_parts(141_221, 0).saturating_mul(s.into())) + // Standard Error: 1_130 + .saturating_add(Weight::from_parts(158_542, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_multisig.rs index 91840ae0c6d77..599bed182de47 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_multisig.rs @@ -16,28 +16,28 @@ //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet -// --chain=bridge-hub-rococo-dev -// --wasm-execution=compiled -// --pallet=pallet_multisig -// --no-storage-info -// --no-median-slopes -// --no-min-squares // --extrinsic=* +// --chain=bridge-hub-westend-dev +// --pallet=pallet_multisig +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/ +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,11 +55,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_958_000 picoseconds. - Weight::from_parts(14_501_711, 0) + // Minimum execution time: 16_960_000 picoseconds. + Weight::from_parts(17_458_038, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 4 - .saturating_add(Weight::from_parts(626, 0).saturating_mul(z.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(745, 0).saturating_mul(z.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) @@ -67,15 +67,15 @@ impl pallet_multisig::WeightInfo for WeightInfo { /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `263 + s * (2 ±0)` + // Measured: `296 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 44_067_000 picoseconds. - Weight::from_parts(33_432_998, 0) + // Minimum execution time: 49_023_000 picoseconds. + Weight::from_parts(36_653_713, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_250 - .saturating_add(Weight::from_parts(131_851, 0).saturating_mul(s.into())) - // Standard Error: 12 - .saturating_add(Weight::from_parts(1_459, 0).saturating_mul(z.into())) + // Standard Error: 1_966 + .saturating_add(Weight::from_parts(144_768, 0).saturating_mul(s.into())) + // Standard Error: 19 + .saturating_add(Weight::from_parts(1_983, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -85,15 +85,15 @@ impl pallet_multisig::WeightInfo for WeightInfo { /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `282` + // Measured: `315` // Estimated: `6811` - // Minimum execution time: 29_373_000 picoseconds. - Weight::from_parts(19_409_201, 0) + // Minimum execution time: 32_233_000 picoseconds. + Weight::from_parts(20_563_994, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 725 - .saturating_add(Weight::from_parts(110_824, 0).saturating_mul(s.into())) - // Standard Error: 7 - .saturating_add(Weight::from_parts(1_502, 0).saturating_mul(z.into())) + // Standard Error: 1_541 + .saturating_add(Weight::from_parts(137_834, 0).saturating_mul(s.into())) + // Standard Error: 15 + .saturating_add(Weight::from_parts(2_004, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -105,60 +105,63 @@ impl pallet_multisig::WeightInfo for WeightInfo { /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `388 + s * (33 ±0)` + // Measured: `421 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 49_724_000 picoseconds. - Weight::from_parts(34_153_321, 0) + // Minimum execution time: 57_893_000 picoseconds. + Weight::from_parts(32_138_684, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_376 - .saturating_add(Weight::from_parts(174_634, 0).saturating_mul(s.into())) - // Standard Error: 13 - .saturating_add(Weight::from_parts(1_753, 0).saturating_mul(z.into())) + // Standard Error: 3_096 + .saturating_add(Weight::from_parts(324_931, 0).saturating_mul(s.into())) + // Standard Error: 30 + .saturating_add(Weight::from_parts(2_617, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `263 + s * (2 ±0)` + // Measured: `296 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 31_081_000 picoseconds. - Weight::from_parts(31_552_702, 0) + // Minimum execution time: 31_313_000 picoseconds. + Weight::from_parts(33_535_933, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_066 - .saturating_add(Weight::from_parts(135_081, 0).saturating_mul(s.into())) + // Standard Error: 1_649 + .saturating_add(Weight::from_parts(153_756, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `282` + // Measured: `315` // Estimated: `6811` - // Minimum execution time: 17_807_000 picoseconds. - Weight::from_parts(18_241_044, 0) + // Minimum execution time: 17_860_000 picoseconds. + Weight::from_parts(18_559_535, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 768 - .saturating_add(Weight::from_parts(112_957, 0).saturating_mul(s.into())) + // Standard Error: 1_036 + .saturating_add(Weight::from_parts(135_049, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `454 + s * (1 ±0)` + // Measured: `487 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 32_421_000 picoseconds. - Weight::from_parts(32_554_061, 0) + // Minimum execution time: 32_340_000 picoseconds. + Weight::from_parts(33_519_124, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_157 - .saturating_add(Weight::from_parts(141_221, 0).saturating_mul(s.into())) + // Standard Error: 1_932 + .saturating_add(Weight::from_parts(193_896, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_multisig.rs index a7827b7200906..5c428bb5e5eac 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_multisig.rs @@ -1,42 +1,43 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 +// This file is part of Cumulus. -// 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. +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-polkadot-dev")`, DB CACHE: 1024 +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet -// --chain=collectives-polkadot-dev -// --wasm-execution=compiled -// --pallet=pallet_multisig -// --no-storage-info -// --no-median-slopes -// --no-min-squares // --extrinsic=* +// --chain=collectives-westend-dev +// --pallet=pallet_multisig +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/collectives/collectives-polkadot/src/weights/ +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -54,11 +55,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_288_000 picoseconds. - Weight::from_parts(14_235_741, 0) + // Minimum execution time: 16_309_000 picoseconds. + Weight::from_parts(17_281_100, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 5 - .saturating_add(Weight::from_parts(500, 0).saturating_mul(z.into())) + // Standard Error: 10 + .saturating_add(Weight::from_parts(549, 0).saturating_mul(z.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) @@ -68,13 +69,13 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `328 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 44_865_000 picoseconds. - Weight::from_parts(33_468_056, 0) + // Minimum execution time: 48_617_000 picoseconds. + Weight::from_parts(35_426_484, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_513 - .saturating_add(Weight::from_parts(130_544, 0).saturating_mul(s.into())) - // Standard Error: 14 - .saturating_add(Weight::from_parts(1_422, 0).saturating_mul(z.into())) + // Standard Error: 1_941 + .saturating_add(Weight::from_parts(164_183, 0).saturating_mul(s.into())) + // Standard Error: 19 + .saturating_add(Weight::from_parts(1_898, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -86,13 +87,13 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `348` // Estimated: `6811` - // Minimum execution time: 29_284_000 picoseconds. - Weight::from_parts(18_708_967, 0) + // Minimum execution time: 32_600_000 picoseconds. + Weight::from_parts(18_613_047, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 916 - .saturating_add(Weight::from_parts(119_202, 0).saturating_mul(s.into())) - // Standard Error: 8 - .saturating_add(Weight::from_parts(1_447, 0).saturating_mul(z.into())) + // Standard Error: 1_498 + .saturating_add(Weight::from_parts(147_489, 0).saturating_mul(s.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(2_094, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -106,28 +107,29 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `451 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 49_462_000 picoseconds. - Weight::from_parts(34_470_286, 0) + // Minimum execution time: 55_580_000 picoseconds. + Weight::from_parts(32_757_473, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_738 - .saturating_add(Weight::from_parts(178_227, 0).saturating_mul(s.into())) - // Standard Error: 17 - .saturating_add(Weight::from_parts(1_644, 0).saturating_mul(z.into())) + // Standard Error: 3_265 + .saturating_add(Weight::from_parts(261_212, 0).saturating_mul(s.into())) + // Standard Error: 32 + .saturating_add(Weight::from_parts(2_407, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `329 + s * (2 ±0)` + // Measured: `328 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 30_749_000 picoseconds. - Weight::from_parts(31_841_438, 0) + // Minimum execution time: 31_137_000 picoseconds. + Weight::from_parts(32_271_159, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_033 - .saturating_add(Weight::from_parts(123_126, 0).saturating_mul(s.into())) + // Standard Error: 1_280 + .saturating_add(Weight::from_parts(163_156, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -138,11 +140,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `348` // Estimated: `6811` - // Minimum execution time: 17_436_000 picoseconds. - Weight::from_parts(18_036_002, 0) + // Minimum execution time: 17_763_000 picoseconds. + Weight::from_parts(18_235_437, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 829 - .saturating_add(Weight::from_parts(109_450, 0).saturating_mul(s.into())) + // Standard Error: 1_245 + .saturating_add(Weight::from_parts(138_553, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -151,13 +153,13 @@ impl pallet_multisig::WeightInfo for WeightInfo { /// The range of component `s` is `[2, 100]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `520 + s * (1 ±0)` + // Measured: `515 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 31_532_000 picoseconds. - Weight::from_parts(32_818_015, 0) + // Minimum execution time: 32_152_000 picoseconds. + Weight::from_parts(34_248_643, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 977 - .saturating_add(Weight::from_parts(123_121, 0).saturating_mul(s.into())) + // Standard Error: 1_943 + .saturating_add(Weight::from_parts(153_258, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_multisig.rs index 8e010d768f643..f3ab1b1cac8a5 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_multisig.rs @@ -16,28 +16,28 @@ //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2024-01-12, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-j8vvqcjr-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet +// --extrinsic=* // --chain=coretime-rococo-dev -// --wasm-execution=compiled // --pallet=pallet_multisig -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --json -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/ +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,11 +55,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_905_000 picoseconds. - Weight::from_parts(13_544_225, 0) + // Minimum execution time: 16_150_000 picoseconds. + Weight::from_parts(17_417_293, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 2 - .saturating_add(Weight::from_parts(596, 0).saturating_mul(z.into())) + // Standard Error: 10 + .saturating_add(Weight::from_parts(488, 0).saturating_mul(z.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) @@ -69,13 +69,13 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `262 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 38_729_000 picoseconds. - Weight::from_parts(27_942_442, 0) + // Minimum execution time: 47_027_000 picoseconds. + Weight::from_parts(33_446_171, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 648 - .saturating_add(Weight::from_parts(120_340, 0).saturating_mul(s.into())) - // Standard Error: 6 - .saturating_add(Weight::from_parts(1_578, 0).saturating_mul(z.into())) + // Standard Error: 1_434 + .saturating_add(Weight::from_parts(152_452, 0).saturating_mul(s.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(2_012, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -87,13 +87,13 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 25_936_000 picoseconds. - Weight::from_parts(16_537_903, 0) + // Minimum execution time: 32_131_000 picoseconds. + Weight::from_parts(18_539_623, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 412 - .saturating_add(Weight::from_parts(105_835, 0).saturating_mul(s.into())) - // Standard Error: 4 - .saturating_add(Weight::from_parts(1_534, 0).saturating_mul(z.into())) + // Standard Error: 1_460 + .saturating_add(Weight::from_parts(140_999, 0).saturating_mul(s.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(2_033, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -107,58 +107,61 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `385 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 45_291_000 picoseconds. - Weight::from_parts(31_294_385, 0) + // Minimum execution time: 53_701_000 picoseconds. + Weight::from_parts(32_431_551, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 816 - .saturating_add(Weight::from_parts(152_838, 0).saturating_mul(s.into())) - // Standard Error: 8 - .saturating_add(Weight::from_parts(1_638, 0).saturating_mul(z.into())) + // Standard Error: 2_797 + .saturating_add(Weight::from_parts(255_676, 0).saturating_mul(s.into())) + // Standard Error: 27 + .saturating_add(Weight::from_parts(2_261, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `263 + s * (2 ±0)` + // Measured: `262 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 26_585_000 picoseconds. - Weight::from_parts(27_424_168, 0) + // Minimum execution time: 30_011_000 picoseconds. + Weight::from_parts(32_146_378, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 732 - .saturating_add(Weight::from_parts(123_460, 0).saturating_mul(s.into())) + // Standard Error: 1_455 + .saturating_add(Weight::from_parts(160_784, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 15_228_000 picoseconds. - Weight::from_parts(15_568_631, 0) + // Minimum execution time: 16_968_000 picoseconds. + Weight::from_parts(16_851_993, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 441 - .saturating_add(Weight::from_parts(107_463, 0).saturating_mul(s.into())) + // Standard Error: 793 + .saturating_add(Weight::from_parts(142_320, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `454 + s * (1 ±0)` + // Measured: `449 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 28_033_000 picoseconds. - Weight::from_parts(29_228_827, 0) + // Minimum execution time: 31_706_000 picoseconds. + Weight::from_parts(33_679_423, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 748 - .saturating_add(Weight::from_parts(117_495, 0).saturating_mul(s.into())) + // Standard Error: 1_154 + .saturating_add(Weight::from_parts(145_059, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_multisig.rs index 1aaf3f4a6fb9d..044356f1e1467 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_multisig.rs @@ -17,27 +17,27 @@ //! Autogenerated weights for `pallet_multisig` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet +// --extrinsic=* // --chain=coretime-westend-dev -// --wasm-execution=compiled // --pallet=pallet_multisig -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --json -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,11 +55,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_938_000 picoseconds. - Weight::from_parts(13_021_007, 0) + // Minimum execution time: 16_090_000 picoseconds. + Weight::from_parts(16_926_991, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 4 - .saturating_add(Weight::from_parts(482, 0).saturating_mul(z.into())) + // Standard Error: 6 + .saturating_add(Weight::from_parts(500, 0).saturating_mul(z.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) @@ -69,13 +69,13 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `262 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 37_643_000 picoseconds. - Weight::from_parts(27_088_068, 0) + // Minimum execution time: 46_739_000 picoseconds. + Weight::from_parts(34_253_833, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 828 - .saturating_add(Weight::from_parts(123_693, 0).saturating_mul(s.into())) - // Standard Error: 8 - .saturating_add(Weight::from_parts(1_456, 0).saturating_mul(z.into())) + // Standard Error: 1_258 + .saturating_add(Weight::from_parts(141_511, 0).saturating_mul(s.into())) + // Standard Error: 12 + .saturating_add(Weight::from_parts(1_969, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -87,13 +87,13 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 25_825_000 picoseconds. - Weight::from_parts(15_698_835, 0) + // Minimum execution time: 31_190_000 picoseconds. + Weight::from_parts(18_287_369, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 568 - .saturating_add(Weight::from_parts(111_928, 0).saturating_mul(s.into())) - // Standard Error: 5 - .saturating_add(Weight::from_parts(1_421, 0).saturating_mul(z.into())) + // Standard Error: 1_405 + .saturating_add(Weight::from_parts(143_414, 0).saturating_mul(s.into())) + // Standard Error: 13 + .saturating_add(Weight::from_parts(2_047, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -107,58 +107,61 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `385 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 43_587_000 picoseconds. - Weight::from_parts(29_740_539, 0) + // Minimum execution time: 53_340_000 picoseconds. + Weight::from_parts(31_091_227, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 771 - .saturating_add(Weight::from_parts(154_861, 0).saturating_mul(s.into())) - // Standard Error: 7 - .saturating_add(Weight::from_parts(1_557, 0).saturating_mul(z.into())) + // Standard Error: 3_346 + .saturating_add(Weight::from_parts(256_292, 0).saturating_mul(s.into())) + // Standard Error: 32 + .saturating_add(Weight::from_parts(2_518, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `263 + s * (2 ±0)` + // Measured: `262 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 24_966_000 picoseconds. - Weight::from_parts(25_879_458, 0) + // Minimum execution time: 30_024_000 picoseconds. + Weight::from_parts(32_926_280, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 777 - .saturating_add(Weight::from_parts(122_823, 0).saturating_mul(s.into())) + // Standard Error: 1_559 + .saturating_add(Weight::from_parts(151_433, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 14_450_000 picoseconds. - Weight::from_parts(14_607_858, 0) + // Minimum execution time: 16_853_000 picoseconds. + Weight::from_parts(17_314_743, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 471 - .saturating_add(Weight::from_parts(107_007, 0).saturating_mul(s.into())) + // Standard Error: 1_022 + .saturating_add(Weight::from_parts(139_694, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `454 + s * (1 ±0)` + // Measured: `449 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 26_861_000 picoseconds. - Weight::from_parts(27_846_825, 0) + // Minimum execution time: 31_102_000 picoseconds. + Weight::from_parts(32_212_096, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 714 - .saturating_add(Weight::from_parts(116_914, 0).saturating_mul(s.into())) + // Standard Error: 1_524 + .saturating_add(Weight::from_parts(151_963, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_multisig.rs index 73abb62b0482c..82fcacf64aca6 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_multisig.rs @@ -1,40 +1,43 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 +// This file is part of Cumulus. -// 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. +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm4`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("people-kusama-dev"), DB CACHE: 1024 +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("people-rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./artifacts/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet -// --chain=people-kusama-dev -// --execution=wasm -// --wasm-execution=compiled -// --pallet=pallet_multisig // --extrinsic=* +// --chain=people-rococo-dev +// --pallet=pallet_multisig +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-rococo/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./cumulus/parachains/runtimes/people/people-kusama/src/weights/pallet_multisig.rs +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -52,110 +55,113 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_056_000 picoseconds. - Weight::from_parts(11_510_137, 0) + // Minimum execution time: 16_209_000 picoseconds. + Weight::from_parts(16_941_673, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 1 - .saturating_add(Weight::from_parts(528, 0).saturating_mul(z.into())) + // Standard Error: 10 + .saturating_add(Weight::from_parts(551, 0).saturating_mul(z.into())) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `263 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 41_105_000 picoseconds. - Weight::from_parts(34_947_072, 0) + // Minimum execution time: 47_880_000 picoseconds. + Weight::from_parts(35_747_073, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 499 - .saturating_add(Weight::from_parts(67_375, 0).saturating_mul(s.into())) - // Standard Error: 4 - .saturating_add(Weight::from_parts(1_227, 0).saturating_mul(z.into())) + // Standard Error: 2_069 + .saturating_add(Weight::from_parts(147_421, 0).saturating_mul(s.into())) + // Standard Error: 20 + .saturating_add(Weight::from_parts(1_853, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[3, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 26_640_000 picoseconds. - Weight::from_parts(21_515_344, 0) + // Minimum execution time: 31_245_000 picoseconds. + Weight::from_parts(19_011_583, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 943 - .saturating_add(Weight::from_parts(58_769, 0).saturating_mul(s.into())) - // Standard Error: 9 - .saturating_add(Weight::from_parts(1_233, 0).saturating_mul(z.into())) + // Standard Error: 1_336 + .saturating_add(Weight::from_parts(136_422, 0).saturating_mul(s.into())) + // Standard Error: 13 + .saturating_add(Weight::from_parts(2_013, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `388 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 45_875_000 picoseconds. - Weight::from_parts(38_052_994, 0) + // Minimum execution time: 52_116_000 picoseconds. + Weight::from_parts(33_912_565, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 507 - .saturating_add(Weight::from_parts(82_957, 0).saturating_mul(s.into())) - // Standard Error: 4 - .saturating_add(Weight::from_parts(1_277, 0).saturating_mul(z.into())) + // Standard Error: 3_064 + .saturating_add(Weight::from_parts(258_562, 0).saturating_mul(s.into())) + // Standard Error: 30 + .saturating_add(Weight::from_parts(2_206, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `263 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 32_359_000 picoseconds. - Weight::from_parts(33_845_761, 0) + // Minimum execution time: 31_142_000 picoseconds. + Weight::from_parts(32_417_223, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 623 - .saturating_add(Weight::from_parts(69_809, 0).saturating_mul(s.into())) + // Standard Error: 1_622 + .saturating_add(Weight::from_parts(163_533, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 18_791_000 picoseconds. - Weight::from_parts(20_017_375, 0) + // Minimum execution time: 17_183_000 picoseconds. + Weight::from_parts(18_181_089, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 466 - .saturating_add(Weight::from_parts(64_780, 0).saturating_mul(s.into())) + // Standard Error: 1_123 + .saturating_add(Weight::from_parts(134_567, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `454 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 33_132_000 picoseconds. - Weight::from_parts(34_485_734, 0) + // Minimum execution time: 32_006_000 picoseconds. + Weight::from_parts(33_910_335, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 601 - .saturating_add(Weight::from_parts(70_191, 0).saturating_mul(s.into())) + // Standard Error: 1_347 + .saturating_add(Weight::from_parts(138_258, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_multisig.rs index 70809dea2366c..5857a140e05e0 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_multisig.rs @@ -1,40 +1,43 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 +// This file is part of Cumulus. -// 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. +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm4`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("people-polkadot-dev"), DB CACHE: 1024 +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("people-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./artifacts/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet -// --chain=people-polkadot-dev -// --execution=wasm -// --wasm-execution=compiled -// --pallet=pallet_multisig // --extrinsic=* +// --chain=people-westend-dev +// --pallet=pallet_multisig +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-westend/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./cumulus/parachains/runtimes/people/people-polkadot/src/weights/pallet_multisig.rs +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -52,110 +55,113 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_337_000 picoseconds. - Weight::from_parts(11_960_522, 0) + // Minimum execution time: 15_664_000 picoseconds. + Weight::from_parts(16_483_544, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 9 - .saturating_add(Weight::from_parts(504, 0).saturating_mul(z.into())) + // Standard Error: 6 + .saturating_add(Weight::from_parts(527, 0).saturating_mul(z.into())) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `263 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 41_128_000 picoseconds. - Weight::from_parts(35_215_592, 0) + // Minimum execution time: 47_543_000 picoseconds. + Weight::from_parts(32_140_648, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 429 - .saturating_add(Weight::from_parts(65_959, 0).saturating_mul(s.into())) - // Standard Error: 4 - .saturating_add(Weight::from_parts(1_230, 0).saturating_mul(z.into())) + // Standard Error: 2_184 + .saturating_add(Weight::from_parts(163_779, 0).saturating_mul(s.into())) + // Standard Error: 21 + .saturating_add(Weight::from_parts(2_192, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[3, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 26_878_000 picoseconds. - Weight::from_parts(21_448_577, 0) + // Minimum execution time: 31_080_000 picoseconds. + Weight::from_parts(19_282_980, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 354 - .saturating_add(Weight::from_parts(60_286, 0).saturating_mul(s.into())) - // Standard Error: 3 - .saturating_add(Weight::from_parts(1_236, 0).saturating_mul(z.into())) + // Standard Error: 1_261 + .saturating_add(Weight::from_parts(134_865, 0).saturating_mul(s.into())) + // Standard Error: 12 + .saturating_add(Weight::from_parts(2_015, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `388 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 45_716_000 picoseconds. - Weight::from_parts(38_332_947, 0) + // Minimum execution time: 54_063_000 picoseconds. + Weight::from_parts(34_760_071, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 554 - .saturating_add(Weight::from_parts(81_026, 0).saturating_mul(s.into())) - // Standard Error: 5 - .saturating_add(Weight::from_parts(1_265, 0).saturating_mul(z.into())) + // Standard Error: 2_858 + .saturating_add(Weight::from_parts(242_502, 0).saturating_mul(s.into())) + // Standard Error: 28 + .saturating_add(Weight::from_parts(2_187, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `263 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 32_089_000 picoseconds. - Weight::from_parts(33_664_508, 0) + // Minimum execution time: 30_997_000 picoseconds. + Weight::from_parts(32_861_544, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 487 - .saturating_add(Weight::from_parts(67_443, 0).saturating_mul(s.into())) + // Standard Error: 1_172 + .saturating_add(Weight::from_parts(144_646, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 18_631_000 picoseconds. - Weight::from_parts(19_909_964, 0) + // Minimum execution time: 17_110_000 picoseconds. + Weight::from_parts(16_883_743, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 434 - .saturating_add(Weight::from_parts(62_989, 0).saturating_mul(s.into())) + // Standard Error: 1_170 + .saturating_add(Weight::from_parts(141_623, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `454 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 32_486_000 picoseconds. - Weight::from_parts(34_303_784, 0) + // Minimum execution time: 31_575_000 picoseconds. + Weight::from_parts(33_599_222, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 585 - .saturating_add(Weight::from_parts(69_979, 0).saturating_mul(s.into())) + // Standard Error: 1_343 + .saturating_add(Weight::from_parts(148_578, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/polkadot/runtime/rococo/src/weights/pallet_multisig.rs b/polkadot/runtime/rococo/src/weights/pallet_multisig.rs index f1b81759ece6c..d63c82daacdef 100644 --- a/polkadot/runtime/rococo/src/weights/pallet_multisig.rs +++ b/polkadot/runtime/rococo/src/weights/pallet_multisig.rs @@ -17,27 +17,27 @@ //! Autogenerated weights for `pallet_multisig` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-02-29, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot +// target/production/polkadot // benchmark // pallet +// --extrinsic=* // --chain=rococo-dev +// --pallet=pallet_multisig +// --header=/__w/polkadot-sdk/polkadot-sdk/polkadot/file_header.txt +// --output=./polkadot/runtime/rococo/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 +// --heap-pages=4096 // --no-storage-info -// --no-median-slopes // --no-min-squares -// --pallet=pallet_multisig -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --header=./polkadot/file_header.txt -// --output=./polkadot/runtime/rococo/src/weights/ +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,11 +55,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_023_000 picoseconds. - Weight::from_parts(12_643_116, 0) + // Minimum execution time: 15_707_000 picoseconds. + Weight::from_parts(17_199_004, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 3 - .saturating_add(Weight::from_parts(582, 0).saturating_mul(z.into())) + // Standard Error: 15 + .saturating_add(Weight::from_parts(639, 0).saturating_mul(z.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) @@ -69,13 +69,13 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `229 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 39_339_000 picoseconds. - Weight::from_parts(27_243_033, 0) + // Minimum execution time: 47_949_000 picoseconds. + Weight::from_parts(33_500_294, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_319 - .saturating_add(Weight::from_parts(142_212, 0).saturating_mul(s.into())) - // Standard Error: 12 - .saturating_add(Weight::from_parts(1_592, 0).saturating_mul(z.into())) + // Standard Error: 1_775 + .saturating_add(Weight::from_parts(159_011, 0).saturating_mul(s.into())) + // Standard Error: 17 + .saturating_add(Weight::from_parts(2_213, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -87,13 +87,13 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `248` // Estimated: `6811` - // Minimum execution time: 27_647_000 picoseconds. - Weight::from_parts(15_828_725, 0) + // Minimum execution time: 31_197_000 picoseconds. + Weight::from_parts(19_488_352, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 908 - .saturating_add(Weight::from_parts(130_880, 0).saturating_mul(s.into())) - // Standard Error: 8 - .saturating_add(Weight::from_parts(1_532, 0).saturating_mul(z.into())) + // Standard Error: 1_332 + .saturating_add(Weight::from_parts(138_347, 0).saturating_mul(s.into())) + // Standard Error: 13 + .saturating_add(Weight::from_parts(2_122, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -107,28 +107,29 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `354 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 46_971_000 picoseconds. - Weight::from_parts(32_150_393, 0) + // Minimum execution time: 54_297_000 picoseconds. + Weight::from_parts(33_256_178, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_129 - .saturating_add(Weight::from_parts(154_796, 0).saturating_mul(s.into())) - // Standard Error: 11 - .saturating_add(Weight::from_parts(1_603, 0).saturating_mul(z.into())) + // Standard Error: 3_088 + .saturating_add(Weight::from_parts(256_364, 0).saturating_mul(s.into())) + // Standard Error: 30 + .saturating_add(Weight::from_parts(2_488, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `229 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 24_947_000 picoseconds. - Weight::from_parts(26_497_183, 0) + // Minimum execution time: 31_246_000 picoseconds. + Weight::from_parts(32_245_711, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_615 - .saturating_add(Weight::from_parts(147_071, 0).saturating_mul(s.into())) + // Standard Error: 1_704 + .saturating_add(Weight::from_parts(156_235, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -139,11 +140,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `248` // Estimated: `6811` - // Minimum execution time: 13_897_000 picoseconds. - Weight::from_parts(14_828_339, 0) + // Minimum execution time: 17_353_000 picoseconds. + Weight::from_parts(17_418_506, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_136 - .saturating_add(Weight::from_parts(133_925, 0).saturating_mul(s.into())) + // Standard Error: 1_126 + .saturating_add(Weight::from_parts(136_788, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -154,11 +155,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `420 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 28_984_000 picoseconds. - Weight::from_parts(29_853_232, 0) + // Minimum execution time: 32_603_000 picoseconds. + Weight::from_parts(33_456_399, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 650 - .saturating_add(Weight::from_parts(113_440, 0).saturating_mul(s.into())) + // Standard Error: 1_239 + .saturating_add(Weight::from_parts(146_249, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/polkadot/runtime/westend/src/weights/pallet_multisig.rs b/polkadot/runtime/westend/src/weights/pallet_multisig.rs index 616aea9c8e73f..83521f3d1927b 100644 --- a/polkadot/runtime/westend/src/weights/pallet_multisig.rs +++ b/polkadot/runtime/westend/src/weights/pallet_multisig.rs @@ -16,28 +16,28 @@ //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-06-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner--ss9ysm1-project-163-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot +// target/production/polkadot // benchmark // pallet +// --extrinsic=* // --chain=westend-dev +// --pallet=pallet_multisig +// --header=/__w/polkadot-sdk/polkadot-sdk/polkadot/file_header.txt +// --output=./polkadot/runtime/westend/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 +// --heap-pages=4096 // --no-storage-info -// --no-median-slopes // --no-min-squares -// --pallet=pallet_multisig -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --header=./file_header.txt -// --output=./runtime/westend/src/weights/ +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,110 +55,111 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_218_000 picoseconds. - Weight::from_parts(14_749_472, 0) + // Minimum execution time: 15_705_000 picoseconds. + Weight::from_parts(16_890_096, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 10 - .saturating_add(Weight::from_parts(507, 0).saturating_mul(z.into())) + // Standard Error: 13 + .saturating_add(Weight::from_parts(549, 0).saturating_mul(z.into())) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `309 + s * (2 ±0)` + // Measured: `267 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 45_891_000 picoseconds. - Weight::from_parts(33_546_627, 0) + // Minimum execution time: 54_293_000 picoseconds. + Weight::from_parts(39_710_880, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 2_347 - .saturating_add(Weight::from_parts(136_466, 0).saturating_mul(s.into())) - // Standard Error: 23 - .saturating_add(Weight::from_parts(1_595, 0).saturating_mul(z.into())) + // Standard Error: 1_591 + .saturating_add(Weight::from_parts(164_846, 0).saturating_mul(s.into())) + // Standard Error: 15 + .saturating_add(Weight::from_parts(1_993, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[3, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `286` // Estimated: `6811` - // Minimum execution time: 30_355_000 picoseconds. - Weight::from_parts(19_611_682, 0) + // Minimum execution time: 36_477_000 picoseconds. + Weight::from_parts(22_595_904, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_383 - .saturating_add(Weight::from_parts(123_652, 0).saturating_mul(s.into())) - // Standard Error: 13 - .saturating_add(Weight::from_parts(1_488, 0).saturating_mul(z.into())) + // Standard Error: 1_526 + .saturating_add(Weight::from_parts(159_314, 0).saturating_mul(s.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(2_219, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `392 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 50_453_000 picoseconds. - Weight::from_parts(35_628_285, 0) + // Minimum execution time: 60_127_000 picoseconds. + Weight::from_parts(33_469_803, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 3_693 - .saturating_add(Weight::from_parts(203_453, 0).saturating_mul(s.into())) - // Standard Error: 36 - .saturating_add(Weight::from_parts(1_726, 0).saturating_mul(z.into())) + // Standard Error: 3_400 + .saturating_add(Weight::from_parts(309_634, 0).saturating_mul(s.into())) + // Standard Error: 33 + .saturating_add(Weight::from_parts(2_795, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `314 + s * (2 ±0)` + // Measured: `267 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 32_500_000 picoseconds. - Weight::from_parts(33_231_806, 0) + // Minimum execution time: 36_697_000 picoseconds. + Weight::from_parts(38_746_125, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_511 - .saturating_add(Weight::from_parts(134_500, 0).saturating_mul(s.into())) + // Standard Error: 2_073 + .saturating_add(Weight::from_parts(159_426, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `286` // Estimated: `6811` - // Minimum execution time: 17_906_000 picoseconds. - Weight::from_parts(18_757_928, 0) + // Minimum execution time: 21_909_000 picoseconds. + Weight::from_parts(22_227_385, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_172 - .saturating_add(Weight::from_parts(113_535, 0).saturating_mul(s.into())) + // Standard Error: 1_063 + .saturating_add(Weight::from_parts(146_021, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `458 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 33_018_000 picoseconds. - Weight::from_parts(34_186_533, 0) + // Minimum execution time: 36_637_000 picoseconds. + Weight::from_parts(36_457_379, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_188 - .saturating_add(Weight::from_parts(128_449, 0).saturating_mul(s.into())) + // Standard Error: 1_709 + .saturating_add(Weight::from_parts(171_090, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/substrate/.maintain/frame-weight-template.hbs b/substrate/.maintain/frame-weight-template.hbs index ec9eee205cee3..b174823b38403 100644 --- a/substrate/.maintain/frame-weight-template.hbs +++ b/substrate/.maintain/frame-weight-template.hbs @@ -17,8 +17,7 @@ #![allow(unused_imports)] #![allow(missing_docs)] -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use core::marker::PhantomData; +use frame::weights_prelude::*; /// Weight functions needed for `{{pallet}}`. pub trait WeightInfo { diff --git a/substrate/frame/multisig/src/benchmarking.rs b/substrate/frame/multisig/src/benchmarking.rs index ccaa1ceab66e5..3f75d92fe0ed3 100644 --- a/substrate/frame/multisig/src/benchmarking.rs +++ b/substrate/frame/multisig/src/benchmarking.rs @@ -194,14 +194,14 @@ mod benchmarks { Ok(()) } - /// `z`: Transaction Length, not a component /// `s`: Signatories, need at least 2 people #[benchmark] fn approve_as_multi_create( s: Linear<2, { T::MaxSignatories::get() }>, - z: Linear<0, 10_000>, ) -> Result<(), BenchmarkError> { - let (mut signatories, call) = setup_multi::(s, z)?; + // The call is neither in storage or an argument, so just use any: + let call_len = 10_000; + let (mut signatories, call) = setup_multi::(s, call_len)?; let multi_account_id = Multisig::::multi_account_id(&signatories, s.try_into().unwrap()); let caller = signatories.pop().ok_or("signatories should have len 2 or more")?; let call_hash = call.using_encoded(blake2_256); @@ -225,14 +225,14 @@ mod benchmarks { Ok(()) } - /// `z`: Transaction Length, not a component /// `s`: Signatories, need at least 2 people #[benchmark] fn approve_as_multi_approve( s: Linear<2, { T::MaxSignatories::get() }>, - z: Linear<0, 10_000>, ) -> Result<(), BenchmarkError> { - let (mut signatories, call) = setup_multi::(s, z)?; + // The call is neither in storage or an argument, so just use any: + let call_len = 10_000; + let (mut signatories, call) = setup_multi::(s, call_len)?; let mut signatories2 = signatories.clone(); let multi_account_id = Multisig::::multi_account_id(&signatories, s.try_into().unwrap()); let caller = signatories.pop().ok_or("signatories should have len 2 or more")?; @@ -270,14 +270,12 @@ mod benchmarks { Ok(()) } - /// `z`: Transaction Length, not a component /// `s`: Signatories, need at least 2 people #[benchmark] - fn cancel_as_multi( - s: Linear<2, { T::MaxSignatories::get() }>, - z: Linear<0, 10_000>, - ) -> Result<(), BenchmarkError> { - let (mut signatories, call) = setup_multi::(s, z)?; + fn cancel_as_multi(s: Linear<2, { T::MaxSignatories::get() }>) -> Result<(), BenchmarkError> { + // The call is neither in storage or an argument, so just use any: + let call_len = 10_000; + let (mut signatories, call) = setup_multi::(s, call_len)?; let multi_account_id = Multisig::::multi_account_id(&signatories, s.try_into().unwrap()); let caller = signatories.pop().ok_or("signatories should have len 2 or more")?; let call_hash = call.using_encoded(blake2_256); diff --git a/substrate/frame/multisig/src/weights.rs b/substrate/frame/multisig/src/weights.rs index 5c14922e0ef00..1c91734e6188c 100644 --- a/substrate/frame/multisig/src/weights.rs +++ b/substrate/frame/multisig/src/weights.rs @@ -18,36 +18,36 @@ //! Autogenerated weights for `pallet_multisig` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-04-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-anb7yjbi-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `25968fd2c26d`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/production/substrate-node +// target/production/substrate-node // benchmark // pallet +// --extrinsic=* // --chain=dev +// --pallet=pallet_multisig +// --header=/__w/polkadot-sdk/polkadot-sdk/substrate/HEADER-APACHE2 +// --output=/__w/polkadot-sdk/polkadot-sdk/substrate/frame/multisig/src/weights.rs +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --pallet=pallet_multisig +// --heap-pages=4096 +// --template=substrate/.maintain/frame-weight-template.hbs // --no-storage-info -// --no-median-slopes // --no-min-squares -// --extrinsic=* -// --wasm-execution=compiled -// --heap-pages=4096 -// --output=./substrate/frame/multisig/src/weights.rs -// --header=./substrate/HEADER-APACHE2 -// --template=./substrate/.maintain/frame-weight-template.hbs +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] #![allow(missing_docs)] -// TODO update this in frame-weight-template.hbs use frame::weights_prelude::*; + /// Weight functions needed for `pallet_multisig`. pub trait WeightInfo { fn as_multi_threshold_1(z: u32, ) -> Weight; @@ -71,10 +71,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3997` - // Minimum execution time: 20_302_000 picoseconds. - Weight::from_parts(21_362_808, 3997) - // Standard Error: 4 - .saturating_add(Weight::from_parts(432, 0).saturating_mul(z.into())) + // Minimum execution time: 28_800_000 picoseconds. + Weight::from_parts(30_130_161, 3997) + // Standard Error: 18 + .saturating_add(Weight::from_parts(551, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) @@ -83,14 +83,14 @@ impl WeightInfo for SubstrateWeight { /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `301 + s * (2 ±0)` + // Measured: `334 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 41_140_000 picoseconds. - Weight::from_parts(31_518_927, 6811) - // Standard Error: 754 - .saturating_add(Weight::from_parts(115_804, 0).saturating_mul(s.into())) - // Standard Error: 7 - .saturating_add(Weight::from_parts(1_442, 0).saturating_mul(z.into())) + // Minimum execution time: 51_467_000 picoseconds. + Weight::from_parts(38_610_296, 6811) + // Standard Error: 1_796 + .saturating_add(Weight::from_parts(161_251, 0).saturating_mul(s.into())) + // Standard Error: 17 + .saturating_add(Weight::from_parts(2_068, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -100,14 +100,14 @@ impl WeightInfo for SubstrateWeight { /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `320` + // Measured: `353` // Estimated: `6811` - // Minimum execution time: 27_375_000 picoseconds. - Weight::from_parts(17_806_361, 6811) - // Standard Error: 501 - .saturating_add(Weight::from_parts(107_042, 0).saturating_mul(s.into())) - // Standard Error: 4 - .saturating_add(Weight::from_parts(1_491, 0).saturating_mul(z.into())) + // Minimum execution time: 36_208_000 picoseconds. + Weight::from_parts(24_694_507, 6811) + // Standard Error: 1_430 + .saturating_add(Weight::from_parts(134_263, 0).saturating_mul(s.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(2_021, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -123,14 +123,14 @@ impl WeightInfo for SubstrateWeight { /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `571 + s * (33 ±0)` + // Measured: `604 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 54_427_000 picoseconds. - Weight::from_parts(43_677_970, 6811) - // Standard Error: 1_342 - .saturating_add(Weight::from_parts(154_697, 0).saturating_mul(s.into())) - // Standard Error: 13 - .saturating_add(Weight::from_parts(1_534, 0).saturating_mul(z.into())) + // Minimum execution time: 65_217_000 picoseconds. + Weight::from_parts(48_235_573, 6811) + // Standard Error: 2_841 + .saturating_add(Weight::from_parts(205_077, 0).saturating_mul(s.into())) + // Standard Error: 27 + .saturating_add(Weight::from_parts(2_298, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -139,12 +139,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `s` is `[2, 100]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `301 + s * (2 ±0)` + // Measured: `334 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 29_102_000 picoseconds. - Weight::from_parts(30_317_105, 6811) - // Standard Error: 903 - .saturating_add(Weight::from_parts(109_792, 0).saturating_mul(s.into())) + // Minimum execution time: 35_727_000 picoseconds. + Weight::from_parts(37_329_524, 6811) + // Standard Error: 1_814 + .saturating_add(Weight::from_parts(157_471, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -153,12 +153,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `s` is `[2, 100]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `320` + // Measured: `353` // Estimated: `6811` - // Minimum execution time: 16_300_000 picoseconds. - Weight::from_parts(17_358_877, 6811) - // Standard Error: 522 - .saturating_add(Weight::from_parts(99_194, 0).saturating_mul(s.into())) + // Minimum execution time: 21_623_000 picoseconds. + Weight::from_parts(22_601_251, 6811) + // Standard Error: 963 + .saturating_add(Weight::from_parts(139_320, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -167,12 +167,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `s` is `[2, 100]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `492 + s * (1 ±0)` + // Measured: `525 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 30_147_000 picoseconds. - Weight::from_parts(32_003_421, 6811) - // Standard Error: 1_077 - .saturating_add(Weight::from_parts(108_567, 0).saturating_mul(s.into())) + // Minimum execution time: 36_801_000 picoseconds. + Weight::from_parts(37_578_412, 6811) + // Standard Error: 1_580 + .saturating_add(Weight::from_parts(159_580, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -189,10 +189,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3997` - // Minimum execution time: 20_302_000 picoseconds. - Weight::from_parts(21_362_808, 3997) - // Standard Error: 4 - .saturating_add(Weight::from_parts(432, 0).saturating_mul(z.into())) + // Minimum execution time: 28_800_000 picoseconds. + Weight::from_parts(30_130_161, 3997) + // Standard Error: 18 + .saturating_add(Weight::from_parts(551, 0).saturating_mul(z.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) @@ -201,14 +201,14 @@ impl WeightInfo for () { /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `301 + s * (2 ±0)` + // Measured: `334 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 41_140_000 picoseconds. - Weight::from_parts(31_518_927, 6811) - // Standard Error: 754 - .saturating_add(Weight::from_parts(115_804, 0).saturating_mul(s.into())) - // Standard Error: 7 - .saturating_add(Weight::from_parts(1_442, 0).saturating_mul(z.into())) + // Minimum execution time: 51_467_000 picoseconds. + Weight::from_parts(38_610_296, 6811) + // Standard Error: 1_796 + .saturating_add(Weight::from_parts(161_251, 0).saturating_mul(s.into())) + // Standard Error: 17 + .saturating_add(Weight::from_parts(2_068, 0).saturating_mul(z.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -218,14 +218,14 @@ impl WeightInfo for () { /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `320` + // Measured: `353` // Estimated: `6811` - // Minimum execution time: 27_375_000 picoseconds. - Weight::from_parts(17_806_361, 6811) - // Standard Error: 501 - .saturating_add(Weight::from_parts(107_042, 0).saturating_mul(s.into())) - // Standard Error: 4 - .saturating_add(Weight::from_parts(1_491, 0).saturating_mul(z.into())) + // Minimum execution time: 36_208_000 picoseconds. + Weight::from_parts(24_694_507, 6811) + // Standard Error: 1_430 + .saturating_add(Weight::from_parts(134_263, 0).saturating_mul(s.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(2_021, 0).saturating_mul(z.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -241,14 +241,14 @@ impl WeightInfo for () { /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `571 + s * (33 ±0)` + // Measured: `604 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 54_427_000 picoseconds. - Weight::from_parts(43_677_970, 6811) - // Standard Error: 1_342 - .saturating_add(Weight::from_parts(154_697, 0).saturating_mul(s.into())) - // Standard Error: 13 - .saturating_add(Weight::from_parts(1_534, 0).saturating_mul(z.into())) + // Minimum execution time: 65_217_000 picoseconds. + Weight::from_parts(48_235_573, 6811) + // Standard Error: 2_841 + .saturating_add(Weight::from_parts(205_077, 0).saturating_mul(s.into())) + // Standard Error: 27 + .saturating_add(Weight::from_parts(2_298, 0).saturating_mul(z.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -257,12 +257,12 @@ impl WeightInfo for () { /// The range of component `s` is `[2, 100]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `301 + s * (2 ±0)` + // Measured: `334 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 29_102_000 picoseconds. - Weight::from_parts(30_317_105, 6811) - // Standard Error: 903 - .saturating_add(Weight::from_parts(109_792, 0).saturating_mul(s.into())) + // Minimum execution time: 35_727_000 picoseconds. + Weight::from_parts(37_329_524, 6811) + // Standard Error: 1_814 + .saturating_add(Weight::from_parts(157_471, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -271,12 +271,12 @@ impl WeightInfo for () { /// The range of component `s` is `[2, 100]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `320` + // Measured: `353` // Estimated: `6811` - // Minimum execution time: 16_300_000 picoseconds. - Weight::from_parts(17_358_877, 6811) - // Standard Error: 522 - .saturating_add(Weight::from_parts(99_194, 0).saturating_mul(s.into())) + // Minimum execution time: 21_623_000 picoseconds. + Weight::from_parts(22_601_251, 6811) + // Standard Error: 963 + .saturating_add(Weight::from_parts(139_320, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -285,13 +285,13 @@ impl WeightInfo for () { /// The range of component `s` is `[2, 100]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `492 + s * (1 ±0)` + // Measured: `525 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 30_147_000 picoseconds. - Weight::from_parts(32_003_421, 6811) - // Standard Error: 1_077 - .saturating_add(Weight::from_parts(108_567, 0).saturating_mul(s.into())) + // Minimum execution time: 36_801_000 picoseconds. + Weight::from_parts(37_578_412, 6811) + // Standard Error: 1_580 + .saturating_add(Weight::from_parts(159_580, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } -} \ No newline at end of file +} From 23600076de203dad498d815ff4b7ed2968217c10 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Fri, 24 Jan 2025 13:32:19 +0100 Subject: [PATCH 141/169] Nits for collectives-westend XCM benchmarks setup (#7215) Closes: https://github.com/paritytech/polkadot-sdk/issues/2904 --------- Co-authored-by: command-bot <> --- Cargo.lock | 1 + .../collectives-westend/Cargo.toml | 3 + .../collectives-westend/src/lib.rs | 122 +++++++- .../src/weights/pallet_xcm.rs | 235 +++++++--------- .../xcm/pallet_xcm_benchmarks_fungible.rs | 128 ++++----- .../xcm/pallet_xcm_benchmarks_generic.rs | 262 +++++++++--------- 6 files changed, 419 insertions(+), 332 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5cc898714d31e..df2c58b7f4c1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3707,6 +3707,7 @@ dependencies = [ "pallet-treasury 27.0.0", "pallet-utility 28.0.0", "pallet-xcm 7.0.0", + "pallet-xcm-benchmarks 7.0.0", "parachains-common 7.0.0", "parachains-runtimes-test-utils 7.0.0", "parity-scale-codec", diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml index 2786321e48e2e..f9cc54495aea0 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml @@ -65,6 +65,7 @@ sp-version = { workspace = true } # Polkadot pallet-xcm = { workspace = true } +pallet-xcm-benchmarks = { optional = true, workspace = true } polkadot-parachain-primitives = { workspace = true } polkadot-runtime-common = { workspace = true } westend-runtime-constants = { workspace = true } @@ -131,6 +132,7 @@ runtime-benchmarks = [ "pallet-transaction-payment/runtime-benchmarks", "pallet-treasury/runtime-benchmarks", "pallet-utility/runtime-benchmarks", + "pallet-xcm-benchmarks/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", "parachains-common/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", @@ -222,6 +224,7 @@ std = [ "pallet-transaction-payment/std", "pallet-treasury/std", "pallet-utility/std", + "pallet-xcm-benchmarks?/std", "pallet-xcm/std", "parachain-info/std", "parachains-common/std", diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs index 5eafc2960cc88..5e087832f0e82 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs @@ -801,7 +801,6 @@ mod benches { [cumulus_pallet_xcmp_queue, XcmpQueue] [pallet_alliance, Alliance] [pallet_collective, AllianceMotion] - [pallet_xcm, PalletXcmExtrinsicsBenchmark::] [pallet_preimage, Preimage] [pallet_scheduler, Scheduler] [pallet_referenda, FellowshipReferenda] @@ -816,6 +815,11 @@ mod benches { [pallet_treasury, FellowshipTreasury] [pallet_asset_rate, AssetRate] [cumulus_pallet_weight_reclaim, WeightReclaim] + // XCM + [pallet_xcm, PalletXcmExtrinsicsBenchmark::] + // NOTE: Make sure you point to the individual modules below. + [pallet_xcm_benchmarks::fungible, XcmBalances] + [pallet_xcm_benchmarks::generic, XcmGeneric] ); } @@ -1065,6 +1069,12 @@ impl_runtime_apis! { use cumulus_pallet_session_benchmarking::Pallet as SessionBench; use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; + // This is defined once again in dispatch_benchmark, because list_benchmarks! + // and add_benchmarks! are macros exported by define_benchmarks! macros and those types + // are referenced in that call. + type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::; + type XcmGeneric = pallet_xcm_benchmarks::generic::Pallet::; + let mut list = Vec::::new(); list_benchmarks!(list, extra); @@ -1093,10 +1103,11 @@ impl_runtime_apis! { use cumulus_pallet_session_benchmarking::Pallet as SessionBench; impl cumulus_pallet_session_benchmarking::Config for Runtime {} + use xcm_config::WndLocation; parameter_types! { pub ExistentialDepositAsset: Option = Some(( - xcm_config::WndLocation::get(), + WndLocation::get(), ExistentialDeposit::get() ).into()); } @@ -1149,6 +1160,112 @@ impl_runtime_apis! { } } + impl pallet_xcm_benchmarks::Config for Runtime { + type XcmConfig = xcm_config::XcmConfig; + type AccountIdConverter = xcm_config::LocationToAccountId; + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + fn valid_destination() -> Result { + Ok(WndLocation::get()) + } + fn worst_case_holding(_depositable_count: u32) -> Assets { + // just concrete assets according to relay chain. + let assets: Vec = vec![ + Asset { + id: AssetId(WndLocation::get()), + fun: Fungible(1_000_000 * UNITS), + } + ]; + assets.into() + } + } + + parameter_types! { + pub const TrustedTeleporter: Option<(Location, Asset)> = Some(( + WndLocation::get(), + Asset { fun: Fungible(UNITS), id: AssetId(WndLocation::get()) }, + )); + pub const CheckedAccount: Option<(AccountId, xcm_builder::MintLocation)> = None; + pub const TrustedReserve: Option<(Location, Asset)> = None; + } + + impl pallet_xcm_benchmarks::fungible::Config for Runtime { + type TransactAsset = Balances; + + type CheckedAccount = CheckedAccount; + type TrustedTeleporter = TrustedTeleporter; + type TrustedReserve = TrustedReserve; + + fn get_asset() -> Asset { + Asset { + id: AssetId(WndLocation::get()), + fun: Fungible(UNITS), + } + } + } + + impl pallet_xcm_benchmarks::generic::Config for Runtime { + type TransactAsset = Balances; + type RuntimeCall = RuntimeCall; + + fn worst_case_response() -> (u64, Response) { + (0u64, Response::Version(Default::default())) + } + + fn worst_case_asset_exchange() -> Result<(Assets, Assets), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn universal_alias() -> Result<(Location, Junction), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn transact_origin_and_runtime_call() -> Result<(Location, RuntimeCall), BenchmarkError> { + Ok((WndLocation::get(), frame_system::Call::remark_with_event { remark: vec![] }.into())) + } + + fn subscribe_origin() -> Result { + Ok(WndLocation::get()) + } + + fn claimable_asset() -> Result<(Location, Location, Assets), BenchmarkError> { + let origin = WndLocation::get(); + let assets: Assets = (AssetId(WndLocation::get()), 1_000 * UNITS).into(); + let ticket = Location { parents: 0, interior: Here }; + Ok((origin, ticket, assets)) + } + + fn fee_asset() -> Result { + Ok(Asset { + id: AssetId(WndLocation::get()), + fun: Fungible(1_000_000 * UNITS), + }) + } + + fn unlockable_asset() -> Result<(Location, Location, Asset), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn export_message_origin_and_destination( + ) -> Result<(Location, NetworkId, InteriorLocation), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn alias_origin() -> Result<(Location, Location), BenchmarkError> { + // Any location can alias to an internal location. + // Here parachain 1000 aliases to an internal account. + let origin = Location::new(1, [Parachain(1000)]); + let target = Location::new(1, [Parachain(1000), AccountId32 { id: [128u8; 32], network: None }]); + Ok((origin, target)) + } + } + + type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::; + type XcmGeneric = pallet_xcm_benchmarks::generic::Pallet::; + use frame_support::traits::WhitelistedStorageKeys; let whitelist: Vec = AllPalletsWithSystem::whitelisted_storage_keys(); @@ -1156,7 +1273,6 @@ impl_runtime_apis! { let params = (&config, &whitelist); add_benchmarks!(params, batches); - if batches.is_empty() { return Err("Benchmark not found for this pallet.".into()) } Ok(batches) } } diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs index ccf88873c2cd1..c0389cbcdb42c 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-12-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-01-17, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `47a5bbdc8de3`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `17a605d70d1a`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-westend-dev")`, DB CACHE: 1024 // Executed Command: @@ -56,23 +56,19 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn send() -> Weight { // Proof Size summary in bytes: - // Measured: `214` - // Estimated: `3679` - // Minimum execution time: 32_779_000 picoseconds. - Weight::from_parts(33_417_000, 0) - .saturating_add(Weight::from_parts(0, 3679)) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `111` + // Estimated: `3576` + // Minimum execution time: 26_877_000 picoseconds. + Weight::from_parts(27_778_000, 0) + .saturating_add(Weight::from_parts(0, 3576)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -82,10 +78,6 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) @@ -94,13 +86,13 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn teleport_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `214` - // Estimated: `3679` - // Minimum execution time: 116_031_000 picoseconds. - Weight::from_parts(118_863_000, 0) - .saturating_add(Weight::from_parts(0, 3679)) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(3)) + // Measured: `111` + // Estimated: `3593` + // Minimum execution time: 109_606_000 picoseconds. + Weight::from_parts(120_756_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Benchmark::Override` (r:0 w:0) /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -120,10 +112,6 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) @@ -132,23 +120,23 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn transfer_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `214` - // Estimated: `3679` - // Minimum execution time: 116_267_000 picoseconds. - Weight::from_parts(119_519_000, 0) - .saturating_add(Weight::from_parts(0, 3679)) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(3)) + // Measured: `111` + // Estimated: `3593` + // Minimum execution time: 109_165_000 picoseconds. + Weight::from_parts(110_899_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn execute() -> Weight { // Proof Size summary in bytes: - // Measured: `103` - // Estimated: `1588` - // Minimum execution time: 12_718_000 picoseconds. - Weight::from_parts(13_572_000, 0) - .saturating_add(Weight::from_parts(0, 1588)) + // Measured: `0` + // Estimated: `1485` + // Minimum execution time: 9_494_000 picoseconds. + Weight::from_parts(9_917_000, 0) + .saturating_add(Weight::from_parts(0, 1485)) .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) @@ -157,21 +145,18 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_568_000 picoseconds. - Weight::from_parts(7_913_000, 0) + // Minimum execution time: 7_515_000 picoseconds. + Weight::from_parts(7_771_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:0 w:1) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn force_default_xcm_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_225_000 picoseconds. - Weight::from_parts(2_473_000, 0) + // Minimum execution time: 2_430_000 picoseconds. + Weight::from_parts(2_536_000, 0) .saturating_add(Weight::from_parts(0, 0)) - .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `PolkadotXcm::VersionNotifiers` (r:1 w:1) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -181,10 +166,6 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) @@ -193,13 +174,13 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_subscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `145` - // Estimated: `3610` - // Minimum execution time: 35_869_000 picoseconds. - Weight::from_parts(37_848_000, 0) - .saturating_add(Weight::from_parts(0, 3610)) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(5)) + // Measured: `42` + // Estimated: `3507` + // Minimum execution time: 28_913_000 picoseconds. + Weight::from_parts(29_949_000, 0) + .saturating_add(Weight::from_parts(0, 3507)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::VersionNotifiers` (r:1 w:1) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -207,10 +188,6 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) @@ -219,13 +196,13 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_unsubscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `363` - // Estimated: `3828` - // Minimum execution time: 38_649_000 picoseconds. - Weight::from_parts(39_842_000, 0) - .saturating_add(Weight::from_parts(0, 3828)) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `136` + // Estimated: `3601` + // Minimum execution time: 30_496_000 picoseconds. + Weight::from_parts(31_828_000, 0) + .saturating_add(Weight::from_parts(0, 3601)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `PolkadotXcm::XcmExecutionSuspended` (r:0 w:1) /// Proof: `PolkadotXcm::XcmExecutionSuspended` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -233,8 +210,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_223_000 picoseconds. - Weight::from_parts(2_483_000, 0) + // Minimum execution time: 2_435_000 picoseconds. + Weight::from_parts(2_635_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -242,11 +219,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: - // Measured: `159` - // Estimated: `15999` - // Minimum execution time: 24_164_000 picoseconds. - Weight::from_parts(24_972_000, 0) - .saturating_add(Weight::from_parts(0, 15999)) + // Measured: `22` + // Estimated: `15862` + // Minimum execution time: 21_713_000 picoseconds. + Weight::from_parts(22_209_000, 0) + .saturating_add(Weight::from_parts(0, 15862)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -254,11 +231,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: - // Measured: `163` - // Estimated: `16003` - // Minimum execution time: 24_604_000 picoseconds. - Weight::from_parts(25_047_000, 0) - .saturating_add(Weight::from_parts(0, 16003)) + // Measured: `26` + // Estimated: `15866` + // Minimum execution time: 22_035_000 picoseconds. + Weight::from_parts(22_675_000, 0) + .saturating_add(Weight::from_parts(0, 15866)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -266,11 +243,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: - // Measured: `173` - // Estimated: `18488` - // Minimum execution time: 28_088_000 picoseconds. - Weight::from_parts(28_431_000, 0) - .saturating_add(Weight::from_parts(0, 18488)) + // Measured: `36` + // Estimated: `18351` + // Minimum execution time: 24_882_000 picoseconds. + Weight::from_parts(25_172_000, 0) + .saturating_add(Weight::from_parts(0, 18351)) .saturating_add(T::DbWeight::get().reads(7)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) @@ -279,44 +256,40 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn notify_current_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `212` - // Estimated: `6152` - // Minimum execution time: 33_814_000 picoseconds. - Weight::from_parts(34_741_000, 0) - .saturating_add(Weight::from_parts(0, 6152)) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(3)) + // Measured: `75` + // Estimated: `6015` + // Minimum execution time: 28_244_000 picoseconds. + Weight::from_parts(28_873_000, 0) + .saturating_add(Weight::from_parts(0, 6015)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `176` - // Estimated: `13541` - // Minimum execution time: 18_242_000 picoseconds. - Weight::from_parts(18_636_000, 0) - .saturating_add(Weight::from_parts(0, 13541)) + // Measured: `39` + // Estimated: `13404` + // Minimum execution time: 17_457_000 picoseconds. + Weight::from_parts(18_023_000, 0) + .saturating_add(Weight::from_parts(0, 13404)) .saturating_add(T::DbWeight::get().reads(5)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `170` - // Estimated: `16010` - // Minimum execution time: 24_249_000 picoseconds. - Weight::from_parts(24_768_000, 0) - .saturating_add(Weight::from_parts(0, 16010)) + // Measured: `33` + // Estimated: `15873` + // Minimum execution time: 22_283_000 picoseconds. + Weight::from_parts(22_783_000, 0) + .saturating_add(Weight::from_parts(0, 15873)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -326,23 +299,19 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `212` - // Estimated: `16052` - // Minimum execution time: 47_602_000 picoseconds. - Weight::from_parts(48_378_000, 0) - .saturating_add(Weight::from_parts(0, 16052)) - .saturating_add(T::DbWeight::get().reads(12)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `75` + // Estimated: `15915` + // Minimum execution time: 41_244_000 picoseconds. + Weight::from_parts(42_264_000, 0) + .saturating_add(Weight::from_parts(0, 15915)) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) /// Proof: `PolkadotXcm::QueryCounter` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -350,11 +319,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn new_query() -> Weight { // Proof Size summary in bytes: - // Measured: `103` - // Estimated: `1588` - // Minimum execution time: 5_566_000 picoseconds. - Weight::from_parts(5_768_000, 0) - .saturating_add(Weight::from_parts(0, 1588)) + // Measured: `0` + // Estimated: `1485` + // Minimum execution time: 2_678_000 picoseconds. + Weight::from_parts(2_892_000, 0) + .saturating_add(Weight::from_parts(0, 1485)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -362,11 +331,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn take_response() -> Weight { // Proof Size summary in bytes: - // Measured: `7740` - // Estimated: `11205` - // Minimum execution time: 30_821_000 picoseconds. - Weight::from_parts(31_250_000, 0) - .saturating_add(Weight::from_parts(0, 11205)) + // Measured: `7576` + // Estimated: `11041` + // Minimum execution time: 26_677_000 picoseconds. + Weight::from_parts(27_470_000, 0) + .saturating_add(Weight::from_parts(0, 11041)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -376,11 +345,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) fn claim_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `160` - // Estimated: `3625` - // Minimum execution time: 43_463_000 picoseconds. - Weight::from_parts(44_960_000, 0) - .saturating_add(Weight::from_parts(0, 3625)) + // Measured: `23` + // Estimated: `3488` + // Minimum execution time: 40_143_000 picoseconds. + Weight::from_parts(41_712_000, 0) + .saturating_add(Weight::from_parts(0, 3488)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs index 00826cbb8d79e..f6a140f3157fc 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs @@ -17,26 +17,28 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::fungible` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-10-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-01-17, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-augrssgt-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `17a605d70d1a`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: Compiled, CHAIN: Some("collectives-westend-dev"), DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=collectives-westend-dev +// --pallet=pallet_xcm_benchmarks::fungible +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm_benchmarks::fungible -// --chain=collectives-westend-dev -// --header=./cumulus/file_header.txt -// --template=./cumulus/templates/xcm-bench-template.hbs -// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/ +// --template=cumulus/templates/xcm-bench-template.hbs +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -54,8 +56,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `3593` - // Minimum execution time: 30_401_000 picoseconds. - Weight::from_parts(30_813_000, 3593) + // Minimum execution time: 32_692_000 picoseconds. + Weight::from_parts(33_469_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -63,33 +65,31 @@ impl WeightInfo { // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) pub fn transfer_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `153` + // Measured: `101` // Estimated: `6196` - // Minimum execution time: 43_150_000 picoseconds. - Weight::from_parts(43_919_000, 6196) + // Minimum execution time: 42_464_000 picoseconds. + Weight::from_parts(43_897_000, 6196) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - // Storage: `System::Account` (r:2 w:2) + // Storage: `System::Account` (r:3 w:3) // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn transfer_reserve_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `223` - // Estimated: `6196` - // Minimum execution time: 67_808_000 picoseconds. - Weight::from_parts(69_114_000, 6196) + // Measured: `212` + // Estimated: `8799` + // Minimum execution time: 105_472_000 picoseconds. + Weight::from_parts(115_465_000, 8799) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -104,51 +104,49 @@ impl WeightInfo { } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn initiate_reserve_withdraw() -> Weight { // Proof Size summary in bytes: - // Measured: `70` - // Estimated: `3535` - // Minimum execution time: 29_312_000 picoseconds. - Weight::from_parts(30_347_000, 3535) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `212` + // Estimated: `6196` + // Minimum execution time: 72_377_000 picoseconds. + Weight::from_parts(76_456_000, 6196) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(3)) } pub fn receive_teleported_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_283_000 picoseconds. - Weight::from_parts(2_448_000, 0) + // Minimum execution time: 2_556_000 picoseconds. + Weight::from_parts(2_960_000, 0) } // Storage: `System::Account` (r:1 w:1) // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) pub fn deposit_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `52` + // Measured: `0` // Estimated: `3593` - // Minimum execution time: 23_556_000 picoseconds. - Weight::from_parts(24_419_000, 3593) + // Minimum execution time: 24_560_000 picoseconds. + Weight::from_parts(24_926_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `System::Account` (r:1 w:1) // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) @@ -157,54 +155,50 @@ impl WeightInfo { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn deposit_reserve_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `122` + // Measured: `111` // Estimated: `3593` - // Minimum execution time: 58_342_000 picoseconds. - Weight::from_parts(59_598_000, 3593) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().writes(3)) + // Minimum execution time: 57_780_000 picoseconds. + Weight::from_parts(59_561_000, 3593) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn initiate_teleport() -> Weight { // Proof Size summary in bytes: - // Measured: `70` - // Estimated: `3535` - // Minimum execution time: 28_285_000 picoseconds. - Weight::from_parts(29_016_000, 3535) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `111` + // Estimated: `3576` + // Minimum execution time: 37_041_000 picoseconds. + Weight::from_parts(38_101_000, 3576) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(1)) } - // Storage: `System::Account` (r:1 w:1) + // Storage: `System::Account` (r:2 w:2) // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn initiate_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `122` - // Estimated: `3593` - // Minimum execution time: 65_211_000 picoseconds. - Weight::from_parts(67_200_000, 3593) + // Measured: `111` + // Estimated: `6196` + // Minimum execution time: 87_635_000 picoseconds. + Weight::from_parts(89_712_000, 6196) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index ae94edc3d7315..8e732546437a9 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -17,26 +17,28 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::generic` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-08-27, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-svzsllib-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `96ae15bb1012`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: Compiled, CHAIN: Some("collectives-westend-dev"), DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=collectives-westend-dev +// --pallet=pallet_xcm_benchmarks::generic +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm_benchmarks::generic -// --chain=collectives-westend-dev -// --header=./cumulus/file_header.txt -// --template=./cumulus/templates/xcm-bench-template.hbs -// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/ +// --template=cumulus/templates/xcm-bench-template.hbs +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -50,127 +52,145 @@ pub struct WeightInfo(PhantomData); impl WeightInfo { // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn report_holding() -> Weight { // Proof Size summary in bytes: - // Measured: `70` - // Estimated: `3535` - // Minimum execution time: 29_015_000 picoseconds. - Weight::from_parts(30_359_000, 3535) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `212` + // Estimated: `6196` + // Minimum execution time: 72_839_000 picoseconds. + Weight::from_parts(74_957_000, 6196) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(3)) } pub fn buy_execution() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 572_000 picoseconds. - Weight::from_parts(637_000, 0) + // Minimum execution time: 592_000 picoseconds. + Weight::from_parts(646_000, 0) } + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) pub fn pay_fees() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3593` + // Minimum execution time: 3_630_000 picoseconds. + Weight::from_parts(3_843_000, 3593) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + pub fn asset_claimer() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_550_000 picoseconds. - Weight::from_parts(1_604_000, 0) + // Minimum execution time: 660_000 picoseconds. + Weight::from_parts(712_000, 0) } // Storage: `PolkadotXcm::Queries` (r:1 w:0) // Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) pub fn query_response() -> Weight { // Proof Size summary in bytes: - // Measured: `32` - // Estimated: `3497` - // Minimum execution time: 7_354_000 picoseconds. - Weight::from_parts(7_808_000, 3497) + // Measured: `0` + // Estimated: `3465` + // Minimum execution time: 5_996_000 picoseconds. + Weight::from_parts(6_277_000, 3465) .saturating_add(T::DbWeight::get().reads(1)) } pub fn transact() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_716_000 picoseconds. - Weight::from_parts(7_067_000, 0) + // Minimum execution time: 7_427_000 picoseconds. + Weight::from_parts(7_817_000, 0) } pub fn refund_surplus() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_280_000 picoseconds. - Weight::from_parts(1_355_000, 0) + // Minimum execution time: 1_245_000 picoseconds. + Weight::from_parts(1_373_000, 0) } pub fn set_error_handler() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 587_000 picoseconds. - Weight::from_parts(645_000, 0) + // Minimum execution time: 589_000 picoseconds. + Weight::from_parts(647_000, 0) } pub fn set_appendix() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 629_000 picoseconds. - Weight::from_parts(662_000, 0) + // Minimum execution time: 593_000 picoseconds. + Weight::from_parts(653_000, 0) } pub fn clear_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 590_000 picoseconds. - Weight::from_parts(639_000, 0) + // Minimum execution time: 599_000 picoseconds. + Weight::from_parts(652_000, 0) } pub fn descend_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 651_000 picoseconds. - Weight::from_parts(688_000, 0) + // Minimum execution time: 620_000 picoseconds. + Weight::from_parts(670_000, 0) + } + pub fn execute_with_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 682_000 picoseconds. + Weight::from_parts(747_000, 0) } pub fn clear_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 601_000 picoseconds. - Weight::from_parts(630_000, 0) + // Minimum execution time: 596_000 picoseconds. + Weight::from_parts(650_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn report_error() -> Weight { // Proof Size summary in bytes: - // Measured: `70` - // Estimated: `3535` - // Minimum execution time: 25_650_000 picoseconds. - Weight::from_parts(26_440_000, 3535) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `212` + // Estimated: `6196` + // Minimum execution time: 68_183_000 picoseconds. + Weight::from_parts(70_042_000, 6196) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(3)) } // Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) // Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) pub fn claim_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `90` - // Estimated: `3555` - // Minimum execution time: 10_492_000 picoseconds. - Weight::from_parts(10_875_000, 3555) + // Measured: `23` + // Estimated: `3488` + // Minimum execution time: 9_661_000 picoseconds. + Weight::from_parts(9_943_000, 3488) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -178,29 +198,27 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 597_000 picoseconds. - Weight::from_parts(647_000, 0) + // Minimum execution time: 580_000 picoseconds. + Weight::from_parts(652_000, 0) } // Storage: `PolkadotXcm::VersionNotifyTargets` (r:1 w:1) // Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn subscribe_version() -> Weight { // Proof Size summary in bytes: - // Measured: `38` - // Estimated: `3503` - // Minimum execution time: 23_732_000 picoseconds. - Weight::from_parts(24_290_000, 3503) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) + // Measured: `42` + // Estimated: `3507` + // Minimum execution time: 24_197_000 picoseconds. + Weight::from_parts(25_199_000, 3507) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(2)) } // Storage: `PolkadotXcm::VersionNotifyTargets` (r:0 w:1) // Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -208,148 +226,134 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_446_000 picoseconds. - Weight::from_parts(2_613_000, 0) + // Minimum execution time: 2_720_000 picoseconds. + Weight::from_parts(2_881_000, 0) .saturating_add(T::DbWeight::get().writes(1)) } pub fn burn_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 960_000 picoseconds. - Weight::from_parts(1_045_000, 0) + // Minimum execution time: 950_000 picoseconds. + Weight::from_parts(1_076_000, 0) } pub fn expect_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 703_000 picoseconds. - Weight::from_parts(739_000, 0) + // Minimum execution time: 742_000 picoseconds. + Weight::from_parts(785_000, 0) } pub fn expect_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 616_000 picoseconds. - Weight::from_parts(651_000, 0) + // Minimum execution time: 598_000 picoseconds. + Weight::from_parts(671_000, 0) } pub fn expect_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 621_000 picoseconds. - Weight::from_parts(660_000, 0) + // Minimum execution time: 571_000 picoseconds. + Weight::from_parts(635_000, 0) } pub fn expect_transact_status() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 794_000 picoseconds. - Weight::from_parts(831_000, 0) + // Minimum execution time: 766_000 picoseconds. + Weight::from_parts(835_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn query_pallet() -> Weight { // Proof Size summary in bytes: - // Measured: `70` - // Estimated: `3535` - // Minimum execution time: 29_527_000 picoseconds. - Weight::from_parts(30_614_000, 3535) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `212` + // Estimated: `6196` + // Minimum execution time: 76_301_000 picoseconds. + Weight::from_parts(79_269_000, 6196) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(3)) } pub fn expect_pallet() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_189_000 picoseconds. - Weight::from_parts(3_296_000, 0) + // Minimum execution time: 5_452_000 picoseconds. + Weight::from_parts(5_721_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn report_transact_status() -> Weight { // Proof Size summary in bytes: - // Measured: `70` - // Estimated: `3535` - // Minimum execution time: 25_965_000 picoseconds. - Weight::from_parts(26_468_000, 3535) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `212` + // Estimated: `6196` + // Minimum execution time: 68_763_000 picoseconds. + Weight::from_parts(71_142_000, 6196) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(3)) } pub fn clear_transact_status() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 618_000 picoseconds. - Weight::from_parts(659_000, 0) + // Minimum execution time: 630_000 picoseconds. + Weight::from_parts(676_000, 0) } pub fn set_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 593_000 picoseconds. - Weight::from_parts(618_000, 0) + // Minimum execution time: 570_000 picoseconds. + Weight::from_parts(622_000, 0) } pub fn clear_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 603_000 picoseconds. - Weight::from_parts(634_000, 0) - } - pub fn alias_origin() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 2_000_000 picoseconds. - Weight::from_parts(2_000_000, 0) + // Minimum execution time: 549_000 picoseconds. + Weight::from_parts(603_000, 0) } pub fn set_fees_mode() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 568_000 picoseconds. - Weight::from_parts(629_000, 0) + // Minimum execution time: 578_000 picoseconds. + Weight::from_parts(626_000, 0) } pub fn unpaid_execution() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 598_000 picoseconds. - Weight::from_parts(655_000, 0) - } - pub fn asset_claimer() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 707_000 picoseconds. - Weight::from_parts(749_000, 0) + // Minimum execution time: 594_000 picoseconds. + Weight::from_parts(639_000, 0) } - pub fn execute_with_origin() -> Weight { + pub fn alias_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 713_000 picoseconds. - Weight::from_parts(776_000, 0) + // Minimum execution time: 637_000 picoseconds. + Weight::from_parts(676_000, 0) } } From a2c63e8d8a512eca28ed24c3c58ea7609c28b9ee Mon Sep 17 00:00:00 2001 From: Iulian Barbu <14218860+iulianbarbu@users.noreply.github.com> Date: Fri, 24 Jan 2025 15:29:25 +0200 Subject: [PATCH 142/169] fix(cmd bench-omni): build omni-bencher with production profile (#7299) # Description This PR builds frame-omni-bencher with `production` profile when calling `/cmd bench-omni` to compute benchmarks for pallets. Fix proposed by @bkchr , thanks! Closes #6797. ## Integration N/A ## Review Notes More info on #6797, and related to how the fix was tested: https://github.com/paritytech/polkadot-sdk/issues/6797#issuecomment-2611903102. --------- Signed-off-by: Iulian Barbu Co-authored-by: command-bot <> --- .github/workflows/cmd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmd.yml b/.github/workflows/cmd.yml index 3d4779064a44f..247fc34f1b18d 100644 --- a/.github/workflows/cmd.yml +++ b/.github/workflows/cmd.yml @@ -348,7 +348,7 @@ jobs: - name: Install dependencies for bench if: startsWith(needs.get-pr-info.outputs.CMD, 'bench') run: | - cargo install --path substrate/utils/frame/omni-bencher --locked + cargo install --path substrate/utils/frame/omni-bencher --locked --profile production - name: Run cmd id: cmd From 7710483541ce273df892c77a6e300aaa2efa1dca Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Fri, 24 Jan 2025 16:05:36 +0100 Subject: [PATCH 143/169] Bridges: emulated tests small nits/improvements (#7322) This PR includes minor fixes identified during work on the larger PR: [https://github.com/paritytech/polkadot-sdk/issues/6906](https://github.com/paritytech/polkadot-sdk/issues/6906). Specifically, this PR removes the use of `open_bridge_between_asset_hub_rococo_and_asset_hub_westend`, which is no longer relevant for BridgeHubs, as bridges are now created with genesis settings. This function was used in the generic `test_dry_run_transfer_across_pk_bridge` macro, which could cause compilation issues when used in other contexts (e.g. fellows repo). --------- Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../emulated/common/src/macros.rs | 3 +- .../src/tests/asset_transfers.rs | 3 -- .../bridge-hub-rococo/src/tests/mod.rs | 43 ------------------- .../bridge-hub-rococo/src/tests/send_xcm.rs | 3 -- .../src/tests/asset_transfers.rs | 3 -- .../bridge-hub-westend/src/tests/mod.rs | 43 ------------------- .../bridge-hub-westend/src/tests/send_xcm.rs | 3 -- prdoc/pr_7322.prdoc | 8 ++++ 8 files changed, 9 insertions(+), 100 deletions(-) create mode 100644 prdoc/pr_7322.prdoc diff --git a/cumulus/parachains/integration-tests/emulated/common/src/macros.rs b/cumulus/parachains/integration-tests/emulated/common/src/macros.rs index cd2b41e5198f8..983ac626177ee 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/macros.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/macros.rs @@ -644,9 +644,8 @@ macro_rules! test_dry_run_transfer_across_pk_bridge { let transfer_amount = 10_000_000_000_000u128; let initial_balance = transfer_amount * 10; - // Bridge setup. + // AssetHub setup. $sender_asset_hub::force_xcm_version($destination, XCM_VERSION); - open_bridge_between_asset_hub_rococo_and_asset_hub_westend(); <$sender_asset_hub as TestExt>::execute_with(|| { type Runtime = <$sender_asset_hub as Chain>::Runtime; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs index a2a61660afff0..d1fe94962f184 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs @@ -25,9 +25,6 @@ fn send_assets_over_bridge(send_fn: F) { AssetHubRococo::force_xcm_version(asset_hub_westend_location(), XCM_VERSION); BridgeHubRococo::force_xcm_version(bridge_hub_westend_location(), XCM_VERSION); - // open bridge - open_bridge_between_asset_hub_rococo_and_asset_hub_westend(); - // send message over bridge send_fn(); diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs index 8aff877559616..265002897ac5f 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs @@ -51,9 +51,6 @@ pub(crate) fn bridged_roc_at_ah_westend() -> Location { } // WND and wWND -pub(crate) fn wnd_at_ah_westend() -> Location { - Parent.into() -} pub(crate) fn bridged_wnd_at_ah_rococo() -> Location { Location::new(2, [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH))]) } @@ -240,43 +237,3 @@ pub(crate) fn assert_bridge_hub_westend_message_received() { ); }) } - -pub(crate) fn open_bridge_between_asset_hub_rococo_and_asset_hub_westend() { - use testnet_parachains_constants::{ - rococo::currency::UNITS as ROC, westend::currency::UNITS as WND, - }; - - // open AHR -> AHW - BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id(), ROC * 5); - AssetHubRococo::open_bridge( - AssetHubRococo::sibling_location_of(BridgeHubRococo::para_id()), - [ - GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), - Parachain(AssetHubWestend::para_id().into()), - ] - .into(), - Some(( - (roc_at_ah_rococo(), ROC * 1).into(), - BridgeHubRococo::sovereign_account_id_of(BridgeHubRococo::sibling_location_of( - AssetHubRococo::para_id(), - )), - )), - ); - - // open AHW -> AHR - BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id(), WND * 5); - AssetHubWestend::open_bridge( - AssetHubWestend::sibling_location_of(BridgeHubWestend::para_id()), - [ - GlobalConsensus(ByGenesis(ROCOCO_GENESIS_HASH)), - Parachain(AssetHubRococo::para_id().into()), - ] - .into(), - Some(( - (wnd_at_ah_westend(), WND * 1).into(), - BridgeHubWestend::sovereign_account_id_of(BridgeHubWestend::sibling_location_of( - AssetHubWestend::para_id(), - )), - )), - ); -} diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs index cfcb581238e6d..799af03786975 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs @@ -74,9 +74,6 @@ fn send_xcm_through_opened_lane_with_different_xcm_version_on_hops_works() { // fund sender AssetHubRococo::fund_accounts(vec![(AssetHubRococoSender::get().into(), amount * 10)]); - // open bridge - open_bridge_between_asset_hub_rococo_and_asset_hub_westend(); - // Initially set only default version on all runtimes let newer_xcm_version = xcm::prelude::XCM_VERSION; let older_xcm_version = newer_xcm_version - 1; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs index cc90c10b54bcf..a73c1280b406a 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs @@ -26,9 +26,6 @@ fn send_assets_over_bridge(send_fn: F) { AssetHubWestend::force_xcm_version(asset_hub_rococo_location(), XCM_VERSION); BridgeHubWestend::force_xcm_version(bridge_hub_rococo_location(), XCM_VERSION); - // open bridge - open_bridge_between_asset_hub_rococo_and_asset_hub_westend(); - // send message over bridge send_fn(); diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs index 6c1cdb98e8b2a..676b2862e6678 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs @@ -52,9 +52,6 @@ pub(crate) fn bridged_wnd_at_ah_rococo() -> Location { } // ROC and wROC -pub(crate) fn roc_at_ah_rococo() -> Location { - Parent.into() -} pub(crate) fn bridged_roc_at_ah_westend() -> Location { Location::new(2, [GlobalConsensus(ByGenesis(ROCOCO_GENESIS_HASH))]) } @@ -250,43 +247,3 @@ pub(crate) fn assert_bridge_hub_rococo_message_received() { ); }) } - -pub(crate) fn open_bridge_between_asset_hub_rococo_and_asset_hub_westend() { - use testnet_parachains_constants::{ - rococo::currency::UNITS as ROC, westend::currency::UNITS as WND, - }; - - // open AHR -> AHW - BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id(), ROC * 5); - AssetHubRococo::open_bridge( - AssetHubRococo::sibling_location_of(BridgeHubRococo::para_id()), - [ - GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), - Parachain(AssetHubWestend::para_id().into()), - ] - .into(), - Some(( - (roc_at_ah_rococo(), ROC * 1).into(), - BridgeHubRococo::sovereign_account_id_of(BridgeHubRococo::sibling_location_of( - AssetHubRococo::para_id(), - )), - )), - ); - - // open AHW -> AHR - BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id(), WND * 5); - AssetHubWestend::open_bridge( - AssetHubWestend::sibling_location_of(BridgeHubWestend::para_id()), - [ - GlobalConsensus(ByGenesis(ROCOCO_GENESIS_HASH)), - Parachain(AssetHubRococo::para_id().into()), - ] - .into(), - Some(( - (wnd_at_ah_westend(), WND * 1).into(), - BridgeHubWestend::sovereign_account_id_of(BridgeHubWestend::sibling_location_of( - AssetHubWestend::para_id(), - )), - )), - ); -} diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs index 60f8af2242f96..e655f06a0f01c 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs @@ -74,9 +74,6 @@ fn send_xcm_through_opened_lane_with_different_xcm_version_on_hops_works() { // fund sender AssetHubWestend::fund_accounts(vec![(AssetHubWestendSender::get().into(), amount * 10)]); - // open bridge - open_bridge_between_asset_hub_rococo_and_asset_hub_westend(); - // Initially set only default version on all runtimes let newer_xcm_version = xcm::prelude::XCM_VERSION; let older_xcm_version = newer_xcm_version - 1; diff --git a/prdoc/pr_7322.prdoc b/prdoc/pr_7322.prdoc new file mode 100644 index 0000000000000..72c566f7a8146 --- /dev/null +++ b/prdoc/pr_7322.prdoc @@ -0,0 +1,8 @@ +title: 'Bridges: emulated tests small nits/improvements' +doc: +- audience: Runtime Dev + description: |- + This PR removes the use of `open_bridge_between_asset_hub_rococo_and_asset_hub_westend`. This function was used in the generic `test_dry_run_transfer_across_pk_bridge` macro, which could cause compilation issues when used in other contexts (e.g. fellows repo). +crates: +- name: emulated-integration-tests-common + bump: patch From ccd6337f1bfef8ff9da9020fefc25db5a6508da7 Mon Sep 17 00:00:00 2001 From: Iulian Barbu <14218860+iulianbarbu@users.noreply.github.com> Date: Fri, 24 Jan 2025 18:29:17 +0200 Subject: [PATCH 144/169] sync-templates: enable syncing from stable release patches (#7227) # Description We're unable to sync templates repos with what's in polkadot-sdk/templates for stable2412 because the tag which references the release (`polkadot-stable2412`) is missing the Plan.toml file, which is needed by PSVM, ran when syncing, to update the templates dependencies versions in Cargo.tomls. This PR adds a workflow `patch` input, to enable the workflow to use PSVM with a tag corresponding to a patch stable release (e.g. `polkadot-stable2412-1`), which will contain the `Plan.toml` file. ## Integration This enables the templates repos update with the contents of latest stable2412 release, in terms of polkadot-sdk/templates, which is relevant for getting-started docs. ## Review Notes This PR adds a `patch` input for the `misc-sync-templates.yml` workflow, which if set will be used with `psvm` accordingly to update templates repos' dependencies versions based on upcomming patch stable2412-1, which contains the `Plan.toml`. The workflow will be ran manually after stable2412-1 is out and this work is tracked under #6329 . Signed-off-by: Iulian Barbu --- .github/workflows/misc-sync-templates.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/misc-sync-templates.yml b/.github/workflows/misc-sync-templates.yml index ac66e697562b3..ce01f010aa718 100644 --- a/.github/workflows/misc-sync-templates.yml +++ b/.github/workflows/misc-sync-templates.yml @@ -25,6 +25,10 @@ on: description: Enable runner debug logging required: false default: false + patch: + description: 'Patch number of the stable release we want to sync with' + required: false + default: "" jobs: sync-templates: @@ -139,7 +143,14 @@ jobs: rm -f "${{ env.template-path }}/src/lib.rs" - name: Run psvm on monorepo workspace dependencies - run: psvm -o -v ${{ github.event.inputs.stable_release_branch }} -p ./Cargo.toml + run: | + patch_input="${{ github.event.inputs.patch }}" + if [[ -n "$patch_input" ]]; then + patch="-$patch_input" + else + patch="" + fi + psvm -o -v "${{ github.event.inputs.stable_release_branch }}$patch" -p ./Cargo.toml working-directory: polkadot-sdk/ - name: Copy over required workspace dependencies run: | From 223bd28896cfa7ece1068c70da9f433a08da5554 Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Fri, 24 Jan 2025 17:34:15 +0100 Subject: [PATCH 145/169] [pallet-revive] eth-rpc minor fixes (#7325) - Add option to specify database_url using DATABASE_URL environment variable - Add a eth-rpc-tester rust bin that can be used to test deployment before releasing eth-rpc - make evm_block non fallible so that it can return an Ok response for older blocks when the runtime API is not available - update cargo.lock to integrate changes from https://github.com/paritytech/subxt/pull/1904 --------- Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- Cargo.lock | 12 +- prdoc/pr_7325.prdoc | 11 ++ substrate/frame/revive/rpc/Cargo.toml | 28 ++-- substrate/frame/revive/rpc/examples/README.md | 2 +- substrate/frame/revive/rpc/src/cli.rs | 6 +- substrate/frame/revive/rpc/src/client.rs | 11 +- substrate/frame/revive/rpc/src/eth-indexer.rs | 2 +- .../frame/revive/rpc/src/eth-rpc-tester.rs | 157 ++++++++++++++++++ substrate/frame/revive/rpc/src/example.rs | 2 - substrate/frame/revive/rpc/src/lib.rs | 4 +- 10 files changed, 198 insertions(+), 37 deletions(-) create mode 100644 prdoc/pr_7325.prdoc create mode 100644 substrate/frame/revive/rpc/src/eth-rpc-tester.rs diff --git a/Cargo.lock b/Cargo.lock index df2c58b7f4c1b..e4bd817300f03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8735,7 +8735,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.7", + "socket2 0.4.9", "tokio", "tower-service", "tracing", @@ -16456,7 +16456,7 @@ checksum = "4e69bf016dc406eff7d53a7d3f7cf1c2e72c82b9088aac1118591e36dd2cd3e9" dependencies = [ "bitcoin_hashes 0.13.0", "rand", - "rand_core 0.6.4", + "rand_core 0.5.1", "serde", "unicode-normalization", ] @@ -20721,7 +20721,7 @@ checksum = "f8650aabb6c35b860610e9cff5dc1af886c9e25073b7b1712a68972af4281302" dependencies = [ "bytes", "heck 0.5.0", - "itertools 0.13.0", + "itertools 0.12.1", "log", "multimap", "once_cell", @@ -20767,7 +20767,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" dependencies = [ "anyhow", - "itertools 0.13.0", + "itertools 0.12.1", "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.87", @@ -29085,9 +29085,9 @@ dependencies = [ [[package]] name = "subxt" -version = "0.38.0" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c53029d133e4e0cb7933f1fe06f2c68804b956de9bb8fa930ffca44e9e5e4230" +checksum = "1c17d7ec2359d33133b63c97e28c8b7cd3f0a5bc6ce567ae3aef9d9e85be3433" dependencies = [ "async-trait", "derive-where", diff --git a/prdoc/pr_7325.prdoc b/prdoc/pr_7325.prdoc new file mode 100644 index 0000000000000..788f01cb32470 --- /dev/null +++ b/prdoc/pr_7325.prdoc @@ -0,0 +1,11 @@ +title: '[pallet-revive] eth-rpc minor fixes' +doc: +- audience: Runtime Dev + description: |- + - Add option to specify database_url from an environment variable + - Add a test-deployment.rs rust script that can be used to test deployment and call of a contract before releasing eth-rpc + - Make evm_block non fallible so that it can return an Ok response for older blocks when the runtime API is not available + - Update subxt version to integrate changes from https://github.com/paritytech/subxt/pull/1904 +crates: +- name: pallet-revive-eth-rpc + bump: minor diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index 9d822f5ff8e27..014231f7f3e55 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -17,34 +17,33 @@ path = "src/main.rs" name = "eth-indexer" path = "src/eth-indexer.rs" +[[bin]] +name = "eth-rpc-tester" +path = "src/eth-rpc-tester.rs" + [[example]] name = "deploy" path = "examples/rust/deploy.rs" -required-features = ["example"] [[example]] name = "transfer" path = "examples/rust/transfer.rs" -required-features = ["example"] [[example]] name = "rpc-playground" path = "examples/rust/rpc-playground.rs" -required-features = ["example"] [[example]] name = "extrinsic" path = "examples/rust/extrinsic.rs" -required-features = ["example"] [[example]] name = "remark-extrinsic" path = "examples/rust/remark-extrinsic.rs" -required-features = ["example"] [dependencies] anyhow = { workspace = true } -clap = { workspace = true, features = ["derive"] } +clap = { workspace = true, features = ["derive", "env"] } codec = { workspace = true, features = ["derive"] } ethabi = { version = "18.0.0" } futures = { workspace = true, features = ["thread-pool"] } @@ -52,8 +51,9 @@ hex = { workspace = true } jsonrpsee = { workspace = true, features = ["full"] } log = { workspace = true } pallet-revive = { workspace = true, default-features = true } +pallet-revive-fixtures = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } -rlp = { workspace = true, optional = true } +rlp = { workspace = true } sc-cli = { workspace = true, default-features = true } sc-rpc = { workspace = true, default-features = true } sc-rpc-api = { workspace = true, default-features = true } @@ -62,24 +62,18 @@ sp-arithmetic = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true } sp-weights = { workspace = true, default-features = true } -sqlx = { version = "0.8.2", features = [ - "macros", - "runtime-tokio", - "sqlite", +sqlx = { version = "0.8.2", features = ["macros", "runtime-tokio", "sqlite"] } +subxt = { workspace = true, default-features = true, features = [ + "reconnecting-rpc-client", ] } -subxt = { workspace = true, default-features = true, features = ["reconnecting-rpc-client"] } -subxt-signer = { workspace = true, optional = true, features = [ +subxt-signer = { workspace = true, features = [ "unstable-eth", ] } thiserror = { workspace = true } tokio = { workspace = true, features = ["full"] } -[features] -example = ["rlp", "subxt-signer"] - [dev-dependencies] env_logger = { workspace = true } -pallet-revive-fixtures = { workspace = true, default-features = true } static_init = { workspace = true } substrate-cli-test-utils = { workspace = true } subxt-signer = { workspace = true, features = ["unstable-eth"] } diff --git a/substrate/frame/revive/rpc/examples/README.md b/substrate/frame/revive/rpc/examples/README.md index b9a2756b381d2..1079c254b9c20 100644 --- a/substrate/frame/revive/rpc/examples/README.md +++ b/substrate/frame/revive/rpc/examples/README.md @@ -42,7 +42,7 @@ RUST_LOG="info,eth-rpc=debug" cargo run -p pallet-revive-eth-rpc -- --dev Run one of the examples from the `examples` directory to send a transaction to the node: ```bash -RUST_LOG="info,eth-rpc=debug" cargo run -p pallet-revive-eth-rpc --features example --example deploy +RUST_LOG="info,eth-rpc=debug" cargo run -p pallet-revive-eth-rpc --example deploy ``` ## JS examples diff --git a/substrate/frame/revive/rpc/src/cli.rs b/substrate/frame/revive/rpc/src/cli.rs index d63d596ab7a8b..b6c57d2c3b0bf 100644 --- a/substrate/frame/revive/rpc/src/cli.rs +++ b/substrate/frame/revive/rpc/src/cli.rs @@ -19,7 +19,7 @@ use crate::{ client::{connect, Client}, BlockInfoProvider, BlockInfoProviderImpl, CacheReceiptProvider, DBReceiptProvider, EthRpcServer, EthRpcServerImpl, ReceiptProvider, SystemHealthRpcServer, - SystemHealthRpcServerImpl, + SystemHealthRpcServerImpl, LOG_TARGET, }; use clap::Parser; use futures::{pin_mut, FutureExt}; @@ -52,7 +52,7 @@ pub struct CliCommand { /// The database used to store Ethereum transaction hashes. /// This is only useful if the node needs to act as an archive node and respond to Ethereum RPC /// queries for transactions that are not in the in memory cache. - #[clap(long)] + #[clap(long, env = "DATABASE_URL")] pub database_url: Option, /// If true, we will only read from the database and not write to it. @@ -148,6 +148,7 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { Arc::new(BlockInfoProviderImpl::new(cache_size, api.clone(), rpc.clone())); let receipt_provider: Arc = if let Some(database_url) = database_url.as_ref() { + log::info!(target: LOG_TARGET, "🔗 Connecting to provided database"); Arc::new(( CacheReceiptProvider::default(), DBReceiptProvider::new( @@ -158,6 +159,7 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { .await?, )) } else { + log::info!(target: LOG_TARGET, "🔌 No database provided, using in-memory cache"); Arc::new(CacheReceiptProvider::default()) }; diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 440972c7a681b..47e439f068513 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -646,9 +646,9 @@ impl Client { &self, block: Arc, hydrated_transactions: bool, - ) -> Result { + ) -> Block { let runtime_api = self.api.runtime_api().at(block.hash()); - let gas_limit = Self::block_gas_limit(&runtime_api).await?; + let gas_limit = Self::block_gas_limit(&runtime_api).await.unwrap_or_default(); let header = block.header(); let timestamp = extract_block_timestamp(&block).await.unwrap_or_default(); @@ -658,7 +658,7 @@ impl Client { let state_root = header.state_root.0.into(); let extrinsics_root = header.extrinsics_root.0.into(); - let receipts = extract_receipts_from_block(&block).await?; + let receipts = extract_receipts_from_block(&block).await.unwrap_or_default(); let gas_used = receipts.iter().fold(U256::zero(), |acc, (_, receipt)| acc + receipt.gas_used); let transactions = if hydrated_transactions { @@ -675,7 +675,7 @@ impl Client { .into() }; - Ok(Block { + Block { hash: block.hash(), parent_hash, state_root, @@ -689,7 +689,7 @@ impl Client { receipts_root: extrinsics_root, transactions, ..Default::default() - }) + } } /// Convert a weight to a fee. @@ -697,7 +697,6 @@ impl Client { runtime_api: &subxt::runtime_api::RuntimeApi>, ) -> Result { let payload = subxt_client::apis().revive_api().block_gas_limit(); - let gas_limit = runtime_api.call(payload).await?; Ok(*gas_limit) } diff --git a/substrate/frame/revive/rpc/src/eth-indexer.rs b/substrate/frame/revive/rpc/src/eth-indexer.rs index 3e7f6b6fa91b8..894143be0a525 100644 --- a/substrate/frame/revive/rpc/src/eth-indexer.rs +++ b/substrate/frame/revive/rpc/src/eth-indexer.rs @@ -37,7 +37,7 @@ pub struct CliCommand { pub oldest_block: Option, /// The database used to store Ethereum transaction hashes. - #[clap(long)] + #[clap(long, env = "DATABASE_URL")] pub database_url: String, #[allow(missing_docs)] diff --git a/substrate/frame/revive/rpc/src/eth-rpc-tester.rs b/substrate/frame/revive/rpc/src/eth-rpc-tester.rs new file mode 100644 index 0000000000000..0ddad6874dfd5 --- /dev/null +++ b/substrate/frame/revive/rpc/src/eth-rpc-tester.rs @@ -0,0 +1,157 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use clap::Parser; +use jsonrpsee::http_client::HttpClientBuilder; +use pallet_revive::evm::{Account, BlockTag, ReceiptInfo}; +use pallet_revive_eth_rpc::{ + example::{wait_for_receipt, TransactionBuilder}, + EthRpcClient, +}; +use tokio::{ + io::{AsyncBufReadExt, BufReader}, + process::{Child, ChildStderr, Command}, + signal::unix::{signal, SignalKind}, +}; + +const DOCKER_CONTAINER_NAME: &str = "eth-rpc-test"; + +#[derive(Parser, Debug)] +#[clap(author, about, version)] +pub struct CliCommand { + /// The parity docker image e.g eth-rpc:master-fb2e414f + #[clap(long, default_value = "eth-rpc:master-fb2e414f")] + docker_image: String, + + /// The docker binary + /// Either docker or podman + #[clap(long, default_value = "docker")] + docker_bin: String, +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let CliCommand { docker_bin, docker_image, .. } = CliCommand::parse(); + + let mut docker_process = start_docker(&docker_bin, &docker_image)?; + let stderr = docker_process.stderr.take().unwrap(); + + tokio::select! { + result = docker_process.wait() => { + println!("docker failed: {result:?}"); + } + _ = interrupt() => { + kill_docker().await?; + } + _ = test_eth_rpc(stderr) => { + kill_docker().await?; + } + } + + Ok(()) +} + +async fn interrupt() { + let mut sigint = signal(SignalKind::interrupt()).expect("failed to listen for SIGINT"); + let mut sigterm = signal(SignalKind::terminate()).expect("failed to listen for SIGTERM"); + + tokio::select! { + _ = sigint.recv() => {}, + _ = sigterm.recv() => {}, + } +} + +fn start_docker(docker_bin: &str, docker_image: &str) -> anyhow::Result { + let docker_process = Command::new(docker_bin) + .args([ + "run", + "--name", + DOCKER_CONTAINER_NAME, + "--rm", + "-p", + "8545:8545", + &format!("docker.io/paritypr/{docker_image}"), + "--node-rpc-url", + "wss://westend-asset-hub-rpc.polkadot.io", + "--rpc-cors", + "all", + "--unsafe-rpc-external", + "--log=sc_rpc_server:info", + ]) + .stderr(std::process::Stdio::piped()) + .kill_on_drop(true) + .spawn()?; + + Ok(docker_process) +} + +async fn kill_docker() -> anyhow::Result<()> { + Command::new("docker").args(["kill", DOCKER_CONTAINER_NAME]).output().await?; + Ok(()) +} + +async fn test_eth_rpc(stderr: ChildStderr) -> anyhow::Result<()> { + let mut reader = BufReader::new(stderr).lines(); + while let Some(line) = reader.next_line().await? { + println!("{line}"); + if line.contains("Running JSON-RPC server") { + break; + } + } + + let account = Account::default(); + let data = vec![]; + let (bytes, _) = pallet_revive_fixtures::compile_module("dummy")?; + let input = bytes.into_iter().chain(data).collect::>(); + + println!("Account:"); + println!("- address: {:?}", account.address()); + let client = HttpClientBuilder::default().build("http://localhost:8545")?; + + let nonce = client.get_transaction_count(account.address(), BlockTag::Latest.into()).await?; + let balance = client.get_balance(account.address(), BlockTag::Latest.into()).await?; + println!("- nonce: {nonce:?}"); + println!("- balance: {balance:?}"); + + println!("\n\n=== Deploying dummy contract ===\n\n"); + let hash = TransactionBuilder::default().input(input).send(&client).await?; + + println!("Hash: {hash:?}"); + println!("Waiting for receipt..."); + let ReceiptInfo { block_number, gas_used, contract_address, .. } = + wait_for_receipt(&client, hash).await?; + + let contract_address = contract_address.unwrap(); + println!("\nReceipt:"); + println!("Block explorer: https://westend-asset-hub-eth-explorer.parity.io/{hash:?}"); + println!("- Block number: {block_number}"); + println!("- Gas used: {gas_used}"); + println!("- Address: {contract_address:?}"); + + println!("\n\n=== Calling dummy contract ===\n\n"); + let hash = TransactionBuilder::default().to(contract_address).send(&client).await?; + + println!("Hash: {hash:?}"); + println!("Waiting for receipt..."); + + let ReceiptInfo { block_number, gas_used, to, .. } = wait_for_receipt(&client, hash).await?; + println!("\nReceipt:"); + println!("Block explorer: https://westend-asset-hub-eth-explorer.parity.io/{hash:?}"); + println!("- Block number: {block_number}"); + println!("- Gas used: {gas_used}"); + println!("- To: {to:?}"); + Ok(()) +} diff --git a/substrate/frame/revive/rpc/src/example.rs b/substrate/frame/revive/rpc/src/example.rs index 3b9a33296ef4d..aad5b4fbc344d 100644 --- a/substrate/frame/revive/rpc/src/example.rs +++ b/substrate/frame/revive/rpc/src/example.rs @@ -15,8 +15,6 @@ // See the License for the specific language governing permissions and // limitations under the License. //! Example utilities -#![cfg(any(feature = "example", test))] - use crate::{EthRpcClient, ReceiptInfo}; use anyhow::Context; use pallet_revive::evm::{ diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index 5e1341e2a29ab..fcf93fa6c0d2e 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -214,7 +214,7 @@ impl EthRpcServer for EthRpcServerImpl { let Some(block) = self.client.block_by_hash(&block_hash).await? else { return Ok(None); }; - let block = self.client.evm_block(block, hydrated_transactions).await?; + let block = self.client.evm_block(block, hydrated_transactions).await; Ok(Some(block)) } @@ -254,7 +254,7 @@ impl EthRpcServer for EthRpcServerImpl { let Some(block) = self.client.block_by_number_or_tag(&block).await? else { return Ok(None); }; - let block = self.client.evm_block(block, hydrated_transactions).await?; + let block = self.client.evm_block(block, hydrated_transactions).await; Ok(Some(block)) } From dcbea60cc7a280f37986f2f815ec3fcff4758be5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Fri, 24 Jan 2025 19:20:09 +0100 Subject: [PATCH 146/169] revive: Fix compilation of `uapi` crate when `unstable-hostfn` is not set (#7318) This regression was introduced with some of the recent PRs. Regression fixed and test added. --------- Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/workflows/build-misc.yml | 28 +++++++++++++++++++++++++ prdoc/pr_7318.prdoc | 8 +++++++ substrate/frame/revive/uapi/src/host.rs | 26 +++++++++++------------ 3 files changed, 49 insertions(+), 13 deletions(-) create mode 100644 prdoc/pr_7318.prdoc diff --git a/.github/workflows/build-misc.yml b/.github/workflows/build-misc.yml index 335c26282027e..e1ef29f305d0f 100644 --- a/.github/workflows/build-misc.yml +++ b/.github/workflows/build-misc.yml @@ -46,6 +46,34 @@ jobs: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} app-key: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_KEY }} + # As part of our test fixtures we build the revive-uapi crate always with the `unstable-hostfn` feature. + # To make sure that it won't break for users downstream which are not setting this feature + # It doesn't need to produce working code so we just use a similar enough RISC-V target + check-revive-stable-uapi-polkavm: + timeout-minutes: 30 + needs: [preflight] + runs-on: ${{ needs.preflight.outputs.RUNNER }} + container: + image: ${{ needs.preflight.outputs.IMAGE }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Check Rust + run: | + rustup show + rustup +nightly show + + - name: Build + id: required + run: forklift cargo +nightly check -p pallet-revive-uapi --no-default-features --target riscv64imac-unknown-none-elf -Zbuild-std=core + - name: Stop all workflows if failed + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} + uses: ./.github/actions/workflow-stopper + with: + app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} + app-key: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_KEY }} + build-subkey: timeout-minutes: 20 needs: [preflight] diff --git a/prdoc/pr_7318.prdoc b/prdoc/pr_7318.prdoc new file mode 100644 index 0000000000000..ec41b648a9c28 --- /dev/null +++ b/prdoc/pr_7318.prdoc @@ -0,0 +1,8 @@ +title: 'revive: Fix compilation of `uapi` crate when `unstable-hostfn` is not set' +doc: +- audience: Runtime Dev + description: This regression was introduced with some of the recent PRs. Regression + fixed and test added. +crates: +- name: pallet-revive-uapi + bump: minor diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index 3e5cf0eb0c243..130cbf97ad504 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -144,18 +144,6 @@ pub trait HostFn: private::Sealed { /// - `output`: A reference to the output data buffer to write the origin's address. fn origin(output: &mut [u8; 20]); - /// Retrieve the account id for a specified address. - /// - /// # Parameters - /// - /// - `addr`: A `H160` address. - /// - `output`: A reference to the output data buffer to write the account id. - /// - /// # Note - /// - /// If no mapping exists for `addr`, the fallback account id will be returned. - fn to_account_id(addr: &[u8; 20], output: &mut [u8]); - /// Retrieve the code hash for a specified contract address. /// /// # Parameters @@ -415,9 +403,21 @@ pub trait HostFn: private::Sealed { /// # Parameters /// /// - `output`: A reference to the output data buffer to write the block number. - #[unstable_hostfn] fn block_number(output: &mut [u8; 32]); + /// Retrieve the account id for a specified address. + /// + /// # Parameters + /// + /// - `addr`: A `H160` address. + /// - `output`: A reference to the output data buffer to write the account id. + /// + /// # Note + /// + /// If no mapping exists for `addr`, the fallback account id will be returned. + #[unstable_hostfn] + fn to_account_id(addr: &[u8; 20], output: &mut [u8]); + /// Stores the block hash of the given block number into the supplied buffer. /// /// # Parameters From a31d26dc30d90ca3b228d07fda8d3f94da6aa155 Mon Sep 17 00:00:00 2001 From: Andrei Eres Date: Fri, 24 Jan 2025 20:44:31 +0100 Subject: [PATCH 147/169] Fix the link to the chain snapshots (#7330) The link to Polkachu is not working --- substrate/utils/frame/benchmarking-cli/src/storage/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/substrate/utils/frame/benchmarking-cli/src/storage/README.md b/substrate/utils/frame/benchmarking-cli/src/storage/README.md index 95c83d2edbc5c..955b52a248c6e 100644 --- a/substrate/utils/frame/benchmarking-cli/src/storage/README.md +++ b/substrate/utils/frame/benchmarking-cli/src/storage/README.md @@ -13,7 +13,7 @@ Running the command on Substrate itself is not verify meaningful, since the gene used. The output for the Polkadot client with a recent chain snapshot will give you a better impression. A recent snapshot can -be downloaded from [Polkachu]. +be downloaded from [Polkadot Snapshots]. Then run (remove the `--db=paritydb` if you have a RocksDB snapshot): ```sh cargo run --profile=production -- benchmark storage --dev --state-version=0 --db=paritydb --weight-path runtime/polkadot/constants/src/weights @@ -106,6 +106,6 @@ write: 71_347 * constants::WEIGHT_REF_TIME_PER_NANOS, License: Apache-2.0 -[Polkachu]: https://polkachu.com/snapshots +[Polkadot Snapshots]: https://snapshots.polkadot.io [paritydb_weights.rs]: https://github.com/paritytech/polkadot/blob/c254e5975711a6497af256f6831e9a6c752d28f5/runtime/polkadot/constants/src/weights/paritydb_weights.rs#L60 From 682f8cd22f5bcb76d1b98820b62be49d11deae10 Mon Sep 17 00:00:00 2001 From: Guillaume Thiolliere Date: Sat, 25 Jan 2025 12:04:45 +0900 Subject: [PATCH 148/169] `set_validation_data` register weight manually, do not use refund when the pre dispatch is zero. (#7327) Related https://github.com/paritytech/polkadot-sdk/issues/6772 For an extrinsic, in the post dispatch info, the actual weight is only used to reclaim unused weight. If the actual weight is more than the pre dispatch weight, then the extrinsic is using the minimum, e.g., the weight used registered in pre dispatch. In parachain-system pallet one call is `set_validation_data`. This call is returning an actual weight, but the pre-dispatch weight is 0. This PR fix the disregard of actual weight of `set_validation_data` by registering it manually. --- cumulus/pallets/parachain-system/src/lib.rs | 14 ++++++++++---- prdoc/pr_7327.prdoc | 11 +++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 prdoc/pr_7327.prdoc diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs index 6857b08e66b7d..fa754ea29ccf5 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -45,7 +45,7 @@ use cumulus_primitives_core::{ use cumulus_primitives_parachain_inherent::{MessageQueueChain, ParachainInherentData}; use frame_support::{ defensive, - dispatch::{DispatchResult, Pays, PostDispatchInfo}, + dispatch::DispatchResult, ensure, inherent::{InherentData, InherentIdentifier, ProvideInherent}, traits::{Get, HandleMessage}, @@ -567,11 +567,12 @@ pub mod pallet { /// if the appropriate time has come. #[pallet::call_index(0)] #[pallet::weight((0, DispatchClass::Mandatory))] - // TODO: This weight should be corrected. + // TODO: This weight should be corrected. Currently the weight is registered manually in the + // call with `register_extra_weight_unchecked`. pub fn set_validation_data( origin: OriginFor, data: ParachainInherentData, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { ensure_none(origin)?; assert!( !>::exists(), @@ -692,7 +693,12 @@ pub mod pallet { vfp.relay_parent_number, )); - Ok(PostDispatchInfo { actual_weight: Some(total_weight), pays_fee: Pays::No }) + frame_system::Pallet::::register_extra_weight_unchecked( + total_weight, + DispatchClass::Mandatory, + ); + + Ok(()) } #[pallet::call_index(1)] diff --git a/prdoc/pr_7327.prdoc b/prdoc/pr_7327.prdoc new file mode 100644 index 0000000000000..bb2d7a671af31 --- /dev/null +++ b/prdoc/pr_7327.prdoc @@ -0,0 +1,11 @@ +title: Correctly register the weight n `set_validation_data` in `cumulus-pallet-parachain-system` + +doc: + - audience: Runtime Dev + description: | + The actual weight of the call was register as a refund, but the pre-dispatch weight is 0, + and we can't refund from 0. Now the actual weight is registered manually instead of ignored. + +crates: + - name: cumulus-pallet-parachain-system + bump: patch From 17ae06272b366cbeb9429e01a3bf70d30a885a6f Mon Sep 17 00:00:00 2001 From: Guillaume Thiolliere Date: Sun, 26 Jan 2025 06:32:45 +0900 Subject: [PATCH 149/169] Improve debugging by using `#[track_caller]` in system `assert_last_event` and `assert_has_event` (#7142) Without track caller the error message of the assert points to the `assert_last_event` function, which is not useful. ``` thread 'tests::set_metadata_works' panicked at /home/gui/Developpement/polkadot-sdk/substrate/frame/system/src/lib.rs:2034:9: assertion `left == right` failed: expected event RuntimeEvent::Referenda(Event::MetadataSet { index: 0, hash: 0xbb30a42c1e62f0afda5f0a4e8a562f7a13a24cea00ee81917b86b89e801314aa }) is not equal to the last event RuntimeEvent::Referenda(Event::MetadataSet { index: 1, hash: 0xbb30a42c1e62f0afda5f0a4e8a562f7a13a24cea00ee81917b86b89e801314aa }) left: RuntimeEvent::Referenda(Event::MetadataSet { index: 1, hash: 0xbb30a42c1e62f0afda5f0a4e8a562f7a13a24cea00ee81917b86b89e801314aa }) right: RuntimeEvent::Referenda(Event::MetadataSet { index: 0, hash: 0xbb30a42c1e62f0afda5f0a4e8a562f7a13a24cea00ee81917b86b89e801314aa }) ``` With the track caller the error message points to the caller, showing the source of the error: ``` thread 'tests::set_metadata_works' panicked at substrate/frame/referenda/src/tests.rs:639:9: assertion `left == right` failed: expected event RuntimeEvent::Referenda(Event::MetadataSet { index: 0, hash: 0xbb30a42c1e62f0afda5f0a4e8a562f7a13a24cea00ee81917b86b89e801314aa }) is not equal to the last event RuntimeEvent::Referenda(Event::MetadataSet { index: 1, hash: 0xbb30a42c1e62f0afda5f0a4e8a562f7a13a24cea00ee81917b86b89e801314aa }) left: RuntimeEvent::Referenda(Event::MetadataSet { index: 1, hash: 0xbb30a42c1e62f0afda5f0a4e8a562f7a13a24cea00ee81917b86b89e801314aa }) right: RuntimeEvent::Referenda(Event::MetadataSet { index: 0, hash: 0xbb30a42c1e62f0afda5f0a4e8a562f7a13a24cea00ee81917b86b89e801314aa }) ``` I also improved the error message to include a warning when checking events on block number zero. --- substrate/frame/system/src/lib.rs | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/substrate/frame/system/src/lib.rs b/substrate/frame/system/src/lib.rs index f2bb5e290c943..8980c6d6c8f42 100644 --- a/substrate/frame/system/src/lib.rs +++ b/substrate/frame/system/src/lib.rs @@ -2062,11 +2062,18 @@ impl Pallet { /// /// NOTE: Events not registered at the genesis block and quietly omitted. #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] + #[track_caller] pub fn assert_has_event(event: T::RuntimeEvent) { + let warn = if Self::block_number().is_zero() { + "WARNING: block number is zero, and events are not registered at block number zero.\n" + } else { + "" + }; + let events = Self::events(); assert!( events.iter().any(|record| record.event == event), - "expected event {event:?} not found in events {events:?}", + "{warn}expected event {event:?} not found in events {events:?}", ); } @@ -2074,11 +2081,22 @@ impl Pallet { /// /// NOTE: Events not registered at the genesis block and quietly omitted. #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] + #[track_caller] pub fn assert_last_event(event: T::RuntimeEvent) { - let last_event = Self::events().last().expect("events expected").event.clone(); + let warn = if Self::block_number().is_zero() { + "WARNING: block number is zero, and events are not registered at block number zero.\n" + } else { + "" + }; + + let last_event = Self::events() + .last() + .expect(&alloc::format!("{warn}events expected")) + .event + .clone(); assert_eq!( last_event, event, - "expected event {event:?} is not equal to the last event {last_event:?}", + "{warn}expected event {event:?} is not equal to the last event {last_event:?}", ); } From c95e49c4c9848c42d5cbfd261de0d22eec9c2bf6 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Sun, 26 Jan 2025 22:18:43 +0100 Subject: [PATCH 150/169] Removed unused dependencies (partial progress) (#7329) Part of: https://github.com/paritytech/polkadot-sdk/issues/6906 --- Cargo.lock | 121 ------------------ Cargo.toml | 3 - bridges/relays/utils/Cargo.toml | 1 - .../pallets/ethereum-client/Cargo.toml | 1 - .../pallets/outbound-queue/Cargo.toml | 1 - bridges/snowbridge/pallets/system/Cargo.toml | 1 - .../snowbridge/primitives/ethereum/Cargo.toml | 1 - cumulus/client/network/Cargo.toml | 2 - cumulus/client/pov-recovery/Cargo.toml | 2 - .../Cargo.toml | 3 - .../assets/asset-hub-rococo/Cargo.toml | 2 - .../bridge-hubs/test-utils/Cargo.toml | 2 - cumulus/polkadot-parachain/Cargo.toml | 3 - cumulus/test/runtime/Cargo.toml | 2 - cumulus/test/service/Cargo.toml | 5 - cumulus/xcm/xcm-emulator/Cargo.toml | 1 - polkadot/node/metrics/Cargo.toml | 2 - polkadot/node/test/service/Cargo.toml | 1 - .../test-parachains/adder/collator/Cargo.toml | 1 - .../undying/collator/Cargo.toml | 1 - polkadot/runtime/parachains/Cargo.toml | 4 - polkadot/runtime/rococo/Cargo.toml | 10 -- polkadot/runtime/test-runtime/Cargo.toml | 1 - polkadot/runtime/westend/Cargo.toml | 8 -- polkadot/xcm/xcm-builder/Cargo.toml | 1 - substrate/bin/node/cli/Cargo.toml | 1 - substrate/client/api/Cargo.toml | 1 - substrate/client/db/Cargo.toml | 1 - substrate/client/network/common/Cargo.toml | 6 - substrate/client/telemetry/Cargo.toml | 1 - substrate/frame/contracts/Cargo.toml | 6 - .../frame/contracts/mock-network/Cargo.toml | 10 -- substrate/frame/conviction-voting/Cargo.toml | 4 - substrate/frame/delegated-staking/Cargo.toml | 1 - substrate/frame/fast-unstake/Cargo.toml | 1 - substrate/frame/glutton/Cargo.toml | 6 - substrate/frame/indices/Cargo.toml | 3 - substrate/frame/nfts/runtime-api/Cargo.toml | 3 +- substrate/frame/offences/Cargo.toml | 4 - substrate/frame/paged-list/Cargo.toml | 3 - substrate/frame/verify-signature/Cargo.toml | 15 --- substrate/test-utils/Cargo.toml | 8 -- substrate/test-utils/client/Cargo.toml | 1 - 43 files changed, 1 insertion(+), 254 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e4bd817300f03..64c68d81d42b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -998,7 +998,6 @@ dependencies = [ "pallet-multisig 28.0.0", "pallet-nft-fractionalization 10.0.0", "pallet-nfts 22.0.0", - "pallet-nfts-runtime-api 14.0.0", "pallet-proxy 28.0.0", "pallet-session 28.0.0", "pallet-timestamp 27.0.0", @@ -2780,7 +2779,6 @@ dependencies = [ "bp-runtime 0.7.0", "bp-test-utils 0.7.0", "bp-xcm-bridge-hub 0.2.0", - "bridge-runtime-common 0.7.0", "cumulus-pallet-parachain-system 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "frame-support 28.0.0", @@ -3893,16 +3891,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "console_error_panic_hook" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" -dependencies = [ - "cfg-if", - "wasm-bindgen", -] - [[package]] name = "const-hex" version = "1.14.0" @@ -4770,7 +4758,6 @@ dependencies = [ "polkadot-parachain-primitives 6.0.0", "polkadot-primitives 7.0.0", "polkadot-test-client", - "portpicker", "rstest", "sc-cli", "sc-client-api", @@ -4783,7 +4770,6 @@ dependencies = [ "sp-runtime 31.0.1", "sp-state-machine 0.35.0", "sp-version 29.0.0", - "substrate-test-utils", "tokio", "tracing", "url", @@ -4827,7 +4813,6 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-overseer", "polkadot-primitives 7.0.0", - "portpicker", "rand", "rstest", "sc-cli", @@ -4841,7 +4826,6 @@ dependencies = [ "sp-runtime 31.0.1", "sp-tracing 16.0.0", "sp-version 29.0.0", - "substrate-test-utils", "tokio", "tracing", ] @@ -5471,7 +5455,6 @@ dependencies = [ "async-trait", "cumulus-primitives-core 0.7.0", "cumulus-relay-chain-interface", - "cumulus-test-service", "futures", "futures-timer", "polkadot-cli", @@ -5664,7 +5647,6 @@ dependencies = [ "pallet-aura 27.0.0", "pallet-authorship 28.0.0", "pallet-balances 28.0.0", - "pallet-collator-selection 9.0.0", "pallet-glutton 14.0.0", "pallet-message-queue 31.0.0", "pallet-session 28.0.0", @@ -5703,7 +5685,6 @@ dependencies = [ "cumulus-client-consensus-aura", "cumulus-client-consensus-common", "cumulus-client-consensus-proposer", - "cumulus-client-consensus-relay-chain", "cumulus-client-parachain-inherent", "cumulus-client-pov-recovery", "cumulus-client-service", @@ -5722,7 +5703,6 @@ dependencies = [ "jsonrpsee", "pallet-timestamp 27.0.0", "pallet-transaction-payment 28.0.0", - "parachains-common 7.0.0", "parity-scale-codec", "polkadot-cli", "polkadot-node-subsystem", @@ -5730,7 +5710,6 @@ dependencies = [ "polkadot-primitives 7.0.0", "polkadot-service", "polkadot-test-service", - "portpicker", "prometheus", "rand", "sc-basic-authorship", @@ -5766,7 +5745,6 @@ dependencies = [ "sp-timestamp 26.0.0", "sp-tracing 16.0.0", "substrate-test-client", - "substrate-test-utils", "tempfile", "tokio", "tracing", @@ -13004,13 +12982,11 @@ dependencies = [ "frame-system 28.0.0", "impl-trait-for-tuples", "log", - "pallet-assets 29.1.0", "pallet-balances 28.0.0", "pallet-contracts-fixtures", "pallet-contracts-proc-macro 18.0.0", "pallet-contracts-uapi 5.0.0", "pallet-insecure-randomness-collective-flip 16.0.0", - "pallet-message-queue 31.0.0", "pallet-proxy 28.0.0", "pallet-timestamp 27.0.0", "pallet-utility 28.0.0", @@ -13085,7 +13061,6 @@ dependencies = [ name = "pallet-contracts-mock-network" version = "3.0.0" dependencies = [ - "assert_matches", "frame-support 28.0.0", "frame-system 28.0.0", "pallet-assets 29.1.0", @@ -13094,17 +13069,13 @@ dependencies = [ "pallet-contracts-fixtures", "pallet-contracts-proc-macro 18.0.0", "pallet-contracts-uapi 5.0.0", - "pallet-insecure-randomness-collective-flip 16.0.0", "pallet-message-queue 31.0.0", - "pallet-proxy 28.0.0", "pallet-timestamp 27.0.0", - "pallet-utility 28.0.0", "pallet-xcm 7.0.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "polkadot-primitives 7.0.0", "polkadot-runtime-parachains 7.0.0", - "pretty_assertions", "scale-info", "sp-api 26.0.0", "sp-core 28.0.0", @@ -13206,7 +13177,6 @@ dependencies = [ "frame-support 28.0.0", "frame-system 28.0.0", "pallet-balances 28.0.0", - "pallet-scheduler 29.0.0", "parity-scale-codec", "scale-info", "serde", @@ -13301,7 +13271,6 @@ dependencies = [ "sp-runtime 31.0.1", "sp-staking 26.0.0", "sp-tracing 16.0.0", - "substrate-test-utils", ] [[package]] @@ -13701,7 +13670,6 @@ dependencies = [ "sp-runtime 31.0.1", "sp-staking 26.0.0", "sp-tracing 16.0.0", - "substrate-test-utils", ] [[package]] @@ -13732,7 +13700,6 @@ dependencies = [ "frame-support 28.0.0", "frame-system 28.0.0", "log", - "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", "sp-core 28.0.0", @@ -13898,7 +13865,6 @@ dependencies = [ "scale-info", "sp-core 28.0.0", "sp-io 30.0.0", - "sp-keyring 31.0.0", "sp-runtime 31.0.1", ] @@ -14265,7 +14231,6 @@ dependencies = [ name = "pallet-nfts-runtime-api" version = "14.0.0" dependencies = [ - "pallet-nfts 22.0.0", "parity-scale-codec", "sp-api 26.0.0", ] @@ -14486,7 +14451,6 @@ dependencies = [ "frame-support 28.0.0", "frame-system 28.0.0", "log", - "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", "serde", @@ -14568,7 +14532,6 @@ name = "pallet-paged-list" version = "0.6.0" dependencies = [ "docify", - "frame-benchmarking 28.0.0", "frame-support 28.0.0", "frame-system 28.0.0", "parity-scale-codec", @@ -16012,10 +15975,6 @@ dependencies = [ "frame-benchmarking 28.0.0", "frame-support 28.0.0", "frame-system 28.0.0", - "pallet-balances 28.0.0", - "pallet-collective 28.0.0", - "pallet-root-testing 4.0.0", - "pallet-timestamp 27.0.0", "parity-scale-codec", "scale-info", "sp-core 28.0.0", @@ -17072,12 +17031,6 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" -[[package]] -name = "platforms" -version = "3.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4c7666f2019727f9e8e14bf14456e99c707d780922869f1ba473eee101fa49" - [[package]] name = "plotters" version = "0.3.5" @@ -17955,7 +17908,6 @@ dependencies = [ name = "polkadot-node-metrics" version = "7.0.0" dependencies = [ - "assert_cmd", "bs58", "futures", "futures-timer", @@ -17973,7 +17925,6 @@ dependencies = [ "sc-tracing", "sp-keyring 31.0.0", "substrate-prometheus-endpoint", - "substrate-test-utils", "tempfile", "tokio", "tracing-gum", @@ -18254,7 +18205,6 @@ dependencies = [ "bridge-hub-westend-runtime", "collectives-westend-runtime", "color-eyre", - "contracts-rococo-runtime", "coretime-rococo-runtime", "coretime-westend-runtime", "cumulus-primitives-core 0.7.0", @@ -18594,7 +18544,6 @@ dependencies = [ "pallet-session 28.0.0", "pallet-staking 28.0.0", "pallet-timestamp 27.0.0", - "pallet-vesting 28.0.0", "parity-scale-codec", "polkadot-core-primitives 7.0.0", "polkadot-parachain-primitives 6.0.0", @@ -19846,7 +19795,6 @@ dependencies = [ "staging-xcm-executor 7.0.0", "substrate-wasm-builder 17.0.0", "test-runtime-constants", - "tiny-keccak", ] [[package]] @@ -19894,7 +19842,6 @@ dependencies = [ "sp-runtime 31.0.1", "sp-state-machine 0.35.0", "substrate-test-client", - "substrate-test-utils", "tempfile", "test-runtime-constants", "tokio", @@ -21307,7 +21254,6 @@ dependencies = [ "async-trait", "backoff", "bp-runtime 0.7.0", - "console", "futures", "isahc", "jsonpath_lib", @@ -21632,14 +21578,12 @@ dependencies = [ "pallet-beefy-mmr 28.0.0", "pallet-bounties 27.0.0", "pallet-child-bounties 27.0.0", - "pallet-collective 28.0.0", "pallet-conviction-voting 28.0.0", "pallet-democracy 28.0.0", "pallet-elections-phragmen 29.0.0", "pallet-grandpa 28.0.0", "pallet-identity 29.0.0", "pallet-indices 28.0.0", - "pallet-membership 28.0.0", "pallet-message-queue 31.0.0", "pallet-migrations 1.0.0", "pallet-mmr 27.0.0", @@ -21676,7 +21620,6 @@ dependencies = [ "polkadot-runtime-parachains 7.0.0", "rococo-runtime-constants 7.0.0", "scale-info", - "separator", "serde", "serde_derive", "serde_json", @@ -21708,7 +21651,6 @@ dependencies = [ "staging-xcm-executor 7.0.0", "static_assertions", "substrate-wasm-builder 17.0.0", - "tiny-keccak", "tokio", "xcm-runtime-apis 0.1.0", ] @@ -22463,7 +22405,6 @@ dependencies = [ "sp-state-machine 0.35.0", "sp-statement-store 10.0.0", "sp-storage 19.0.0", - "sp-test-primitives", "sp-trie 29.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime", @@ -22486,7 +22427,6 @@ dependencies = [ "parity-db", "parity-scale-codec", "parking_lot 0.12.3", - "quickcheck", "rand", "sc-client-api", "sc-state-db", @@ -23223,16 +23163,10 @@ dependencies = [ name = "sc-network-common" version = "0.33.0" dependencies = [ - "async-trait", "bitflags 1.3.2", "futures", - "libp2p-identity", "parity-scale-codec", "prost-build", - "sc-consensus", - "sc-network-types", - "sp-consensus", - "sp-consensus-grandpa 13.0.0", "sp-runtime 31.0.1", "tempfile", ] @@ -23808,7 +23742,6 @@ dependencies = [ "parking_lot 0.12.3", "pin-project", "rand", - "sc-network", "sc-utils", "serde", "serde_json", @@ -24151,12 +24084,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.2.0" @@ -24353,12 +24280,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" -[[package]] -name = "separator" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f97841a747eef040fcd2e7b3b9a220a7205926e60488e673d9e4926d27772ce5" - [[package]] name = "serde" version = "1.0.214" @@ -25087,7 +25008,6 @@ dependencies = [ "sp-io 30.0.0", "sp-runtime 31.0.1", "sp-std 14.0.0", - "wasm-bindgen-test", ] [[package]] @@ -25200,7 +25120,6 @@ dependencies = [ "snowbridge-pallet-ethereum-client-fixtures 0.9.0", "sp-core 28.0.0", "sp-io 30.0.0", - "sp-keyring 31.0.0", "sp-runtime 31.0.1", "sp-std 14.0.0", "static_assertions", @@ -25353,7 +25272,6 @@ dependencies = [ "sp-arithmetic 23.0.0", "sp-core 28.0.0", "sp-io 30.0.0", - "sp-keyring 31.0.0", "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -25400,7 +25318,6 @@ dependencies = [ "snowbridge-pallet-outbound-queue 0.2.0", "sp-core 28.0.0", "sp-io 30.0.0", - "sp-keyring 31.0.0", "sp-runtime 31.0.1", "sp-std 14.0.0", "staging-xcm 7.0.0", @@ -28275,7 +28192,6 @@ dependencies = [ "node-rpc", "node-testing", "parity-scale-codec", - "platforms", "polkadot-sdk 0.1.0", "pretty_assertions", "rand", @@ -28390,7 +28306,6 @@ dependencies = [ name = "staging-xcm-builder" version = "7.0.0" dependencies = [ - "assert_matches", "frame-support 28.0.0", "frame-system 28.0.0", "impl-trait-for-tuples", @@ -28880,7 +28795,6 @@ dependencies = [ "sc-client-db", "sc-consensus", "sc-executor 0.32.0", - "sc-offchain", "sc-service", "serde", "serde_json", @@ -28982,12 +28896,6 @@ dependencies = [ [[package]] name = "substrate-test-utils" version = "4.0.0-dev" -dependencies = [ - "futures", - "sc-service", - "tokio", - "trybuild", -] [[package]] name = "substrate-wasm-builder" @@ -29603,7 +29511,6 @@ dependencies = [ "sc-service", "sp-core 28.0.0", "sp-keyring 31.0.0", - "substrate-test-utils", "test-parachain-adder", "tokio", ] @@ -29650,7 +29557,6 @@ dependencies = [ "sc-service", "sp-core 28.0.0", "sp-keyring 31.0.0", - "substrate-test-utils", "test-parachain-undying", "tokio", ] @@ -30843,30 +30749,6 @@ version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" -[[package]] -name = "wasm-bindgen-test" -version = "0.3.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e6e302a7ea94f83a6d09e78e7dc7d9ca7b186bc2829c24a22d0753efd680671" -dependencies = [ - "console_error_panic_hook", - "js-sys", - "scoped-tls", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-bindgen-test-macro", -] - -[[package]] -name = "wasm-bindgen-test-macro" -version = "0.3.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecb993dd8c836930ed130e020e77d9b2e65dd0fbab1b67c790b0f5d80b11a575" -dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.37", -] - [[package]] name = "wasm-encoder" version = "0.31.1" @@ -31380,10 +31262,8 @@ dependencies = [ "pallet-balances 28.0.0", "pallet-beefy 28.0.0", "pallet-beefy-mmr 28.0.0", - "pallet-collective 28.0.0", "pallet-conviction-voting 28.0.0", "pallet-delegated-staking 1.0.0", - "pallet-democracy 28.0.0", "pallet-election-provider-multi-phase 27.0.0", "pallet-election-provider-support-benchmarking 27.0.0", "pallet-elections-phragmen 29.0.0", @@ -31969,7 +31849,6 @@ version = "0.5.0" dependencies = [ "array-bytes", "cumulus-pallet-parachain-system 0.7.0", - "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-primitives-core 0.7.0", "cumulus-primitives-parachain-inherent 0.7.0", "cumulus-test-relay-sproof-builder 0.7.0", diff --git a/Cargo.toml b/Cargo.toml index 18c1dd2c68d2f..7a906e7c0d64f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1036,7 +1036,6 @@ people-rococo-runtime = { path = "cumulus/parachains/runtimes/people/people-roco people-westend-emulated-chain = { path = "cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend" } people-westend-runtime = { path = "cumulus/parachains/runtimes/people/people-westend" } pin-project = { version = "1.1.3" } -platforms = { version = "3.4" } polkadot-approval-distribution = { path = "polkadot/node/network/approval-distribution", default-features = false } polkadot-availability-bitfield-distribution = { path = "polkadot/node/network/bitfield-distribution", default-features = false } polkadot-availability-distribution = { path = "polkadot/node/network/availability-distribution", default-features = false } @@ -1209,7 +1208,6 @@ schnorrkel = { version = "0.11.4", default-features = false } seccompiler = { version = "0.4.0" } secp256k1 = { version = "0.28.0", default-features = false } secrecy = { version = "0.8.0", default-features = false } -separator = { version = "0.4.1" } serde = { version = "1.0.214", default-features = false } serde-big-array = { version = "0.3.2" } serde_derive = { version = "1.0.117" } @@ -1372,7 +1370,6 @@ void = { version = "1.0.2" } w3f-bls = { version = "0.1.3", default-features = false } wait-timeout = { version = "0.2" } walkdir = { version = "2.5.0" } -wasm-bindgen-test = { version = "0.3.19" } wasm-instrument = { version = "0.4", default-features = false } wasm-opt = { version = "0.116" } wasm-timer = { version = "0.2.5" } diff --git a/bridges/relays/utils/Cargo.toml b/bridges/relays/utils/Cargo.toml index 8592ca780eaa8..6d28789daaec2 100644 --- a/bridges/relays/utils/Cargo.toml +++ b/bridges/relays/utils/Cargo.toml @@ -15,7 +15,6 @@ anyhow = { workspace = true, default-features = true } async-std = { workspace = true } async-trait = { workspace = true } backoff = { workspace = true } -console = { workspace = true } futures = { workspace = true } isahc = { workspace = true } jsonpath_lib = { workspace = true } diff --git a/bridges/snowbridge/pallets/ethereum-client/Cargo.toml b/bridges/snowbridge/pallets/ethereum-client/Cargo.toml index ebd8a1c6ed11d..87b4c66d77535 100644 --- a/bridges/snowbridge/pallets/ethereum-client/Cargo.toml +++ b/bridges/snowbridge/pallets/ethereum-client/Cargo.toml @@ -45,7 +45,6 @@ serde = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } snowbridge-pallet-ethereum-client-fixtures = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/bridges/snowbridge/pallets/outbound-queue/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue/Cargo.toml index f4910e6e64571..e343d4c684ab9 100644 --- a/bridges/snowbridge/pallets/outbound-queue/Cargo.toml +++ b/bridges/snowbridge/pallets/outbound-queue/Cargo.toml @@ -36,7 +36,6 @@ snowbridge-outbound-queue-merkle-tree = { workspace = true } [dev-dependencies] pallet-message-queue = { workspace = true } -sp-keyring = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/bridges/snowbridge/pallets/system/Cargo.toml b/bridges/snowbridge/pallets/system/Cargo.toml index 3544925956b41..c695b1034f698 100644 --- a/bridges/snowbridge/pallets/system/Cargo.toml +++ b/bridges/snowbridge/pallets/system/Cargo.toml @@ -41,7 +41,6 @@ pallet-balances = { workspace = true, default-features = true } pallet-message-queue = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } snowbridge-pallet-outbound-queue = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/bridges/snowbridge/primitives/ethereum/Cargo.toml b/bridges/snowbridge/primitives/ethereum/Cargo.toml index 44ea2d0d222ba..5c249354a53a6 100644 --- a/bridges/snowbridge/primitives/ethereum/Cargo.toml +++ b/bridges/snowbridge/primitives/ethereum/Cargo.toml @@ -31,7 +31,6 @@ ethabi = { workspace = true } [dev-dependencies] rand = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } -wasm-bindgen-test = { workspace = true } [features] default = ["std"] diff --git a/cumulus/client/network/Cargo.toml b/cumulus/client/network/Cargo.toml index 11025f8f62e6d..3fb7eac591aae 100644 --- a/cumulus/client/network/Cargo.toml +++ b/cumulus/client/network/Cargo.toml @@ -39,7 +39,6 @@ polkadot-primitives = { workspace = true, default-features = true } cumulus-relay-chain-interface = { workspace = true, default-features = true } [dev-dependencies] -portpicker = { workspace = true } rstest = { workspace = true } tokio = { features = ["macros"], workspace = true, default-features = true } url = { workspace = true } @@ -51,7 +50,6 @@ sp-consensus = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } -substrate-test-utils = { workspace = true } # Polkadot polkadot-test-client = { workspace = true } diff --git a/cumulus/client/pov-recovery/Cargo.toml b/cumulus/client/pov-recovery/Cargo.toml index 7e7da7244a86e..7c85318bdde3c 100644 --- a/cumulus/client/pov-recovery/Cargo.toml +++ b/cumulus/client/pov-recovery/Cargo.toml @@ -41,7 +41,6 @@ cumulus-relay-chain-interface = { workspace = true, default-features = true } [dev-dependencies] assert_matches = { workspace = true } cumulus-test-client = { workspace = true } -portpicker = { workspace = true } rstest = { workspace = true } sc-utils = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } @@ -54,4 +53,3 @@ cumulus-test-service = { workspace = true } # Substrate sc-cli = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } -substrate-test-utils = { workspace = true } diff --git a/cumulus/client/relay-chain-inprocess-interface/Cargo.toml b/cumulus/client/relay-chain-inprocess-interface/Cargo.toml index 2a590bbca5628..1307ec76de85c 100644 --- a/cumulus/client/relay-chain-inprocess-interface/Cargo.toml +++ b/cumulus/client/relay-chain-inprocess-interface/Cargo.toml @@ -45,6 +45,3 @@ sp-keyring = { workspace = true, default-features = true } metered = { features = ["futures_channel"], workspace = true } polkadot-primitives = { workspace = true, default-features = true } polkadot-test-client = { workspace = true } - -# Cumulus -cumulus-test-service = { workspace = true } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml index d612dd03c247a..3da8aa9b6cfea 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml @@ -40,7 +40,6 @@ pallet-message-queue = { workspace = true } pallet-multisig = { workspace = true } pallet-nft-fractionalization = { workspace = true } pallet-nfts = { workspace = true } -pallet-nfts-runtime-api = { workspace = true } pallet-proxy = { workspace = true } pallet-session = { workspace = true } pallet-timestamp = { workspace = true } @@ -226,7 +225,6 @@ std = [ "pallet-message-queue/std", "pallet-multisig/std", "pallet-nft-fractionalization/std", - "pallet-nfts-runtime-api/std", "pallet-nfts/std", "pallet-proxy/std", "pallet-session/std", diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml index ace23e71c4d11..132e42deea4a0 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml @@ -50,7 +50,6 @@ bp-relayers = { workspace = true } bp-runtime = { workspace = true } bp-test-utils = { workspace = true } bp-xcm-bridge-hub = { workspace = true } -bridge-runtime-common = { workspace = true } pallet-bridge-grandpa = { workspace = true } pallet-bridge-messages = { features = ["test-helpers"], workspace = true } pallet-bridge-parachains = { workspace = true } @@ -69,7 +68,6 @@ std = [ "bp-runtime/std", "bp-test-utils/std", "bp-xcm-bridge-hub/std", - "bridge-runtime-common/std", "codec/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-xcmp-queue/std", diff --git a/cumulus/polkadot-parachain/Cargo.toml b/cumulus/polkadot-parachain/Cargo.toml index 9130f60ceb38b..6b578779997c0 100644 --- a/cumulus/polkadot-parachain/Cargo.toml +++ b/cumulus/polkadot-parachain/Cargo.toml @@ -29,7 +29,6 @@ asset-hub-westend-runtime = { workspace = true } bridge-hub-rococo-runtime = { workspace = true, default-features = true } bridge-hub-westend-runtime = { workspace = true, default-features = true } collectives-westend-runtime = { workspace = true } -contracts-rococo-runtime = { workspace = true } coretime-rococo-runtime = { workspace = true } coretime-westend-runtime = { workspace = true } glutton-westend-runtime = { workspace = true } @@ -70,7 +69,6 @@ runtime-benchmarks = [ "bridge-hub-rococo-runtime/runtime-benchmarks", "bridge-hub-westend-runtime/runtime-benchmarks", "collectives-westend-runtime/runtime-benchmarks", - "contracts-rococo-runtime/runtime-benchmarks", "coretime-rococo-runtime/runtime-benchmarks", "coretime-westend-runtime/runtime-benchmarks", "glutton-westend-runtime/runtime-benchmarks", @@ -88,7 +86,6 @@ try-runtime = [ "bridge-hub-rococo-runtime/try-runtime", "bridge-hub-westend-runtime/try-runtime", "collectives-westend-runtime/try-runtime", - "contracts-rococo-runtime/try-runtime", "coretime-rococo-runtime/try-runtime", "coretime-westend-runtime/try-runtime", "glutton-westend-runtime/try-runtime", diff --git a/cumulus/test/runtime/Cargo.toml b/cumulus/test/runtime/Cargo.toml index 4cc4f483c0287..71509f82bfe13 100644 --- a/cumulus/test/runtime/Cargo.toml +++ b/cumulus/test/runtime/Cargo.toml @@ -47,7 +47,6 @@ cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-weight-reclaim = { workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } -pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } [build-dependencies] @@ -69,7 +68,6 @@ std = [ "pallet-aura/std", "pallet-authorship/std", "pallet-balances/std", - "pallet-collator-selection/std", "pallet-glutton/std", "pallet-message-queue/std", "pallet-session/std", diff --git a/cumulus/test/service/Cargo.toml b/cumulus/test/service/Cargo.toml index 794007532621e..407c657bd14ef 100644 --- a/cumulus/test/service/Cargo.toml +++ b/cumulus/test/service/Cargo.toml @@ -76,7 +76,6 @@ cumulus-client-collator = { workspace = true, default-features = true } cumulus-client-consensus-aura = { workspace = true, default-features = true } cumulus-client-consensus-common = { workspace = true, default-features = true } cumulus-client-consensus-proposer = { workspace = true, default-features = true } -cumulus-client-consensus-relay-chain = { workspace = true, default-features = true } cumulus-client-parachain-inherent = { workspace = true, default-features = true } cumulus-client-pov-recovery = { workspace = true, default-features = true } cumulus-client-service = { workspace = true, default-features = true } @@ -89,12 +88,10 @@ cumulus-relay-chain-minimal-node = { workspace = true, default-features = true } cumulus-test-relay-sproof-builder = { workspace = true, default-features = true } cumulus-test-runtime = { workspace = true } pallet-timestamp = { workspace = true, default-features = true } -parachains-common = { workspace = true, default-features = true } [dev-dependencies] cumulus-test-client = { workspace = true } futures = { workspace = true } -portpicker = { workspace = true } sp-authority-discovery = { workspace = true, default-features = true } # Polkadot dependencies @@ -102,7 +99,6 @@ polkadot-test-service = { workspace = true } # Substrate dependencies sc-cli = { workspace = true, default-features = true } -substrate-test-utils = { workspace = true } [features] runtime-benchmarks = [ @@ -113,7 +109,6 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-transaction-payment/runtime-benchmarks", - "parachains-common/runtime-benchmarks", "polkadot-cli/runtime-benchmarks", "polkadot-primitives/runtime-benchmarks", "polkadot-service/runtime-benchmarks", diff --git a/cumulus/xcm/xcm-emulator/Cargo.toml b/cumulus/xcm/xcm-emulator/Cargo.toml index ae8cb79bb55e1..b6fbbe3e4ce4f 100644 --- a/cumulus/xcm/xcm-emulator/Cargo.toml +++ b/cumulus/xcm/xcm-emulator/Cargo.toml @@ -33,7 +33,6 @@ sp-tracing = { workspace = true, default-features = true } # Cumulus cumulus-pallet-parachain-system = { workspace = true, default-features = true } -cumulus-pallet-xcmp-queue = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true, default-features = true } cumulus-primitives-parachain-inherent = { workspace = true, default-features = true } cumulus-test-relay-sproof-builder = { workspace = true, default-features = true } diff --git a/polkadot/node/metrics/Cargo.toml b/polkadot/node/metrics/Cargo.toml index 318deca4f2438..8d15391b11c2a 100644 --- a/polkadot/node/metrics/Cargo.toml +++ b/polkadot/node/metrics/Cargo.toml @@ -29,7 +29,6 @@ prometheus-endpoint = { workspace = true, default-features = true } sc-tracing = { workspace = true, default-features = true } [dev-dependencies] -assert_cmd = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } hyper-util = { features = ["client-legacy", "tokio"], workspace = true } @@ -37,7 +36,6 @@ polkadot-test-service = { features = ["runtime-metrics"], workspace = true } prometheus-parse = { workspace = true } sc-service = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } -substrate-test-utils = { workspace = true } tempfile = { workspace = true } tokio = { workspace = true, default-features = true } diff --git a/polkadot/node/test/service/Cargo.toml b/polkadot/node/test/service/Cargo.toml index 54db2a0ac9425..96bbdd2e7bdef 100644 --- a/polkadot/node/test/service/Cargo.toml +++ b/polkadot/node/test/service/Cargo.toml @@ -62,7 +62,6 @@ substrate-test-client = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true } -substrate-test-utils = { workspace = true } tokio = { features = ["macros"], workspace = true, default-features = true } [features] diff --git a/polkadot/parachain/test-parachains/adder/collator/Cargo.toml b/polkadot/parachain/test-parachains/adder/collator/Cargo.toml index 20305dc07c3a5..301a0d10ba851 100644 --- a/polkadot/parachain/test-parachains/adder/collator/Cargo.toml +++ b/polkadot/parachain/test-parachains/adder/collator/Cargo.toml @@ -39,6 +39,5 @@ polkadot-test-service = { workspace = true } sc-service = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } -substrate-test-utils = { workspace = true } tokio = { features = ["macros"], workspace = true, default-features = true } diff --git a/polkadot/parachain/test-parachains/undying/collator/Cargo.toml b/polkadot/parachain/test-parachains/undying/collator/Cargo.toml index b964b4dc49ce5..f4e6d4e585427 100644 --- a/polkadot/parachain/test-parachains/undying/collator/Cargo.toml +++ b/polkadot/parachain/test-parachains/undying/collator/Cargo.toml @@ -39,6 +39,5 @@ polkadot-test-service = { workspace = true } sc-service = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } -substrate-test-utils = { workspace = true } tokio = { features = ["macros"], workspace = true, default-features = true } diff --git a/polkadot/runtime/parachains/Cargo.toml b/polkadot/runtime/parachains/Cargo.toml index 7c00995d2291e..6c87f7773c235 100644 --- a/polkadot/runtime/parachains/Cargo.toml +++ b/polkadot/runtime/parachains/Cargo.toml @@ -47,7 +47,6 @@ pallet-mmr = { workspace = true, optional = true } pallet-session = { workspace = true } pallet-staking = { workspace = true } pallet-timestamp = { workspace = true } -pallet-vesting = { workspace = true } polkadot-primitives = { workspace = true } xcm = { workspace = true } @@ -96,7 +95,6 @@ std = [ "pallet-session/std", "pallet-staking/std", "pallet-timestamp/std", - "pallet-vesting/std", "polkadot-core-primitives/std", "polkadot-parachain-primitives/std", "polkadot-primitives/std", @@ -131,7 +129,6 @@ runtime-benchmarks = [ "pallet-mmr/runtime-benchmarks", "pallet-staking/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", - "pallet-vesting/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-primitives/runtime-benchmarks", "sp-application-crypto", @@ -156,7 +153,6 @@ try-runtime = [ "pallet-session/try-runtime", "pallet-staking/try-runtime", "pallet-timestamp/try-runtime", - "pallet-vesting/try-runtime", "sp-runtime/try-runtime", ] runtime-metrics = [ diff --git a/polkadot/runtime/rococo/Cargo.toml b/polkadot/runtime/rococo/Cargo.toml index e7f463566e3a6..67c7cacc296b9 100644 --- a/polkadot/runtime/rococo/Cargo.toml +++ b/polkadot/runtime/rococo/Cargo.toml @@ -59,14 +59,12 @@ pallet-beefy = { workspace = true } pallet-beefy-mmr = { workspace = true } pallet-bounties = { workspace = true } pallet-child-bounties = { workspace = true } -pallet-collective = { workspace = true } pallet-conviction-voting = { workspace = true } pallet-democracy = { workspace = true } pallet-elections-phragmen = { workspace = true } pallet-grandpa = { workspace = true } pallet-identity = { workspace = true } pallet-indices = { workspace = true } -pallet-membership = { workspace = true } pallet-message-queue = { workspace = true } pallet-migrations = { workspace = true } pallet-mmr = { workspace = true } @@ -115,12 +113,10 @@ xcm-runtime-apis = { workspace = true } [dev-dependencies] remote-externalities = { workspace = true, default-features = true } -separator = { workspace = true } serde_json = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } sp-tracing = { workspace = true } sp-trie = { workspace = true, default-features = true } -tiny-keccak = { features = ["keccak"], workspace = true } tokio = { features = ["macros"], workspace = true, default-features = true } [build-dependencies] @@ -151,14 +147,12 @@ std = [ "pallet-beefy/std", "pallet-bounties/std", "pallet-child-bounties/std", - "pallet-collective/std", "pallet-conviction-voting/std", "pallet-democracy/std", "pallet-elections-phragmen/std", "pallet-grandpa/std", "pallet-identity/std", "pallet-indices/std", - "pallet-membership/std", "pallet-message-queue/std", "pallet-migrations/std", "pallet-mmr/std", @@ -234,14 +228,12 @@ runtime-benchmarks = [ "pallet-beefy-mmr/runtime-benchmarks", "pallet-bounties/runtime-benchmarks", "pallet-child-bounties/runtime-benchmarks", - "pallet-collective/runtime-benchmarks", "pallet-conviction-voting/runtime-benchmarks", "pallet-democracy/runtime-benchmarks", "pallet-elections-phragmen/runtime-benchmarks", "pallet-grandpa/runtime-benchmarks", "pallet-identity/runtime-benchmarks", "pallet-indices/runtime-benchmarks", - "pallet-membership/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", "pallet-migrations/runtime-benchmarks", "pallet-mmr/runtime-benchmarks", @@ -294,14 +286,12 @@ try-runtime = [ "pallet-beefy/try-runtime", "pallet-bounties/try-runtime", "pallet-child-bounties/try-runtime", - "pallet-collective/try-runtime", "pallet-conviction-voting/try-runtime", "pallet-democracy/try-runtime", "pallet-elections-phragmen/try-runtime", "pallet-grandpa/try-runtime", "pallet-identity/try-runtime", "pallet-indices/try-runtime", - "pallet-membership/try-runtime", "pallet-message-queue/try-runtime", "pallet-migrations/try-runtime", "pallet-mmr/try-runtime", diff --git a/polkadot/runtime/test-runtime/Cargo.toml b/polkadot/runtime/test-runtime/Cargo.toml index f35bb53ac9049..cd5507decd5d1 100644 --- a/polkadot/runtime/test-runtime/Cargo.toml +++ b/polkadot/runtime/test-runtime/Cargo.toml @@ -68,7 +68,6 @@ hex-literal = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } sp-trie = { workspace = true, default-features = true } -tiny-keccak = { features = ["keccak"], workspace = true } [build-dependencies] substrate-wasm-builder = { workspace = true, default-features = true } diff --git a/polkadot/runtime/westend/Cargo.toml b/polkadot/runtime/westend/Cargo.toml index e945e64e7fc07..3317484419a9a 100644 --- a/polkadot/runtime/westend/Cargo.toml +++ b/polkadot/runtime/westend/Cargo.toml @@ -60,10 +60,8 @@ pallet-bags-list = { workspace = true } pallet-balances = { workspace = true } pallet-beefy = { workspace = true } pallet-beefy-mmr = { workspace = true } -pallet-collective = { workspace = true } pallet-conviction-voting = { workspace = true } pallet-delegated-staking = { workspace = true } -pallet-democracy = { workspace = true } pallet-election-provider-multi-phase = { workspace = true } pallet-elections-phragmen = { workspace = true } pallet-fast-unstake = { workspace = true } @@ -159,10 +157,8 @@ std = [ "pallet-balances/std", "pallet-beefy-mmr/std", "pallet-beefy/std", - "pallet-collective/std", "pallet-conviction-voting/std", "pallet-delegated-staking/std", - "pallet-democracy/std", "pallet-election-provider-multi-phase/std", "pallet-election-provider-support-benchmarking?/std", "pallet-elections-phragmen/std", @@ -250,10 +246,8 @@ runtime-benchmarks = [ "pallet-bags-list/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-beefy-mmr/runtime-benchmarks", - "pallet-collective/runtime-benchmarks", "pallet-conviction-voting/runtime-benchmarks", "pallet-delegated-staking/runtime-benchmarks", - "pallet-democracy/runtime-benchmarks", "pallet-election-provider-multi-phase/runtime-benchmarks", "pallet-election-provider-support-benchmarking/runtime-benchmarks", "pallet-elections-phragmen/runtime-benchmarks", @@ -315,10 +309,8 @@ try-runtime = [ "pallet-balances/try-runtime", "pallet-beefy-mmr/try-runtime", "pallet-beefy/try-runtime", - "pallet-collective/try-runtime", "pallet-conviction-voting/try-runtime", "pallet-delegated-staking/try-runtime", - "pallet-democracy/try-runtime", "pallet-election-provider-multi-phase/try-runtime", "pallet-elections-phragmen/try-runtime", "pallet-fast-unstake/try-runtime", diff --git a/polkadot/xcm/xcm-builder/Cargo.toml b/polkadot/xcm/xcm-builder/Cargo.toml index f75c984c068ea..5169f586d7237 100644 --- a/polkadot/xcm/xcm-builder/Cargo.toml +++ b/polkadot/xcm/xcm-builder/Cargo.toml @@ -31,7 +31,6 @@ xcm-executor = { workspace = true } polkadot-parachain-primitives = { workspace = true } [dev-dependencies] -assert_matches = { workspace = true } pallet-assets = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } pallet-salary = { workspace = true, default-features = true } diff --git a/substrate/bin/node/cli/Cargo.toml b/substrate/bin/node/cli/Cargo.toml index 9e063ee3cde0f..7b355074823c3 100644 --- a/substrate/bin/node/cli/Cargo.toml +++ b/substrate/bin/node/cli/Cargo.toml @@ -144,7 +144,6 @@ assert_cmd = { workspace = true } criterion = { features = ["async_tokio"], workspace = true, default-features = true } futures = { workspace = true } nix = { features = ["signal"], workspace = true } -platforms = { workspace = true } pretty_assertions.workspace = true regex = { workspace = true } scale-info = { features = ["derive", "serde"], workspace = true, default-features = true } diff --git a/substrate/client/api/Cargo.toml b/substrate/client/api/Cargo.toml index fe961b4690fc6..dede50fc01e8c 100644 --- a/substrate/client/api/Cargo.toml +++ b/substrate/client/api/Cargo.toml @@ -41,6 +41,5 @@ sp-storage = { workspace = true, default-features = true } sp-trie = { workspace = true, default-features = true } [dev-dependencies] -sp-test-primitives = { workspace = true } substrate-test-runtime = { workspace = true } thiserror = { workspace = true } diff --git a/substrate/client/db/Cargo.toml b/substrate/client/db/Cargo.toml index 7e02558e007c8..9268ccf8a0645 100644 --- a/substrate/client/db/Cargo.toml +++ b/substrate/client/db/Cargo.toml @@ -43,7 +43,6 @@ array-bytes = { workspace = true, default-features = true } criterion = { workspace = true, default-features = true } kitchensink-runtime = { workspace = true } kvdb-rocksdb = { workspace = true } -quickcheck = { workspace = true } rand = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } diff --git a/substrate/client/network/common/Cargo.toml b/substrate/client/network/common/Cargo.toml index cd1bc1cfe8eb8..30407423da29a 100644 --- a/substrate/client/network/common/Cargo.toml +++ b/substrate/client/network/common/Cargo.toml @@ -19,17 +19,11 @@ targets = ["x86_64-unknown-linux-gnu"] prost-build = { workspace = true } [dependencies] -async-trait = { workspace = true } bitflags = { workspace = true } codec = { features = [ "derive", ], workspace = true, default-features = true } futures = { workspace = true } -libp2p-identity = { features = ["peerid"], workspace = true } -sc-consensus = { workspace = true, default-features = true } -sc-network-types = { workspace = true, default-features = true } -sp-consensus = { workspace = true, default-features = true } -sp-consensus-grandpa = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } [dev-dependencies] diff --git a/substrate/client/telemetry/Cargo.toml b/substrate/client/telemetry/Cargo.toml index 4a41a6b6deca9..1ebff618e0cef 100644 --- a/substrate/client/telemetry/Cargo.toml +++ b/substrate/client/telemetry/Cargo.toml @@ -24,7 +24,6 @@ log = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } pin-project = { workspace = true } rand = { workspace = true, default-features = true } -sc-network = { workspace = true, default-features = true } sc-utils = { workspace = true, default-features = true } serde = { features = ["derive"], workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } diff --git a/substrate/frame/contracts/Cargo.toml b/substrate/frame/contracts/Cargo.toml index 5784e6dd15533..88404803fe0f3 100644 --- a/substrate/frame/contracts/Cargo.toml +++ b/substrate/frame/contracts/Cargo.toml @@ -65,10 +65,8 @@ wat = { workspace = true } xcm-builder = { workspace = true, default-features = true } # Substrate Dependencies -pallet-assets = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } pallet-insecure-randomness-collective-flip = { workspace = true, default-features = true } -pallet-message-queue = { workspace = true, default-features = true } pallet-proxy = { workspace = true, default-features = true } pallet-timestamp = { workspace = true, default-features = true } pallet-utility = { workspace = true, default-features = true } @@ -106,9 +104,7 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", - "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", - "pallet-message-queue/runtime-benchmarks", "pallet-proxy/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-utility/runtime-benchmarks", @@ -122,10 +118,8 @@ runtime-benchmarks = [ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", - "pallet-assets/try-runtime", "pallet-balances/try-runtime", "pallet-insecure-randomness-collective-flip/try-runtime", - "pallet-message-queue/try-runtime", "pallet-proxy/try-runtime", "pallet-timestamp/try-runtime", "pallet-utility/try-runtime", diff --git a/substrate/frame/contracts/mock-network/Cargo.toml b/substrate/frame/contracts/mock-network/Cargo.toml index a7423b33abc17..84aa95694b509 100644 --- a/substrate/frame/contracts/mock-network/Cargo.toml +++ b/substrate/frame/contracts/mock-network/Cargo.toml @@ -21,11 +21,8 @@ pallet-balances = { workspace = true, default-features = true } pallet-contracts = { workspace = true, default-features = true } pallet-contracts-proc-macro = { workspace = true, default-features = true } pallet-contracts-uapi = { workspace = true } -pallet-insecure-randomness-collective-flip = { workspace = true, default-features = true } pallet-message-queue = { workspace = true, default-features = true } -pallet-proxy = { workspace = true, default-features = true } pallet-timestamp = { workspace = true, default-features = true } -pallet-utility = { workspace = true, default-features = true } pallet-xcm = { workspace = true } polkadot-parachain-primitives = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } @@ -43,9 +40,7 @@ xcm-executor = { workspace = true } xcm-simulator = { workspace = true, default-features = true } [dev-dependencies] -assert_matches = { workspace = true } pallet-contracts-fixtures = { workspace = true } -pretty_assertions = { workspace = true } [features] default = ["std"] @@ -55,10 +50,7 @@ std = [ "frame-system/std", "pallet-balances/std", "pallet-contracts/std", - "pallet-insecure-randomness-collective-flip/std", - "pallet-proxy/std", "pallet-timestamp/std", - "pallet-utility/std", "pallet-xcm/std", "scale-info/std", "sp-api/std", @@ -77,9 +69,7 @@ runtime-benchmarks = [ "pallet-balances/runtime-benchmarks", "pallet-contracts/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", - "pallet-proxy/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", - "pallet-utility/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-primitives/runtime-benchmarks", diff --git a/substrate/frame/conviction-voting/Cargo.toml b/substrate/frame/conviction-voting/Cargo.toml index 2d23f493ea013..e2d483609769d 100644 --- a/substrate/frame/conviction-voting/Cargo.toml +++ b/substrate/frame/conviction-voting/Cargo.toml @@ -31,7 +31,6 @@ sp-runtime = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true, default-features = true } -pallet-scheduler = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } [features] @@ -42,7 +41,6 @@ std = [ "frame-support/std", "frame-system/std", "pallet-balances/std", - "pallet-scheduler/std", "scale-info/std", "serde", "sp-core/std", @@ -54,13 +52,11 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "pallet-balances/runtime-benchmarks", - "pallet-scheduler/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", "pallet-balances/try-runtime", - "pallet-scheduler/try-runtime", "sp-runtime/try-runtime", ] diff --git a/substrate/frame/delegated-staking/Cargo.toml b/substrate/frame/delegated-staking/Cargo.toml index 576276dced521..3a2498fb99128 100644 --- a/substrate/frame/delegated-staking/Cargo.toml +++ b/substrate/frame/delegated-staking/Cargo.toml @@ -31,7 +31,6 @@ pallet-timestamp = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } -substrate-test-utils = { workspace = true } [features] default = ["std"] diff --git a/substrate/frame/fast-unstake/Cargo.toml b/substrate/frame/fast-unstake/Cargo.toml index 98a9655074e7c..209406dc3f99a 100644 --- a/substrate/frame/fast-unstake/Cargo.toml +++ b/substrate/frame/fast-unstake/Cargo.toml @@ -38,7 +38,6 @@ pallet-staking-reward-curve = { workspace = true, default-features = true } pallet-timestamp = { workspace = true, default-features = true } sp-core = { workspace = true } sp-tracing = { workspace = true, default-features = true } -substrate-test-utils = { workspace = true } [features] default = ["std"] diff --git a/substrate/frame/glutton/Cargo.toml b/substrate/frame/glutton/Cargo.toml index 317a9ea8b7604..7f7b24c12117b 100644 --- a/substrate/frame/glutton/Cargo.toml +++ b/substrate/frame/glutton/Cargo.toml @@ -28,9 +28,6 @@ sp-inherents = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -[dev-dependencies] -pallet-balances = { workspace = true, default-features = true } - [features] default = ["std"] std = [ @@ -40,7 +37,6 @@ std = [ "frame-support/std", "frame-system/std", "log/std", - "pallet-balances/std", "scale-info/std", "sp-core/std", "sp-inherents/std", @@ -50,7 +46,6 @@ std = [ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", - "pallet-balances/try-runtime", "sp-runtime/try-runtime", ] @@ -58,6 +53,5 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", - "pallet-balances/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] diff --git a/substrate/frame/indices/Cargo.toml b/substrate/frame/indices/Cargo.toml index a0030b5b0edf8..fdc1753e44fcb 100644 --- a/substrate/frame/indices/Cargo.toml +++ b/substrate/frame/indices/Cargo.toml @@ -23,7 +23,6 @@ frame-system = { workspace = true } scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } -sp-keyring = { optional = true, workspace = true } sp-runtime = { workspace = true } [dev-dependencies] @@ -40,8 +39,6 @@ std = [ "scale-info/std", "sp-core/std", "sp-io/std", - "sp-keyring", - "sp-keyring?/std", "sp-runtime/std", ] runtime-benchmarks = [ diff --git a/substrate/frame/nfts/runtime-api/Cargo.toml b/substrate/frame/nfts/runtime-api/Cargo.toml index 4d004875468db..36f85fbf61128 100644 --- a/substrate/frame/nfts/runtime-api/Cargo.toml +++ b/substrate/frame/nfts/runtime-api/Cargo.toml @@ -17,9 +17,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -pallet-nfts = { workspace = true } sp-api = { workspace = true } [features] default = ["std"] -std = ["codec/std", "pallet-nfts/std", "sp-api/std"] +std = ["codec/std", "sp-api/std"] diff --git a/substrate/frame/offences/Cargo.toml b/substrate/frame/offences/Cargo.toml index 4dd9d7f10c9f2..221a4918a511f 100644 --- a/substrate/frame/offences/Cargo.toml +++ b/substrate/frame/offences/Cargo.toml @@ -20,7 +20,6 @@ codec = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } log = { workspace = true } -pallet-balances = { workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { optional = true, workspace = true, default-features = true } sp-runtime = { workspace = true } @@ -37,7 +36,6 @@ std = [ "frame-support/std", "frame-system/std", "log/std", - "pallet-balances/std", "scale-info/std", "serde", "sp-core/std", @@ -48,13 +46,11 @@ std = [ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", - "pallet-balances/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "sp-staking/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", - "pallet-balances/try-runtime", "sp-runtime/try-runtime", ] diff --git a/substrate/frame/paged-list/Cargo.toml b/substrate/frame/paged-list/Cargo.toml index da029bdd7423f..07755c351e288 100644 --- a/substrate/frame/paged-list/Cargo.toml +++ b/substrate/frame/paged-list/Cargo.toml @@ -19,7 +19,6 @@ codec = { features = ["derive"], workspace = true } docify = { workspace = true } scale-info = { features = ["derive"], workspace = true } -frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } @@ -33,7 +32,6 @@ default = ["std"] std = [ "codec/std", - "frame-benchmarking?/std", "frame-support/std", "frame-system/std", "scale-info/std", @@ -44,7 +42,6 @@ std = [ ] runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", diff --git a/substrate/frame/verify-signature/Cargo.toml b/substrate/frame/verify-signature/Cargo.toml index 37cc6c0b30659..453424bbec7a8 100644 --- a/substrate/frame/verify-signature/Cargo.toml +++ b/substrate/frame/verify-signature/Cargo.toml @@ -27,10 +27,6 @@ sp-runtime = { workspace = true } sp-weights = { features = ["serde"], workspace = true } [dev-dependencies] -pallet-balances = { workspace = true, default-features = true } -pallet-collective = { workspace = true, default-features = true } -pallet-root-testing = { workspace = true, default-features = true } -pallet-timestamp = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } [features] @@ -40,10 +36,6 @@ std = [ "frame-benchmarking?/std", "frame-support/std", "frame-system/std", - "pallet-balances/std", - "pallet-collective/std", - "pallet-root-testing/std", - "pallet-timestamp/std", "scale-info/std", "sp-core/std", "sp-io/std", @@ -54,17 +46,10 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", - "pallet-balances/runtime-benchmarks", - "pallet-collective/runtime-benchmarks", - "pallet-timestamp/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", - "pallet-balances/try-runtime", - "pallet-collective/try-runtime", - "pallet-root-testing/try-runtime", - "pallet-timestamp/try-runtime", "sp-runtime/try-runtime", ] diff --git a/substrate/test-utils/Cargo.toml b/substrate/test-utils/Cargo.toml index 87c9cb731e3a4..75eab46cb217a 100644 --- a/substrate/test-utils/Cargo.toml +++ b/substrate/test-utils/Cargo.toml @@ -14,11 +14,3 @@ workspace = true [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -futures = { workspace = true } -tokio = { features = ["macros", "time"], workspace = true, default-features = true } - -[dev-dependencies] -sc-service = { workspace = true, default-features = true } -trybuild = { features = ["diff"], workspace = true } diff --git a/substrate/test-utils/client/Cargo.toml b/substrate/test-utils/client/Cargo.toml index e7ab4c8c8367d..b0709f4e244d9 100644 --- a/substrate/test-utils/client/Cargo.toml +++ b/substrate/test-utils/client/Cargo.toml @@ -26,7 +26,6 @@ sc-client-db = { features = [ ], workspace = true } sc-consensus = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } -sc-offchain = { workspace = true, default-features = true } sc-service = { workspace = true } serde = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } From b9aa8817cf93847caf36a450df1c654ca04933ef Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 27 Jan 2025 10:41:29 +0000 Subject: [PATCH 151/169] stateless data provider so that EPM and EPMB can work together --- substrate/bin/node/runtime/src/lib.rs | 28 +++++- .../election-provider-multi-block/src/lib.rs | 4 +- .../election-provider-multi-phase/src/lib.rs | 5 +- .../election-provider-support/src/lib.rs | 16 ++++ .../election-provider-support/src/onchain.rs | 5 +- substrate/frame/staking/src/benchmarking.rs | 6 +- substrate/frame/staking/src/pallet/impls.rs | 93 ++++++++++--------- substrate/frame/staking/src/tests.rs | 4 +- 8 files changed, 103 insertions(+), 58 deletions(-) diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 8f9c2d2e2957e..efb7cabdfd091 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -831,6 +831,31 @@ impl pallet_staking::BenchmarkingConfig for StakingBenchmarkingConfig { type MaxValidators = ConstU32<1000>; } +use frame_election_provider_support::{BoundedSupportsOf, ElectionProvider, PageIndex}; +pub struct MultiElectionProvider; +impl ElectionProvider for MultiElectionProvider { + type AccountId = ::AccountId; + type BlockNumber = ::BlockNumber; + type DataProvider = ::DataProvider; + type Error = ::Error; + type Pages = ::Pages; + type MaxBackersPerWinner = ::MaxBackersPerWinner; + type MaxWinnersPerPage = ::MaxWinnersPerPage; + + fn elect(page: PageIndex) -> Result, Self::Error> { + if page == 0 { + // TODO: later on, we can even compare the results of the multi-page and multi-block + // election like this. + let _ = ElectionProviderMultiPhase::elect(page); + } + MultiBlock::elect(page) + } + + fn ongoing() -> bool { + MultiBlock::ongoing() + } +} + impl pallet_staking::Config for Runtime { type OldCurrency = Balances; type Currency = Balances; @@ -855,8 +880,7 @@ impl pallet_staking::Config for Runtime { type NextNewSession = Session; type MaxExposurePageSize = MaxExposurePageSize; type MaxValidatorSet = MaxActiveValidators; - type ElectionProvider = MultiBlock; - // type ElectionProvider = ElectionProviderMultiPhase; + type ElectionProvider = MultiElectionProvider; type GenesisElectionProvider = onchain::OnChainExecution; type VoterList = VoterList; type NominationsQuota = pallet_staking::FixedNominationsQuota; diff --git a/substrate/frame/election-provider-multi-block/src/lib.rs b/substrate/frame/election-provider-multi-block/src/lib.rs index 6ba447726da67..282c45b76c38b 100644 --- a/substrate/frame/election-provider-multi-block/src/lib.rs +++ b/substrate/frame/election-provider-multi-block/src/lib.rs @@ -60,6 +60,8 @@ //! 3. signed //! 4. unsigned //! +//! This is critical for the phase transition to work. +//! //! This should be manually checked, there is not automated way to test it. //! //! ## Pagination @@ -552,8 +554,6 @@ pub mod pallet { Self::phase_transition(Phase::SignedValidation(now)); // we don't do anything else here. We expect the signed sub-pallet to handle // whatever else needs to be done. - // TODO: this notification system based on block numbers is 100% based on the - // on_initialize of the parent pallet is called before the rest of them. todo_weight }, diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index 0f4a593419697..09bb54221b573 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -768,7 +768,6 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { fn on_initialize(now: BlockNumberFor) -> Weight { - return Default::default(); let next_election = T::DataProvider::next_election_prediction(now).max(now); let signed_deadline = T::SignedPhase::get() + T::UnsignedPhase::get(); @@ -1525,7 +1524,7 @@ impl Pallet { fn create_snapshot_external( ) -> Result<(Vec, Vec>, u32), ElectionError> { let election_bounds = T::ElectionBounds::get(); - let targets = T::DataProvider::electable_targets(election_bounds.targets, SINGLE_PAGE) + let targets = T::DataProvider::electable_targets_stateless(election_bounds.targets) .and_then(|t| { election_bounds.ensure_targets_limits( CountBound(t.len() as u32), @@ -1535,7 +1534,7 @@ impl Pallet { }) .map_err(ElectionError::DataProvider)?; - let voters = T::DataProvider::electing_voters(election_bounds.voters, SINGLE_PAGE) + let voters = T::DataProvider::electing_voters_stateless(election_bounds.voters) .and_then(|v| { election_bounds.ensure_voters_limits( CountBound(v.len() as u32), diff --git a/substrate/frame/election-provider-support/src/lib.rs b/substrate/frame/election-provider-support/src/lib.rs index e9055d456c458..234314d21d3b0 100644 --- a/substrate/frame/election-provider-support/src/lib.rs +++ b/substrate/frame/election-provider-support/src/lib.rs @@ -330,6 +330,15 @@ pub trait ElectionDataProvider { page: PageIndex, ) -> data_provider::Result>; + /// A state-less version of [`Self::electing_targets`]. + /// + /// An election-provider that only uses 1 page should use this. + fn electable_targets_stateless( + bounds: DataProviderBounds, + ) -> data_provider::Result> { + Self::electable_targets(bounds, 0) + } + /// All the voters that participate in the election associated with page `page`, thus /// "electing". /// @@ -342,6 +351,13 @@ pub trait ElectionDataProvider { page: PageIndex, ) -> data_provider::Result>>; + /// A state-less version of [`Self::electing_voters`]. + fn electing_voters_stateless( + bounds: DataProviderBounds, + ) -> data_provider::Result>> { + Self::electing_voters(bounds, 0) + } + /// The number of targets to elect. /// /// This should be implemented as a self-weighing function. The implementor should register its diff --git a/substrate/frame/election-provider-support/src/onchain.rs b/substrate/frame/election-provider-support/src/onchain.rs index 65f5bc05cf6e3..3fe8f3b4bc3e9 100644 --- a/substrate/frame/election-provider-support/src/onchain.rs +++ b/substrate/frame/election-provider-support/src/onchain.rs @@ -184,8 +184,9 @@ impl ElectionProvider for OnChainExecution { type Error = Error; type MaxWinnersPerPage = T::MaxWinnersPerPage; type MaxBackersPerWinner = T::MaxBackersPerWinner; - // can support any number of pages, as this is meant to be called "instantly". - type Pages = sp_core::ConstU32<{ u32::MAX }>; + // can support any number of pages, as this is meant to be called "instantly". We don't care + // about this value here. + type Pages = sp_core::ConstU32<1>; type DataProvider = T::DataProvider; fn elect(page: PageIndex) -> Result, Self::Error> { diff --git a/substrate/frame/staking/src/benchmarking.rs b/substrate/frame/staking/src/benchmarking.rs index 984c4aadcd7d2..4538556f56c71 100644 --- a/substrate/frame/staking/src/benchmarking.rs +++ b/substrate/frame/staking/src/benchmarking.rs @@ -42,7 +42,6 @@ use frame_system::RawOrigin; const SEED: u32 = 0; const MAX_SPANS: u32 = 100; const MAX_SLASHES: u32 = 1000; -const SINGLE_PAGE: u32 = 0; type MaxValidators = <::BenchmarkingConfig as BenchmarkingConfig>::MaxValidators; type MaxNominators = <::BenchmarkingConfig as BenchmarkingConfig>::MaxNominators; @@ -1031,7 +1030,10 @@ mod benchmarks { let voters; #[block] { - voters = >::get_npos_voters(DataProviderBounds::default(), SINGLE_PAGE); + voters = >::get_npos_voters( + DataProviderBounds::default(), + &SnapshotStatus::::Waiting, + ); } assert_eq!(voters.len(), num_voters); diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index 963363623349f..a08f15b47b7aa 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -1038,7 +1038,7 @@ impl Pallet { /// This function is self-weighing as [`DispatchClass::Mandatory`]. pub(crate) fn get_npos_voters( bounds: DataProviderBounds, - page: PageIndex, + status: &SnapshotStatus, ) -> Vec> { let mut voters_size_tracker: StaticTracker = StaticTracker::default(); @@ -1057,7 +1057,7 @@ impl Pallet { let mut nominators_taken = 0u32; let mut min_active_stake = u64::MAX; - let mut sorted_voters = match VoterSnapshotStatus::::get() { + let mut sorted_voters = match status { // start the snapshot processing from the beginning. SnapshotStatus::Waiting => T::VoterList::iter(), // snapshot continues, start from the last iterated voter in the list. @@ -1140,29 +1140,6 @@ impl Pallet { } } - // update the voter snapshot status. - VoterSnapshotStatus::::mutate(|status| { - match (page, status.clone()) { - // last page, reset status for next round. - (0, _) => *status = SnapshotStatus::Waiting, - - (_, SnapshotStatus::Waiting) | (_, SnapshotStatus::Ongoing(_)) => { - let maybe_last = all_voters.last().map(|(x, _, _)| x).cloned(); - - if let Some(ref last) = maybe_last { - if maybe_last == T::VoterList::iter().last() { - // all voters in the voter list have been consumed. - *status = SnapshotStatus::Consumed; - } else { - *status = SnapshotStatus::Ongoing(last.clone()); - } - } - }, - // do nothing. - (_, SnapshotStatus::Consumed) => (), - } - }); - // all_voters should have not re-allocated. debug_assert!(all_voters.capacity() == page_len_prediction as usize); @@ -1173,17 +1150,6 @@ impl Pallet { MinimumActiveStake::::put(min_active_stake); - log!( - info, - "[page {}, status {:?}, bounds {:?}] generated {} npos voters, {} from validators and {} nominators", - page, - VoterSnapshotStatus::::get(), - bounds, - all_voters.len(), - validators_taken, - nominators_taken - ); - all_voters } @@ -1463,13 +1429,58 @@ impl ElectionDataProvider for Pallet { bounds: DataProviderBounds, page: PageIndex, ) -> data_provider::Result>> { - let voters = Self::get_npos_voters(bounds, page); + let mut status = VoterSnapshotStatus::::get(); + let voters = Self::get_npos_voters(bounds, &status); + + // update the voter snapshot status. + match (page, &status) { + // last page, reset status for next round. + (0, _) => status = SnapshotStatus::Waiting, + + (_, SnapshotStatus::Waiting) | (_, SnapshotStatus::Ongoing(_)) => { + let maybe_last = voters.last().map(|(x, _, _)| x).cloned(); + + if let Some(ref last) = maybe_last { + if maybe_last == T::VoterList::iter().last() { + // all voters in the voter list have been consumed. + status = SnapshotStatus::Consumed; + } else { + status = SnapshotStatus::Ongoing(last.clone()); + } + } + }, + // do nothing. + (_, SnapshotStatus::Consumed) => (), + } + log!( + info, + "[page {}, status {:?}, bounds {:?}] generated {} npos voters", + page, + VoterSnapshotStatus::::get(), + bounds, + voters.len(), + ); + VoterSnapshotStatus::::put(status); debug_assert!(!bounds.slice_exhausted(&voters)); Ok(voters) } + fn electing_voters_stateless( + bounds: DataProviderBounds, + ) -> data_provider::Result>> { + let voters = Self::get_npos_voters(bounds, &SnapshotStatus::Waiting); + log!( + info, + "[stateless, status {:?}, bounds {:?}] generated {} npos voters", + VoterSnapshotStatus::::get(), + bounds, + voters.len(), + ); + Ok(voters) + } + fn electable_targets( bounds: DataProviderBounds, page: PageIndex, @@ -2295,7 +2306,6 @@ impl Pallet { /// /// - `NextElectionPage`: should only be set if pages > 1 and if we are within `pages-election /// -> election` - /// - `ElectableStashes`: should only be set in `pages-election -> election block` /// - `VoterSnapshotStatus`: cannot be argued about as we don't know when we get a call to data /// provider, but we know it should never be set if we have 1 page. /// @@ -2304,13 +2314,6 @@ impl Pallet { let next_election = Self::next_election_prediction(now); let pages = Self::election_pages().saturated_into::>(); let election_prep_start = next_election - pages; - let is_mid_election = now >= election_prep_start && now < next_election; - if !is_mid_election { - ensure!( - ElectableStashes::::get().is_empty(), - "ElectableStashes should not be empty mid election" - ); - } if pages > One::one() && now >= election_prep_start { ensure!( diff --git a/substrate/frame/staking/src/tests.rs b/substrate/frame/staking/src/tests.rs index 200d4e2fd8dde..152d623330e29 100644 --- a/substrate/frame/staking/src/tests.rs +++ b/substrate/frame/staking/src/tests.rs @@ -5625,7 +5625,7 @@ mod election_data_provider { fn estimate_next_election_single_page_works() { ExtBuilder::default().session_per_era(5).period(5).build_and_execute(|| { // first session is always length 0. - for b in 1..20 { + for b in 1..19 { run_to_block(b); assert_eq!(Staking::next_election_prediction(System::block_number()), 20); } @@ -5635,7 +5635,7 @@ mod election_data_provider { assert_eq!(Staking::next_election_prediction(System::block_number()), 45); assert_eq!(*staking_events().last().unwrap(), Event::StakersElected); - for b in 21..45 { + for b in 21..44 { run_to_block(b); assert_eq!(Staking::next_election_prediction(System::block_number()), 45); } From ee30ec723ee22e247014217e48513a2e7690c953 Mon Sep 17 00:00:00 2001 From: Dmitry Markin Date: Mon, 27 Jan 2025 14:29:49 +0200 Subject: [PATCH 152/169] [sync] Let new subscribers know about already connected peers (backward-compatible) (#7344) Revert https://github.com/paritytech/polkadot-sdk/pull/7011 and replace it with a backward-compatible solution suitable for backporting to a release branch. ### Review notes It's easier to review this PR per commit: the first commit is just a revert, so it's enough to review only the second one, which is almost a one-liner. --------- Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- prdoc/pr_7344.prdoc | 14 +++++++++++ substrate/client/network-gossip/src/bridge.rs | 10 ++++---- substrate/client/network-gossip/src/lib.rs | 13 ++++------- substrate/client/network/statement/src/lib.rs | 23 +++++-------------- substrate/client/network/sync/src/engine.rs | 6 +++-- substrate/client/network/sync/src/types.rs | 4 ---- .../client/network/transactions/src/lib.rs | 23 +++++-------------- 7 files changed, 39 insertions(+), 54 deletions(-) create mode 100644 prdoc/pr_7344.prdoc diff --git a/prdoc/pr_7344.prdoc b/prdoc/pr_7344.prdoc new file mode 100644 index 0000000000000..a3ffa32f125c3 --- /dev/null +++ b/prdoc/pr_7344.prdoc @@ -0,0 +1,14 @@ +title: '[sync] Let new subscribers know about already connected peers (backward-compatible)' +doc: +- audience: Node Dev + description: Revert https://github.com/paritytech/polkadot-sdk/pull/7011 and replace + it with a backward-compatible solution suitable for backporting to a release branch. +crates: +- name: sc-network-gossip + bump: patch +- name: sc-network-statement + bump: patch +- name: sc-network-sync + bump: patch +- name: sc-network-transactions + bump: patch diff --git a/substrate/client/network-gossip/src/bridge.rs b/substrate/client/network-gossip/src/bridge.rs index bff258a9a011b..2daf1e49ee4b4 100644 --- a/substrate/client/network-gossip/src/bridge.rs +++ b/substrate/client/network-gossip/src/bridge.rs @@ -254,12 +254,10 @@ impl Future for GossipEngine { match sync_event_stream { Poll::Ready(Some(event)) => match event { - SyncEvent::InitialPeers(peer_ids) => - this.network.add_set_reserved(peer_ids, this.protocol.clone()), - SyncEvent::PeerConnected(peer_id) => - this.network.add_set_reserved(vec![peer_id], this.protocol.clone()), - SyncEvent::PeerDisconnected(peer_id) => - this.network.remove_set_reserved(peer_id, this.protocol.clone()), + SyncEvent::PeerConnected(remote) => + this.network.add_set_reserved(remote, this.protocol.clone()), + SyncEvent::PeerDisconnected(remote) => + this.network.remove_set_reserved(remote, this.protocol.clone()), }, // The sync event stream closed. Do the same for [`GossipValidator`]. Poll::Ready(None) => { diff --git a/substrate/client/network-gossip/src/lib.rs b/substrate/client/network-gossip/src/lib.rs index 2ec573bf9e3ef..20d9922200c2c 100644 --- a/substrate/client/network-gossip/src/lib.rs +++ b/substrate/client/network-gossip/src/lib.rs @@ -82,18 +82,15 @@ mod validator; /// Abstraction over a network. pub trait Network: NetworkPeers + NetworkEventStream { - fn add_set_reserved(&self, peer_ids: Vec, protocol: ProtocolName) { - let addrs = peer_ids - .into_iter() - .map(|peer_id| Multiaddr::empty().with(Protocol::P2p(peer_id.into()))) - .collect(); - let result = self.add_peers_to_reserved_set(protocol, addrs); + fn add_set_reserved(&self, who: PeerId, protocol: ProtocolName) { + let addr = Multiaddr::empty().with(Protocol::P2p(*who.as_ref())); + let result = self.add_peers_to_reserved_set(protocol, iter::once(addr).collect()); if let Err(err) = result { log::error!(target: "gossip", "add_set_reserved failed: {}", err); } } - fn remove_set_reserved(&self, peer_id: PeerId, protocol: ProtocolName) { - let result = self.remove_peers_from_reserved_set(protocol, iter::once(peer_id).collect()); + fn remove_set_reserved(&self, who: PeerId, protocol: ProtocolName) { + let result = self.remove_peers_from_reserved_set(protocol, iter::once(who).collect()); if let Err(err) = result { log::error!(target: "gossip", "remove_set_reserved failed: {}", err); } diff --git a/substrate/client/network/statement/src/lib.rs b/substrate/client/network/statement/src/lib.rs index 586a15cadd68e..df93788696e38 100644 --- a/substrate/client/network/statement/src/lib.rs +++ b/substrate/client/network/statement/src/lib.rs @@ -33,8 +33,7 @@ use futures::{channel::oneshot, prelude::*, stream::FuturesUnordered, FutureExt} use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64}; use sc_network::{ config::{NonReservedPeerMode, SetConfig}, - error, - multiaddr::{Multiaddr, Protocol}, + error, multiaddr, peer_store::PeerStoreProvider, service::{ traits::{NotificationEvent, NotificationService, ValidationResult}, @@ -297,19 +296,9 @@ where fn handle_sync_event(&mut self, event: SyncEvent) { match event { - SyncEvent::InitialPeers(peer_ids) => { - let addrs = peer_ids - .into_iter() - .map(|peer_id| Multiaddr::empty().with(Protocol::P2p(peer_id.into()))) - .collect(); - let result = - self.network.add_peers_to_reserved_set(self.protocol_name.clone(), addrs); - if let Err(err) = result { - log::error!(target: LOG_TARGET, "Add reserved peers failed: {}", err); - } - }, - SyncEvent::PeerConnected(peer_id) => { - let addr = Multiaddr::empty().with(Protocol::P2p(peer_id.into())); + SyncEvent::PeerConnected(remote) => { + let addr = iter::once(multiaddr::Protocol::P2p(remote.into())) + .collect::(); let result = self.network.add_peers_to_reserved_set( self.protocol_name.clone(), iter::once(addr).collect(), @@ -318,10 +307,10 @@ where log::error!(target: LOG_TARGET, "Add reserved peer failed: {}", err); } }, - SyncEvent::PeerDisconnected(peer_id) => { + SyncEvent::PeerDisconnected(remote) => { let result = self.network.remove_peers_from_reserved_set( self.protocol_name.clone(), - iter::once(peer_id).collect(), + iter::once(remote).collect(), ); if let Err(err) = result { log::error!(target: LOG_TARGET, "Failed to remove reserved peer: {err}"); diff --git a/substrate/client/network/sync/src/engine.rs b/substrate/client/network/sync/src/engine.rs index 4003361525e18..3b9c5f38329b1 100644 --- a/substrate/client/network/sync/src/engine.rs +++ b/substrate/client/network/sync/src/engine.rs @@ -657,8 +657,10 @@ where self.strategy.set_sync_fork_request(peers, &hash, number); }, ToServiceCommand::EventStream(tx) => { - let _ = tx - .unbounded_send(SyncEvent::InitialPeers(self.peers.keys().cloned().collect())); + // Let a new subscriber know about already connected peers. + for peer_id in self.peers.keys() { + let _ = tx.unbounded_send(SyncEvent::PeerConnected(*peer_id)); + } self.event_streams.push(tx); }, ToServiceCommand::RequestJustification(hash, number) => diff --git a/substrate/client/network/sync/src/types.rs b/substrate/client/network/sync/src/types.rs index a72a2f7c1ffe4..5745a34378df6 100644 --- a/substrate/client/network/sync/src/types.rs +++ b/substrate/client/network/sync/src/types.rs @@ -127,10 +127,6 @@ where /// Syncing-related events that other protocols can subscribe to. pub enum SyncEvent { - /// All connected peers that the syncing implementation is tracking. - /// Always sent as the first message to the stream. - InitialPeers(Vec), - /// Peer that the syncing implementation is tracking connected. PeerConnected(PeerId), diff --git a/substrate/client/network/transactions/src/lib.rs b/substrate/client/network/transactions/src/lib.rs index 49f429a04ee2e..44fa702ef6d4f 100644 --- a/substrate/client/network/transactions/src/lib.rs +++ b/substrate/client/network/transactions/src/lib.rs @@ -35,8 +35,7 @@ use log::{debug, trace, warn}; use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64}; use sc_network::{ config::{NonReservedPeerMode, ProtocolId, SetConfig}, - error, - multiaddr::{Multiaddr, Protocol}, + error, multiaddr, peer_store::PeerStoreProvider, service::{ traits::{NotificationEvent, NotificationService, ValidationResult}, @@ -378,19 +377,9 @@ where fn handle_sync_event(&mut self, event: SyncEvent) { match event { - SyncEvent::InitialPeers(peer_ids) => { - let addrs = peer_ids - .into_iter() - .map(|peer_id| Multiaddr::empty().with(Protocol::P2p(peer_id.into()))) - .collect(); - let result = - self.network.add_peers_to_reserved_set(self.protocol_name.clone(), addrs); - if let Err(err) = result { - log::error!(target: LOG_TARGET, "Add reserved peers failed: {}", err); - } - }, - SyncEvent::PeerConnected(peer_id) => { - let addr = Multiaddr::empty().with(Protocol::P2p(peer_id.into())); + SyncEvent::PeerConnected(remote) => { + let addr = iter::once(multiaddr::Protocol::P2p(remote.into())) + .collect::(); let result = self.network.add_peers_to_reserved_set( self.protocol_name.clone(), iter::once(addr).collect(), @@ -399,10 +388,10 @@ where log::error!(target: LOG_TARGET, "Add reserved peer failed: {}", err); } }, - SyncEvent::PeerDisconnected(peer_id) => { + SyncEvent::PeerDisconnected(remote) => { let result = self.network.remove_peers_from_reserved_set( self.protocol_name.clone(), - iter::once(peer_id).collect(), + iter::once(remote).collect(), ); if let Err(err) = result { log::error!(target: LOG_TARGET, "Remove reserved peer failed: {}", err); From b2004ed89dd58e7f30be7312d32fcaaf98d6384f Mon Sep 17 00:00:00 2001 From: dharjeezy Date: Mon, 27 Jan 2025 14:01:03 +0100 Subject: [PATCH 153/169] `Arc` definition in `TransactionPool` (#7042) closes #5978 --------- Co-authored-by: command-bot <> Co-authored-by: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> --- prdoc/pr_7042.prdoc | 9 +++++++++ substrate/client/network/transactions/src/config.rs | 10 +++++----- substrate/client/network/transactions/src/lib.rs | 2 +- substrate/client/service/src/lib.rs | 12 ++++++------ 4 files changed, 21 insertions(+), 12 deletions(-) create mode 100644 prdoc/pr_7042.prdoc diff --git a/prdoc/pr_7042.prdoc b/prdoc/pr_7042.prdoc new file mode 100644 index 0000000000000..00fb34c6af493 --- /dev/null +++ b/prdoc/pr_7042.prdoc @@ -0,0 +1,9 @@ +title: `networking::TransactionPool` should accept `Arc` +doc: +- audience: Node Dev + description: The `sc_network_transactions::config::TransactionPool` trait now returns an `Arc` for transactions. +crates: +- name: sc-network-transactions + bump: minor +- name: sc-service + bump: minor \ No newline at end of file diff --git a/substrate/client/network/transactions/src/config.rs b/substrate/client/network/transactions/src/config.rs index 239b76b51485f..42a335d7041ad 100644 --- a/substrate/client/network/transactions/src/config.rs +++ b/substrate/client/network/transactions/src/config.rs @@ -22,7 +22,7 @@ use futures::prelude::*; use sc_network::MAX_RESPONSE_SIZE; use sc_network_common::ExHashT; use sp_runtime::traits::Block as BlockT; -use std::{collections::HashMap, future::Future, pin::Pin, time}; +use std::{collections::HashMap, future::Future, pin::Pin, sync::Arc, time}; /// Interval at which we propagate transactions; pub(crate) const PROPAGATE_TIMEOUT: time::Duration = time::Duration::from_millis(2900); @@ -57,7 +57,7 @@ pub type TransactionImportFuture = Pin: Send + Sync { /// Get transactions from the pool that are ready to be propagated. - fn transactions(&self) -> Vec<(H, B::Extrinsic)>; + fn transactions(&self) -> Vec<(H, Arc)>; /// Get hash of transaction. fn hash_of(&self, transaction: &B::Extrinsic) -> H; /// Import a transaction into the pool. @@ -67,7 +67,7 @@ pub trait TransactionPool: Send + Sync { /// Notify the pool about transactions broadcast. fn on_broadcasted(&self, propagations: HashMap>); /// Get transaction by hash. - fn transaction(&self, hash: &H) -> Option; + fn transaction(&self, hash: &H) -> Option>; } /// Dummy implementation of the [`TransactionPool`] trait for a transaction pool that is always @@ -79,7 +79,7 @@ pub trait TransactionPool: Send + Sync { pub struct EmptyTransactionPool; impl TransactionPool for EmptyTransactionPool { - fn transactions(&self) -> Vec<(H, B::Extrinsic)> { + fn transactions(&self) -> Vec<(H, Arc)> { Vec::new() } @@ -93,7 +93,7 @@ impl TransactionPool for EmptyTransaction fn on_broadcasted(&self, _: HashMap>) {} - fn transaction(&self, _h: &H) -> Option { + fn transaction(&self, _h: &H) -> Option> { None } } diff --git a/substrate/client/network/transactions/src/lib.rs b/substrate/client/network/transactions/src/lib.rs index 44fa702ef6d4f..a6a9552079459 100644 --- a/substrate/client/network/transactions/src/lib.rs +++ b/substrate/client/network/transactions/src/lib.rs @@ -469,7 +469,7 @@ where fn do_propagate_transactions( &mut self, - transactions: &[(H, B::Extrinsic)], + transactions: &[(H, Arc)], ) -> HashMap> { let mut propagated_to = HashMap::<_, Vec<_>>::new(); let mut propagated_transactions = 0; diff --git a/substrate/client/service/src/lib.rs b/substrate/client/service/src/lib.rs index 2a3144a33e1ae..322726a1eff43 100644 --- a/substrate/client/service/src/lib.rs +++ b/substrate/client/service/src/lib.rs @@ -474,7 +474,7 @@ impl TransactionPoolAdapter { /// Get transactions for propagation. /// /// Function extracted to simplify the test and prevent creating `ServiceFactory`. -fn transactions_to_propagate(pool: &Pool) -> Vec<(H, B::Extrinsic)> +fn transactions_to_propagate(pool: &Pool) -> Vec<(H, Arc)> where Pool: TransactionPool, B: BlockT, @@ -485,7 +485,7 @@ where .filter(|t| t.is_propagable()) .map(|t| { let hash = t.hash().clone(); - let ex: B::Extrinsic = (**t.data()).clone(); + let ex = t.data().clone(); (hash, ex) }) .collect() @@ -506,7 +506,7 @@ where H: std::hash::Hash + Eq + sp_runtime::traits::Member + sp_runtime::traits::MaybeSerialize, E: 'static + IntoPoolError + From, { - fn transactions(&self) -> Vec<(H, B::Extrinsic)> { + fn transactions(&self) -> Vec<(H, Arc)> { transactions_to_propagate(&*self.pool) } @@ -559,10 +559,10 @@ where self.pool.on_broadcasted(propagations) } - fn transaction(&self, hash: &H) -> Option { + fn transaction(&self, hash: &H) -> Option> { self.pool.ready_transaction(hash).and_then( // Only propagable transactions should be resolved for network service. - |tx| if tx.is_propagable() { Some((**tx.data()).clone()) } else { None }, + |tx| tx.is_propagable().then(|| tx.data().clone()), ) } } @@ -614,6 +614,6 @@ mod tests { // then assert_eq!(transactions.len(), 1); - assert!(TransferData::try_from(&transactions[0].1).is_ok()); + assert!(TransferData::try_from(&*transactions[0].1).is_ok()); } } From d85147d013e112feae5000816932d0543aee95da Mon Sep 17 00:00:00 2001 From: christopher k <30932534+EleisonC@users.noreply.github.com> Date: Mon, 27 Jan 2025 16:01:49 +0300 Subject: [PATCH 154/169] Add development chain-spec file for minimal/parachain templates for Omni Node compatibility (#6529) # Description This PR adds development chain specs for the minimal and parachain templates. [#6334](https://github.com/paritytech/polkadot-sdk/issues/6334) ## Integration This PR adds development chain specs for the minimal and para chain template runtimes, ensuring synchronization with runtime code. It updates zombienet-omni-node.toml, zombinet.toml files to include valid chain spec paths, simplifying configuration for zombienet in the parachain and minimal template. ## Review Notes 1. Overview of Changes: - Added development chain specs for use in the minimal and parachain template. - Updated zombienet-omni-node.toml and zombinet.toml files in the minimal and parachain templates to include paths to the new dev chain specs. 2. Integration Guidance: **NB: Follow the templates' READMEs from the polkadot-SDK master branch. Please build the binaries and runtimes based on the polkadot-SDK master branch.** - Ensure you have set up your runtimes `parachain-template-runtime` and `minimal-template-runtime` - Ensure you have installed the nodes required ie `parachain-template-node` and `minimal-template-node` - Set up [Zombinet](https://paritytech.github.io/zombienet/intro.html) - For running the parachains, you will need to install the polkadot `cargo install --path polkadot` remember from the polkadot-SDK master branch. - Inside the template folders minimal or parachain, run the command to start with `Zombienet with Omni Node`, `Zombienet with minimal-template-node` or `Zombienet with parachain-template-node` *Include your leftover TODOs, if any, here.* * [ ] Test the syncing of chain specs with runtime's code. --------- Signed-off-by: EleisonC Co-authored-by: Iulian Barbu <14218860+iulianbarbu@users.noreply.github.com> Co-authored-by: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> --- .github/workflows/misc-sync-templates.yml | 65 +++++++++++ templates/minimal/README.md | 11 +- templates/minimal/dev_chain_spec.json | 85 +++++++++++++++ templates/minimal/zombienet-omni-node.toml | 4 +- templates/parachain/README.docify.md | 9 +- templates/parachain/README.md | 9 +- templates/parachain/dev_chain_spec.json | 108 +++++++++++++++++++ templates/parachain/zombienet-omni-node.toml | 2 +- 8 files changed, 284 insertions(+), 9 deletions(-) create mode 100644 templates/minimal/dev_chain_spec.json create mode 100644 templates/parachain/dev_chain_spec.json diff --git a/.github/workflows/misc-sync-templates.yml b/.github/workflows/misc-sync-templates.yml index ce01f010aa718..26f89ed1cc978 100644 --- a/.github/workflows/misc-sync-templates.yml +++ b/.github/workflows/misc-sync-templates.yml @@ -31,7 +31,66 @@ on: default: "" jobs: + prepare-chain-spec-artifacts: + runs-on: ubuntu-latest + strategy: + matrix: + include: + - template: minimal + package_name: 'minimal-template-runtime' + runtime_path: './templates/minimal/runtime' + runtime_wasm_path: minimal-template-runtime/minimal_template_runtime.compact.compressed.wasm + relay_chain: 'dev' + - template: parachain + package_name: 'parachain-template-runtime' + runtime_path: './templates/parachain/runtime' + runtime_wasm_path: parachain-template-runtime/parachain_template_runtime.compact.compressed.wasm + relay_chain: 'rococo-local' + steps: + - uses: actions/checkout@v4 + with: + ref: "${{ github.event.inputs.stable_release_branch }}" + + - name: Setup build environment + run: | + sudo apt-get update + sudo apt-get install -y protobuf-compiler + cargo install --git https://github.com/chevdor/srtool-cli --locked + cargo install --path substrate/bin/utils/chain-spec-builder --locked + srtool pull + + - name: Build runtime and generate chain spec + run: | + # Prepare directories + sudo mkdir -p ${{ matrix.template.runtime_path }}/target + sudo chmod -R 777 ${{ matrix.template.runtime_path }}/target + + # Build runtime + srtool build --package ${{ matrix.template.package_name }} --runtime-dir ${{ matrix.template.runtime_path }} --root + + # Generate chain spec + # Note that para-id is set to 1000 for both minimal/parachain templates. + # `parachain-runtime` is hardcoded to use this parachain id. + # `minimal` template isn't using it, but when started with Omni Node, this para id is required (any number can do it, so setting it to 1000 for convenience). + chain-spec-builder -c dev_chain_spec.json create \ + --relay-chain "${{ matrix.template.relay_chain }}" \ + --para-id 1000 \ + --runtime "${{ matrix.template.runtime_path }}/target/srtool/release/wbuild/${{ matrix.template.runtime_wasm_path }}" \ + named-preset development + + - name: Prepare upload directory + run: | + mkdir -p artifacts-${{ matrix.template }} + cp dev_chain_spec.json artifacts-${{ matrix.template }}/dev_chain_spec.json + + - name: Upload template directory + uses: actions/upload-artifact@v4 + with: + name: artifacts-${{ matrix.template }} + path: artifacts-${{ matrix.template }}/dev_chain_spec.json + sync-templates: + needs: prepare-chain-spec-artifacts runs-on: ubuntu-latest environment: master strategy: @@ -52,6 +111,12 @@ jobs: with: path: polkadot-sdk ref: "${{ github.event.inputs.stable_release_branch }}" + - name: Download template artifacts + uses: actions/download-artifact@v4 + with: + name: artifacts-${{ matrix.template }} + path: templates/${{ matrix.template }}/ + if: matrix.template != 'solochain' - name: Generate a token for the template repository id: app_token uses: actions/create-github-app-token@v1.9.3 diff --git a/templates/minimal/README.md b/templates/minimal/README.md index 22f396c243ef3..4cf3fd2a44bb4 100644 --- a/templates/minimal/README.md +++ b/templates/minimal/README.md @@ -42,6 +42,7 @@ such as a [Balances pallet](https://paritytech.github.io/polkadot-sdk/master/pal - 👤 The template has no consensus configured - it is best for experimenting with a single node network. + ## Template Structure A Polkadot SDK based project such as this one consists of: @@ -61,7 +62,7 @@ compiled unless building the entire workspace). - 🛠️ Depending on your operating system and Rust version, there might be additional packages required to compile this template - please take note of the Rust compiler output. -Fetch minimal template code: +Fetch minimal template code. ```sh git clone https://github.com/paritytech/polkadot-sdk-minimal-template.git minimal-template @@ -147,11 +148,13 @@ docker run --rm polkadot-sdk-minimal-template We can install `zombienet` as described [here](https://paritytech.github.io/zombienet/install.html#installation), and `zombienet-omni-node.toml` contains the network specification we want to start. + #### Update `zombienet-omni-node.toml` with a valid chain spec path -Before starting the network with zombienet we must update the network specification -with a valid chain spec path. If we need to generate one, we can look up at the previous -section for chain spec creation [here](#use-chain-spec-builder-to-generate-the-chain_specjson-file). +To simplify the process of starting the minimal template with ZombieNet and Omni Node, we've included a +pre-configured development chain spec (dev_chain_spec.json) in the minimal template. The zombienet-omni-node.toml +file in this template points to it, but you can update it to a new path for the chain spec generated on your machine. +To generate a chain spec refer to [staging-chain-spec-builder](https://crates.io/crates/staging-chain-spec-builder) Then make the changes in the network specification like so: diff --git a/templates/minimal/dev_chain_spec.json b/templates/minimal/dev_chain_spec.json new file mode 100644 index 0000000000000..91d703c6fd5bd --- /dev/null +++ b/templates/minimal/dev_chain_spec.json @@ -0,0 +1,85 @@ +{ + "bootNodes": [], + "chainType": "Live", + "codeSubstitutes": {}, + "genesis": { + "runtimeGenesis": { + "code": "0x52bc537646db8e0528b52ffd0058bc49057eb84632144d10784894742c8bde903e277d4efa9ca4a5f0e2c7b95fe173920d2da637ed48cf23dbde08721d93d1ee93927f0102b631505ddb8ea0b7b14774f612445415d300fa8db44608216493bdf7965b064c159913b313096873c0d17b6e346bb3af5a029e75557bc411556f2ba2ca5e9ad86212d0f07087ae3d5d198eaeed1243c2d34ca1707fc049eb76aaa5caaceb92189407aafbc3c594d60975bb7b688f583fb47b6c09d87695bbc6769f302219c8da27fe84dd441facc8c231b5d69a74ad3d6e2d9240ed9309ac29cddf13d175b79bb5737b8918ef3dab86bbefa39046d142880dbc14169b2d78a6a5f93f2e206435a7f7325d5bba6e1a9888023fdb164ce213c96adba2f975d3d0fcd27be662eef0c51170700433e2f0f1b1817787736ddcf4793fe0420726c8c1126ca26821c4c65d0a8bcd8ba205dbc44b59808d3b9c521660532aa309f712132e86efda683e7ca98c26e2212dd5b8187ea4a5329a8077b47417c38754a6eecf8f7bdd36160f27a082187008830fccb08537fffa41b6363d3060630937b88113de18421d6cd6e6bdcaf47d7c6cdc163c43849beb62a6f40ac1f37a1a3f1f6c1146bff4fe6c21a58f7022d3d3efed5688efb09baa759c3be4e907e5684775a6c7579eaeeeed136d12294e0e6df2f8fa682db53b9cf10fc962614a472a86847f112767e5e878d864b130a5e12bf78bb4c9cad1b08902a2fc34a46258c153c4179e50c4179e406b15eddee67e310c14f185275450bfc32986153c2b870df5d57155aab4183e4056b0832ea8f0105482b086cfa362e568317c8af8c2137ca202a2fc489fc5c294ca042b870d457d560e9b7dbdfa7d3701bb45d4fd49c14fefb416c38707198480051b7c7c6c965ac123a9cffed1ea1cad54b46b42640632f0e00f519660697e0df3700fe43886b55164fb8302321af6fe4861a35734467b8f4aba304e045691b586ec3704218cb4350e2c76eb806cb107f4f47bb4dd6ab3486bd966eb9ed6844002c3e97b8b677d6f87737404bec3699aa6f6699a265adf197e9ab1450869757f144705304218638c31c2181fdbe1c469f44a021492382f9cb57bc694e8699cdd577edbac31c172f1551e3e4e1cf978374747288ebcfbb6bbe2746fd3e2186fc568b9ab4ddf81dd6ebd69a6c6f3db1f7c23d2b6d12acf140aece9356cc1abcde9703274b3ba4b8a03cf9f627478b0da9b15febdcdd191f6f84871e0dbb72d9e4e748074fc0e6c7ecb10775d3ca3333514ee788933fd495adba7b7e6378c08b7346337fbe3e3e3d34c2bfc44a1c08eb7e0e5663bcff8c59278e7d668758714e7316c3c45ca18bd3f2780a36ba8df05d0fb738237bc432b4eb74c6acba4fa9a1fa7bbe499dad34ce50f086f410889b8d7309cadad76fd783b9cf36149b486af61c933a322ade121958d732501521d9ee6f6284ef684054076f56b2bf460737f309e3f625a84efeee21b3c8e8b973cc31dafaee11aee69adad61ad03322b24058da7444fa39249efef56e8003a3cfdfedebead1fed80b56c6defb32e9e191d7155aab43bce0ef707a054eddeae6820edda15d6f28cfb103277fe737fb7de6cddb0e7dc6bb8f562ee35bcc472df5fd35a98ba3fed7e82309cbb3bc9d138194713a58c8a44bb43aac56dcaa8da1d09b67127e8a2dd7b9595ff24cfe0f0db9769328e71fc47abec9ab5fbb26899f4fe98d45eb3febb5a95dd5afb8f1673d0349bfdb55811fea3f2e219befbc2388ab33fcc2c05abf7278b7592ed4f0acce8e6d35f98da7c9a6fd1b46be703321cf7edda7cb8f727056474eb76c68e349f5e5a9b4fbb5b8b1179df09e5f5fecd9bacf76ed69e76df3d627453f57bdd2ff4bb13997e5427d4fbba55babaf36bdc5cbe7696b2f6d6cb07d8374ce7edbe984ed68ebe341fce021cfd1e69560d1f1f9bfdb3428f67e2f758f4deda3d0274cbeab90e9abb422e5aadeb70c75bb4725b6fbf66e5e9ab35a83ee169ebefd5778fa00e5a21205aadbb63a7e6ae108cd6ebee35ced299aa54a9f2877ebf66ed29d2fabd86adb95d4d9c47a1b48ebfe18e77d89177a6b5a7f9dbd6dbacaddb1d56241e746b8e6ebac624aeef0a69af71357c4d9d56a5e541738425319da2b535f56ad6d6d5ad386beb78ec0f43f21ebf1891eb356c2d08b2fdc9e20dfd1e6fadc5d7624960b7687d6d3986221b74106dd8cdeb78cb5a0c446b4f837e4d28b2af5bb3b6b6be7bc445ebf59c237a578875eb35ce9ad6745f0c44755c63b3c6bb2f76e43dae1a3e26689eb1be7f3c537ddfb0245a95bee8ae906a519d56a5adc35b743708e5aa769f15023fcd1a7ba8164d60ddd16411fabfd3b3e0f17821fc2c7c1cf401b8678182769717f1753459f8a0a595bb1fcf22885bdf4b4793854c163deee37b174793050f5ae2f150e83e440fcdf37889072d8960eee3251ebf1e7a168e96a47c464bd927f792a3254951d05e9207e2a263df678e268b8a96aa33dff2414bf4931027e23cddc74b7c1fb404e1335aaa9ebde4e3d6b378ff414b2d2d5fa125778a96a84feba5798b969c7bc9fae82c59f68b96207c454b8e9680bc7ae69ebd743da325c8f2127ccc877829fb10b4d4d2f21db4b4e3d3742068a90588ef98767c8897a60f414b40500e889728188a02120b2db17cc78e8f5876eca017a2aa0e0490aab22a6aeef0c143e4b289a98a8793d60e189af062e943146239a7cc65d5b4c382170f1f3ce40c89604551cecaa61da22be3415d3e44134bae4213ee7070870f278908406671685a178068888a0822587c588e8a8979891f1343f1e0414fcae449570002f052f50004604e53e8f43157f5184822bd344f228224433bb2d307c087e801b8005ee27101c4c4bc643d6688980be0250a124184354f4f823b4e6222882022002f4d0f002d114184c85d00430c01e4a5eb40a8d3c700712042f010c0635ef2f1181209001f829648176288c7bc545d8897320a731616f6717a228000b9104000f1d1810871202f890e449e88077929f4204de6e5027829740134980b2180077989c78578493e86f4f242ff12f39797e65fda4bd65f3ec468742180a02520b404e4427c04c4472f5d1fb597e08738e9a5eca40600225e9a4e042d018088535a02c08100e2a497dce94bd483d052000270185a1ad1d2e82c2c7fa1a51f3f3ea2a597b3b07cf4d28e8f68e9c7e83f5eba68cdc6b4c35c00425c2b07424b3f68e9c781fcba4e5faa4e69694588d3972c4ac5c6b4af5c002f55b48a8d6917002d0580a2a0c5581f8296aaea2db4d4726b885b2f51d4098b962aeb2fb454fda27ebd3429131bd3fef2d20e6a6463da49b444c48e038096282dd10f310f434b42fc9496b60d51f4c046151bd4c105526ca6164ed04516f620071a64a1059b2ce66b7c50b931ed93964ecf83eec6b40741d7c5ec8100e2e32fb43472f90f5aea510211dcd0c61b3cf001169460e3a3a7062608430b9e00063dbc81031b1f2f89a8cbc6b4fba02517ba2ea6085a49bd2f8568b631ed3da874317b1e2f39ca6463da79d0d20e8a82669385e82db4143a0b45818c4d16ee2bb424023d444b3d3b10c30f62eca00e3bc0610e366ec540071174d0821ad4f1043dd864e11e7a29a34336a63d444b20ba2e6600b40ee97d69a2321bd38e511717b32f3155b231ed172d591405249b2ce22b5a42e20869c4210876e0021d3fb0a1280a6e9345104ec043119a10062df6e0069b2cf8d34b929236a67da2a548d7c508a155a6f72548bf31ed8f662e66bfef713c312ca4ca26c9f6270a1dbd3f587cd135d4fcf542ef0f1664b4155a98e599655295cd5fa6598ee1efad866111a5a7c7f374f81aa764125fcec724feb3e9db1c6f261a5fc30d83b7da155b330679269eff78469e2fa7b96dc5b92d676b0bb678794bced60e93346e0dcb196fc5b9d3746eb28e67ccc876a417931aa4b5a7a18c72d6d7d57dc7f5f4c588c0d7b0652d96c444ebebe9d4e5ac6ff9386bad3532a1a256d1f2cb24f91a474d9d52bfcb595f7a9adb71be7e3aaedf1723f2e863929bb23d2eac6d2dc8f6ed5b590b4517935c98d40e7b7f4cd0876e6152fbfa7869afb5d6dadf61736f8ddf1cc3f72efbd1d111f877486b6bfea33badf92e899e66ba4cda065b5382b53cf3e06be4c729fbf10cfcde82b3f59bdb44dee11cbdb77eb71a4684dfe1dfbc8690c5c3bb378c88a36f6ec3b97cbdb94d4464055398b4cdff41840b9f15b2ed3022fcc5225d8cc86392c38c6c7b0dc7f91aceed375bf7b8d6e6b0160059fbdeda20b2c558ac85a96ff7c733fcfdf24cfb9ea7b533cb98e47e31c97d31229249ee354cb3dad2aeca7677e1997777ab8567de5f7806dedd6ad79bdb8bc14309a29ff4c86e97dde8e808bf9ddfe84e6b48df6c6dedccb6b799567e7b0d2f8c73ad29c1046fe82a7b9f31cdb3d94b9ed99f2be8e8fdc533adf72e3cb3bd5f98a538ef15a2a7350458db816c7fae60c35a28b2fdb9228dde76e1994a6a3e9fe468dcf92f4cd36cf819cfec8f4f1acd6fe11926c2c5152b64bb86dd8c528616b72903f6c5337bbe4caf103cada771c381b5bd30356bbed5eedaebd5dc9ad526ff607c499ea9adf70821b2fd6249382a2dbeda7c7ded114e1a1dad3335949590e5ae365d6350e2154743b9407b8493e6d33a0ce876fe62489e8f4fb7d770c36e329f6ebf78669bbfd84df3e985618a93f50ad1c38db360b9f6ea9c3b8ea39249fb9ed61c6059bb22222b90c2a4ed8711a9e17db16eb2f6bd15da963c537f5c43c8f8eeb5fd31696fb52999b47fc66fdf2f8684e963121fa751c9a4fd3269efe63289e932a9bd617cab5d6eb6e6b9dda664d29e31bee5da32e831a9bd866bb8866b98e7c5a4f61e6ead8b2b07456c7183eadac105c645e5b2c18583eb06961dae2b2e1fab8ac5076b0fd717561fac3a587ab85a706521dfb856706971b1e0c2c2fac3b5854507ab0ecb0f540d281a5033a0bca0824001812a83a242d980c2017503ea0bea0c8a08d4102832a817505328165042a0cca07e40f980ea0135062506c5036a075418940ea81c50605052282ca82c28145051641c241db20fb20ea907690749078907ea0a3907b907c907e9072907f907b905c905a9054904d9862c8234821c82cc825c82c4823c431e41f640064102418a21c76828903c6842fe8027027207f20b5983690e931aa41753179e19130d9e101e10a6379c1ea62f4c69906080714c3e8862886f4437a216a628530e22179e1a261d4034241c2637b83d383c4c319864e0e8307901e5806f806e8863805198bce0fac052380accc26486298ec70687856785c90d788257c6f30114038e017b0075e0f20073007920c9783f7865806abc38c82e5e1b8f8d778528834886490c08c7b4c6540548063886298c4bcab4034a05540a60196217ae294e0b4f0c2f0c0f0c970a1c17220ca2176f088f8c870604e321e1254122e11521c6e19d5124c6409621d1707c78519c1fde15b18b9e58067804c70637c7c503b786f885290bb7854b06ee0df7057806e785690cd7854905ce0d8804f70777059705578763e37de18ae2c200992061c038b8c6b872c061c035700c5806110c318e6b06d316ae8a150e092f081318cf079a61a20254c21404c986a98de805de62fa99cc902f786b805ac02c261e5c2790549c14de1c4e0d39830908118e498be9043009d3192e144c6f885b7051786860185c5c4433c028310cf20d52064c06ff807dc054f805dc02ee82a7b00b580b3e01e740c6e01243b68051c029602cd804ecd34ed0ae683e4da6a0c004768c58a0020db0e91a0608510012093802010ba09931c2000520804c1141868821c194e007909711c3c16abd0d168162b7e4c9131d4842c5134b982449623517459325499254b18d7db204044f3449328a59d89d254f9638a122c9ce920ff88858d8253b4fec3c4912458e1421e8c013299e2c69b224891ff10a4c763ed064490e1bbb648907924cb1936487c907a45822829868857d9223c5133b4ba248b2648907888855d8293cd0448a242370b224043a49a4a0a2779a2c01c1089e60e2910abb048a26533449f2441229a8e82550349902023b4d42300514503490b846145340b1248a20e214f6c90896e024799264c9ce134e963459f281244f46b0e4489329a478f264c9046694c28a20c91449a4a0a29d2cc1713285059a48f10413283ce0012433466197ec3c118514394f4049b224044b42108126a258f2810f3cd18400a21afb04078a254ca080c23ed969b2c4c912264da4787c4213293c308a4e5828a478d2440a273b552c4932821c2aa8782289932551e44811021b1c28a6c8b179893ffbe4476cc23e91e42532614190e4899d27a2786267c912a2b804124891f384143b52349162474c6371a09822a7034996ec84008a25552c59894a58275334e0dab624a7034fec3409810792ec40b1c489145134912249121f3ee20aa62951e0628bde1e6b7f7c7cbaf5381f91e3c0716854b4e9adc0951598ad6430cb569677e3c2c7eb769f73cebd4751ee2d8649f9ae8bafeb3dea516d6237bd1727e95e6cfce2732e3a574927a3731bafe8a2dc8d6f0de09cdb7def718471638ccfc1189f8bb072f12dc3085b8b710be0228c0f937141cb4fc6ddeb72ceed7b8f1d01648a587e6e3742f738eef21bc0dbddc8c33917e3bef7282af2d8ddb7ceedb638f79c5cf7a27cbb6eaddde5c7bb3b39d7844c6ea5dce85e74cead73948b1445518fa2a8dde81c3bb791da6db9ae0bc6785dbbd15d4fb6d62294419e48241245e822bc9c94b1b96d020840cc9246448c441bddbee8f6edbaf69e8b6e638cebe03ae75e8b2e42aa45182386ed6edcf81ee4756e59de7b2fc6e8dc42ec391eceb15be76204c59d5e73cf4dd1bde93de6f7765f7bcfc9f6f8b15c1777dfe36ddbe266ce39c7bcbbece4c618e3eec618dd2ee5761fbb5d7e8b619894eebdd71af6f839e72045b9f6dee59c7bce39775d975bf7dee3f7dc63e79a8f186504c01010b6441863848930bef836beb71be31342b294987cefb99de48f1f58931c61f44161842d13c6173fb5c7cc8f992f7e8d995f8c172842e7e22ebb37b908375e39b8c2702bcb237032050896802016c08910d61660c993274b9c50018229a048e264099429a058e224c9134fa28062491202e0cc1ca0005440b1e4098d8b07a470b2044a120410e000072840144b924cb143c5089c40214403c12e019638f140122753ec3c01c51439394fbc2c0181143b4d42e084b27860270440963049b2d3440a27542481e28914207809228a254e9644e1648a24330728c012299cec04c0c9133b4f88e8f1c407105b8029a670f203078a273a9093338593008c60094e929d2a96340162c7ce920ff8083dd9792245154b5ea625409329a4d881e289274d9e70b29364470a1080600a12902d401552786089132a288b932796e44031ea114593255450f14492251f78c2c9143b4f5e8c980152c51422d80162e60005080d29000192146009ce13391d60f2e40926af729640599293a48a2740100494264bdccb1220499329a478e2640994241f982227c9079a48f1810f4c919364a78a253ea03459e246720b5e1e9e9e9e9ec84ab68787797878781a0f0f0f0f0f0f4fe429f5b8520f8fa394f4b490929eb63d3d3c8e95f0b49ea684a7f1f0b092e5e9e971a51ea9a4a7f5b41e9e1ea7647978b8472ae1693c4d494fe3e1e9e1e1e1694a9687a7a7e7f54c4ab6272ae159253dad072ad965253dadc729d99e1e56b23d3d3d3d4f494feb694ab6a72931c2f9b4dd6d2078f2c40654ca6de2a5dc86a23c12c2c34fd159f023ebf164b2945501e9c83a846f8d8e2c487ba8346344dc1b849227b27cc37ab738741e276cb20a60e769e115ec2b2b3e8ec52fe604f6957935f9bd13252b6c8668826db2bf6ec28a1ebf6e45f61e5fac891ebf6809f40c7e3127b2f730821bed6e0435da7d2f8835e1e3a25f133b685ae1e3a0430c033f8e3dce267c5c444ba0b750b7e0759897403f8ed1928f5fa0b9362ed38aeb3f0e33adf8711f6f995694aefb38356b4fd77d22db1f239cd1169c4daa86a7e64dd5105e6fb965350c03a0b73c34ad003d74d12c812e7a364ba0675f0c033e5a662974985fb3a7ad57b7a60b2e868192e8d84ba1bb7cb1267cfc072d65c744d38a1fb782de8af9128f15417ce52d2f65173d344b7fe9077d897ede655a512a5dc75eda71112db9bcc4e32c5f1b986945a9843d2bbd1c88b30dc49a005d74883991dd072df5d884de63969a4d88c2d904085e44adc82eba355d0eabc0f51fd40aeca04f58a44df8b815d7435f9925ec20ca63458fc33871512c7efc7a88f25436a26393a7b2c9768f80b4cab4185240bf284f65733d5e3443cf26f61a87cd6653818c96ae63540bd0cb881427079e7abce8356cd12ad3d42b5a999a4d5cc3e2c745b4d46c86a05694462f0179e9e5401ce6724ebf26163f0ef31eb3e4f21eb7e20aa8f462614a5c01958e677cd1ab856e1ec8f6070a687475e9f7f77e87c2184fe8c20861d8a18b204711ce38c2174690e308721c818c228451042967d471c61b67ac7106196d5d179ca3891ea13abe75d5d31cbd724c73745c393a2a20cacf3b43193e1d3f756c0b74c139829fe608d29b8ea75af66237457ce1093e1776a380283f1db1300574185a6a36252128167a89521e8cb6bc34a994db84284f10bf7eca13841652186093458f4de8a1b7bc86db7441b70331a5dc467420f365f2606ff988f2646fc932ca835129b7c97efdc72cf5d8f8d06140e3d46d45d4fde9c1946ecdc6654ab94d49965e92721b1ed0af8bde639678501e10c522d4f2d20ecab3f2162ae536d859a894db8828cf0a95721bd02f8a4588ae8ba9fbd30329dde8ce5b2d74a320c8b0d0158a6146e76c1bfd1e2a4d6112b88e3d73341895821d44b5c028160cb001ada3f9bbc424805df4ccd188a814d033aa058896118ffd7dc294e8e908290e057a6c426c03fae3c964a9f9f86455eccf081e742d6e037a7ca5a2231d41da44d65e95b4a3c1a815d8afbfcb9680461f310cdc07fb758735711d444ba05f20ecfad1233983a36d08b2776b9f64fb33461d1dcf9289cb09258f076dbc76c2d1e11a9e348467eaaed1f0109e0998864234bc1324daf04aaac333713451caa828124c036de06578667fc430a3e18de019d8f04178069e6435e7ee68742bc4ad8bdf9d4e7418d0bc42dc77626f6b189ed16955ba1d4e27fb9d7824894e10ddbe538701dd56886b7437c83aac08c492686fd803669d662d41c797acb08247f238775e8a56771c496b2c12cf374cab15edbe1d696deddc6b9867fb79668e1ec0770d3be2deee8eb8f0cc74f8176b9964eff1ade3dcf669ee0f0fc6e83861673cb391d6e6878e7fd820fb566583ece3037a9ad0ecb341f6351c6ff80a7690251eac9582ffd9d38d30f17820e5f5f4c34b59b3aeef29beee1b3d5d09cfd4213d7dfa10979ea6d74c461c4d9432249d1e05d3389be94eb8d1d335c033fbc23373faa2d1a0276e179ed99f1d78c16ddd663ce33ebd8567dea769e97027b0f603d9bbdb9f1d48e92a1b7e1b0fa9b2ebef68a294f1dc1f756fa9a3dd2f9e61626d0732be737179914cf36c5cb7737779d923b6dd81ec11edce49c05a2a06d9be9dffd212d0f8ebb2ded7ac8fb430d2e8d6da17a6cd8431450751deab4bc39f67aa4cc3c3cbc837fc90ac61100d0fc3346c03ef9283311afe256be1197708e1f78704560ebae8217a7fc078433b4a89ea1495690390af610c64d5a96ae264151dddb08f4fbbf3f8f83465c411788ad61d0de5750ef591916daa3aec9dae618bdf7a0befeac23adeebae2faad3aa606f6d591606bab29515111c2cfa4a96658c31b172ebaddc822ba115d04519081402dd724b6891e31f64c5d4f7d61bdd5ac718e3a9c94dcdc724790d64d3e3dd655a11f24198244f6292bcbc45723446708cbcac46b47c955d497d0192114c923583f2d5882eb88b9aadb97504630b1d3bde6aec9a03d59e1edd389e233e94a9759a4f5f6f5dcd117f54b51e55f4013d7d51314ec0001becfb03461bbd3f6068a1298b569ea65ec3748fa02e2f1d4de8f299a36936f22e8e4674f9b7045497b744b3d954b38ae00a5d81f103c69406c3c75dd5acd547d645c0468bc00a4dcd27adc5c82e9a23ebcd866d427364d108c8d0b56836a1df721813a25bf0a2e6eb86116142742b5ed474bd98044412581b906862b155eac09e4d2c9a4d888e2aaa13aad2624831c2063b16a13fd868d16cb047913e3e3e208ac554c506bbf52e6a722ff644c3e612da1bb644b684ebe912f640866e7843d335d0e8207a7fa878d154a4f4ecfdc1c11ada0abdaea486876909d8e64e71dcf9f58eb70f5d5f1abec625dc99e2604bb06b9c78f8d2f00dff78667fa8f8a15d5a02b8dbe46ed8fbbad7ace125cff0e1af9680bde13bea5abbb704f0126e0999de15d25c195e4469f8857632b043cfe65b219dd6b0d1da1a76e49d699d81178d1569770dbfd0ed6fe2f0eb939e6e87d7d92a55aa4c690727941d5de4cd875fe836a1ece865d2a38eeef09137f3dfddc4613aba6987e73f3a3ad220ddb1da8b2e7a87dbd1ca87d2dabdc6cd767336ef58bdef818dad108330dabd8a3f0dbf3cd36675eea2ddab0a897bf5862931d110645d9f8f4fef5455c5328c21a95e5be3b457b2e228edee309ddaced69f5b06e9b42a1ddfb053c7ce6dc0464110500c2584eca8f37d1cc3463150a3ddab8f8ec5e08c7677d875fdc24097658d8000f19b10102042cd8c11866118364720898d4e7d34a25880b01b4046a3d16834816411c8791c08193c03e4f0564b102357c728880b63b91ecf54383f5e5da6eb8fff78f51a7ed811f7ead65a6ee53dcd403e01c1dee3d82d6c8ec8e099d1e1278cc8755d2faf6120d774727301f12bf40b087a852ecc1a8dfe32fa85843bfe6a281afd8db0bb1cbbf5bac9e663d6d63fbe189298c67e4c1f3e5c7a4ce80593e0794c67072689662df5caaca56cd61268d65237e805cf345747c337670776c36a58454737ee38f1edeeefd3173312dfc9e019ecf016866167ecafe136416fa3de9f26ac21066ec4400e18d8010672c0c00debcaa4dce6d75fbd3a2dfdba1a193cb30d8f61af618a3ae8d441a0c35b2010086455d515baae67d574725355d6e5abea55655d6d563a0b68d0b72b5068eab406818e218969101d1dc1de8ed1460693e0ff59bd86abf8e927a53eff9cf26dca567d96112f5b831891fd61c11add6e62bad1980693e0195b82ea8a561174756b79013eb7dae4e9f8366b15ddfed9aec56dace099ff7d1c1c6ee8c2c7e634caf201e4b5b8cd2f697d9f6b131f5fe117f2a2b551afa6bb356b154d7db125484ddd54d5748f69f00c7558e1170d4f4d6e8349b05e0d9b190d4790c1731bcd0cd8ce68d8d2f0d9bf0ddfc838fcc50bf8e1c39a98ff1dd6c45fa23f9d871427ba9e11939a602d08cea8a8f4fb8fef654bc074248c76ff312bf7e8a02f86c4fdc705846c7499ae53a6657ab111019a09d08bfd0042162fd30d43f2e3951b6bdd686dcdb7162b8280de1522df26466b0f3fce861589b3a7db1c1d34eb8ff312073d88208208c20511c42d770511c46b38cb800002885bedca2610409c3122408040d7755d5d74d15ede2e2051fce8fd6982196dfdb87cc82711a9a43ddd662dd576d0e3acedd841ee0d57845bf40b85ae8730229dcd6d6b7435acfdc56a1804e0a311f890ec43f5f21e7f798f97979797eae54080bcbcbc86fff2f2225fe8e84810bf1ec47559a1500be3c7acad479f80f87520aeef1e5165fae57574be8515711fb57eb90cb93784aeebbaae09046248627a1cc87b1c48652091e50ac1292120402e204080cc5005e40e4312d340e8e888cb437779e8d6cb74f2fae5a1d772c533ae5028147a0d83bed812ad23157a888e8ef838e83eaebf4cd98d0acf5c3b803ce9e938ebf4c63b365b83ae6b1bc8d4697d1dbb5ac7bfcced76ebfa68fe788dfb31b77dccc90d26bdbbcc680693de7b4cb8c5f4462d55b396bac22947487dd1d1111e079d07957530e91d4477cc1164aeebf446bfcb3aa6375ae60832eec90d9e69f10c963982ac7534836740154ee977cbcdc1332251551d2b42eaea165f6e0e9ea9fe98798dea8c11a9ce6af04c7b9665d917abe1cc72eeaa2d8cceee3022d9dbdc162189e98cb21a4c7a6f542c8a6ad41f25c67c9b35dcbe20b0de459416d2fbf30237bae843d3f4fe74a1060cd4e899de1f186cf102355e30c60bbc6820bd3f2ea8a35d30870bd8686be59a666d96752d26692db57c9b35ebf639a5dc665e6dd2d23c4f65b37c00296f59d79c6bd3e6bb6cd67551d4e1e347eddb3d3d3a221f9f84a4d5bda1df5b6db496babda79b9b3bdced124312631d74eb200ab760d2b3fe6e75dde1b6245684d4d6172362b5f886bd45ba4c6a74871b748168a8ca8ec7a6931becf2372bc7be4e6ee445bfc92e8fd11abf429749d8315a5f8b68d5698d5dd2ba4c8acf406d62733b347516d0a06deb35dc96852189698b8e8e54a75e51370793dea9575ff00cf547a5dfabec43bf53b35161d2bbf5c3894cc709279cc81a9dbe3f4690e1c86f57f7065ee3c708b2f67749db4390f54ed6918a21e536253e83ca15c0f0b1e179733881064fb0b16204a51e1b989f40c6460b199b32dae1163ce3dec033adbfe8edeae8685ea3df990d21dc1c42f01abc00216a0ba3dfdb1894d5a02d0c5e00fd6b5ef47695fdfe7866142f8694dbcc6fbf372a3c338ab489ccc7c7c7c7669ebb2d60bab422dae1fcb141da21b644d6ee0f6b827ede614d7c9eb108ac8d10347331ed0d8bc0da502a5d4cfb6211589b49d7c5b457548070116b84f6233818d4fb33258c5e9fa9f7678a94de9f57c77f9020e3de1f16d0d1fbcce2e93ceb93729be99a265a9ace334d96bbdcdc5e426aabb9d7d6aeee70bb22a4761743be86792e460436814a93da95fa9da79c526e232f29e536f191d6b700494bf23cd3970fc0af0fcd732fe7da2c86c4359feab8692e1fa0ee0f0ba8f40e3753e1996d2431ed68a3c25467ea16074b89ec45d1c5a4c7a416e768761daded4d29cfe6ddcd26329bf75df7ba5f68772732eda84ea8c5787b7ebb189f3058ad4d1725b2fd6bec0241590961adaf3d627777cf670cc96bfb25e264e73287613937378a6e35789c7d6885f0eb52d00ae1778bdb99afe86512bfd18c499012d18fb630a9b55e187ead75613ddaf0b171e1d37cf956a49dcf6f9812ccad5d8c2971842f6f2d936c7f564047832edf2eab5d0e2372511cebd8ab59f9212a27e816688e6e9e8f4f87fedaf5860e9adca1d73068b6b6f83857bd2868853838bd610b45207a3129bef271e4abc35b0fabd4419812af1508f41ab61e9644bbbc05e5b75dcad7b0c38e549738a015e21e5a21cea7df0a714964cfe78a0ebdbeaeaf410f4d273a4b878f4fefac74f69dabb35fcec94eeccbc9f59dd8d9173b92a443e7063d9b3a4174e8d7d46140bb42a110dd0d029ad6bb78d6ead7adc592e0220b4711daa137eca681517baee810659edbd8acd76cb7e61ea77d5bb66f144d346352a4b421a8c53a584a64aff767056c502283bd3f2b9063578810d3ad9d56e8867115ba85495ca5b11532f1944c9adea6ef6c58342cb277f7c733dbd3b13d82087f9a7eed11d317665283b50fdddad9fbf3a346b7fc44a15b1e954c0acdd6496cebb42ad80a095d2b0474ecd7adead4e5a71fcd04593cacb2430ffdda23f8a1637bc476e83c61426bb0e00fa9db26b504d31ca5262b47b31554545d9da295a7e5172b42bdcac6c9a9baa24d2a49eb7b95cc7f8ff9353c9da7f5da631264be45e4c8fe34d35ad37c8bba168adef7a7f7678b337abf4e68a68f49709af5759c35e7ea77482f2661cb241d58f030e31979f32cbee07c0f02193ff25d98b46bbd2b9e7a8d6bb3c5055a6f42690d2794d611567f33529c25e2a375a75fbd1ade92a3adbbe6b646378b85296d1df69ba32dcaad0808cf24786b1baf036d2b02ba3773ae1671a829d355841302597b0b93aa399d9af20dd391fdb242f6922e7d543289671ebddc7c61b06ee083b0869f7c0dbfd95a3e7e3a6344e474c92c08a5bd437e14e7dd5d2ef1eee47cf36f7a3bfc93525ed2f71aa773353f8efcf8f11bff85b6c5cde956c392e04fb7f86ad8f436fdb851d2ef7437882c022f239d669d8ef32e6fbd3b2a18899296afe1dafa49ba1be4315604fe51f91ad73e457a31692bbf397954c606dbe06037ccdd6ad86111b6b16a4fb7876ee263bb0eaf57d7a957d56c9994152794d6505a5fd7e3dcb678dbf20335fa1512e74571e2a98c67ac4f56a8b5b518918da28c49d565fa7ac388345a7bbacdda1a279efa1ed17a9a5f21fb8c67647a6fcd8c492b7b3a124fd16785aac3bf5f6fd811f98aba30697fd19665d2c6378ad69ea67ecd36df5ee3ae59ab5315adafab5b6fd3716d7dbd3ea0a709bd1be4a2b59a3a5735b79ba3a68e6bf74d41e69eb57b0db3ceebc98ab3c2b7cb396a54a7f54405d056bbe414d2d37c61d21e7eb1f6a9413f58ed0b5365dadd6a0d2382e4455adff19269dd71bd4ca2dec233d5dd5d2acba2d53a756b9ad6dd339e99ee6e59b3b575bd618f49d4adb97d4dabf136076adbd35c26915c82bcbc64dd688d77adc5b94d4dc980b903bbd1162671a4b58a5965afdc2822c130c97dfa3269a2355ce376be595bebb42a55aa54e9dad3fb38775cef975e4ce22f0669c624fed5729ed9bbc3f04cbb3b8967b6dd0bcfbcdd83ec11ae7baa06b7b06eb27d4b66a9b14bb2fd61c194aebadd72cd7ad7ee11cda7b7c81aa6f37c7c160b4474386e1c131a120e4a8eeada16f0ee0dc7bd1dd7f08f04c3a41726b52c64ed3548c3d7f7e034b242f6f21dbe41f80edf71e0a30e60d2d630691f9b83a3eedd1ebe3ab7777b1cb754b6753463d25e4ce2aeeeceb9d6f6aff57ef7b9c0dac68ebe1fef15fec13947c381ad3566fa073290810c6428438f5fb3c78eeb228bc79c7ecd9689669f44bf567e9dc7ac2ecd83078f95ecbaa65fe7d1728baf6bb67cb1eb35dc5299f475c68af0f8947dba084a10bd7251461fd0d32b74941d67e52ccfa665926856262dba446f115d1f89cee3138f8b8e442b67f9cab5a30c2b2b3ccac083c78e6b07ad2b578f1ea3977ef0782b038f1e3d78f05869e1c1e3a2ff68c9321e5fe1f1e359c6e32bffb1c2a3c75b6eb5f4c0187b404f0fee315bef982de731b3f798d5a57bdc613b8ec3e3d977f078761e3dbee32db4470f8c1ad91ef5380ecb79bc07bd90908d6e58cee32c54741e54f46592e83be832a985569736c2e7c78f16beb88d8eb71ae8d6144da2d787bd8a2cd1bc5ab7fc9a2d173969fde31a4286337de5d3459fe68ffbb8888a563e5597beae6b7a365df4ebc78f1f5ff9342b939ece5811243c9e5df41d5f616161a1a31df48607cde8ae9095af5011ad4c5af4c9e58b216179f61aae2bff41af21642b34c7a5a78f567e5d44273a5aa1372ccfcef24c34edb4ee71ab5dd99dce9305f44e6361f9257a0d5f3d6e39992efa6247dc7bd09a51160aa5b5a8e5f5dd60df15525b2a73d1f135cec7aca2ef5d6615ddb01823c8e265e040693c2f2f8d6819d6cba8d6a1acbc8d2183dfde153aba79affcbd72d1775a8f6e562ebf72e9a4f54e6b96165a7bbae5a2959616f55859b948d483d6ac45e7f11d7399c483d6ac579ecd952fd38cce8307adb2579e7d34976344af712bb33269f9caad159c160aa535cbb76b8e4bb35c748b2f1696ef386337f22cb758e6ca2d8b312496bb5a66eb95595d5af4c592e05e790db34c6e165a5bb37ce5d9ac2e373cde42778564e7d1f21ac732b9b359b3577f5811ee95b3eca037bb42446fa12e2b44f41a66992b5f915fa138d65bcd688e4b5b5f7ac47a6b35ee9a6a65324737fbd313c581d2ba4928d3db77260aa535cebbf56237bfa2db7d462f31a0ca8896d11e290eb7b183ad1458d61b35484cd9f59eac71c05f483046c8183144a60d8c484b262798ec7a2f67d234c13069fa90ec8294c4a42908932618264df49d6a91d4a5be66f7606c43561df06098045d5a5c5e8ce8e97232c2a4a60409860d591dd2adb556c50e1349cd2af3d22e4f3ef2f4b61c4ac5aee1000dec5001d34aa29009c224e886acba34ec96bdb5f3cb46efe56e4b75e9168549f04d0a93e06b7869f36112a42f6352cb41d66ead9c227c8e9b04d52a7a64e459d72a7c7c7ca4f4d2ed428f6c2b34b016060e34d7d5e09055179786b48e1240648be0571fdd5ebac1740b52d10ab3f69e1e52afcb08134d383135c0246e7dcdba53683e9cdb4a30895f5d9af92e2e2e2e57d6625996bc6559cf7ad57459217c6a1e66926610263db8fd22fc6b4847ef3dfddad11726f1e5c8daab8bd5200bacf7da1baa4b4ec353d1b0ee34ac55b806834c2609f92858116a62d21abbd68620e166906e33e3ec7cc95575c933d3ab5f4c63bdaa60148ee12301e8820fbad032c7a5293a3af25afeb5a423f89efe1bb21f1f1f9944153e538291c130a99d89a3a9714b874bdd39e0ceb17134ef1c43907082499c86acbd0e698e5a180283041248205191687e153224fa5dca2cab2eef95245342630e681803668814a4747fb46e1b3deb90768263f85266998bcb4f22759369996e6f45c8e08f573d6392555514f5c58e94c008701003958639433afe75936da31fa59afbc2b21c7d7109d2ef56c3889098d42c1a630d5fd8a5d31e9322ad39303ddd7a0d5b158949ef90d61dd8f271a2d4e18469f886305ef214d109c2a47719263d7eaf2d04c1ac90f78c49ef62d2db210d6b500796d3c01ec1d204cf88424af08c75de7107903276b4ca60b49242b4be2ba97907ad2e5d49d30e1f9ee1717e8bd2f21de737293c43adcbac38a412479359715471844075349f7a153c033abfc433d8ad2f6773f0d0809215e2809a7df2629291acb2b866bd7518d1c3a3c52d2c978ed588662355d2606723988de657239aef44a504134df08c2561663517b3687d338924e3b27158ec66d0461bb5f958a1f9b4f4693e6db4e1a6342a71d2d146c8266e73f41cdda8b829bd42f4c4d6aee03324f41a84f48e221a234caa58668d8ba9be3287b898ea4c1c8d882ec74cafce84a3c9a80638a6fa45ab39ba7a5d38bafa0ecf30c9e119ecd5a9e019d0ab57c133d4ab8366102655c7a60c93aa5fd38875315535af21643c04632705eb6e5a8fb3c29c267b75a95947501cb2ba70347f72eefc2898847278a63a3f348d6012ff9a4198c4777388a361101f9b128c8c7a9551f2a255c6aaa85965fdaa0103629705ea6e5257f56a72ee7cc90d0fedc7a9d1528d8e6f6b64d3e5ebf6a1a59bd7103235a6d6d6a0a3c7ddecf138ebb23183bd0691e9588de8588734121d635d353abeee1a1d5f492fafe7f1fa225d0cbfb674fccaeb6b9696d7ab775cc6d1bcd01a8ee1fba0cba416872878a6eeb8fe711f671f3ece57c2332ee733e11959fd381ad234a4867acdfa9a128c4cde099eb9ced700cf5895a3717131fce9a3b9b342dc7f4c2a7ccc9cc984491ae018d783d6ada3dd9707dd319d60927bcb6c826532b1329530c224f76c3a8049eea159c32477d0c4e69bd2c5d150954b73cd5a4eb3caaefb46e72c1b3f3812c1e25b0bab38ad4b30b2e9d4ebd5f16e4a303279ead2d15832cb5c5ca64aea2a5d4c95a1173f42fa98d4f68e29662d584b6bd67bb93f26f13447d61ecf3891496e64fc7a5158775e37286c6b5de4b8f44d00bae0032fe4b874a4a3238f0a93c87b7b73d79bed72501445518fa7601873f1c3537e24d3b07c9a265a77da191d23732185b9e0293f2c85b9e029146fc152786679ca0f7341512c85ca7e2a1a61188f8afb82bd681d2977c1a4e92c85b7a03c8549d37ff094185faf38dd55d931d20afa54b16fbc689946f47816eb71c7b53c3f0b6575c7b5f518a275e7b53c9ce635840c44775e4fd4e11f46775c4fd4996efbeb89e250d3db797dfdedb86edfd71dd7cd0ce6e2fa7473519c76d813b4286c6f3aa495a774642e5a11315627bd1c978e9f66298a7edbfe6685b971e9ca243671e94771a622d3a92bda1d2352dd611712327831441290721bd15bee5a2e8e992e9a2b61e0251d8de84950d71463e5708ab1729699710cbc352f26419fae1ebf524dc9a478186a658a5ecdf66956e1e3e393513388684a17e3a46cb2ca2adbf114e602c4221ed0d1effdd9421b1c1f998654bd668dd64647bfb616001953e9d734435c8c7331f135bb3efd4d7fd3f4f816c7346144a61af730222dbe9d21363aacc733f0bbfddee8e099e98fca1491588dc8bbb17aa2431ccdca0a55e262dcddf72f0e69f775342b745d0cf68abaa5a3d169be8fd1241f2ec4d144f4fe70018ddec24ffbe8fdd142186db90b46793d51f6717de03a5a4fb4d98149efdbe26809d89e68a383490f7eaaaf772aa4d3eb9657d935eb89d6d011a2d7eba04f97b4b6b05c7ee5eae92dd5a7af3c1fbba8569fb0afa361a1d8abcc28466b4e4bcb63f31a4216a2392d7d59c7ae2164d7ab75c692c0897f7d511c8b5e392d1dbfaf392d0dbff4c795842c1e7b85c7686d72c07e6f676809b84ebd613790e2c4c3bea8ca61486047bac34d474f6f6768454c2e4c723aad4a5367ac88eb76a659b9fcf2b43225c74c9fa6bff04ca4eeaea7d7b01247b372b7329763dcbce67be1998956978e771705a7fc62455eb76ac2cc1726b975b4ca2c73e9f6ead2eedd265a4b3dfd4d0aff261ded0cad88f7f7094e91eab886908e1087e4602d1019e5b6d7d14caf59bcd8614498d69e52194d4c8f135d17e3de2c1e61807a7c13f293fca34d50ef14bd8b71af8978494b998b718f745d0cbc04d6c6c5c5b857fde2ebdb55366b33bd12e1d738d2dac059652399ef86365f00eda28bcb0ce2cb38763fd6c25499261d0de520849069ed2995011d74566902b0ed111c01240ec29baa610dcb7531ef70be334684a77963b550c3f384b762db235c2f91277cea16e5e262deab99b998376144e06535baa93d38687eec413375f28eefe9b816755951069fa73a75a40c6a1b17d9fe68a18b764526509da25650af4e81b5a168197c3831099c80013615bd8b7927b998770b43e2ceb432217f0206d850a75edfef9593b30c778bb11b515f3ce12376236a778bba78663d4dae4e837357c8e54095020da905ba9ac56d19c4af610d74cd40bbc75208d6b61564eddb81acbd8d2f9adfd254907119bd3f5718423395de1f2bb8a049bd3f5578c2cd0e167474ed4923760d410ba4d01b6a2ac8dc137a7fa6e0d34d8dde9f28a4d1417a7fd490a36149545788a37594d07183d6e10175dd390319425d77c6c0014b0ebaeee82007d619baeea8e00cb0eb133014a117890092d046d72768b4d12b7d7cb0add26f16c3d2693ddd829fe693d653a338fcb63f6aa8d1951b9ea793b642e07764eb6c95c65e5518d35d21b06a736fcd1a0fa31a981a6e34f6ea16ac56484f7375876145e4f56a6267ec08f5d6e8ae90cab4ad90f6ead7ac6c4191ade3fee0d37b396beb9ea9b11c2c7959f9fb9a1d375a5fe3744add2e5b7b6bd779b9a24a43e1e93189a7d6745a1f3afe4d879275cef3b9a22b5f67eac315551a8a6c3e352bb4de28cfab3253be8e8c62822bda0994d8d4a14cfda4a7a977ae3a131d0dbfc33aae231dddb49f7667aa41e17eb79837089f72df362b35bda6de989969a5be6fd412a5961487bf9f68913dffe6f5a338ee8ee2ec79dfde221baca645c6772d8bec6d9d00f6cca4f652eb697cc413a2f45b62877ea76e2d08dea313dce805811afd8e39418e7eb7b0ef5b5c5e0e43e299f8f74751d7dd755d5e974ea92febdb56c8c2191d990e3f4d933555a0e9f216557d9baa681179ead475395bc7569dc2693d5515852179af280e7c7d7fd2d3f11243f24e5dc7897f9f688dbf28f59e86137b357bdaeae92b34e3acd61d357130eaa119254a9428a16bb14b80c61ddf6cb4d82540751d69b3a9716b63cdedda735d48c8b03b87dd588fb462b7e8c8fa748c8e2c2a1f67f5494e387209272749516bd6eb23eadbd6e31c51b408bc758b56164ef51a9158745708d56955a83bab63eb6db88e666daee8edbd8c114390e0996d9ed653b145876d7c2bd47cb2edc6ed4c581c47bbf35c22abdbc606b2d7a2aea1762d8e1af9d3ef6e2e9155690607b2d755faf4bbf4e92acde877f9c300f7de6c1384f1ee138430ce97c6460dbbc0bf47fac22448f73d66369adc235c7d69f7e6e45dbc8b771f22a3bb73f1ce5991d7d818dfe247efadb9476f0af170b6481f935652c1dac3bf3d227e0ff788d6463009fe092dc324f8c55cbd7caeae57439aeaf048ec115f21f21993e4e187c4c8c3308de4185947467c41d3c231f2f0a33da235b4a8f9c22479d7841a8e522ea1e547d35d738fa6e9fbf38346efcf8f12ba65b6a6e80b93e434e10a5921ad1d0fac8da2ec45aa6eec76f6704362ad93a5b07afc4e6b1629228f23afde9f9f331aae90c897dfc9afa6e44fd397d6e9d62d5e5a5befad59f5a95de2fad2665954e7494f63af22e0831faaf4936d638c2e52d018b568ddad557475e4a2b58aaebe38d4772965b9c65891aa7785d4b8774dd379ea8ae24ce73a4d97737b923b7bacf6806095b2760af4d8f0ad70cfc2d11330c086cf94875b104b80f788e80a2b75534131e4a2a12115434e79382b50c4179c50c566ffa01856f02c16a6d848eab35898524b0da97644b4d5160b536cf644f4c2b82c588b352aede812070278812a78551ce3a57c5549525772b657f37a9c53bc554d48e3b424c48a60afeea6fc35add73869ddda61d0ac6d879b97c853d41baddea6a4f52d935a91e8b6dbc44e5177b45a969b9343426a472bbc95bd86dd456ba9afca65cb0bc8688927f4d2f201dcb3bb3f0c09a9dbbe4debd7e467736d42b38ceb9acb240a2b12ab5b53bec65592dae659db1b8624a621bc8090352abd8d7b2e8e527a7adad9e16e77b3969c409177df798f670c09a92b8f9bb50a9d2090c025ae095568b704de82d7041d24a2211147a1483a4d78871d21a21d0d2d7cbf79379d20ba395a7b1a3ece5aea78c68e10d1d5c5ca7787fb1d76d811d8954fb7bb38ac7d128ed6d698918d523d4a19ee1635f787d9e26be3c094781812b7366cc1e5760fe861414b24324b07046a5ade614738d4d3ad064195678a919a9698919a249ab40ecfcd15c4037a70d0122bd243a57594b405812a0f1539dbade98a93bc2a49cdb5b1a8ab9a6b43cdb5b1aacad369f8a0e3336adee7e6dd5187e95411c50457f8b4135a1d63489e8e6b1c37d110b8f48e4b47f7e670da754a5ddde8481d41f323c33bc8dc647bf8da62ed16efbcd218a31f4b3e4fe789f25d1c4d94321ad3381bfe4f946617262d14457a5dbd3f697c21c327702e8d2edabd455e17b8007a7f94a0865e9073efbdc724f7f800dcfba30439dc7e81c803329cf7dcbbe5269479f32a4dc33b9169487542edde9bdb6e2e63449ccea1196ef800408d99f617d16d886e6f619a96042a32a02a48b0832329814a7b5f379a975003097234df85666dd5a93ff8f78721e1bbbf69fdb88090c974e55eb5cac1ca5577d72ff7aabab3668b5805e4ad90d40a2b5e0e3bc277b7e4d5b00a480c68d16ce4add0a2d9c4782ba213f2564827e4a7d7dc0cac33dabd14312484d1ee0ebe7a145df438b1908f8793a7b281df3d627aa43c954d7c6d481c1352a843389b0d35cbd061404e565d8be96558f7a96ce0df43df4642176d59b349d5d6af795381feb022ed2bd7ebc4c9b17e1dbbc38acc26d6b1afccdad3a2f650e8d9bcbe98128d86da4173646168c071043ba0c146c3f4fea031061a5ec0cb9a231086456cbaac39b240736461109bb023d8d198a377f848d5ee91622169a9c7a6a25a4c6f36109b6248992e6abda6212d45f8e954a43c103ebec4231f03af71342d14e651be6c77c8f6a7c6014676e963d2102619515515b62ba45e5d572e22d2fe9061b75e594e9d7a36e5ac59cb5badba75ea2c0d2bb242b3875ee344f4edbc065159abef3c4b56e78a88cc08a68c381a1e675ec319cd5c59556555542e269944d195e1195973de93f25d8c962f86014e4233c07bcbc5688952469509d23ba616b7294300dd32338eb9debc56584ec15ab9198b886c51475f3ce34444b6408349db4e943169f446f3478f16e17edf1f39d0d1ee17d3503e2d2e2f4c33844d3b4c1834f7f1f151824dbb0c1ddd6e04cf0cd1ed4378667fd2f045b72371854dbb03de24c2451437896c11c624b2c518d80d37112e28ab2063e9fd91838cde36beb0e2ade864e7a2a295f4aec392d019ddfce8da83039d2d43c35b3ca1bc83f2137c0d4ffc9dd853652793849f18c243ba1b84efb60cfde6a3f1ce41082b3b81f11dae4eabd2adb1226d8ab53cf3acf79ef55eac6215ab5855af9291ab73dc55eb57d14aab73f1138e3b0e9fabe6fc10e15f83e99b1734e62047bf473a3af2a8d05e6bada5a18734fc9055986b3ac7b6870ccaf105a4b09bea90d6eaeee1b8c73f8c081abce87ad13c1bfe39c3164d498a2f49ddc8e3b8cb89f32e59e573326058bbbbbbebdc6bfdd6b975ceed4a192fac85b16a8687ac35be24912cb2eb2508cfc40769d6d2da9fe3f61e04ed0ae113e1dd922efdb68ac86930698a11b6098669b0bfbb389aebcfe28324132ee62dc730ad3b8f925c13039956f83827a4b5b54f93d16845f01bc3b7c94968453035822201e71026bd33e168606a1c8d4b907e97f4bdd9219319d394a8d0e2369c846680f6f7cbb24ab4a7d5381dd7dca6b44336d1b771488e6c08431f7af6fe84e18d7e830e9797efa6c1a4c68f31224b60a267199ddab349fe70755955d324b34b1dd7b57588e2b48b2aa74177b89bbcc392c059b9e813e8a25aeeb8946559f51a57512c59463d14caa88e6b9ce92daf6196ec2d6f09b1bc62a175877ba21476597de532f4ea8d8aab40151517e3ce32b1acbc92215a5b68a83acb1b1596ea9a6895a2cb5ba1299a15b442076bbada1c321cd061d75d91077d876764a5a88cd69e9c2a1a68565f99a06373ba9cd81b86e46a11add5f489f20f93a8bb368567b0bbb72e1a9531c8a0d24213f68651aac65d13f5660693e0bbe8c645ef0f19a2f4db20ed8e4c8fb7dcd5e81064a2338df28f44c2013c63dd9ad79475c86ad6aecab7cf216bb5756d54c755a922a505c3b046d716a55d6d694c619ab571fb03861f8c93d08a685f8ca28c462b02ca66c691d7d3046bd604d3d485a3a2958246e47437512a97aeb48c4a19dae800f4fe94218c5dd0ad09210882402090dbe186d32104c183ac2164ef923909b76e5d07cd9ab5e3521518b6460b612090f5ac3d03bdc681acebb8ce5ec3a159791a07f4ebfcd3184669dd79aeb59a35836e1dab391bc242ad3ee9a9eda7db43a1764ea31b88ca6377bf2627a1156155f3495adb3bd426258cc10eadbdf81deec99aa631c8318634c6404617a0f7078e3a26ba9319d3d7bdc64dfb6d17e527d74b8bf4b49cdaa3d5f5f4b606ab515bffa4d18dd6bd5becc6f5f4bee9efe7e9b8ef378baf9c4667998b94d933ebf1d5899e4d3813ad6dc7a7f7b01d530b36619f5ace21d05b40a0b3cc155a4534a32110365dd344ab45775e57b4f650b4cae6966b85e513e098038e9f7e95d3e8ea1eade8268633c4b0051c663411bd3f70486931d4d106e8fd11831a6dadf5ba365a5fcf9a754f8967dde5198d96806a45b7cb365dbf89a3536a795ee3c635e434b636bab4721aed1e3122920893d685492d2cd3dd9729be7477342d54b6cc1e3acb79457416d1ad49abb5426b4ba345b4e65c9dd11005d1f3d7d1baf330eaa68ae6e45cd44471aa9deb8c8677e724b404481a3f5d0d696d7f9493d08a705446c4b10b161418697dbd0bd360a0c3b730b21aa4f941648c1872dd3a1f896bcac7a475e1981813e38fc5dc84264634ce7b9c46d0800e4faa0e1f846942872f342b87f092694487cf78c63accb8e9e0e530249e89c7ce0fc233f265ba58533425c7c4670fcd202426c583e639263649af9d52e662a683b54f46b1693db29facb33e3a9daaceb3bed9c5a5dfe3f3a7ad661f9a291dd74c47471a4e118aa38f568dc5887f33c608f9f17b74c438c1085f8c7731b638302231c217a38330ceda13237cf0bdc7538370b00ee734cde5b9fd69e9e89a436a2929396b9cced4b2f27484d374175fc3d3f418217c53de74d0f10c7c5e3009bae8e4936ea916676059d6626fefeddb5db8fbe07b6b077e67e0f75a1cb3d101dffeed3e334879bc054f5981f537db19422fac36ddd5dadc8ead4dadb5f73dcfdae0dc26a515d1ce1891a5ae3d4e7ee3c60c679bf2412e5cb0ed0c2e3dc9d78c7aa5babd6d6bb4be5bb49a7e78decdb6824534bf2106cc020c3bd836f82f4c7a4146e20f41a2c6013cc37f69716129cc454bc0767bab61291bf5e6d2d5fabbae9c6eafdecea42deb357c92f5277fc7537b758bb6576f159386a7dcdffb42e676d4cd4273cb1a0fb2fae6ffe7b7d104eb3cf3a650e77d83d4eff07df1ba78535e0c9e17cf8bc73330f49886fad6ab1ff3abab39a6bee6d6fee3861d7a9a6ea696ce4d93a33a53579e76678cc8c470c2c3bff384bccc7f2b99666dda2d8b9d7beec9f7ce33ad8a86e74607848fea4ced1cbda6e4657a1eee99381681a23bdc7b9c27778baba9af175fa4404a0a9a175e50121ea1096434337ae7314fe12e780b96c25cf0941e9e025ff375d5a08dfe421c72df7b538eae392def1dc2a92b086b9cced4aff2c84fb5d9a1eb0ecbefa9bf16472be2418cc84e5f24db55343a98247b7ab3d1c1248aea4c3da728e723125f12cf65ef0a65f08118ae46061186592c3d16a4b28c85a5aaaaec22161616f8ac86b6217561d7c4c130a6588e5dd73562a10fe8e9eb3acb555915954d11ad21ecba300bb3308b8585e52db7ac6358666556c6f2d0ab4b5b9675abc75940976f59b95844df711e2bd7cac4b9debe437489aa4b5b59265ab945a104d12c1886b1d0510bf616ec2c9895652bb4e6dc124dead90cd10afaf5809e1e61c7b96e1dbb6efdb29649555551b43d21fb811894602d8c55b53164f1d39fb5486675b8722f90d1edd61743725d17cb8e10863dbb2e1008849de5baae1dc75a7478da77ccbaf27dd576c56a21cbaaaaca42b22b84ca42d764a1b5c50b5f7475e96733dd0b5df4f41752b3ac1eea22c2e3a053d39a9589b6288aa7afafbcba3465511645cd1ebfaae8f2a0a9c3d37accdaf21dcdd270aab7ef98d710b216ead2d4595ec32017ec156551af595bd42d5a4718bda17e559619fa3557683b9f9afbea883b925d2130cb28a53e8d9edc804e1dfbe849757351745f7ddf9c6051df571485d2832cbeb563d20c4716ef3ebb4da266e540c63a865dd7454712c342c7ce7384047491f56bd651f66d267dddddd151466f4054442f2947217a5351948fd16acd8a1d6bc956ba0acdcaa443b75a5eb3b6342ef2665708955d97129ba25f33741747d376632a45b31b63bde58b2161d22069eda89ebdc655335e34335a9d4befc056e808abb2e1ad4f67c16e71b7cc4ac4368b942d15c3d808e630eb75dfa0194247d7450fdd25fb8a73d5af63bb424474447d541dbb75f96cee0ab919511412796b44b91b5921ee327d49eab2424034c7a5a9cb6397e1218e8eae577777b1768554777484557444611f5d77c76875ea7b49eb486217bdd915e26edd541475b9a37524e98dcc0ad91b59210ca57593c14c60adb54a322b04ab4bc3927067be44e2ce971876e3ce77c1a6ddeb494f5bbfee2c1a8220bae5ee92ba3c4fc6b007f4740b1db1b0586e77b4833ea0a7a9965ba75a6ebdc5927ccd7ad58b56269d73372b939677a067b332e9ec2bb778bc669db918787755cac8baebae8976bf9868f71d6f99d5a55b8ecdcaa431d129cb7acd2c5a993475ac6566c7e6ca33472339c69d07a4d8af63d82f8c5626edbe838ad1e3355c650bad590fba1c13a2358e4544ab4b876675203abab0eaf13a3677f8a2f035ce715713677a3b35735cb4b84d199394eef2cbccaf5b18b23064bcc4e4688e4b1f812b2cd760b56d37222f3304defddd858ea0759cbfbb6d4b47d3f7f0d61c4d1fc9f3484a49779ae899d61c977674f48ee3fef838eb287ed76899dec35947713a4e3cfc44e321a46cacb16bf4fef11dd39eaa3d1ef8c0073de8814fbb5bcd07ed0719ff669dd0ed6fe376c1075df841b7f87e30ba69210306ddfe2894e69386555bb393f69d37bad9bbbb7dcf5935daed2eac9aea2364dbdac8ea3b32b523cdd8168c93a4667ddd1a1c2e3460d615422d84f86a6c64f56a90c3de05ad584d94bc5adea32d577d69fe0bbd98d4e48cecfa9e31895b98c4fdfa7a6dada4dea3dbc233eff5ee0c1378b6dc7b7474f30e0f697dfd68f342d6ded36c01c1da9d79776752757467de73ecaa544900bf2ef72ee33869bd55aaf4ee1ec1afbbbbcb805ea705841042a18edd7541484ac8c818718044c119dce8e535b850830493f66a18b142d68c36b6d0c6e38106e42b857dbdd15395dfd3141ddd54a74e51af2a8961971865a2665eb6c4a8c42427498d3069ff9e03608491ca3089dfbb7b94d49af9b1a16ba630dc3d587b6cf0f53ab9c97dd7b975c3d26aa477881a3b7386f4ee56a16685b4c00721cc340013851c985ff261216b87300d0689214c8a628841f697f715b287e93df7a4b16a5c61d5904f893da2b442da3748154c6a3454b89826630412350e30a2036b615a2e9797f3ccd6203b0c2948d6e2f222dfbec3c850c9a626984603469460a28d9e98e098e97b984ac6883d027b9b645ccc647d9aecd0d3a73fac7391b6284c9a445f268e46caac66222b5e3dad010e33be202376e1059d2cfa9814a940f8c0c8e0eb45471b5f78c15ce38515622b63c22456120593f82029535dd8cc59217c1947d3cee098e9d3f48683acb62d7a7add397aaa57dba24d695d30c9e119ead3a9981a3bf509fb747d9a2e9ab36d3446047b4d5dd35bce346b333d889cdb0b335161b06af6082a56c80a1f14c2b247c372aa8a708d95d95a033ce300238e46c60d59a3463a9a28ac2fae4974f792752947bfa0310734c0e430290a26d99460b003782608939a4bcd10921572691d84675a98e442729b6173db62994ca20023abcd67e9f079438bd2a4b42d4a73b4752a76aa888249ce10279468a009268c2891f12405c290f688a9bdf71ea504939e1126bd75a3dfa73face4577f724a17cd0b9ea968144cfac2fa76f577234f3ecba57a6ec8102f8c28c1441315cb3f09cf24181726f1abf94272345470ccfb6b34c8eaf6a1df7bed2f8a11257826fe9d89262a2a76968e7e54e80450304357ab4657d56b395f4ff1bd779846257ee1b9bfb8e42093cfc5882eecee5e0a59bb64d28b8b156ae2d2383bfa28ba4777eb874f1a1d51c7716fa73e9a3e596fb72a3a3dce3d356993fb96b371bc5973da18adbda1c11a3db98977871f3db989745708df3ceae8ae907582139ce05a8b628d3edaf7fd6b7fe2e848fea3781c78f748e1dd21efbbfba36ea24b97f9fcf63edb4fe65e21cd6963d44773368e76148a93edda1bfe32b5e8dc1ef13ec40a818f89bcbf2033efafdd5be3b8e7ba3ae7dca4019825e4c0f47b6c96bc943c0dd8efce39b71204b342f654644ad879f07d9aa6697ad1593ed8dd5d07e1bb92a7b1031bcaf7e282868b6f31c6e8761b115a88d0daf0f63d7e97e4130c013eae52aaa27db142f64c5c84e04245ce1aab86038c0c09c29086512861d25e26c84f1026ede3c896616d798f562b3a36a42d6734fc7b6f40c81a844d4a8b32650b26ed1eb1cda779d1ba0861c10c59db37dc60258c4882114a189141d68290d0f0de7b87f385278e7e5490d0a8b45896913aa4dd1b13b276f7217206d6e866e7f5d22898b4d54d3bafab373aba697474c36ff11cb1b6263aba919f3ed1fa7abaa44e5ee70c6999634453efe99eee690b5ed7e1459d60d2ce9a6a06518249774d3069e183940926b966c41cdd3cb7c456a9f2d38eca3069fb6a2e0d66b4dbf17db6c3343cbe2f4926bcf2bd11a61932939d4492634644538774936385b0c1a4761eb3a9c1317130a97dc76c6fb898f696d9da7031ed249e0902fabef9b4284cd3f27ddb8267a6b42ebc30e268380ca6542c8b45a6e5c3e099317866ba388c6bba688e4bfb61990d0d6bb63486642c194b1c6fb8c1b28246d6861b4c5342c3d128695bc8d1a2c031071a74ac51453663931ccd902abbd9e09a32eb72baaee9ba56c2a0820ba69c21d319d96c6498e1e2826166b4315a18a229c1c85c184ca34493e26864168e33d68d4646bbaf94763fdd5e9b1746a4a3696fe01877d7ecd0ee1668c26e73bc81b7e0198b26b1fd05cf8ce4b733394de982bde099898a5507cf341f071addb88b562bdaaa42ce9d1068baa66287695ed809172408c3c0b8b6a80b93626333b6d53291e018e7de6690d5d687766f7de099ba3fecc351ecc0333567eb68eb73bcc1c27eddba2e0cfb6247b034da70435bafe12b67d968cba28d0a935a1ba385d1be6052c392685e4ce982cd10b188093acd0826359a1a17d38cfc4c1384635a0bd34867441019be74342d1c69ab412663c410240e430ac2332d3c23efeed2d2aefaebc5a88ae65c2d179394c4a433a9bdf9609c6c1c87b5339bfeda0ac537c8d17b0d304d0ec7c4ef99688267aeef9de099e700a651c2fb21f1d27a46ea35f62e4c63c4c5c4dad27b8983ccbd5405936e90d516a5e3a73f6cf57858d124b6f990b62e9814a918587c158ee6714cbc94d1d1ead2f475ac430d6ff8619ac4a6c13323feb66412e49fc133fc860634e3c93c0946f65e9f1ce2e8986344f323d37d13dcd275d7f0a2baae21cdbfb89ae8ce6b668249ac841126f187381a99f8241819df88a3418263f8510d92a3696a704c7cbb4156db171d5fb78e8e6f63f04c7ded0b9ea1124615f2917a7c8bc23335a7cdd1d7d5e0998b67a81a1d9bd5b02391083360a3af9c46a5afd73ef1970bd3ac4d3c4c6b63e24a05efbdf7de7bcdd16d79686664ed2f4cdabbc804e12d2c8470dd7b2dd9c5695cbd5530a25726480f5a1959fbbe29a6afa8a4a9156a84a19e441d5308cdd048830002831400303824148b4703f24819661f14000faeba544c1ac9b32087520819638821840003044060406486004d00be28462c28a2f6a0a610a761374be6040d6f85fe866efb38d9846c6f50a36c3692030ab77989c490c3d599539b2367acc9ee78376b35db38b11f3022bbcdd0d7371409efbbfcaa7d7d4aa3cbc64767915124db2f65f80c2bb40bea2112f58f71c45e2e805c403c15d3ba53e0302e8b98cc80089a97357470cc6359cf2b9e930a19a80380e685fd157e9b0c09ee32fcc33f1a0f73a7d4af0b54ceb009a198fbe0d7b56861c41cf61f65c766392069e1e56347be940fbf7191528e2624cf961e68560d4ac7e3cab8a0fc85a6699c85163b49863afb2d237e88c969a0526caf548b6e6598a88febded86d82eb69726906f4164d977e7397ca73095c66b059d3be2184ee95252ab7ca778b1cc98bee6a6dbf182c09821e54d08a520739d4b8d497612089ed27268ce59a7c4ea418c92521f41cf6e8defcd4bda5ee6a56fe3b3228275a53e6c912dd4dddc6cb68bbaa85f938d7efa586396f292b728524e5444b5fcedac8e5a053b054f421a9689cfcd19c0d9060bc4042de8229a1834f68b0b20d270dc2f696f3053c8419fe694159a5ec96f472babff7eae48d67e79bd6ab504b252a980657eeb701e75ac260129e422bfc5b5d795c61c3b47c84051684ff4d4a8cb8fc8870451645510ee0eafc80d78f9556ed2d934c415b443306799a6e9303cc1e1ce0f15188cf2d02d79c40c8e1641bac5472b63fb533a8c9391570530937f16acae7cdc17a997d642369ae3bcb7249136fcd8452d933caac278bd2161939a3fa4d8d34a4a02a0303b272d4a1e9f2b810639149e285566d19e53fd0d4b34bb83a010d2dc035aa3b94a6d7a4d42ae83c0f996297b2f44e76d8f6c69e4b24419256c156d03a1907b109bc6b3c2308340b25f0529b39a74bf68a6548b908ecfaebfbb7d5797eaa250c660903c1efd81b20f8dab7a641ef7ff007ee7a493e35b8849ea7fbdac1cc1e8805256422976286a5db42a09666700d75b3721f046b5669d0a29000aab575ab53e0d24756791bc920fe29cb1441b907cb92f278622194cac86679a20531233065d7be23b010b5fdc8ba63bef52f141d02c25b7cccad8191482b5f0622e583660188ead0dc16a2db4afdfd2954edba293ad83960e91f8cb6ba11d472b4c3f226cd7577d80e053d4fcc4070151733a5496c8d7b57c701665a2b415a6434e4761ebd9e61383c6e0142d8d1c2da054e04ebb0903b0bd95d402303d5129265f7b6669a462e24784c71db10f4d05847632a521f0ab10937ec2c543bc84d3c224836e5f2945566db65e5c035807848eed0c147fcbb2cb6c093f7b8a6f592f7c625aaac6e12db762a45483d5a5e711fecf58acc63074eaf798483e335a0e6243f2a075d23494b1e8e7a49af6e7836a17a98547bcef6ececce792a8d4d6574903199036bb7458960ea0c0ee465d582feae412c6632cfb2789bf44372fbcf7b8be77f0809b0d0ac9718e165ea3eb3cde224b7a127c63d604a3eb76533d07356409bf48dfe3512e439252b46fe3ba3564534163fba63187621855e992f6487c3e3e72f2e76183372e6a274671235c8e257b26091e6f7b1cfada501d999ba3fe4dc875fc39e8773311986e81b461ddf074667f824a808d9f5e55bfef178a84586bf636977d0cf0e02cb1224bcf014ab91d88c2304955a41cec2f470619f81828a585b6daeaf88288212ec1badbd517540205a735443d8c4c0b2ae3709c68ce193774ad68bad8686a39027238987a249871c8c8ebab66d63339f43ae325f6dad06e447ac0fb7fd1c6a5ff2cb39e08a35bd4d49db863bd2f00240cb9a5e0a9318e3c841f928265a7048e5b10b112ba49c92e2994b44991e1c6cdd7a5dafd3ed44ccd3ef49f4fe410563d50485ca001fbcdd313644af4d6701d5682f7aca318931f4cdb4df21240525b878aba868fb054a1982e095efbccca34474ff2c2f6c2d801a3ad3b87d3e1a7c3780332482e8ed10076b4093caf0f95a321257078cb2ba82162367df5c44dff36d123294a234c39d92d2e99bcad214e314e399dd67e1f847ef7684d59533bdf5ea63da4b7b7dbcf5613e2a39df6454e4acc7a145be830123fa57bd44301f8b1c24b39b54881fdb5ca0ee1a69ef850deb1600fb8684fecbe8c9f5b3b843fad4eb0152b4530baf98a56e9b18a1f67d8b2e888aa7ac03a392b5b6fdb9dd76125cd0cda3cf40d12fd21a3dbc6723b0e8c8adfc1e5b2bf4dd54e27a0052bfa80b546dc0074562bfab87367123de85ae43100a973b955b6c40ea2d8aa4ac21c3099b91b99497e7939e0756a0ba2b386e9962f5116819dadc26b9496cd2a444e2b51f8c6eee09ffffbaa457c408c5f46bf6782eaceeb7b40a5444531a9c40af7d2e3839e5f07d034a2e7a19fab0788819eab059726e0568971eaee84bb88a52bb678bc8106614e8f00979fbdafada57eb1bdca04f8811b863a60e027b7601731757bdb69a177bc026bd0f9ff5b0e7e80b3157822183103e41d5c036c110d5f8ff410138959cb060ba3d27cd541a25cc5277df2a752f231c173bf64388e7f24ef06a37fe5a16a9761e7a02b10fdfac435ba9b731c6293ac87b3e325e2c77d7d20b1a5a4392aefaf1d2e4b62afcf193281ea2b0aced940cc3c631e70064c1e3787dbc123b3284a7c2045767614832dc079cbc9f5649990394213d6e8563dbe98072834b15c545560335d15c778b0c10e53a95d612fbdeb953d438ef64a9eefa1581b368a1d9dc4ba4a59ad2c3c83032974c0b1221cf5e21c6ba2fcb6bc33bad49add125f3dde2cf35806d7ab8cf745d2c4ec6e8ffa1a04a653a77861561803e5717d2e61a84d2e7ecf615584f4ed91b5a5d3464b3b3fc8d97b955bd29152ce759c9dcad4f6e41ef0550fabdf0d743a3ba33a436d911fc8ce92d83c1fcd1bd2c155bf4556d83eaad1d46bf1172360e8b87b523d23d2aa4f1c630c0b778710f044df25b72ce10d39e2e46b3ed11583fbc3b08b341370c351ccb6c06381ee5885abd3635d765d2d3d4e3d53602e878c4dc13df8b89740f741f6cbf3931c430b43dc2501458de23406396dd26260678eb80cb3c0d4682a66e61b8da9e80512f7095da522fd66df587a8a00d9e293c9889646d66db31d8c089595c976d0b5d17285eb246733595c21d50a20ef19343aba5649c2d51fe8492e96c100da53290aec36eb23b2fa0e7e737e4871c40441cb5e92d190d6820d482a87b065b1e37692df7504e7735d0e95ebf56b4e4b706f144ba874b03036c3b74c3cb64e11887a7cebedb432e0f8ec50e35251d64769dacb5c20ce737dbdbaef867ae501afe450161ae070fa8b7d517537de560e8f12dce55879f572dbb8f8686dad51149b0d96e799ef7fd0d871bcb2374f0ec1be491c1ff7313f1f9e7a2892a006ea12aa4f5a26350bea1de6323fe0dbc2c9910f71931201ac9db4d56df67d88d61fbf56b47623ac7b9202126b51ae56236585ca0a894c49d0bbdb4d81c3b61144565a1455b3b6599aae3e85cdfab13abe786399667700f9b8ed9a85e8e581ed7a892aadfb4ea11682f4d77ea89c5dd36859f9eef514c9a5f8aa3d672a9fd5c3bcbf61512c714ce9ea82a6e77b4c47c7bf4c1f7e224019f274ebd0aeacb71de7ba8d587b110d711d476aba402dc0389a3e4131173dcc005f41abafc1f5ab4ced996b42d11e5a976c73f24d8976fd761f3c279a5b936ddffa46d954f0fb2bec88faeb292f9de3e73d3a90dd3959365a1a4d6ebbbca12ed674d0cf1f4dca4c15e8c230267557e7e47a0d0130cdc76215413e7e4d12b5e55d0893ab406d5e1b2f72894b223c9a3f45e9fde1ed2ea4af01400e19f24e571dce0545114a60901fa2a73a22c4b791894bfb0dc3ea767072f0cb5075a244e19bbb47f33059c9374b6e3e0ca0064a274b18ac4fb6146a3d418c6035aade5433b53d4fdb8224549646b1a0b6be44cf03ace5612ed4218b0d42a0e771588a6ebd5d0088476113b0da970c57ceb1510f6204c934cfeab38db4f725012589d434e0aca54473bf52f1887e145b4f00207459da412b5e359802622c74355f245d0d95f0f745718875ee35f9745b6879bc30bfe29276fa63bf76cf0e65fff01b4f177de957439b98c2ef8812b3cacfdf71503ba5dfaacdbc3b2dc95715ceb06064c67cf2f6eedcfa59b5a0be27bbb082d305dcd1212ae5fd952b8a929e0fb25e81e53b5db52d181669f40dcd966dac808b555dce81e26d5e3f654f4ccca819720ee56ccc030225c157b960e04600631ab269429decb3673ff253d4ef5a4f6fd9047d0ba5b7202d237b57d85142174df550602d646110c212d57e5ee87de13b0f9340a80bef77b0167aac9aae6db7588758248c5c54aa071acf74212dbb1e0a592cff58f12f6492599ba6873c0475197030d2513f80da0c9196e679cf9891e4765c37abf1dde8bc2fa3d8749653fa39342f29232cf4a5e7adac47e08a87079ca8a3db81d41e6fa0133d63ebca5ed945f00c75677e739ebae6e127b0e4385190ef009b1416f5e8686e09443c603418344659433c0625baac6b2b5024812c3895d9f8aabf6b829994c3d5248243e003cf3101f7c83238c9d82dfb3b56c8dfe0e6782bba1a639c0ef47b2a2477e4b7906ea9fdda1dfa16a7830360776dec2e0e7897d113124c531a6f103f329b2908d92f9cbc248ee91f788234a0247e170ac6baeb72089f2ef35294509b1fd620c757bd259fe0f5cfde5101799349f7b0b915b48e4baa169fb2f7b860898694a5acb13c7cddb2de4cdbd952c8973d97fb631b7177403147f0cfaf008ebd4721c8277113a79cd3161f49f36106c6538d0a4850c7dd487b2d7cbf29de0a05fe0721bb54bed2588223d76cb889e8f2087fa12278de7c33fcc9db14003f8b88735749a11eec0e364707ed5bb7400246e03f7540ffadd079052001845f50aa64e20d9389067d28fc1287a7e0d02f6a2a26c0dc9ac3459e87085ae62bd9f5516ea61fa963adedc84e19f3a7dc8aec6e22d2ec8655ca8c800a5bc8378fafab729b8f9a1fc2e28651c8ba75bb8b4cd8f5b7164e4d3425433b73c094888b4fd4c82b11386571b1732effe3985bb076c7a37acb70c51a91b6c40ef5e60bd1bb1746df0db59ddfb5980def8f49bdc8e115fc08a092357cb6dd58ad1fb596c264d446ec2978904d92caf4acaa223b2aa3c7f5fec2856a9d50b1ce7dbb18bcdf45b25f1d06be13d928b189320e315eaa2c29bdc859bc9d662f56e017d3368e4cb564efe4f907019e450d2a1d84fc5ea02d80af785221da2c558b5d6e141f62ea4f620a4086ed978502eb69516331cf65ea82db57ec934c1cf433682b01ed2f0562424c8dd033fbf686dd869e8cd952e919a97a76b554afa9b83f8458cfb35e0e0fed849a458c758e3530d0e6ef6a3e26ccd9c658e0dcbbb26e558e34ba07b926e43f921fd9036c20a54fa62d144bf74c235577e15b2b837fa12f1d3e2bce84a5d5b460c59edac42f895082d184e6c7dae0c2a009e4bffce30608a2c3a0727b7e1aa496cca9b965cde9bfe81fdc4729da3c1b7fe568a074d1e340178d3defd226da3c8dffa2b4cc7a101739494da9b448f8f9aed684f742ea8f155e4d9cd6993a2f65613e31f58ac60679a451438e7dfdff992ee05bcb235da53ac0cd0cd35af227e57116dc901428141e19515b88adb6f71f3e980144ed98f374475e734e91d3e7d0fc51e852b21d65fec478e492bec9f84482fb14bde37a4d0b3809317397a49ee483d781c3483d03bbb8c79830fbc468d15084dcfb15b640c217cfb2f5d52bc32f508229bf535c1f6b4733da196ca8455540c226e43f0d064c6832b3360d022c42537ab69ba8415732f57d1500386b42350546cd5848cbe0c0769ba517208fc690fa3e05fa4bd21664d7b487dc9711360af1eaffd289016290bae5461d622703aacebd57da603160ba36fc0c3213b4b659698fc1737421f4c5cff24a5fd2bcda83f230e894e57c1a4eef7b33f9401c4d8850c68ae6c7bfd20accaa85300bc6799cf1023c4c4d86cca4678eb9cd2a16595f4a07ffcf069926f57454629213d7ce4a5786ee355cac2c3ca25b9102c62aecd9fde9405d94d5502fc12fb56e14fb4c4002f97fdb1840137b5d77593389ccd5007a19617fe268b736aec480f83069c8c4f3eb55f258f7437a645362f16e41d970e4fc929775687ff02e8202cd26824cce56d398939df8886d4cab5e3f4f74e262ccb006af5d1ed55d6fa2baa4b53f521be3a336e72326e1f87fb16ba07a008b7d531cd970468d58f2690475cbd2a923e668a11ab1067027e52fab45a9065951d7013c8b661c09267adfc54f5679009969c5680bf01d4a9a6ca85ed40c0c5c38d9582d8e612d6562c0889f31cc2dd51f357bb90106a1888418caada8a99886b033fadd09a63aa976d0a067054c7427547bca61239482559ec64bb7496e47a4454302a97e3840453eb5c28d54ce84f309221152e2383e18be3ec6a8069c44a1d2abd9edd389b94ea94eabc785dd6c7f6653eeb6ef7553820d266abcc77c26cc4cb041fa4b72e36084b76f69862700b050deb657dd09d333aa7bcae81855f42b3bd145a1956bd026ef792af1365f2b15baba052119e1d26fde4a1d65aa2ee884d32794fe5d6625a10eaf3626676483c6081b49df0958e4b26133a3db874fcde69fc007512afb86c64e24946eb208e56f25a740208239aa58e16f013db500d44ef243f2173bf76e3f08517a795083457d4df03f96c6e8ae4900f7d4df78fe260ad07d437d92108487dd9bc449c8c79c36331658e3d6f9461ed07a3fce6cc0ab03e53dd4429473b943eb722374e690878340c31704f152f8d31f2a0f602637dec39816236304370e423f6d0411e2e0164f64e00a12fe748369d7d4dbee3f5d65d1c9bbd7cab55718f29808671a4ebd89c33148a5d03f7c4e2c1dafb088d8daa3d86ef68294f2830548d001b72dec9d07f8f7a208451dc2c71a7c7748805ac58902dbdfc7e1413d2382ceaa0f643fb5d8079d4a39005ea75b65b6e3301e0a06df132af3e3e95a56007e6239be1300b84277ff6f7a98ddd03346abffcadaafdfe1f66c0d5ab580c85ca50bf6cb50f904a067e4f4c808dec6a8d539fc25800ce111b331d37d55eeb2d44b5168a9b037380a50033be796d4985d10c46deac0bfdbd996947ff7996b180250f25eac3556792632f41f32b7a0654735be6b88e928e2189cdd5f458aa6949bd1ed06b0bfc7097bd12c5bc85b1beae6647a9dd96237125423326052c16e9677e2d6666a77fcc7bb65540d48307c1eb48bd83f4ab5cfcd0f98c8d3bf75d77ef047d25b83de010f4ce739f2329eed5d6eece6fac4e7828cd69a2486afddffbc6879f11c391b6b1fe654ee81e1f82e89214bf736248a34b957d1640d51b031981c45f8a7a7f756fae476d6bfb918f2d773f6fc8d24963ab637164bb8183079c3ca3079cc0aa7714ba401f4da2b3e2505358d8675874008a03868ae2550b5590d9238748c82cc8f4c97e4c60390bd1109a89023aa89a912b964fc60053d417adcee7518682345f0d520cdaf0bd12d73f367a31fc796bdc674a84af99be2f6b921e9739696539d5a03cbd13806d52f559e81b06447b004dfc8aa307c650549dc397e4db9d8db3706923a2ee6c21ee500ea222dc6c1cb691ad0b24f49748d21f2b8bb33b40f1f95918563f4ed69275e527735d356cdf6db7c5e13523826f2428e22ff2720edf902f3f4db944834b546f514217b74cacffded8c5e0321368ad4e65a3d3e92a92b4afe6a0e8591f88dd430b15569ee28f2597a1735829c0f4ebb97deea0730260763d5a7107641e5afefffefa484b56c50747b648c875685de1e40761c93c8810205635bba8e3e2250a1c54c610eeeb9a7bc0bc2a6efae701b474de72c2481ff3a0095f2461b0fe5927694252b8b51c4b1d85c5aa5a5a0d428bf6fa6911a873c0845cd45c49ed9bb1487009faf1c145172b6b941772bad782a969973a65866a9adf4b3cfce75e4e68ed30e66d079b121a201586b9563bb53afb2916aadd268b6636d4445b12b93213f6f75ad115d0f4991b1d9d42845da4530c66aec35a1096e5113982bd8f50f8b459c4645569fbae39f8fe8189b096fbe40036214336db5b7326251cc2c00aa5e5ea871ea65acc2a15680753280a4e2d737a8d61c6fc4598b35364c569eb62d7af4369310714ba5416fd5f29a0b953789efe8cb4baab9744e6f2d62fc6b96da6e9173842dc72af3d8452b91825061f87bd8be2536f321d0c0a7628a331381e85aa0ac9ae84920f1da55abb8f9cf33247f8779be37e97bd98fb6704ad788db189e6433b2f0ec40d395ea551b0db180562bf1d8a4ea45f22984ef0dda1feb43b14bb5c10e9643c1de9e082d719b9a04a9ce9f01d62c480dc8201fe908ce9759a08296b2c88afc2c993fe5a87924431be91bc10d5e28e3af08e47c3b4c4fc4129dfb88c3f337184f47e63a27a5bd71210ce8742eb8f4ef79077a89ea81ba0fa5e164dca552dcba5ed8bd00d47478d0377eaa5e21e493f72e0dd7916e1f2a0c97bcd7eb4fc47f71bffe1f586fb70b9711f2e37fdc70b6eea164fbf34a3fcb00d023a22e30e44331debe9a043509ab55cdf68a9e5be1def914fce98cf3757a8cc1c76e2afd4fd87817c5309a71fad6fd2e3d05760deef776a654f4bb7c6f2484e2a0eda2615c485d1de103bdeac81d9d3cc8cfdbb3e4e0cdac75c5a343cbcaae22b3e29990d00a7d836a10d4227da67d0fb2b3ce64f8d6d867a7b75b2c3ee030bac10aa1e1b7a0126458fc7a762807c29391372da839c65178b1ac87260499136029a1e0e6a487a5b7de1d704f19234c18d38bb3289b1061537e4be1616bea1a29bff736cb0891d6c4b0f9e051ef4b0641f30de07c52449fc90569ee5dffdb4876473ea4db27a74b83861d0ea589498d431b43258f999de05e4c8507441ba68858a12e98f787f9c1cd13adf444cc44bb3bb64d38be8f64d9235a29f209ea20a9cd48bf850ee167c9b865466dfb9d7626077c9a2c230e12d01b16aeafb7af3acc67b67791387bc53f8ac7048e73a28e0774b8d4a2cf1e226a393c60bdb9487e5e4280cfec435852937d5ad8cbf0f763defe1b29050dffec6372b6cf831d0d88c1ee884608fea9254cfed8694454fedadd7b66773d4616b3fadf6646edf3487ce2de0b2faed810c602941b03d7a14152aa0b509de63a4dc289222e9ec8f025f2a962e69c5701b8aaf3a1fecc16f7834f9602a3ef0485e667a158e028c5aac567126b197c4cffa961373fafc2944ab48c8fab98748b1c03bc078a44728129ff523719d7ac954c20c01f236efd19fb10ab0e2c79189f32240457b00096e18f7a17ce8a40419c0c9acc79cf021955f167c52ae0b5c49b653b4ae71ef3535901121402ee0b4fce0fc8ca73b1bb7c5b56762e87185b417b7bc0e53219831028aecf61570242ea7ed62d5119f6223dd81eaca5674b316b44ea9e91cac5a1ad4d4a51e973e2fc7dc00fda240674cd3d8da9bfd758ca6217895f074c200fd150286d7e162588e570aa812617a55490f0cbacca5fbbd6afb7892cce455fbe78654b34c2fa8a80bfdea8214385e351be8dcdcb85093b4833ad7a836fd9973bfaae5afce82d230c2d53b4e0929fcb739393a6f5c6ee43a691f120dcaf3976a04d3599952d53a98344081fe5fbca625f5f5f347bced397b1242a3b1b701c056d68d4e150220fb766b959f180df32359065757c5fdcfa58354e46d246dc48550598d513d149f88cb42f869d59370c6216e78f3c790b3715756381066d7af3f7cc3e5dcb808aa999575fae0ec122aac96241998227d07b565e69811c9cae263a1f1c5cfb1b71a78cadea4098455d7ce1c637e2bb876e20043cc4fe6cc802b891548b3c414cf16f3ccea62953a15ccef0552fe486d2f2cf3b0baaac5faa6b7c64c23fb41a59fe3906b87ef6019d2978d344e8853aca69af1059c24414c533fcfea3b0190ae626e46bb94298b9c369be68811a790475a712d02b3b920265ec85a99520661ebf25238828f103dd6dee50c494b1b6e9d6eca28e27e7a65d99d0427877ac8b9f8a5a005fe4d25d354448876f31d346375847cf2becf06498f15d07e686b7cec45ffc59b1e59837322bb23a1c623588e9c8ef819f408fb227972c3f98e4a18dc20511e9558c92d0591c118c1dcd846b838b6f28275cab004cf27eb419b52ddc25acfe26979dc167b7fddb2a659f5c66b51a7951ffdb17b8d77aa39e77a81d505cf53a2d11bfa928a6c4f9015c014a8f5c1d2eb123dec322e76f1cbaee3ec227bb42b40ed626fbbb475bbdea02f6c79335af09cd32b3ca318fba8acab168d2f1738f2f965fe68a1309b1e93fbe3348224b6a0bbc1ca6ca75c669386e1e0ae4b374bcd569d134c269847eeaffc355b641322cf333a6b3648bc39171b31c134dbddb4ef6469e49b31643a38b344f86fd37c90039df5576a6d1fd5e6a0c3b425f9311f4c6409195417d9eafdc8ae8a67405a887d5ce651bb8bc473b72337484c27bcc2e81a88d4207dd24c5eb0bd61e52fa437f214b23aa19bfa742389c5c03ff0d26c767b0a8f4563d589e298ddec5473492c79bd8c9d7e48a104dcd22b9026394ebfa20cfa0347897c30af67d2d60a45427ca4d55c98c92464a7fbc0bc4e4d33f83e4180a494a04ace19559549b398d02e8990293a644424d6c97c7ef6b12ab56138a4dc15ae618bb35da4a7383456d9f8329d12514f0e113cb44cfcea348c3dc8574dc7b248b31c87298885a850bd292dacfad67df25dbbf1290b3d02900ad5149c8225699a4083e972c60552088c0af0b569a3b82303023d6492570b8f49843b2edfbcbb55823875725d5bcf4ffa93ce225712f8801622d1976044cd27fd09375609f5fadfe4c409b37962af0d0750272858fbd430fbd7f6c47e8ddca6a8dc8c12b365d997896555f681747da62f3cd954c16ebc92bc5984c3f583597a313d4186171c6062ee7a24950da5de2cb2e788dda794f28bd978c93172a9086972ca6919f67bf37cdab7c7901f19e08736cc227c0b9a3c2113d8ec084884241d3b319b0c91aed67c7dd9a0404f48d5b50003ee882d1b2be381ea96a57860417d1fd7c8158d43ff5378d2826a4f4719433bd46b742ed7d9c30cf2649657a9e07865a668100380e19fe795ae6e05e20db67d658ec404d8efb3314e7a85e43355ab21963c0d16e1834e01429a28b8ef07e00e5124764ae4409dd4179f476e27123a501589ce480ffc00312ac68734f9661d03e5479933966bce2dcea73d77e14e2ac519513c630b770800e59eec6631c0fd07015937334254dbff9ef00a282aae2d03599c546d1551409fa6605a19c9a45c70a03b80b7390901e339a79bdfe622eb0e85bdcff4b2f74600dbe84e814dca7c0364bd2c0c82bd0268660239b6300a2253068ed10d376f23617caa26335aa5ea538d1be1f4d3c346517eae52fb4f5544975413d7f91e83a91193aa95267fdc10072dbdfece422fa8c5087f2f78c1fa4b637d2d26270c49fd1a44a09666a7c35051ff63bc4bfec6f60d6caa05df9aa65d8d2a2a4a7e425187751d9c7efbfbdc1d61891ee73b3872a5023ce7014f7f43283354cfc5b76d385e41b035e044c63e2cc9b0ae994d9a48bac691d95a2123c178f4efb5495fe317614e46aa5fa2de0dcc01acd4bca469913d7ff8d6dae4aff9d9b63a8481b5cda085089800e06619a4c775da4126d345ab50f3a115f7719e4103ec40fafea8445f17b9964909b8dd6e2c63b370b2f3d36a0e4430e81ca5508a99fa572adfd5d820f53df0e7078f3aeba494214f695cc36e7bf5b150e5ec193da042046309ad59fbb63d5087197faf29017e8d9b2147c6ba16bfbfcbcf25859258605cb693473fe35175802139c2b4e7501f645daa2346b989e2cff817f11c7ebe846b49b63ff01631dc983d44525dd9f243388029a3efc58d12b9f39a3b090d75e4232462a7187051353c5552e5d76063d39a8c5394178c7ef17c33d7400b0c34b4fd63115a318d8baba2d6fc2e08226f4e361ab16b3e48b11e18a66788621bfd383b38023ad68727b3a42928cd075fed55892ddb8c4b0dd1cc6ad2d90403a44eb338259fc5a152a0eb57872b97d1b2f44c2f39d64525f5223187e448957bdcbba9f5c9c16d7f3cee02707a95e54573bd0b184629335f1025417dc16e9adfea8386f1c20095a347866635b6b47150b41af8060a4d04c216057a00fec5c2e4d53dff94a4b1dab02ae8937783b1e3ce3e5528533b74e298b6dc07d1fa454200ddef5e7e50294a2cb120865e86dff16e7367a4d89543babbc71ae43cc667561cdd1363aed905b13abda7930d619718cba8f0908ab08779a65dae4c32cb5ed127bfd16bf29bdc8170eaea5528d37b383d176959232c329c5f64bb479d61be3816704cdfa97f9553dda0b7dfe6f1af6b3165d5b0b05d158ff406b1d5016a9f433bd6b4548004ee8b4a710eac699d580a1b6f1c2944a0c41bb4b6bc3b0ba9554e8b1f7a40c92771a49a1691d26aba67051bf0b2379d94a0477ecfcfb94398336af9b8fecd3d5247999b48350d6a41183f094ff59f7a3049511285f7bbfcfc08342bf03c7bb4f90b3655ba1bbc196ac355af1f71ea84972e988d1e884b9b3f5fcc47274e02259ef34861c9eace6cef57aab32f8fec68145abe4326136d272187bb8842f66c929bb5b6ef78a026247239adfcac8091cd60fb28d799343b56927c26a1bd2634919dac5b4364ffa421614c29d52a104012407ee5e883307c12123fcadc6f59853ff80ba68ca5ab8ed49d139b5d29e59c17629c22142c4728587cccdcd6279a2020977b1096794861d3fa3f3c204493a07a6b776378cd7f369e5e6446512be9cae7224cfe0e7c930d09693873edab5f9d2487afd25e9ff85cb068faabcb797be67f7df30969b1c16dcd5440c97984f72c26536ec099ece4361e8c4e89efb2bfac73308238add8c049a1b23810a2b0e00597f72a6b2b325bc280c7953799f179418a498bb95785f7f0961d51e082da618a072d86ebbe55e80ec31c7c1ed23375bcbe5fa298d3a9d9e18f6d95b151fa046c90df76453b8e287f908c4892b8837275102377f74ab56d16705b0bb450745115b739eac193d7fd2a2c0a042c54d02836501733c8662c1408660aa83bc3f4e2799044110a931db2c478b772516eb5126d17946f78cc74b03cd0252ba1dfc3ffc7f03aabb05a73c52059bcefa01dd77ab4d2f85ece22cca8be675b3ffe736cec4ca0e9d9e47b2cbea3e1b2878544ce8aa0fccfb131547bc98f874947c189f4615fb38c13ff66942f0cb1e94b684c4637ddeff7b2b11c7a2b756caae7ff9e4b347a089018c5620edbfacfcf6e00100b086a630b74edfdb07fd9317a0ae835e8740295520de2cabdcfe79126d13b14de6ed41fd5867c44f2234629e45ae4f14174f10d4205967c529f0b9cfd89482e33374e36b8b281e4bd76dc6720d9835c138516af682bb001f3060f5eed70d4ecf3dace026d437eaf71b2a2930308a3c9eb85c2a5d4e4634cbe4b10462dfe10fc0be7fa91c1126b9c21c60924a50445c6af50d50b285ddb1bcb9e631c9440c98f8a4d826d2890af93466234ced822546fa57d8c5a08af9a9058069fdab474b2040b3f32ffce6ba83234e2a727141b3fefb938baad8875b4d7085474495681eb7b00ecc2dc8d754904158933ca4fbda69375d89518e01867b32ac16d93aaa973df754afbe9e447e87d253423d626091e5cf1bb7168dfa5a07cc070165a03ce52c97c6bc3128747b24f326bea753eecb55a0f0a45062d10022e5246847d6a86c952049424aabf170b8cedc093395e07eed93671de2740e68b372ac35a63c95877290f7b8922db9ddc5204be9291d0be75ed35e6f961bec1987dfb902c5a5b91e1f1ff8d9c39394288d30990cd0c448f59e8b1c0e54959048f263541473c64ac2860be3ec04895d7c21418b9176c77d61d8cca2badf3dd62bdff66a59e06265eec45ef92b2af94ae71b4f1351c0d908bf626bb96d57f9c2a0c9d1e34ecf26aa344f163c8951db4113fa2339f5c0222677e7a8b98ae9998cacf922157144235544d7a445cec180b3ac156f356a7b65825141ebc799a5699c03d9206e83ca7560163f88807b336bfcd3c0048f7396c35b6f448cd7c730c735b595b49cf7981fe58a0121a62ea277a3e6ed70363586bf89605b155689582ddfd8e2dab05e742b3524411220410a2d620a3c378fd853c9a24258ea2ffee3bc7149e8683f779e3767751a91e514f9e60173ea2290a1bf855f50ab9a860e324f1f99a930790a114cecc0d058a84b79039e1d94c7da0e375f26ba03c4e2dd0212844413dd037d168fae7c3b2c1d2dbade33f889f47d1bd64be25a199173261f3a5ad1772168ddb16d9b7c400f49bf916822d6014093db6ce9c451b07d33a2d246bc248d1bfc25577706916b607703e5783d28cf51a8f36c8230a3a8b5248091aa40113543909b6042a1dd9a29ad1e5c223aee4380bc26959de1c974ced229da1fea6ea318f5847d33b9b79c87a0e89f503448125a518580df6a101e01e9d10f0e055f1119da1f1d3362957aa56b8a652311ad191085aec3e03ade7e2c7e8d54798280c061271206a3d5e5bc3bc05da8f912bce7e3fe0486c4962cbc11a9465918fce6c4615af8174513ec22df96fda54106d38ae7dc8c7f8003ca6c3d97573608835f1b3070c82b235cb202f533569368880ca6b436d829b81722801779edeb622c101f0ddd48df58b3a5abe685b1096e417c8b5a835068c4ef417b29ab122a069e76b110cdfebe0150b10be3e11ae6c9c83e4446a01e605fccd34e8a0070710b5fff3c6edb8a02df18f0fcb895fb118ef021b8a8cc29865df9e5dc77d397d6df3e0a1c39aa833aece9ec0f64b7d06672a946f6900554bce4f5033a62902c490b6b326700830ad04256e11515fad7f3045be7b9634278857b8c6894b3def989e6e192d8b779665d23ece511f0f6c08ba9b18676615d3ccdf522e42108bf60a6f5a40f6dd5df63b4608d7ec5f7b40e64eac6d80de60dc6bcf0d338b07fe88845fa7597c653717aba0379f9bf8edec9c7a0b120ce562b57a4ae8fc88481f22d1b4455d30bdf8328dd7bab2cb6dd6a53968ba3d4ecbd76fe41a33de94c5a880d7a71ab93b8694090494d7056298cfdf6e1b0cf73b650d7fc4effcd01b52944360f547612842bb907aba6e5eb09328fc6c9fabd033ba518fb18f1e1340e04e3d1f8305183d345f44b52aab0534ce85ec1d37401c827509892e1c08e87c219c73c750644afc00f19291211a41f6d1ff00aa869b872f90427311844ed23c533ceaa4509de78ef0d2ac952fba9a73941430c2ebc3f737df713236ca892696450deafbb26badba9490ae7c5d78910f11ce0c1f3c26f85a6c6fa9ecfc9cd86e2633b74044e2d664415dfeb11d82a9478a47446ef050d527ee64b4db60ac4ddeae918b6e15c814398b2f656750f6beb417ea04610af63c8229862f9fe542ea9fd5aa5b8d73f55766e71a1dfadcd3c3a910ea71f1bed12c96f25148b695fb27af31d86f57c8c63b9d67ad25c06a7738bc637e829e31da1c41885b1653502b66bedcaca0394b3f43d0c275a908a72591b698b5f1d62300a0de3879f2e766b03aef792fe9a0d20e56e98121e9e1033283ca3dc07630093d6f44e8fdea382b353763314d489a51c51edede03ada9a04ab6bbeba349c96e3ea07594c636c61c82d68be3020259b87449a71a34c8d2628682a69948b20d21005b89e0670b4cf04ea0c22be2628f8a28591c3b0b54a51481906951c8e907511aa7baccb66dd580664c2aeb7de9bb3fa90f9db32ea6e769afad32508ef54904197148cedaef4072edcc993a640ca090e88fbdb6a8a2d7cbd3b383ded1210b4a06b952156cad85540fc1a68ecb557e25044fc70f0de5ef41495dbfaf06ab5528bfec6613beb3aa4f099585e2acb2a279a2fd1c7fd37a42f6476319af0b78d1d0d3e03fd76d69b23ddee4356197af7884a21ebdd5f791e33715bf6d5d00ba0c83846b45c7dc22a2ae4c4b7dc4f8bb661a5b567d78a59a748ba30fc6f7bfc180bde1e4fe3a4e4bb4a0219a756fa78e651ec0036a6eabd5569eba93778723efcca652393cb997fc0f85f85d0d99a74c4ff182d685d81346a56ba7d86c6d9d34fb5a9677aec6bdbea1e5398ef5a08f55cbbe48bca76ced3139d998ad48decbe49dc0489aa7702138ca619e9acc3926a0f693f673ac45398d433ff884b6b0e9d2539f339fa59361c7623836a274be1a479dfdd704278da10384ff1f981cd172f63b9674a49635a548414382b2d0cf414d3a186648396b5951dfb4896407161743e0f8040d359457db4aadf5161b18530b283a73eab873d52248390fc6eab5ca42bdc47ec7b251c48ea146f5059dad73fd4e1a43af64f634f92df85032f218f319b46d2da7c96efbad476928d420b6c26b5a972d12424e082af9047b9498a11950c873af1f430c2bed9a26108e882d2dbc9e0ceb3e9e2263575a54508f90e8e51603546c2071eaf2c09895d11d0e2b01465976d5e5cc16eb0be4b286fb313c003712ec69776d93dbfea1b44f46a8bd26d4c1477f163564fc82a3460c6d411ad16571b02a979589746c04d6922d38718ecd9f1c95fb077b502f095d3e6e27eb63e77a58e33f427e9fd3175490e981918d851d724a979e6ccaf9d3fc77e1986747b5cb1f1f5bc7d2ea9ca60095ec006fbe19cdee029ab47d958588d0d36d4d4159123c3b5b195d439bb9bc89445e02503cbcee3045e37462f8e610cc833da61869c528588be08e2c55352e1292aec64f974509e567fa1dabfdab2757e4c62d6acf4a374c777dcfaeb3db15126c6557d97a81a5c7850a91925b2549ffeff42fd3eb6f2325828e740a27f1a62c688804d00cbaee7e73517f244f255e3eee3bac0fd6c4c20a0e4090963c7439e896e606cff0ae92407236598b18ac4c15e654d26157c89d4b07ae7bd7b494b2a3c31be29a000b56329a69b5d3804d1a30c3aad1de9ab6a3f782b62d43b1b9437f381e19f36d5f3c68de51d921e4d71f5f3f10106b5174d764b3d58ea831c62823ce021789b7126cc6dbe83c67e401bc8adc93465c04c56e6b1ff6a39f69c0bf4c32c8cc6c4738085b91b0b03d48bb4c22fed446cf0173062f995cfb317b4b463c444eb37e30873f13c5fab98e1551bd8641128f135c486d5921d7ab9591520389560bd90bf84bc32d4a6b3a09e141d3360294d2f8c6295c693bdf7c78f82ed89ceb778c074d70487a967ebe556a40a167fb2bcf3e9c9da2cc9cf814c0f744e0b7b3e81239762320d82a001b44c54e588f379e0044c1428e012bc8b15898c0b19a20d8d2ca775b12acd9cf2ae2afa39ff3b77778300cc76e5c554192eb5d87bdb409c46d3928e5f254d4a26ef6f95a6615481390abb24d002d6c4a16e71416015414413674f2a006ae54630a683b2f3c8d4d6e5e8a754df33a2950d391718f5247deead8d061a2a39024401445ec80167904ac06ba3260e11f21710d5e053bd4bbc66b49ab52fcdf1966dfc2f499f4ff5dff470fd861f5ab6848008272ad4c2468444897c86da323885cbb408142fa6d2520168137e204ed0784354ebac76c05831f58d840778295389a91fd80d0d11c5f5c90c62fd1e6f3c1d3d63ae260efbc4f88125a0c55013ec0aaf6f0f106585eb6860fcd7f576e83ea0f8d250860d338e0a374ebaa59f9d1a63b5f71d45d58ca7d134084dd4b2c97284b1bd8e030f177a3e398f3330a1b8494093906896a369bda2c5f74a6e9ba6857fba8d49a832fea66e3e57e7a329d1ef0dd9b6652431e4c86035f545eb1c98e2111033711c892e4c20011d45151d1be2f4628205d5eff325fc410f2fa8deac42a5d0714c6256cbc056aa410e0f71cb82c402d2196148049d2a16ffc2f1172c22782168397417fbeaef7a75e58c13460611b77308097b5dd77a111be9b0b1c8d0bea825608cec9448da5b55762f846ce5d263fca486119ebedbf75f90900a61559adb90ccc35d4fd6577458461697b1b27cf875b77d87a5456d6d81a843fe07fde8b629740cbb5ba505c36138d13d0dde063f44ed08086d766d4ac7816bc08f8442f201488b3a55744af53b94b659f9801b0f8c63523354d99ab219b324ff05361e1914c34af43462ad2e3525fe3e1736e4f26c2f0616bdb84eee49e1b244e540da5527716db9b346aef00f203d8d57d2ab85efdd0770c72787dd03a2199473c031d2f5c1b21b4ff91d36ed80835d46256e767d21fc36e905ed5d40e32cfacee03dab88ab4030082f37b38b69eb31ba1d44864d35448256bc72e92d0bedac1bc20c25fad01ec058963b914299a76029a45a1b1d5aa0064880841d981499bd9c032d709c35774288a5da4200a8a29db5c32971fba28c486b2d05068c760728259fa9741b6b110434185e689fb2dd94cf01c69a691d90e74239469c71db2f1a7844b336154fe00863ad5d9656f721613958fce88ebfa1ea4a8926799ab35449a2862b3dc4b812670f99e3a1369714f6972958a9f0e1529e021f61c3982e9c4e6e24d2389fd662138b10a0364e58ea30456bc76dcb397b43ff1368274200c69918641b302df36f2a03083fa8a4dcc1ca17cf5febfca8a80da7a0c69635a1c604fcaebdf50ab7be333a782b9c6a69b847c435c6fd8aa81a53f7a6aa037029cba1835033d349d5055bdbbe33e46967da93e4076007172b6f03e31e3d110a9483f88fc136ca5f44f070689b2f04806f026675303a4ad17599a023cffd16377b740c8d2ab8a82dcb444f301cbbbaee8013ac8a98c1e04dfc91891311934dd816535861c1ed26a98113cf84d01c6291d431a681f01560e2d76acd6964d9d9349d85b98abfb996660ac5358a9a1f6be9cc5d928d77e7d5cbaad3792a1bfeebe5f232b3343ad6d9c3600a0d76d9b7769fecea0e652f8c183881dfe448e0addd9fde9d549a13b6bdcd86c93cf3501f1a3d57d7c94974c859cfd197a9772ba5f49e3a8f608b6df0c5f5d9e3b2191a9530150f7a10f124ab8a2c360eefc78bf2cec6ccd238dde2f25b367ff41070380efe4bbb58cff29f70e2b3c0df803b1c76f852ec0405261d9977712e33d8934ee4e1855a3bfe9862c6457bcf025d22b641fafa9fab550122f878c2c103c261c13b298d26ae2eed621ee5a437c8c232148793ad07702f4dc08cd2b2ea525a4158c428e0a9f3df8b3f1ef0a8cad1bf0a3219186f997ef8b42794c2234513a03ecf9efb6281fe26d6adf1868d54996d495cb8577854fd7c63c38d34ad9ff3fcaf505f76743dd43023266a76b360604e486e4de7a0c5440e85024c143d8413a65e393a602a4dac8e68f9bb0ec0964fe1079b9424e1f076ceb97c667d4aea8b9f8b05c361b10bfec021515b4150157884061bc69d7d28f99d5c2406c3aca8241b2a37f7da967a7404352227313152c919258239e13bf3d6229b6e21830d82d4a3c1c012c4ca46ff15da98e1beb3a46bf155ad7dc6412c706a95019fb2fe245ecd7bf0241f10129f929b0817142da258f6e101aa35004792f04d13af55aa2f221c04ebea881df720a271d1aea752ab2d61041b61d767fc179cc650c9024d7ffb25d6a541744a7ca9b06fcefbe384a7d137d6178877b90951c616e627376afd3cf27876d61cd020aa68ff765222a6e0c1b97ec084b8bd0b54ebba66d81910d6646d5d4241a02e00d69d20c302f6b0d7889872b2c383da71f623f98a5f849844f96a1edb16b4a4951be6815a98e71488c185241493734d404a27cc22d0caaa8394e824e6b8e33945c959ff03d3951fefd347eec5174f046ce1fedecbed2293f288967e226f82742c2d65c3a1c781792ea9294cf2098b90dbcb6ecb9e72321809b26e5d1738c6cfc9917a1381bc19e25502304a6734be3b15d2f804669c0b3da5e822d3d243194f416f930436cd177404a6618b28de71a0f0845f3799ab3771a0ac1d0e059f7b499a846fbeb7a344c52706f10610885f5dac67984bba29182103daea6500155658569b462c415f2b5375aeef7ecb81c0741b6c0d7495e669ccc90cfb966274ee96ec3f4e1ac7fa082868f23cdd5cba09636d02cbe932d921fc48f37d2d042ae5f31620d94f8bb35bfd5aab0e4ffdd2d5f97f696fab38c23ec2acdc2f52dd8a8572e123c7b54059090ef2456eec4cd70f07002a4c35d4e5e7e8b2d10e37c0db4994dc40ecdda2782c3e3ccc44a7fb122a1d288770519a7eedac434464c34bd2b7da03f627101e37be01d529896de121e58d01b38114285bd3fb992c991e3240b9c0fd2c7c3622a14b13be4a83dfc8c6bdc7fe2345b441dce586aea867979691c2c87395144b96dde12035c582d93c4bf015800789602809a58e4c86e4be64adf8d28bf936a34af8e37a0c8d8a0a0ed6ad1b3ad5db9dd9ba562d9297258a2005b4848e883db955ae21e15431218337c2c2d2cb3c9e82c050aea844e303444cb5167e41faf3c0866e4bcea31cc56165bc9528aa3dde69a94436521bb9089955130a66a13da5610b48bc8970a4df578f695f1376ef275b0bb64858a05eeac6cee170d1a2a4162c87874c06bf0b81048941004de5f77164491cded9ea2d20e188f37d6cc506c0436fd4536ce339f003bb484f14c069fa4b70c01e7b03acd3f3ebb9e1663e1b6728fbb90c9af4543e606d365c61028f37f28a3c9b58dce068d2aa255c802a70298f005781dfb985179390f638c985761261da05b4787c62ea07ae8b230518870d874692b9a8f2af0b62e890172e66290cca34466d13052c6e108540aef9e60a6e359a519768841011919cf1d2ee755748622f24968c127131bc7edbe4cd64bc503f8d3ab22582d1c778bdd5358332918c0203625d6d5098b95f69305499b20037e143f0ddb3579619de739868cb4fb14ed880a880895f553b7ec34d3078986fa16b1edfced45a7c2d7c7a282432800591df9976b750a4b7924084f82f22bb9b3fd6a240e81fd20e4d64bc37cd0e5a4921db076154bea0c105c68a5243b4947da3c530bab1c7d2d6a9cf49f1c6dd41e9e9a8fee5e11e6329e5c817240151f5318da4bb7912a5ac3f29723c63e2c6c5a6c02293c4e680779636527c081d3ec5644b6727ce4003a423837d32913bd050d0409c0e5e8cd2bc5a1311f0a3d4cef4896728c9349897afafd1264659077271928b913538fe80ad3514c13f922753ef0acbb573653403809bae13adfe18576fd593877e42c5d53bc321179c216253105ee9008a82c39339a8d4dc16da7ebfa77fc81e1c137d08b4a68433944b30ed3073eadaf2c7cf644fb76804b723829aaa5349b2217fcd9f4d1e962922cb20dd21d786d3f52dca682c9462c482e27d4d288611eec05545dc61390321cc44c2bacc24c5226585526d1a478a95043c29698bf44baf7dd9200c2c60330bbdeaf016ce37a72fb0ca78dd6d48ab3edb426c276dc4bedd9555edf69262b686acb33ac1eb09270bbbcf28d2a00eca8c7d62a3153790537ce5cf5d33a16ef6fca54e47930dc21a69e86d0132af4591f3eccb3e704b5f45fe7c9468bf370847ef58f3087e6efc72fc2b65a31e04be5326e623ab7ca9b6ac375a18b9262605d99c1720b81b6694150cb8bac586ef7b26bc4a9c0ba0ad3dd3e1c5541f70a4ce8f9939dc8702042b8edb808fc0c4fa3a8152ecd83a4e571b068d2c7a2eb61def0f449d69596788866eba3d02b568d5400a1c98cdb9049e37d418c1b3b90313a9c3a115445982c785a1cb2ff93cf7de5d4d205621ab1516a019190be846932ba26704226c725135859247f773a2d4c73766b668ef20dbd614b8ec20a650dbd920bf398a2845e99a00cfa280ce12b6d8c302fe24c8c2ecd2404abed1c988ec29f9f70daaa14900a3ad7843a0098f2eb3948f71adac7fa9cf3bbf0a41700c3c3d817292b586d1f355ad66343d486d8765d2bf4acec02ec3dc1ed667b442f5ae78f9a8166ac9fd118c59a09abcf03007e3fb65ac33f87fe00cadfb8343441d48916b02d0c1e143e76004785043545d38b3f4622907aa0da0150b9cc874b358a3e131dc50024f8a78e9227bf583377e5170e9973f22e7fbcc8b41dd7737a5a2e08d2debeb5237d0ddea10584a66bb796ba509fe24d28430950f7a767ee78425504513e05ca344e2830a238ce6960d4c48664ea1c571840e75d084a66e5d1cb90303d0e99b46e01621e945805e0e9d6487ab97dd201fd478ba83d57052440fb14fdc5d893595ceb0d6a4900347e0a4cb0ae0279d3a8bdcd25bbfcdc1d546dd197fca41d1239d8c0b2411061f53887a7424392c0c00018f1c74b140f1e3658d297bbdd65fd35fb4a3f68abd19487162fc41dc2df5d50e0a564416f14baa10df3a0cfeca54c8bb75a740cd5594a63afab8c49351486d7ae57316927ac209791b08761e91f70b93e69faf8e4425dc33ab8092dff6a06d61e084dee51b59758d9235f12be5fdd66f183d3d164a6ab60f6ea5bb991eca7f650abe2eb2a7167e4b2bd1fc071568676ff9cd2d72348abe223d13e6ad851a61db7411030808230c807a18dd2e4d280dfc64a8d74c0d7440df0429773be06b4012668727afdb747171296de2ca8acf5295a1087e96d0d0cf9c6d20631d6ba17544a99b827cde06c4c0b99894e573e15320d5d5eba9cbbbd893a525f5d7c4475bb8b82ec8c09822dd9296dbe17d06f48094141b55073628d322a6cf548dfdb06921d624e4087dc1e367723b96a9d5e8ea87666424c8e7cbff352c107f4ef2cac62ff5c986f0562ea028a4def00aff5a8b7ec4a3756bbc2ecde4af5779be98b7bd4bb25bb9ae8cf413ed77bb4801d05ff92850380ebb193725b7d4c9517cd8e4176bb9ed7367f2b52389fef4020982347651536f36c43964dddbf4c2367c59fbf890c1d86349d9f1dae176ae7d9865f8017c39d30e22073c40b3bd4898f95c7a4d2ac87107a23cb0f0cfea879972569b9d0e381132cef66c4af14f59a67e9b440855bbd770e06561e5dcc0d5cf4e33557530de88d3acd009e38972549841a531389a181e9f1fb46d73bd3e9d77e5c4300994cfbc70187bc03be9dd626a6176ddfe5e2222226f2ad191bfdb2a923cd2966685e22cdc5292e47b7a0d098b23271518e0a8f89c3b782b007dfc1c3d59de02e376758297ec4c163c0bd222e03573bf807b65cf37307d9cf7a65debc0ec2365ca7ea371ff94363e03a588c0c14fbfe0ae8932d10f99c0f601f5b57b632e59c25f742750575d748ceb25a3f21e4d4cbbc13b277df543329f541e706c36cc44e2105dea6f9a225f87a098ead8e8320cf3bc65ad220d80e1cbb0d8f416ca2d0f7f702c1774117149fc2a92bd6b8a80f81c0058ce16fe0c2c78289e90dbe53b35e8b2f7435419ff3089abc65593aa6601cb5a01a04689bdb6b890d12239bc7dbc3c9bbe2b2499c6251b2367e756e548664c13d372db4b98a5584d30168ef6fdf363e19585e043fb780e85bb6d6fa9375409bc5884b5e097a84a8145b38fbbb096a0e96aef3ecda34f011c660d5313d492ba2b1fc8c6c1c7a1077aab15d749e99d318c97f386c933842c530bc395749220498220560845bc3d833a6206a21aa7816075290f2098c94262b8b072c13e0743a235b9e6e00e2d435d4e01a395962e8a8c534173660004434a78089a7643d5533ac9cf3a4e6ac22ea5264ba348843f888b7546594939418311e71f070fd7c3725902392574fb5d31cf7892c6f700a14f28384382e307833b0bf6dc64b7d748be4ea365f97abd5cd39197527ee29b232c20d2c0d25e82feebb95b685bde0cb138c01260fbdce09941a63b0d9d982996ace76a12662bde2bc965e34866830f5a4d7b636dbc8ffa50b5194123d9ae40ac2fe2d3b7918069f23cf07dde4cec4243c3992f08c86ee2b35eef2b5559f12b4d5ec95fa394778bea7ea54df16832763e78f8c7d81ffdd9780612eea0fd23b02c7e7c4fa16eea15d10a98c113589ec84d40116ccac14c055b44b259d7ee46f9a32741c92427c825a3dff8a4dc30e2432fbb32b6a8b9b01b1e8725fefe20e229b375788d7a14ccdcbb505b2eb3b24804e78ff7dad2aaf67c7efcaefcfdf75cf101b3bdd203837a2bd0db2c19ed3ed0fa0896198c21005021a176d40922e4f3359564dab9d9b6b457c8c7ec0dbfd2dd9968940006700cdce109fbe004dab39043988fde3cc2692c3d0e23a3421a4dc885ab0729235adc9f18c1c9426af70f1243ee0721c9e625682061eb2f63e98ff658941097b6cd171ae6d01ba420c07ae0f1ac0c3cba788dce7c1674fe6a46562c17bdcc50ba11642ea9e16f21b748b85faf9a332c76c823f19e7a4b11ad53dda3c3960f7cd3222fa28a97560882a6be65dc4b577fe813f98761e91a542951601c7cb79bdd6ee9285aeb744134286741f17ca52fe270832ec2804f1873ec91086a3abe1906ae0336594350e610a95b36980781205395accc282852a896848353f3b1a4d2354f9732c023b5103a44c202e9205c8548a88abbd6c89484ade915c82fb0a3b05e4195255e1ba5d654094ef31706567a9736263270d754b5569650340c1ab10e3d894f81ce083222a7d19024d9fa2431942af65923f5b3a61cd68d364a303d7a659ab807df3920fab8742ac9224ee825b832be0aea177c6b080323b8f96206f2c2ade520ecff1d2a5bcf25e2a4ca00ee1dcc6e707770de2ba437a105ce01e19d81f476e0d5084966665f6063e7d18f426dec8a908e066b8437d833e8feccc18f9afbb945bb58eee3e2be515f07ae9dc4c7816d4de6a5dc372ebe6f003db09248d9c6bbda5e9d208c8bef7c1521ef7f67bf3464e44cfa72c6d8ce2e9a4cb091fdfddad741ba6ea735fde9fa9a2eb40d11b2ceb61d537953e28b7ee42f5b691c9486d139b2e361bb8a20d4da1029320c2c7b735622e197527f9f8a2df1f319f4a6f461168b00e3af5f3d5c258e89d47941495da0dc66d6e4ef0399fe1df544909ec3b597e01458879353557bdef923cbead4891cc3ad7f4a416536cc89909374cf8dca298a665c229397742d6915c6fb6c8660d11bdfae16d65a23616aab1f3c27ea74813a23724b0b5893fdf72aebebf67e2ba7d4b2e0b3f3ccf4639a76451153606fb0b2e83c4d2b1a4f42c60706b31aa0884c013019cc13b4a405cec6b68668f2800ab27a82031c716089a43eacd18f1e3902a2b5aa6f29cbd93eec43c1a432b2796fcdd0072e42052b4d2b61b2eee4763a44dfd433b01011854f94476b1dcca10d38ba54ec14adc77bb155796a9e0fb936cda0cd1e9cc58fb7f9ad859260036b1298f6990e6ad36b31f2d7890f1e40ae8173dd1513bd1e92cf897866cdc9ef61190acc7c2694e6255617520dbea15312569b8dc12deb9d881c8b18d6222fa11dcc49fb67a68cc62f2918bb2e572ed4058eff18338c8f433d6bd506c8e6834f8c2209a5116c462ddd91c86618a6d349ef65abe0e692b64dc3498c0e57b220d065f6194217f7b701ff7e3d06f394e89597a2e1ae9cef5cb9dcc7a4ce1e5aacd940dcbcb2f20f63ac238f8e0638b48efcb1b00472164bc86819cfe3f313967a180dfd1fa21209471b9d3156f50ac6f3179d37f02751f851964745690ce5c19a4031a3c9206d7331ea4dcb5c6d3e14444be5d128f8ef5e2a47652512dfbf6b84b427eb3b507f1fd637c8a67cc91cc8601cd636d2b5d274e1d312d20795551886d782d07a831ba30e6de44873c9586e2af45d09144d987beaf8c1f1672d9667767da1d4f5a4d2d006044dcacad0d9301824aa6922053bd68691c9de52aade5429ea2b7386a8512d04966488c2596adf2ce262e3ffba800a520d54cd0202bb75cec50a9b4e09c7ff6cc76cfbd4ffd60056ad0a6877ed3f02cc4d35a27c4fa4a664020ee35f02b855fa5fb2b9dadf018ee3cd8744a8748b9af849cf9efc43ab80f105b573a7db607b3b5c8ebaee02d9ac78c16fdc2603365007c6988cc0cb54e826dc09b0ce4767d972162c71eba288bca6305d9c1622c9d30f7d4dcbbdb6c14771fb088ff0f8deb263f683b46c95b113791594517f960346e7707184615246a19d091b33e4a2a4190a21211c222b1fd4bf29c9230766857ed6c9f2ad933d9d68ac3863b5cb6f9a2e8a670c8b7dd7a41c420ec67947d292b4a5bf83619214d1d3da2b5d0c1afcb93f4b634aa42697b76c9f212547fc9cbaacc80dd4f427ef5237c8136ca77cb239e0e0551ae5394fcf916468b51119a4faeb4fcba777b6a06a08c4dd9890885c6336547b290099098d77ee6e687cb11c7e9c6cf27af73cf841d9cf6aced5c09375a06885de401ddc7e26dd9d84b0137de29a780a5f6860c0b4a6eb0e8cbf6a8f63e39afa721cd79128f56d13181a6f9b1d745f1740b5c1a0cd0185cc3945ca67548be7054881cf11e09ac2c1613c63c7ddf74077ac1a9e2f33b5550590376f11a412dd2abb573a8f685f3481db3226652a2e6357e775a0e1c3729552a120cbb509d23e37dff70d219006890dc96f888dc99ab86ca6e9388b312d3d1da998443ee890e3b76449a974a8796634fdf0effa751ffa5d5286a739e58b70c93e0ff70158e30165096043a7dfc91a492b090e1eedef333b3c8445c433c7126e11da1087d8e647d6f86f27382b97bd2c6e720fa738a83203e9f55f366fcf16a66488bf1ffc28ad0a2f6fae6d99133dedf84df23021494d61d98baead2ce6f7272d84775b4a57f060feb0f9e22f9046c843f81396a7f26af4ba366f006b16016704e58bd14816b877fd01d2432c5b69b20d0680560a65902c690b9bbbb1135c5280d5ac338b42943fc90639311d1ce1de215098b235e672cc3c9fc061f1d83bb1bb47d14a54765850dd65d4b55133fe44815f413435c9ba4df82c74deebf33a14347a4f75557bab78622e5b4f7db44cd0ebd74088cbcf7a3a7e5809f61acf57ef1c3db6ba821f71916bec182a14f4984d8679eeb332e0c8947e94f87b53e1b0aad24cd22fb44d1c7705d87945771c2bb7b83b1e45bf2cf9b1be2afea8cd468b4d77f4eae5a03ea8b77273810c28b7a026143febecf3918274d1747d83041f9870b807affcf2973f6991c6c0ae36fc096a851834401872a7e3886a1aad8cad581dec602906eb0e47fd692c46bd5681179e6a8c8b8ad961d5ba71206237ae0d68e5900f8d1c607c32a16417baf8ce2cdc8f0502e9e12c3c3c6cf5629483a8dcd24cd94f3e01a1a8c84a5c7808f3e661221620cab499102cc85625e4ecadd32724542848d29a8d68717a169169db269c301c84ce4b585e0b6f33db009658664c8819fed14167cf3a7efd01358bd8b9d08c5c9879d5f5cc9c7d3c392a3850ce88790f26a039cfdc05485b37b2978e49092e847bdb24a9ff05b6578962696789f75f8ccc17dda55342b5e2f709db2ec09387f8ab1a7639cc4355e452fcee0066d16853680319dfbddf011c0aabc34934b45bb9f354eba5573bb85dd808511acb8653aec5acc7952ce8f2a2590b88bd147d11d5e32c3a392be544acde48bdbd132018364c89461aad7be6ec9f7ee14bf9ff18ffe1324c4c8454f7de56d3d76d5f088ac2dc14b0410f9a02f5670a5b93327bc6165bc4fc256e08903232541215f90d26994b405b3a2f646d731137c0f3a76def4e4144e784bfe052165a48e067d9a9f59504c3125b679fdb33ad803ba0e230793d24f25aba2348ac407a2f76ff7b46662b0f6a5826531524045fe7fcab449a302f2cc092d40dec112d918387f51cdadf39ceeea1615ba3863896882c489c9106971c5813378922d0bff67ca8d1c1fcf93e78d580789f6de15d66089613236def1e8cd1d477e15f3bf99c16abaeb0f4005d6773a74ee88fe61f2cdb2bc632d44562e96fcd7af498c3f135908ac039bfa2061040c82f0a74191d4c03d0b84e24accb56233562504f83b82d08de658417c96619b1dc9c13c467788db4f8f5bfae0d91e20f90590a1bcca437ae113a06f0b717193d508f8ffa7035adc31149114952a19b6afa7ac690f5cfadd4c91e25aad3370d4ae30c336e11d0216cd8ea103ce23e51fe1f7cd5b4a169c702c5c45693fd2a1ce7bfc2b5d7763b2ee0129f36d3e8c49451bc04f92748022989d3d86ad1fc04174a95fff8ed2dda5fb9e526634c135593bbc0f3ef1707076e6780bc079fe036dcd9bfea3f577d548bc3990c5cb194e9b57071b090e76bd62ef90c61442c08bcedd834000aeab09580afe23c5e53d7780276d5a0c760474af107546538b35a80a9276b6eef71c4140301bc1e883c29b3d3f9fba37603f342ce1e7e4f804d28d51d6c75535019ec175355be0b7f0e2dd1d1aebb7f74522fca17c2c7e200e779413a36a9422b567f8bf9d8d4e2c90ab7a7f3e8f9633a4fcab477d180bdda3f41337bcc6519ec92352a09b9d52a3fbc032ca1d464e76001a2857c5daefa766efbd9e3ebc5e6344c08ce81a8ced0b5746bc9593ba0405adace5ec54ec510ea368c38bf2f673c4655856c2a1c5fd2cc6a61b228e31a92450695cdae85a281cc448585f4e2dbd98ed582e606eb25df9acf57eefeb500037eda579c24608f6f4acf6744e679cf36604f90d0937541d706e262333011c8b0ddf671ba1fe693a649b53a2978a794b4353e7de9840d83a7d13b2cc543cc38a11a50fd75d55d0c119fb7d7790d37a99f333b2e40de6eab10e092c2fa119c1ea4ac5efbaa8bdb17f40569bd877d320a50d014fd0a095e1b940f857b09ea15002d605708ff2d4b7b0609c9352a2eb72bd66ff459c3df08c1135f8b9f604ccc15956c60cc054496441c8e725de84da221af08733753480cfbc08d13c42db674d2314be78e0d1435ffb68044453f1300868d620ab3cc0a494fd735f7e8d4c7fb3cf74e23dd5f8ffe6e47553c6562555f28dea8c6215ce01167f78e73a1c821316e60ab03d79b64b10583713b7b3776435062253c82ea6458cd2cfd2ec5b76b6dc1fc4beb1d699d751febf7abaf7ae17ae8bf7814f741b44b8ac3e8949487bd746dc958087b055b228bc5eecdbad8f76ab3cd70d72f942269d165b131395e41781a90606024c3b65c9db264aa883e4ad64d7474f3c98c7a32f6b0a8db2833391548daaecd31919adcef3d7e433fecaafc6a8c1aba144325fdf1b2dcfa94868c6f3e5c454133f6c009c4b4609c967d6941aef575737d1b0070c0df3050831eab558053c3c856e3c2b054116b62ab8c44049968af5bc47b63994279e4e704736bf1d6d9d9a196192e106f35991c0ba74c0c55ed48d955e07d9165e062f1529cc6d5b48d381441d09f67035560db4353bea60ec641861985af1d2077c2b21efb9b0d6a4d2ae5d886726d44713b99e709b565c9879d911f0f751e2761e402caf9c4d1a96f395a58d9e235a9f03cc8d812e59b6686adc58b81cc787538ce80fd1fa59d6b3204fce5136dc0fa2c39ac4d56493e33e61a9861e33d911125e8c6d146efdcfb0e17b512e873e4feb8ae9a8e40639063d1f00eefcfd5027bc4815b688bf888183ff986cc857ab40e57c2ad9a0178310408b62751509fb921e340bc4f625382affc8ae74258c30a0461990d8a60128905191e867b0599e2176f1791275bafb721e5980a12ccfc27688246601b51089d527fe7f75f0db3a64c8890b048c5ec40cae6ac8589458a6c5e6c34e8a7e9b7e691ac281fae4ca114884ba0202ffa205fe3590e29f4af83ced73068486c363d39e37defcc68f45bf70efb0df33c030b78f9d3306b27b71650178db31216d10eed01828f48b93bfc82f3cb48ddb5056b6c17c8a49f9c83131b3a4752df7b1cc13110d66b19e8d9d0ed9a23b4da5f46c21998b939f5c0d449f04028794e75c79139d3ee10768e44f4ae3bec42090fd0a87e03aaef1d390062626e1535719a856d42a771835b3095488098026a40173a33f93d6081f5f126f9457a1a37530cd96093d0ed921f7c20ef759e5220c8d9d841771fc69ae42fc1d49f2e79cc5f69f53f9b5bef017ef7d56b0cc31a6ff991a08ebc8911b2ac5ef68200f3b63b71138dd8c6ee1d5dde975a9e8002bebf52d4adc7e9852e5803f71de6a952a9f5ba3e813d7ef782c8f246b68cba9d490459ee5ee190f225e01e586e3427d51e9b835091cb968ce45a0c5aed59f5823ba6534294f3aab622a3aef6f6b00ebf0e945625abea830149a911cc836fdec2b1dba1782d256e830565998d4aaa1918a4dc00feee0d5c7e50882b9c56b5995f46cba0c89b860b6a4fa3a164001bc0f131c0d6efb0ecc8d9301c5024a408e11c0065f2a1035cc1ed189b0818ea6b786718573e0ccd973804cae0cb2e5384fbfa382db85faea9f30c7aa6a11e2d6929427f6c1e9e67b18ba92fa5206b8c96e7186cdebabf352999bc18d1bd87c85ba5e8ebbc96e7b074356a997d281dbd9ed67987cadba5682b91ddc7c02cb57abf3f25937c94d2e9864b5fa523eba99dd7e0ac3d6515f05f07030bbfd0c8baf55cfca013783b30877286cbe5a5dad980ba3349bebf3e0a2420f03b382f0389deb8d2b69336b9390d8d45d1e0533308c5b2fe84871cd2dec27e5fae831800a54e0fa1b1989af59f5ec9f330012d8c7942e2e33c4ddb325fb534ec32dc19d18f8b0b6b8455540131361e2778106706841650a82a5e0d4b4f99c17664888d5a1d8b99c3122b8b38417ec1084f0874d339000a1c59bc6817d853d73a146a46012a0ebb64a622aa3750f0e1a1a80f523b0526cae0e69e15d91ca1782df74958a71bd7bf3aa5786a7f56ed8b981d4a6dfd8c7d895f95447ec1660b53044c7556218a8b7a808431e0dcd0adc8b59f21e6dff5814595e05cb9c963d38dce6f0db04a2634d991536a68d15cc3ee64b6577c41f5c95d27cad63bf943ee37f9520dc539cded8d66353ff216b0ff6a1c6fdf8034f5bba855ae43bd264c0cbe4871d834c032dccd050cd19add082056b492242d51835c0da02acfe86f8af3a4c89d041d9b011d957a09261ebc0e8356d7702db3e844d3a576842417001bede3cae6cd49d3b558cc5be3f3da95f7634dfd61038e0331e1d7e46b8c5e220403abb8819eb2b39c378dfefd727e2896996e5de9c6a378cdd7fc695d676b8b5f404164fd754946484cedb21e36e73fa701bb74fa47726840670235602c5be5d3969607593142cc792d17bef419b64f573ff645b3cfb14319d2542af6908b371f958d0cff0661de4f9f7295630cc8344cacea6a229db5560c875cea6cca6e3d3bf94cac1f30239cf0c59b9eebe5a04d230377eb87cf0d1c9e7d699d39a1045935508ca3611fd3712cd2b1cf4447164cd0c07d22c3f26e65868f04506a862f49e1d3c03dcbb83f53411cf1f1ea890f50def4167a0c64b8d6802f740a8616ebd1a6ff9150ff102895065e7afc6e8068592b11019dcf7525a03d44f55ad29c655a61d9288c3ded98d98f032d6484b97e6466914ee9f5bcbfd77ed062a744a5c7930a77c1969c303d51504784fc7d9bc21f5258c7435e5ee906a5d2add33cbef7c0d5a9ff1e8425db34a08e67154ffe4f27cdc2022b25f724a6ee1b8df3fa11e3dccb82d284e1828a717bb9ad9c53e7c134516b81a7fb58f69e2357f3a07175f92b1213ed8a8389f5eda2751e2a317c503b858f4ca1777a843a862c0e3fcf82d456381bdab1601ae8d4330c5da85dab67f3c446ca7b8cd853ee1ea1b63663c78dea1d7a7f99f55e211eb633451c12169c4685a782b0aec087ea52158c036f0f88421b1a03f0636341a41b26dba174554703ef42f885edac08d51b6ed5e60c87a895facbc922110817533f0025ed83eb8d06c6680c4590b852ca2ea89354d7010f00ff51413f35a30cda38bc6eaa107173965727a1500e4c88a0e16ea1388adde6bd70a8348d5b03a0f4e3b406ad7c85ef9a99bb7dc36b06984a5c550762130607b69e27b78b51a10aea73d2bc6da1b07bff659cc10407730446b7a059cde033aab0fc258200eda4a0749824964ba0b049c4a5a44b1909f2333bc848f774dde597243edfed8a79320f582a469ee220d0d1d74a49ce8a4fe9880e497dbe33503d6afec195eea38550ab6ecdcab4d94ea4854354bf6265b6e29a54c5206880cd90bef0b679204e1c1245a1fbaf2fc90230a172b5188f64c171899ca49d1074cf9814b0d4f6cb8e01c7104ee284e62c862ca03e70a0c6b39eef53a727524ebc8d6112e1a6c3428d1b064612cd925fba45a242a7e787139f1bae999a22f9ba541b8c107322cc441b304172b306c32504055191b94f002831c2137dc75d3d679c6df755d87b18e29ab83caeab0b23aaeac8e2cab636bf6560717d1db1d3618b78ea33ce02156b0bbe546b7c07cb6349cac1d443c59c3039b2666846410812d304b4d5c69eab2a447171f5de24451e5244a19191342f094c903c40d0db63056a6bed8f034f990a60549439a20d1d4022d2a406cd55b6badcd51a9a8de726f3f29c795765d145c4d9336a52579468d1163a06c69e9e1c6083060e89d31543fa0d85cf5502750e58a112a3dac9c6842660c4e82c22686a516bab0a8387d3fc8275953e24c8182a50c92315fbc383180288a60a9f2c389ca9c4bf1ebc56449d32413289809549645c151a2e02b79c30e516ea420910316145509424ca1a1a2618c951a68521078d2a829226b8a520d628600d2c6cc1c344d00d18ae28b14cb8369c614c54c1049941942a5881f4af507108c45085492a517a282d8b9f6f5e26a6a9a747a8a32e2ba5d8e48071ea54b04d7540d9381c49344a98917a628994941105d9044c9e189993979640c4a40f9de7bbfebb17befebf5058afd02d5346973ee59ad4e63b90b15bd31279e17d6f05ea92f585e525db8dc4a91642edf7befbd2ec37bc358aed98a01aad2d4610edffba240c828b54d783401d284c895e18ebb61e88d0b4eac0450518ae082c70d1dac149a94a27cc9bab2868734368627ce9ba85c9b1bc40c779cfd6b5f2f26db6552ba4c4b9789e93235f9df3bc3dd8be3e1890d38f86032d5e489681d0d9d3b3c1471553565054c9430f6c57987b921a28ce9a29c49111ee18a3284122a608c102d9c19864013840f5074f0ec911a589da96262eb2a061984904b58f1e21424093a504ac8a96ea97bafeba65e4ab500f5d6caa923550897b7da3d354d46e15bd3b04f354dfe28732944971d1d519c70b821c8d45b28a6bc8522cb8c2e4d7ce9d251e60d113998ebba1f368c7f10e90fe0ecc024082f33a891e1cb6dce129cc40a12048839505b71a044212f00c58a5697114ae059c2cb104fb0e0009e2194b4c83083d21214b8880e5ade1ce126c9941e5d6ca84a7c5ec490e64d191f63947e284d224c4046a06041c7ed081eec4c11ad971e7bd0382111c4152e45e2c08a74016d2962c647aac81658112e748de99202c48b17d12291e187ada81e3ab2eca0a5eed84a7ad244151d6630628ad2122d60a8de8ec048d690198c648121b3651aedb1fa61c5fe0882f61112312801e7a908229a442bf556881be41b2b64ee10c1014b9e293b29a29662549e5e50565eee382167071d98a498116d78c7883c4bdcb8c981ca667da0f2a26efeeabad70b8b53d761f0c3ef16875fcf6aec01373c30d05b17d337bf0419ba747842c6894d8c681dd31aec0628334b58a5412287684359ecabc76acc2b109e0433fefade4f9743c46e73479b3ad9b59f424569f231c6af419a23436cd0414d1b26a275c05b29195e782b15a5c82bc9062d2f50b8100488520c148916cf7a537fee769e5bd780a86b5f0de6d68d42e85a6e9a744b930e033fd00365d3300d87f952394ed66e6179eb990555fbf064cf3bbbe79c73ce39bb29f54abd3e0d852250e7b4d7daea42d866f230be56fa22042d112206186f6b28848c85b4f3829222ce349146089658b96c2951ba6265cc535703def8e8dec400b290c98c28d19b1c5d977be7154708278444984f2fbd3b491ea55527c9728649ef514a9b6a659a73c9b60d5b698b34f2a067330eae960b65744de7636d03a8709119020d569d21a056963560885ced1005430b0c4ccbca478b8a8f953ade287d1cfbdefbb214863c4222a23449679452a732cf7616acd9d697ad20c5948624296d4e244a937e020ae83b5101e7dd4b980c19d366133919f22672610894b6c24cba5db1fcbcbcc501d293d624185ea083e60c95325049d4be6addf0b56a29f9aa42ac2e7e2003e507132b2725628d517068d661ece52d4e93f75edee2d0f0e35017385d2f6f70887c8b97373862be9f648c2c5b54af5eafbeb668d48d248ffdf4d5e914ada960fd466b2a5cb7a2359b87abc9569d46d1275a73a2b5a67aa335294e517df5a6a2b5db39e6bcbbdc6c2b5ab321be6e1ddf300a75dbfd446b72ea8ad6a658bdb9a82817adc925275a5b80104f534f51b436896043f564456d4cd5463b8bd6a47845959aaad25771ecad7a35bbcae0611963b628ddfba991c20c20c73ee37a901f3981b98471cecd1be7dc8c9a4f41e1a4e26a1b2c9c53389c565b5759269430a367d99344af3a2cbcd1349d6a88844314286d6670324da5a51a29d42001c83927e13acc4190ef047b8d19383e7f3990cfc0f1e39e5ff7b2dbecd66b504fa10609422474a28fd740f20933a54dcf8010093e1ee441a10a228e241104264a9b0e047613a54dcfa050fbf524d1a36005a4c8a3731ff7268c849777b0199efbb884cdf866fc84ad4469d37d4221eab0d07b89d2a67b60089436fd052251daf40e14a221120e6594369d0b6717b4a05087484494363d495cc12789f6ed6aef1af2889fd63a6f9fb739d5b5adb36aa5ab6e46515abb578209284ca7699b4f5349c2aa8b25505abfbc3dcbbc1738450e447af92b54a1732e9c617046284402e7d871a882e79c28456f7eaf238a843bc78e6385ce397f852b74ce8138a29030e14c19c2267ace398e15c2153a57618517e75c37d11af6f676a235aea13403acae28ad912615a5753bf56cfab44d254a6ba41b94d6a01009b80b674224705d4844698de494d64d603b515a3bfdf411f7d31502034613fc306a179960e84fa8ded74523e7d89bc69087c10f3bc99ccba7b56bf2f6e109a0800b4d30f4373cc1daf01bea0e2237b0a2a2750d64d714adb5d662af32acd6bacc6f44b67efd86ea9e14df8fac1149794196d58fe845073990b8b95085490e54b1228008d12488205578287a9307ce0ea0fa82a832e00d9aef0d9b14a112476e48e2a94c112a78c42417cc702141226a86214f8834ccd3f6cd8656bcbcb971e2c7a19a8337675ba123433f527783840f0d690c7df558be99d66ef8a2dd8cf92675184ff2f8e2450746dc6cf931cb9b063337597e9cfdac5d89787dabf55a6b12d4125170edb1d2f4430b6d2aaa0e15a9344d8478110657555b2b1d420f1ead1cc27cd10a225600007913e6cb99a52c59625dc0d73756bedaaf6f82f80a136bb5d6d637505f6b97af57acd556c7f57b797323c54d127b65830d36e80da8329009c99a51d326aedc009a863cca8646d9e4d1dd9005768a43234a9b9688178e1eb4531ee5cde82ae0a9160c4fe94deb85a73ea401754c4316d3665f346d624ac371682e11c7872d4fc8c9a106a1383fdc6e9853a505220a186ae9c6c7e797b736796edf274fca183705961dd4dca8947864890e40338318a920608430234a9775d1062f1d89d6e46d060c6a446b52c4a274a9012aa389ef913ce6db0b181d30e5faf2d6c6ce8ff256cb6de6d41bca271913c124e696c4151e27b2ec5519aa396de4984942745fdeda68fd38ab489ca8e3c412efc902647f7cc194b52c53c8da8b1ea5957df1d38310baa8dd9b86f1c5cfe9b219ab9ebbc8c5579711c91ac69c729e74d67064f1d369289df8f9f24b131fd84c79238410714469c4d4115771b22461078ae8e2dbdfa8590ce12599bebcb5c9fa7176c3e3c9210a820ca43bb9dd9b5346cf907a567d0334ba68723e637e09213001051b37ba880b6584483dab23b4073d738271887b280fe5c8e3f7b5ba2763d17dfb5e97b02bc72f562b96366b839767ea19b22e5fd36b10fd0c53f8f9d9306421116693996faf41f41da2a0b6a030645b2fbd8ef11cbb06beb71dcfee0edb27feeee538ffb1ddc6fc4aa1b42902b719a20ee0e5ad8d933647a857a0dcb3a1efa64053e84ac7917b2674c30ffa2fbdb523c9c8f3ed33bae6627b0950df1e821fc4761a6ebebfdd4617c95bd6926fbfd145b62be72c01df4e3483b4f8f6cefaf163077f0483927c39ab1e3ae15c46b9b0021d01b1ea196dcaea512555c6b45e190b28ba662173c2b975a396d11a171a511b52afb5cef9ea7dad1ec81badc96ad5fa7abfd65a431632fa8288c25eded82861bb32598d13a96585e1d336421749a217120559de96c87aa097b725aa9eb2e1e129750f471a433fff45571e898888a64f974179fad45de230775970060e511cb2fcfa17bcbcb199fafaf2c686cb8f43f409f60b620c72dce5eaadb7fb0aebb24142dd9318638c31eeba5707ce7fe1aeebbe9e593b2fec883a073ba28e31e793c338c43db318fbc4d886a38d8a6fd775afce5eec95c31887f3895ee7c07abdd6bef7de6a6f535ab1f74cf67cd130420e557823645cb34f8cbdd6a0da05e15a7307c93ef8cbf5b301323e5fe149a227291015c21c11c5e107fd7846d784b67a7c7513ba26245647e1e9abdb905ae8d4f91af4554b9d255f314789b4d021525dc232f0f98a10c7e72a422119400e73204f014af106819ec77c60164c014af163778a419f25ea594581d2aac3c01b9456dda7fa0f5802a5d53a83d2aaad445f12d73e5c9e1ca23e1dd329faccb3c4b5035be308861d810b559c89698d07e73c7a6071c7dc234699de238b31979f4b99657bf228fd06ada9a0b5cfa94f265a6ba2b5205a4be1403e4ea5497dda421c3db350b6824e7cbcc11a47f3a967d467d853017e60cc558053f4260c09a642b6a0fe812c863078ea2b2603a87b5747ee49a7d38dae11a0c90921a71e8e08da6284699a20b71801f2d451a0351e51d63cf589446b1d8cb983f6b183a731db95db1b7684c403960252ccabab40b6a0cda3878b722e6094513cf5154c06fc3c17be845ee107fd2fec37ba36735cf950bb40a43e97b89efa24928328a59e50f214e8a9549d2deaf3e985bbce250c49851b355601dea08eaf8eec04e61f386de054ea19f5093a51c222cc63a0521661e10d4afbf91b7a32ec5ee752b8ce8520c8b718dc1c99eb0a4831e62748fa8ac423c861eecd648c9f18f879bd21d31f673324a50991f0b90a5711aa100b25a555972228450bc24029a698621010e8790af0c78390606e61483c5848b2d9fcc781602e6135ac80143f17bd0f9c4b94461d06fa803fe0b4511a756f2a3df501a508133d300976847483d2689c3c4346cfa8c593a79784dbe7c99c5758986470450e941636800d4fc21aea4c915227e84624050d564d7a5459d3990bb454d1ca818b962b587ce020db16244e645b66c8d2218a2d34500de980da8590ba687a83b96756b5f0ca66b3e590522d80e1a76bd0b59838a5c874d59cc4e9465c2b8aa454d5d64f1f4146a5cd23ca18fb299bf8a9c5071a7efebc7ad9f939abe797522d50f25216c9af3f23e2c896942d3ee871d2803c3b740c69b2e40c197106a115039494223a94ca6434699220c224cb134ac4e997852c6b2530154162899c35554dc4e932594b011b198650355165a94b9ceeb296d4031163c600f9e2c4081e71fa4cd66ecc60a489234174a8d2244e31cad634f962d4558273adde4e711e655fbb2819e3549543111f5dc41ab35c59be0d3239902f3d6350abf238bffdab2845e9d98766d6dab0c6fcae0ffb640bea144bf65a76cfea33a6e06f5e0d1bf8b1a300bf0c6bd8c8dfd3c5f4fe6a6b57a78e53984ffb935d97525e16de218d92a54b9fb48d2787c669adedaa5cb44e6f6169f5ac6265f5ac621f3babc3e1ad6e611fcc201ff10a1f65d76338e6d855a870ec2b402969f8ba0a3006a600c3ced6d186e55ed5dab09fdd66afdee5ae5bd359bdc67ad35a6b6dedac356b3a8b32c9eca3ccaeb9c2ba5a9375b526cb5e615df59acea24cbda6af7acd0d4aa34ebd5738e60c4326cb524ea6c9446979f40ffcbb6c39cf96e3bc837df072ce6f00b9e792fae7927e55b27fe71c5098c351f6138ef9b34fd875f706c518875426cb5962ce3bdce1ce47d9e3ce311766d9827e6005629a92ce883aabd7ccdb4f071de1494f7af2030253381098c2f6fe524a4cd37b140b0b0beb07ebe7e7e76762e58945e4eb8733666011f9f119583f33666011f9f999f12382d35a68f506782935e6e989e07a00bc940a93e63dfa59ae5a876eb5d69cd145359c583f6ea3af8f3fe10dc789158eedd8a5aca5f0317b75d981526275ddfd6c504f014a4993e1751976e8321c659a0ad26f98fb3867bd7e8727c0c2fae338e4b82e3ce1c7abfbf88489f0139e60dd7ef5f97c2c57dd271c27d6df7aaf848570c366b3e9f038ce1bfbabcf9e738e73093be2fcded88f347efe0d4d107b2e3cc17a97af8f17cc3f41274a2e0639f54c01204fe1d227c43de338b7e018e39c04792940197bdbb90f7841fc13b40ee417f471ee031f0f02794481790f17a37c6e4481401f10f681392663e158585858583e0d74c5eabfc754513f7b2945b5f4322a01eb5c388a601d3601ebdccb31f6ea2725d397571808d8bdf004ee7bebd8bebce6d45fe027e585c127e5050502f613da9d862f8e0bdd56f0d682221f315b4ff4524accedb5bebe9498a522fcf4b73bffd52d9f6b2b58bae8e86afd7c9a9e69cd67740804be689af50b8e80d433eb52639a9e012fa5a8b25e46cfacd3ee6e2a2bc7ddee17cceffdb93ff7fef4cf8d00ece7fa48f4edd5e9edb6c1a9f02ff7248cfa055fe15841f0fc7ad3eb344cd28b4e7f5d7fd170acef8129707bbabea91b3577eb22cf279d3e369739a937bf397d42cda8ae01f9f41b86329e64eb23d1db8aed3768ff822c5e7651dfbfe0d0537054c153eff5514adb3b9808f4bdfad7e7d3bd35e7bdbaf3e67cc45cfbf4e6c7d5ae01f99d1ca5dd3e230bec0d250c04ea81d3e9e73e3e6120485a734ab36e69e8e333faf8e87302f61ff77c8479d87f3ad8f50fe4b014d5d47bef3996afef719ee7795d37cedb7beecdaf0b611666dfb3f631f8a4e4515ecdaead5907235df7c6157c181c55f0d8bbfbd73d70ab95bada5642d65fef54803fa735224ab35eff7ab7be77b16656d3ae57d804bef79c88d68c9a76ddcaa9bf5e38b2e0fb3ed95d780276ce5ff37bbdbc709c38c45e57da3e230b1e3ff6f9646572208fb07c68bf66c164168bd9e8b6d96c361bfd7d43482ecc45c910c925b7e09955a1f484f9ea728b15c1eff4227b1a32291a1f8dcfdefa36ea5b08c86f975ae6a45d6dd42168c41660240c1b1a31216c541b55527d6b7c3822a9c0d0980120cc1a281a331bd66d369b0d4b8560f17ea61f65208000b010c20000b20b56a888a50802ca3f3eb0cf7b7598bbb6d29eb245bb13ea46dd2d6b3494923641f92bcca4ee4e82ba639cf3287bba822787bc71baf5fb59cfb65a1bc67a36258fdf57675173ac5a6bbfd63c9e9435ece3756e042bfeeb7dc2c5f686e3f5399d0be7f5e9f78439441ee904b10dfad3a716bdba6ef23897be12d51908f39d609741451ce25841949456eb57eb5793d4c945012b7230e48a2b941e30330bcf2c7a557f74178e35e4b15e9733ce2587e3ab4bdacbdb5fe1681d7fdc57ffa8e5acbddfdf5b433a43ad75566b6dad561656ab09625fa9d687a5c2848900fd32c6f96b92478c732c16b31f932da8db2362f8cb0b3012664d2c968397bf5a290d1bf258eb37a35c78c3bc2657fcdd2b93f5d05eaf2c7fd567a730df528cc7a4cffed12579c438ccde313b99f6972f77ebbd39d0f8de5a6b82ef6b2d73c14abfcfc72c6b63e7f5863cd6a7345fa7b2977de198bd1b96f0d75fde3011fc2f3756f9f80b6b067bf6ecb1618544e81ba2c086f3a99bc9d44f29a41497a6b72e27539728dae54a37047e7ae1be70bd565a47ab4e9d3a5d4ff090391153354c069e01e6a813f7ded72bcf5673407217d65f0a8688172a2c201982a78d1416f126118499365d412481c41df186b258d79251c7a6c88830955de7030bf6c105fb0823237297e0faf9f9fa9252385471d5fbaadcad5d281056556a959abdfb892c6cf4c456a6b13144f7ef7dbd9074dd1a78dc1a80dc1a88dc1a90dc1a96dc4b645f1673d6da98b52f7054b66a15dbbdb49db81f276bef8bdb9a46779a32d0b2858bd224bef7bb41cc2fb97b5a61b2d27433ee38cc71615e50aca05c41c982b205852b8a2d8a928581bb1777c30b73d6da97a53170659c202933448e932333a8a1a185166c3b64014283133259baf4bcf16287c814135d3e39729454d97952e68d8f7e72f524eb75efbdf7f5a4c6a27889dd2a89a5a3494c58c88262073743aa0c5902cc9310d22d0b0e4ed010e7a6e312b9f96cf101828b7e52b26b0e9a1684a009f3820d5709f0b1e4c70d32d8a85c9154963e0764b1e1ad7d7d16881a458e26505ea25893438a1261921c20141245cc953a46e6f870dc71f5d6ebc255d36416decaef0297cc29f7e389fb418562ee8b963b45dd5c2728272c4e5c9cc2385539a571c272d2dae18c10300c8104092e51a24de2ed142004309187cc115a6fdc50055da178b8a0b857a0ae6091dd9d7207c88e2917c2c85044bc5b2c10d1049a2d3a808c41224b8fa02c4cb7ca7cefbdaf98e79cdbfe3a0ba92c3a2aec2adfdbd599678ad2a4c5dcbd79b8fe5ef71d7f7d0806f1ba064457cf8dd2e45b8ba198b2093153a68851f3e3ce9734533dc069f232664a0f23cc4005b1302871c970040d9a24d04885e92184822d3c04a1468b942953ba224cf5da48f2e329dd7024285b48af5502bd6ce9369cb3ebeeee6eae7ec9574a29a5a46dd00050d2a477bb3ff5592ba54d6ded9e738663fb77fb77067f4277b7d79efd28a594b2b250e78f5d9367b7ec5e3e1e1e59bdbb7b8a734f1e7f9e0299e2808e5eb0f54c3eed5e06ca18f2e79eaf501cf7d28bf7d3f1f43cf3fc741f9a11bdc1414e51c9aa128a982e4739caad9f2e47c935278fe97309ce919f9348110b7b76f46067c7ad05719c4c1d38e1830d15266e7af8411c67139e1fa7d3d3cf39a7cf9b113fe738c749f5735a5dfd9c33eb671c363fed0413248c12a8199814218e93cb050b67ae14015385e68838b68debc7565a6a22ddb485c4cf5ef2938910354a4b4059f962471cdbc9070a2a34ecc1b2840f32dcf9b1a36e0d801fbfc72eb51ee32d2e8f3d533de660382fe0472e0e149c3459017fe124f9eb167674a7de90a96f9c3019ad31140f400b87efc693eaf1ec62b8fa49c3543597eefa1ef1f491f2f8eeabef76baa465e4f5232572d463c818abadaa224dc49132c9b14db520070a2855cc88236db2f32375fafe9162f9a6372b8e94cb776fd5f9760d885e746d8aed4847783fcaaa14dee0b005278a282822e228ada4a6f8f0660d19294ec688a3bc7ae27f945ba3ac334aae6f1b8f6f9f4d557c7e9c4bbe7d9c4edf2ec7f9b4c18f33aa3e97714e7d4faa59f5ed73cb851f679d6f67735543d417246a38238e934b3e91840b134ad8a97a228e6dabf3632be51f7be9bb892c8963337db738f6926fef9b94ef1f9bcb07c0861ec420c14417132ae2d853df4838c5a0f4b485440f9d1fdbea1b878f053c645942e6082c4d4a22c0525a224798237876883de7e99b427dff48a3beff5c8eb1ff5c8e2c58fc38f49f4b3755fef3fa7976a3e63f277af8cf0928fef3fe39ebf7f3eaf1ecebbf60df8c03f99e63d891d732991b21ff02ffe51dece845e5c8949a267f89a9c9e9f6d4b4365fbef3ce39d8518745ec1676843387f59c5fd811876551db8e6e3dc0db2c1ede625934b3d96c39b66657356d76159b382f9e7a2be55156f5d3653fafbebf09f332f3c4f3a7a482f1f527be922fbf3e411938365481b2c388963138a4c1e3c30d6b6a3023478f0f2017648c6e82c8531792c954e4c9d183142b2626551c122e2955b458b909b385e5f323d324766080f1ac91794f00c08eea0cfaf1c345d06479eab2ac270815f302239228e2a9af903fce306111e3664c901f1660a40cd61e3750ec88214e53d06de108ada75ea9abb0f7f5cad465d4bd033a968e24f1c306a629626da1079e3c3ae881018f48e1c819e2596b53780c765427137164310e5511a594a3e4f2ed729461bed57c7b6dc750be5d7635e6db9dc9b7cf96b0f3ed3098e0e1db8d58f85663f5ed41b0a3a6c293e65579cc22a07a46eaa9e7d1041adc534aa9931e236e4b77dad8e08814852b46302d998941c91a91be798a278aa734043b256fa08b466935a3d61fd8111d639fe5a8c1f7037e7c51cbb90937df2eebe17b4d13df6c9ebe9bd0f2ed3eb0a386214b32f203172f5a63aa6e98a2b1c38a1b1ccaece0030c5a7aa0dc6f48962161a483a70efb210b8b6fff78dcca631679558b12499efa2bc89938dd05c254806fc7b0a30693394995c609c53113ba68be6fa06b21f4d114356172b8ea51c74a46f1e4871e4f6f828099e2c4cd136be562c8a34c362382f1f296e64ef8d4ad3cf3429e95960493235a24bbf3ccf9760fbae623f6fc5acb3450e82219434a2579fcbe58fda2043c45a2054f29be2f4c0f61042429ac331fe3fb6a006b3ce9812ca58e48f352ea88a61f3d257eba062fa58e78f2ae7ebae41c7b0d6559f9eac5f5389659bca67e3a92acbdb8a4069facbda660e41b91c44e5bef35f56c3a75889770e551853d4f5dc7d350e8ba74faf1a5c5f5d35f5a5db4f5aad3452ffa7d37356dba6d93a98fb8e93da7d4ebae34beafdecbab703ad0de2c16d046c9c91e35dd7b4dd11a4e2269d39f50f5f0d3abfcf4f175f5af295a932295d5bf70936cb155821da460f97003112cf818418c1d305c323ca174459c3ef1748fc90bd345e3acd563eada5473e53575d11227efc983f2b04cf76e5df4f3d33d2e5d24a5aca27eba37d545b09fee5175ed13a7d79e4d3c7e4e9f4eadf4d440a284dc864cc5f0d37da888fcf4cfaac5fc749fabda357d66592a3fddc7a7cf2d0eeba77f6e7efae4bae1a7fb846d7e3abe217bf62eabdbc24d5394369dc3ea222e8bdbe2b2b82c2e8bcbe2b6382e6ce3b2f0d26c9a4dd3693ecd284b85c888e8769dee938d8aaa5d5d34da256bb34ad6666d4a4b4cef3f5a9b7512e3cdaf41274248f265b44df40991a0b2147dbc044a9358a2143f1fe515093efe854248300943f209853a4becada6fa40225dc4446bd8a99b64d965236e72aa9fcdf44d4e4f51b889d2e69bec3fdaa89f6ea3eccd4e4db75431a060636b888c6182be45a06f4237700891314c40436245606e094d1b0fac6f5062bd0d88121025003839d32307228aa0719271b8e060e4872e20748a282d11599ade3cba48683221c14dd3711376c24fd3719477e535b8c593435ead7bc0531f815eca2f2e248dd2aba73ec66a488d3425b7537947e34fd68f8c41245b50a79e656d0349a30e00000405fde8f3faf1f5a34f8c1ff9f5637d594317f689ec3fba78ea48334aa06e028542f513cbda7529abdd5083a6b5db50362d455107d6afcb9ea2b4760bf68dd266df4430640d6329c205cd1e2f647810db8d644d0262a4eca012d3f5c31db1674432467ffbec32c9e3ecdb673246fd76fb52cacc969f338a25d710d7196d0a444a29a574ec2f658e1cd6b4a086853b398010a5cf642dc8698f0b576f60a0b2224af76020cc9f445bdf1dd53e6befa8c6d2f93b77d10c85aa8593db29953ce6ef2c63acf87622ea3c38effaf3bc1d86918c51bdfd650cebed00901cf418e942c6981dd5b76f37e2db47a16ff73e10bb07be9c82d5b9e904bb047da869d23d5083a649e7c01d386c36117b07eea051d844ec5ea66d55d4b5ceb1c5c20c956ff7ae7d62fbac26a9da5f38fdcf4b29334cdf1ec2d4ea2221fc5f5b697df755577db3f9f69cdb65edde3eebaeea2229932d660f2cbadc40b64d9c608b9c1b35285b4c1c217892f802856460249a24debe6be8b2c5f4b6217713543cd83e49ecc00e834232300e19a509c91092f172cfbd2ec4a0900c21191d0e5988714829d02217f64db28dc93c3d2de1a74bd04221e9b4d1338a42cfe853cfe4fbf59147d992096412991c95d007b49f4e1e329cf43814925fa91351e740e91ce6309e36ec4fc75f9dec3e238af50c4ef61f65329f113dd93912cf9933a5499f4718031d01b9f54c7ed2cff71f3391a44997de4b2d7286fc0cc7bcf523d15b3181c7083077ec3cf990544874e0b2b3a3871da888524ad9840f524a9ad8d2f3c4264a0f41d62e8095468b920e3d5cc022522d9e27a767d4357ee476a19e35b08320bfc3af4a96bdd4f243d7736ed435aadf1f99bac702f419068f60801fc82a58c3067e15d205751bb27f0a820d334d93a9cb1fe554082374d1d7b317ac7a269fd61f5b864cf11246893265a0642862092171608873844b0c4154901e4a78c0535564861e940429a28d0f385944ea9268ce39bd5b822f7a7695e4e9560af7acf11737516af6b01163a445932f3a66e09a214f1d5183a7eea1144315858e0e30f418424712142932559e90e96265844853bc942a93f5764b962a43f5d45f8c444f892e8fec4f441d067523595b41c6ca0816c6bcd1618f483d0459c3c0871f8e5c41e2e34a0c22b541485a030a82adb8c0e7277f414e4127310702599822502c680538c50f4ce11954e117705e012906b9515f00c27c4517048305cd2e05ebd861419dd779decb7b75dd8ad78aeee5c4c7613e1ee4d37521081229c88ddaf301a5c821c5dc8b813f17ac80147fdceb18f8e329c0cf5580d981e6530a58500528c5d8e7d9a88380603e3ff99322eecf8d87612956585a53613583d680bc3a0a37680de6d5c7ab97406b3fee25841be859b5ba44ae93154e4127d9695eb1443d0f0cf21f30e61fb8c27dc0ec40e01461a00a2a605c97653e5ffd3519502905d234fca09ff657023455a72120d15aad3e5960165f89beb2902d2abd58b6a895f309034a0169855bc7806c511b2693f8ea19980ca88e43dcf98c0bc4ea2854b7511d0750f5a934976a87b14b1812901b750704e2ea5dfb48f415834e56f8d7a0093dab2b1c062a6571453883d2ea0cd9a252ea983abeed2bfc0449144c01293b17ce0072987b2b40a133f9462d6112b86ed454df556df51d0a8960bd6d2884939eba85e333ea44d49baadbaaa957d087684db28d7b78f3a7e4e0657be9ed4177e920d7885c7c72660059231a22808c515d7a01aaac654b01b002b245fb042d2043d2ba4d17ac34df443064174b2fd01110aa9ec99f21a36735742224639454200ece55d0719d0b8548c02a708ec32852e490ee91a4429a5182093632ad5140d2da8f902df1eda3b7f9f67126b6681ce18d9e5d2f9efc40965c9ee87a223dc38e044f256a257569e712c5ce35e5713a3d76393990d95edea0388df3c9fa38a31e63bf40d628d30a198366894d0e2816ec30ba28e62abc2673910df063065d96e4c2386799cc2320b111198d4c8d74e27165f2187b084cc8211e7b97456b358aa461b72d9811e15d64403ceeb2a63cf62ecbeaf1d5e390cb6e3df60eb990f8b1d2a192524961d35b7442b6c03e3607b58d74813d667bf9938aa6a158f48a52512b5a5599a857d305ac022daa086b65a2cef97a3539a85b46ea7075b8baba257acbc24039cb7edc27ebb1c3bc6beaa220c7de2dd19ae5a223b65cb4466f3485b7e57aec1d97ae61d9611e77552c6e96e9d645edb866d16e49d76a5703798a70c4afc1d0108b1f6b168b1fbb1aa6ab9aead6535cf211e5d133ec40a852cfb073d1e8efee6eeb144c6185ef99c2a4b69ee12e5a6b117b908c41b5e4c901bdcd9e745a3a9d6a9131aa1bd9e23639a859d3050e0a2b57cfb0d32572cdc2519dd54f94861d038523ce391cb3ec271c65ee4cb4566d92861de39c65fe9409cbe4a0b3a68b2c3246bb01ed2d8ad66a1249c38e1d67d19ae5d234ec42cca4bc8aac87c751f5c9638f7a6cb11efb178e9ee6b17be128ab7aecaf70cc36cc63efc2acc763be06f733ac59ed46c6a870648bacc941d59a2eb063e7bce4eb23bd3d5ea2b72e1a2717972e1afbeab163a7535d34be6ef8ab2ec2de585dc485dc553f3d762e8bd6844850e12b7c45a8c20a57e14bb4262577b5c571611b569231e64bbc24f192c44b321c5be9f1d263f75480382e701574a8f00b42211256f8057e41a8c205bec2a58843855f106618928a19450863af36ec55097b5dc25e99b827ee898be26edc1447c5597136cec629714b1c13d7c439515daa6b75afb2b6b8640c1d79bc4d8fc76a7bec35ab6e552e6bb34a76e93639d15a92885d0588fd46d17ba3b52a62bf5332c67cecad246b938ad2b0e3a5cc84c776a24bb9869d55b205762e1983730cab59b205beca7aec930ae32c6baac72e877078c2ab09d633db8d2e1aa747670d3586cb16dd3e2ea037901c80d245e7a6b00f7e3ecb16edf549ee6ebac1c525b96892367d830648292b431e67d6d7e9ee06f2b979b4cfac79ebc4d23539d3f40cd3930be6ea121196e59ca9546e975dbef7de6afdda7aefad469a640cd9bda474d24b6f489d4a6046f134ec1fd9a36102932c13b23ea7b76ea09b8cf9861af80c5a23b282854083fa055358e1670a73848943083eedc47593e4ea1b94d6d3c6249365a7ef0e7ad6307ad673ce39c10e8e68c83dd5d44dddd44d30a877adf7ea3077c31a82e48c319e21b9ac16c961c2bcbb4c9633c623f46c3ed1709c717d3fcda7f9e44e79945725b8cb64394f25fa6294571d3497a69fee51af7e74567d466944335aab61db28addb65325b1592c7f964fbf6f934adba883ac6395b9f31494804cb8211c7d370265b4ca7eaf681aa3972e4c891c2abbdddeb4b11e4d287805cfa0b295b50971e1028e5acbbdbab40120d4f39e8c8e0254a0b94101b8485e3f7f56d3c1038bf41f9383e855390c6f741b34abfb072e5e92388e50ce612e6d3c60f58e50ce60d130176c307947206f30913a1fe07f37cce0ebf3a2c6f642efca49e3cefd3ecdacba52cebe6533ee49f7c812634ad9d86a04e3692d8e839a7d69d8e328931e644f0e7dc4b81eee912a16d9f02ddb3a7c5f5b003deec50d07fbddeb046e7a0747107f0431bf8eab5c226506d3650ceae13e0af6b0026827deb163681fa9d7b54ce4aab4f0af4bbd0658bcb85f4b657a75b1c0d97567b47ce895ef44c8c19244c5c4974e92023769221884842a9861ae81c3181ccc97acd99e3c321f161ebd112cf862aeda16f1aa0f000068e1922a4a2b869028b8d9a234c55e248f4fd038b0172a0c9f1c39b2a2e203ef002d410383801451a17a60f2d18bc94baa3e44e0d5368f40c7bce199e306532e4fa8df9dbdbb3d5551652f728e370cf3e6bc30f06be8dd92392b4e953f258bfbdfdf3bc5fd873eb9e67633f59e44fd2e6a8c14f8aa9d38825fda458070d5e4ae941f3f98503f450c5e18bd5ed538e9992244c1041d8b1dae14297150deae14c97265bd00c792376983f0e7f3116b497527a94be4e0e0578297567ebbd97527774f82dc5f723db8310b0f2f8c594440e9cc5a4ec7b2e19be180b172fa5ba5af8a097527698f87188fa70fbbac804b23f617abb7c19e5f43497986e4c9bec2b8c0225506f9f3eca293d45be6819634588942444ea1256010ce810256a071528305d620d43a0b449695d44ea8c143b3ff4181147188ff5260715971d803cd51167931e215aa04491c549bc000826be2c1102c38f3de2e84a3fce5aa061050b93168ce1d1638cd8e128a97a7c1122cf0f2d678820421c65160b3fcaada922926ca599b3670e10e2386d3c7e9c4ab28d921c6a728c29428625c47132edf87136fd802a13064b8d141d2e88e37cd2e14190354e58c8dcf9a1469c510c71d245081613737e10678e9f1e02adc9aa1dded8808411b63954e2f412688d045cb28c6822868927a8c4e937682d290725d40ca16487eb053de2947194c85069810b149a1d4479a76b8917bec0b941cc1471dedea857e8a46e2ec98d2a03a4aec96e6235f6ee965a4086e090068c6f67219319f50e3f1ec89b918ce9a5e463f172fc0a8d28ee9e3620be9bce96ef2b76638a44083a4abe7d48031cb6e30e5920c95c07be207216b26e26f2286f1e8c603404e3ce9a6f4aef687db7ac8a422fa5ee80f9fb52ea0e16dcdd158f92c9d225e7c9f911458fd3ac61d9c224d616e4717ef5922c3121aef011611b080f53e20a1dd4208310913a8ed9435c417a11691845687ad2cbf265a1cb95b8820b3c4958714a554ce20a442c302d612c6b3640e044ddea0f34e20650486cb48663f2e8c1495c81ee10bf66c074e9a3cb5ee697d36d0eb197524f604d9f524ad94e316cce4fb668c760eec852ea09ae6f9731a4ec287dac67d30d950d8564581cd8ad27892f4f123f4f12619e2424e33ae75c8883eb3c49f43c494c12e3e4e91f983d5076258ff97df6248f4e04d63779240a0134d2217b4e6372d034246d3ac6f3496ad170cc52eb41305d4c1fe7130d1b37704c1e44736912994c5d24a5ca44fda43ffb496275fdf4192599d0f3d34360027d1054f7aae44ce66da040a36713c8d528c968fdf40a6460527549f586b5825e6a915247d61e13fb70ebddfde8a34b7749938ef3d37094a978b925861cdce50d8c8cf3be73da5a2f28872897375fb44a496bad94d24a29a5b47ec913574eca22596badf2daa8b55689ddca7034e1b6c9d3a9eca573b023e992564cbb7e7d147398d2a4f74929e594eff5f98c499f81a278b33faf7eafef38d0035f1db45f530e731f75fcd52e9272465dd2efe26aa7e2ee3ed1bd6c9e9757bdef931fecc3b43625edbaecfe3a4d8108dd572963ccf7beabe4640d7b182ba23efe7cb7bc76f2f47c3e29637c30eff5f948d9e2de5026d1b03d30de8ba6a973a54c7ef5c095614bdc64cf88963a42c664d84ba93a3cb6f22b0f11adc9a613c7eb2a7191a8a6495c25ffbc94a273e5850e159d294fe24c27c98fb2db04cbea0ff65e297e3a6cc45facc3748a74218da1ef0e42f5d1e101d42d3979795e7849e915c7e35e60eaaa84ee10273cf55921531c51959ffa619c6188ba38cf394d525022182f5c594c4444e7b81080fe30ce40d2344d623d10ad0a3bc232a22e0e06dfee2f6008b10951a25826e7eadb59d86a1e36e5204c419a1a7684ad5ee090e70fe32cb32fa5e444fde8415fe64099c3c4c91ad1734ebd7a9768a867b3e91d4496bd94aad2d266b22bc95225c44ae65e4a55f9002203e16902ce54fe9888ca9e19aa7cc1adf3641cd554b2cf4b292372789a23df9752462c912fe789d264075e505699c8d35f744004a38be69696ac2fa5b6b27e94d929de387ffa7bf49bd4a7535983a394b2c3f436faab6c21c7eeba7675ef82b58698d2e87bb2526bbbdbd2a111a8144bd74c2339ca94442403000000b315000030140e878482d178a026fa3e14800b7dba4a5a441688c3c120866114c518630c218600400830c420c3d854b1002e16191c79da25a86b585dd58671ea9a60ddc7b77b2d358353cea6cb8d194c125d979a33c7be2f77d819580da3c1462e8207ef881cec82d023fc7173b9c89b26d682c1712a6e8e36f178917c768946d7edc34bf8dbb7e66b2f7b13ef34128f9219e7c7af4662dd55662561eb219e72a8c61561d9128694f88749fe1722cc10ca9074b0e024992ae50cc71392720265dae389c580711a435ed4a1d2eae83d5c421a5ffdd9175da1c0f246dae2d41a1af9a27669653fb01c9a83763945373765a953467c9498e6c58a9517408253b5eccd271be08843ec776f72f788366f6c15a691a48d77e61c5c6212891e8c1c8c05c71336a43c9c08b5fe28cc5f25235cbb9ed90ab2173e0819aa7652b6d3da5dc41eac192b1067fb2419983b5f88bd94997da2ce547395613142a3c2b23cf2bfaad07a662f6c726928400e89e5af6a9aecd91599fd9d41f01e027925e67ba9692a03a1cdb3178582dafd4c2a9e783e132fe4ad56d5afba5035ab0f744a4f60b14f27d210133d36fef4b45c0d1c8a9ec48c468eff2379acc1c031a0045593f54dbda0dfe09eef95297c0dd44c7d9dc756a40603d62a9128a869a5847a9f3bdec18fc970685c55f1514d05d46b11de1c90f5705cb329a6ea2d05b85358e3aec6bb97d2c6a64f2724c3654cb462e820c35707af3a41788e3e77025192d15ac075d9dc89bd8d9f0358e8cbaccb2fe5aedb3c61b8d8f64a21be0d6d63fbbd9699dafa943c895ceb008b495d74b425a4117792d92d91dcdc07fb99ee822050449a6dcc0b128623302037b864acd810ccd81f33496de45e08b679f11fa681f0a65fe78a4641286df7e32c2c0ad2db6145643b4f37b99c52a1241312479565d019adda3682cbc1d6d2e31fc29a2a48015afc03f3cda540a4bcf9c7b32d5d02fc1603917f9d8588d48901590175bc71b7fd7d64a82a432fc9d1fef10b32f5760d280dfca781f851a2c34ceb4bfc3ecc6732513a4d32085c5e0931ba7cb0fa4f6ff63943ae16177450dd7f57dbe3f128a6de6afda6ad5c5b20a7e9dd03da1719ac0c63f8a6b1b877e48d6c07caa24d4983f03c700c0436bd940952db262bc298ec412a893f3e8760740315b4f312ac7f94c07402598f53df3a3d039842d1ab9de3b639ba5084351c6ce2067563ade4d41f1f09af3f181ff68936ded1b3edbfc1b7b4d9821b98a1db26dc650e0e3f36fa3d2e9f8943beea6d5f83cb2d84ec7974c543ff786431b256fbe6c08a6353c8b25641790dda35fbdee117559072bef43c1b6a6a351a9dfa9a8a0c411b9b643c274e9591a096669aa52e1236a459959f20eaa2fd97fe48fc2752c63199de7c9d28a6620be841b02dad5428c531e0e845c11b654b1aa94673f005344848f5f7e686a529251d4d6537244329dee0ec6309a76524570b8221c8ae1a80f7c610bb0fc94bcf79028116d3a57495ff30f1becc7136d6fd950be298b967d33f81d96dbe01a9c3588b678a0a0ca88c209eb6eb5845f3744116918e214155361ea7f6ca819dcbbd0020126ce39e5560dac6324e5bf4e4570e5f6752c947653f7a9ec006f7911a622f3961cf8a6cefb14cdbf028df765ce0d7347cc480322e5d45d3da315c370c39763ca9f4c391a516a8a71eb7aa52c0e1a4b1e53103642f246cbac49ca9233bd8eb5fd29d3c705e7e350d06a6f6bfc384eac8c9151d218c7c1a605a362df13787c3d2e5a7d551d645fb996985c0d8be4b20c7988aff42bc8e86b4eaebe8502ac024d275623fde3c2fdb2035e1dcca726f43ace01e07d8b0585c7a2e814ca4b62c43a5d94b4b62e56aa20db213af71be453909e9e0417ef7040d513cf0ff0778174050a3d43c843b15b23f10f5aa9cd067c0117d086be399d979dea6bd6fa2cbf74505985c2eebb8510d64169451220f74cba86a902f521c2278beb36239bf011b63f416bae8250ad2fe67ba4f3cb2e06e1484640bf54082cb178c26164ff61d2b1f271337da9e64157e66c1925d3c0b37155487616c392b657d5a49975c73560b5700bbab9a6238da85bbdbcf27cbd41113fc8716b99651d5bb00bf604c3bffe59788af6067476c2a9463cedd0acd361801388e2337f2b87a008c9a4de842181059c47d3d0e478ef4a96ac95a38e844571730c5c8bedeab5df56144006162461f9cf7227c9883183026e456d5de395cc8a1e4a48b18e07d5b6dc42ba6b1c61b34785d7ecfd36f70bc6eac6bc1f1de38894a9d05499419ae84b56a0b449f4c9d75f924b7a5a787279192dfb104de7225cecce680f9501d05bef0469f63efba3a8758acadc4ee1a4dc0dafbd3e047dfa5579ac7bed299aa3383f141aa2bb980e78414c2b23271cfec2df27396efd72d4d33e8e857e69c9411fa6748887730a26a4c3f49b0371c8f4ae4c4fd9d3a70e01d43e1f15279ecae5665a9918a84aab2b72bcf778025bbeef412b6f08ddd0a8673df805d016f57e4e7a84745c8d8961b644fad3626b1e7b720231b984ef47689e8e9b00da5809bf266afe4842271a35613b131dc44cdb8d906b545dadb5feda5212de77b3b34ce0e051e2dcb0dedde45cf5039fb58baf4ce8a56cd22d25227335baa83b4a3ab65b131b3f1f2b73f9df4549a1d8e56095a419b9094072627e7fa9b644b3a191f0007e91aef3b10110fac2f068411446dbdb0a6888cd098fdb260af6f1a29066dc3e5e4109e687c110c200a771210b4dc798fd1033bc729163b7ba2c8b673b7a6579e0064f4fa33099067437ca986b21b8834b605e17be7eee86b757c7c81b88ed7704ff10741303f6be21f75e3f8677a7d79165334ba6b28161927a41cdcf3a0c43141faee87042d020f43537fc77b95c820783d713c4749d8f824c8a4a690ad72a8e5ebef26306268929ab10687e4d23e917e42c9121012acbcd1f3ca72ce4d7702a379b3b7e758e1a7fb75050a2448a655e5c6b795867cf39850a50625c82646f6be936552278c99ba2e0dbcf08301fc6e75998c505a3cfa50c40d70c833c3b3bf78450735df32e32b105a0ec7f1022bbb8439ed2a1b235b70b77dc9b48105cfe91d374cf29e91305e02022a237383429bc150bcdb0b711f1741f866bf333091c1d2e58bfdbf79a5a475656fc569c96c4372a1399caa92ae449996475622ae63146c8463155be12ca2c753b46b18d6cc965c45dbc7136de516d50e35126491aeacd3825764b80671af65f55ac311792d3a214746ba565263bbdcea01816bd9c3ca1a259793c472e5c475e16e0b374b1fdf914760ad6611599b6377730be4de5ee91615d7e49c582ff8642d37161b870a16a8631d5981a5b61431d5f2384e057e6e1fb42a3e1d9e6d80d68dc8f21df8a9eaa42bab3aaf5d6030f4b8bfe5c59e3dc11abbaf50c0bd8d7325d093659bca093a13a35a722c9f1504c3aeebde0bf30f67a53728bbc57ff8cabacba9e3b81b87bbc0e7ced16ba295fa0b74b48601a9498f8759f14276475dbde7ca5b5945c91cbe48e916b512dfbedf2acc10ab7a707d1468a81beb1693a55108c7d93268feb1592d54962e0d598a9c49501a3d55242db5d8159c03254d68bf2b867609f282b60118b8997af654a96465a2d8d156dead00cf30e29e61a38ac47d19688bcd1d08ca06a765fb7ff1e1ef78eed2e21fc875421e6f35ae12e2d8186d207f8127902843911585540a250a90055623674efe9d37e32c908f09bb080be9208c0c7cd45fad2b742c9c396cdf47bcd9b05a603a17ef7cc34505db0bfa98a668fedad0ce02dd3bb181c73ca55daa4e8378de63fff566817b955c21614c920c3603877fc8a811f114d44a78f4ff741d7b8c42f5ccd3cb4dbeb9069d88cd140ed2a15ac7aaa83b87db7a01671e508135667d16a902cb1420d9bea6a5673125c241fa85c8aea45017839b7b55098fa18796511cd89cbb95b30f6c74efae2af2d5420255edc88167bebbbddce0a2d58cb50d1433708c2e1421f0d9e22bf3161d0a97598b7510e696eb5257185fc0258f6b7b0a90d7fe2787c74ea4af5c83eba13839f258d8f2679311234037cb8c6fe8c42212289d16dcec738615228162838570570670fd17e41247bcab36b35aa17aed6a6e2faa93b7ea42f2f78dcacd553a13f31db43535aac7d51e822dec27f4958492289b5241338685d33e6237652574b4d551cfe991f0549df5246550a625067364fce202fc5661e6f67115e29534a256fce0471be6f59ec16519cd7eded3d4146599a42cb6f71c22351e07dec84d20906fb8127270b224e993dd1af6340252ffa42aba7525b6a19160d1670e73f5def6ecbe14c906bc050fd48b5e849937504ec8d4978ab381f2defe5f0b5bc6bdade2e8a6a9d24e8028b12d61f89d28b686113f451b462ab3b6d5cbc402a2d360271dfef3ded6a06a795714832bdd2b82f0ae6b415aca75a2cba510c5f50523175ebe821c3f08ee6d3478f46906776e0c84aefd4dc6740b0b3e3b56ccf022ffd7e4ba845276cda9fa3f7a8b7ef3ae0e1ec1c5b14dd1401e3b7ada8da48cea5910a81b4e5bf119359490c5afd118d4f4ae8fcf10aa5ea2661620dafdd804454417ccbce9847a4d7082bd64f226b585234ad45cc4d97b4eca1498e9c3f53293af903893697b1ee4a74f5740b212b201d15dace2988f4798a755db10c5d192e8b5f5fcbe6b2c29761417b89da9d53ec52319fa2acd39022100c827ee6faf61f27243cb1da675bfeabf26aea9bae26af1445649443775ee9d871b2ba3ff8bafb6dd67699d97234741452c060de6ccbc720d0ee0aac80c420e98b2368b5c394e9804c3e40489919d869e2beb68943d4c7df846f4e6b8a769bc9c6e9b564bc2cd7e58a9d10b4723422a926c2554248bd7a415c4437692cf06199df5a2468a6c4f8d0bf94f33939d303fbd27d6b6cbf1fe25fbc79496b5ef13a94bef4149bd00e4fc3c9a7c54013da7c991bd283cd10c7ce917895824aa9d1a1c83fa93b97affb4ed08045e53a53d8332080a74664fb93c68da0c2d921b1f5819f53fccc9116ed7ebcc0b55775fa4059dcf81ace4a84a37df65d83cbf591b0a48608f86897d2592c1b99436fd371850f313415405ca7906c145d44e877af5abfea2593ea368695f4fac20475947445d77cc067c550d7dc2e03b8d1f45bcc8e469e4644f2eb9872c2b6abbe102978642b9bb4ebfaed4aa61f6613590b11540bf15508dc11462ab6d52353cea0747e47c71cd3d00edacdff494134d4562d3e7f7a9d8f09f91143393bb59019590e79e988dae0269220b46a172b6f012388f13f6d755e77920d286327581016513fd5c6d16b0bdfc246f9f8b38f6e5096807e414e661b25b84afb245f84b1ce0b94db3bfbc5614e6d17be369b72e2f27229e6a48b7a405b73b2754f81c1ebb53c25131e9653855170aed075c5e54e1285d0f89e40da04c33c28af2dbfcdcaf44c37ccc5909d8cd1733432fd48b83488f47c6e64f9d33047f25c71a00181f7d551728eac7b1e921852013c078f444f30ab4a53144f3623dfd9ddd1d278306f621c73dbc2b326f837bf239dec6cc738cb1394ec186201e355fa01dbaa38521d955dcd7228221ec72ab56265bafc40f26a10d83c952138e7d808fdb5f745a09ec74db495e146a3fc84b0058d7b40061236e64b405988bf749e0dbc562f6e3e7194a1a5d8d94095f952c080ca01ea139c1be3becb4c71b7fd458cbac18c4cb99d1e04d6bdf8b0dcc4f9bf8fcdcef144cafe913b4e62b8d57d4586246757c6d31630b8ac0752468d8fbfed0831b892ee22bf06d058a14bf53949563fb1bca9eed958d1892203a221a28eeb615ab64e60841764931bf408434797cf29d330ce670aa92af2ef395fe026b8c3023445233b95563caa53982780276de19f839ae12ae55c3516e21ad6bfb53cbe6e433716fa4efc9a12954c521496f141dbf2062915fe2ca8552e5152274d808872deba6146060c38802cd01b4d0582c127ae2251b0ef085bb2010a03b76fd33e90d3674833076b550d0e3ca1622ec5c148bb1fa9528dda86d2d59f6dabfbe7084ac04b37c217daa20b9d8409db3135de76af678466b572aa3982f137d03a84fe706c33c2875285343c3dac1a27bb42b3cf3050e9871460f3599b99d7f4c2c7834b199fb5b9d5380f5cfd3a2591071abc3234f4c5999a2bdad1f2628f2d85f64dc3665b8ee67680c80c1ecd03f90ccb0735803a241f1acf567bb174f533d8a8503f3d23b3f9f02cd4425b21b3484387575c096bb06294e4af645b6034758e2925baad8029a844444e49d6fcad82f75062f99b25f6cb2a1f75bdd596bf43a0d95dae06c12701cd0ba2eff97319fa72f3dad5d1db8b8b70769529b207d3528c6662b778d0dac50b7d80832acb79869c911b25cd29c7294e49e559afafd65dcc7348b6e674b67229ea64416baf415b6f2f32858bf6ac02c967ec1579956623cc0ea6c7c82fd3e4a84fe13fc05d4ba87933fbe73bfe240f545136ec71415c95817f3d62fcffdbece1b3e91d64b8f064951eb7e4f6fb377fd1843f1003c48dd82cc9b4a9591c345edcd019d320a90af0e2f6da40acec7436da10275786a2baa786e3c6a5515ad83791729d0f87f80a7e964ba3912def2c715b41cd3706c64967c5183b62921b7531d0ce2118e89c11e8584a96aaec9827cd1638a2002033374a243b5131daedc7005b56e7065120b7c39223a162e01916b8328b79d89d824465f996db6dc30d7edc369b35ef16f3ec8c3fd69da69b77ce6eb0464849b6601224b0b336434a6980be5fa28453c3f677f6f1eff1bb7f9c0d889ddc62d9a199fe396300de1a274ee8386f6a1550c8616c1705d75351224a46f9096b962c7b9fc24e8268962602b7f4be81fc6e226edb74398da6f611ca9038bce2c5627a3d0a1a8812cc2033691673e4af98fd61f67ca73231409d230a034e2cceb5aee1114e5fe6eb23b12e369c62527090f7f2c0ecb57af7210784ecc4d054a379a8b3133c66d858e8315192f691c3ec8e8e89aaaadee4850ead64c0c2f54fc10214a44b0f99fe2ba34ab82f9a868f12e35b796fe4b2c54f132cb0529427376a865cc02f760027ba9c23e610981ec1881ea3e4588b6aa8fbcdc6e13799e159b0673b2e18fcf21dce5c5d6c78b2aaaa69aa5173c03779f242be80fee1346558c7a5dcdeb04677582c09e6aa609dcc17c8adfc30270f3798180669ed87b96b2f8d389afa5fea87fbade489b92867692e6df596a78bd421d469e695111ac958f6e6c3fed8ac13dbe3701e7c360d18e51be8b51dd7cf4bbbaf243f405be11b471c8fb453f607d58b08984f7be450ecc7c230a6be3603e29185db980fbfc425acc0276882166a2ff84249b8e751edae625f683bc683d412a9ed1a317db38b7621ae72e4dfa8928fec5c299a096da2ee8594d37f3da4692fba627cea66be7e676cc7ce18c6b15fab1d1f22b7d5eae0d2920e6d988af7493aa953d1e1950be7d1b9bffe0a58f39afe8a2544643aa3eb8a3424d046c7cd2cd722c68757442c48bc6df225cb4a73bdd5872e90d7c08f4bd3724cbfee2ab0ab81219046024daefe553e37b7e2647ac3d5fcc7eac60468ca5af90044c3c02ca249e4e9ce3b58e5121ca17eb6cd070364ee3ddbda0d54a1073436a5f7f8d200f900357058b29ea156b08915d321af9d8d07550d675a113bc63b9e10e7fa35708ed0ce6c480bfd4f9f88f2cae3e488c8d323973b3c5be9a587da8a35d339a6bf44ec6a0ef4d03a37afb0e53d393cc3605f99163916f80dd32e84293e0f74aa064161dbb48d04b5ea8c80b70407ea5e71d6cee557ad0cb18289c0cedcddda9e7e43c050a92fab0d8d93decb46206504534477dc9b48201d8f51ac1e02954321296ebbdf21835f4c42f263b43f1cdf5de625b8685820d5a2f3c24cf4a3837547770945ddf07bb154015aa36f13c03bac69d8ad52d8142813b3e85ad80c7a7aa3d144fd6a08d20a884c79828424d7b263b0d6055ee5b2737cfef2e16d56dba7848046292bbc474096b5a386bc7961c1907b5f407a4b9e2071c082b34fbee0b53861be9eaf0bf83e6adb4816367ae0f1ea3b22593f5113810eb2ee845ea182d28bb4ff8c047d55945f6a56ba3dd6a3044fa870f084277866eae7b82262d47d0f3ae1711353ba2cd82837437fe93e0b034c6a07fea06b9fda119ec5c9ad7cc46e3add145312ff514206a02b420f687b54f6ffabde11671a3ceaa2ad745f9540a1abc5d753a84b8a72deffb5fe765fb0c346e8398d29bb7125b09527a04bb08a18c834207f37db05ce08dd06eb8e4f6fc6fd31948233c9191c72deda98b35b3bf3de674bc772d1aa5a566fdcc014a12a22d6146063ebd374f44339c10f0dd9a0aa1658df0e2a3c635335c4104428e393206a6be706ad00fe4b41fa992409e3e072f6d4d7ac6e2b0367a325b537fa56d9672acab94d77ff103060c9c30a888e0b6aa4aedc704b1387f15575b9e15785f20cdc4498630b887bfc28c9627a91ea5b46a6a586b2b0ebef90652f5db340737d9e386e7951971b83e1dd4fcbcfde9bb5fbc86abc8ccc74653008c8e180af8833abb2c9f50a4c9da8fbeb08dc95916742d31a04457c91ec7327ea10ce75ab091bac4b8df5e1cebd01851be1ef17f55bfc3a22704b4d723016be25263b0672c37fb3963f4b36cd8d8c12174daf1e47c67abdd2f4a973d895485bed0a49b4588c9a58e79f2ea839bf130f121272a5be7bd8a35cd7284f97ea66e17b476bb6e397952dd1fed25a769bae7997ee0427a1385464d94febc34214dfbd4f7373b69c2fa5d2b4aeb5501c4a571a60bfd18b2cfe9e0a4097e973bc499be69b0f7ec5331e41ca777cd50721f59ce7e980c4c4630052fced4700e4ad6f14661ff981aea8ca9b3fdcb6e0e7b0a3d5aacc3a74d2d6942818f0c29c2574804c4e091a6d3cbda4668cd2b6fe9e164f3515695616093da48680200c65eaac5c09afe3cb7c67f333ba300537daeb623af1582ea1e961f47552c64b500aaa22c03738c9f19a8a1f3c2ea19e8261705859c3b52cb914bbdf36595465008f116b82c8ce2ac76c50e89a0ee96e67343fd791e8e1192bba4edf78ad0023d40454c48a2d1cfe60bc0b8e564a498a9d72a57367ea993c18a4550d89986e0fde5f3a32c5244d7ea1684f87d819c1879c1e3ea934bab5e756ea4773659f63f7892570f29709c2463357230126e87231886f557d7dc5dba7a025f80129eedf63f2c074ba95f1f109a631fe3c5f650f95a15811586434eb54a4e87e4632474b9c3d2c59c5cfea62fb480797bc8a90c6ece557f9ed31eb179a70509569d89ddd2113bc994b50d667b8d5b92ef84d6c579c98717fe50ee54cebab267e6643037eb8b446479b49328964a10bbda7ac7cde40f1132b50ccbe9ddaa2660afb584efd12961426f729f2386a602884a13750a5c6a3f3ee0416810c26d238f6d04c8f3fca9bf93d234ec71564a6e3d40cd85ab82374753d7765cda0fb3cf15c21670af20c9bdd3c5431c34ebaf92ec70c449429609ea1e2f1772373286f282392d358b9b3e5abb62e670bb87087d729dbc697d9ff54b3b560d46e24a1771ca3dba1d321bc5a137ce877b5e4821431115e51061ce9f54eda0ff5b78a126006223f7425c714de9469e2e2af0e3221473097af2f23cee45f6bf7a0079fca44a4903b3cb111e5d4d7e4e2771db397e827a0e529f118ab65d0d793157832404d86606eaca3abd0a236e8af72a723483442d9d86858fd519156900af331fc4fe5397bdaefe2171f256178593be23420a322374711ca71a365606e8cc0b45a23a1254f9ac7e3c5ed0264d18d1cdba3d28409e2611ab8b4b30ae345a3492c89a77e36cfd2ca5ec6121a8fc8d8f66f146a4d8f4594befc801dad5d709348a5cf55361f5a51cb2664755d51e3b20afa7667f7ccec7190c744b511a18134de313373171823c1e225309057f0415998ec01cd32b7cca47e925cbaa907091e6835735b01f103b62adae7346d5d472ade2ca4c5c4899a5e2aea04e756e23f2a55e3ff1e4b11e694d34496ec60c258bf9fc9c7384644cb808d13c0d92288c09a97240675d64ced2de9690aa2d27a789c7d4f0763073378a86b21aeec669214c5f1491752c6bc1bc62781c20d6c6ea8c3b8ad68d6e4901f170bb02a77dc7edb5f7c423f97ba7fc8987124ae6d331203e94028cac919a6a932a0868490e9a559f85c0549d36303a8e32febebaa7c01d935453fbef709a271b90527947e77d9cd6f25a9690d58bfd244c5b68a006a01e4745c5bfb4fb97ed77b9f50722a5b00690ce803bc01c0c764b10ac82875f39d2a8ec6a5af5fdbda5c52d3457d8b1144736b0b44c3921d3f695c037c5dc908384a66055a97a99c642b4ce79b3c92708a4bb5833b61331691e4599d5c87d8a5f16d957a5f6501ff9b98b3567950cf1208dbd0dda3d999f44293f7292e3b28aad13185544a4403b2d13e60a213b18bf3a5eb9babbf2cdd9167939db9388db27a33e04197a9c1be6d58942c3e74720e0b5080fafea913ea86c4faca7aca8ed8f71ad20f5559489402c336ba8eff8a2d60e4262180219bc869b6e0d274be2407a3808b8c49ec4667360b7cc293aa00542789fc43aee8a26de1728bb1827aacafea8e8009492794e8e16ac0fee541671850af71baf5f25f82165e8b2f04f2ce1616c0f4876f5b95709a86810336cd72438263d6f7f6f2cb13f7a252d10291bb6a2f7a1dd3f812972d81511646f57b4c08923b23a49ec7d7dfbb745635108233c3fc07f0b6a2cfa4b0803c344169efe9c76a0d0e9332fca5245dac4a6d7ad20ccd268a42dfee4a6ff44d025bb2451675fbf5afc413063b08503bf5517ddddb3516260b52769b5f84d8ead7c52d662b1dbf95bd15fbd937e232876728b54ad0b22f8e3b3196bc0d1bf2185aef786902d149b9e3f408d416e092303ede98b2f895e26118792fb059618d50c566b77012a11ddf43143f9be4e5315c7f3f0083957fa0202576f1c7a830c0d05569f3f92dc5e7c761f017877bc4bef1573af2964640e9183458eba5dfae03b4e2a4c2714ee4c3809ede09787dc40c02acc41e870e88171224d615d09770029face40895cb06d0f8fbacb58f61a467a1892f4ecf63694f2911e25ab32aaa318f3d00bc42d2071347099509d56a46e7f34944c88f49c4f12744c20ce78f4a55b79879e46c7ee07c84ad2982209cd3b748648214259709ff38e1cba4d12dd3c144fa76e74174daf06f74a04829499aa31d561595283a46a4bb92163d87cf35387d1c682de76fa0f09c665cdda212e0a4e76013fd4d83108a8ee7e48cf460c421f6068aa477951cabe06a45caab1db07218734467dd6908bf2acee961ab2e85ce1ac7e70df556c1c5f671b874331ee23c4184f94853709a0f9645536e3ae3bb73b49071cef19845c2e53b65ec98a0699cd5f43e56e2973c90879100168a4aedccdd1aa426c787826951d7e2495d7d11a914e2ddb1a2878507f3f580f74d67154a7f0e2db3dc0ba78762d74f035f6875f41d8365839522901614f03fb27a4a074fe73cef197b19697106adc1396286ea2b84b308ab647c5d3a5ebfdcd77171b08350c7950421f2fbbbbb81e6584837630a42ee7bcdadee3dd24361532692227d7798be6ad0f7dbdb57b984ce31c6a1a24447d8547bad7a82c27aadca0e6953aca683b2679e471e03b1985971790acd478da453339e656a01379158374ca1c965087e01359340742cdee310ef099a399698f2fc7c36a1b4e955905084e271fa4f74a05e4e4fae387ce52c447b3d70cd76b035a692ba99e1fc2befc88c5708721da67904607e87f049f6014220d9996516b94b0dd69b1e531d39e9c3c607b9a0fd69d6f933180fa798da3b0ab5f0f120f834377e1da614e179de5fc51d6e2d6780e076030cd5b76e872da643f6aabdf5d50fb89925d24bf9096ab7bee9d465265088a20ca3ceed310b7d6b907014832b74c47e9ed34f6c17a24206529ad015b057da21a27437a294e40e30f2df6abf950840e1b2c225ae6b1d8005e7d74ac26ed01d949b8fb1be500d032fc13d2be6211b98615f2459c3873c258b5412603dd9c53e6a925dd37e239f2289d97226dd7d49d31a7550167ce11a0b9a4f54994673f19add7766fedbe5d7df55ac861b3ee9054005beb98412d6fa73779e410d093b0ab5f69312195de079797b4d3d9487ff75d03ba68db0c0e72398fb89d165af22f657afd4cb987448a12a5e5d991da427b54c4867b207cbcac0720f6cb1f3e1a6076a814025041a8d694fd64f6909c28db66d1c108dc2d3d74b03ea827a1331606f7d0bd9ca01a3639d4d91b43fe7bc82f0085e3aee0edf79d5ac5b9847ddf60e1d8dc88022e5849df696bd3391deb29dd01c597825b8d117029b4051e91d93163adcab4d141dbc8fcc439864fae48116d5c24861e275ce7a888c8c281a573667dcefef4c87dd4225f6c2ec18c261e49a0bb747db1883e6d04d8e34f85ff561e9ab275dab445e59ed7df68ed37d28aebb248a208bc66711fbda57873ba1c1a58c5a0378a4d27a4485d397016b1aa7912fb4a993bbd55db6c347029d7238a697e96369c7f50afa993516df4896ca7f8bfda410fde8efad4d5f74d014055e14c2a73c5e1098210a03745005456df5c93736b0e35a4fae34fb1c52e42aca704c08b8ad504cf86539924aadcba9a45d903a398aa8180e68e0aaa0a0d4cb467813b623887c94b17d6c92a2c9795fc910a1aeb56beebe3f62a17d169741633a0249e85e5b3253be8a616636f3e1b0662d795802d11b83325b9e96d66f696e8e59f8cf020ac959b5fbc28cfded11d56ca441a3aa0b074c6433e77fbe1b204a21adeed122f71737701d29bdb5538052067a13c1fb24405ea197b8523ad9f56401c9e0e7c89e38a8fb0434e4c1cf1979ed8f8edaced33373731f54ab19412d30a04ac3cc11e0712948194de196ad5792b138c3c3438b9ba8e52a1d6247c1bcc2897be21a9689c038c6919f890f8b259c7b40618a00672f556a0b58094f451020273a0158701a9a8c0af5254c403a5be5e20c482d4b30f843a6407ca486c3a56489a962335666d1ec059e0ad9e4b9bda454fb425689fd26e639799a546323a7780179f0658b48d6bc9abdc40a3e316251dfbea60e8ebc05662bb379dce7d6f64faf5b8e0231da9b552a4d22f9192a22b5802dc48465cf178468dbf70c454bdd3b0eaa219db8f5dfe4ea696ba92553dbe69d12e0bb7d275b6ddb57cba1c492b51a32dde88996cbda43973bd3772ecfbc964cde5ec1ddc4f4600b7e76649976f43cbbf6d3f0b584517827e21f472a40a6fa65f11a9172f44f98d9852dfc0c6914204842d5fd4b40db5920ffe22191f22f1ccbf2508272fb7a2206b3e21427a0f8c872abaf074352de7cac008944b400bb4cb1b114bd83c4db10c7bb74c7471fb5c098d4134ca77006dc086f82769c31193a7e365a3770501d283412c8360cd125b3dbf6f92acbde8ef0063d9854fe55b4f9e1dfaf2adf9608c09f005c11ec805a8005f5517340e725b19a210284c18bb3bac614c7a43d85b938be80f96690de91ba1e835b1a073cd353db0404309941fb3f8082e059161294da253742d509066fb6b433d3f5fb0ef81479a655e840a2be09ec8f8e63e2a37e146ee36e19de82ca0d2de443d82e758f0781409b17147092d24a2d117dee43ddd096a4d6b6992c3f0b781f009a4e24e901182a47152ecd965a08108bf1504208c9e007381455bc920cb6f4ff87bde66bce32c63e9b932377ea0b3d885d91247e28456e28c56adc5e99b92d8467d6d35984705627a64c74703d4410dba93158202e2931961549990204102c25764b274cc9984c90b94000f9299859a142879a040da862f5c5f29d73959e2f6d0114e991790f09bc3cfc58cae0dab6353225f9dc48351921c644a0af186c093c88169662d094d49e18cca07a4e762e8adbf0acae24147fce9623c3a1338872b45e5fb37faa4e8adcb2c11fb8a0c3e093488925229d5b891a0314e270520a373345d98b91e393baa69eb9791101169376decb85c62211b888be6dae879a55f68f8ff03938a6fc18bdb4103a6ffefb1150bcbfe5d9d260d45323a2c68ec19b9d24c8f6d42b9b5676c343e9b67edf360c33d6a4ce6e0d90838eb3d981f62b7c76b9231015fff63da03188f02d7a7d17dde978f3ecb2ffdeaf5fb7111fc7266490ce1a9bf73b0df1dc4c5dd9617bd47cc5e8e440c80df6b3d0c4eb65524bb57b0dfd5b7a611c871db0b129a8023e1e9cb0409d490fa3207190755d7c14877d91cbf15fc2e569754e713479c4ac7f847400483d46475b6f2ac42961dd3a8c68ca1d0c1a976a7e7bbcb3c1d23e7a26e43960664b65bdcc6b22020dce617c9ebd96423f06543336c4632c705a4f3219042f221f435a58c12d10c50d2cf20d1adad2e060ac6e7661ae88d0267aee0665234c7ed20338b0232127cd698f2ac0faecfa982ea58ccd36e6276c0281649c9ea3b6034532ff849f474c0e0e163e484cf1169df9793fb15e4423ceca744972087cc654b2da675eb3e7cbe65305229130f5e9a80e3071a1ef329e0f28746fc22e1fe17853f84c68199cb83b80b0c8621249ce43bb67d886e371d307f5c0a6efa6f71319e249b1fc14024818aadb2c93725d97a5dd2def6cbd97db0f156c1b0f2293122603ff01612ccf5363803648f32f582bdc7d0649d7055c4ca512912acf59b682a50b495bd8c92b70aa18689753383dd076ed0689dec9c93fd3e5ed98036596682baa3245057c5930a0a22431895d68ca267b2216b0611601b66db8c442aff101ae8f94116ee5a2bfed1536677a236572dce950b3a34369f1ab7e0edb42eacd2bb9ca2fd7bd76943a4d533631735238103d9c908b99e07d02e6c12d7eda48529a60e90bed0d4468ee43e776d12e605beef7da9521d95f59d6a2b1a17567f13d952dbd589fa7e154d99e52e8ebc97d32f86b42b73abba2de9991e69f413fc79b2d924e69fa422373ef11652826f877bc1063300a202fe298d8d2b0f6f6d8502bb76ef3e4ca360384949c32cf4daabb78eed386a6012845a7cc61cbe437f327332d410d761746721a4e19b99b76d46bdf424696867dbe5b41605a9e731ce361169368cd120a67066d2030446aced4df4c8bf124864b276632a74be6c41a5d911239562804107db447a839a6cc6edf372ba806db99d76c48138546c10ced86cad58d84fc6e2a6fe44aef0dfbc76043b3b42f09177fc2be3493298a833b2ee1ee393b06374652648e819f048a7e6317e287109b39cd300b16fdb58b0e2c3184757c1957a4eb2cc343b9921baa97977969b76e5963dd6a857ba760496ab8988d2cde467ad9c8861e67ac0a7d54818bce3afa8415401a83b028a415260cc9c0ab04da6f4c65134a1df725ed0781824b8b743279145cf31134a130d52fbb9bdfadf92da1530d41dc1796a82d3cb56a78ad18ed0dbffce80b13bcfa409ae644ea8a5b3ca36b8dd547b594385e3e43549ba91ee97df26add4837329f50be470a6a8f77d005399ea7419353dddd42f3b822cbb24ee4161702b8b61823914d3f9504c4d8297e16351a4cb806246ad2cf2f802e16248ad300f12852848021b63ca4a9f23d03a343fe54fc3b5564836cf5aa9d92dd2dd90f4c7c776b6cd147f3994346d28a9140e29fe1c41481e6176d71325804ef4c2bb82ef1b3356a4d1b4a5e4824e71f53adc726447721f7313dd921c47faff7219ba86b315ac23f0e905ac6799ad3a2b6fce2182a6cc7a0efb20ddd502963c7966c11ec3490a3f173a3362d6aa21b5f92c422b6c4c44eb61d4b7a0fc3ee721203829a2ea13bf82d9e148d273088f30257335138f261431a320910b031226331e08ddb9ab5d11134c3c02830b0c3c3a182ffec75380cbe6c853c79368b4e6b8b513495468c9aa1849e317bc85260633b00f07da3bf02fdbd21a4c0c88817c5d889e99f5bd0e8f571e3fac22e7a0a5a6b3dc8d5861d8047b8b02953d0c8d980a89c8409861d7e5dc7b7ea0e50345d07df521401bdbc128786466ea44664aa570c089ca2595b27eb0a3b9665c1635161a220b06a599f30555f04263534a2f980d86a430902aa1db4ad19e5d2925b208202f8967f188b62daa204c6ec656a69bed229262f7131aeb879ad6b2aa6cc2ae0558bc262da4660f9afb7cb0ec71cd9665669e0284cb3bc340d40dcae24e3c832fa40de7c5cc7b22492056e757c092cebc7df2e21d9b89cc8223b398128711dc9b4600ed28e21e6ac04f07d196cdb03543be25964292cd5982ef2a31e25ccb0e5a3f8beb23264251ee9696017bf62a17e7fd302f26a908cf830455f1a12db77df8fbe451bbe339c1f6917c53552e554fa60ef794eafd97616480b9c8824c22e573b0370c71e2a1d9f6fa6c900c49a10c70d78ebc6620338c75f6390ea42e33b541b4e41565ad342b0a84a4283008f1ce17e51b29909208f46e0dfacac20c5dd29b9e014861df3754b9073d8e10751b98c149c2e1c492b0862e07f9c213bba449b2074408cf38e3df0c2800bf31265c058f4788ebbfe178567d52d19590a4b428687d379395292d18f1c0689217895d902f54292c627d99d24edb823eb8bf392ff8fd856dadc93f27a9febd1e8965fb37c7484167093a2467b2fb237d8cb65b684d3200dfad92930568fcbb9c0442838ee10a2484fe643fa506ec5a0df25fb108dc232a63ce977c7d1d1cac1cd0d390bdd283bd4ec1445830b659cc4e218fd82523a8b4481961b1c937eb21ddf571ba01a140ee6309fd44510abda59243273a33e1403a45d9a168f218fd1e2ff45e4aa8708a8dfbdadfd610172fe5696d9982dee9ca065e21a3b7fcae61329ab32ca6368c1edb0a5c2d447a4a640955c51878c00fd851ab5ad0818a460cb38f3506c7bf8e945f48d52869f22d3462313936b7bf95ac3bef97d13472b50133e606cd0487f9114468dcbde21ae899f7719263472d2718453695c7048bd3fdf302f1a9a839ef41a1fe60c68a35418064f5045bd5588a0f1633ed9310b95cc07a443fe081de2c10236897658bb21a8cd090a28068e20091bc11c546be9ba32e0c5b2105320a703092895e34fc9b1d0ba08d224c80178e9c0a0670f158c4bc83077d00fa04ac2c4180b9cc5d1e3ff21d4b3d0ade2f3e5410e3a040a5aa9acc09cc180597547c5c4f8f6ddb97a5c92483d04cf47ca22bb59b8f8a6eed5982f6283cc94bad8e80d9fcc6f49fe28bbde30565783a4c36b00dc6b0f177181045434cb12b08dfdd96968fa1f88529225397252c9671e5a2a8a4151bce89e6d5dd85b46144ea10f6fd3004a408263a5896322202daf52ff0c318883083d05246087afa1c07c6d8462a4bc8d9990c6efe7bded7a4b8d7eaa0a10236444b199bcb0fd2543493583452f8215af683a92332b01bc80c212c90208621a6dab063fb8d9a840f8a6af820378db1be5be012773114920c870c6fc30709a251f83644ce3911193e83e48dd567837e163baadcb54dd29179e7fbbc4535f5890d96c1850cc24fad18ef42af5743f7c4574c9018595f4c7da64468d8f37b3dad46e7faa931bd042911f4fcebd9c5a07289473619f655b74c6e41a496c8e25c432ecac8959543b1674a3aef60e46c7f4f48534a945533a4be8853ef1e109c4577ac636e6dc2d04f29da465da6c532e107d8d95a5821130c7d5808c6c356e41ad29714ca88148cbe74faf4e34984fe231caeb5a601f22f2307cc989285f77838f3954afff3955b3eec6b1ff07ac8b516c82b44b3f423ebbee5732c4b2da76feb1a19f84cfc4089a37e5a4b9c3df76ddf12608b33c54833e65144565a9ede0d2596dcdff3ce68055b8afeb556c80de06afd812effde790e1164d5f7b21eff16ee4ffd3fe04c5ab796e258414594f1ce8d35c559fb14229c29687b9bdadab68c34320719c84432134692bc088f03b9724a4d0837ffb40a736d85c493f0d08a6d1d096f9ca869608afc8e0e14e5d1b527b9d248c4d5e3f688c4c27168823bb1aae975a4bb82371ad9c52ee5794666bdf5042ec07c010aaef6f1d23d28fea816aeca4ed442f40751da4461b4622ee8c01b57c10c498aedb123e948911cee5cfb2a279871abf1c7a32aec94ed1180acfcaab845f710fd20da31ecb2f344848156934a329b97643f89f0dc249bb7bf01142d2ef0e5e3b656d8ef37436793e1c0bc867db18d5c280953a064423544666e8430ebb8ec305d038bd4f70f8175503d3c40c2d0eca064572dcfd0c69813d4eb247ee98e9310b740f2a096e110bd0eb7c97988c542cb2267200647b8357f7b995429bf568a2754633ed89e89b0be79693ba0cf4592a859a591be8d772449b90d9cf8dc1874945bb6ec64d7bfc74ea3930b7bae21221d79b92dc76f93f20170c146fa1af4e80e196f3270cd3fa362b014f87153c1fa55e551bd89fc106bdd24d9ab5052edb0444133d80e665746237cf7ce7d74262a6e1482234848db995403b800a5722fecb71dc853b44a20458d014d024fb08b9eedce97b9425355085b077caabc906e4a90228cdb83dd40753b896e11e1362a172543bcc6b313ab43ec345194edf0b6f78f23728f4b0bf676d81e4e0128f7a26639381f711dcd766cf4638405de656d71ae8c53d062d1b742e3a892671dc84559436d0dc4846b68fb05b6e4480b6b0f8b77a3a610dc277895c07af168b9cc20a10eb49099b2040d5b63bb39f163e0574b945382331eddb36428bd22ee9f481c2c6ada3b436969838c58dcde1a0547fcbf89b9fab554dba5f8e3c4137b5d360cba9b4027afb068021441fc8951c85e6d21502a29fc3eb51e84ac0305c3fa025f9c43fa240a6d9752640c63e6f6e8333772438d3e360ddb3cab69304b6d495b2c09b3035639b5aa96988b74de3c59916c29bb26ddc09f53490ae0f6dc13012e671729672ba151bcd41c3e044b196b20c06e1f5c569302ea6e371165515f9f940ff6cd3bd2e69aa18a2abf1fb2c29e82c6bfdcc2b41ee471e6db3f901e6af0ef3fe1fe4fb38ec29a9f096b572e9b27b7a623240fd7b63cd91a4f12ca73c17b1a21248a7a7815b12d24838995111c309470908541a1f6b016c8c093f6da88ed76bbe597d341b8f6df19388d815668907d17a4b6793df243c3e4b257a3deb511d563f973467da01ac3fd766423d22857611c779537a559a6d46ae9384199d6d9ec0c5a79ea8e3a8268012c9fc77e1172a925d9dde545356813e1e8910784e0fecb6960c304a3cc77203246dbdf3b9959c791d2851624bd55ac646b447b7fbc4ffcc20c0dbaccef335febc470f7c4c504cd72296b3159b385b516d4a4a9f364744534185cd6687c0fb343a6297eb9cce3021c1388e8f1907c8e527a7d512a21d19872fa8882daa24fb0de2a3ab2637f7ccdddc8208e87372119a3e20f734ac04e93df55484ac9e8fba0537a567fab64a9ab107bcc73b816f60e6d1050ebc9a184c40751f14a8f81e664ff9c65795bcd73ab4fd8d2dbf2f3d7589e8e7585b4b6263a3310dcbac6641afbfc6fd17fb111d0ba5d4e29d331d1351b3c35850047333a5a962c3bc7024b034eee3e3f8a0141f0c2dee8cfb29d9dad11f11c9295dae4f70f759291a0c7fe31794aec28467361c5d6298652181cf1fc6dbe24434bbf14b80be519a3e916d48c81fb6b6ea07daf73fc27c61c41a78a377d96bbe0f2edcf36c59ce645b773d0ae41dbc5dd8d3bc0a3454ccd52619ecc5e115d9e911efba3db3ed29d818c29cbd3ebe9e5fa71a5fa7e31057684ed8826defe0972cae0dbaa15154600b843dc7ae952baa47d5369a954497ce5d1508ac78d886989d755b80e508333a338cc9ac14a3e0947376b7bcc70b3cf3aa90a5690e47c1f3b8da5ce66f6b6165a46b39c56a1e42ae166fcef77cd5811f5dfa8ad9ef1439be3cb720e0014f907a563f1450dfddcae8220d50e5d029da66ecb806952901279c7c231a1a710fc1eee39bed407d174760f740b5bd933333a2a26d4bcdac51674c8db4d104d8ce6a95bd587d38611f2ed484a96110a64846f42efb166c70560dca6cc520fbc2fa344c78daf025267ad8cdf9bc4eaa65459456fd9aab190c54d92a0c8532fa59875cb8cd037e0a70b2bd6b98271a09cc26a032c876f51e514af3f56812c7c11deafa8d96eeb0b5d58828baf62d978fe738ff93b2db764bae38d8f40f29b68a73c5460f8d000c830130626914f02982a7624e68252ef4a1a5e55f301eba0ed40dacb998e301baff9aa6215444ae0e079390bf60eed444dcfccfccac66c881b2feec9869a4c09045a1ee8bf168f0b158cc50f071f7a9df037577924185037bac5298424a039f0696c367e2524c3a4535b7caa48e6f68974877e1b62a77888f96317675f51b76c48c4a2c9c94940b21ea0c662c5697cdbe90d5ad726f77a030cf418aa89c251142934ad7ddef49afa3f21f1e583aa5e4311c4df89e38175731fc94ef01e87c30fd700a8e464e77f2e30683d7b0e3fdf7c128fd16375584e5b2963e8abc61bfe8105e8b94ef53aafce1678a503188c8fe416de3aa7d2880d4300dd5569601f4f8050bfc232eb5e126bd31c24964d1ed932b53115e9933720a58f842e95463a22403d4eba2fc6cd30a829808420e37b19b91c706c963cfbc5d3c53430a83673ff3bb69a6a6412fdc68b2b340bd77db581f928b26b6c403764447ac49f88bb6bcf008d6eb369a06f140eb162057383a76b6b48c585bf85bd0a5eea57869bc2cd4a587fc925162539e8f03bbfea0c389c4dd550acb7fda39a766d7a4224110c8f48bc602e187f6e6db39be1cec889e7370c1eb166f0e04a07137cee0a8f0401aa3b0a39f5fab7ede3e847c8e61348b79ea01f4e07bca9a7cf4b97694fb05fcdc44ce3e944bf4fc7371d4881cf69ae3bec6006963dcda2fdb1803c9609c9ab44baca4917ad0ec301817220731da38a8512d94f28962b7a6a1f3195011ea9020cdeccb05c075c95b79516b07ac4323e9057fbb94f562d3d3e8a2c575fbf4189f5c88b4bab47ecc9445345271edaab7cf31093bbd9ca9f2fde84f5f47e300d6b5a10dce57eb86eb17a3547b2b915a566ec55d4658ab9dd7745d46c3415a654ae2e1a161045c6b81e57563d4cdda13a31d067a57d020da7c908c7524aeed363a07b9ebcabda3cc50ce0db18b7056d40a6fd5b14df0fbd465ae3b1ba98de50f81ab9312336589fb31d1f789593c293466a71ae28c848f3d1a451ea5fb83cf06a2c2b6a7bc7a38b7f582070c1c3c7b32cc5e98f17a0cf17f65c3645e9005627020de06e97b9a742781cc49b6dec14338e82598b5b0d79f8a407d39cc7ab6ba8d928aa673bd8f3c7a2f9dafaa87b1c34907ef12832ed1d298f2b62f87ca0969efd3c8043a20bbd64b295a60783d6260439148e6685a4b9e7c45fdbecb5117a2ae9b409e5782cc6f2239b0377b485647c3121b1a267a5b207d6885722812489c9ef7fc4c69803f9254355b183b1af50c57b9559ce9f9741552a059da689e2be743e3861485cfabd014ca5221eb995e7836b0737d87c4f43dfc44063dd6d5cb4e16c6f1d101bc0fe1c333b665110c24d340e548c483778ad265a64eb5adc0937691c1030ef3836371c956f027016d51e157c676da1c8d9f18dc75471862bfe69ef743924ea372307d27f40fa55df236dcb61b0f4bafd7471d5a267e2bde4995a8d83e3c1100fb903f9d492b33f0f7a2c686dc91ddc8f5b6bdc4fc0e353a351a6cda0374ea031c8ee0df7e97f6ec9436879b8eff9b5af8fea550655b5cfe119e470f1fdec99ee87dc4af5c42d0bf6cb03e299dbb560dcf5e8ce7664daec52151f0c29cf000aee88b8527739eee6a29be5369d233e89531f9feb1b0e14e13ca970d9907f4d01aa175ed050cc35083434d619f6f7d25807228eac8f4db66a55ae350876bc48b8a2cdd8cad0d46c5163123b323132e82c7ddddf6cf56571e242d2494ac4b46c86bc2338891ba3c4808bf4265b6410d2c44e2bbcecb9805dde36fbda45c2624ae2b116fd2d2fea63d6d8d60d6fe229b466268e273a863983f6c8538cf190894f1dc40be9e18ace1bed7bebecc09d8ee50f8409a6140f03ac08a9363052d966bb45e6cab8101cd713a37c0b154887a6262cfd567f3034798b12564cfb3fe936b13792985eb651264a1f18f7315da70a3f41981d32ac6bfee215ca1b18470c57140661133e0186a578848b7ab8309f1d89d9ac2354301a19c510237d0854d315538327569a7265640ab72ba7492ec5e3ffc897a9492cb3fbaebf635324e6cffbd8d8dec93d2e1a621e4bd99aa10e699e9951ce5c090a1c47a8560a3aad7a44e7257e950c37fc2220ec52201e97952771d13d07edf157844a91cb56a8324e12a0321adfc128cea1755274284ebac1da9c98ebcc77ec73b5c791d2f342385343298cd8c4bca037698ade5540b363af18ab833271dd54e8033cc5441386ebc6f6a5fb54b96e4dfbad25a1502d0998c11dfa88ab0ca9657013c1f32c55718fc5ff8362d69714f7a532d3d7b0b44a135725e858295c055a1a7afecdc38301ffe9ab50e9025c924391182d3000b3c4386f5b75754b03b36eab17d22fc64cf014327fdf05d9ea1ffaa8b41c24ae83e935948f0d12b8d57ac0cbcf13a1cfe0629039196e9e641f8158903f7b5a2eb6b587d2f4b874db09c22a949b7dda88a78e667b3952161d2143e286efe69e2fa5ef450c58a92da116bca7b8d94e005605f1450506480ac3f7f4dbfa57dd2f5d9c3684fba78921d82765424fe1f339c7addf9c1f5240f5570b421e4b978b2f7bc8ae452747438028d9877880e210897fbbef3820b47f6fe25521f00fbead6b27c1475fd17f4015033f3bcfd22629a9620fbbc78678d498309c4201a803bfe9c4f323a82cb40e7007fca20282a0f1557a5414bc365b8ad8128c1a14feca5d89b0898be49cff6a102c86edac060705284239def46f98edaf391ae5211c298f29bde360fa36fc2e03f9d698c325169f9ed8f86270d6dcc44d7c1fe60cc9f19a99e8d31f2d0ef81af56ce2c50ee11ede5a89f1c78786c07522a6d87aa8f3707a60fc5fdda171f7e89a20979443ac449cde866ad518aa55c4ff1c619ade3a22a6939b08dd51dd228f45e6be56881720390b58b82405d384ff4fe66366cd1a1df8703e0b6b7168e1a32400c431a71d68fe47fbba968cd06e4e383b41b7207895e1d14c900af23452a880905f7ffa518a7e9f383b115ce6f756aaf1c5da93aa517807c5f93b0cb3601a21e10cc23873bcb61fcbe5cc415329ed8f7f25c59c536da075437492a83614d3b7a1acae97671401a11342c685825a7e0fcf521a931fe91d55bbe0af1ddfc8e588be93eb8771b343472a99f665832b1dea154509d95554e2fe6ee443d5e2589583a31f1c92c398295b9cbb7a94c3fb012832faa7b08eaa40a900d1524c243e323af07c82028279cea96814338943c6691c3b09e86aa0f1fb587be24d2a085c2c94fd8839f82b85d0ce4dffe1569e0f7acf18e7267478fe5c59f489a1af364fb7a2d10a647e28a8e6fd1298240d4aed6aa548cd8855a3e9f03dd7ae5760d8ca4c2763ea680435c145acdda8202ebec79f39a27c350aa557b970cb7ceedbb1472340aee53f1f27ce2e5859a24901b09341f7b91034b9a1389c96021fc2a3e30beb3edbff5dc1087b0050344cf43976829389dbf2e4f6663cd5c16f234a6a7547a266f2932919e2d9f6a10e278f204284228388cae631c93ff295aa6c52450185e2d1259473b6444edac497c627d831ba8a9c29aee662950db1031567222102e8dd7e82a47cdc77a23c96d1581e76c17d59d66a3a7a1a067a60829cc35be55d10fa06d5ae10daf44a4d8f0722bc43ef2ffd31a0176bbc370471e6ace9b00512501b3b34c8fb9ba44b289351cc7f07b891054cc8f493d186e097b85b1a7e0538dff95bf68f3abede879925a01c5ddcc58e388fbceac5df40233dfb071ac787ab834da978f284d7275328bcc10a32e41132800b138930f0d4359f83f1c645d195590bfdcec87313182713b2ddc5242f5abd5845ec0ccc422037c901f8a3239f73f2ddcbb7e847480640030d998153434b1fb7c7cc2f788e99104fdbf0e3b1d49daf1640fdfd351cae53404619d30cdeb7587bc55bccd00fc5f661e295aa08b1ba09882368c1edd88fa4138a4bdfac195d734f20d2d388090e9938846823426140cbdc588f75b049ab4982c3b5734810475d874d3a4390bee0a571223b5cecbbf0b1ebe95a5544462d90f1a4c13e7799c7dca2eef2f69ea299a4f47c8f1a53b77dc07e385655d2f16102cd5f2a2411a1a30dd1c298974e48af674a2523b054a41d36069ee36807b0288dd7b231a14687859c73abba28bd50aff3e54cf921807802ecfd40f3cbeb45785eae46dc02e381d3397c26173a07cb4788ac141050d189f793a2d143368a3b9d7299a4a72111d2c4197e28a1473d2f386700d17ab26356c552396b2281dae6379717d26e2de117d169b0f07699d595aee9b303200429f10c15ffc9ddeab9dbbe15d037ee9d06767d10af8a640173ae58599d2a6bf9d53d00e68adaba897a1c886435e722412af1b92e736e75977e60369dd39c6023a2edfd7cc28f8a918a27ad2202d10d7579c2447877a89b7f4ac7d08433f916eaee26f8a30c289fc6162db19fbe7bb94dfe9eeaff6137a24c940175cb451308a855a2d4e1d1b0b0bc7861f8c890062f3695c40eb717bf586b96a8d1378c92a76f52346f006d2ea44e4b40a9651467386dc032b8162db9fe38d685b36b821f97c16df6ca1d98cdc6921727a3077d7c8bd2b619b4bd3e673f491ad15c4e2153aacb4651b9610d930e5dd52db377b9ecd76d232e964ca1118d8f2b763af10eb4ed265ada09b546421de8fd0ef1d8b5195730352608ea5e610b37cf980e984243b53f11222930d02271c770d794c40371464e0bb29aa7d9d6c504c26aec42f0ecc04bc6b270dd1a0c106c57f1c644bf11c94d5972d977002c7391194dacc90b0bda4ab855d396a5d2f23ece741f615ea4cd7e0cc3c04b10158e1f33d2c32cf8938ff4418ed43d0cb971ee95b89a0092dbc1c93417ab208986a1d013ed1c5fef46516f4d2be3db4afd5edb07230b0c82ac345e2cb5a1f55770180975cc278cf11f22396d28d933444476b8caa2a4f1bbce68d6486691380f3190f1e0ea7d7041da4eea571e76900afe537f3946dea6c2996386bbf830f6b6d37a0728824860802ad07f4d5993820df48275e714284b85225091f5132f290030aded2b6c8071ad206b07b10ed41c427c88b64d8341a4e7ad03a0ad107699ee4566b34e28c5a0795d63c44dd24107901b9986f3ffe3c011b84320f8eb44a0f4ba67a28ea2708ccd08e55009ed8934bade6918659074661db5fb386db59dc9553db8fee320f4f68065bb5c99bd200362ff5a66d4fc8500aa641d6067c74db62afd90203c7022df49dc166ff6a8802c4f56bc435d106599e4d0c704e31ad08c9030477c82f60413512634dccc382732ff636410ca12ac4f2fdb2c7347bae8bac7040cc1540021410b3b3fc93f066493def2eae11a130b3c73e1f67e2eaa41148ca209cfc2aa2030885f5d8987dc67520d03e9f04d98b6fa43f0b47d5d25b79c3d69f58936763010c9c4d51d5508aa4b8f1eedeea24dab773ccbba9c73a27c380e158a4bad7d17b012569f24edc30d518b9d88e3213008110ff3f8252c721f1e6b0365d9ebd4e46445f27c2c4fe334214f41aa2a6d4e541e05d5121b1fd8705810d7dff1947c47c34e6c0065b883a483a99a219209a7f6fa1c7f4ad6d1858482530604e381ec31e2fbcc9fe1dbb8c18359b09374ec0486df81650b6786faf94869d4cf4ff88cf79510ffad92988710ef03a996639be1b5174744ef8b4bb81058dd6aea57bcafb65a5b174b72a63462c55144217fdac4a4d9b955faa1243c9cf98144e18f25764a4fcdfc2b7d64e65fcb24ab9627b82c2b4c945927e1918067684b2c0cf12486dd33c44d58fb882deebf9c0e7406ddbb0b0cf3250b78a3e56ca999c6ac2991e7d004f6e8649fd44b8ddfdc40093e32264cefa4097bc01661cfb44527e107b5018a049087cd06bc26467880472766c2c042d8351284e7024e07e99d812655b4a2c2cbc4941eada0de8fd46e1a675b19b0d92b29a59e2316139437c4263926cc87efed007b324e95a5d5a6e71c21f0ce8c7a27201a1c52ec4582d663821055ded918d1323d5aaa121e56f578ddf48d80d9664841a0f9da85cfd093698831d98912f05649188b935aa63d61ebd97c84795df7815818aff1623eb29db200d80343a77aef981fd9ee220ddc0769e808b1971fe7b3e2888388d9b38754edb9c43b3834c67aed0e42176aa5301317ad83a8d04bf5a11ca474ad0e5a9e7d478951e8e2f250056553dbf882c58ec2687304a4e711826b2e9083bd5931118532d63b61f5a3ff07b1d2f10b0552ccaec5353b86ac7dccb804e2ddeb05cc2b046c5799a6ecd1a1efe04a3f5ce639c713d29b64878c6c4c664c34ffa3480b3614032ecca94cf282e60504f6a25421b05d79005665d560edf1a7163e94448698e7c01fb7c4cb4b6209fc6da9d549bf7ef5910b9845ec91a79a83b4d942d018a9772bb01fb7320ed0b6f054ef38649d46f9b154cf384816a0911bc04361fd93a23a7fcf9e2e8686ac8c90c2f945a202cb98513eaabfd4cfe0ca410c0e6d2bfbc09d08b94351938fd191f5669bcddfedc494c34d5a30ec9667364c87f6d2203472973083b6c2f608c5880ae2f4c699e1e03cabdb924ec07dab979a83779b33057dfed0754d59b9a578952a6a414b5da39bed482ddf51137a6bca5b2212d61fb2793ae2928e3784f6a29290e1764d3b4b4b9952c4ad497a4410da23f152efbc7a11a43912bcab67cbca638a3fc2910e8d662a97fda6aac3a65c00071a34a387cbe89b780a429c5c4cdac3eb184791a591e730aadc2793469270c1fbf1385df47f12f922fbbcc79302563befb6f22a11a53e703c2bd4b1c028b1159d7026d0e23175ca8eefef8c8d12f2d91c79591caffbcac8bdb52c9acfcd4c650cf30a20550b54d797b39b06ca90e570fd90b3339ebfd8f937613fbd656fc80edbf50fa0941e3bbe9b6261b88257e8be6e12f78ab2e6d3afbd27e1013227a957b81d4d205ce1dadbaab6d685cfd8635e40621c839d59742838540fe44e04aab3a7bc5820103b4a07821d3da163aaca739be6ea1caa39a4b1b37ca99c5e8fbe3cef083fdfb8ed637d61160c18e971a50a070b40697246251c4aabf0ae3401b6dc9ee5491946eb7250feb96ffba389c9c7ca4ff6de7b4bb9a59449a6520bbd0b1d0c2c1eadfa5aa5dc3029632c43f3510c6e313310e610bee03803d6a1deddc72ff481c6f7eac3f8e51bf3e959ef19e5a95a4fc53da2631e0f9f1a352db62db69c3972623135a35c2834623c67ec1a6c1c5b7643e4e4c17a6ab9af5dcfb4bfd91ae3d8b6e62627a58c9376742aa594524a29a36dd5a4ddb41cd6cd8c354dd3e2269d689f148ee7a88ef81441c25bafa69428977d8b526e685bc6fec81ff3a47432cf283742b9a18d9357885478d51e9df8a6e1240c702b9691458efc48b12a6c1abedb45036fa78c6c3580916848c9550c6e188b7e6e71c3f8e54ba4f9f9c522ee41f3b5e8869c54f4edd12729292929c9a7816c0dec9c38389ea3e2c14d8bb21623e24ad79e5f31a8f1cc0c46af3f4e39abcf3a278b2a353183d100921098908441a65685ab671598d60678d51d6594b141cbad132cc940ca17232d3d8001db2fb798086d71058a2633b8f861c12d5ac325c5144d423100db9d5bfcc61a4c48e183144654ca00dbade51fa96fe7e2a701eed15e8b6e7777533636ce692ab79d5fc5a3db7796befdda8eadada539337df4545062bf3f7afcbcc509d2474f059a5762bb2134bf799ab7e3ad605f7e3c5ac52c31674c76cc1148f62a324ae233f7a06eb9794894b2daccebc73de61136048f8f6c615f1c1a9ab1a800b763110f11e4970c425590b4aadd2798db52b21424880f1f3c787cf3120ad4865b255a691d229a94797ec560e643787e3571f23caebef48b1b89f088d01ccfa4c10d99cb08d3f5573651e9428a2b404af34998970e045482d42b497d74c9300c750a674e399b7eb2e395b29fd838875f00faedd9b9cb3fa4f77ac1f5fc7a41e8f905cfaf178a1ed53a537acb2f94eff54f28573ed73c6cb225d71d59c6ee8edcf6dbe4a4f61be566f61bc651fb5be53096cedca362bf659ca5bf695cb6719afc8de3b6feade3b84e883c715d2784bb137777d318369be3db9cb5d65a67cacb66390b6779f678f427f0e439e78c93552a166b6767ce1326fd98c98c14680be3bfa878e9f1157ba701ac0484ab0fbd67a73591b3fcace2a56f927bcc4f3e9319e56bebeeee38666666e6acbb39728fee6e66a6cccc3aedce977b7477333333339f3a8ee6d451eb610df9f340dce87a19638c316a5a8c59966532289cdfea30bd922e398e31c618352dc62ccbb29e160aea8edc4bd14977eceee8e46552ddd2282871768ea7e57ababb3bcb6ca5cccc6c264c648af3e542fafa31f7a0f18b4b3808f36114f3523af56298c814975a25bfb8615c7ae9282861e106fc2506db08377a11b8a155b1cb2fca6f4981901279268610172003e91b93707f4ee9cc927b2875d1d6d0f2ecf60c2c8cd5b1613b5c6e2be1469fad8aded1968f9f9562e543fbcd59ce34c97286ca87567af69ce5cc9447e180b90b1ba3679b675f9665599665dbb66d2d8802250543dd60d862c0b87cbb6dbf4dc8c8305f69a460866f7795ec72fa764b43f7d65b6fbdf5e6e1e086eb52255951dbe4b6f3dac7c92837daaf7e3dbf2f647db751831cdfced7a68b4d05cd6f84f46b5f0af1cc73aec4bee6fc9c1643902ec5b854f3b0c72ae65d96394f733d99a6716e374dd334cd7ae7a5e0c26bcefc9b671e0f0f2747b5f3a55a25dbe3b461e39c701e3d17a456cdb6d2aa49b1a6de581fb5ea4a6f19f08ffea60ddc634620f29b3f3184e952492a2d3135c916cf2e33ddda7b532977d5cb9fd6915e7dd66ffec895936e4da15e4d9fa1fd268f9c9fb0cbfc740e9234b8f3a74c13956957ff345007f510125292d21253530c8a414391c8c8ca11339980020bd13577767888f81c41c2c17025d1cf331339dd5ed962024e4fb9544916ff8847cf02d0c24872f486183194384aa20b143882c098dc5046d211409cb181992b6c8c3db23cb161882d3f4cb1a20527be58a38833323c31268733310ce0394b1c61c4917406534a87f8e01eac5661d643ead537ac39ea189d3c54f329e621a5f3e90934873ae68d6118466fb51e56bfd54355cc4fa86d2957ddbaad9f4dc2fdfcbc3eb171cea65d23ac5c3571234e0a4b9781ccebe7c28d1110ce809947f0870333ff996093ea3358307202c0d8159960f6cd10030d58bdc17681dafa3127a057b0718e74f9b520d97a294fc92d468ef2648474ee8c34284d90ad1b23b305eaa06a876767c32a101b94d3e756dd7a58f53abdc18d7ad89762d07a60bc7255bd2b577245bf192248a5840852fa3528bd063927624cbae7169beac6886c815d7ed219b4eda57c8dc9c60d551fd9630f6184c1ce7aeb3198e21f8ce21eb251dc434a1d1c02f334873518bb213433e812699d3065edbdfe2955c6c376d359e52ae7ec0932c8882e2f2d7022e6b7c84930e0208223e8e83746e17296388c7ee3142e678963e837cb4dd597aa1d07c66d97445c768e763df3fab48ac974acfbb8297dce5a6b8a8bdde41a6e55b424d02eee9170f9e572f2d914dbf3cbc5c4739633669e1d7b7eb98afe3ebf5c421f37c0b763cb492be3c04a6d9608bf6027023b7107881b46a298d43a61aa4beb84ac4844444484adb8476465b2078dae60ab53f8875c710f2edba1db8ce8a02616e4aae58344b678344d61125a92f2ade4e44faa03a456a9ee3a21349f12ba91cc0ee63b181266e22617d7bc138992b02f462397a949e7d00d77be91ec4849bf6b6dc5381b4a3dd938476563e48614550462a523484c6847a1b9fd8f742b47523b92f610b49bd08e42bb926e7152fb92d8a40848c627dfbea964ebb25849aa5447e4aa55ed3c581197bd49750cfb5a988e79fd5aa05e3deadd14a96ebd7e3745ea12ead57a374530b7be64daef56cca3def4527235d45de1d83619e646df62b79d734e4a7b6c37247aac56f118d2afe21ecc6d958b1ed763876418ad272967d713a98c4e65d3aebbbb5117abd5462f8b9eb66d713b854c9e47abd819d543bb21fd9cb72a6e76767f967b30378df234fbfb0b57f8f8d5279b0a9cebd9bb4830ad92ce9f546a558d72c3965d6497972ebbb44ebbb5f7a652eebff507464691ff26074a9367778447aee68e5ccd5662f9e5ce4827c99574955c49e9d2884aae8c6c996729fb0dfd84f4d3e912a85b1e2405492139d42d02c82259455a9158de70bd747924919a4eee223033d7a475f6ecb35d91ccedb7c4aa5ea67c587235b72f255713f370e7a944b79fdb0df36299a75ebb7e7a71e8a917897e7a6104aa1fd2538f937e7accecf66302eef0c895f49d949452ca1db99212e8a54b20192487a44b222997689a6c9c134ad757df3206b2827dcc19736bb9625fb5e152a51baf1e87f887f5ea510806a2c3eb075e9d3948188df8ea71087e40079e625d951cc37ae0294f35c1f12600f0947bf0dd14f1c07778608decb86967d003dff1dd14d9e101f0007c4b767c2c5737f2336207139041acfa8e6c793f20001e5e10be54cada7b430fc017e64af92373f68c8158e7d3096b1d8ee9e8939f4e7eca3e15945c7e7d759e1842f56d723b3cdb0ca8bec33f684a6870c7c730b8212755a5f7699d9357322187d9ac67c40ed7b1c33bf07e52e08eaf9564f5d4ad1ebb0a3478f3edc855f5d9c6bdf11d31a8babd3abef0a66ebc063ff0706a003bf032d07accf43a3c4925e93be8ffa418b7b31ef65bca8b810597cb05a61c005e0cf54c1c2e30e5385e0cf10c98eacf00c0208ec720e6ba619bf9dadad1bc95f2ea714ab73cf07af4d5db4cebf4a78292d46f7e4373696a686c6c9ca73b85aec7c679bac6b9ebc9e13c8d6135281d8e6118d635aec34bc1055463b15abe3697da5dbeba8dd7535a555b553d8757e3b5945655bf5ed3783dd4aaea27af8daa7f50bdf3e251c4d2aaea3b352ec955f532a9a6cbf4b5c6a0dfbce803f7a8ae554f797148aeaa7be04522b9aa1d7c1149ae6a0ce21ed5ad1779e010aacb195cccf9a94b174ddf2c648b199c20bbea7e03c1922a9099d8494bd25bc97e38bcdc5c662ee7916c692ebd9164cb7647ac11edb372457d4bd96e48447ae9dcf4527204c2ddcc11a4cb9636e4394d2d028065aa6001a50769d2a179cee2069697ee236333f551bce1a5f751eb74770f0e3f1d1f3cb6047dea96faa59ea2afa0269e52cff9f9a232c3cf29bbfc3c459e35b6249e986d8f8961adb558bc9a98a19fb8b7c3b8d937978b7de4d12b9c1c223c37e5a9e797135ea260e2c3d487b70dd6dbdf9800576b7afb4d4c9090e20d971560b63c41050ce188207488c28d2eacb802642acf9ef21cb8f1c5110d5b10017d019e40030d9ea801092e690c815cf44c9af8700516196640c5880db08418826ae0c50bc478e20910896747c26e023b0aec2cb047174720f618c41e879809102c2002074b34f8f2431590896cb9610b971a42481105e478c4b3c723d92ac2c518415c6962e6062740f688245bdc022422212b6228e20c2840f698d43a9105cf1e9b2299d62933956e7b18cd3c7b14d33a22d8f9133c86894c71e967e692474e8c7ae6592fc94effd0af9bb4cac5c60ddb05446bf2d3a3520413975aa79f1deb7a66cf2cb43466161f3dd4c67c6b6234305a98222330d4ac68471a9296a42925c9a16f8d48cc875a9026a435f90e3527df9a14cd95317d674ddf5999a46cca7756f49d55c986be3dfbc980beb320373219dcf8e86146c377d684054bc69a9156e97bc9327d3799692ed24c97f963add8a3f963919820f3a135fa765ba58bacd0d645768ad645b6c8061d59d7677d647f2c501553c97c8735ac4adfce61659a4258bebfd06af9fea650e552ad7c7b1d92ad692457d1e9175aa2ef696424b3a83fc82c82f8ae4232a93a91554a9810138391c1ca5457fd59c2929630256c0943c2b854c1f21d6241dfce213644830f312223acc9b7634ebe43caf4ed1cd2a62f3ea46530d7ec3297c07c3bb5d2ce213d0a29124d6a30a8130d0614dfb44a2fd129bf54f4ed1c5229424d72d504d466cc7c489b7cffc895fc994df2a7cc5c9a6184b80875f90e6738ad84f308a9680a157df4704ef90ea7ebdb399c3f4a1f4ea01924a51889e563bb4c6ae7502a7d289724d3770d1c42f45066f1b1862dbeb938c1125d0e8572ca772889bec1501a492b422d45488848ba88e40f11946e3358c260118385cc529252145305cb77f476c955901d2212ea550bb573189b1eccff180c847208d1435ac6b7c730fc836dd4d2ed914bb492d3ab98538545b4248c4d94be7d846e1d6937a1ad902634ab7da79da7bb533349b6dad5ab38e71b5d7eba08dda2e0740d200941925692f4717edd6405376c57bbb6b8a1ac5adc700211c7534a29a594022166522a37944b1f7d9ab961377df4f946f499831bca9f8f615e3ee0a54e1154fcf4b63db25671c328f4d12b15d1e5d1ade286391f5d52d1f4b3e92ba3bbbb092e5f65e96310e9db1968f44f7459cbb2628c80aafcfcfcf8056aa0f813b6d0cbb0811aa88358d15a1ba7aff03d548138038d18c43fb08af4ac1359155dce17c3732f66e586318dbbc5c9cfb34f3141b66a2c4a11912d77d972ca29fde917aef0d82757f7a31637f66c6eec437fac06d52a6ed5b44fa400a0a37b76ca3fa4538a514ae79c33c6b845195db60ca29973d239e794585e5e1afb686a119fe7cdf32b0a2711755b27a4de38a956c5e9821ba23e2ab15528e48dce52d227a244923b6cd359c696b2878be23a90f726c8d6f34a3a164d5cbc94d2bd0ff988877b483fe2aca6e706a356b18752ba1efb88548fc713e1883e21f988e9c354f5028b1bf2118f0844e49c33cc3ecc9e316f3ebb5ca5aa951bb2e80b9766ae7d679d7557848fc0ec63c9d576138170621fcf6f3309f7413c15e6274961d572415ac5b562cce21eecdc8f7de10aaf52fd19db9ccedf09f30a291be784f188fd516822c53dcedcc8ec953681399ddaabdc4bd53ca7b21408f64341560f21f0ddca9cb9a5795800cddaec5e9b4af515771e615de23667967b80672d494c2f5bd42db511e85e9bc48d1eda782584c0463379c688ba21e7ac28f4ad2569893b52416de2527007c7c465e21f66c8a8c6cbe8c6cb7ee1655bcc977eba5e4ac7bce9a21f4fea33ca0d29100da2412f2705e21e46ad9245ad9293887b48cc8b4810fb96b8004e975c49fff949329469ba2a4041ca144acac4928c2c63a675624d92705c19e737e3942dda2bb76d587a7ed540f4edade7570d569ebb9e117058393500bd7f7b10fe21bf882d96cb2f1c983e04617b0f010e4aed13cbf5d691cee27c78e939a92a2f1d87bb281736d6cecf1e7efa4e90d691de38fcc839e79c627ef251e46263f7ec951b4ecb1e59f2375b5580cb1e77549a58ee18af1baa7c4710c4d8b3f14b2872082e5e4e887eb209c5cd6848e074741c67636363c3794d8eec723442aec713ae4725f46fe6ddeeae17daf8bd9a739e6599127a592a1e3b9a8d8d0d1ff1fce6e1ce6bd743d6250104ce9c89c3adbd57b3094f994d9629a19ff96b9e59ab695ba6f9fd42247f95502534e37c05fa27b073ce79e52a57392e73cf9cf3b80a5d257589128184fef0119706ad619b4fd46cf7e524e823099993808966e33aba13c7e5c891c3c66b74b8cd69b3e9bc14e26f276449509f755977369e6ddb969d9cbde60b51d9cbc9cfcb896bf3b865da75ed0b3de840f342d56b9e79e191cf790de73594e68173346e3d474dcd091bea37cd0b51af695f887acd372fdc7c0510dea635cdb34f7b0ea575ce79f70b69be24a8939f4ea72f4c82faee1301ca371270515ce73e2e8a6b7fab3e2439a15392d01a92b03eb619e58611088a941bc62020168f185d881d71e062e89503d22f60bb61e7befe0d7fcc3d5e4fb43cbf9e243d0a01af275f9e04d5299e5f4f7e3eb4569e5f389479fafc7ad2c413a2ff807dd6f20319f9ac4508266a8468a012638c3f31e9670003684d708965e510375ef12ac40f63d20e2d29f8f61e6ee54411f8479c1e32183ebc7cccc9e2db69d38d4a71293285905300d6c1684e4be6508a61b27ad5b98b35e4f043256d79925ebff8137f62c78e1ddb65524c1aca868897a688cf90fe11b847f449e9bc348dd9ca4a2acec266ea6c9a0cedb432c6e831bbf45adbb8ee74ea368f076b049f2228a8545d673da050dc3c78b07cec04c141a57238724c5cb9cd393b4c467ef9cd6f5ad9556b2b0f140ae3c1665e83967ebb9dadeaf881c7ab6f21f1b16a6dc5a6bd01775129968ac70ed749296dad5689e53a693b730ec8c61693f254d7b8fcd9375da57a22254b9652f250390e0a73ee2375f993ca39c7d8388e07cbc74e1014777176cc3297e50af380c6260704aebb3b4e8ec3a670e7e6e25c92ba288151790eabce59d983cba9e81eafda5797155acd373ff2b7a1bfcad99d8871c6192de7be54aba0534a19bd09482a51b838f3120c1a525c5d06aecbe5a282b90b54a247ef66ba31b121f1e7c4a4006111d33224edec44273b4e80a4c4a028c4b31384087b6f4c2e5bd14947274e82b84d312c3ae11f34df3b3bfc43c76bde19d1dc6ecf5d0f3389626688889f2a7ce841e802331071468c3153aeac006cdf34efc608779a97793797890c4fca88e20a23a42c5e2124519484153050c24b10d8484370b811c7105f80be80ed3edc62e22a82083398a832c59630603bf7dcf5f8f074acd656b9b39325c16271dc45a57076583e78689ed541f6ed18e01f536e177221177ef00d985f68f986ac2f4c71d19476b3583070179562a978ec741c3d47c5839b16e5fddd7336eb230fd5c7abfaa69742a43cb12c51a5e2c1f2b173c4a7089293c7128d93e7a383342b9e717d64ab05ce4f193839e7ed91e63a4f6f3efc83bff37624ed1d6f5c85a9ce63c955b7e39d3c2272d57daa266ee8be63ba713f969be4ca3ad5a656acebb18e65b6da8cceb96933b375720f1fad352a9807cbc7ceb55c8a312a8da03e3d237e1ca42aa6d5072c5935bd9ce8499ce768ef951baa54d6ce34b65407e1118188a7705491e663e692254bfeb162f4ed38aa0c2c89a0f5ece30f5c7ed5e0fa3035c1d74f996fc751717725305d83043294c2c84224a58a20985c896387a12ca0b8224b134f718bd540438728ccf8b0030d4d22d490a4c4501b668c81860998286209a1a12186b802f61846414d4a8051450e6800db637f9c71462c95b25796542a6bb98b4ab1543c763a8ed26a6de5ba13d2793e9c776404224db77f01aa08843904e9ed070801c8ea43d5cfae07f3d90de9df304e7a91a39452c6cfacd536b79b66ede41e9635824ab5a4b57da139765191519501589e7772b2639f624abafdcd8ba0f4fa19dcbc0eea6ab99dabb459662ff7e0b8a78851ca18633f7f2304516c1760024ea00ff864bdd597808f2e97b8d5638c9e72d9ca7c8bae010ca171034667117d741fd1837891e3a38b105d4da844e7197fd21cd4ea11aca911a4641693e4391eb43c4816311f9d47842c603eba4fb396364105499b741688ce92ab1deb799d29b98a114876edb2456f213acb158b7ea4ddcedae96ec75c2599b81dda26d407b3938eb91dbd5ba6b1a9d0aeaf45660b9853cb448ed9c9b0efa6c8f4ac3a8b47b622587dcbbc16680630c79cca5abf52afccf5c608c739839ba6d55a6bad95d65a3967df2457690b98d3efc6415565b1aaef54e7a94ea4baec7c30979d0ff5e662574211b98473e94c6e7c18a41e83374630e99837d95cf3ce87b3952557b556af6e6b6c72443a960d896f04f508d45d65d8386763f689a5646b7e3746b6162676f9877d0cbbcf71df0ddb7b53aeea3e972b0cbbc19d5f0bd3378fdb77e393b956647eb662d55972a5b21553c92b579863d8746e923d9bd898ddb25ff6143b3f567db40af3f676bc4d72566298b3f8c7f698efc856bb4df57763a4b600daef862d86c465c9d68d912cfb6e6e0af3cb9d8ffd78e40adb912b8cd52aacbf163a647197816eb7ed3536691576e3c3922bcc89c815e6d149fc6915b6c5afbdfa8db70ac3306761be83390fe644b008a43e7f738d4b5d3a506c8a064639caad76f6b07b0e51df1f120f70d355499aa75f4833bf90e6c31b2d0bdc43ba4ab64ec02b197f5e785987da2590152472553d1350902bd9df11b9926e742350ecea51492102fd11a955d287f0472dad923502394fcf23373e9108890622908c4b358cedc608d682f56e8c44d07a04298b40777c50ee7aa8730a42e297c02073773e986fb239ca533be60c4eeae398c71eb296975256efa60406ad777e63e4a6482fb9a1ce60e6fdb5d05e3ba76004b98bde791838639c25b4d70a52239b77be39833cbd75decdf41b2311ec3c829d778ea93081f8445a25bd478456c5bf9891ce39e7be1638ef9c41acdbe1bc9be9ac4d8571759e2ea279d24b6050a58123ad9220771968bf61d0ca95949e92eed255b282be5499cca6495067695d5a2f2557944a671a9d524fc996f494d24da2ac9d7b839b797b782b607db26f9336b8def9a46e705bc8dc7a04d23fcfad83350dbf4d2e03738dcb3711eb96686ebf9b2abb22d6b56f89741b825651ebddd4cfe7ca1575955c5167908eb949a81bb76997423c7369f8add2d03f3f5bc7d280557baa3fd5b8d29b646346b44b1c9ccf1143885ee54a07476474648e16e4e86e6eb140668a0d6088428d2ed600a36bdcba71bd91832b2c9801063080f1fa42f4fa62c5e538bc8a6e2d4ee3f052cfd91e442186e572b98eb8d24115621a8909542c7d6cc1cc711c6e72c122064b182a92a840fae83c767c2ce6d20b8fbc7c4dc1f47cc6f30b8b99df68b0288ebc74f62e163754edc816834c5c2e978b59eec05eb8ec72c5dd0d69e7b0a13b9da5f46b3dba2a3a2bfa4e741ee91caaacb35c7912555db264c912fbc3c4e8b1238f398f5c426712d577d21b344748286e64fe711f63c1eda4b7a81750c9810b0c37a44106c8592401440e2f2f397cf9028caff710d0b8ea63a7628c593696ad9b21a860024f32607281311c0104b25b2b7ef8a2245b4b2c1420864017f8d5051954c0ae61802ea09000fbd3b265063f5d60f002498b38c4508a40440071c4aa421303335c88706a00d96f16e51b5b85819d350aeeee86e1466ff9eaeeeef8fd85e14f11b9cd089a29ac88f9769ad8c59ee8b20930b3e3a2b8edfcb2b285a49452ee684f2600e1c3255109e998981b9f5f39343db78a6b95f42c2e87d66ab9482cb5b05b742f51f60a0eed4720edfa373e9c0e45ca18364c8d1bdabfdcaca718a5b829d54eeb44c7f11c1e1ebf666ee672dbdafe72a3e7787e59f9c2c505e3e586975f56b678dac50d1df0d10a16df3dbd6c518c9ae797152b36d850609e5309b3945180919f6d1510b7bd82b9d185c824760bb1714ec864f813d27f9f38fae8231491ddd383c38fac61d9eed33af123cd91135e524d08818413354e54f9e845625f2a3e7a5bf6389be33ccd31d205f77af41d688c8172460c42445534b1e202a59f25ba7c01466771eb041cb4f0e1066198015305187d875b1a18830651194414892106185d3626c48dbf3db9fed1eb1437745ef100acecc18067c79962ae4ce2860ef8d8bdc316725c591e60a8cba57c87c5a170aa17eea8d520e3fa00e3ce1994b6c0e20637d65a8bc69d33f8f072ad0d57b829c0f0a69f36b583c44b6fad4dfdf42e3f3f7b9612a98830a44c92a1c4f232a8572d3ba8e8a574f2528a1a1fca201d602885baf4aaa574961e7619251a0f1bcc52d1930f3b0c0d1836131217a42e4d8d05f3b0b5f49493874dd445397cd8461b187615a20e22ea212bfd63a5816424534605663e8c666ac0b05d3f61827a2583c060d182840a3e8c481d8061e462825cb59465a48c4e22100f6624c8dde448afa4949209263ee4250b861ca67bd51eee643cceb42628e5d2fc968a58c153ea07e87a28996ec5a6293dc5af9145133ea03144c41a5b300c769872461958b4d10232b8227410f3ad0eaa5f94f26107751359e50730b62fc11b4d426b8841841458980b28d01023882d3394e1014bc4865c2ed70b559b32a44161e3d2e98888d7cf2ee0f43be5889f44c0f0d30de0338021f714e5050641419eb2162edcc50bca61bc0f59cc10484f7d52b7673cf5d40f4d4fdd81d8f2d4553b3cf59d209478ea3c41e0e0a913197ac153f7116288a77e4488369e3a92156cf1d44d18c288a7beea7a6853bf752b5b263861c5171b663003284980f6470d41c4e0850a80b090016dbcf2f607a6b7aeea7a6c0d60c8a1429c90c7d71bc0d0c7d7205f598b071f32971f9a7cf559dd3af9ea29157c75ef61065f5de54395afceaae1abf3e8f0f52594c6572f40d75331ac879fc79c005d0fe6676297a229da4f23a6065bb626d860fb3c6a1d269a43336802d97c5118df2e9b5ae701df2e91dae551d196a1d661299ecba09203248e86bcb0e20688250a173f5af490c64f0d60cd1725c1fd51fc9397174e80d0210a1e76004bf0d28516519e5451640436e617537acca7c57c005d0fe673cea22a7e7a4eadb5d65a6bad2e80aea7ca90f574e7e953b7d46f4c41911045403cf50f090989314fe34fe35ed74383440dba0420c104ebab7bd753ed85c053249078ea20743d9488480a0efee4270f40d773aa2f444143075f519b5c2ed70bb44a0913058c2f635e5e4c41c24b03c2e0610c144be400694cb4325952b6bcf5214a0a158f617ea5d0f09803a0ebc152d3edbc428aeba7e3743dd343ee09580486393ea6cd818da77ea14001c58a7f4159234a114ffda6eba193916460c60b4a4c0cd5e00d2033e0808d34b458630730a0c8418dafbea3eba9fe23c677def9075d4f07870aba0c7152a435aa60050af8eaa9aea7524a29750fba1e6a02d311afe77c72de41d7c3c17144124cec5046107050c91287164cac9469a38aa62eb28ca9e109592390cbe58a816660df16e282c68006416990d587ace508293d141187115f88889eba8ae88da7bec3821e9e3a0f0bd078ea3aba1e8ad4b5d68af43509a97a17c313153429630666c400560234b1c60c636658b2c1126075e2abfb1a5f9da708325f8b10f3d5515d4f5d2282c4c3d06f3e37b7e97ab6333b40a16122c4133e80f102cc144cb06048146ad01064c38f4b8858251ec93a77b872b2e1c76583866c9c605bebf08b871cbe7d3a691d1ad9622f4da61823c5c9116d6c01db67906cb57e9021c90d239a583103b6cfa1d6894db44e51eb184918c0f669656251922d3e0ac2f29c739ea3ebe1ec7c66b752d3f5d0cd79072e7ef3dbf56c1683820b95329478838a9f2f3e66bc1086061a7478f2068831f1d80e583ce6345d0fb6e3adf30e42bcf553d763ad16d5e6439caf3e80d5af0e5f3d1546071c7cf5aeeba99efd743bfd4e4f4d1d6ef8e95cd7335da55a628dd7ba1e4d8922ec1266c4860d13218ade503223e88c1131806de40b2d63baf0e10c3784e212958fe85c8206356c668720cad8d246161bb6d031e3a7c950123ee420c9684ad687a825acfc5ce2e7a76b5dcf4cd110e7290b40ea3789a79e1a63c4e5a9675d0ff59a80ea56004ac4f098d7ae07b34d7ad5de2409283ef39939d6f564666ed8414d8ef8aeed4dd4de46ed6d45b6a2eb483652eb706926177c7b8b699deedbbba9bdcbb85a87937e9cb40e050a6a2964a575f8958411df2eb1b4cefd6692c313549688c286112f68029bc78c09628c2621b480c108b05d26b54e54e2dba592042397a4646a2966fec816ebcb5b9fd56df55b63125f5520f401f1a775f825b6719873d8a4d4e3fbf4ada34b6a3a21fd4a58c87219f3ec96dbee53e4886c6d2e7ff358f0945cae8940da95a3da6c2f7d43719867b1dce8e21fda071d65a155d2ebdfbcf82357d2b12ebab887f4f8e46abfa455d25968d5a5403764324bf8478fc75c49ebdc97ce02b683c8088c3446a55eeb0db49a1293a241ea6627c8ce183bd8d8b1c68ecbdca9eba198a7cab0e9d356c7343199c4e98464181bb371381b01ccebc6496ee34e2107b0aebbd8c98e7aa79009115fa7639403aae1e262bfad13a56739dad2512460328e8de6e05be8284c32a55e8839877da8efc62ada6bd886c93085141a860106b7af054d73bb650d50c90348d7e16a8e71b11b822d99ee82066a8eea2640df7aec4ae0ae88e61a8d82696eb9986d4180fd429fcf342ff479ebf2a232dbf5448a518f9a8db2713c658fe770f93df43b63a45e8ae334efa61ddc642784c710c25b8420e0319f18e6b51bc27a5bb18f4e0bc346a3dce9d229943ba3752cda8b02bac3c5dc8742b94442ebd337daf1acb556e766d75329cd6e4840647a750930094a00c2d7ef048b592fdc3c89f6d6b7da4da0fee6b51bc26434c1039711988c267878c1d7adbae63cb292a0bdfdb48ff38ca751fd1db9af39743ab135e0a6bd872a399eb2fb0093261eeab702eb75ce39b121ac578f9d0498b03c40c85aa4207a25f231ffc2aef2d3ab3704131a70b9810c68200d8950bd3b0930f1008b50db89c7be13302701f695000429a4730e5dd24cbaa550c79376be6ddbe6ddf56c9dc7b58a6e6eebcdf685914ab9276c3ea1dc13ac87d36bb87d6194de9218e219904ae72e68a0ec09d03f797725f0007ec0858c2678e042461341a0a4512894bbb9f4f6c86d5cb83976eabed0e7b9ef041bd6eb39548a77f2a2e8ed269d2d3dd228e9042c095f670fcf963e3d7ac38d5d04343a84f5b5ca48e5fc6e1ab4e319fdbaa452de56719dde7146d9915023758ac3a5d8c7a16877fb168770abfae364790a2ff01875ae5591e270a38e5cdab67872a3571ee355f08e9bfa9d5d8f6c76fa8e6d94a3727624509f4e71b8d52790f97c0dfba78d08f8b0c62108d8f2758caf9f9350a334639b7e23e589499eee689d0a61ee91d9cc0ba733f99e9877bf1c39257a94878b39679d629e20406007c8020ef0801f07733cc0c6010c04001c02e6ec02e096cb15d6393b0e001888e6ec20b04e45b9f593c7d83c47474742996f586739cea31cc66df1372a7fd34e96db4ecea753cfc9378eb31ee72e5b48fce47cf2a80f377ae71991c3ad97e21f616c7a65c338daf5e4787e8151c46f93abb9ce791d4d8d2391adf82357985b7b7f8cc8e19bbc3244da2ab68cdbbc79050ca18f39bc085ecf881c6ee3fd386863f3b1ccf1c524b932d26a2724be1139fab72df32216dce394f2f0eaf896e0c058c17327a4b9cda371acfb68bcc845896bbde3366f56e185ebb913c242d7715efcaedb3edac3b53ebb14e2739105dce6c5a2566144dca306372679188984f4db2cd73a0a65bfe892361e83a87ecee3cf11dacfbc7ed411378c2e5713ba7131a95598a3bcd8a55798db78dc0c0823982654369fc1c11cce93c288a8514717c41030af5ee4927159ff16bf981423532a7531cc7b098e4831a91980b98d48adc27cc39c2ad172d3b8cd9b545c7e29353dbfc090f2fc9cc6c5ae67fb2813d7ba8d3f8d05f7c02212f7c09c2281611e7f9c388adb52fc23c53d22977934e8f20b0cd28771cc63ce6f08e12f5ee3386b238659cfea66bdcc3347c56dfb4213bcf6d5ecbb4d5c7b5006e734c7dbc1c7683daec27a7b53e6d445c995b78ef5ee4961ba5c515e89e6f1bb9b80079e398fb41fcee73bd817b25cc54b4929b8eddc128a7f44d7ff804a824a82b216854a92049504d5d26e3649924f82a2417d679323439db2cc2687bda713ea2f4d1214eab7930d8f24417da625b1a72fb4dd1726b9495034a8af41322937b94929ef26a5f44d06d1d236e7715adcddd75a9b04f52b8010e455619067577d108b3abda1846624589bb9e6d1cead671ee699b7d7524acd25739f04983c96655f5813669e7d219257423fded0bf73ce0b913ca70365b3b9a6792192d7dc7aa1bfa571ceb22cdb5cebdc7a9c0e94cdc9e6c03a8fe60b6bbc9df3fc737861cdadf19450fb79a1ffe69ba5f1f09e429e974efd33e7692991f4e62c3be73e9a4f9e3e0e91bcb4dcf785a994b51ee69ba78492501df3eb9d70ef17669eb523b14cdc64e5263789c4f3dd6cdef9c9a5fb77b37d44b8935898f2f67bfa6e30bfe1dc7a76c359d7fc06db36977673e99a6b5e78a3494d4a2e0a9a4f0945c284855c86390929b573639df3fe6e36b72efde475dfcde637986b2e6fb09bcd6a5f9882ff0a3b9e044c32cfbcf026735e7a3ebac93e22fcbcc4ec3558bcbe48f1e2cb0b552ef686136c88716d1a5aca48e26661b871312533ae4d5aba16298b9b556174b128ba898b35a144916c72ed14335dfc40832e52b044172fbce1c699ab3dbfbc8c2153c61a5e6650c6adcf2f2f4a5e92d85e2f477c9872d1d84d3bd92b44b6b5d150e705071bad891fce8beb9429e1dac870568932da18343589a14c061b964492edd2020f2813aefa44379530c2724823263a844d2f713471d011593b4c1cf58a6d7c75719dee1241bd625be61583241b197859ab7165053959eecdf3eb0a0d2fb835cf2f29da0873eff34b0a325a70699e5f5248d7e59e5f52bc008e6bf3fc92228d97142d838b7a7e49a103180324d7b7cf570c5ef87e5d91c10b066686b62f36dbd581b2c9216bfa368d7f2a65ed17fbd4715b6b595b59897c8dd129592fdb763bcbd595dcad036593a3e6d29c3a6ed3b20e4e2b283666d63ed28fd1931f5598af843ed72a7bc3cee4f9c3aa8f5cb1b78f6ecf7b29a5fc543a9efe6d1959d2d3fc8dab65d25d55b0694fdcd834eb69d9fa33538f1400e14fa0ec126b617245273639fa44125baf354ccec0d862cb8cc63b62658bc6ad0f919bfec86d956b2b6dd454d44ab3ad891a0bcfce4eb6351116ad8b342c5bb6d1266a2c5a5b6923adb1d4b06f908447c02d1a6f9a6f09e38097b87163fbd6755d4fe69d3d71430df40d342586d0de457064e16904ecd36cd31a684a6d29750a9d426903f5941eeaa09e42bf1377ea7a363f7134fdd338fc90ce6d5a2dd34eb6e6d8a75ac646331d74f0d3b7f8fdaca732edc4f64fbb6c3ba1b99cd56868686e13531b72890c4ba55a2213860cb744a689894ca79db225324d4c6468ae46c3d1743dd3694e35dc4eb72212924f6f9a731e1ee0391a1969e1c1f2b16220118c8a7f9c8cb4788a55d5e3905ae7b20ac5a503a3e384cf747ce65bec325a4f1349b62e98ce9552499d83f9ee004de6db3d7d1c464de38cb454e940e336232d4756b4645f0d57d3f5645ed30de9b79e8323ca49a2d2d86cd31a482c8a21b4b3d3502f6315e69ad7b10af32d06b54e0dab740875107e17011d1c0aad6ad73c25ad6aef92c420d9aae15854dd23c2fee3c0a98b80f7d4371ad991323dd92f720fccb31c9c96d9aa754d801bb468d1e5719eb568e1c46f3449f2ca1d1984474606b2825d212504f30f7885c32d1ef363958c1fed726353a48de1f8304e28377ef4f9717372b93040e3431bbd7011e6a34f2b3a54098303339050238b1184e1c318176c79614c1a710bd00396a84992f28b1b9da373120c5e4fa9a76647ed9c13063bfc9ca91b2ed878e90d002eaef8f6489f98b3861cd14a0b46f779f291bbb0122f45308c4d64f0718b31c61859d1c1307679713185d6aefef9d824c63863d2189ff9ccfca88fdeda2eb3534c0bc05a9376f8eab5eba9292828a50e78ba85d2d32d7ca0ce61a654925dc821a40cf261909749a0f45bc44b4f7131f3d23d8b2e2f5d75c4cb971659bca08b8f60cca23593f2a389e590058e9fd3adc521eef0f3a7571b454a31af205e483f1d4b01cdf9110b30e4421c2c9840c04fb7582cb980053f9d42b9e14c7d7455eb4c9f36cc1c1e3f7d04612d5cb6802177f9e9d30670f2f490c9f0f890cde80060462752662c72c1740e239616547981d3afab5851fcf41d1e9f2d56fc74242d68e3a7a3e0fae92c68b9c165450a7eaa00daf21a226a1d7e5961e6e7155f7e7ab422c4cfe8c59dd3cec9d91748f9f6d6890f60e2a78bf0d3914c4f828212fe313f8ba40617a66da8d40bf7b549ce2a6433020000000003150030301410088542b1509808c3f40314000e839e506a4a190ad424c9711442c61843880104008008888cd0cc560000106300b479d21305646feb36a226480bd58950b0b93287eb086ea7bc5e5392f202ab88e9a9066bc6dc5c46f8e735cdac8d156924cb73370ed34c50faa4d1a3407226f941a47d0c4fd9b2ef3a633a3f04a52dd00ee7c8af34aa95cd98b6b960d8c18a1ebd06ac77cfba8eed1899a4979d98496749b66b952d0e60de04ce18951821f29effb67f59d70617b3abea6c91be4907ae7966cab403307f735c389912ccb0a45a623f82edeb7863d3ca1ca987ac33aea6bae33b71235dab1be4358a5be977fab7582e68836a1ce0f0d5277e67f165563fa8a8db7f7f63d8ce61bd3e161e2b6f27ba6a992c5b5aeb69f30a815a60c704810a23ad63a10c712a1c4ef99403639d7140a775320e45f53995860ecfb0cc96d1303c02e24ab1c56716f74bf70b1b305fdda771d2fed5009b013026795187111dd1f5ea7274a47ec3aeb06946c39d86cd58ccd8f06c909920e23821cd52c85ef61de4d88b6eb73f54316331004d646da1686984c64f54ce1ecd8a9d2c9e8a13eb86d2f0c60079e70ff30feb050c55f0ea46b6625dcefdb83c060ce57ede3606313b23573190ed3264933119eabea3b99f82b2dac5a8e82ea834a3ff428c8e82e6cd367c0d24625183d7a1b4e503b42d4220ccdd299a702e7d8334cd14dd0298055934e32ac50d7a2abf8a6a005dfd48056e2c04f8135933a2a3e13203a2dd80e867ea4867965108c494d4fe00c891937ba2be8d5dc4b5e69a8cacd8f422ea6d12c894459a04182a810754611d9d51eefaf8fd045181f130f60d43215ccd18cd3bcb5234537c7b643705d127df3d07afaf498edfeea2f2371abeca91c3120e1ef59f2b3591f084721febe2acefc755513ac2871957beead9109badae7663402e7ff498106ef4ae2d2f18044b8017b4c59166a7d4fb69b10fb8ee3583e3449041d29e7eeb7aafd7c8afc0ac01190843fd2e5fa392988d75a94facdf83fe3a04ee14b11ef4e420afb56a724a4d5a8202b1a62673037b3d65c2294826f289f171eefe520935b913b1f84492982f42c521243fcc1629af52bd25d1dbae2d0be73952ddf9acccd5a7b67807b13a30a38dfe020a475ffe49a007ac0b6b1adc7f5549195448eb5465596d7f2acad10f908f98b264830d8f21498dd58b093b504f6882f4c4b77bfd0de53a9ef8a4350dd7d16c7f3138a0eb79235c984fb7ab6bb9cf560707704c3b4cd3d2c9dfe335c42fe84e82d9634ba842598895268b2c89e0a6be08027e83af7a660c10c12bee12388e04830dd347d96fcd49e6b49d8b993c8ccaad81da0e50f8e976b80376cc4c43ac307e5fa145b884ac868bdcfa4485cfd725acf933818b64193b1d50924688dc6eb1ea7f3c04c4aa0aa97bf49de9bd404b330818a0876b2a682e4fa26d70eda4f0d3d5dc20fab9300fd584e54ee043fd5a64a58cfdb9649dc423e6266fd49f693341fe591d41a14bed4edca95d32fca263c3d34a5e4f8a91c73779f8949bbd73c084a4fba25ae9f5a09d520b5166567450352c789c53f297823b81c5d01c75a30b0777d9cadcb25ddcd58ed4ad8cde13c687901ab836aa43a4e2650e28afa7077abf02c00d55a5987b00811d44b6d7aa3c2deae7d5fbf04b24a904ec8a6cd034708caa9400fe37c8a566d504fcb380c66a17a67029610264fc0dcec04e7d55240698922359d447188d1f6d57cb002b7fa5a0999ae256c997eda039fcab11e9883290d43f794cbb55d823fbf4062bcbbb015bd646ea369741d4f5ac9e3aca9533e70b7c580010b811413c27e97a4af79ddf66448d92fff37c621d738e83300bc85c909f999bf2b53577e1f25f5e882f88f7bf543861fe0dfd25a40ad1fc158671d53f7fc36a9f2746815cad33c172930e22e38300c07a36ba1d6882491f8d86dbee57529ad22ff8da437969c895a3e34dfcf47349e4ee624a2bb87c311d214f0a4c62e63a85a68da83d9edf8308a3bad2207398d00115b9aadd6179f9418006bb88b046f488eda0d307767b3c361836014d7e7e11a40c7f6bfe37e856cb73bfe66e5e21829de711eb9ec8d4ab43a85cd000c3206825a8c919ff94b572824246413a2d26ad4a06c02b8333b133ea7ec8141603e5ae39a0c839ccc70fdec0c63591f0cc827569f46faadb5de648917a86b7305054fcdd3862689a6c4472db4b8c15d5e71025ac1fa8d8bd81f9260a63da9e4c505136d5898eb68828cc0c4e726911dddbfb2f87ca73e6198ce0fd28d15064771736e65f5bd752617846b38226ff312d50ef4c63efc2f0ce9c309a1bcd9f250e4f3670ade80f1715b16eb89f49fbb67c1ab8fb0bfecf48d0cfd6763c4a69f151d5d8f510e06e2e4ff1d52bc54ef71b4e61dfda51cdc56ed35c613ec596fb92279319e3b12bc8bb2ab7f787b1bd0716224e54d697a20c4213ab57d8c69ae3bb6693968cdbb874ffa1dae6d5a7124719e184dda75d072e557fd4af14b55411deacd663b669e7ba2f133b22b7d4a140e4cbf77da20b33ffd71a5002578efaa2a95ab6b2d9aa5329335fc4dcdf178a9a466d1ed5515c84f3d7c41ec13b913e47d35cf21b38e781fc75e38ca3101c99de95ef5694e2f6b2b7f5f074a65828708723b26300a3d490fed17d91e855deb76b5f90dcc06d7b45377cd1480d9ddbc772b0b37319072157ca197079b5f795a7a7d330908bf074b6a90f3ccca65c619264a620d092b7a60aff7b249c4800828bbab8ad84090060d898740df46b460393c322bc4d7e22c80eb78989cd6a09a5d0c86b29e19e190f9003af29fd571fc6c183666fbe65ef055c374156b109443aa5834f898cc1bf4c0e1cd41933bc2274a8bb9c82f365c81d7b9d84b1128c880f80abddaf192d77dfba564ca9c144d9923fb72745e20e0803bc4a20a246b094731913320c890930bb4d5a20c48e96e510cc5ab71fb45a006569ec1ba7fd78ab85880e47110d27b771553981d4b12c08ff17858567543bf2a053249aed68454fcac4278892955fb647edb429b2b2b932151ddfda736a5186083ae76ea0d596ae165d161e0e86a2a0161a6f6434a4ec465856578a3ed062e4da813c72adc48756dd44a1553d647b31575e46cc28d8edef41b475085202721fd44d9fe047057261ec2c71dc9f7a7602f3c07c342eb99075113d874669204d099054d04f8c8dfd40a45690693761d87fcdb462ec5047b43a446578be8d47a1f96a784aa82073e6d5080752e1f28d3099f74c0a4deeede0201e095dc7435f32526932387a0ca120da6b7a96f0f25dc8e5853e12ced046eb8402d1d7378309d80866b5f230e3b7276cc5ec9861aee6436db26958085178b29963343dddc61a99d1da183396d3b698f1e370bb421c5034c6389561cc9d8c10b03ac1067326bdcea3e311206128fb3527463a0993936f55a0d772e093a40edba6322d524df9b9952f1003b75ec6cba7983c9b34083968ffa2e9024a5b14b74643caa00d541bf37642427881cd96547b4b7c03fdb186d35b750a08780cc80e7d4fb89e0811f3b9771a4594c28b157920fa561112ea69c61dc71e799d2c230f930c6a241ed8d8058585bb17a4eb9473fde37de629db806744750cc8dd18c40cfd558f91e40f4dddffd5d22c24cac13636f4b0ea33f5d3cd0866b9ad9d4cc2bc2a54a9a20a172ef91bd35775f4755ffff7b979124d743330d6f14140058f61c4053c7b3c29520bd39f854bc58f5d35c682dcd9c63f60bd00f891ca4e027dba917034b749dfd036d4c8c4d61b84fda2981e931bf241ef744468fd009abaf7ad86595969e50df64289e1162e1ea86b4bb8bf5372278094ef9aea16f9c772e59fd891afeafe4b978f491811e5379a71fbe8b2fdf53e6d71fb70f57dd84724c705efff117b44cbecfbfd39fc3456a25e0aa0f9ec908c1f868030137cd2d547db3eac2f0c2b254cdd1a0864ccf1986860b0e42a40f570902ad2077ea5385550aaa7e3501d90be63259f602ec1703e08656f4b944d856ee62bed34dd404f12dfc3f382fca8434025437b6dd730f0747625cf0f57f60041e65f065928661ff7141c3fde1221d88ccc23278f09c1f43b432536e557d27b6fe8ecb292e8d10c471f10c9f547c917415cc0854ca5fd9b44525a4e453c9ec105927c8da9fe027a41c6e340ac0874e0959051096504552b7bb5b2e4aabace7841f2de925e83f8cf8d7a4325d0e071465a31c61a36d6551e43c4bb75439b5d7226ff5f35619060de50fc378f12ba4ef606cecc116db33e6b21e60d1afb70474e6ce6264ff2297485445317adf4c045c19d45f9ee9b327db8401d31882444878b0975e6e8903083347145b073130703d052e0ae66094c88b44156a99331ad8828cb9dd86fa97a17a3a9eb634fd2596ee362ea35f534900be16127d834caf63b4b7625db3726ce8d7fe2e6e1b345c6b87298220af7fdc2089eff5dea9f550fb62785e921ffc94776d9c48c0d4b8311d2329c9c8e54634b545fc5b503f9f219e8dab1acf1f6c806b2ccc77540a89d23d6db2cb5a1b83d11d4c68669bfafc0521eb0fde58281f3847b52465296575d5567035680514afa68058dfb0f8097384f4c025c6abd6aaa1453e1a5d90a2c70f224bd5e102cc77bb8929725ab34f982cb51972ecd1d5fede98033c41081e4e0f676f68a000d7a08598c62db715b8dad12f9c3b68a57a403097eaed5d3936206682dc3074ecbab0261f8154c61edc521b6606e8f954ba786f5f420e916fa50eac7d7f50ea61c6564d3151728ebaac4351c27ca05de48bf19f527d158e8be93e198e29d175c7ddd5253a6e999d4b9badc8145082c0104fac11f71e9f6d6f37077cbf952b4692a3e9f066d7b65e86053d295de13ffb421305b0d976cd9729566198d79ce0c2b63e9d1ca5765ba935840b7ffe4a78ecb8766ab290814c9e2255191f9f589f41a36a3bf278a479d8788919dd9a18c795beedeb474b3aa557f178977db85e9453443e4755e5d126806f3a83305ba4241f7adcfc09c694ee0ce6195e3b8642e21068af18bbe1193cb7575e9eccedba3534f6fbbfc5eaf45f5a677060525fa9d8757cc02b0560eae1832b18c923425c817a6c3d7a99d633f17c887ee1cc758272662980d75acbf8def7813d460bbb4e83bc83781f72ffea18344d4db6ba1382d0a23e76d11d3cf2834055d3da230ae3255ed41e6dd15c2f6359cf97c87858fd0773624bbd350ebd08e0a1fa98d768855d570eec59d6d4c2368d1d119b2b4349490652e9a9fb6650d491326055d8fd695c6354a2b0e4926febae0561cbc9da04781a65f30c7c8481718bff93b3860765e7b60143ad26b562427286dc55a29b6c3831b3050539e0089ac7b470a0d189526e254ae0a7ca8d511cbb2d866009e714566f8bc568dde4c1d4805eff8772818e66e5725b3bf12932fef53ad7d6287f59be60ae6b91e34d94084c75a7c66c7868181f1de4d6c2d028f6131b7bd3722e4bfc26c6eb2eb6193dedb1cb4394adaf8168ffc37fe07a6cad32ab869604194ac159981ab59058a0623eb64dc6f1629bb5e36b7d8adb719879aefd0632bbd46150a4e7dc5da2a6b664d9a0a2e126b582d23e756d2c797bd1793fe1e1d3bf59ec5f2c8b253fadcb9756734cb3bd168611355fdce3984718f2ae8c1a5ed3affcd9c8aa381f73e6908f9f60f07d1883561b8fb76e9ce4122513c08cc81a0a8fc362e3cd811e4ead372fd4248c916e1dc3e8336aba896800c3e8ed1f0fe31fcd0999e2ec41b5344703ea9ea45e3db58731c6103aa161aec306ac8c6d8e0d8cbd922728ee86b1c14b1ad51485dcdb767703a49771371c148a07c2bfa82a30c1651d42379b717731f7c2483e6046ca1b8fd2cba6995eda42ceb1279872667ccc9005a2ec896138dabd5e92931813ed8159e5b36ea7871cf097ab51f9620f769b284076c53d232c6bb15db18d95dd184763e98b8905ec0ccd0df662276152bc97a7f8243148214ded7017b8fe2d2a2006ee6a7360494fa397da44cff6e809b6021478786ed379857cf0c0d2595d15b27c250b6b4f37583a0406c3ffeb32441851b29aef58a94f458423929ce255218846660c546ca64a6c9faae911be1b4533581691c0762d78a109bff2b0f31f219a2899219d03969fdd69e9dc5879fe1d053c9045b01b2641202ab53b1504e7127fab1a7aa9559559ea0165ee8023e21c72160ad7a33e766d79cc4da5ccbb9ff0206487d2980301012efde4a4dda188c5b7611a70c60241f76a8989262a5d4875798166448018493451c629d289126150338373e590a3d4a198e4ac950805d86bdb75643bc4a8594bf5d6563d22ce704a5b9548408c002a6f8a460a79129ad15ecdd58b22d1ddcf27b953a9972d1e5c967c88eb3377a3b196c08854791f4b75bf5f557330db01065126c6a57e5a31cd180c10451a3237dfd318a693ade1a44d94f9c323253ef2f582cdf3f20beff808ddaef8839e612ee6ce22c2a6630ea444fce47a63cad5dab5128ff1781b48f1870deb1f01b88de2597f3cb2e2173e4df0bbeb41d76a65f28fdc2956ada5e3520577cbd153d368b3edc3b61116009f6a26f806fa9cd1b6e8cd150a2a998333e3d307b7ee6494e648102c8e28cdf1cca3626e61d0e51c27055de42136ff03a3cca78f665f787be45772e2c0183217621563e3df352babdc29790845b89c097cab9824182e0df8b8bcd2ae14ea3d32f6a40931e3c1fa31cc0048a393c8962e5ba05825867d6c042425e91908c1a1feac3a1c578884bc9726f146f3948cdb97f536bbaaec141dd14302391e74df31beec85d9ad0829074d7a53b9ded2724bd054086ffb750b5dadb223ee062e2da91c5dafa0691879c3e85eb744b118379ae8183398990fbabbf40a71afe190aee81f4a24cdee9840678e5f22e40bfd239c5f9373c45cfea99e5ca4f7be4824b2a1f4c61186550eaec3b10639967ae2a72fd87f7eaad3c37cb49b9a085355eb546bc3a6c5060b3bec0284158cc0974378aa44956c24585a58933b985a6695bbab8d9764fb1d7a1b21158f36456d48442520213ee45f171741232ce3bea5e16ca635572e1db117f4c4bd43f432401157258173485f632204fd9be5645d202b29131aacd527ebbb948843b7695d10d7e025b7b42824e253e91b18310d71d9f4df920c706c09c54320980272d34f9d7ef44198ebc8a62abe7f0c3e8eafe7f02910b4878c2b574f8fab59772f47d2b82c3f6502babefcf9c3bcdfe2e33a04187bc0cd52b3c84cee7b397884b567d6f6d2a1e08d59149b7bff0e70c14f44b72cd86fb7a7d3ac3a9f7937ee421198be36fb142f307d9b34886947d436c8570e8d42c6d710d09c78bbc09231914f4a2cf33706c718a36b649fa199e53cadd47592b78255c6df8084c6ec06bb5b3740eb276ef10f217c30b8cf46d7753e5f3edf08284e2f03e9fd5acf46962dfdc9ac20d35fa75f0a6755f16eed332b28b09a4a605921964bfbdc036b5cfb3d2fb0aded97786fd82acf92c8da3d5f178da89025aaeec1658e71888cac0de9c0c000b5c870b3c1e8e82bc6da131d2c1981a6b7c71be781d49c8d8addc85b93cc4270aa49fdc82dfb99bc0692990b5400041586facc91f109a91256b6e05dc8c25b6a07547fb2c5b6e02046cbf5a55f0846b1a8c19ec8449311838f3609bc0ab66272fb58a1c9c88533df2f4f03cb69db65cf72f663f73e55d8b2521091a218c4710500742b2aa8d3c0c660fd70587f43f44d4b4a0fc82e195e2083711009230c425028ac11f035262226036b90779335fb75432dd70dc1b023627a74da37c4ead1d6035d86a5243d67c449d4a8089e0af839e68820172ea2fff75fe689856ed004ab92a3220f6c1329d9f6c4ebf6a3aae028a0e68a60b3db31abdfb113c309fc08f0eb4055bee4a1223646964613edf782e40d41e021aa1fe18949c937303e177acfb8caf65c675eadf4ca9a7e03ac4827e4f3dc13182b8ff2edf29ac5607d6b321581c0026943a469ea1928fa0fab0414edb206890e7e2e26a0c2042e429b25ea5f2823b3a050e495538319acda8054ca015be6153c8ea03d90c1eafd6134bc402245d5a8f77958f6f0723d35ec18e04f37b8e13010ca6e6c703fbdaadb554aea3fc1d2b0d7156907000fd5c26052bc8d3b8e27675e7056ae985336cd92187e2b6f2b7546820195d38bdeb875a7ef12048d2f202da4ceca533dce48824918939359201a12bf443edd82bebd73a018f22573406447a487ba700aea969f30081251f16d64715810cbe4e91d1f15477087babb4e1d9f89bb6572795d48752c2e0a60b479474c150d184f6669806e9f2cb806b5b72ab18d008007a43dca7180a1a14a6e9870650b39ddf129f0b103bf0f46020e707c9bc44d2e3d68216da180fc6e417b5d990097bf470fca2110c954f6da4d2ae40b9bc4d1fa47d7116becb9d807a02796e9d8a299e266836752889f2d8fa5aca7a5d64e5c1830028f6d59cc54b5d912bb7dbf8a220a2832ba9e1aa59d8c7e3af7b58602834c0bea1723dfc3db63228bbad8e25269aa30a9779eb6127c33800770ede9dc4ee1325eccf1c420a069a2e97e4485cfab4635d628e8826b2ae3e970a7504c20687ce44a102877623880a16e78de89bceff54a0ec87dd8315c4f3c23805e2d3f3f0f47dc39a881bfd72d6364336d20f41471c135d812a3b09353ed70ed304d9b393e45634859571e6dbdd0248204612a8a709e234424a1e973bd7aea8a9e14579cf640d0f35bca68e8453ca83fa0d69fd245b832a8d3e291620a94a8f0d6a9f663f4bad40e9c0f4c146b9494d3d1deabaf17b6a19f6b78225ee1611bf4a363b8831dab7987f089170596d18bcc6603e8aa0e9c78d74cd17dc214f68607efd9a76e7f88001ff1cc166db34d5d00903dc1d7e7df99a52191631f8ddc2b9385e236d5d9fae8ed171119610830379a4e178217020178d8e4cf05f984f3cb727bd5f71419b798c3bf864256789224e3552cc85db02e4640504cedcaa046836b9d0993655fd870f2449b3f7270295d47e91feb5f4509c732bc761f7889d32983258be6a358f2cd8400df6d126c99fbcf3a7d7716b4663bc8e9ac2134e97d4f4f40b32018f8a0244bc59f123c43b83497feedbc89214aab9c232a14c99879da26ec0dde5ff600215d225500aefa7a1d813dd51f7cc6d0b2b724354efdb33f8b313393c69bf844efb8b0beee600a9935d2e9a4b996af6440ff1fe672cbadcab4f81611f39d5e8b92674e6591b97f48d6909e027e3d50cf1de353901dc752b03ae7382df6cfbd0354ae82492f045eda534c7fd6207ea242079f53aad686428b1d012ddafd87ec32a8fb4fbdb3d17375980ff7be85273481e2e598e74c7f55b90c7f2c50a220ff69f4beedae95b8661afb8d8fc200b968b4708345682490fd6b0d4cfc3629c1e45debf6c864e4c19a79b78a868d9f75331a677a551189a6fc7264ea37cc4b39724c8c5120db1b0619cabb872cc1aae41e8e83171a181e56af421fe0764573bad96c4c0a606327ac6429572f4e091249eab3822623d7f0cbc6a04556165b1c7711d07bc0be37072d43929bd0b70554c17c95582d1fec3ae94f72a0d01a1963905ca67f669f6112c58555891fd33d297f410c6d0037c9d68dfdfa2f9bf7c2f0e4a3ead0bd8a508db0922ce766aa73dad1c7ade8d38ce0d839bca1f7e137fcd05437d9b4f08dc3301774576c7f0c3e109667d31bcea4462e7f85bfce2dc529845ea73023de30e948bf03cf2cea413cb6fb116d07459bc158fc65023b24f9383aa3b22c46781aa5c22ea5ddd0ef5ac3f6d043e0a502c49bc725956d709040852165ff20da38ccfa4439ec45aa360a0a9ae02eb75615ed91b6c7368841e784f6e68e184af019d2a7d02a4396bf6e87bd4aa47f4209d00aad00de3bca2a1194162616f4ebbd34706f64a8b06e572e29f0d9a34fd84dba96731d309f090f25fa9bae6b89f43068cf1302817635c361ccb322d5e6e0adfb9a58b3fb866806a8e5ee6e19b386236b4fd6bbfc3c2c50abcd05d6c4a1c4c27575a325a398757d3be087a469d1dcfe309549d6b2553ba3605b400a5f65dfea660a20319d91678e0272f3da692edecd0e690c8ebd3bc270374fdce985c27b972203a4e78b87503e9e8712e9b9557ec38bbc5af5d3503e730967e31ab406267845eb10e041fbbc938dc18f0c8e3ecb9467237937d8ba6f7269c4e1049779a3cd30e105adb8ef28b00746ef4119515754a2fc39deddbdb3bd98393020388b04fdd96c4f2f714303156dc0b0589314f0da408fe72cc00948ab3743285a4cda74fc2e5ac8581065b27c643d7f3e0c13fc32c6ee448ca29d9141a234e8fdedcdbe686d0001585ded9d65f884cfac1dac629ba99b9a3584e59c7c61019caaec2054093b14b0e2ee757774beafdbeea508b7e8e2f105a7eeda19c922e97c29e018d34df7c5d0a31478519cea07a1fda8d96e0384c826b04b8c35367b4441dce2dbb6d64f043aaabcab56ef5b529041fe3f5714a9e4901259f4a5ab3f2ca85fe0008ddd0e80352f050f4f998f20266fc938671f101c98bef8308b0de627c551bc8852326ebe3533135de646267222e310a0217deb2460af94fffdeead3e7f39df439ad2ecddcfaa0134f13b968fbd10754c5fd99e5ef239ef64a7d7de64b614ec055f069126794c81128296ea19fa49d836b2d013375be141cfabba664495d82ab2274bd3109e9934d4d2a49ce27275ffa72ce2585161c97f0bff5912f06cc0055aa50222abef76f365d2ff14ea99541ccf95bd14825d5a36846c4d060e09bbe2471fd72181445750a226145af6a90c903739346c31185d30da496cd98df115742089b741fdc83f05b590afb1dba9d3484e317c2707f2566b602a094d3f87f11f6ea2e2c1bf241e42a1ce5ba614d63d44e91bb0ed791cc36f86834f2de864f4ca316504dd8b32556ac3c3bcf4dd0939960d2fb1f3a30d61f268d58e1e0f0b4965f09b604e9d3e54091d10172f512f1bca0352f61174a771f0d32a4308840add5ba2184f085cd783b1991800d0c15897cc56362d1d06861d7cac643a292c296c80ad89847d55a2e5413786b3885c69a556b99613f81ceb0d6f8b42b2b8bb5e63cab0678bb496acc57159e37836c0a8a2b08ca04210650fbb308e97687c4d648ca9c457a5f971316a43b54018689c14d8408bcebb099917c60a1b9d34d4991eccee3d33ec9b7f72069bd2393b92e7068f58649f1118727e78136d97c11efa3338947b24f2e8f2af890fb437060c1091610e84762ba7abc7cf590f4ea891225f86e1caaa5c4499a00f9e611fbe97282b9624a545a3e3267efd6df725911d51881ab58d050155752750810507d59f3c4c8cd7ae2c0c9ae4908f49506e5bdfbb3643d642c52ccd66b64e03a2db21861ba9d2805cc6341eb43208f02dd225a4d02fd40e016823d98438ecd51b60d5cc7d343cb270e321650adbbe62d1481164bab79455a0c226df084534c679fea77f9820d2b0051f7743d6642ba29b83d40ccd4a61225829881165cf492e881b049c3bd682ddbe443ab9f4967d848bf0b495d843714f55b00d96886a11edb0921c0170c75e960519d22a2673210cfd622f544c7c03648f598b2210f954d21b73601b4b20b4011b678874e187a086ac9a170c83591909fa09609f694d2aa64f0fb397b863cb4731a7cc9e3a1525b52b23266814aa5eb9ced6b7806644d0f38dc34563a421edc51487200f964ec15c9208c7a9a44459d0e83d0882ae53e77abd2995a9bf9939f81ca8ccc3d6be0b80fe36decdc1b37b533d6009ab32a1c9d1b19a050221e4145871a195ca67be8584f09fe62b504d82a30addce1f87f0d02e709f2a04505690874cc115603b2f536d1738b91f8cba32463afc6aef2799b9a0b68d538d15fec432627bd1f451b4c5d65483124a889a67e603e4c9a4e0c60f78b74b2cc1d39cbbfc5c672491bea3ab3ffb014dd4f42519cf8632882b69f4b138b00d541da1afe2627c8061acde25ceae7d1e066137804f39985da5566c5ca2f0e566cf8f1324b4c8d4ed275fef2d3c6ce35384e84a812fb334f3acf9b02120d56654bd9e3ff4dcc58320468f9067a057416c9c0b91f51b9951feee063f3c48be125c12abec6b08cb21298d7db22a4957a98512866facb448f425b0a19a40db8a5a646ba00e4a6c1c1c16a404fc0716fc80aed20e10cd5e1426d18d2cb23b6f403512007ba8b22a47a91d385d65de15bcfcbbf0279800ec9757d41a03e4e0194440771a2a63e8acda76b4affb089ee570acd7c6e95addb0bf4d5889266997a7bdafa336b80fc564ade55d2a64775cbda9b918693c9ae02a9d1e3ad1d7899d6b4f17808e44784ccee6916398b4aaa4a4043e259b71fccf2eac41e34b903041cef7629035d770026e4c29293b9a70131a2a893f5c03497b56530f4b944445b80fbceadabb3b0ce32bf9621eac3d873a2a64945112262352e02f5fc95f489b9db60053fd1f1a70d84e21cce468ca9706041773eafff08860084a118de995d5e0ad2a45288a5180ae34d9941375c6605821dc7b34084a9768b27b2888a392a0e95367f9cd2113cb791ec4b28642c5f48afbf9758ad585ae61932ea56764810bef8b8cc4bd6459ad4f0c8eeb6afaf851bc451266e2550ec5982004fc4d108f6363975165338e5d18565989319f226967821533b559a7dc4936d7b075301e4c5d80e360b1869aeb700dbcf37ee7476133ed50eeb5883d22dfd9e22ebc60062c84dce82499abb56c4e46f14882600d7d8f8116c3409454366858b1f6630fc8d23b495fedc85f797ee70bdbe8b237761728028da46ac2a9102187de9d4a51bbcf8d1ea60bbf3d8127ab28b1ef7fb5882551449ec5dcbf6eb7c75457ebd6b65fae1c6e06bc7c85406cc33e3bd472186fa77735d5503c04a572bf33f100cb2813073980e170cea54b801b4873b043c849917765218bea30440607adca2ebae00fad3129be32f9af69ae697b94a15650f9e7b3765a623f3d9d7abe1d6ab5d3e44ce83dd83a10678d8b0c9060f0dbb6a1507a7b58f01dbbfee2461fca44f4471e27c12cf95286afaec00345524458dcaa33099e48a107cd93dcfbf5261b881bd399df3e2b6de2500b761c8b24733316812fe6fe7945e2b600225f22ff24c6ef437298ab589883e4950f379950d6b2dc70866a2bf3ae40660b468080a645e2e2a65c605ee19cf9516c9758d00fe478ea50f57149efd0bc24ec54c9c52c232f80dd88d8b69f588a7d7874205ae78085ff56681bf2efcc7972a201f16556eca6ae06f595f94d02fa10d8ada4a9f081385cfb41add4a260b84606873fd39462ed5ed108a82e085924cd3f42876a1328a82aa387d4ed0439db9d0207e5c1365d09211200760391098684216728d221791a35a50792186228df9d4472964cc718a9d02c96c600a5c51da8ff246d3743f891f570f307509bdd2a08d51016b37fc0bf63df60b8e8145cbbacff40c85c72e1a5d8e3b48954b836ec3c65a225bcecef5ca4ed986c45ae0969a09d8ba12a50591d983da83519386188563ec22b8cf4b99e03b750ade78c1e5b42675fd09afa4551cda09427375bf3a462140b0c5a1c525d06942f9d824efc44a459115d9f47ff3cb2bc2390cc19b702bcdb9eb91598e4581701e491d7f94cc00a7f8de7c0925e4f16ad5c924fcd10e497cdb1d8a7af9ae07b0022132d4b5e6eccc1a59a32a844a9555bd1bc76eeea0b349079894b8f4c07a08f4a9715fffff61fc526667464413f4131e9e094e231b97204bbbc82a311506704a0afe753ab56de9bbe7d77639c0045ba0b5a4a48f4619bc39052306bbc3e4e0cc014dcaa26522eaba899ad0b46ef5a852f21dff2188b586702d1aa4684655d9f1919249d8390f63d172808ef92d565842d39877fdd5442f0eb35b0479cced0b3b343c52e2df86589daa6c92ab33d35febd396b7d046ab082473fff93269fa6c070a2ee06c056e7f9d82f1915a14d681e7dc1f025054053cab5c4bf56c05cafe91abf7843ffef43b65f10861b2c29a4da6858f759c6f99fd5b704104bcaf0a870314979e25768e8d0c3bbf8cf97616d68042455c94a03ea18f8d3327931f14df149f6818169fd8fc3b4b23928e3899ff4a935a927fce912b57debafe8a0d64f92d6d4c56279af894e7b32b19c3c62b16a8228b05dae25ac4a4592fa0620913013722e673628fecf0608f3777a74e8f10bfa1e1ebc31bb8c15be6370435779a94e5b86973f0f25dc9b1f687b04c30863c5fa08b3058eeaba3a3dd2ac77a992db010536183a83856f50a3b22f5a83a58cbae24b524353c9bce15eddee8c21018ec8ef688cc82f18e26004ee88a99e07a4f18da802cabc1af13bd945ea09de8e5dc6b920f477efca148c827e3889d2d57acb3f3a50b2fead245b53913b632ac0bfa6fb010b1b1a883cf45da579557d2b001ee344d8705054b070a8204b0e4dc6efbf7fa10961f386ee30150afb8c443aeb36078452664b06007b7c2745c96c97f1ff75851c4556c1431d61c77e096548a93f139f0ea84ba73f0c620da068a5b57ca97d960ca18470a495693ff68b29c6a3e01094a14b36273ecf6afa4930e7cb4545b62db1f6064d748fea058077f998dac8fa11eb0d730a85576ecfaf47a7f158963c6fca963f66c4df292b9906592944d7852d5e6bc8594ecd5a3fb0837c327b2827040f5fa32e46d00a1234b45147ca1fac91596217b1291ba66f5311e415b2b8d7d1459c6aff550340609c8f4f0f5ea907d7ec24373747ed8b342adb281d77e79b900f878c300f81e05f68ffd594ed01d81babd29504ff2052a457cebdc95f0e1c0c960ad150ecff0b456ec21a22bc646721e4a5aaa811f4a40f30a38fff8aed90bf4aec62a841513c7f23fbb7b533520df43c1491ea1ea15d807cec4b2d4aff75a930cf4026dc588bfce8a7a3bdd5ff9cfa16b8759ff79936a86cacf277e6cdd9cea4133381fb4d50faac3a5af1a5489b41d2d6a2c8d2d19dfcb4717fcab1e384a459b8ab0417c54d1d1e4456bf0d39d12fd6f6addfeeb53071adeb899060088a0ae41f85b76e84c8748d06582eedd5401f6a332ac8b6b9d763e80a01dba7ca07c470addd0351554420e1d42450d9f24a774abd0e369ca75e86403166ce1362d73aec8236209434fd0a7332fc9b75df88a2518e8ca0e4315e93d57a7b43b132665f714df9f5316334bb11be9be710d53b2737c5a64bd8080aea37d7ca5d801cb4d4df6823a60a3e719b08943205bdbbb9484c84bf5a05e4a8dcaa848bf828a790370e1fc729bbcae29b6a86b96ab3a1e38e2407a83dbae1b513892d219a69ee3888e7bfebb174835b39de1ab17558e2402a39d552ab1cd46b1f626b93388a1f2e6642c9e995d7e16e3745148a121c7aa9d7675a5cc4a1315dd4f93780e777ee44df7ab8528a7bbdad16564ceec1b1d08c97327c6d4fdc60df6173189dcfca4426c7c72e79babbb6e09cb47f6e51ca305c881c9e7361e8aa46545ca698384de867c85377f4ec6b8797070cb029b65480d5e153b89bda750755f6b0e32e963c0c04d23d3f14d6c4a26ce1df38bb3c382f5cb27d0243a5b4a274f5a612a10e9a0c39171e489b2fee0dbd23e386ac3c2a8525bde525d5d37c0d62e63d776e30bb148aebe8de8047682f2e9e32da139811e9a4ce9d4e910522644c1d32165a7a3de1967983469e2e81a50e0d793b2299063d7c1924df4918ffe6c337036c1a17614644abc02319110154b462da0c8b390486a1d83e8b182b45bc8593cc0269e9e2a04426de4124c501361ae9a8e3068a365cd13d5350505eade833b291b88a3c5461be141161d1547d446af5f01212ec83bc89c737b961f2aa7d4090c4a46ad3623d4a68d0665caddb96a58053c48b5b06a722917b1f120dc902110c2edecd1873bbbf82560c155d72d12275ddfdb97ada1ed5d3c5cf5c09f8682c5979b54892cc00964a1bd34914764ce1885032c83fd2748a51f66335a006940f21f14f74853da4ef616df2bc23bae8a284340c535db282b8695db06ad01f0c57c727bc9e92a9aee7759637f8771b85996a471b424a7abef517d34b33454714b96d2c25429a22305538b6187c4e215cef6ea67210b53c383e4996b48a5e7949dd92a9da513e602500b31c47d9c1b1cd3b2ef8f10bdfe525c670978cbdf6ca1e4cccc65c1b618fd18c9893039bd490e26400218b9e137c66fd1a2ce5b19aed7f20be622b9fd7858f04bdabe399e32480725e45789fca40f498a1233f8fd51d620297270879f9fb4ba4f4b01a647ce764ac768bb3cd4b36a8702fa1b2447eec5c0e92959b829ba69610e0100065c93e240306b605bfa6aca3ee20b446935405b81009c3caf42fde9e5b6a58890e272bd9757418d7a9775a4babb2dd172b5f8548d63c666bbfa837efc3455315ee2f637e3cd0af9cc2c2761fb55086023d4c559e1853f9e218f9d530474c5d924a9090f24c9e574ae8f365ff765a9751896fa859625ad9881c1c9c0bd804b187b0694cda94589e4d43c1c6c733bb84e6e48e37aacf4e3cc9d0e3582e34399e333e0e50417d3032430c269221e3a9dec21edbb91f636ac6d96b268cc8418f3beaa4846e165af5e82921d9bad682bf64b0694c7c781d591e850e7e47e9189136ccb7f2636023ccc072aa75550ca1b195cb9560a8f4ae7507f1dded3228f16f149fa2159e9acb74dff27eea5c54ad5848d2866e695e0b34be2df034c49fdf31af012760395342ed79c1374d4efb8a12ee263bce6b9ccccd4d089cdf03ad009a3e15fee4ddaf96ea9bbf676e859928cf8cf33aab931820f28176f4377663440648ab4ed87d06f5055ea85d02be31a67db79864dcecf97b99048dc21ac055a3267484952f0dcdf90b83e2d92e6e3585073f32dd60471021e04d04d28710f538c37f70bc72c079821de1cbc7c2ef6f2ef5cda777e5ce316d4fbda93a6ba05557a7492c6448c6da484b34e9fcce9cb2c0ebefa71855f0ec307e40256beb91ede38658e4a9761d2e4ede7781e4a62f488525764b77e121b699fb71c4e01cb8be3942922ac327f8d1523300113f84438e81668696a84b96432798d0a384702e71150b2b45a5a5c9042a1d52d3f910ec6d2055e0a599d79b4400f4e5cd6f0cb94c68cd607e0e1451dc3e01eb6748858573e1055d1a849e05a2cf67b52e238e2d075731a0e5a798b7f50c1b0e133f33f638e0df2e14657b084197a097b4eabc3d5e000fbbeb885858de5841f886718e200f155494273ae5af01feb3867d6a317c89e4a944ce6a97b6f2c9fb59656b413fd440fbc1a165a8e30e9f96c1c7219685d54d25d273e7387645de931170aae7b84621a19635f08676cdaa55c13dcc3931df46b6d7318980a430248d60061bc4b92230a4582afa4e7f2c2cfa906bd91ad3c8a6b815eb221408b8871345b70d281fbd3e34703f56cd2ba140772735725ab2a0356450002ce651a18d7a9b34a01c7b6c034e7639179920db695df84160fb43520a80a931dd6a64906b03bdc58a69e323b50ea4444aad5c0109ab509710f98039796abc1805039d62312f39bb6ec0ad28fbde39ab1d8844327fb668a90ba70f364419fc65d8ed05d1f9c982ad057de4c83019e381fcb54b5086b008a6d6866665ebcc920610afee51cc4958550507ee96119f529ba821b894c58170e126001c154c9246c21e4741ba0557edf2e402bb925438c4c8bad3a6236170ded47248032c28b2d5879899f05cf797b26a3e1c7555f52e7272c2479deafacb0bd82c7dbac3005c6b6cf84c0f9212584c323fa766d1451193b37fa39c6132b691100b27a51f6e9d13b246a8807069e13c42aba7d1bec405ad4cded96eb735c88864e43296954965eb77f95f71a597c8bab65eff1aa6b8abf411e89d6b196c267d80bb06c64cd94854a08c1ee538d36d6aae9e632227a45cf7955675c8182b200a0f84d8d6e780d6d8584986adc376f12d0934cb0ad007eaf452e32c165ddbf1225ebada125be7a69669e546001f2f614b76c402aa8f7550cdbc7ac90bbb0cd5c339b4130f744757ccd5a6d0d6e284be5141b33327ee0c97fa47fd5410f4e8b528e7c5c633e9c97d731663b243eea36cdd497ccd82b880f37a3499a0d688d909566bbbc0109d23268f2a190f3fd7f84f65ea76c9a2c208a9b8f0d993cb868fd1fad5d47c59cdc5577610d9f702379ec14beb8b9daac6183e2eabf428c0319c88557c8511326feece9e557cd6f70fda9cf2e24d74617dd92118316f00e4094144e91257564cd30d197d2ea94488bb2a1b99aa10d418d769995970a5da5eb9b27ade6e2dd11bde504c529c35a190ed37f88789340d7a7347f53218502e46ed5750847788506b900e8f5bdbe27b8d06ac2c919f457e800bf06caafcde56bf294c40f27fd863581042a6c9363be869e005160b62b4b9bb802b68435e33e87a3295a82f360137c7303123afe644fe413ab008b2e01b245a8084aff0b184e05c373280bb8b6e759fd8e09f4ebfd8ad3b8828c87bc1f1f438650bf4c0895b64627817ffe93943b1cbd823db3d44649e7109491da4d2a29fdb02f84ea22b9066f4f8b8f91f2420ca651c73ede0fc6f49f7f46ac6cfff73be2b3725e021a3846b9b11c92402ba0224a17148a87d5806546548cfb4c59bf56f77c4bcdb06fcb4ba09a7c3d30cf54c706ab95f636a3e10beca58dcf1262b7447fc89d51615275a058c4a8be8db9ccc27753950534058cea65061ac2b4d34b123a6b2efd3b06daa2cf98bacc619e53e45858215f1b2fa218b9443b870ad9f2253cce667bd314adeb6e758d45c1e376375fde1b845b447f504f36d57de962973b6e5256a512d8aa927e0d22c120629327bb8237195565a3c12ceb69509fa35543e589f34ff20004a1e863dbab4f0e811e877161085a5d1e34885f98491b47c0b610f4808623e8da352d92f8726a864443798d9aace10fac11f636e40c84726ca785a8ffc4e1463f05c24c7ca16c5d61cc6f405b14b33d462516c748dc399ab6945fdbea7a3b48f1d024be801767f374c4704f8520b36a9ca5040aa81abe1f92287b7acf105bcfe2297960b0cd4694f1d71b615d65d19f4a7570463d3cc476c58b55068b95018ed1a8299e2ed9cec832dacdc2927c6ce9da8649e95b48d93bd21ff8a42c22061099f6a43cd20a89eaf9a0397f98a944756dce13ece6cb24e90c6123419a7d88a310165755cd7d4bd882d90f6f989a5f38acbc3eee2851b5163debe28b1bd17e212ebf371b6c7ddad206dfcae93259a992f1f994f3c9fe42a8a3ac14469172871ffdbaac8d6e25c47804bf3176fd73ea2b20908bd517f6c78f087847a3844a09d878d72f64fa425012d6a1efe6d5964bd248a2ba684f886883d3c51eace41409ca6e857f2ddfec1809c688a6833fc6cc9ececc112907488ae50c4bdba2e32c39e012ce49d329521d2927cdda6f97714fbb3ac41a45734f8ca0b6be18b2bda1765ae43b4a64715ee4d52aef2ee8611c2521d8cd8e4036dcdcecb571a4c0e8a5c67c6c64ba5a52b89fbfa23736e5083189c7894c3bbf26a9a342851297dc35e4365f0031a18eee8ef2f9e6b80068b6e59192c62b64ed2508c4f24c20234a58d7b2e719adce80aa8a86bacd2fc2c6805bc99e6922452fcc45ce4c65a82f0d151c0525f7697815dfe2afa2e6eac4f6389b7aa6bab20ee4c44472521b756c711342ea6facd6bcadf64c5ea40453c94d16e6752175055237359284fad3ba44458ea3b4865deb3406a930676f48228a670833a2c42d28bd8f132aafc675384721643acc950e75afcdbe9306f6a66f4e9e9620e947b62309830165a9d9ec3b68c121cc92c6e5d7ee12f818296707491957f2c914f4ff27ded392f7974021a97196e23c13bdf6c635b88d32e68447ede8ccae401c765493041cf4791817ef3ff6ea2a7575c8538a3338135d260877d1915ac3a61f031b0e682dca88dcf9ac73775f8a93c9c9d8e5de8545519a93210331aca202c00cc2d1f8d778479769b0dc549a7bd36a5ad8d4ec3d541c168e235b69d50762a6bfc3384699948aca6e8b0a0c91e2cc95e72750224231ab0e71e622ade3e0184c43e47da7607249718a4ef866d96cab94760b37e926ba535da66d8c2c14529c248b6e360f4c9e6631529c1f29c4cc76b43637d44a3541cc4410b0f6f33329012b84d1922fadf2f03340cc143a0a9a8176867f7d792c0e067ec399a918612e34bb973d2f55033e7186476b2da716e758030925892105bd18091103e56d264e1b87fa21cdd4cb749202c29666b82f2fd77061905fa70325b466f597cd4c6e4a768050b3a82aa9d9a1ca630408b0e0ebe78b0b0c010e88d9cf401800db5b641130f913089f45b2acdc3d4e5c213714870ced78388a03ad985cc3f84990faefa6a69a9b79a650c3a01575cc16c2d9fa0b946232a690e99581a453e5927240f3e164abfd01965f9f48ba3fc3105979754d04a67f936455dd3935c751754c8f60d3a94a12f46adcf619b0711e44d52c714ccd6a0f8262f655ac0400c4e2f555cb56a9db83d8dc09b164fece6daa2308bf446b2cff6c94e50ec9c0180b7e8fdd6147d088e1c768380ba14b7d023793c266005de59d247b52418142bac1f98c49204b5ede8f21a50c77c0d8c1a25164b742ced31b992ea5f172a9940097caf82d954498a4534df2321f46a8c8fd67f6db457039940116f519d8d29684d1e9450c88f6f42bcdca438ad68ea913f2fc38a48568ef45272e143269eb97d40b327543bd90c3fdb1b91f0fd2089b06234aaf1e9dcaf7fa51b391adc0646edce285cdc7e2567518b73a274fe289938df719d9fcd178639930d015dcda5f55a90a957fbb2cb82e69553bd20620d196c64c70b6476526308984a2db7f3e8650d47048734421628e807c9411d20653c941b1b4e49b90b28d8ba93ada699d6c419f8cc0039f57421c428d0631b8e0b4bc3c4d58e4f8a1297a602bab95873b6a17819cb483bded9ad2f5b4c6136457d803fd51ea1560be0e023fa6b64c9cc1f4867dc01effbe14be6e8b65e011cf6bbe9d42556d27ec9a7fcfe8c083f53e92fe2146f54e0342b6673384a31ee8638f3058af90656a88e1bc94b978f4a00723ad734871edde1161155505d45cb2a095f14830defe11f35bc4bc1706025007ef546695028ce2d73db8088a3f9fb4f512ae075734264c3d51b8a8480f9e4c7c44f0460ab7b5815d2d9606109fc6bc7551da40d045c4c6af7bf21662322bbef745cd156a425aaa4dfd4c25299e117309f0116231f581b933b945d18fde03958867eb0b2355e9350e88fc40464f582e294f2869297791886cff0c6c0fead39bfed9cbe8379b3ae348e497d6ee475944635f0657d692b43b9f3fd97ef0f4f9abbf6a74511f2c997ec0984791f6223963164453d9e619406db3245ec7e660bb300385695783d136e1c432b9c344720871381daaa85352ff8e3d52bbc97518c2573029b4d54a99ced64d7714df2ab87df5e33755fdba239e864ee59d103eb81d00bf61f96df23c6f8a8bac6ac8c28d0576481b53d5a795828f54e99723621c9a1d71864c8d15460b3fc898eeea8f5bdfcfffbcd4369800775e524ebafc35f826d1c1cd94452df55d2b3c9af6791cbe410980c70e388777af4232e7c5286aa6d3155d5726da4e66d58e85c07ffe61a6b721e631b2d36f70cbfea7cbd2f430272bfcf0dea4ad2ad96ae2d01e791cd87d4ecf6ec0eb204d49022afba367a8bc9b4c0161ba298da7546f7ab9aa3a8315ffd2b7069c1bdbdcffe36c30f1d04bbf1c1d38195f9a88a9ff187ed8082def8446c37ec7b4a6ceb38147c031e050c7ebde4f73bb584d4d709f46ac4f9c86e536320dd0eb1bfb96868699bec0902ee0e58c4963fd0ff9e8a21f5180e73142c0672867b0cf6792052da0e69db83c30cfe4e56f0f78aff3603d584f65680c80004a04b20de344b5f3d50455888196c96f3558150174bedb17a7da28337560564dd83bcb51d73e525fa29da87fde64e6b775b70f3c1d95e88dd05e32c2636f851d1533d0c6d2c638ee1d667753bb6bbff1d8164ccb6a7f64919326d09e5761da9fd4620e9ab672659fa3d4ae6ebfa7e18e4c27b171271a612728a621691b671eed4883181ed8d0ab7a3e70e6e205829e277e3dd672b6c09b21a2226eb447a529571d53f023bb96efac814bad9b852a8c0f482a9b9c6ac1a218a6979e5c65e6a758983e1673422394053fcbe6ad84bd81e3a14e65b4df8f426e41cfd82f35b364aa243ffa224ddc631d63503c12c223200e3e76b9a85944337dfe593a3a3534ee62f4c55dd70d78d2e81d477292b756aaf3c7f9b8fd1cd57297ec7f8e60d180a821c7089a0ff91e7cd8c089b67878351daf0d069be49bf903ecaca641d20aae69c81f4a821782f939b252629395220342eaad7d2f7f1c4d75db8cf026485c12e6037319997fff62c702df35b569610b7f630085376c58708731348499c07c9e008c9da89d26f29b4aa9e6b2396ba069b2186ace6eea527f2194ec91a163e9cdaf6efc6e1d44d2d730d58cfbfe148bbc979c9a60f596ee935abde4ccafeac02a05a899f07bf96cf89948a1f61eaad80b9a7a52d9ab922080e53e41ae74cea6c8578199e92e55452ac2078019eaf2813d18cf4b085b7f0f5038493256add75f8149e0eeeda1a96901fc26493ad25474baa3f0a9e3c0f15dbee31ab8e130c4e1f24640f20e3b97e62157293117f912ce9493a634a71d9e36c212e809ad666a3dc4a6f997c93aae50a78c6d7278ec511bb8cf314ee13b43ac0c73b2bffb8b87b5b316996065ed230be4adc6488526cdacd9f79a55edd85e0783b72cc7fd45022afa2037dcf8f2a9013d5e20d65b74ee44c31c05c1b523daba8038fa1a0af67b1e52a9b4373f7aca89235ccfc25f838e1b221b2c6c657e27d3786fac26f4297a539e6937b88881295cc0b3f7beae9a04438d1b7184589ee105d622f92710a58670a2a6c3690bb85482ea10b7cc14d641294d755b5c51d9aa42b566d31ef3e0d947afe4ba7d6fe656e1811edd6cf6a7d662b3ecae24f2b6c535f2f96870601d1fee1ec24bba1f80c87de0c850838a17b0dbcc6a5136395b2e31ac405ed21fe7950328fd48718424b0dd74e832fd20a7273c5f70baa7c55c9210ce4ce8dc2fc9f5b7c4ecfba9ef0a27b336c559261efc391eb1a571cb756cd914f3c2d4551b40da13d2c1777f51374f3facc41d78da29998896fbf400a86043e1523c0b261feece91a8e474de9126298de7a3278381a0161fb4f26c77f98aeede27f63b11aa0ea6269ee89621f8462d635b28c317b3e4e068bb4feb0a6fdf067706871aee15422803045ea54513c402023902ef375ec33408be23c89e56db0075a18678fbadaaa8527a1f5a227a2651c2df5ce50a2889c361c8ffd6ae273b8227294ca0bb6051770defba687dd0b12ee1bd98f6a22f464e59c68bbfbb62a9d4eb3da95157096c32b0d4275ce8f39940a0038267ec7dbfe343926d2b5ed1b8f314c623b376c0f84d53547f6ada255d6e69481e582cd71ca85da5c8e19532d7d5fd59df6d6350ed65ab5557056e171419572da4d3d334d7136ba7c3e437245e92030f652507537360eb39c6cbbceab51c923207c313bef0d4b2f74dde3d11f719e0882ab559faf83c392519e817b51ab20c6c1bd4c54f866e63d133e5bb9cfb84ce02eaf42fa0433c04449142d4d6d3c2f19b848a87bac2998981ec3089ae2947e0d00c5b3bd0e368719b1f8575b30fcf1107064ea5d827e4267f4a8a7078e0fd907d12def9cc850df4020aa6ecc1bc35fde5e99971aa02fbd384d9fdc3948a4563175ad839aee194f42e9c30c49a96f38f1b68216cae913e9d4830b06f5d2c2158031a50e0769337a0e876b772401fe85ac3421dd0f96494dc97c4342791cefdf280091732b40fa42eca1fbe60e45f5eaa00db4d1d55d4e1091aeab6f324b2b91a9522be91c5c355981001bbc5a5bd5e301d7bc49b3c8b407b0025c494a717e6e633dfac3b410828a5a2d843f0d50ea16698c61260e081c7a34df4319f184b90c11a330e2c843bb6eee9ca76ac5593795989e47a67e5dd858c0b93a2cc7014d1d17c7e394d13675194d299574bcdc72ea499fc147b083df6049451a82657575c299ecb073f41643bd73681b11836e888a24e428297b360c85b69999bc90801ba053d3c1f34e5908d22887d3c350496af3a18b8d975dc4a66ea72d0a1c07caf89200b27805286e07001369c0b9d8b4f708ab2ecd725b803b31ef7c749839802222c1b7b7499f4360033219454f087fa9a526bf037361412b358a6bd9e07fe29bcc29478b1d66be9bf5dcaf76e9899bae27724c90b8a16d6b88e3a625ba8b73f4f610c774f9b21a9e5a6640130739f49f21c894a10fd75fd0322a20c669331c322f4d582c93de7bf75cfa7dbf8a72d2f5436c322090521477f0f646898c47a3b801d4b7eff413a5b4c2f224bb478a0cf3aa3947ea21798174a136f291fa0bc5b7003fee60d7e12e11c1abd37283d10de6dc47103deeba9d2a89018fa9d5b694323adb2b7378ff59022f05b51f729f216ae55fb35782a4562bb99e99a2c4a36a543af23c8a36952933432379b19b9268665b79b8403193115740b1e2318b74450856e740bc08ee7c8f656dfd44696aed3aedaf716b2cf5d77c2e197a622e11815b2d03cc2996e094b796b81363e97a780960654c9d5266b21437e62e7ba8175a18003a9d10d511c65bcbae53c7b40e539706494c2f4930060353f78967c27261129d302b507a70e2c56285e88538271b70527e84a807cf9a9562f1e267f23d54d1938cc12d14c10375ec67a34b809d3f638bb1e156a1bbcc0a9c1f05673d3274b37b52d9a2ab13492365adb9109c27fd4b8bca8d2238cae0dc8c6f4baf8fa0fbcc701ddd4eb03c961063314b26410649b2db820a526563b38be6c5822c50b31126bce4a1ebe005c1e71110f62a09ea877d63115588bb715af61dcba5599f13df81986a1cec0abef5008986b9a86f97610f6875721b4f2b6a5b7b9e0d614ad60db2f4ec2aae4c312c0f8225163279b61f84f7a8cd40415f71916eae3abb8a1ff0ed64b2f866f3125f1c0ed738988c6dd2875addc1e1bb99b79c1bf6590dc6f367a438c84b7bf8f7e4cfae617c892d36369c6f924091879ef5ee387a28ba7f86b322c2293361edb85448e57042b8429b90349e1b4c532086c46663e1be1c3db1f2d1e2bb47ad65c7cfc6ee2928a052cf1d1acce38ba3a294c708d0fd4c79f2c4d4aedf5f2e0449417112431a2840338a533c21d1f2862b3d02857ceb9f24a9d861225bf2a8c4d55b8e392b980ddc19b8c38bc78e52b87e0f85cd6aded3e969f75cd1631b4c8f4729fab9744d48b4d4602dacddd316452d7b8a3cbd97861c8a64f2f3df09583984d6cba3d8b661c1fb86c606d98d82373bab9747c521b2d3c35d5e617211328486b214682664ad196f7793a1d1f0371905b3b907fa9c7202df1c88b9693939289930373d0ffa610cb036e8338a92fc6deb14e4f6d559e9a5d9f30e23a3e42e345263a85096521cef7219c989e31b2480e7b9941b28d4562a99a2dfb39af6bfcd1daa8831f11878d2d9e0b570b74120714c73f670d1854caa4b33ad355b55bd7ea996dcd110347a10e6105e7609046a94dd3a51635c266cb53454d2ca5ea5bf52581deeb38a4d6bf9c40635d2052bba9ba4af1a4bd178f7e4c11ed46c8246863cb84f17c0662ad9578bdf6f0d8ce7e37fb118a27b9ac96e9762584e60ec5dae132b36e7568a1f458d6de9341a715b6a8f0a14825c42b615b678714a0c5e1e61292d3945e6be3097dbd6d0513b458b522fb66d075f02d4a67a83230359333703e209ceea3c780f02ce411a7085fd1eb4dddb4c3b364330d425b2d7cdbb4a3f414171293c3e3ad7fdbb7f3bc273fa873eced09eb216d5edcd24ca716fd75035e88832bcc77c5d1cd21981684c90c417c5fe707a249202184403cd6a9ac275329d687bd0e6798b8782c0c340a8440c30a82e6987cefd689778ebd955f6cfc1f12b10748b5394f8c185a24286afc023250ebc0405299652ae4ed9c0cb4ef34b60b01ee304c42f8c9c910c461d47ae512431aeede86d5a5b769e371833e348f58f539397803a60f7b35837fc923e8163c2d55af9d0989d3a06b92a54240cf97d445075491b775a327a7417ca4b233844792cb985c48c6232a9b7cbbbc6f79d89a78605fc850dc88c84bc1b356708a3ce940b7b67bd133c10e31edbe0afb33911ddcc43d7f4f932f2662741a7eb9fb40775150cc402737607fd695656443595cba6ae2d51840571cd553685d033dc49a63bb19a690f6b22698d4a98e4ba716ed35f3acad58b889f8969412d004d487894b776313cd0caca1826ba1afa24f50d0dc7c7f1a84ad0a2cc5fc7b961c9dd5645fc11c13b3c819937417987fbacdc5bb5f0bda544e348fc981f39b289f7a4cb814dacce76791b55d547c4891477d128b8171d9865daba0bde6b52a3901987a85bebe97e06f7b14a564db638411dacc4490f68037829443b6c499b34a358d99ca150481d118d06cc3bda9eca909449c86468df0009e92f9ee4e49c0200797940f01d96ef032d6e49ede00d116f7976728381992fbefc5ea915b0d6b0ba509c390f69390b742e14b2b9b35aa2a4af027938b924bf99ac17ac7752651d7528f62a05d264fc4490a902675b18a2190c46b48e300f88ad98c10015e6c03ac4f3a6f1b66c1c55ea021cec8c3a46ac5e129d857deb62f2783cae6e6e282eafffbd6e79793070ff9ad8332dd3157a8332b96b3e1f6db4f66c1d82fd4f7b1cc694f683b222f3a0ea5c036f7c0c239222798df00c1e871d21087ff698119240eb6021582fe947cb73c99e60579ac2dc036e2cc21754803eef50bd1ae1ef10da70b2afbbf5ed4209990ea68c214c44bc4c2132e2a585cd85f7279716488177fc5ce09422fa0590713f88234f44b1afa505e17a6997bb5acafd5d148fe93dc30627462a7b3a044192b240f59a1e4e8437ffe0fc2fca62934ce8754acff9352532667e8d43090be840e9b59a10098b7417e379c796a14df471aa57fe8914f37bde975f60fc221930ebce4a6965c532dd7fb47a4f81a671f39aa9cfa94b63f7e3676bb5f2b0e58df13c41f576c8fc7cecf4ef3af8e8f5f84ef27ff3257dbd8198051995955735242dc4b6818cd42a3b243d8f82c0a4cd8b16dc82c91c9a3707aa1490e133d0b41c6017f86af686962f04903cc2abdaf589776996f94e8a16cda28720f4b2dda50a41899e15596b9584f85f5d05c5ad010b16a4b4fc4429e7e9b98d2be3b9d33b571a56df05b91509a01d22f31baa9f8cead38d93c8eca8414bf1553df074eb59565c0e1408b17572294e1240fc286134a7ddfedfbb1e521bb22d8a8efec137d3bfb060b4e9681a32920fb1454acb98d97b07a8e936c73c131251e805e2c693e383fc6365afa437f5675e5d7e6d592480c9dc40fd8c48df3042dadc91c4db6b74b28215eb104f4feee577515cbb64aad2597b89d853bbffc1c547d50e5ce7dc9d54abc78f74a27d5073715fefb085cf241343edb25de690cc3d40764b9e4dfc7dfea89a64746f01338eb813ba037ddc9e9ed4df7301bd3085f391ef897e6ef256592bd3853f883dbfd4f274b5b3e1c678508a96444fb890899944434a9ccb409ac681a6573f020710a39cfee3ef809befaf6ce542766faf2ca0664b452319bca00393d650aa5c94ca3ba1daa37983ba222a619a299083633c811aa5ef919dc743848f2bc065e4f7630ac17600828476eee010e5391ccd0537a0e22cb9c09630c0cb5804c597843dcd8c3c6bec69d6618c4d882d28d031274f5803cea4cd8b6968c9da1b3852875b462c35a11a30957352413a3f9f845895bcf34b7035a2d97d47d8e64a6a3caf36097809605352de65658941a59be29739acaea130b65f653c58a94c23e6e78188f532cc3218720a34ac5683389759fc402629eb0f4513f48caf6cc6e0161af0ae39649af65504edec70d05a80b1407e22ca812b0ad53a556154710cab80d56f06ee518d7b584356089ea8598a5de919999ebc41f37d85e59f7f51f61da21165d6eb5913bcf69ee4265ea39727aa1187b02ee85a1e2340d393105f0bf4a8a8939650fa04aa786ea99d0c29ab65902bc8d76e454091c1b00692507c340af76ac09a3fa18e15e9dc2f09df95eacc66f51408ede7c51707dccade0e2e05081abfa7f03fabd7b50d0d3c06d196be72a3dc4cd0d4298dc28558658f9a814ac937697a38e7ea06e8f295192b109864b3b20749002f83adbd039432129ff0ca6e68660d2233840f26e54628eead398704db95beb8f7352318a72e4d552ef5c091f44476255501ba820651550ce54aeecba0a03065c105be408f128fe68b82640195aea9c840feecafc573e18e64c95a927df91cd99ea705414952054a1f038b8155948f3bedb0d7ba59762c289a63326bd01912c43f2269e7b74d05c5e795a42948e120ccd12c8403488f9b5fb953c0466bf0f043faf49ee8f411eb821fb0dcd92e308e8b2556f19c0084ed0a638aa80699a300b52950f53d54192d3ae32d831211fba3bbd8b61569429a678b83685ce69b498d85add6232e6baf778134520090a03d7f90de8265e31493e80c2e53711a1f68a8b6354b393da613f2c0e2ee5e7c0ef0630c699408ffe14b95ee222ecb8d036a3dd8d96a5e2609eb0030a5907025107fe50a56a1db4670ea4750ffccadb28f6eff7cc5c3121ba61f97b10d270fde0503bdd6dfcb83b1e1ed9032aa12e1989da03cf56f680ac47caaa105368a8241dbe1992c455c137a8f0b44aba150894b2fa9d32b97e855b0319cb5e49c07b750b04bd1dd355089b80bc032d63926e72dba6a590cb5df165eb64eda20a6818dcc655a024c681e8fd1b6f207cd71ebbe5504e1201d076e14e346b55214b05b0d9870759e22ac6cb8a87d869bb46a36cd913fb851937411954ba0be859a61f228c08e29254c0004d10f9f4f2af8d8c94546c6fa0e136b40afdf34dabd058b4a30592418d661536b08d0db3fe45ba5b82df30ee95bdbf901e0d1b5bc4aa6032226f12c262125a60bcfdf4bce2acad71fb996b1216577dd1a7028e049707bb085ff9fedb147f356287290c7eac2b563e8c8d46a98d20d646e691af01b5d8aa6f6c2314e9dab2eab14fd18bcf536a114a332ee59f1e345c6825f5eae2d71f4f97beea6b76ad06962db7ec339591202be8f97bed327857f3ad21ee25c77c22d9577e1150844a89249a131b687c2a389a308dae42991469a882cb0f0cc472cb969e5207d09585770ceca86c41ebd00318a928a1b27ecf70c3b2a00868d96463035bb431b7b306e37226a5fa485abbdab43056205d7da3a10ced95f12c2c63eb4e5c94051012c5a8539ea7c1e10553fb2ecaf1fa70d4e5f0c36918eddc1317dfc6a50173df226e1ceff93f85396bb434134b6d8efea336166957faf62948fb71185077ce5834dc1e80040cff2f6eaf723520ae0e959fbcd4a9426b845f10f309408739b76d8af3364c656fa021de301d4d2f9f7ef7247670f6fdd052c3578ba20d03cee9232a8625125df831cf73c0d8917c7c1bb88a4bb106b2f8c69f33d97edf593aa9bd987aa82c74f1e1a0c29acde05b6512d179d1ed00969ec9cbe1485118d2f385e8366fd154233f2c41826906e6e1da734e104c61c881519081c7866559d403400d8dddd455c6ba01672fc052ec39060d3da76ca31e799c5f73b8663befd58566021fae1b686d859e6ad92c5faff0e0720642555f9fc05b2a88013325e99ebd8ac54517c9e510a62fb64427cac1a0400b2fb5a50a33bf3e6852e539757102fc05189bd997969abe48653867a21a203852c8b93cef826f85f64e93fe3da936dc8100156524df49d0c1730f3685e8cdc1e898b812f30c57dac4f473f6ffb83dafe9dfa70b4175037f889f624618aea102c42a7d3325726a5328813ca55cf7cdaacd3b02f182b8fee70b05021334226013038f35ab01a21e0d2ee709b55f4d087b641f68c6acb0e469c23584a8045269d728e8b6e5412ae1c409d8a036c12a6548d6c69744d1d9931a95dd5e188e02541ed7b9d3636528529ea3a1974bc3fa786b9853ce7a0f3fdc0a4ad956308723710a9acc389b6452264593594a02165aa0ef7835465cf2a61502017c93dc3b330b2ee15363f80281852eb1f428173c6d8616213e396d385470dfc0198d956c2712a73335b4bc88eb50eeb848ad15c430197aeec6ba2101a47633b615d245bc1970dbd5942e22cf9b60376d4d67b2d5ccc417e3950fae363a2d3a1813ccb84ab20183ae45830194c52f8a6c54406f4c0a47a455d30b9f72d40bdefc7437dab4198c253e15181342c0326b28572c675da1e73e6929da21288e767c2e44be2e4f5c5c8f80fdad9a0e354fb4e96962d88b9a859138e690fd3ec6d49ee8263391a064017c927a7db8ab8c9b8e6abeaa14c52ef270f0cbc777da6c38722f0a88aa151b5e22cbe70783289f91b1a428e544f054b6d23a2d69dd0b216d884432ffbeb49170df7b1869e7c1bb5effdd4d871093efb02cf048877402b6ca9ac8f51d7ca2efe4f6cd823b42b1b7073967f620fae9ee035410e3c37dfe472bfad0d1445db43595b76ec43346618bd1453dfa3d2063d0f647bbf9f869d79bf74956c5c9d96d00e571b20be9213599cb85dc2ab453f4a0f5ae416775630105026424bfc88806098126abfd518cf0ab1901b0c06925497b7fc7ce6753f28f5d5545a535b1b724286b13531b23347bb069f5feaad2fb4309a66e2a3a07f3f194d00fd98be183e563b75f0e0a0265054446941946a49f4fabb656d666aef8d8006e4d94920d8a24dc4f79aa14b73ce78ce79cafa123b9a30edbad94fd980a08a515720e4c716abc359a441d5627898b76139e1348e1e4991c4a5e1cd9526d1ca1399c4d637f325668712d5c33b1e81f1fa1a54872ff4fc4147b1a8e845437baba36815c1ea496edad776402f5219a295858307294ecc478b3dcb64bb8794318b991e89b90d9042779da3a1e0bb836a0a6d42bad71e534a3c2928426dd7d6f5ad718bb4431b938b7663e2e8984e7b5bc340030c7006a38b0fcdea219a67e25497e32dd8ef30310e55c843f1703df76d69b4f0d3c1cd7789191a7369ae1fedd88e7d034009af172433d05dbce1cb9c55a000ad1188379f43bad33b15b674115e45feb5b790d8644cb65777fe80b292aa0f64a97d244bbe61c1b0de72050a54329cf4e05e60364248408b774841834cf739dbc04d5031a8047168bdd43a4370edd8171c969e403cd024a1478e550525391fb2028802b180b7e765587c8a7b0287177ba8b2ad80dcbcec8e0774d1637940e38715dfd2abb6d71d326ac7b1d95341b2cbec17d807f12527a9ffda456ae1b93e47be1290baeef2b3840259a104c690fec1d064ee4027b0cd95e2d5bd9429f966c3d8b26279154f14eb1036c87b776a289c8396cc1029eb3a5ebe9363492525c70ba8992fb3c1d57c6477ffa03dbf9ec84c69b0c0f363d6363e610d4d39fc6c2100d85b110bcb61c8c2952640360b4b92d9e960fe536342b3c0b958502197e779bd6edc0a07f50fb6547d36a932fca4efd8244326368077d65d8b0a64e8cfa36bf5126970a4cf0c8c924e98572617bdb25932de74cc43a359e3158f4939b6ed21bf2e78dab4113d4fecf5d2383841a2cb35c59d30f37c58dfd9a1e7af5761e7aa7f2dfb235fc11b5f8865af9ba034571f33c1b9d6b3e2d2d99447751c112b8bcdefaf314d71327d3558c30802049f767ca5d0c5cb1027949e5f13cd7edbaa4e558fcef9afbe3aa43efacdaaa6b295b4eb3d973ecff25b080334ffec52aa8d97a2530b5f7fddd6c08de4be36d9ccaf86c5f4643a19003faedabfd7843cd7daf34d1f402d3da8304700e5fb062ac0ff4741dee9e0eec990c307bb44eeb172a84cca725156e88b30ae91b506c58e0d0afae1982eb4d5122afe272e78423a83d47a28b652df91ad875a90bb2bee1771ed3f23bf462ef3fc4146895cc883255bba586d266d2d8584be9f8e6ca54f4996f4df1c822897be02c8989594fd611415d22ab94af9ad18f5ce9f11b3c3bf1f52ddd8270be9143faae00f2d0deb909d838d339df20cad108a0790acdb26f5c70682ba841a0c7185cbf002fd5da4530e8c9a746fc0ac47e0127a64e9059fdb47a5c5aa9d55283a3cd8fdcf38b7c89b113d01be70cd13e7f113ce9d1dae25bd174aa98ff561e19a0e258537bbcf5ed48301d61baef10333abda8e3bc8f8691b45c91f12bb310304e4c892e4d8369c0c142b9fc874b47131f26b5997cf952bde86f5e2868ba0400e056d1cdd6e692fc533b6148d1915ca9de008ab97ec9a068ace97dfd081a0942d6f50bc639e54db8c1bb79f736322cfbe6c44902b74a36930545403fd74a883b7ac7d695858c09f015568f11c3d2248c44f91ea59be1ec6d4e1b009efba1c4498de0990801756e70638fc9942318680e0ca8fd3b66bdd8f0bd9c2fe3ab3ed6c4d34bad0be40137370bf02d0f85901ed687ae106da2afb619dd8f75930cfb1b9138ba4e4bbc5eb7cc73da6892e8d481c406f0dce25889296a1154af3c86ad656f5ad534b9d20dc727642e85667877b55ae7687eafcc72a929dd09d662f040a47cf3b95ea0782e17c51e08897a4799d46fc3c7047ab3df47750754153969e8e266095b0bb1c159655a95d40d6bd68ff955dec31012d76583c6d4e5d4126901ac11045af976945830923a4a074a33f2acb4c9a6f5b6c4921558af31e232c31d436bdabe82cd3f0a4ec0ddbcbe9beb697013d554701f33c031087167c43156775b8e4da66d3db73d70955300aad8b00fb1e088326de5b7bd3721724b29534a29280911091e09aabac3be5c17e7fc48f341a263f062dea76a1673f215424e7240cf06668ee23e3978b53a3630038b2354d9c202325aacc4195e9c784119461411841b4024146389979458480644f182b8aa903b604c8431484c431fbb070548653e4739fbe9743a9d86ecdb98fb79ff981da2a83073bbe423a9ee8f351d2a5d5dba94d2fde27d1e90ca94ddacc57eefbdecbd2d6ab852a8e17fdeb7bfad9b1a73f006731d6ee1c7ccfc38c893c10f98c4fc7bdd5aa2a238f4bbf7ada529bad2670ec257e70fa2ab1389b08497319e81605e8d40a7cd9e147d07f49dfa69c9c9af5e91b698f298d230156969e8db4fdae387ca0ffd55f92e3ebca18a172eaf19e6ca75f101f0b322ef33c736a21f42f39963ac83c79a8ac75e0e1e83498fc5ec068fd27870f0618d611863d8621816310cc3b00b85990e88c9638eb424c5a3364cce7877729ed1ee724dbd3b799c811a8f93b73b00021298e13a4d07a656a618fd4308a163de417f501dfa0b72f2f620b03b69303c0d022794b29fba0e011010f79daaaa02611bb550d5002619204396ba8ce0336ae46b9ce72e870143e0e1d99f9caab446134aaa991e6ab0060db20833141404060a8008a2c265872e2d0642d345c6c5cc4982b87e3084115c65082d668062f0040927b428a3e84808403f30f102841228b0ec0004dfa52fcffee454b1ab7c65e3156909084b45c801d358c454242e8a1e890b222e8652412de98aed906c35914f2f231f83fcf03f8e84d779afb9cb9461a189ec6b9932adcb757ff89bb8d0ba78a59e04074c7b07cad32c30046508cf07f969a096f4a7bd7031d72d74b90949d701ca109e574540dc1d40ff5a6feb6ce81e097fc5b0abacd308567be114fd6d445e78ab2bb82fc0675e665638d35c11feeb11b99c880ab0ff1d79e1fdfadb5ee0a05f8ec8e53ff2577140e8cbbe508af05fded0396d7e3b609743c72e6f88ba7c986586f2a79a2995a8ede672c9c1154cf54bdf0d561b4e9579b954f10a3a0eafb44d7140879d3f74d58d3270c9544fa918e5d7a6d29e13a7521cea282ddfa9f0d0f57472d475a55057dca463ae272eca2b42254c71544ac714aa74f5e56581a6f393afa603f3d306975c7c49ef9ded81b2a2473078b8f81884e8daace940a96ea84ea793a6e3e4a793bebeec3169b6d581aa107d66db2a74157d87a2ea35d754539a277f9ae4209230457c4ac7f86cc631ac47b7bc9a19e5ecf1e4d8d5af485a8e3e6a5712449ff9d5af4949f4f057c6add25eb94e27eff6cac94f27ce4707fcdad66d0f5e8f7c72a80f639bfed58d6532d324709d4e1c0d7b459b681082424df96ec990198d0a30dd98f58ee699b6a74d7f6df0a4c862e82f2a36c511579d838979e79283464ccc59932e939a20f90bf59ad4e4863fb9bc300c730cc3300cc374602e37b8e4240405a56d4243de9a711dbca59db6939cd9b485bd6c6b92b9a6f98cc665dcaa59d1593b714b3457bcf21d989cc1e6f039f9ce85d3693f3296058b8e299f30943fb951ca13e6acf1c0a45c7e4be44cc597b5f634d5744877b882ce4e38bc624d47eb8881e697b6e14099d23bc736cd4fdbe5518aa7618e61dbc9df0db010f3999f1c636d022d2cc250963e263fe356bc8a7eda70bc4f9b74d4b6d2d2f4faa72b5b8d87e39ca04c95bb4a7543625bd1772a4829e58bd811734e8650357995aa32f725ebe87b2f83ef71433b4e50ed0127fcf5af07d536ebbcd5bfcbb78cf94eb978eeebb5067d351dd7e2f00ac75da57aef82ee4d6677e36f703839bc62b92d099fcc2ef58cc3ef317cd76bd8a9a5cb057d5d572f0bd1d551abf34eba365e5d3700af0478f599ddd16bdb25b35da53ddd9eb626984bcc55db4669963e1c60305e9a0e94a603d374f46ad66f667de37505b5627d6d3360196bebd86eebe468761be4647db71978b551240e3067ae00500b5ba5796f66371a9ac51c105e4121a6be74b99c9174c69162d4894b498ee786bd4155c84157bc83768b8416227e78a83479787543c7f3ecdb5aa27cb7523cab58667077af2d1f7d852e5fb8d773e818dccbf7df15172e0655db1b36480693a68b97b05b5c3b5310f68bda034cd882553754704baa049e736b581fab216b5adbc1fde54f7bf4d7a594ac6209616f3293184ae371820f9fb4079ce010a6f057b70f250f55034218a3632d25d78d202707453540da32f4bac509ed15698b930cfa30314c8b318b61da35b1abbbae4dd3361b7e6125687e617e6d26a41cd3fcaa717f1b1b1b9b1a1b134c30c135bfb60ec85f29c75cab71ad4673cc35bf5cdb3a20af39b675401eabf14e7313b81bcd6d1ce3b41a27c16b2e0c7b50d4d8406163b3753f30c3fcaac1ae19ed72f513b6eee27ee067da8c0d28f8911b0e4ae650ba8cc6311a282e1a9a6bb31180000210000000e0c68d9b1b145038e10495ca04136cd828a184548a04126c6c6a6ae6a4a19999d1b42cc33014ea742aa104aea6c6bbab47e534f04b701368b82ebe8dcfb86a53c531c375f12f4f6d7fb99a50c2a68a23c575ef6d5c55a7c66d6cfcf20bc8773f30c80f7c1b5c917d12fcaaa9f1d66c780dd779cde529aef3bfba1a4e27f780cc777f46dbf41d080a355bbc276c78b6d928e10495d770a9d46759365397d7788d5f279c70829094d7a46cae948dd7b88d5f5f63c209a95409ef89d47ba2041b365426a4de133636b80ee86d78cd66534209291b1450d8945935a9140a426c04b1912ac1860d1bdbf4d66cf8e4bad4dbb051e329ae4ba55242504d485b9656082d9a5e91b478f29d0b18cd3e6f517a45da72f4067845d282caaf52609d159801d2d97924e370beba21c34ce85d77e3c6c302d387da50db7e165fb220f33d8f39f6b6b527dd86d732f6c775d869d3bc3ba09741962bb2edfb3eaae735ae43053d74242d90609cadc65c07f42cbda5afa68a636f9e4b3f71485a2cf9e79ab1a6396ac35cdb5ee87914c67da0a7933ba43ff7e9d574a01ce33a7eacd31ceb50fe23b3ad938e6d97abf60411d2f328ee033d8f72144704e53ff25571b49f5cb6772b177ad1fd9382ded027afba69b2432824fb1b2ad6e11484300710c29da92d3eb6f600131e72dd083e72cf1b461bad6f0bc94f66121295d0aab7f1f4a91a0f1d7b4f53b323ce4c3782879c5f11591f773f77acfbe97baa0998e946f0bc5d549b5180c1751ce79c15208cb088f832b8452033d0d0308857dd1fb70d5a2166350632c7b82350a4671a05b435e3be741b502decda4e2fb9ce87b6b0d52450f3cfa176bd60f3cf00589451467f7ae299c709f10a955d3b03b2fdbd5596effbf01afb74771930e4ad93ab1196ca8093abce40c3eeeec69b18170ba316cad8c95aeb3dd661e09d19c3fa0f19c668fa7520347084cacd40c3eeeeee6a2bdb0d8241d01d5a24ea0b442fb8ed2fa0af501d3c6b9527a24ec32537081414e4ab9c2b939ca4fbe908fdfc704367159955d3d643d53b3fcc62ff4126aa56aaf7021acd1e0badd0b3bf5943b3d87586768cec0fa98adef989f3696eb92dba4fefa878a50414e4d33bdabaa2ea9d1fde5c8a9bbc5205acc74d6e44f086d6565555c90355a1abaaaa425555857a3a22e88a2ea1943edaaddaadddddaaaa8a011105a638dd2e0b34af72a5642b41632407f1d036be7d87b4e77d6f12df5bc4f7aae0d787b815da6d837628531f5ef17cfbd31592aac39c0386e9a86cc6b59705fee15bfa6a3a88488f8ed95c3736dc0e7e9b9a3d5aeaacd5be4538f89e3a3448644c8932c5a6e6badc877d4c8e86ebe60ca7715ce7ab5feec475abef5429470f5e754fc829c92620bc228c115eab2984a7595ae62337437cb8520ae2f90d6a143b8a33f223c5ec548cced4c980fcb00e8f11d541f9ee0cac03395436e397c6659b90998da759288e065eb1f7e02e8e5b0efeeb9c345a8ce703fba87d7acc39678f1ced3e397aecfaf4c87256e0e996bacb50e21f17a723aeebbadac83c713e381878d5e30891e3227cbd234f93c0f3f702fcf7972fb7b30e0eeb4414ea0625d8bae2a82084509799795d99873e740821ec26b1e366c1317adce17e5d3af43fbe1ef3e3ebbd6bb517e03eed191d11ca7e2de4e974da54e7e3fe81b65a7253653df9ae0703b37a470f5ec5227039bd139e5f5783d11c9763e4b89ca352d6735796b278afa9ace7fa3c6e529fae74f5ba1fae9a1fb92146b28fef2337e4c8c3b70553397ccb6ff72dbf0b3691db0d293965ad6f8029ce748875732ffb88a1fd473b90f619da85b4d7f0d3ddcbdddd3ca74ae5be5a754e8e0fefd1ac4e323bbf02b994e3b4cfdee966028361ca174689f3300a1d84cea303399672a694157d5a81fc3a0f84cee3e33f66763c3c57f88de95658e115a90c2654898c7cac136560963bc47cc0d0351b9b6763dc3e0af3ec34d4f0e4d97f9ee862228159d2cba35816092e9e1d1e315f91c004fd116697031fd7e4f2d95cc6412f465230f8048faaaae6ac5c9fe8eed656a59794a598f931f39583939a99aed0420e4f96ba314611475af3e0134f68115ce0258d1da400892108b13551850f48c06c31451a2b84226264de272f80315830328872658c22f6b94fc41823c91534d448c153c3076c0b267d5468c831be90085003aa810228a50a069f1e181a62a8311130060e6588f1c5698b7792f38f52eae504d35cee628ceff52a5fc1a99965df65057845128308317a78020d662632880046d312137b5404a6e28bbd50dd0d1198994f582089b1c3cce19959a65250314f317060a42f64be5b3d30b0cc2e2b50f12a3539788339e76a31b157a42e44ff78f5aea44bd02b52d39227c02b52d393efc13acb8457ab380b638af96dd0751b62c33f7f43f69fc375e51f66b746eba46fac91d11a298e751fdeb7d401dd69818186b2f4d767d09e7defb1ad1fd468dac18c6f81514659fa9d8af19a88f8f7cf33ed511991e389f9e73247df609d9fb7ba51d4a58abc670ff2dca78623b37529e1d7ed638df6a88b96df45b2f257084ab748b4f86b1382d2593c6f3fef21bca1ba826697939363858c75c0f822bf391f2366d7c2afb790e36a6a45c9c8c0de86bded05ec6dd8db5ec0de86bded0584dd673c2ec256737296d9871237d459d7c781f19cfded1ee13db80a5fab2a9f66fe31c3207c30dbd753d8dd1e3ac2c971f73566fbc5a9aaaa5ac1ce52e2a870b4e7fdf3550abc71ddaa8567e408cf0a2b64cc7a3e31e6325ebd15cc4e3ece8a59cfe512d39bd5fde476dada5127d4e9ad78f57672dcb4f585702bf0f0ce93cc7c44034c972e954aafa4a07a1e1e9e6765b6773ccf17a757f499596f8ac92c250fa64f0bbc7acea35778fea95431f480c1077b4b4160ea53ccf8cf53fe1c877558677bebf75d3db8ffecace9d8521b0655f505e13591d712b3e3f937c5ecb2f899e2781ea1985df6ea06d09e22f3d99fb39405f6e72bed81fe3c851cde91d17bf3c9c1bdcc331d0faf5e0b5b8e1ecc7a0e37f858cc76e6619d1c30dc70de91dc6396fa63874c4c7febd96a3af6c62b921731f87d766666d6301eeb3ca4d9a996025ce9a14c5a1ec98b2b332c0bd0d93db02cc41374f5b72c48578fbb9339e6ddcc7636d63b00702b293bf9267cb77a13769ce03ba0261fe84204649ebf2b620366e6dd5d4ec04c07e4e464e5174b0f7ea700f343f40a18581ee5fc3c9ac9436d8786e9b243bfa0f2eb5c202184237c6103e5f244b3502ba2501ef440b3d8420846f86d0d02403510c202b858400a66ba17729084f2c00a15ac5885f975ae0b0128b12a03051458514384005ea055826ef89f2bf517d781208b9439335d0d52d40ca123845f1ed8975f8fda0e09842b5ea8994e044a3c2e22ec8842fc6a3ca0113b7a8a03a2925f07c81ffcd63fa4bca49497bc2e29afeb92525e72ad98e9de010dc5afb3e6009aa35f97da0b3dbf8e9de0b6aac2c9e5d6aac2c957bd03957572bee142097f62edaf2050065eeda33820d2cb841c11ff935ffeaaf138c00fd1326bfc76dc9f5c3a767abc659e4891f828171ec5ade2b8fc65d001402fe8d16f6b2ff04b0e880cbcfac12b9e418cd909e11eace32387575b0a2b30279e6901b6ab36453c5f91bc186a4c8d07066a44a0c91263bc2412d313d52b1213954fc9243ec5ab45622a7a5d15d4c53c35ae9031f288ab97ae9bfe16ead77505514f7d90cb23508c58f44eb9ce86e79a6011e5ba75475e3b220de1093776e423d769ea31a05610b5509e7ad59e142e6f9694337bbbbb45d74ec8aa8190dd0a65fecbd9defcf7fa79ea5d3142f8ccf822e8a333ffb6d1fc82e85b46efb6dbb66dee8697b9cead52cdb9dcccfc63d41e227af445f8872c99672245e443ff89cf1f37fe1e7ebe901ba265ca3ce3c01d4c1d489eac0afadd7e3f8d31c608e1855df1f278c50821112e26f60a185c68f4d590ef41850a55dff30861e40197a3c0de2fc71acc2efb55f67d5631e7cccbbb459061a85ee7bb92cb54bf505552b68af90b966926c4eca6770fb7ecb1859bfcdefa7593dfdabcca53ec0af985485385a47e1f845048ea95016333ece870f9bd77cd8710420cc21cbc8adc7b2fee3bc2022e65458559ba861566e9773c4865b87c271fa98c131d3e0cb10eaa464712238c9958797d1d489cb08e5af9f2e4f5db7dbab71dfb3a7c10e2950e1f8458e7a67dbf393e4222032686116b7041785dd745c41284ef4108e1d3d7c42b7dfae5bb1ff8c583906989bf78909bc0d8852be5a3bc72dd4fc318211398724d685ca9489593d9a956f8e73ebd9365abc77537febd48e5a3ebdb899a8e48e5a147a7f107a73fdd7230ebc51eac93b94f0ecce1c9318dbbe9a1af390f7d8dbb99f1209a5f3ec35d2e1de8e3d665ddce6cddfbe81a966d408f6d42b247497949b33d66610cb160ff79efbdf79eabde1182fea1fea150f04979bdf8e07b6f677a1598e274d0e89f630fcc62bdab3deaa3389e63dda330b24786528bce7513afded16a8a47513931fa57d4ac57f41dd42c2ea6fe7a3759090c5fa13b3ccd7a7e94c59521fc53ee727214ca15c3a2cb2b4657d7b80fcc0b93e130d559a977de52b39e3371137f6130ac83f2e72c86c744df23d68962e5b94c21051c9c6ea37060fe418e978a30a1f7c662989b361ed3ace7a88dc1f0eaf969e32fccb4c45dcf31189d9785c8b1d232a13d90632c14c7f3c74b50e6b2d2b2c037fcfba13866b39ec72666bb2e0b3641af3e9b64eec36f4aefb052679c4ff9e74fe967bc7b57fe266adb4d8c71cb7cbf49c61db161e6b5d27306f3967e288ee7aae799bf30282ce3364ab39e87793b0522f1ef229aecacc4ab072167612d526614d6d19b8c955ee9396b79ce4bcfb9094a4e3dc9a4aa6613ae31b9058e8757eb9dc554ef56602e829966c426725e6f6436c65ce9ea54f96a0b754c5bda827ba47d9d683f1e7b346a96fa4f95afe0c6d32cdeb4b59bb694a7f0913a5f5187634c7f755775ff2872aad49d95961b08987260aeeec014a7bbb05d67309e4f5aaad034a72ccca05fa2f99e32df51b3d8a59c53a5824b580766e1d503b32cb0431e7407565138a43d8f83478aa308ba0365a02cf69c7f4d2cd60b430209d0e83575ebe2bc9853a6f44e5968d4ac87c5dc29900bc885f6a87e5050efa8360add6977555050178386aa10e9a08bae70f2ecb189b90e9d680f0c94051633b78bb947cf0e8dae98dd4e99a23dda438a83b983dc55aae864c628cf45e40fb9ca6c73fd6476ed8407eadb4871b491ee539596f661f21273e195432ff386375b6262cc7379590b3b8361676d594bb398dfd1d1d3f2fc8e6edaf7890f9418d3da273c80819356dc6e9af3a13ea0a2c503440bebad676e62e8d6e5fb4d2eee880db1530dd16276dae4c5ec74494bef28162ac0086a31f683163b8f5916f89566a74d4014077b511cac12e23bec3bec3b550cdd8abedb901dd8b7eb68122f6ea7348bdd8bf67cb7539a96853d5a15d8d92f206617cb3c7b6a893625ba826a9ec0f4574031a69023c47cc5815a474c25cc510bd55262386128cf3ce3805a1105bcb4ce5f58475f4e1a4cd469f32a66a78abdf1407a82ccabc643f5ccc4abe67809a231fd797c7af8e827e60ac17c5407fa935c2724f597b78095313b9ee7e17c32262e8f575c35efe891e44b8c339c9c6134af0b42082fcc48caebbaae6e9af1c50c268757ae244aaf56ca80f2aae9809c518e115391cc78f2dd0e0d098919b3a3ad339afe0d9db184a488b52c8aa9ae1cfcb7cdc79ec643dbc1bafa88f2ec57e381ca8aa672ddd55d7ea5b665c2ab1e621de6d609af56723798b1c9bc7c30d9e393c95c0b3598585c626ad7dc400859b1207a826487c68ca05f5f2a3268adaf15d65124348cf0eb7b65b158f9f5d5022f1e4242030cd1182abfc4c49e4785674cdfdd290b6394a9494a9abe877298447ee66f0f8a635da66145a77784a0c0df4d83cad098313baf3506ccaf2f13d6d9b5a168fe0ed16076cbed97c3ad065ea90d450fb1e8d712290e0cbbe236f14a957761a05ae2378ad5eeee122d125e010dfda2106730b589683a3a99fc3731b43f7f7dc4553267a5f2e159bd9b18b64703b5786e76681533985eab98a1f4ec3716781707a459ad1b0bf40920f7a3592d759da9a962069667ee87bf86f8ea8f6b35776381534bfabbfc41ae87b41f973750ebc47590367d0d469a15b45751b3b4d534346bd79ba88bdaa89d7414e60b07f6ad22863ec20ec4d3dc8dbab6dab5a553a5f2554eb3568c7eddfebabbbbbbbbbbbbbb5b1e59574ec6c01d51871c50eb39f6de902dc3dddddddddddddddd2da7ca8d5280690b1661baaaae15e525457f05313bf973648516c9b6e4949053655e6666666666e69172aa7c05492960f23ffcec399d1379eb7f9be4e75e61b3d40d32620ed3d755175897eb735d85c599dde554ed00531c76ff65397547b5b0c1981e80f08312455180924012a3458a184dcac0c1b7882d93c1ec52377ee0bf95f6f02ff117cc4decc3189f54cde28db57387eed3ae6a54fc81713b6276da8387dd2d71d0a2469076aaaa6fdfbb349ee249d5a154bf9e3e9452218c2eb7e7d7061b2af9c1f4aa60f1a51392f9b091df1899628c4d30c218e18f2e6064628afcc35196e2550c6697eab2f752a96cfe605a1fd6eb3a752914ca7db84bfd721dcadfe5aade610ea652170b6697752ebca76bcc1db04e64c1ec5ef6e21193bd475f377cdd31baee53c2dea6aa1ae3c0c4845446962160dd567c8a575784af39436185a6220d61c977d93b698ff2e007aa6184a6eeeeee6e1962529e0347840d681c11041920c8d001870d93c587237c49238d2d5e5421573aab8f0e6406d659b5a2a1845495ee68ebbd552fd0db4e0265e018638c31c618a3037230a2228d15b4a00c325a1b88419517a0618496222eadc8f4d1cac70883c4e8610483a4179411e2075d50e9e18928987cb10416672c11a480fce2a5394a05135a4bf1e2a52e74c08b255198be28c3840468055d960883c5920e4d4eb60b2830ca2cc97cf9afdcabe252b915638ea0c419517c09a306100e375029430652948c386387a0c4755d314028c59c1ac30fd8fa01ba0cbc239d06de51fe813da04b15021efa2a073a0febf8b00ef41cdc6b010fc5da1bc330ae25d7c98beb268429de9937fc493fede1ff95dcd990edeb29ff3835e2116176ef65bf6e20eb409fc3ec24aeeb5e2a8e75047c6f629efc0fff102c63dc7849785a79c5852de18e1e026e14d834d9d7a888995d30c9c0dd9e131d61bc21c6e81162dd375c8133952dc44cf0faaf3d02fa1d0cd3d783ae7e3ded775d9777af566017bf0e75ec6d99d4f178d5ab8f57da33d17dd8a16ecf444e9f8b70f91c436dd96b9477ef51cd350a750a8a635d915eab7ce46eda81beb9ae7b7b7bb57be671373174ebe4fb4d6495eb07c53c866e61dc111b5a9863d70f735b6644afcee5bb4c834e38e90e8f6916fbf39b07c420aedfed51a3fc4da1c5775ca45180335f2d8639c486a06f8eb25714073b17f54eeb40595d545454b49445c76390bedba3a3a7c31e35eb0aaf160bb3d8b938f9cd62b403fe1e0d053d6cc9ad09e6d9b64bcdda2aca62d741505f31a4e58767c7b86d3053715c91351e7af4b03bf243f6e8b9b51a8ff743768d878eea7e906d78107244e43b0221944fcaab3bcaa908dacae99d76e9cad363cefb48f9b12d2865aeef172f382d73ce0c30269bc382c86473fc864ae2c0e0d38355be4a0aa2073f5f938290e2bb1c35aae0a420be4812c2054941007134037618fde2e46cb10e1fda504c23bd73727839f409e80e4b59d04f781fda73824ac1cc213c643df44eb378e839ba23792207957b685c35340b9e361a80340bfa89fbd12ce8402d201fcd821796e98a83c8a638d637d92cd469c36e62c026c6298a53c9d92c5695aed132843345c2dbde3a75f8546210c285efbdc7b9005423548e3080cf1b4265b15fced7ec33a0c61823848f45185ff3c2196d7292d28a2b5b9292484125099531267c4d4a02c597c9af49499cb8543a6796d3f12461179292c02029c9d067af4948c8bc1214d80bc2061d65ecd357ef753cdaa9d2c6e03bd5a29d769a04fb7427cfeef3e4939438f11c245b3057af4941a4f136af494180f11d8e1106bd20e2091153d8ca6bd210639e791d689708e6ea9d776f9d10c95bb741446386c06c50d39825a5232d5796943c67c593d3f17c438f2e3c6612942953460b7f60f5cd0559e72956698d76ca4ed9283b65a3ac9475b2466bb44ce40cd0a36c48f04e45c2a6cd9ad122d74121e6cd03b4eff66fa2037de47e55a7c6f9348b0bd317314bc50a962c4c9e48795c98be888154ac60c9c2e48914c985e98b9813152b58b230792225c31c9b3333bb5df9b7053979f61dff89eba0b79492f3dea9712985acfe8580e6a3d76cde3b5c98be88592a56b064e12f4b983c91d264be087d41f245c9e3c2f4450ca462054b9618c612264fa45c64c2100a0349184a2417a62f624e54ac60c982f2b284c9132918192f425e9078519275e1d285a9cb972e62b4cca795b4d064333333ed331fb90ed2300965c0f09116252d4a5ab42865e12b7cc4476c05cc3655e91f2b0f14ff701de6b388b9df2d0425934f3af4cc31f8a4ef93ae9fc94ccacda4bfc032fbf9cc9f76d2951682cffc6933d29fe488cc484765fe8af0cfcc705271c8d3cb11d06852e33a239fbd203f6b7fd416a4fd3d4fedd0c89de9a70d8759d2313ebabc69467fdeed329281b1ce7b88613929e0f80bf29c3fcb122c4d0fc7e4601643e83eb63bc18e1fbce2ad391978f5727777975b23bfbebbcc33dd09fe713c3e76f8e0d5ae472a783708c2ece419eb641caf9ec65be01d1e9acfd336dbdcb45934dbcca6cdd2b656a50dba548cf28ee87088b99f79f732e7a1d646498e3776425429eb268a879e25c741637e157a0b7c3974082184101e81619d77714254e9710b66dd0863c9057b123694323434a4830e3af4d0430f489020a1d1276143d891b7b9ecb0c30e3c44f878881036ecc8438c909f60f0bd6667f8de763005b3083fd884cc5f1e23731912f1b47517274ff28ad12fae63324599f6a0bccb32ef321aef32cc6d9c667b9f6df2515bf6368e72cc668b8eb241d9d4d4d4cc39b56dc66936f9d9963d6a9bafa94ee6ec3328c730c7b42d3a86f5895bbf76acc36b834252dffe382a813db8edd1b9617408bbf93584dcce0d1f723773ccdd03ec3d9f8650da20a907a29753caae5d7292875a09cac100537af4ab25d6c913732ff07327fdc4c95dd7f68e2513ac01394d442f0b2184f02d61526633c6089b71a08ac8be4ed8673f272d04f234026944f2803d14b8c06838c88b5bc6c47c987cd90ce673fd18dfebf8de7b32b663efc177043e6882183509bc87106edcc1de412e083b76b46139beb7ba7a0eff71e4a8f05d2fc068de75f973ecdade0c263b3bc68c2396e0691e3311095585e73faa7f2e8db426810775f5fc2481f7523ec9c16702b8479efdedd041341145e00f6c2545e004cc00a2882459084d416600442c31264c109c8c49028c0c9ac6162d787085135c7860869671a2c8092114f1200a17325465509f5936a0aa1ab8118421149103297c5832c6c98c1978616a22093292c69001e88828863842891884499201096058c1410f5c04799981180418230544528c41920415404f888e80628a951438c1e58b06948ca4187cd1c3971e78589a61e98b279c20cad2258c3034a0ba011732c028ac818415445de800060fad1b4330d18352103c00518617193628537441d484161cdcb05d8abc60290b1914e080430facb006a8d0dd1c24a5c05701ec9005efbb0b51172274a5df5dbfbe4decb17577155252cab36738dbd3f72159ad5d47a6dccc78c5ef090f42cf7ea3b96cbb9183d9654966a75a2325b33c7b96b34c94f3ab1573d102ef6cd2d006f1d2b3ccc199b089d9e5e4ace0384b66ec3329e079b71ae2202bb3631e3a3d0ff50b621d21eec6c7f3ed4f7ff1a05f92a06ec7ec98260309ef20d46058a8caece45f55cc05c32b299b7521a1da9c83e79a82eb40e6d971f4e8c6c773f537836f3e78c5ae0b66881c177054579efdc66664c82d87c5d2ff75b7bb0a3a10b1a6a3b3959d738ef3deb614274a41419620f0d00fd160675947935060f4eccce472c5e95e94674fa13b875b61eb9e0d5de186a3ce4cd809d28c4eccee0dbda1ded93494c57e03110f45cfac02a31f93343b99bd28118b922544444818296162a36c231253e5ab647dbbf8dd4f7fbb90ec9b4b3d21cc8e8998a8777688b928cb6176efdf66b4051382f272babbfb12622ec7b302afd8230bae2db393ee9b24148505b3731daf39b5647639392b1cb940e32e84103ef9638415c0de59303183b88b56eab6094ab1b32d4dca01c9b37796e78ad3c1a3cec22cf62ba96930dff5c3ec78ccb37311af9895f08a994871f07bfeb6a959dc4acdf2617652e9d983e0d197143b27e1a0e5d9a702d8b37f00f61dce73cf9f4141413eb480be3bc78925ea322543377c412204e529125e11b102d8a30eb3cb7a87be7ca7629d1e7ad5e907b3bddb21fd5def23302128b06bef6d7d688778b53263761bd48d0449d20d529e7d855883eb6476a9d8c3153b10e3d9533a3cfbe90161761bb44d7486d084d2decd3fbcda5555fd3172641e5926a5e75d0821ecc7751b64e4c80fafd8999e99b689775c660ba6070ee220fea12929a8685b11caec5688cce330d939e8fdc695c09eb7d650c22c5d3299a89e04a630a8842df7643deb14d10c000000a314000020100c8744429160302854c67d14800c8ba0406e4e988ad3288b510c21638c21841820000002003233a45515386bdbd7e022303e8890492f8dd7465338dd39777c286a90474699043cd69a5b2da4addd02a635e971a33c292862fd4a2689d5f743290a89316def58e00fee32058058fe7b598e7119c68c175cc461d2e5591e308694b29ce59e4fb8d1f18bae33a1232d95bfae3c7433ac552d2a0e9ecc12b3c0dd03b00be3a0ce8293c8f1a19cd7a448953aeb0bf9786a2662dd30d4a287c875b26959a0d6b6a58ba3c5a9c6617021d9aa1a8c2361028de9cafb4b8126d184fb5d6606e85a7fca16ebb68dc57aeec05c7846ce7c70d9d0febc2acacc2749d1e1471cea42768ff5b5def5f65b093f4e0bf4aff6b91bd9f713ba9882157f726444682c1ca9d097bf8817042ea34dda67d2758cb15b502d5d0457d3d783cc31e48e8bd1b02274743159e2430592d40c6159beb1286de1379030c0ea3921a7d69ffce4914b0b09689c055b300aec3e214675b44d4bfd8c12152ff15090637b042750d16b6015d68c0f6447a7b38cac9e6f0966049d1af179596b94bcfc30dfdfdcf7953d82211ea693682e29297a37c3a22b68f7a647abc89515015dbc4a32dfcad66ede57767ba1001a9142a0c5c404a65630f57bb08bed56f77d4a08f13265fa3f5c8e8622877e0c110cf48ef91802cfc1b09bfc381ec1ebf9726ea7115643635ff7a8ec00c107e54cd5e906d2072836c502b791cfc72b670bc22fd28e2acbe993049c82f137aedda7f355f9ee748823e636df28c41ddbadf8d512cbd038c13bedace8fd132388a0ee9a04e055166d08f3f611dda1a11e0d3630ab00d12f7a78526e21a9cdef52e9ebb25d321230a57b26c44482f9012156e3d101faaf9415d4990b3f535e85a0d2ddc1b09f3c73024bbd69bc1a298fee7807fea9b8591c56d5628d98cf1114fdb644021a25838dbd66283a86e563931da0b97a47c2c8f99f5e4d198da3fcd90e1e34b21c7eff45ef92cccc946aa5913cffe1948e07e76c72f29a746bdbbb1f8759a86a312242f6d6825410e7082242064124331a1dc32d1061a1c2d436b4885a1b640f016eb04e0008378b445938a26f1c82520d9f46fd269277550e42fb27b627d5babeef2e9f724999c08cd23ea5d62b1497d2c3117759d6b55ed7dd1ea1a53189d929c6d28592b3478712e4527bc349b8005b138f6036e52338f6b42433b7ef8b479f8084414f8b3e32ae394c58fb9a680b986414e50d2e47342a55fd22162d003d892c258b854af31fe626815b622c2aff4b61f744b8bbda024430f663080ebf627ef36c9ae4f2274ae81e00c1c972b6b2bf8110691937a015cec2db140a981c16ab050145f07ff530e41ec162c53c1bc5c21a9427d14a9ccedc24aeee9c37a7efa0fc44d1dc181c96e4151c89407691e09608e8982f174711db1215eef07d532a8b0e589927220d2a0ae07fa604608b4c89d10490109bb0c2c8d6a9c77dd1a0a2faced0dc466d1862f7bb292a72a2f5fb05c5d07aca85cf5cf1c9ac42a1cc93dc17784879636bbcbb1a6a4dbc5f796d503ec6e532a511fc4f6b30be20e654701e8a0f875969f06877a1591222760ecc187c622f6987847617ec52cf66e2723a26191e84cacb2f640ee2e8d9d44789da0036c724bf3670fe743a66888288c8e3c3eaab66cb5e0904ac3b7de8a2d232ebea2a6221ccd5b18a30d3d2eabd3662e6adef3bfafb844cae7cb35e53bb626affc5e03e69e9adfc698447ef8603a79189e3eda4956465c6ffa176ac8c026ccffb4f467e530d70ca86cfb8caa091c2dfae5a655a55f9a971b43596c0a4cad004342543953d5cdac8792afb16a599440cdbf2a805e4c9922c4f062e9c360d36f5066f9d725eecfc5354f5949cea96087079da12a8339008e72b0871268345ea4715280f16936f5554d5a2425916f85075c1788c30b2df8eb41a72b649d92d13dc5c020c45781eef8dfdc928525791ab1bf84c050e5f064abb9ed9d9e5cc0c71b8eb21d4dcfbddb31c5fd76caaf5c4efb4370839d5400f87c7b40129fa65ae4d7a895fc6e265672d06615fbe2d3aa833e95718e98c09d57be742b78984817e902eb791931dd6d2fe1adb8c225d4a524196f9de776c125b286966e58db429ffc2e841f70725ebef85ac6e4f16b28c3bbcd2a1751fbc443902a8db2d98d3ba1d2bb76ecc3cb1813ed57a33d0ae2666c4e05ff28b28516f54714e439c4eb0de0c6fd155c6b3821273baed633c7a2a728ab94e579a590492a37464241a04719565d87c85f6b81433a72b507f42fb95a85fc717e607afae301249a9afdeee7bfcbd4acc0579c66e60dd3cccb1ebe80e77c3630d5b2400eb18569da7bdd8c32a21dc0cf0230021a8f81b7da3f075aa498ab448581386fcf9f13761ed9c620eafa790304aca23888a05ac7dceefb3984aa5e8a459663fa1fe3e5f5fb34116350e656cca7ee5172d0a27f1bfa530de3ee690125e63f3202db2093a97104ad6a689306c217a28a3c85757f68f8c516c562dc2d2c291e706121c8ca3f16dc1b288b7def84e5e7e99ebe68589a922070f0a02772ad9f4f6fd58649e0602e859231bc653605c5a9b3584156a349be3a8499cef5b5d82c390ce859fab5e86f2dfe31e9f88568fcfbee5d0d375162aa9afea48e329ba5eaa6e5731d9cef6d68ad86b5186a15c5e5f6329652f1056d7932ef82ad668c82738d964378e6e1d95ccdc25610eef1d7714799e91712974f6d00d06d79d4966f8253bf71b8d3378335707f6d793f42f86a975bc19622af62e7d06d961c08f4a85cc30a6f56fcd6ca44a9281e36809a2e20235a7bc486035dfa1c464d08ee7183266f29fc5dfdf758c9f8b240b2934a2b3aab782bd16ffc6570b5755ce9a64d8f4064f4927fa3696ae6be51d988f70d725a38ef696cde40147e18aad6b015fac82f80de3687ba13b87460a13107b2d4bb7d7ed8ca3cbab52b22fd7a46574cb408b2be04ded44eaf53cc4468c9923a495b284291af4639f1c4aa156ed29e083cfca36eecae130a1b310000e57261d4ec6c7a24da2018a2e75d379473cfe941e2fcaa450d0584493b46c581d19a0416af979ffdea31a587252e777c1ea05c70493505568b27cdf96c3e5fc11c0cdb3cac002d65529335cf2cc376b6a2ce3bab418ecb392c1e67e1c2694210db2a20936986efc1a9455c2c514fa668eb44b2499d1b898e074f223c92e199b07e918eb60050ad9d18896451fbe2fb9e66ed302de0863b13911430d443a2018b0f779678a2d3f7e474f5d2fbb8a9361bbb72da648c2aa2f8f8ef84d13ac2dee718549cd1d66f8e340f099a8f115adf07100ab8b791aa50c28103b7e8864aa0293aa6d435ff0873ed7a8abfef81c4dac1a903b8c380e90d0c70d5d22ca2eef9bce21027b73b8c0dc707dd532cc9886018d7dd1b89d819df90b2ed99b53e61541fba1659adaba5d11abe4203610d7fa3d2515399849866c4198ac5c30755f7cb730b7d890d58e1c2aa1c6e00b59955d4ab23acf6256ab67e168318ca419dac6aec51cfbc0b609b02cec6c8d6ee49b4636a173257890ea8d559ac5001a23c09e76eb7bed42f7007a9cf4072c2343cf9463c7023069ddacd0110c59ffcf1c4498a4dc4ac96c2c9961f274fca34f77e45f1c2dfff3ecfe0c2f7562a0ab9231071fd07e5b31bc0393c47fe27622737502a9a2180b64abdd49f2aeb3d0ae75c791b72367a62aa90864a74aa2496ed6c2fd995197a774b8b701b9600f71a99ef00c02174fdf3690f01ff7a13b5f9a47e743de20e2cbb65334734bba215803cdf8e6d301bb6d08c4992e32976882fedaad383ee978778ff0689c2b3a8434479c8256e16e38ad7438770060e75f587dcf787ca21fbbe0a0fc1c00b8cb1303843c3102b01a516c77fcc3f247000440b61f7ca4441640ae6b57dfd48361f19a72039f5914cf13ddbc60dd46fdfa855e91da4b791a2ce8e5873dff361e5c3086c96166d4ee4577f32200efbf55b3db2f7a6502b62eda2ce33770855fece094f03a36009fb088b1e4eedbb438a1652ef5c5a3b486840c4bffdd1ebac362fe628db181a5bf767f4a11210da0fb4f6cdf835f399f14804e41febac07e5008444d2f28e0c4aecf7e6bbc41741b4259b8766f42e8feb186b1f39b8f278aabce3604fe4be18bacf63279709fec83aadad45d4008260ba194e987b7b18daa983e8180c06e4626f6a36422563a8d14f30ea89fc3865b30ca69906a1682c3bb62354471ee2d0db8d51a76d62eec87331cd83cdaacf24a33fe1202d4db6c48e92ef6be01cce34ab3ca217641f935b34e476a82a036f5b74bf43e47ccef9e895a8b7fb45a695ae5da6908d3c4cff292910c189b7d044bc6f3e7de0ab851b7c0fc836b785e699bf5c2fa1c2d6247477ceef1deab8ebff9d01240cc3b2cd7df499d7bc277d7f3fd302518cb996ca1c2704f9ec2a98b5768fe68ae7f45af268ad07a5d4d31495e421eb5d3248e52f5c9e8a8e58a57c973de46fe311f2bdf4b89f60f8f1e14d176873e4b216b0e8fdebee35d1af4dbeb3c55d39adf4c9dd1433bc4680e4d5d626819b6c2133b93d102065d389c71b756be21f260c2fd9c9d6375c8ca1cc36a1924421bebddf2d46d4cbd5c686e4c28587d72ea666b7083f3dd350f51c20c937652635926c4fd5eda10008e061cafdf850e2fa2d3dc6f16644eb28d62df312b083ed30be0da1f819d499861516ddf38de11459160015035d33d465c0865d4109088addfdc3038da414fdd1802c34005e4e671d2ed23b90a42c949b93389ed8ecf16a48fc42d30371329599febec15374d76f298cf600d5ba3acc9a52aef5599fa2906b5e4fc5e624ca4a951ef4b33e683756575d05d143a4f6d888182b222efc0dcfc76cb31a588bab4b4239b049028b27057c9878b8bc16361268479036a91081503312eda4b95614b1066e8ae42c91eb4c79139de33999f0e00cfedfab36e384326416bead6c6a37ec00dc1680342b792ef47f993e8251d2bf727e5c351dbed3fb9ed587476bb1675da2ab9c0743c690f1f602613b2699ded61b8645712db4f7b02a09f6fd7b8f1452f2468de03a46773469e7d2c6e3d0320ae8381e084fc76236d926d8f0d80e632d1f7c0d54eb9380c05521ce188c5a93c85061412ee74c81bb785a7f987b09757405b031313bc64a06eac8424c46ec3d1d0e11c6bb90d42eb3c5459b909ea36011b78b2b008822fb0999d375f0a87b682282ddfb96e2db53f2010b9b5a245d7f43f4be1cff60e0d9848d6a9b5ff6555ae55517454a911d6e1cc0bebc9ebea267c8723310b1fca9a7ef0e31a1597c661a2d25c2f7ea3c4ab32aeb57c0e39c60c1c2483a73a7e09a878887d8adc925002a35518c68db33ea8cfea1d715d40748b8ca41209e8191684c338f7a5403b88d5502076d691307f82910a666a5055936c81c2f74d92f1b4e60f3951fe5a2f2c043281c6202ec8352f238d5162274ff38e63e12a818cd5fd3a909cabc2c6978f966eb885ae435166c84261f44fbecb83eedb2b3ec02a19dc766bc9a726cb77a859b36d11ecc2162ca184f4eadfe6d18570451ef601f6ccd852f833657f458700d683dbb4594167127c75f7eda948bea78e346750b17f64f2e378ff6114da845f181ec8af084ab3fbdea4879d5bc90f40dd0b4f416ac071cbf0c98b8465dfe07958d5627e46c7c7145124b7e74da47447403dca68e9190838599a3046027ee5b50af22e9ce6427b7905a49b640f87021fd5adcb42babafd44f6282de6385fc6bb0333e592da11056bfbc44e58e192d40b132f4ced019f216181cb0ac897e35f55483a5af4d2e751882a57012447b217c6bd1cba043cb78ec29372706bde851bc5e0bdc940d7fd7014ac22adad8be15da27c9c7a5cc7da1c710512060aa9fec66ce63a2ad6d2b6895bb1ef1ac075539930feaa6f748aa7cee7513f32b434dae332b6b6868e64f1410b2630586e65e909e141089f0e9e79730e85f6581126c20de961379efbedcefc1cd7290eeb16c979262b56b8632215d7413010d0e700900677fa4d8889d60fe2c6bb227e1e9a0ea4eb31c29ff4d2861202434108295f075f0849fe0474548df6680b215469b63a4c58ec4d0327dc4f08e17bc97a4554949a8913decc8bfafdabcc990ee9325586c3e19b419c388dba9e97c811c384a794566005807398717259bc17c86616d2291c1f56f45099ebb46266acd72c9733dc3e195a3986d9631b89f2844bcd30f184182f98d5b77baa1c64843ff308b57642f28656e535b1869529684ef7027944bcd0183307130b1ca0b3568ada3fe46182fb702451893889fc7a46dcef212375d6b20c1e91bb4e14cef7c6c7d331b4cf2674fe452a7457d7d19c2305c25cc706e2d2c3558d42fac7f4a97238944c447591fb0324223fcb0a0d40ea4ca56eb27370e32507a7ba8cd2dec4faa067e4279e9215a0469ef05f1b0ece2d4af4fbc249e8d9fd64f08d18a25f94dc91d70233bcb5ab8932b35fe1060e9d7180df8cdfd84c40430b3f69875a25aa26269fd0b61296c4e825221814411035579e9cd78f3725e31a35ffb600eac38d7f2a2b4f1e05794e2860981a5c00a8d8922e92244598dd99144c2e373ce0273bdf1e24a0ec6a1ae4dd1271e24d87bbffcd3aad29bc0c7c252ee1e20a73a50c86fec87d8f280664f518a62e31980962a383ccc8541d631ad0024ade40eec455630d3fafd9712adc03acc15d6938df72a9f956278cdd8f30f25ebc748f16c83c53201fa1bc8c0d55ba2e6b35a930c34cdab8adcdfc9bc037d96b441da0d738f144b219fe9e2ce7e7bc4e057431d0d1f39196f14c7b0980042e4241323e3e64f108a3f27bb2428263a03f7603a56917213a85c58056f5ba5cf7273b3f6920a395617573014e7c053a61471594914b971943fb67fe8a47f81bd43096e6d4c1a49f7d6fd625165afaba67bb1b0abd02ceda86c3499f0db5d83a9c4e79cd1dd7794691e6b10ccbbe2bcfc326adc9d23205a0e936b77497111f119cc602c21a12656779f196a6c00ff4fe4965f97925ebe76f72a7fa332e28ac6b4bfd09c8247a5e6584a6ffdf3ef0b05659b61aae6dad8b87c35d409b1468c03fc9adc2b8890628d6e19ea0296af24b7a5992dd4b1766de847e2b0c87156f5dc44301889ecd7e530acbf5d9140d7f778c675a421f54771db2b21037b04de9ec8312fa1c743ae2a417802c55834d0080dcd70a3ecd45ddcb77dc8e90b027117315599d00984293b32b58d9b84e438f313f96710dff1e70da3e8020899d7e347d3fa2b5967b5051c69a678674dd2c2f0d0d58a54c5d4c9f690a8b7504ba0d44a7ca4ae8a411bc49561308ab71775c2ff00e17355d28f8284699a75ed4429614cb53e20c11bb8a9d5e2d8c7032fbb4de0ab923becb44a487db99e4bd1dbb89d52669be9057748c680033daeee4c3c113130a8588b509ffa5cd77aa3d9779432f1e9e34c1503208faa118f4665aea8b25521be89922989f41b13f8b50621617382b6f1983e3d0943d315ae0be13c567b06a00461cd20b6f6100275b71d2ab0f0b37bd936dd2145875b4e62b31aa582eea2eacf22e4b1aa7ac7a1746690626ec5b97970a53c253e0970a08ed7fd1eb7b6694423442c8c2b3f64965a99d3264ce90a64cbd4a7a55e326cda21b167c7276cb090242eee7c7da6ed01dee9c86493f4649a68c0fec64bfdebd28bb810b203115c13b08951574d5876a0174d9b6a77260c5d767849fad2b1df459612fc0fcb13a2fb203c3efa565cc856415d7c0b2baa6ace4bc23c82b733a379e52444e5e749712645790454603b4e94e32cdc711321138bd1e20a358b7efff266ac7c19ef4914225cea8b5f98519c9487fb38095dc55d071c94ed41db20c6892c790ee1d5cd552f31f144a11526c293b6bccc5ceb90d160da86e96b2388cf442c8daf577c6085fdb08d819d61a74c44dfc17af633d6baa627756d8f7c94ec6d4e39099e95fcdf68302ef2e65e14a2437d375ab4c6279677fd6ca7dd2d1ddd1d9599659131cc0a9ca68d49909bba1e3898c4934a9d620c4a519b5769d1937b58a5eddd66f745de2da1d72d5b41ed44aee62fe64394aef5cca370cae8d5a4dac437937f51109d3521190f78e47116e85f3cc0ccb5436416140a7d7d18c0c24363e4b5ee449fecefdc34279ad87afc8442768fbb6c8e9e47eddfa2387653b44f04489c11165d3ad67bfd22f881ef39ff0566316e27deb09415426034c38d1466814734784a3ac73f9bbfb46bd10acfd0b5ae598198f55702f7d1586c910283bafe369216302abc38242fd4abf36ec352f8642315059404f19976008571b4940725e65c09b74aaf7c9c6300bb5aff74a85b47b2d7f81bf67dad69aa28b72a1316c833ddd446d06d233945b60be453b2d5aa7880040e88a503ba1cc563c1b1e07a6fe8e6098e7e92c0fcd4d87f8a4e3e9ca7ade28d4416582efaa9dfc4e11d1e91363ebf854c11b8d21a4fda5ed4a06636773ff294524e55d1101e550438595284783f4e457a04644a136d5182026f836221120372eca289f3a70e6b3d21c10564e6c4171606c7c73f151d562f12223e03f940f6955e1d182fae7afcd28485d8e16ebd029b6106639517a9f5433674ba532bd4bc210ae74336762934c573b1168eb55b4ea9007a8bb4c4c7b0df0fa422a7dc4a70d9c6604cef5bc4d120c05e160f88f61aa12e38f86e655f877f4375e51d74f4ae264c9cd98821115bde7aa8162ef531915c2589e9751dc7dffb3d55bdfaebf2ff7ff4c1f17d586939cced7bf6c6c57cf1c61d1aeb5b880e8c3c5997a858296758db42c3d7662647e75ff2b00c349cb52a45036db50286577a65200a98817f3ef30c5480b3478059e8d9d653d9048cb4a3efab22e543e6dd5def362305f4af855712ab90e60d775d712320bd09711bdc1c0e91a704ba7f8b489e9396741696e8e077b4bc1057e5be8fe27eec5c75b62d07e2ba547864f8c73737d8f9e2fe37c0566b0bd6dd6ad94e5cbc28d66125a236f5b8c3ce6f80704f5db297700a45284114223f89e8d0b4556047b4b563f3ea6971d552e67f0542f3bf4432a820688acd136cc74fdde006c7f9587192834f616cff3728d8e65a1c9e4fe3d6d907b62a37ecb31337f9cd6008da5a40232fadb5d9ed2ce986e5f85c7580933a64c7253417fff7d124396295950d063e9d648c0fc7a9aad455100cd5f0db658800c9a1e1105c897fddb151c8efd290da98cb1c7f740b173d670d608118fe46981e97e80588b75e5608cf279e8f2b8081c6c400112ffc5b436412ebec6a64e6149406bbc81161e3fb92fce0c66d003fa9e55d84147a42da6a04fdf438a10228740769f48463e5b4ba1dbd14c5b0e719ccb0b1fd5cbf8adc71f72905e357c4913240b67e3465576ab5ecb644517157bc358166bc1bca7ff4d4f65cffc3086ff6be36d858485fa1e174954e503a964a750cdeda906c7d06f9de9ce75d9a03c6bef454d9f70fead4643150c5798c5103c1ca95be9ff51d1556e90f6cba5f9c203436d8f11cb92bd900dc759c6f445f52fc02dad4c45b1da7a6d98ead76e8079034a0df96f38ef708778c8ed9cc5e631e7e0eb3886b9cafeb4aa8226d33ad881c4adfcd222fad851043c041166deea5d489b297f1237f771e48862c8527be04d220369e67df0605f469cc9c264b3073b2b9f9d7ca63a587f2ca98ed942e3565813a07ec6ef27ec43cbab662f0f2784e154ecaa01ee2bdd75f5667522ed5ed45de555780508530f116b27c055e12a41f4206ee09fa96785171095f5242b1f7f3f96296963ea21dd94b4f222d3de588011772ba0d7f1de3de0e34ed2d3557c84786982290cf9725f6bb72bc9ef6bd134d968a7d32ea08cc5131f0579c43d4db6ac18c46eede6a438ce83ddf5ce7f39ef175f22c00624f2fe3f6a1c6b37d7c7ea4ae244ca3207aecc4eae9fab534785c7096bd062a422b5a466a835d1d6eec4d6968302d02807abda1f7c2e5ac20946e94fb33ad43c99d61040b56632bb1e97d36363c3f21dd852a876e182db97d13593879e08cc71efe3a86345ff84643e0b7faa056077adb67a7e1543282e0f16c5aa581780924b20d5ddcef0810aaa6080525124112b8415ad9ded6a49913e4c02285363f807ac865c82203d34d897ce29d272cd046169f0866a50040f463a56ba816f59d1b3758591e6c4d25a64224ea62bba3fa68de2da5b8011f297fed0329c457178ed074813f5ffc3a0850431a444939524c7a09e15aad9bfdadb6631288cbe5dded0960244a4d252963cbb221a473584a6d65c709da123821988d278d9dcceb3f6413930043e3c93f419e2c708977c6990046ca3e3592c4390d4c58ff6aa43fddeb74cc52d71f171c6da0b9a3857958281b4a75076146ed676241582ff367e408de3ab4a45ee69e7bd2fe1c9bf5060f0e7e198cc5bd6cce21ea65fbc7f0d5d03a8ca132c477bd490bc3614a6007697f1d73103b013756cf7f2265c4c4c750b266077a3ce03609049dd05068101169036359407e11ab2dc6ab7d01fe3ced5d901eaae8279e200ead9b4d5038c94f304610961f1fb8648cc66429d4dbfa20a8d2f01d067469e8a5376c1fb59cc3597227c87d2fbb47f068906d812ce5f116d0178105e07fb2f34b5a38700bb19bf741525443b2cec7850711aebf21e8ad7622f8618b1e83c84bc1890bb6408fcf043b44d1c8ed7081339bbfdd558d06b35fb21932003c69e83469ab24cdc4976d4a7ce25adc1648a7b93995063d43c5d082cfff90fad850510dccecfc7d79daae3249a6d694aee499418545675a6f73dd90f0a6fc81ce5ef4cbb89a12b2cc19ef471352780154b27a4893da59f053eb6d4414f331535449cdb6f72751d66828c9adfed1c0cb39cae5aef8aa702de2e83ed4187153d33d755cf518dc6f9d7f4b93f4a6bf0f7919770a131598b2a60515f021411e48ef2414cfc248759a95672a7d19e460a3920121d5575d240344c0c0dcf17d3259892bdb9d45053a23dc040010a2836fc55fd063949df35ae64053ff6b597af4247b46311f2170d1b44757cb0c13f7cac7284ae11ad6d1a6fb54d639525aaba3effa79422e00ba92266d08d95caa7c17b5eea4a21b55d795f8e227a80919e0eec9d1c09e51752929577cca9dfe85a7f156d6f264a571d9747e9e4ca6cdb0cba73c3fb9fa4d9542a499fe89e179512deb2ba1e062426a312bb4f288fc8a1581254ed0a69a094d1e7fee11d45a9f5144ecbc4c6aca1270e31b51298726248ed76d203bd40d6da5aa07b92ac83c23fd281a384bf780bf9bd894a76179db8952507c1e72eca07cce3b57a11411114b739dcc8c23fd4a32451890b9175684764b12313d95fd2f8d08a8c16b3729020ba84fac87ea4989e4226d8c378e18f789d4c50889e54a68351d54583a965e1029e2b84a7f41da97d7ab036f7393ec86309842c58de504de6e461b48a18569115327fe1e4ca1ca2f1528d8978e5084c213ffafc0b34002825c56b45ad74b684ed4f5918015aa29516e8b2f94add3652ab56380a777ba9bb7649663395538152f24d6ae349b89260bb9bc6e65bf38fcd270ba3a868d5554390b334920367a7a022a0ac9e49669e71660a4605ff74ed2e0ce6a31e2754ca2cd36030c8dc90333413021cd145508440e97108f0c266d32b1e67e56aca8db5e3abb1f28549ce04b1a54197a13be329f72aa978d184a6b4ac55ce44afa702d2138b4ea642cdce951656c0c12a3b027ac197bad6f3981684e0807fcce98d21dddb73cb53b76b72db17a3696a04221756ec7df675adf4eaab4753b93c09f19c5f045b6153a8b4f75c35551528605012b3f0e4903360102cd39a6487d70d46a5cb502697bff4b6544b16b321c572e7871aba395549883cebdc39b285360b4e9088fe89fcd434c823b8f806b79fb8c79fb6185b1f06b15101fc6c9acff875b52ace784fcc41cc68706478410afde420e673c5d5dae30d3324ce62e36321852f61bb0ddfabd3a716f36a0274eaa50d2d4e341b73aee2ff0ed59ede17e11f535043d037b3a269707d8b9e425e44e81da0099fa58f6bc616b50eec47fdfc375c5aed5c981a5ff23d036613381199c6c3612f2153ba64b42c5ede7ca70e4d66d36d3de91d420d6ecc51d7905349ede560249284839c795494cc98b445a7b7f79bd069a49cbaa65a7b79f98ed10779b810d6e4ca1d462f010065d7198dff22e64f2f892675521a7f07d97f0e8d552e59b76e3fbc7d852462ed73e8dfcebae7494316532a6be8b67cb72f45e7579c6311bcd7cb049406946fc394cb8009ff9d2a44df1185956551b9beba5e672678b26f7d026fc6cb113bfed602df6b733160e1197744687a16fff564538e15fc9dae205e1dd686f7ed49fbf3ab50e01e135c9450c160470ba3adaef74a73aa6022f437611734a0f2c1201bd6404d8b61218ad2e72e2977081dd61132a7c38b527abdf145f64592d24b9f0c3f979ca4295596a8b8ad8290b8f3f08d738e2bd6e7ec79fba64e56607dbb72c30986af7026e86ed2e87cc7edd81550dc29812bdc9e521c1cbd0656523b1ab155132d7c0204da278f4d4bf029b89f2c8cdb0da9755ec475aa5ccead3eaeff3e2b758a857a0abaee84b76a5f383105527405c57169f125bb3f5f73cab59140c42bad766b379715c42cb712f6b98f313dce6dcea4dbfa11abbf204dd2811b79ef95440af3187cf7434b5e0056720436f30f4464996eb097317a851433df63c898f97b31a4147e173b8c4ca864748fdca38ee297fd327627aed8b7fb251b0cfe52d39349cd8f3e6e75cd41fbaa7b2621ceaaa947410cbd2dc8842e8977901309db0fca79f71089f9315dea8c6fb9a43a5500b0a88beb59cc9800390d04266c4068a295eef85e0b8ff58f1ba42f05d0fc123f4d6c463d08e044244ce3f561e536106ccbf758d94bc2fa4018f1d9afa1fc5f0330231e96657bdc8b27aad44088190faa4194085d4a383db79e956130f215cd244a42e70d667271e19a336853176be2adfc3e39081e5977ee6f08e6d1d988be8f2077f657e36ecd2d4941f8054146c2a9bd6bc3948bbc590f1a3b0315bf455467b10f5d9ef22ea65115835135d98ac718fd2c83a6c3ecad6ad4721c3c91ea17313b6e444ae1fb7e897219395936237b318bb498cbfe75be539a453adc517d2f23ed0d66e450c41ca32f3312d46c236dc62c535dea64eb7f84bcae909c5d88d7a9e6bb454aed9a537e7757d09e71d2bb0a95b7beda3f4c2e131b0441cdf7d41632abd67d54afc26e69da03262eb8e3f861aa5b5f7f62a24ca7cfe8743455fe998202efc1d0f898fe66ff10242282e9a2ae846c494e0a3d99a5db6d988283d6c6c6315e11e6e85d13f6ac2d9fc6baaf22b6c01348fc2e52d163d3cc2c14e1bff20b321f5d94553c4a19817f544ad9a6146feac29af2c466223619a898fa4fd4ae3988fc612dff13e5a1c8086a9d7139b844ee094b428ef9f01831b8183ad9a9f440f3d345eadfdce474e14f537ec0c68ce1d6b1a067158bf966aa2f15f4f6465a957b23d6d70a7a06c86c7518962eacd19d4f82a99947d925e6837bc5ec22260b7accd4259ab1eb8bf488f3a9f7c11a8d39e934e61084a705c25620f9fb38f8d82967fe818cb0bde97cf63321c4070d88a2d66e32cbe0b82b3aa84ebc273e36b4fadbf826a99c0d730eac001efd4dfccff21f8096d325780a3e5d9d7bb58edd86a9adb904150f4aa3c7550872ab27a5d54d5ef6473e80ccaf026495b77c21e1f31c5e125247bfd63c3d8e49c4c706883ffeb69a1837ed08e8c8282bf412f64fa9bd16178010f06e4c2ab898835f96461094aa6fa9bbe71655db140f9326ebb7f5138df5c04989892965dd3a680f57013702dc1c7c4f155703fcb385189584187e94d27cfb4d1d362363387a72678f0d552afd7f476f0a1ef6d877a8056c5013f1a058307a39bfe0ccee4d3a5b965f2e54185275a4d87996680545de9e26ab8fb2fa4136d0a29bb3b8503b0783bf4283a46063bc5eeb000d8b2d1e064b2ac0551f49acf953b902de97fb77ad41861a198c86b702385b51c41d389d215df390b8e06f90846a8385f944960397ed92b8ae7a71dd3ec9628223adc192ca773e77a0e0604920322b7d5590dcc5cc3d9c82fdcaa4c9153a0e78164230544c3e04b84676c03908d2830aac886631ba9fd2cc82144d2bccfa29f969938b39b57c4abd6c368a97eaa50894338ccb266fb39e8d4e69a835a737b0c48cc95d9ac066113dc3b8b12757799ead896d7e79c435ea42e74ff9008195ffb8ee777b4cd5fdb0af37274df841010d8e2d70c8a485ed3ed5c3dbe328f7e1e935a51879c08a4962ff2e9423be38267aa94ceebe9c7a0bff1ff6c10456fded4d805d7f55a1a4f15f751511115bfc0546506c65936bb334f88ba37a0692fc49e80256872857365ffb4051b710da83d555823ab42bbf5209c74d6bdbcdfade98abafd2daa9fb3a704351c690396095cd4bca3238129c5d57bbdc236d20c5cb60067e2f1313261bd7981e82bca34a153afe483b07934d56c2ad966881ffe53a4a25a06c0c552289184072ae3d76d5629c75295d3b79d3940fd995da744b746438c4c8d9272a94bad23de136d4f88bac0e445c37485e4691d09eb0aded29c0798a0f099a03bdc1bde2739869e26774e923f8533e232b5cda52aa072b1777a5870543a450833cc74fdee0c12d1f0604c267db6d1f81901a68beb6cb53ce812be5659f5f60dfbe3e0d0db3e8a79a498ed51bd7e6e230a5de3fd71346d4eefabc6805e88511d246e539478fd9a1dcf9f1d01fc0066c3ac1173383bc0661299d643cd309250b565fab29c70b16797d5fd07acc4648d6593711abca7b4942d2cba23c806b655cab6f5971e499a317ceb2dec0969afc9b2208e275e0d751bea9b03dbbd2cd0b1a486b6ea5d0ce79ade13340cf8c08f715f0eabb0219c8c00c7c0400157d2a0e586d9537b01e42fc81d622419da1730377c8dd3aed7a1c55989a97108d444bd1eaf1d55d6d7e9dcf7e84153385574b34bdb14f0ef2956b188f1ce519db8106460a5701ac4ae5cdd869798daf60658dbee306a6ed9e1e6cf0623b73c8fcc44fe93a5406823d8e4c6e8388d4ca507d308b7c4051c67a0b66ec9c018d978782c8c8bb03bea8c37bfad71b43eee6f46e2ff199a6a9f59d7f6cc67538da4ce54efd9f842f581028a32aca0d5a92069876aa547987fc969205ebcb2fd6465e398bafd6f97001ea0e932339f9685bd8b6ea06b9d2b4070a8bd1eed00fa37e878a90b1b011df73646e6ac804c1472212349e5fab4baa27067db10108f399f14d7bfc1a5b78fbf21ca7326e8cb8262f96fd66247cd1dc8d2187fddff347a9343b48ecb816daa1efc1082bd72cf0b612a86bf3390e7002e99fdb5ca90ea35478643dff09428899b36a709b44bfaf44254035e5e8088159ccb203342c1a5072b3e44b7619e46902d6ada5e35dc1a9c164167cb4510398f0d21de0e42d6c0dedc42e31165fa6dfffaaab04c19aeaad16ca267aed6c31bd69a9bf0e3b9fedef832cf045fcdb0e574297defa156a6d93fb170f1c42a0fcb12ac157b62770a457b5ab7c876371192c41e06a6115a9fe01364f4a32123e4f4c300bc3a99aaf90251995b8ea3f1e98df9b989f2ae708f404964991ead62596795d0cde8f793e84d1d1867018ae537dfe23f7c7440af91a04d482ba78b0ce35d4c7802d4a5c55b7ea71289340d37074be72b7cc6b6c38bc3cd82c5230593162237345ac1688943e9e74b7e58ce374bdcd87c9df901ead87030e1cd7ce24d5f42e6a8fb8d8c2b8fabdd767110c92ff4e6c73bb7062264fcdd114666c45b3a94f3ff5541384c82a1a5a183f062bc8cd4b03b1d4393ee3c184d5c71c79663db9ac1f96eb852ab4ca02b3450395d5a3142b21e3dc1a105901c29d4159a2e02e28e926956d09af58fe48b40e47950edb1cd4d622157216702de537a4e85767bdd188789d592ffc9e5b8df2799af981c51b7de02b5f7c44195edb52aa723523daa137b43b36a297c5045bad0a7339a7b6201d2fbee0d8d36af303017659114c516393c872386158d94c9aa95092cfcb52540a9fee7862a3edda271eb101f58db5f8680c44e763409680ece9d745ee631deef0cc87b772aadd4219036cd7cb76539215d3d102feced002e1ad52fa8eeb19668450f4bc778f66344258e983d6a774c8513faadae61ced474618f69c2469035766ffafc61814ea80c7abab622d943fa9e0df6f5f413540665d37b463241730fb211084eda7c58e41de45e1658fa017a509b73dc780d242ac523375b39a74e16e37036e85008b85330028994637066b3a176d86391d972320eb841b796d8e7473e43178b5c78b6c8668b306e6f54d25f77a692c3525b16db8e053d8c72b1368896e8e6eb125a9e47fd17ffe60fd2772b83a6eb1f99f055ef14e190c7f5797cc5500fb686187495ad7d5233ae5d9beb38d3d27373ec6ce2b3bb231ce590931b352e8e2f10155a1d236fc0c04fb83d5905319dcb31c85056d02313785325b7957f08e7299a47aca286862e7d8dc5de9c4846dc79e7c001c377850198d57b7e60e44401d0671d385b642458ff9f4964a47fbabf6e4603650005e6bec9cdb0908123a760288d0102dc26c8effa0c7bc8c4caeb1595aa50f0be71771afb2678cf3de40e4b05e1345764551136b004bcd60a6b4992945ed0737d9444f9e11966f800a459b8a643b49dcc78679858ef7fd38ceebd5ae97e402c79cd707f7c1ea49e0c9a7c25c0150a0b0558fab25801249aeffc56e6fc451da6e5979763398493cea8c5b9b409503612e061ff37a9482012fe76a3a99ba81bf0084463ff264aafaa16d320a4c3c786d198f20e26b77eaa92cbdcdbbd162693d993ba276376fb8bc11818ce188adb6bdebc6ef4467040d386b8904a95db358cdd4894da7f182a661e2e71098ea4089e4cfb93364ce2bbe0b32ce9c8f5e056d51e0d9aba0e6f1f451e6d92bde8955599357e87462bd9b745b1f90aefcf8b2b03c2e59baabfa02b1567e5837a330ab64b517feae80e5ddc66301b19386b51983546276e4496fccf7431cdf56bebd70740cb12b0d689e581af71af56689c031707f4a25f45a8098b7952b81a8a15c445e5d34d29a91b1016b70134dc885817aff7182feebb308a8bc4f6dc73027ca794380236cbc565f3ac007591607a106153a3d091d9929b667c2bf2426f8efe8d0d1057c061f1fba85b7214b4596509885931e14fe6ad4794b3e3b8756401eb68c0c24d62479bd48e287c6ad2ec1e7b15c142d30cc7dc9137b74db7de0741c0916fb987acae665382c640facc4e8555025c428fe9005c6d4140e341e2f8c03ce1bce0b0c3010be1806a6848724a64d2c725880610a438a506f4e402777498e659bf82d258ebb398b4014829a69f4fcf00ea91bd986400708680dfda84d300b463b202c544bca365ae65c4ee5e0a3663a7c8336dd9d938826963b435837cc07b70253ba9849623ca12295b9c48c4846b1752fde896fa106e250091a60d96e3224a6f117c6570517f8d9cff57eb787b2d80d02bb2b02fc5fb89c045630a14e2adba4c19603208a9cc5fbd55bc2a3bd4e0d220ae7d0957dc2d3cb9f81007f4f6c05fc1642c6318fbb2c6ec32f63d254077c90c787a166122e858005bb023a0c0c49738d466b60c7f750378518d54ac31868f5c53f608e079546ee7847ab15769ead850475ee3de71f6e11110578d7f2086f443842d905f08da2ed9aea466966b81e620d9e59b0e935656b4797e177c9f3112c9733acb71ce63b493281565aa24c4770bc35d469be053edebe5082dbcb251ecb23a4a5aa667fd0edabe8e5ac1bd1a9d6ef2173c60cda5bb21ef0c4cac31c210531e9ba65da39e27177657a26c16ee133a2778b8146935524f65fd1b8e3f81032c27ee5b34fc18f218a96af5c955373063f0b851e52f346d82c5ef4356968f507da8d2c0e7a7b18ce7bbb9f592543266f4866f0bff0dea5009d7122b57c75ce380bba0aa533193eae66113dc82722fe6027e950c800e87ec29ccda4f8fa619cab9f5beebfcb2259214d31a937c7f8a4551f3499729725ba02664bc36f77a09d8b4cba41c12d3d78b83ca155a373f5a3e311eaef4f80c1aa4b538c3fd75f2de9529cd2fb87c64c78cf2f336ada7bc8b0a2fe20df71d9afe59ccef3e34ecda5be51f34409680b3e3568bc471278abf60abb06678591d50261166c3575bbf80d5a430d8b3bf57c44603bb1615ec1ff08c9fd44ee404851e203e91fe246eb103a081558fcba690184a7a49282bea1ce7eead2cc1c08c6f1a85ff7a78b30c190b854ff931b0dff1d197f413fc034a8dd55973b13a806580402afee5977cbd45f50ccd47f7b940ebc1d139af6b60b80a308c98baa57c43dcce809a48cf294ab520887659d7464f774a2a2633d3a9b34742c2dc3773469d228f71f3637eaccb80df32744ecafd50915f89e29cc01f98cb3eaac1238c7469281fd16b7017091dc3e749f29878d3ae47940bcd64067401826f37d341d472e89899a6678eca978e66878f6b7fa7e231aa1229b7c0201900e8c6d5bdcc6dc5f2e3ca2a7b3cc34f05cee679ffccacff6637e63f6f58fb0b28d6619cb0ae0d7f4e61a83cca0176913c956f381ecd59cd11be4607170218e28b9cccee7b0d6be24803a8e0fe5b92b3449dd924b701c20c1d741397170e1e97a5857eea5adba1c7a272c5f36644f67cf85d405361afc866fb2939d1c988d780a0b11ebac962be37536d8fb38962954cc8cbbd989f86b12a9766b29e2fbe1673c06df94de47ac60db744e954ed3d0469bd3c6eff84b76b93ee09472f66ec571845e3a86079d10ab240c4d9e3180dd166c03eb930b7fde73508dcce1eadf1a3df8e673808419edb145fad67f50fadd1522576a52edd6ce2b34850a38fde1822ee4526dff5d5f0ca007ea15d078ceb46c94f3e365eda167300fdd00f1f5a8f9e08b67c960df6d754a82174d584740dd9a12d7fe36322d2838fcfdd16a9fb305adc0ab10046b969b137fc69d20d89f4c3b93c3030b92f16e3f98a7024438b014841daf5bff752c9f6134f853ee349f1c84635e41d6aa51aee9ae852be62383bf3169d595a0edcf6cb5d7ab2e8a48a1da81c4b9a60b22b019988348fda0923b0a050d40e5aba17ae1b814025569ad39cd0d6036c6b623195704d0b9f1ae1a624bb97e2431a0e54e4d9478c2524499bf5ce6236f97558bed6202981870d7d14c59b22d82c3809d6e4548691d89b4199500648966c45838fad9b975f59fc896aabaf667f274522079801929519b30900f1858c1b3d1df4206c2c8f9674c94d4cddaf87374abb0b3bc3d979e3c0413d26aa86eabef6e9f360ad490116b451bdc7a8087cacceaaac0725f720bc687cd1ce5bb634d6160a900cde1f417d975c120a8631944c9b218bafe7b992c15dd422c344a6caab95996b0ec09530f97348001239b8db4879f12732786aa7640d8735e6e720f84061c80cbb0350e864f7127e9d5670512f93c684e3063299aa2ce7e495bc9968a94abdb1ce1feb74015788b0d546bdce3cfe367e881f10571325755cd023075f6c8ac08bb0a46cbe1f4b682590c8bac8bbf8e2bd091a5234b62d758dafa06aac2246d47f5cdcd9cc11ef20dc1b57b95c0352f82befc9305f3e37d74a47fe27ceec26746eec5dcf0353fc4da81cbc3532e39e7304fc36405bd52122d7d6890e3f591a093f9421870ae5ef1e1472a7b80365fad45e08544f76d62651aaa67f342923b982a101c1fea76f33ce037e24797a3b50876ed048799986917996b12d298624269e6c450dcba5b0d3103c61c704ad8ca9a7318257e3075ba636c57875d04e31539ec1ab00798a887fed9cc2fd0035291ac9bfdd855f189f77739b1fa8c300380e80a4298b64bacb6d6241def941311aacd4e6d018d804c6f9faadcb21a1607922d49afbe83b6bfe12a54bea624ea105381d6479e2b38ac0fe9dbe2a7ac319a2ad3aec1d81a9cf6b611f849fd0ef6e8ffcac40281f86fc88e8bd5f17081ae645b654fb469c40dfde53364f3f882dfa8bd587afe9b8d704cf75b6850003047795cac34d7d7acc3c5ac4de0fd1259a8193af817ee7ceb792a3f03336ead94953026b9313333bc5fb82626d1dd2f009417c484fa4d065ecc4b1b5f8974ba7a788ee37c932fa1da8d664c4517a74642e87651d6c05e88375147dcf3638afefb44d4b241fd840727b89f3e05014d6b8de2c8b20730f1e5be74dee805d979fecb87f4a9f1e2171246ba9c3a34d54a26c63d06cd02df19d2e6dd28e1eebecd939c69183c8e5bbc92038316f650732f7953252c57548ac7b8afe65ef8200c4b306ae51ea111651afc656f54765851861436152851dbd3485413e6cf2c7c81ccb59d1087febf254f2f336fa397675e358869e605c09b1bc49f43d8c48153e1cdc24deafda954bb0bcddfc67d870e9e38784e2e77e137e924bcc73b370d0229a7f08ca3b7c95e999be41c3b733e593c1b9d2b256cd6653580bf521737d496118084c5cb1252d6cc42a057dfa2a05507654b015d1ba623af2e578ec6434c295526c19538060de1827dcda1b9de99a6e6d54ae852a45d5de63d866124f61cdaea7c28ac30e891fffbb952997d090136d7288a576e6bbd50fa283ffde42befb2b247af84dbfc8dc95a812b7c25fae0a5d2d73ad74cefc9e757c9606ca82e5aa56fcf2d6060f04be161845ea753977a60f0155df90675f09c55a675d4f3903cfd0cbdb1bbbb5c9702852ac9991644ecfba683bc20d61129dea880e4dc79b87a9c6ab86e54ed419590a3d1a67256d5679313c74145653928970a538a4564fb4614d1e857e5a8d38392e9abb673ace0604b348fb7a0d164674bdebfc6ac988fa81ace46088e6ff817e4f8a66c6c996ae0e272e8cb09cf565f283f64ee394ab50d7d177069e3b17a3f44d476b2ffbea9ee12f5add92cff66eb3bdc9e323a5bc66644c0ac4b973affe466d93bde3058bf960b60a83cad433d8957a402a25c34c52509e38742824e2e63ca2ee4a87cfe12da9bd9e129dd432fba8d8c9b474fa4ed2efdb9b1abd22a2710408ee3f90e7de55f65abd5a85afae5e3b2a9256b6da14515c7be912977df8453d5d967effde60ce3d38fad24552971be11ce9149704b68b489b27d3f518d802e3f2a1b3842e42c062d0d19dc253be441399154b3dd5d94241b5ccab7083e58c1b26c30415bfa39d02de2c2dc86c3ed17d96c707d18e41fdd89a5d5c0f6cfe77d4a392c200995e24912af2e9f80a4bde952f8d9491b0ca986d2a13a9e0dbe255b7f2402de19e3fc351ebc531af60d3e377faa8736cebbf3ea4a26defa86935c46908fb95dd855c11a9f2f3f1a9c73db30198345e8816166ea307a0a50fdac6d1c66e125445fef544e9ce191f357465db83008c78f6ccfd7944ee62cf655f67adb0005519ca50c6a05c0f97071227047bed7caeb64607c95de4c8963c32c2319dee978867ab59ee5fc2c029c45e330cdbe23679072527b00ffa93ed339666dffcd2b28f8554262df45f55414952e1ff7c702da6c118363bb2ebe7e4529db6b97c2c5ea048cbbe491f3faceb0450b70dc310387a4b565ab8e82f471035574728dbe1e6bd197ef6f4e1b13c1f7c9b44be82acebcbca6bf979e8134f75c35360769d05ef9cb6e2860f31da8008c895e09203c5f66e5f71b1458d7a2884ee5a288bf6739a257bfa6b083bafa45e99f0b2a635f80032c47f5e8d788aa7cdb7d26a74eb0616a6c58d93d9b79b4a518280ded2982ec96e70d91e726f3293a936f8a4863e58adb897592f81da9e9cf8e00b8c1f6067be49c313ba8e53ad3c826f13bb2ff204b53d6d8e80d68bc6c6f515c351c12eb7c0ca0a1e6072d24d8ec2b8114decac83ef8a0cdea134164ac23807cd0f19ad91057b711a86f83b11050dd6e83b10d5eba13dd1f5c12f64b86296b8b1743176e916b1e2b2d8ce6eacb03f29a69dc0d47e1f9ca4c5ddac2507d87e8d9c031b847d08efbb1c12285dc3105cc80f6f7e6fd62dcf225a752350793a71ebc23eeed365c4798d4b3444cc6055c23180c06bcc138655b671d91417a37143fe91b0b0cd2c594cf165ab63efca1de48aea60d0ab0308af4e4c51d0f2544cf0f3ffbee55ea9678859a1b39b45afaa2fb6a8147d444f2f4005324a453314c44c69106cf22ae815dd8029a867601990e27ac7c1d5e13c99ac5a690bd3341b674765f22dc7da8b545de196e5417a6ad441c7fe66be518adc5be1ca0d1d1fcd75b227db00c5a26120d3506fc7ffd7af7cfe60bcaa760bbd890fb9a2740312fa5042cb94f40844b9dc2edb567bd881c804d51f7b051c411e69b48222ee7573ab023e6477536621e8aa8f4a324b9c753c3eec8c49a82e9436519ad948df3aadd54b81c2d9c7a378f21d18fb194cac621d033b0548f29b5eea50557c1a55c8f30fd10648cc966217617c51e3ef2eec141c8ab426782510e8673906e1ad1c1cf50a85e8c0e1bc45311c8d0008f70e06be895d974727abc84714b31c394d162181967b89438684d1fbde6becdfddddeac8d08ab05299c1fe64fc8f364a146d71693cb044188c445095ed4ac3ddd14d788b17e30edff6255b97b74ca2a67234e484490b4785814cd01cadf4354e279556a0678fd0fad08cf45a11de0f53fa48a6bb565fd774a889931c16209a885cb44c488fc8dbdf986523bfd359f1758fd7c1f20f8e73116ea870feee773de2334219211948d409cb7da220ff8b2bb943605762a23563eb740ac3af0323b44082a7fd260d781a276d767c04b6285480b6bfdc50af89fb8083d96d35750576105e45f8713502e5991cc6d2109525e1c3705659d79d1e20f468df16921fe7d9a791958567c2ccdbf8d06e9400d6e7a626fe5fcca3c953b2b2e242f2d6df3042a8f38ebafb63282ba268806035641b2006f8491536e735925ac1edd9fff4e06ac20a406bfda508f6193b7b7c73b285e98ecc3e749191800d986a2edeaf7740866a3a4c925edb4a50ff0ec4712cc4e8b69e903d1aa82dd64552336b9500d97932250e38cea1cf62b2c6a5b0fc165ff874495213139ed687c9d61d1a5456dbf00aad7500da0641b48696b257381dd6bc4127c14a952b1bcd4ecf1b40151b7305c17248152b4b7821e769dd7a4adfa5d45109b81b8fbfd6dce8fd0757017fb4bb19072bd4c9c56c75c04831c8c1d4f63178edf07d1c68055bffb5e1424499dcdcc462bd6fadca12ee59a6f4465c2c73601215822111900860c15fa86392c60c1d552214376a7103f541b3623ccf51163e5b54a582357758a8d76ddda414bbc9d5ca0295d3125f21dc68c8540739295c653c4b72e520163d873b5e17a5af440794b8ae65ab8a9f4dc28dd545eca3f80ba1255dcbb7b69fa1f8b12d0bfe50ecff015c7bdade175a2379a17d901e58d249368573cf93ed59e655a069fcac516511b51da0151b65c3136bbf7bc415497f708436606652d7839d8c5704b5fcc9a0cd8c192f034babbfae8f040baf5f5d38cc7601c4108657cbd2639e284b27c68af1e2a520676403d0c95cf8643387206dc0fb6232f277ad49404211b4bce6d162929b29b8c78ec5e484c4dcf1bd4b12649b0e82656b179a2e2ab5533e86c3f7affc98028b9f50cece74dc459c2df4636426be4ba14b12cf43c5c9340be281a468cec734093f16cfcc0aa7fa11c63b0a687dede0a5560a2ec3ddd1c1282c2c4b090eb8fad66d7f97c400ea812c9f5596df70236bb37e5de9a8fd08cfd6a19504df40482105f0fa03d12e788fef6261a58c6f2f00d292b35c8c7b4c6158a0957ee152c81532b79799c633b34f4a4970904ae1d238907596367d9e456fc2b4b754da48395e1ca5e052352212724433e55c717fab41210940b1f52b8b1bd251cbfd1b5fab1130f6900f98e09787e7bb4dce965981d5ee667d63da8126105db1031a594e27eeef2d763961fe2a11d49fc6677cf5a8fb5877daf9127cd7a66a2dce341780c96ce651578a4ca9fadb0b042c312861772e0ffb8dc2532a6172966b606ba4f0c2ff7c58e283dc89ec106eb76f979fa7b1bfb8f617e5b8415f19360447534f787d5dc28dbb87262e86085ac1dd9a0eca086897fd7bb1d5be390c364c2545f1fc2d6ff91f664c51020177360324872c66725dbbe617d3e0f525957fbd8de886d88f193012dd98788c44fd8cebfdcfe455952e3b6791fb06ea26913eecf2abf622900f1d7df3167bfd9fd32c1400ccdfff32e274863100b9770d91f9468a5070b9e6b97f3175aa85b73c360d1bd3eb645cd97fc0a7a2dbb53a9bd7170a75073a49fb79118ca73dc3d16bdffd6f8bade6e298192e67441ed8624946702d9f0a9a93336a4304ea4c16cc7e4f6973b02a5b6c6d490e14a52035e9dfb925fcc0111ff2e05e9d45e06fd141209731745868c38f084904eace6d717284d82489624075081ddb9c9472cc98c672586b946e772b3ae08ca7320506f915a6b2ec7fd4aee78525d85639e76be8be055eee668b65814b898e2552b2a6c268fd29bc223d83ac1336f73e2132dfd20bac044fd6a72cfdfb671a027b9edde8d5620d6c78016344241118f8e5cd7840219eea97764ec4ac92aad01bfd445133ec766b63a731486c4cdfd08471b17828f385a49af3160778da3b12bb58282c1b73b47ba71c671b7181833ce3bd15924e58d15e0cc7c954a3b7e38f423b817cefa5e3c6b4627a3a689467c1712965186dcd4c0f841f01f04a502a75513ebbc5e1c00ed3c13dfdaa3571bae56bdfba18a4413b5d8768d07c64b986be1c73fc2900ecede06345b80480735e23e7d1c4b48afff7424aa071bb728d76362aab8d1db36f88847718e4e0788290d5aafb90c6e22bcace03d5e794cb229e61764700bc36176e9234ba10425581c8859a8283e07330b58964130e16ae3fc006af2f189bad1f02998cf87e5820c4cac6704335909be5b5f68783957c715c8cf52ed7dbb1165fc71c994f2846ef2f0ed281f89ddf43ece9bf8384458940ef42ddc30478766d32d4500b5cba7657a9ca2818015cb6e9565b92fb95cf94a39abeab30b46cb625a08f8c16261d141a68bb6ee20733561f17affef6d2e2d14338ad7a68a3d4da06f8e689bac851a8be09e1076bd711a44210e14f9a84790705819e9168d6b2ca7dc7d8e946f5e2f2d500160b92c32bda45ec36f1d34065dc23425578bc96397140f79ac813a6f2cac83ccd155900193f8c5169c8f4aa34f498bc0599590616686124bf422129d7e22ed52501505017d2741860dd33c6b8779a5fd779fc0edf0240be9481d3cf1487329ba808169d30583cdabe6b5fe175f9a81789221c1ad756b61bab063b29db01512aa08afe9a66d599dea55a60aa6e82e5863db19f32c524bd2a323ca57213412d91bc7a2d12585c32a8540c9914cb547e258461162c2882137ef5dc3688aeefca12c960fca6f051fe09413fd290748be2663681c813fc1031c6cd003b4346a61107828c099aead9298988c9405de8f65b1e8a0475ca712797065c0ba4398e0c5a2a58a4925f55da39dadb498b0b23d77b479aba53ad379bb4dc40126a53d74cba2b3fe0dead7e4a07da7e1f8f42aa02a1a6cca1d66a50558e619491e14ae60ff943218ad4416549e1ca05167eec8cc893191eec11cb778fa6bc25052143ce31ab12c69991f9194d562d64c71b05316d632244f7ce17175e3bdfa4bf53c395e7738eea5decdacef31b480ac49edcd5b220942d95e3593043e2ed811cb0520eaccb0612ed80d8d5f558fd2444f6e34ce0eed617adbd90f34fbaef45a88912a474b3615187af9a31b48deb623436b7becf3ea40b1321a7245d4982e04f0dcdce166d285bf922a68f0e88781abfa2683cd569d331828e1efcaac498d552def5f08e88fd637557f8d2973367005dc81c498facc35b913fd63d8042704224a11c7fe7847015540c1f52e271d3251b3c4817e3249a18079b69088d7f9160196ef2b1f51d008cb58e7a44021712216d249b92157d978ebfc2fce024a4b0c72032bb96a8a6f8ce64d1c17f84a261370bea253263ec9df5b97e46416ad12d60c55d4aa6c3c5d2249c4d01cf515993303b60372b25095ad48981bb280106adde12224e7e070837698c3068a9791899320b12543b9b0a439b361b9ee5d8dba695c49976421b80b75909afa92c65bdb6662e84775912653af51ac22e3258935c31a6951c04001944f8bdfcd08408f4fa0bcec7897ac1727a7a88323178441d822aba1b3d79013a5d8f01e0001121320228afacca286a1d8d05c178efe215aa1697f9202063793258bdbebc2209fda4f08f0138b4c19e67916fd36c4831517f30c7a8bc5a960504c3afc9c353276cc8d021a00680d2aebbd94475efe7bdefa4f828ab0c832f378967b232d7c2609b7623d625f4fe6e8283eb042b44b9b015a33790c45d35670e18a54330b3e939e4eec0a920614b7d609ac162de937662ff08a980d6bb2438f94f1cafe1aae14f7302a9bc2b28ea1848cdf04e81c0a0f4a66a902f0c4822d200aa4dd622346a0f56b55e503193599843cd8fbae2c93f90dc8a6a247713d2221a8d86cdb61685f5484e15c82e7c8bd34492040f129b7d505469031a44da7b1d9afda2b7c041f8ee57af129da08c02082b386bf94f02ec0b801eeeb938a0e7a748205634587a913444b31fd6f28fcbf0d4de2195cbcb1b2f9759bc2b58858ce6560b226e3246a1323aa99362259e886d5834e1900a360dec6b380d43eb5b9c30a5fdf5d2753cf712001e4e17669ba86ea165b1e58c1de584eb35f4552d77cd16d34c1ccdd62bd00db1f76ce426dbd5949adebaac777995e56fa996f14815a1cb068312e3e656839ac8c54217156cfab0e4288dab66e16cbe43321c10e3f49435cba39745b160bc34eb08f5e7353a8a352f2964e7ff31a45a55486fb37a5eb7afb9fe0dd526e3b619612b5911d9f655274b8d30545246160c79a6c502283e4ad2be6db63401e51022c52015b128d79f80937c8f2684d54801e44efbde4bd564d91ed9485d796f46bbee29fa60e226c717411e6240586dcc51a99119b632e74948a5b2a60c8de875cce6b8de5ced9f19a12284edad2a86514374da7fd6d7470d01f3e6717f727c7dcb1af8faa8f794cc4ba10ab6eac7fec2957acdf72fc082658ad5b0008fbeb724ede7796d602759585479fe84c1327e39be23be2a0c2a8395a88c2dfa1294a4469b2013da4ee7959e95955520b0bfd1e47c30a938c0b0b7931d50d7142b0486c13b68591ee824dd61b46594c42cb8640ea2e2adbc3a02b7e2c12260d7c8f1a10a40209d8262fc32e57ebb6109c08609739f732454170fe9daf52b5c2305e4f33b0faff9986e2d559fb831e13a6ba5a365be630c101e0444bafb8b8398c5312c86f26ce3b21ba72e814893f62be5b84343cee40e650913a869ba7583fbc4106155424767908e8cb788ad4b9fbe68c506ccca07e3c6600881155364b629b36f5faa3138a72fc672ae8247fe338b7f25443cf8cb98a88901588892b54a44e574d44060b220945738a12e03c44194d65560e6e4c54d71601b50a2d5f3d44e5cb982acea8a2702c04b467ee2f39f120d226cad11925d3d808555662dfe7c105d219a5d820eb896ec4da9033ca6e3aa3f6606335c44e132b7711928a8c646e45ae3214944e1cc82713a1d9a09ba531bafa3f8907d4bc56c137f780ec5f8adee807115a556ec6160a9e52ac36a6c5b59c403f7db41b277943d63260b28ec184a2f523beddc80263e225eba89802681b09a93904621f39efd6827f24026cb327362458e126dc78f2ea7b3e07b58beb9e5d64522d63009b1df72953e045657593e0429fcad29dad76b9c9139a668a5a74f30e2cdbd89913bac76936600c09128b01c4d2054c54e13932b7d7f00752ef62902ac3334ad36884b443effd1e07bff83ac72d4620dc28ff3fa08c6297c54d528b9222d9904acecff2225f56ac1d567dccda611d3e0f421f382ea4ade400fd15e5b2ac000b3339b36c39caf12d19e4736fc80d3a9259987812f6e0f999fd650aca09635b606cf4c6538679a31f28bf5afccc05c61e27a7df75246876781fd75295fee6652c078862e39714ddf23a22f898f63639ba56f8381f58335e300a2c8e64550cdc097608e736b42ad8740b0d4dbca4f931159c6b84a005d12491da628707159a45033a62755cb1da8830112c0076eefcb120aab07b0277c369edf027c06832fd855cf7bbc93894ab4336bcf543b30d9ace4ce4ca7a6b7a25ce98a8d427570e6c1da35223b77eaeb7eb4029fccefac6e71d41a00eefbc8ea53375ef097d549c0c8ce9fe4782a92667aaad41dc301b39363c5802824edf7a64e57728e555fd633e9edf5a20f1e19a0a997a76d4fc32b09c44a9dbc1eadb725ca6a61cef6db5043f19c3f2b33a800d5b964895cca8e8373847c460631b39ee719c5287e422e467fde3e120d7e0e5c0e68bb73f9a282558754e91f18398aaabd141456020626e4fc03b5b0a5cba7966951863c6c2f543874b256ef3be435525935d72dec4564babd93ea267e7a302f923818134b34b8962bebca1935bac15b296e366d5e30ae2a979c9c16791e61780c3571529fe4bf4643604b6c7e4cbad04484f0141a3d3c052881eac67c6b95a3290024d89b27cc39b1319ed1d1d36a69409997d56e460257417cfce87b4bd7b12fb11dce2a79f31e615c9a37cc3aaa48f867e5815ec93a345e6d0a7a4ee42e77f8ba6b1f78c1cf539dcfe41ed6a113eba460a782ec9cff07982f80cc795501cbce1061b59c167bd95d1ea8f523663f3b6005f205faf38d7651fcedac398ffa9f94a8837eba1092c1b06c552af3671921ec600c83b8360505c068913124b5ab5856fb3bec685cb7e45baef951ada24ca657ff945892b7ce6357e441aba810ae76413309fc1d5b340c3cf310da05b7e963e89c32b4669a1162e6b33e586e6b1a85d14ac6dbb5f60626bebed6bec98ad59966ca700fa2e9a2ea5b5aa1d9c12d634db99fb3a486cf396a5c2a65fe5135d3bc11a3690164346581d89146af13a120bbea4c390f44fd65bc4b2ffd5d474da46d093c41630ca80997ade9e1b4b4a567f603246d86d19e280437420fec5b3ad67c210443aef7df168c82cd0709e08ba2d157a5a0bb549ebede55b138dd6aeadaa6686bd7c986066051d7444a595369a8c077389c9d181121f673a361ef243c6bcb090ddc62b63399388db05551ad3046ac90b7ef69d6dfec2b38a682ceefbc6253741d1ba2fa3bb5f97b9c3a1b9d7c2c6ff1104ffe0c2c72b73221a3dc1346ff90e604b7bca13d7ca7b6b7bd5e7bda30e87ea23a7241f6eaeff7f3707488cda70ed3b05de7206619bc301ecfb64df655fcea1bb5f17fd52c93ce89ec17dbb4b8187f1cbd420e935ffc25bda57d48f697b2da6152223566955ed9527eafa3176da807361060e278aa661990b1fa5c7dccd6d652135ef206604ffa3ae346faabe7d29b8ecedc947198aa81dda3e3503b25fcce3a5c9d8198829765f6e583a10f6a8773c935b35db8a65be9d135f7ec9356d1c44f548d5bd2a1ddab9acd836a0ceba0c98b80a49b904287f35e12e50a7afa364118343b9da614e7f1b46095b2b97c0e54cb483f3c6910d48181bc2173b6715a2ac2a900cad2ea7b09e1d80485de4c496ec5f057c1c324253840ae6949b252b947fc8654bd68f49e415271c2605711380da7984ca6f5b8ecbd095e91f56ac5d354aa0d81b569b37b41cd96f6d085cfb63295c03a905093db1428d6cd28293b4d21655e8d97c8cb146a70ddb14b17857ab1d1f480bb13c6d0ea3fd352bf2536ab6379ba6a8cfb07148f0cc06fbdb66d30aea016d4821465710588a8efb648f54f19424b458e22e9adacac77ecd8b12ce5add521c563721094a170b4523a86871260321da3c65ed43b59e817a326c35b286c13e8988845f53ee71a84058849dcdf90218d3ed80e8454cc6d2fef60ba7b9fe6023ae8c28a82601bd0f7927961f6e49d3a3bcd87b9641d9b6bf80adf7ee254ce7b114d57ac20d34d67bd02fe0ef730cd24e6ec12b128299f88b65658165ea82831176d0418150d1d22746b4c7daf0d42505a98c9aca23aedac4ab0d42e54aa75e77a537df65f7a2bba3edc3587b47df52d4b47cc71202f83dbb982154aef7ed3199a1938cb06a22b07e267a1c92b1347e06444762cf53348107558b1c936171f875e2e32522513d4a9256fc8b67369f94a6d3844500cf5feac4860750e428d537e3d2d1c51b370816ddb2ce0c4864a30f0210ff29745d1ef01842e45b7295e8648e9d9d75ecdf2a88bb5c5820ac1b7b6571b8754408e55a55e0a07469e1d8e189de034b2f7174d704e9cf5f6f8f436fb03dea91143c8c621a83984bd199251814861b90628b7ea510b828031818a7e9e3c7ee9a93353c705cf92ddb1823d749ab016db1e17fe05fe22c2d28ed892eb61a8b3680bf0f8ce110f60f4e63b68d2f78c5a9dccac90f32924a775e62e28b26535dca6dde958273bba0d610bb7ba0909c922d38d7691ae9f2fffdcee54f7829c6d4153f4c191c1f0332201b27f63ca8f31aa7f6bede044aa14c3fd4406e53fe1ac48d15d981e779ae3ab4e5ba39eb05014bc49c37b2bc4b65a01ede5622a587e2c6787fbbe86960d110d7fde548a29117c02289d9bebe43edb5edf672e0a1f4f480a2e9258d748034f416c1e86f3e26d02d41d0cc10078b09625f03110b1a251ac7814571f361b74272bfe8eb9fad1fb25296a59306772f53e1d18ed3a63111897d15c3457078a906a01785742c35aeb3a520bcd5a83fa92b5e2f6706f3aa210eed138f74efeab01e5cb458b81c912b80f6dc88bbb782a0840fb631b9528742818dc49caa2d0bceb3e56e4cf6067b4446d5664798934e91f4e5a34fadf77e629599fcd48e202bd167409f19e35e24f0c5f0edcbb2bf210391ec3aa3bbd765dc939c09bdec50a97e1843d44b6257d81cc6cd4cc552373b821be11b9e091380c5fa41fe5360419843431a26e603b02daadbe3684943a7fdb70a9b0cbc38f403a06b692316cd51c8cef37fefb3917255ea0ee5721405432a099d0fb9e93eae489327225389640ab263845bea2ae85c2c1f2ebc0ae150b7a80c709afbd0a44747fb914a72bf06662f36639edba581a476c481852f710459a6ccc5feacf77cc16ab3c0d3790b6f45f7813f1a34319412e46985f2f424597700a1c2899302d01a9e0ecb1bf2623d8c2de9f38af030f9250e173d80ca5ce83cd158d658d10ee7cbe33808bf12b0718764a024302925f2e8f91a630f9352514140bae355118bc4815f3b7c1281ef210aecee053d6a8b6b9c1d36243cb2354104227cfab6f2e8e2e01571ec87e73e0795483cbca6bb57294e13dba5c2dc8701a61f5d9c5f32e6eb29f8aabceb1667e4f93db1334750f00762148424d90d5b0e91e859fdce1826abcd2db2e1597e26216b228a537c680d06b47a4590caa479f20e3592b6bf010aa851b3208eae09a0a9199cea0dd4664157a315e83a670ec6dcfe0335fa41da2c859c671669b3bad5faada86aed3a5139225267399a7bc54c270e51ea0153cd9c7764af930749f10f27b31107763952af12444fce6e71c0b6979b085060080fc43571e4a1cecaab0a3a7e3455592b08434192ecc8228d1f1195eeaf2043a507d079b5fa70c52964b882d82fee5898cdb33a361f074b6349e38eaf9ae1d6cf1142188f2de58076cd80c73b7d58d6f8479749d382dc8826cd327006d02147e370de0b77fa142e81d870c8feeb7246efd771d6dd81515dcd7f74afb4605c4287cb3d4f4214e2fe76939c35f7788f30b6342107be02798fe0286ab49c537fd1a5e8d171a4f945d722a37296fa8bdc228cc869fa17ba8a3c1ac7945fe82c32520ea97fc955c46839a7fea2dba4e5ec536a58e77107e31fee701bcb51d3ca2ecebfc9b6255c3e6444c4cf96747e7489f0b8d0161713bf919bf837c6ad28f1d7bb8f5a67149c8110e58efdc103b197daf275847a2a785bd08eafc5b4daf40343898366d69caa0c09c66eabd44019df2d70a86627e9924b65764c27d57845a97a8fcafe90c7c47dc0a7f774f31d55f59e77190f1746a184905a6a5658bdc701207c3e76343ceb37891a00e8d5cfe171d82e8885f0b79e78661b27299a59f8fe310ea7a4745da7f478ff16657143911dfea2de9324793ec38da7cabd274a743eec51d9e9ef156bc7d3443071794fe5633c45a88ff2de7325cffdcadb77b452678e278fa44704a6a3b1bc5fa4f878146ac000b483be6702cdc07709ac1c769a7e40f23cb61f9dff372a15dabf7d6dfc0a68fdcffeb54ae80f002963d4c7bc2014dc2ebeeb505c745b220eef651fb36508ef21863821672f52217122179392c21adfaaaf6b7804795888a52c0d7662c4502b66086846098652def19882b62f8c7789e4595121f189739ba602c83e9395d0afbcaac9ae6344abdb81bdfbc2ab8fce4fed6b211b0c0b5b20748e014b47c41ddb0cb8761c18f3e70e1081672ffbec2eabc5870b0e082b947d764a2405d6746e5c5f597f9333fad96717c608526f123f1cca0262dcee70d680abe85f95a2b46bccfcdf5cc05e1a18b5bf220f51ec6412d6833949972f3ae256b768f3c62e490118c9045333263c242a54c6fa424adb7be100e2315af087dfe2fab18f513d852c5c80e18afaaab3b3020d59a303eedd0a5c0288190563efb4a422b8fc4192e09d1888541b441ee388430724e8463073f6e05416dba9571f3500c9d64ab3bf66d7f51f83a11a0ab7de6627c69ce49db22cacac750919b85557d6ad4582a23249e5f3643015ed987bf4774bca8fe6d3e10aa7d013745818cc80f82670d63379fc5bf331d5c4f526f70ab2fb1d4d5384e36b574b7393ff5330bfd062b372a216d15af4e15fdb61aa36b67d19601a762038e73009e6c2feaef1055d3646aeaac2a127ae1ed1ccfb4fceec83713fe8d509621d37b2e832d8bf07c6cb19528f72fdece0c6372e88bfbe4afc023b69a140f798807c4e09cc0c1458d99e3c9500c7b5637bb95ed66746d13b4a13b78b65edc1a5da7beec6e3acc98bf863183f5d988ccad98f67032b59be00130e6a8f250233e672344d2ded64dd681e711ea54b85e93b2703c96461c14ad1005c9dbb18b1eb4aba3e721b196b6e5068f5e915b0f4d4844138c6d587f3daae68312dd973db062cd840d5df25c936689bee51906cb93d0dce5048f7c294e2342d401a555c0f766fd25fcfc0ae9c0d06f3a05cf277ebd274159b6a07a2031eb3b93a5868b79ae6ed2204a66031b75561ee7911fcd54b1b5ca33bb4026a6004039d07e6b8960400f011ba655a797c7420e665f16b18068b5d87a8d96547365878b167505e4ea6c2f60307f77e70641b9fad8691eca3d84a5030d85325b8b69c530b6f645a9d2d1606fbd11d82b42d46ac8988fc34c8e98327790725f6cc157ac551a043ce78208261d2b4d65a8ef7b4181a433690bbeeb0a9e414e024453f85406d8337eb1508d176804a45e4f4b17a56cafa9d067b2ecf7832e7a2890e0ad384b3450962b18e1353a91c837f4ff90328c61bf3b278ef290fc67aca8a810d5d1c792811bae2854903573ecb9ead49c831cc362441049a5e6ba95720d23a4a078d66554806ef59e26a20e33fa78239fb961c1bcf5106fbd513392a27e6319974bd8f08673ec0443bc05088c820064957a0ea2a108ae92a7b52d1d4341e8ae5f2101e2cc462b34412b2f7de5b4a29654a5206ef0a410a5b0a524cf1924a19e50456b007fbdc281b96bf5bad60091695e301a1ce0dbd3b18af9582ae41868ff08fc87ae2cc1362a0408251375060b9e818dba11e0a1ca080f25d7d16a3069ba9974cb50143ca9311f6942f8e3892860043065f6ad0012b5aa9810e31a8810a7290062b32d1c788e4c4103e4656a4619ae0e23b1c6642052427ca2039f1e57de9f2dd13d329f8b81d0f1f4db4e2e3dc1e1f2674233531037a86ed7cf86013909a28e2e93fa42688f86ed56f40a80535a3a786082d76e8f9e109a8bd37a6044b0800ecd1fe5c8b66ec3c51e5882c8060a28b33ac159e636c8175151221c8c61696c94b1e045f03aacaab2e397ce66a777794eeeef6876d8ec9ecaa861e725ded34f89b3317e1c669fe3676cc376ee859c33c9325e69247321f573cb964fd4385a43a64094156e698c3072463c19bba623b545dedf460dc7b39d973c13e2420aafcea1fd20f29f8ec79c03e2420b2fc43faa1cbebfc43fae1cb738f27023fd0000df04a7862ccc008fc208500cf9f8d89315ed7550d7995ebea57975ca75d9c068545fa8186cf58a3dceca19f17a6c1f934eaf51b19134714455184293868fc600bebed80d539f0b4449928ac28b31db0a40f71905b5132ec659e29e0624409b189ea397cd58fe99f9bdee804ec41f18d88da6de73c6666d8c5162946909028f30f090ec2cc1d91e88284c417e642062e6dbcc7850c5806135c9828838465078e84c4130f91d8f290ddf9f956e8167aea05d97b527ae7bc1970619ff315b6e61fd2113db8ff905a0083fa8e20e3530f21e4242222a21872c01b585f3ca0bde8a4feb63d4477f7ca28a05f37a4d24663be3db6d728be3d95e58a6f771fbe7d058330be7d0706dfee1383207c7b0a51bedd48956f4f22c5432a22e9db9750f9f617ae88628416538278242d678ef092250b9439d4fc63218a72c072062906a31f581d241ac2d2c316371c21850561706175300a84e33b3845084a7c89d5c129be00f33de6fb850b7fd41853041bb670f1441a5643207c03f1ddbe812496d5ddc2161bd62082123eb01186d58fb505f562a1e7b0a2478f1e77ca640fa78b667eb6a51c64a5fa87ca08146aa43efec25d3a677ae56655c33ed6e32e49d3f9cb741633a3195e02568110da964d842061165653987e45cf7c889555ce4775a40af5d4bb6364d5397d84fa7585b02317f562dbb9a7d3cea5308e72fe742ece5ef7512e35c4a27405167a52e416f3ae13b04822db813203b0104262b14de6d4ec76768cc2d87081c50958e060b3033d018b19e80ef4842b885077a02af8a490f2955115dfed24f11549fd832bbcd8ae1003001d20f0c08c36b6c040891b58ec061a2f443c89018d317050567b2913767a4ee0fe2119b5e0a97c87d346c69e317cced079b2fade8561d77bfcde711e7e16b52a26e52b44d1220aebb9edf149c1c8c459edf4304f06c9ac10df75b93f1ecfb2cce516242bb2b9e6fe7836bf4cf7c2f92d08d4b61fcdb7feb85d9e71f0336e870bd2c4045e55efe155f55755b829c82058ec76b215f95fd205290fbc679472b6ac32c68eb11f6468c60c1a1c62ea86dd97fbbf7ec61d99f9548afc318836c4289a90a845b851e07dece05f57f5d3c57895688fa37a4f07ba4a08fd1ffe78e19066b69c7a3af04529a5bcb566cb9ac32c1761c5a367c0c206cef55eb529a3d4111c1026b13d3193e5c50dce378f6f7f3ff81e82d54454f926a28def1750f18d85876faff179eba1865c4601aa0e57244308655c30368cd606431081e4021f5c0852baaee297ec3dafcfedf3d4f3e793f594f147a2404267789bcc9f08b7983d9650368cecc40de20c1367ced49854f79f159f9990b89cb940d6ef4d202cf48efbe8cf549f59ae2083ccf299b97eee2033333333430db2fb700ef996237c5172d7de576c8e99991932333333df54852fde0a5110a2207c37055110055110055115f2336354b20724be077178f5babbbb6baabb3b6a61e1db8272a14d50262817da04658272a14d5026bebcf7e5eb63d7e37c6149b13492c01e54ab5add8248af5c254d2189176e2f1c4c825d5248f2c4c879554abf6767d5396f05eff7783678d8e5e570f17e848073f88df1e2d93b0463562de52a6e3fb71946ecb3aad1262c139672b934db3fb44c4bf1ea99e9f20ceb45b72558d8fef21836e5f428a416f6791cfdde7bef598df9458e02d51a5fe8037d3db38e12eb34aec4f070604228e29c576528dac119964c9198ec552bc5772979542f1c221652d196cc6afce31d1565b9e43fa4221bc2541bcd0427d651b6665376a791844883c5222e9cf3a6f001428bb72035f5c4523445b0f41f92106f7ca7656173fc431202cc03c17ef94ed350111082528c6d3efe4312c206990368698810024208a2610cdd907bf6d7022e86c83229d6775e0e7bcf97a32e417abc1ecf6e64852423c8eaabcdd227a573c246ba7420324ab1804c113a5cd031242e532071f17244c506882f58c09c5982868885814449d51fd2952a56bc14d1c5173fd80229e90b761b861756b4f07164258bcb8261e50af8b4b082c51192952abe5bc536e01556b89e708519e90a1b4857aef8fa0fe90a15ed907b3694530924259a434a220e9a84994735978324cc7894945262d9d196946994529a6944605c34edbaae4bdb92c021092b9bd6485b84f0a852c603d3298cd54aa552c99ebacc2fa78b8a1aad91b624f1281bad660b525353b30d81ffdc06a5f543da6205698b11260d8dec3deee6f7861a3a9149d0cc777bb7741f2653df5b45d853beb7f0aa8de05567793a479a25893aa7e3c726236f9381672094493f9044789144e460091f6880a2418a09a2879e628248be8048be604e08a44993e661908cc17465ca43375d99b284bb5bb162454c10c5e16a50580a04058202814352fbf01043984396f4cfc1e0745df4d5b39efef15818c6fe4a808fd40395cfa2d6d33f5416a83ec4f2e73e29185921c903023d4a0a12d96766043f9eea881616308f6ef2c752194139ea0a4196f5215645790e47790ecf502647b9c951350e59356ef20c6e44aa2339fce439fc74554820ebe49075f21c5a6cf507dfe7cc574781796abe7a0a347c75159847f5d58d8cf9da53be7af59e52bdab44f95a5d09ff6056b5c207455f3d04ccc34152c0415647e9c1576f1c9847005f4d9e831b62e5b88ca673aaaba4c3476241eb83d8c43551e7541733c4cae03684be7dc3da78e7466c2091897bc1c66daa9bee083aa79a92744e8dd79f4e74eee944b735a9d3551d31b9757b854cf7f9744e75fb3af5d031f7a95ead1593aad97dbcca9274ad0c80ebd60bd494b1310f0e2c617091420d334f887ce083106bcc90c31a3198410ed88da11a72a0e10d2e55ec5085053de001e68a35d2a0f2858d2bb88d36de083460e0450a1b7af0c11b475c20430fa62053e6881a98797001eac6ff21f120c363ff90a8a0f90e4707d99cf276ec19739a33107b4c4db1508b9db55ab31229da51a8a0fe6ec8cc32f6bb95ccf4569eaa91a977757a4629adb5522965aa73e69cdd1da31d638a796b7c7f7140ca15f07eacd0393d7d095da17f40345e0e7408bfacf15007f0f6abd5deaeba503faac01275b11dfcd2031a2258421fc11e6dc5d369ef302abe93454fbe5962ffe6ed2ed08a0704dede2283376629ea1f718997b305f6686fc9c43792b5e9242aba3879f8ee2eb08408c6ebd1dede5b3c20518ca7d3de5f3c9d1663bb566bb37c3b19d0dbc7835f600939af07f496de33489900bfc4d592fee129345405b646397bf87e3acc6a9f1715feaaf41a3915bc46e6cd2819708a5d41e89c05859251a66e5a0754bd372886de0d5bf26861617d3a70e537d46f523b4f07facd152f1df563d80e156b804e6db037505ec6d455fdb4e4efee55e7402ae97509bf39d5f4f6a1cfa4949076aa1f7e1502350596ffb5aa6c4e6088d09f0779fe3a219590e7d1819ec7f83eccefad1ef45e227b105ed7e5d3218410c20b5e375e8dadc8b2cc6ad9834e53f887c2bfe98fa3f276cc02ce03f173e622b732cc6987f38d9aae39bd9d3f1582ed78f0d8d97c528f3edd044a7de39e3f1dea26ce720ea5f4c2c7416898b7d769810bf3d4cf572d0c3dc40cb364eed7095b679797380debf9f627fb49c4ae9965ae790a6b0f8a9cc59bab572cadd5da7a1f4334eab322a883baa7c63071702a8f5e451ffa88735df18a31c618239c1ccae2ac78cc297de8e58edcd9611e99edc81de61902a57c46b8a1772939857a667a37686c75769ce9113a140ba9402a900aa4428579824c0885776eb47ac93cf40b5efdf5aa88c49f1e7fde15af5eae98673a672be65931cf90c90d79bf81e93eccad30e79297d16350b4686c57bf68b61cd50f4d3f3468ba7b5eb34fec0f4300028717a2b9dc84bcd0ed7ca7c4c7c7232b6c60dbb0c622e646b2b771d10c16cd74438f61905b618525cc63e4c806ba77e69969add6a652b44bd1548a7647253d2fb3d4141b79c5fc8385bee5522263d8d1637cc8b503f5ad430d610e59fbc7ce7e447d4c79bcdd8c5e32954c5b69731ffed2a6719a45a56e5ec68e1c6b09721c381d8ef6d3a27ea6dea9c7cb5d9dbe95b6d20dead7367f69d26cda45659c3f1d3a84d48d104a218e1f51a3d8ce1d277503a7a4d2499a3469babc0c923f7768a8324cf769d71e9016a06724fc2479da09e977e408afa411d84920e92caf0f0abc823ebcfa79406e7e9c37ebccbd277accb9e8db5544d8a7d778b764c70e54f2f974a833c76ee3bc1569b7cedce6373e1dbea7d35b9166975b11ccb31a0da7661332f4a61f76aab27ef2f75de3365cc7a988605ee398d75c957515919397fce4252f7906b51b9f0edd28ca7b0a40f98933b9e5840cbde9e27025ce7240da4d615eb91e5e65b71b7aec9a70537a40201398a65b55b6740bf0e926a4371586aa6067a74fe9e57dd1d9a47ad02c92e4a900984080c7f4f9f77874ae3d1de8702b3d9de705e6261f2b1e9eba61cbdae381d5b1dbfd38dc8a54c79c624ecf74d1cccb7981a8476f4cfa6be7544ff58e84e95d9c3d3b9863985fd18d1bddf0e7c3b38a2cfa68923665e69cd3a7c733cc037d07f3f6fabefa3353f4b21a0e7a4fc9641de32018688583556012afa4f487e5c0dc80259cbc7778a7c3690ea73c9ee2504a294539a514db81aa8e79a7034529a59452ccab77392eaa2b1747b05a3d32b046335986f97cb18c241a1a020c68befdc532593c532b566b3cc33c350eab77f10c74d8f506317c5f3c2fd0c70ef364177e81c35af99593df5ccfd7f9cdbda39f5c67829f59b350f558a6c6217cd75c2bf55886c633df05713fd0bcb622d985cdde26af25afb78b66be9d07ce4d8dd55fbd134ed31503e6cb17495e6a67424fe748ef7c7c9dde984fc74a37887f367329c232b0c7ac0edd72a69ed4773dccf5744f4f6a6ad9e46c1c665efb47b535b7b3d8ed525c579fd5b80d3333f3c6b7b3f10b74f9e6d855e35dfce91a57af0bc3a2998f9fd19dabafcbe305ddfab0a9b4d570a6d2567371f2ada9b461527ec764525cae27580c7fe1a42ed3b1eb7627b0e1a04737600fe817d799e02fb7a6d25633db8adf9029d3547eba6d1c7ebaa9a1fcf4d2107efa76e6a7d798e0a31bb0c71c41ff8055ba95f5ce5332fe74123425a6773b0fb11a54c63111af3086c239d38df8e9a7dbad88702b3fa9fcf492641c7efa762516dde8ba9daf3df628d5e5cb55e2e3b1cbc7675e2f76812ea75eeb75ebede2cfebf217dd8866ae1addb081393314bdf6d1897ef5af45e5cb533769d80ff5e9d2e3f438247abd40b30807d3f0ab2961a36bdceb1c392717ea40f54aeaf3568e9b0a2a2d2a55becad7d60e51bec503996fc0bf160f5b6689524ae190ea5abc1c6e81694032cc7339f5de82d02acf7186b2e1763a677ac6f5f06aa67835dd6b8c31be18a95fd02f1a63918c524a23a544aac78da35c6a52280652ae73f299532ebb5d1ca239f518219aa1333054875ba098ec96f16926b4ba0f5f9783d4218499438765984773e89977b0cc43186f107f855ee1e5e80f6ca7fdd42c8a79665635a75c67b983417fe6bbe854bb2e28a6c2329749fb30dd8a649b47cfbc3bf955e3d7ed9ebdb7abb941dc9dee95451bbfbc0b8a1be4e6528782eacdc5f761eaedd31a77e1165e994c5bb55e4db56e9b577b3b083522986f5773936797d6e261ca675f2199af9f55cdc4512fd12e88fdc2bcfa9539f5ecd24c5cf5124c0396e0b53ae4c23c2468516024685104a9eec318a7d5aba1b22f9a76bb13609c7cf95ec47431f2e504266ef3124c03f630c1633e93b66bc64b92cea14ec604300de7af545382832d5e5147713e9d435de3e2db70afa94f98062ca1de0e05efd704aa67f7e2ba8a610eb7c020900c76d5231d4d64ed90d580ed9521acb1ded884df0501fcf4f60ebe0b02f89640d61c90746d7a877530bc4a02fdd3fba7efbc0492ce60b67cbd5d74e95a14567a77bb27e688efa2774d01e93db05d3b36bdb7145ebc4df8a343cc1b07dfc121f0e577f1edcfdb0d6d299c5efa5421b95898c3673558983712ba467c3550819c390831ff6d481e00a50afa6f4312d330220debdf86a4871833b8fe6d481a4c1b3b60ff3624042842228bec0c521936d0fe6d480090c30ba808b1f110507cb04515a57f1b121d61927842c6f46f4302d540820d2c3ea449c3c2bc39fb6f437242230c96223c11d2221d731f6eeef493ebba0991f8d3a5ab84c86702d3a88c3c812c8c490532c7bc497699bc64cab8ad84952ece68739f2e0119655b86b989cb7cdb7c724c824c2642808c4cfe5e890b6f945debd57d5abedf3cc35826c72e11d6447a052c4724baf04698676f43724b44186bbbd80aa54b84b1ac679708931c11057c67b4b992212b6f846dbe79c695dc722647624d9e5da2378655721f9eddf51b4581000b051d64783aef3b2e01051d885e0f88714298cf30c43a39d152cab2cc88e63e5c847a0349ec124916c6196143ffb621404698bf5782823722922c4c4792b753f2cd854a177b2fc9fa636dde59d3e96a91ee9d0b36afe46d81c5064f1da89dba9096d739f60a2db944fe90a820fa0c690a343f6f1091fe76176a5e492433836f5939264634279a97b620469ecc30c4a241cb8c5b1108fc9cd04ce7b09b3838a573b873322f253d7be90ac1a44ccc119624a608164df10e1609cd30c4d222ddb5246cdf16ded1337969be7b437820176c5e890098182309212d473637b9e90af176cdf86395c47cc744db85c22ccc1f2bbbaf7380fa020d19fa761750afe4022e98e1db1fd21a5f3609b497c0cb7725e0e2fb094703b3b2ab6acf1c4a955c931ffe760b3c56c6c494e6dba11b45f1c1c23ce3b04bc5f33a5419f52583dec130743d74785978636e0743abf11d3f3ae4da2347a487183348d3093db3e04a869230f37d5970ee8420bc46348bdd09229012a187ad294f7cd01bb324f635127a0e8329298b0c8858f43285af934230c64c90a43c3043dd78538091828d7fdb1019a628537760fa8724851852e4208b4c0a2f2dc4163e93f1e93c87dd49220e24afbdf50179f1e93094285aafb1ef548ee199a1f8123393a625c0ca081465bdab4efa24d24450d0c98f0e2449d02e91024f1d8707bd360c51ad3bad1becf0d7bfd60db43411b23a80561759fef4afd5450fffb621150d3a9d527a83123fbdddbc4a709efa4dea8d2ef893522d9e525acf401d3da5ee8f7aad67bca359656cc6a93edbbf56d2126ffad74a22429e014605638a242a3688f2efd4aa819937bff2aabd846f1fefc7fb6121f0ed19c717b36ee6a9bdc56b16b6f3f13e4c50e1018144df90630f50837de3d3810c9ff6b2a8b63eae1ca5e428a5acd563adb2ba740833c83c902133330c82fcd5b9c9d095ca45e5c242af15d25abdbbea75e3c725a07c10ca18bb2594af6e516b2da1328147b08aa7cec305d9c3c9e4cdbb425aa0cffb3aa76385517a0f0969810e03c6920c253bfca979962de3e412df1b5ea5b4c83cd11d75c3636311e0c5e1d52a084e848f4b9d93f78a85edde918f659e19637f5fe7155b148e75e6691f3c82f8f015dc7c9e0ef7f08a1dc7ab16d684e83e2caae38deae776ab2e1616d1f9792918ecdd8aa288f987ac535876e880e8dd1e39d8108cecf943f134b1afb585e8db6109d09fd62f3a37793001fd74a037793001fc74a007d56fc2650adc32e5e94ccdc629d2dbbba1efe44fae89cfbb30ff27d724c9bba07d3b8c0f0e791a8dd78eb9e281ed98a99b98caa645596d8b5e8a3b3e78c53ba87f95df3bcf8fd0ccbb50f3d2dfedda630dc3c6efeea207614944df2e8f883ec621ef07b4c836e48129226fbcdb4fa321ea5e17a2ef27889e49a321fabe40b61ed8831dc95e29c82cf34ccfde53ee89496a2161464bdda43e9aa845cd128494fe6497b66d58493355dbe3a4d509a646abcc33ab183ab41c5b107aab15368746dde4383a34d38a475c31062f0861a510c219a13c52ea3a90d54842573df2aa86612750bc5d04aadb4fb436568dbaac343b693e2c5074ea9d8f19ebadbce20ae6eb7d4f27bb472f7fa4669fc7a734e35e8c5e4df01d7517fa8d4ea0b911ed3e19223a2a9a12058a0c3110b19e4079608e60c10d57a4f1c61360589a4311f2000fe0888308454853851896e64230a4a9e20c1ec0110711d2b066b82b88d272c10aa2b45cc0d27c86219676dfcbc9a84f8eeba8d7237b39e6d865f2c203c55ac49fba0f23b1e58179db901b5dde2e8d91655e618cf70259641e20e92f46248a9e5d2231e5d99b9b3ba1398122ecd3250704d961f51686be5eb8d5dbcd14eacf4bdda7af6f2026ce847a0b434f857eba0f0f4d00fa24d29d02fc76224db4dc0834ce0334227abfaa3d7eff6be3557d3130c49afe5a3558e35fab06639efac0ae9ebc3468e6a5425e1fae31b0b09f937f4270a89bb3a851b47b53f9f6d50e6c04114f072c6068748dfa7bd5e5d55f46027bb36b11f3edd1db598b996fc788be7d7a3b4b2c5eb0f8f04fca8979bd1d567dce5ae7acb3ce8741ace86b5c73861a97e605d2bc26d6b46b9aa6695ee33e9c3d2d6e3ff51aed701a69dc8ca2386bc785016369a17e056125d853af4d8b5c981f31e531cfbca963dc7529f7833d360401d7dbb8845da0ccb12ea3b03b98f9dc7e32c73c3b6d3fedd36bb8396be664ed629b05dc09208e08430d2ab02862bd97436f87393bb6fdd407c2bcfdda86601be618c64587dcdb90e8708bce9dddee7ae99b67282ebe60ccc9a5d32fbee4da0bce7cc927968d03cae1dbacf1c975d1338f5b11ea9a5f9b90e8d52ba72272f2e8278f57a5c3515ef28cb7a8c323d074a0936fb5cee8efafb71529f9bc2a1d57c8d05b57fda0fce4283ff969c8d05bdf38e9912b79763be926dfd13bb81c5c6ac575435f737bb88be270b81d5e5da0d2769d5724c02081a1a18fb793db10ea926af272a17702c335f4bc614d9840e0352af4d54d9a93af97bf0c86afd8bd86bede4e6af4fae015e4b68e62af7fad228afe421d7cca8188a47fad21aa7cf778d01a22cb67a6e7fc63ebceb30e647678a7f20698a733210523ad21889e3d042b8cc004145e133f3695f2b77ade57b68ae0529db09bc6ab777dc88a887253c6668c121738ff5a3130e211878f2c5a3058e2841f9050864d97284a4668453a6481f2864d18ad8b11a020418896119416963224d42f2cf4d40e1d395036352394e414358c2a468d2d2c54a294123eeaa5cca84718a78411a749d094e82804f540d0519fbd0744e7abfb70163d767d3b18a2c64ea6cf306f0d106055af909b11b6ae302123bd443e6fd71d35cacd792b0a4eea03db550440f761aa83f78424e0a13f7f3ca2f4c975432ffbca16861cbaf46e97724aee7d4f2963ec66869b10d85748029e086f5c0723f4068a2e04327b15d9f3223042bcf119e790ca81af40797edc8b3ec4cb17deeb85a2a543e1d02a2ad34f9ee8d57835af58f92ec5d622bbc976e0d6bab245fbb9e14a96df4050c59397264d9a0c3ae41e2ca9c15b186dca226a52c8e8c46c2eb41a514b2db8341d3021a2c0561043789414218820ff88bcb10d6422af4ad501edec75c551bc20e07966e21a2cdcc108cb34b45a30e56b4de93d1d213804991a91a19a0c79d19f1402e385134e87504a19f98d27c01cf1f17a66fab1d05f2b0830ff8424e0e79432c66ee6788524e089cc4d48029e2f13a1ef7ab6845d844c051b5f7c2d20acd07fad20be680581f428d80a82cba378c544ad2088f8e627dfdd0a82cae3704e0bf12ea871ee8a082f720c6334fe71c339d429bda1f56a94a27845d3d82ef5347593848660044b825ea0443c6859f9e25f0b882dbf807f2d208e3ef20f947c6f346fe41ce9f2c61c6c472223cc73640558027ce94942c03c90875d3a8c0284111f9dc304617d78f556d267eaba6e576bbc9db5a9d4bc47f2762b66c89021a77e681ce1c32220d88659ba14b511349a914eb720f3aa88b44f1fd23eeff4f1b17b4eb138f42f88b97e0f877f54e1950a8eb8700ed35a83ed60111f75619ee83c1d17f804f34c2e13e2005fe81c3e595ec1a7c31e75b05dc3882fd620a2995826c201b79079ad1e8640a60cdc42868c1a37291ef45aad5e1cb9f792bb6fa7640bea7784e39199c0e7f72e18c384617a91eb1739cad8cd31cb20c7662d882791faee8567ef6caada60eb7f85444e9434ac4e198298d5192cd4b4db05c5d5d35cbb38676c87533b47d36e97b2d235cde116ef2d8836f9043e218ab3daa35b3ea13d7aea26abd3e9a642d26e720bb49beed097ae0a09bbc92dc06ef2a1d7b8cebe962d79cc37cec4bdced13c636c7a1614ffdaf2d3c298710442f6703a1b67f8e7468e7c68106c7d22b85c731f8cd58c1d87e37b7e5dae7906294ff88010e0e9b447ef4d939059dbf9b18a2b5e3510a6300e2043b0ddea1be7e9b457ed76ecd44b5ce51cf6cd7dfa0a8a9fd1abb3a01851175fccf5d959c4152b9d7d151f10f874a0f38f239006eb83420a2a2409c10894304fa724f57e6405e6e9ae167f72b1e260bb77c4734395a282121c79856055f041c1486c29031fc83cf5968fabb700bf7ad65f9eba8a4874dfe1b4cb531f9ed0e1dd45b7698fdcc559efa2d72d880e8f5c0e6f4e3339e6f1b263db10ea28af1cca6b38200c73eb254ea5c3df97fc3af9c5a97478c9dfa776786a8763bee3aa74b88a48c94f9b0e8f1c6a04afae831332f425cfc17599574771361c508d53af351c50f5d3b5bcaa3d25a0eca6ae09bc92f5def02a722d90f003c57ce4ded3a9f1bec7a3fadc8a50efe83e2d447b3ab2f474a437e78357d2a5c328d21f8e44f12c834990f6c9c4039811f938c87510c62a48181202be57ebbac2d6d8b47070f4d0377f5b91ebe630699ae635395c3b6d9a8d3f376d9b437f36db96656cdab82ef34d336d3b4532df5c85ba4554d92df29ecea5993cf3cc847135b73b6d2d1c6c69e1200b36746dbe4307ca2faef3bf4a2dbccd4fde596cebac5747655b4d069439f41c1cf4ab5e8edd6ec96335288b5d548d3fbf6cfcba3ceb29954a289f156a7314ca33579534238f393fdeaeab509777491ef352e976aaedfe5cce362a2235eee46bdc4d8e71a70b9f67455c88ccdab5c57c66aea32557ab44833a24eb5a3367efce72976faa222737d552e68fd3b86ec96baad2fd395dd37d4fa76aa6776daf5bf21ad7f5d5aa89eb96bcc9b3f77eec7839d2df8f6ecd6b97e4db977cf5ccbab6dd6ec9b756b993679cc977bc1ff2e5b4cb2539dc8773dcae3aaadea048eda3b8ea355755331b2e88bf2f7f03cda0f874baac7f5961bbae5e56d8be5d10fcdb75dda999573fb9a9bb5e15e69a57cf7cabbef39b67aebaee8f3f9ddeecbb7cf3da98b7a65d5556abe6ed15f3cb5f0d8a964d0f7dbae58c07b8092992f98575f63bd5f4c92e1566c48b5c5e337fbfe4a9c530e84bf8aab2fb73dd771f757855f3fec40b5ff5e9cf0c197e5faeeadd1fcdb7cbe3c6bda7835d5575d535affba3aad255b555f5fe2459d21ee7f5793acfe535f274de916a3af5ccaf2ea5a2f707735655d5745575eaed5755ef8f6ade9ff774da55d3773ad2dba9e6fd71f913efbb762883d5cc85c585c585c585c54e26e5bac2c635cec64f7404ea278e044fe5f0d3c9ea50e5b83ff69e9c9e4ea713d72df993f326a488759453afd1e135394e39ae4ac7fdb1d4514bbec6e9ed963cf593fbf489db810a8a7f72558d6b4eef09e5d651235c575c57a05040238c80b2b149ed088ad715355cb744a55af25d507c94d77856c30105c547456bad0d8abe044b02c3b0a8a427a9e769c63cf887f5d59b9c52b71c0fcea9f7f1aa9aaedfcab5d6d8b7b2c6dd253baaf5763ebed66a6d2ae5d7e58565795d927d8064be25d7c93b6b602f8f3eb9c5403b5d900f69c43c46a718c5b0cb24c943a759c5710ca310c26ee753bcba280b3e1e483abd9eb01aafa2c7962263e19179a0e99e79fd0c6a357a7d6b7cccae9247a67273660fcd5732dfc128d7179b719a12765e578bf33e161e998f57c943f37d2bb3131714a9542c80b5f1fa99a95a3f797bca6b5b1bb78e61369a635cfdb711f9f1dadb84d85c253dbfb9e6189759cf4e5cd7f327ef7cfcc957cc63e327f7fe91638777a93f79ed1f23f8c95fffd0e127e781f9c947e05ee7a4b8d73bb8d7281f8fb90e2e07e79d83b90db7e295f513a7e4253de6f6865798bd295e616ee278f00af312b7c3b1d0f35a7651bcc23807f3197d3c76a94313c7277548d7b3b3fa76fb9b5bceb9d24df58fedd6cea13eaf3be94e64c886a59e0694f77f2d34c69c4ea7d3c99ff519bdd36c972a5da8d641935b1b454a157b9252e514454a15d8a548f627ae837f3ad99a9ee25754d238f306f8d74ac30b1a5ffc08ff5a62a4f8ac356944ccd2447011fc78a19528b084f714563939f458ba91a23c7635ae79677d8b6ee21effb07193a32e8fccdde5d16db8c7268dd3de9e384d096bbd3b7985f6029d6e07dfe456f34ea3251ca0145882e90295dc7ae9024d976fe105e92d55f958ba500aec11a38c34baf4254a521348fa65b5b736ed761362aa907bd6724636d19ab8c7ab68c3556ba14dac55a4549152454a152955a444cba72a2360d837e6df61d8e3c18f3d9dbe52603b1edfce63c7478f0929cb0e8f8ec884c0df61db73643a72d8e76fa26c6ade7befbdf7b8e832953a59d5bcf5ed77f5c2c1a69159fb6266b250b4707b2a8bf08df89660803027dbec8b3ea42f1e2ce4c7498eaf917c5bd4e0a81d799545eebddbc669d781edb4ac35f8d8a1bfa31abaa8e06502f986afd1f48e4659179910f8d829bbec8b991822feb5ce18f309f8d73ae3cc370bac45a56e9887cb43ae09fc6b74f9510d5d3e839a51b550b454c766d2e3895eedd67e71aa76f8927190515bcaa685a2055e1a9c55b901ec3c629af817b33392f871a682182e12a7d6e8a3d654aa8f5cb63e32285d6e1368a77436a5153e2e6d4386e6aba720395409f3c4c3bfa8d8ce3e84337808c36c79c884e395038453761bd98b1042080d60a13fc84456029b3dd52ac7445020bfbd72dc24a87e50eaef75f1f613e1b310b6fdf40468b66d937590417f7ec6d1bf5698a3ef4cffc224fddb7e26bcd03e0864cf46678e5f0bd1044fffea2581c07e78e34644c8106f42a2b3f304d8dbe746a4037fb9ccf4d1d9e91624f20dd414bb18bd259d52d22965bcb6f95a8185344479e970e96026d3ba9e7f9569d6d12b54224c0ca615dabc3746b5b61059bc6a78648461412b0c1051de1b8afad70af3447691c57e4450dc80451262bc11052b26f1d153644433c210000e1cec6089379a10830c2158b106af59c147b565469616182eb0230a29a820b92b3fb460082a7ee0031747acdef27db49381325318218311645c9185d5570db1fb88ddb2a7d88c193ca7bcfe6b99f1c477383b6e5c3440f99e151c6024be5b7d0ac2231eee402c021043186fb8008b1727aa18bac1063031c881941a640083b5b4c01051e1a839580307d3e10fb9946f0a117d735d19533c24faedc74ae6e11ffec3592a3c205dc61175540a8a964e2be2dff6539f7cbcad5619443cd12a838be3c48be891f1c5185dbeb4c620d31a03cd992a2d32c6b4cac8a1550614385a6140c1d202e34c2b8c2bad30a888d1fad70ac388ef6e5e8631c5dfb8741ce92be93c3c324f94db0dafa4bf60a5bc64b09db53b4dbcf72630f4aaee8901af504ae950144e905183278e580194385c20860e19e59046d1186068b0022c171b454873e5cb974ae6226a8151c667ff5a6024a501a3e8eb3f325aff5a6444f9ee756991f1ce45cf2c0483f1c473dd86d4fa784c576125b7e93eb0afe50598ef5e1777017ed73f9dda1e8ee5edf1c2cb47e7871b11f8f1eeb061a7b777abe7f7def3312107d67f4e8eded1bbbbfb49d67e415e75511fc1a2f6eaf4763db7db59790abb9d850d7bba9e9dd50a9ae99c4ea5ee108b81bc6f20e890c889becab7901678bbcad369e7299d939414bde9b393400489a00b4122964f10fda962874441f4e7050ac297c94bf3ec2ed0e7db0dd57000fb588f256f13b13a080f3d0ae13905e641018c1edeaacb0342795000c387b77adf3d2efe08fb82f0efd98012042f69788043872eacf69efed1eed33f5e1443cdc82cb76c7de9d212e38b34a816b4beac41e34591b13645ffb5bce8c18b28378c8172cc9831eccfbb15e64e38275a8e602677c29b43cca31fc13cb2637edd239af71582aef9e625bed6760e753759e0b1a0f00e3fb9909623d6775817d2724487b343e8991f819e650efdba47526eba42264f5d235748cb9193c32bb4d339d44fbee39eae1014e22be49d435dc77d9d1339acc46d57488baa02d1313f122f17ba47dab52b04a7740e855748cb11eaecd811ccf90a41a23b4548cb11e87c8584b41c6187576873be42257f9d53bd66c7c61f2b873f1644f963e5e084b41839b90de72473cc6b38275a8e641716750e75cc9d68459caebd1d2c7aea1a177b9d0869b1c06349ec3a91fe581ddcf2d44d5c3bc63d960685fd03f6b5ba40f3d9e3c725239591e91678ace948204bb24c43cdf408332a189c1e253d65b0d04306945dc928021d76bf377768b1ab18aa69b4d5c51301a512a8244d55b5458e32453323000040004314000028140c0744e28050342416e6e50314000b94a0446a489988c32cc751903208196308210680000800ccd0482b003e6198211cc71749f0ef63aacb2da0a52367bd48667896351781ab0237eea66ce75f2237adfe1844347e9c71c8481993d92f76a471eadc39f11f4f9409c3a8a64c8c6b65e7509b3511596dbffe2ede8c40b4883bbcd3440085908fc3d1a067c3260e039346df1ade95aaf0256bef702024eecf2ada2d6088647f7ba5b351a3eacc095d07ef27b1f0c872214190022c15e94dc711cc18afed05cbd444dfb9976be15dd0104d1f13f3e55a20b81a71543c552e168996b0aaae7b908c90e430181da5930bd716190bfd4710a1761001eab7b63e24f1397161e6a21c3b1c66304c0453122a4eb59dd45b054a99712ba430b7bcd72f12f342581a5cd1f658a8da127de9a396ffa16655400e4d405de91356d692df908deaf03eb3d46f0f97700f537c1e7ebb49fc2ebcdbb50f1060097354e873cedac64ad5ddb324c5b791539d8f7b1682dc5e50375a7c67119452149e15d2e6af0383c618ce5c245f2636907d7061b98d4449aeb66a92b7bbe0a88560202dae977a6d74b187917b47c5372f35b299d581efbec967c1314b07f23cd7c2c4c67e530c5188f79423fc5d26466d44ec42df11a40ea8929e526a06da7d81f65ea692cb2460aab8c596ef215ae8484af8c92e506c9d58131bf7f5f491e341f3271844fd8346b834bcfeee39143d15875fb5a0a765ea5bf9affa743822d69628867a9193535ab8f9e2e0a08aba57fa0ccca94f0be49816a294e7669e05843e1d2d3975cabda72ca70fb14d43eb37f4a410a1b4af2dfbd249f15718dc310883ff2c5833a518f841da1bec9ca33537c7238c60ab2a9513069542179b79255f6690fed842241b08e598a3d583030b91fa54173d25bab0c6590806472d7889cc0d5b55c5b0ee06333c2a8af6d4823ee16ae100cc89790a880ef703906ac112373e66a552806aa1839a034d6b218ead6de6720d0bfe892fb3f6e7af765a0b2ad7b49b850e639093e72c44ffd530101b5344ae0517b96470972a2e5c0b947dc9808a80856c082cd8e7eb0a03168abb58eab04fd01320281615a89b458568e5afea21ab6b40a7fd88c1c8c5b900887f58ee1a0aab0c759f8acaa02797cc38e2cd9d969ad4fc1e84b13517d6d096aa13ae39e269278e0c238b907bdfb2b4a24152ada34fd1ddbccd79854ea32979e0cf91454de7b05d731e698bb97b0883c10e4bb003a5d54beae365e414ec808d4eb6efbbaf954a602e0a364a4b684c1fe2ba393130ffe8b51a5c3da245d2475a5f5cc3786158e164702edb6991766681f7ff97e942362552f512c4c4777c48946ab3e51c27352613d3fa407148d467faf5d51c639a45eb617fa26184b055a999ec95f5a80444fa0427fd298b8e86063d8f7290c3b64dc52e4fe105f888947423f02d73cdfeb0e853f66b9103beb067af7eb737c5afeb90ae4499b64b80b2c7cef5847ed27281ad1c0d4f3d0fa141a407ffa07f66d42b562019e04fdf27629e4d47f5687cbb61bc2856af50f0c2d530f3706596084a3835f51a48d36fb8f4652325ddf37250e91f63dc199bcaae96a45f331b0e437f77df7cbe2290d4664f1be4e74802580e4347aba8d350df210c493f7be3991fa72d41b387f1af65d3150b1ffa3362a1e5302e1d7ea1ace4c340fdf78b5f0ee93060350e1996e9a17b25408ce1874fb565def3301c65c5492882b5d48e6977de87e32330040e5b2cf33b273105154d86e3b97b57762f1cf8e73b524c6985d34df7a22d303bc5de3ed1c04353416d76c16c680fc54bab26594493ae005a58b426764b51e51f1c31ef034b9232242a92d0ab6283aa4fa5858938fce22b6f92597ea9b81a88439905bdbbebe26c3b8007453d83807656d857fbc84f774d911883b0b6c9aecdc710cd464da2615c0f642234505e9848218c643fbfff6aea68bfc379d85b2b5854294c07619ba95d2563db06dcb29b9ed8d9844130775e670741b03efe7857bbefc7b5204cab35775e417e1c40f492861b409b67f9fc94ddcb2023810ec10cde1f3acb8080be011897dcbc4ce109d5dfdfcb05dc8dc66406653c4c8658803d3493046883e39d9c702479da8a4281a4005c3ff912001bf6a23da60b00705be1bacb76fdafb5c5d83f525b509d0a280d21f79420a91b8bd6f03a497322063d011e9d1a45db4096bbc36e3364d7a606fa9542caf7c099f636f72274ae783818611883927a980d52c6188710d1d2e1d7916b12ed3ae9b7c3c3917f0306711729c59cfdc9a8080b51b8cadf50b4562e44b541f0f16c5e5669000921f0ab994942b3014311f263a6d099d865b83077c377e40f59de96b96c357dbaab49c75aadecb5555add677ce7847b89859d23e52adeb4f596518279b1cfe5c8e41a0fc5acc23ed9448c72e5605358778361ca595b7dfe93090d786e8311444913fa660e737b958d99e0d67d95ec923b7a766097b7ff1caa68160b6f1ac09aaaca53712e2b76b9dd36ff2e654a42ce25f9ff5ebfa0816d44611f55beaf977fc57f549c1f1027733511e2864189ee192806a1c9411be803a1f3111c20aff73c50491aa3cc6f41f107a9b9622fc1a6df7d75d8fd437f51e20e2e803ef049627b79406fb70560532803a28ff97f177fb7c1335f8998bf8603652f31fc10cadd7cce0a0894eb98e4cdd45ab29ca8d1e45a5a0670e4796878b5943562709f526be05066d8cfba4e04346e5da989b74d47e7adab024a4014b6c98b68e3d8002319913dca7b56b81aa4f786b2bb6a30e44f94426efd96b7a7d9e5c67a6dcd30157d57273e014c6874efa5fd9b182567ec506d1eea7bd91e8f38748e37a3e44548e5132a318fc4de0a019bf484f969cdc4ba2da5517456daecbfc20733fe9beb094e9bb9c77f006d32bb3f01b84aaedf709c748664821bc9275a88b02eea30300a59160de09a035e961e468e5b6493f57bb61e11ec06031124833a81a8f89409fad71a1b399a52db117ecaee68cba5a4575efee445108b5815f2f4370339b6c0279709b8aedc790c75d2ebc2b5ce483e2c7e30463f39a58c32ecd0cf5f256539c935435230d54f26e64b027ee494deb4a355366e055c798bc9911198d77ec10d241a61a2033cd30884d94687308fde5bc077617e6be8dbe8ea171d3e2825aa23fe3f138798d3aa1a5c374aa9ee330cc9007f684d76e3be467faa9ee256a19778449c490e4f04f9a8bfe408f384d38057f375bb3d2e937ec951088c358fb960f6100efdeee78d25a8de8a0c0d7ec911a57fc2013d83d09aba8bb322bb0a5c45875a0a82b1daa0bb5ea84ef99acfb39baf93ae407fc43c8544095c5536c6faf810edcc9491a368fe696df6b655a888b9a2fceacce03d3b5e29f38cd1a501725c1f1ebb254782fc26d15894cbd15716e107e27e8c7ed7ddb0938124b0f9851fb6f6117e17b8528dd6f61bc0616242702ddc728e235fe446340082c475fa5ece8bbc45df4e4bcce3d0bdff290c29b1eb7ab88032705292f69587240c036c725cf4f53758d84ad1404caf5ebe1d7c761bb15a4886c0b3cca7dc11de39f16cce0d547f0baf30d79e50cc8e8de793c1bda97b5443e0dc00021d8c393b9ee409adffa4554258ef8d960d47670fd8b13d3bc18ebc8c0fbf4cc7aa9266463c6706f9403b93e24f222fd7c8d20cf31e28cf2db1ec4064d744f298711899fe0f8fc379738316cb03708533ccfe687756c1a923b14324fd41308ca6d762aa65f418722d02ce9d7cd399a8a99b7705fb0039d973b0c9f9b9d00950dacd3fee7a2a21eaa04a3272641b959cb4a83dfd2d1d98e7f38bdf0d3b268e0b35500555b972f61be89d399bd6d97b27bbb412af63d52c386bfaf873b6905a1cf7f1c2fc74edc14fd9d374702c032798914891a69ee47a8617ec5ff26044735872ee2069a38ab50ab66bf2754ecf3e6af18f8c0235716e963ada1eea046f9653a2b90e3d5f7b238ac91137ef54665964dc587b922fc4fd0f51dad910837249b338bfe21f1184871d58eaad87a9636668382f44eec07b5fcb52ceae219d2d2e22be07e9fed704c3229f548922d45e4e27933b2e9af7e39e0e8399f952bbfea3e06b39f91c1e2324409d8f0f4ffa0c5af8ac72afe90a71cb5e94630d57d5e60241f0f26c499ae432aa654241804cb6bb00194d74615c27dc26063b295b06937e2c35688b127a59f789c2ea878e33519d9a28b4b92b0f42d3b70763ebab2369f19f4b5a72cccf02ccaf8c21fa28b441619b0c343603361bd96958294886e834fad4df1f0e5c7ad4396b4c2edb221a6bfd80e1e42ff94b573ad83ff34f22545fd68971271a7fc3ccca10c2d51d4493763005fc6a88517e59b9751094aa771e885e21b2b8774e0bc54514eafe34ed21c4292151681dedf5d37151a477f38b6a5681af67c3258c9dbcf9d2670565906bb85544c466d1491df1fd40631563cc42441efeafab047c27230444abab38c63a8ac5cfeda9de6a9efb0ef3b55a093ff1b603daee9d2f0be5d621260151ce3dbb1580095f0540bed93f2e228233a1601bfae968bbb23392df8771c106d897417e184000cfb4b3338bba8f82f07dc4afb02d8e0d97e30243c637ac1c65a44d651eb121a13c188b088632bde05696250bac9ed49076cc148c6ed453ddae3935e90ad3fc205cc0a88cf74fcda8a8a2c926b81ee9e8c1e438c1df872835110b2c5e2714906502b1a6839be068d143c35a1eba0a49b157161e5c24232c1c8e395acb6d2fa4650c23031910b20da8afe43920764c64a93de531bf787a4f4eb8010170cc0576d1bffb02d7eaf9ba34eb95d54cdfd5746a0f84c6587412002e6e2d8492ac57150c13ee07cdc234cc59c3cd8aa1c0168e348a9a52c9a9e708069007c5a5a8e579716612670c2cf68b699c6758964c8a75f8f2a47c27173c1dc4665654af2072b37d2cd341462382b5bb225ae7e0db6d93c8ff4a6dd426a0f7ac9bf5bd4cd7d24eb6aa654b4c8545e16a69d1ff3ff01ea437807f640221a791f73df4f8b1a13ad3b20818dc301c47c939eab13ec85822ff0670900db945ea8385c4597311f3bd6067bd17ac29cd78a1c4f86f610468001c780703125714dc13c7d9a529cc03c875f8292deed1fb1c417574c431ed1cacde80d28aa69950f633c05a381c652e7b17fac1b935e04029f9bad0ca8e059d30712e34784185e0463d7ad6ad607d42892939588c1b0d81d0b95c39f6ab04670fe3175299d18c6692570a79695d719456b5d80583ca4755f1906a1b0f09784db320c59c8faacdfd1463b21393c5c0b579673f2ea6a389f9072443867253ddbe16579aeaa60a2f95dc9c36866db09335d4b473df5b0dcd6e77e8983efca2b81a0b73cc0bbd8e7a476a7b80c5c5c88bf7fdf3220d499ee76279996879abf018e1af76d34b2e4fa365e02e635758b78e93ae272466d49903c3747c0b35f5d6c1d53f79605ed9b2bd2758ce91725bdd599f22257261ca7a9a43f17f5d2c993c927cfc1487fcf08e4f8d57d1ca5cd4f4bd658a0454817058a069c85f3f7610bf0da382e202baeba5a8746b9856149efa5045ee0f8b02df86fe68c06c6a95638a4987d80f90473155aa5874e07554c927cd0c224821af7385bfdb3ba358814c80d9a12829ef7c1ca5441986965b74888f58a6b8c7c10f0d77c0753e1904ac761880ef4baf9b59b3e06390ae3bad2d721c7be3704e930c838b4474ba48cc3e6ca760a5f469bb413b7ba57080725ea601982716137a10cf28e61c7a64e2b9db4815b041541ae8a0ed3880ace590eb9c7475e4b35fd75cea89aea23596e2980c6171f7a037fd76415d3e1ae01a3c66c238f1e4a2b07349d15995abe49bbc034dd7f979cffcb0ab60435ac0b6484a164f0dbb165ca11a2d7c180f83961be0eaba4b2670d91479558f5fb12dbce77e11a25f8844ccea88889020d71da68ffe0a5f427b7449d68b3cbe434ac82f642195b4fef02f800a511fc8cf88f8d0d64a6b960111034352227e4b358aeba4ce25c2795e4abae0846a0fff8fddfc52c50edb2a137e8089bd045e62f1e120362c0909d7e48815832213aac2fd13750a35878e8f3e195ab1dc8c13c91a2d2457dda3f2aa6b39ae9e5c1283f819a548b0d827eef4c71da959bb447392e948eec02f31fecb2f7ab761b23c6a93c3d847717752b62262c40524d2c83ddba3106e34e26efcb80359c9c5ded6961d01b01727a3b69151ab649c924bfa1a68154b6ee4e18aea747d98d0694fe053e09b614c7a6ec22d0536b0164eeed785271d98ab6523386e9d2af5d0abd72db9f426b18f6fa745113be4b0d9035e3eae7f60a7d56f37c1d7e8a8852b219d30bea3c33fb637206a78ee79b74591573924afab890239581d736802a34bd2515be1ef03ab4a119a1acbf8644c08a75b0cb96da031378a684cf188a9a8564244733b311bc1d11c3a6454580e327cf3a143562fe6ba45fb89c3c38beb949b0d8395114c96fb690f409b179146d1209b2cfa299b602ecc9639dcf45cb2ea2e23a2a304cf03f5a41f568c9f7e96d984599959a8b2a287aea9e18336a38ca3675b70316e7afb29a174b12ca969ca23a28814e0553056601f2041d013e63d405d475df52f0cc2c0708d04ad7b29ca5068f978cb54d83679a977d4ec33d50a7d5e171e80497ff25456d81533f329fadc44b99d2474027812dfb5a2af0398e1b0cea6b9891240e77e2f172d9a597ecc4d048860885e810d0c21b7dea34d1455dd00b93f421d2c13fc039242613402b296124996ebf55f02ae6a41c0420832e35cc72822d244006c2f12b5ed3e06a60017c2db09520c65a10045a71b11dba83f57680613c20896e878fe9df9bcc43ec3ab843010b73882f12a9f3c1c511cb449b1a528c6aad07acd1f656a502cf67639120aa0ce40e7d8d24c12a49cc753ea8b110dcf2f3e5ff20241685bf4b334d33a467899e6088d0b8578117b765b7dab73b9c8d6c944d1dba0d781a736874f18e4ec61b836cfdc3707680489effcb0c0e9b36ed03c6c2b9236ddcc9fc53a38a1225c0a816e671489b440100398a4b252a535292d40a7928f51eb8e509940f5b5093a588f8474ad49c930b9e56322069943b0156d27cb5efc0e1cbc2b5fb601f39214cd80b4012d7fa244ac9dd172f0e709b3185a94e6c1ec733ccb5df541a2fa3ecbe1e3a75e9f68894a4483167b89aa64271ecfd1a4677cc83f86c460ba5d0d3073da13b4041fd46602a2dbd1a76e9e7dd033941b0fcd20f3c8a8d033b8b7e0d08f77b6c67f7b713d05da88062a3ddc55295b3e79d0073e01b6ebe7bd593e22d30c6e6d7111a0c7de2b99df0d2ff8ab91ad550b45bd3350836136b5821f992e2c89f88181063f30f431ef57607a79eb22b7e29755a07da33d738d45e683fd48a9d656d0eb61aa21842f8a0b96e0b5ba73ca8a9efb13db64b96dc0f627af9ca1e7092fcd28b7ab856a8c0a3a715d741c03e23bd10f221592ebad0b3ca6e82b67a5c0447f4a16be0992de122a2952e407577261ec857494c499c2476e9ae069ce91986437e749941889975d3f6eddaa89dd2e162721cc704f39a64042642acd6415c7e39ee0aa8b0f87fe45f8d4d05e3cfeb0289529766b4b1974fbd6ce4f6fde01989b5824d15d1dcd9e1066b660d0d3af03eb2eaf84550b3126b3c100f8ae3ec2e9604ce52508e3663983d5c38e01fd54b75842337b2514753e88df1403b5b1a7a23365082543a690f923be736512a1573da2c46eb879b59050742a97ccb26592a19f793f2531fc2e3a5392b1e935219be72d760ea47e22184cedacad9ddb01d5ac111c86241faac15cbd12044a92fb5a260b381e28aa3dacb9b72c172f5167995694e082e645906f18fc8abae0d87ca80671126343d66aaa313db818dc0f8b29dfe221d52375a3b22c409a8416dcaa584aaf32b940304c320bb9178cd1e63fc56c97f7a554cfaa21efd58e381830c95ade87e3940a5ca3051480cd27e2d5789e99d1be550f52f9d419a9f49f2d862e34a97f24bc93f5369c51a95502de56ec4428511bbd8623564b25a55be1c38fa576fe2ea59523c567c94b945b234fcccb1ea64e6f7930ff9e5bb7925380f5b82e8c154446a54db21dce0da9f5824d4d2a351413c2e6392b88f5f2d3acb2d92ce0aa4cf5d62ee05fe15af23c17a453bfbf8e22a0bce848a03ced5226a8e1099df54c209fd0626f94c64838643ef3922c42cb590a7ea3d2a182977f08e0b092db1be1ade9a168bf0bc8541bcd2c9ba9e5725a9747f091a0b96ca6edbe11ee14801c096ed036e57cb49f8b15b408d19e95dbf04f5e5bd48b1a69c1379115fc55ab88e8be3d57725bc036b833b4dccf12f723b5995b0adebc05dc4ec11b01c4d69afd39b686b1e1a221728776606ef26962dadb6b83195b225a1a30811cc133e06dedfbf4359c8ffa8b32a8d9d1c8d258113288db1815fac65877e450efc87da1e7485be12228a4d8836be6650f6c07eb13269e15afcd3757205d626686ca4775cf3771bd835613e2f983d9abf4c6c7520933c0740fcf42471ed0b7a417ab0b2b4c22b8083bd14b96173a959c9386da76ef46ef2df743049967c6e385403daba07856db7b051a32e411d50fb5aeefd8b6ccdb4303ccbc081dc057994d3af3464a402217cdfb1c976c58f64bd70dd7f5c3e2c8ea3e422ad72120e196b31b01654000a7eabfb0e5c57d5be6dc3e51e9e59fe8c3683d95c3ded3f515876338bc12f47b86bcfc3650cbc0c61998df2e51fc5a97de7b90652ada81c7fc5c8c8225472f7f3ff1be8b28512657f0c082747a9957e8e5f4409eafe74de815f5096521b5f6df551bf13d2e05930925db62ee5d35f04382b230e1e593a7360244defd33e1bdf9400d90c1dca3053a13f3f18bbf16dc8b8cda7de25666ba595c2e70822ca2e4d1e6837c6ffd893098961697bb2c9d70309b2e453f2a78f77f6549024834e39c191b96a04bd9f97c84fab70c8a0622d23fdb441c0581ec2a125b945c82c2abf7db5873460033a73ef803f11f28a0baeab80a9659f627c345799f0abfa87967c79faa84c7fbc60b128438128a0a293757fd1d9049c17bd853260e584be2ccc860079d7325084eab095b1b0106713d8dc204df74db40bc255636f72fbffd1263cc2301024de9f9b81d7f8b3004a54e6caf24fc90c6f8e105ae23d17248ee3475affc17b8e02c4169a4eff180ac7fca0ccbb29a73efae5942e59d9933b12da56530c66a562ad5a669bb7657d494229ca0a6c16689599fbeb06f6ce6cd8501f29a392c16668346ab7b66cc0666cfdda3d43087322fdbb1211916f292d3f3edf7b2d05db95ec70502060128d78acab44417601d5fba8008c224524535bdf6cb2233215737673c3e6d76bca0a799cdcf82454cedf1ad52dd390b6867565ee7d6b3f8acc46abf10d33fd446f52a0706f7d1fdbbdbb650c4cd6a8d48076d98a51825ed74788ba10bdeda2b703f23dbbfad6ac017ada6741088b23dfa12ea4f1e730748572017b37569e76bdd3386ab2a228c94a2c3601f1b667bbd9e90aa51333e5a7882c076013878f58532d061c195d7279babf4459cb0a68e09c9717c5484ea63cbc9b06dd4d2597f2f5ca3450787593bffaa6051f534374bbf709cebebcb3a55daa3aea5a72184d4d4ddeaa4a319e39320092a33fa927171dc99f194db63230c244dfe11749744fc2032f337c3ecc89e5a83d4b0c240215332cf82b273534f0bdd667e1cd5b8aa6cd474cd50309ca30693e1419d851b751038dadf931a85bb23b2e29b8f08041b6cd0268bebc6539cea9cfc75643d703ca24efbd8c46c8d10d5061e7cd453aa6f3ca46d3f0f4bfe7def5623505e88edab4e7476a4509cee2c2e7438ac0b7cbc28c1e91ee54f8e7590eb93483164e238b18d891b3cf4fd9ab5b1a99d4fe774203a800cb564359a6b30bc0becb63b05bd9e648d8bddd9d13106c1957239d2a687d839876d7f777b7263cfdcffd753a2ab7bc96732fac3b8c3c498ce7ef7d516c0a78d14e4c9d3452a93e1f433d7443c6a65b34c27a7992f64f7df486a8ad80b73679fcde8482c4323e014574bea6f4ad3ec6d9551c6ff56dc0445eb8b3686b6600fb9f499dee36c11eeaadf27942a44a11e78c4e91a9034b0bd47e3229204f7287512f4842f0579de191063593e93fc9632d9f7642ca6fd3aa25760dc3ba0bf47c6eb20801b5aab46ba3d81aad0c5039658b95673baa9348d1da2665f970aa00d049483899bfec9527a160113e66b83951eb10f8b133742c975006927baf0c99ca88198df05dc0ff41d057b3e7ff01960aa919d808ce1f5bd91d77e89162c5afaed87770c8ee95b4403ef5b9565197a3762b5a27c501d427972c2949aa73758b6c41b93fa80789e0f8686c65eefa2ff5df6b9abfe77397cafbefb5012068535e7f86d68378494279ba8181eb52a77e26d60ab3856b745f117a048b2ec24c225cd95c98eb754cf1a8e91cf273c1ae87bc6e7f04d20979033143d24ab957506870bf66e689afc1528634ed86ff86720bc1f00732492be4a3cf3acb46096e329d8090fee2f2a3dc656a00a4b82ddb8ab5f3d82a67002e244616411db34b4c53c93a43e19ac36bb246e6efb38609896312ee3b5b2844df968d36f8611a5ecb7b8560e6e105531de274843d2a8d5c368b60a32fc06039740f671d528936da10dd14b0a764d3b62245aef8df82b5ed19f55bd05be02dd3b5e026bac1d33bfb966fe1ce4076be155af8ec6e161b33c8f618be2e2ce60f1bc582bb166dfa7bb6cc3342a48b59a3f582cfe67477afe5a6df829617a24295e183f8935e71d5f1133032204e2a94900e2f55f1230741654afa33d18eeb19d9cd46df07325eb5bbc39a978ea321aafeac938704a950c4c49b2b88ea70525c735dfbdd07c76487c56a3ac211889090ca647a6f09da410ce10e617b2c896818031bc874b707258e9cb0775e51bcf55f31d168ac8028d7bc4299061dd36ae71db27e2afd31ed1e42363174d0a91034eb9741393b0dfcf4d57d2f93121ba0c4f4968583e10a3127af48f39554883506b8550058c1daa9420c15225f5be68c551abf5303b02af921b5ec7ad4af306dbecd5745233b852c04b28fd7c82ad5c48611ec28fd8cecdd13a17c2a51638a806e8dfcff7ece9008ff7bcfca4ca922829786e7a750ff641d157322f4aadf7cfd50a21201b82cbb214223f5f780d494612d9595df944844ccebca218934e94bc18c7f23715b1e44457da2ce6ad8b5cead45f64d456317b43072751330ccd0425224a50d0455829c105d1359eb054b366f49063b1f842e2bd56b7616e536e82ffd3183a3111bc49a1927d902e36cae4f129603d0f5aafe41187a5f80e231a17ab16f1b4bd46c910e527ac0c829da3c92b4241052b9d20c378ada0830c77943d7a2a023f2d0dfa3ffe41a3099e2f90379ee2dbb66cd4deefd7b2f4be7b09852c29014c0b54c20bd4b0ffd90b79bdeb8d3ebbeae199824b29c108d8d48a246a0a8da2a415887d52111ecc2177752549fcfd84096076c4e119323eebc8f69b1c228e83a7b588cd84b322c9f0a28ee2dc8790b2c9280c807cf59ff0941e0d948b02b94391fd7971304acc3ae0f3cc3a3cf8df104841fe9d1c8b59354d43ef41915cc22990d9af490c09e6da7acafff1c4918be6447bd664f9447e2c259cb1afaecd456cf36e69dfd65b88e613c8ef1eaa182249ee80aa2a9f1e72c33ad2038c4dc18b73ad3b06624587ee45feb95287bafa0680be83d334fe866429fdb130dae5d5c1c389093af6c17cf5f81254c647c766e12b6712e4b0b96718fea240b9b8638493ff0ccc19345d30c613f39a61841fa1671e953fac561ee4a28ecd055abfadae51bb9f33a40896560e33673446b7d700ae6247757323393e3e1f0217e3f07c0f18246c77358ab4010b009c560f528f8133ac2a8be97615b77625e79b8ae621f4ec3e85bc7120efb313115dae5c4df308f01400c87e70ef30a9fcf0e0b028aab4fcd8e6edb4ed509d59e7bc24ebe4463c8e069e041d21f51e72b72343bc41b859c7742365c137ee819ea9fe821ed81c2b0558a7121aebe5ce7007d3cf654d79ba743d89ed9433eda61b81933c178d9eaf1d0327287bd73068768cd878c8ee39e1bb30fad83ec4253aed6acc53ac3f61bc819ba1f1936fa2e2663cf4ab6715eca41a9d622652cc9be754bc6604a1697ed1a695946486d5d91c6369b15e80669a7747860a80160b8d6ebd3ce0ca9cf08382599d82e618276a01e2513adae94551016b3382949f3ed4509df7d1fbf1e780cbf47a02da881792532bbbc72e25c138c6b1a04811de93e1aa054423bac198e9494df0555b6c6e2b88141a87cad3454c2bb7f20216796a7db97535d64009794a6d9be24fcc92454c8b5bf836cbba3d216f40e0d942ccabb72068cf9c82590fbf4a15978726d767e1e57a018bf5e18c09f129b8a1a1feea4f0bf9f9be4e1169de1945255039f4a35e2dc645211954f81571fc54422092b16ff146dac0a5a12092b0ce15aba0502b905f51f1fff5cd2cd76663c43152089ec0bd694e354921e5d057b76c944bad4c28a1f296a80ace0008aeee8ede80876093f0f9a45bb908067df1e9fabbf71912e5c344ff246d7151e79874f97034110d8f9c07d1d98a80009c8af7e764e86b7c417baa7d8f4b664698900fa5e6a8812e23d0d934e6273462c7ab44668ec1cf46f08438fba44c52e7c667ced905bd040765d4b0b7aead72a27dcd0501b74721d653c1106a5077cf6ea0f32830a1068a4088cd0a17f987b18c2d9c188b971f18c4ed68726598899907467c1caeb858d822eea60051a99ad905375c1ef2680ec8c5ce0aaea219ee5afa5fc5447ea30a299c4662dd9417788bb1f0460ca0294fac9d7c4edaffe6e62213fb529269700355a2682e65a9eb526c306f61c61927236508fbb23f0ad346eff0d5a711be3444fbcae325b6008c425b58c36ce392bbe00d142889d2e8d652c350ba01583760bc70890a55edc52a2ebf8c60e1bfb05387dd77042f1114594b628a9bc29cc829cd527970ef65871917094a1df8c46c870a7cb1c4561c98e91c5a5a68c18fb6873b38a45896d792189498e0ef9c5148255b3c545951f33736206043e6d92727f555a8ae651a26cebb5f4402562521555f3aeb7469be47142d010342bf252404244dba87d46a8c7f984524e7b34c153f40402d559c93736dbce1ee5e7cb58ff67fb7a7de86d790d234c544300b1bd076fd1662a6afa580cf9c83a6472f7a27a36d5e64f2cfc83ce778c88309998c835f7ca8757e7e57c0036c1240753c44c0734a7868413d9de2dbd4aeb0721f8ebc14fa291ea2549265014aadeeb491a321cbf05d8c3c31804149396b52d77d621e24c89a8b1c629d827ba1aaf4bd9696f6fe595edf15ffae016c19f986d6d55022fd1c96c1bccd31a0e48b96f3a51b93dcef737128088713092ac15a05d78d77de34dc9501edfe3a53e7801a039717c42f8cb5b9d40173252a633c31c34240e7fb6ea8c7865d73811a1e03fd3b1d67def1ab73f9212afacddae40076db20301fe1d049ed021c097648622183645b806265052cad83ce319365c1b84e6f6754fee4c90af3b0db3c175c70a05b1b1b6a68541801e1418d671a5de8ece387a1df895b2aed7d1a26df352d9473ceab3e281f80a8f228257c0e47b28c68951b31e9406eb44368f7c4198fa94c6920603aa6c12908e1074a3bedcaf6c492f641cc00604c86f0c37749281d5c063a8fa55dca889316b6d242ffc8a0bdc6f677e719d2133fecd78031ebef3b262d08caffd3c862bd902da0d04f88a608c492a8a54a5ea30cced680600a881f0921972ab78d3a5280ee123ca93ac380653dca50b48462a8cb36b224c4736d64ab751f6a8eb270a45ee639a91c5992b08b25bd320cd00a467b5e181de5388a988f720a1acf503ebc28f7543fb109fcf9a298b015880f8f76534838a5fdb0a72405975135d1076bf76f74d4803cc2ec906c591db67e26c2474ab7ca3839185f1e6574cec37463269c86800cb0011e213a2316f897371dd1c42fe98ecd7fc7f02c8cbd0144e7ac3ac387f504177e107ad1a1518499235ec17fa11c342710b3b1dc7f0e98b90cb146928b71a5ddd463c071d3d846f7ff92a50c294182dae3ce6016ffa4c4c3c9c313ec5efeab07f226d4709a21c8b46c030d3f46d7eb5dcab4b92c90b8a39180be3522ce19ff4335463b77e5a8a2448f2a46dce4449834f8094f36a4a2d169e70d87a4a6b2d695e882ff9272985e0603da02ed8276b68240a658d842c9effa5c66e5c351e3aee31edb1a6f8cb7fc3fc90af3c5367f127dd1cd154843203eb07e3aeaffd5a38d328fa9572bb79cdde3abde985a5ad65833f866c7e84d7a41d8e3e411f8c9478829f967e1b20da9ccf2ad905a57451555058709aa6496238ec6a0ca949f35f2a00a1eb3fc70680cad6335ae5065ce75e52484bbc5ac2d6e17a88bb8e8f027ea9323a5a2c507d7a857abe0f74054c47b686a2aac7915cad4ccb8a2a6b8e271b81a0a38547b032e9e0f6153b157d347bddee3c7d18b7c725878f4f54aecd11eccf02fb64ee3149f129ee713ce6413d7540ffbac231fa7afd111ad5337830578437dacafc7d13b6b2e0c584398ad14214b2063a5d87af1ad13f371b33e6495ef7bc02463a301c37340ac8683b344eae72ba3a7becee0df7ede8aa96dcbf2d3290f225885dedd85835446f826bedcf2b6460867d5c3bc22e11771cac23dc06b323e9082e7f643af854d6f4370bab2eaf23086e3e37a62fea3806e87375e0ab3aecd47f3067d461149f332d6440826562d83061f93999707966e5024b5e6b11c0c9ccb6a81ebf14a15f8bad8b9f495c944467b438663ac08f1090888429f1ce64dcc07b6fbcbf8af70e318e0837b8277247fe2bb006cbc2f7b237b70c2322ad430a1a9b3c50760c55a3bb156dd7fee2f495a622ef4358367f53ba9f0a3edf56883fbf684cc4049d28fc08a5db03a267c606f1f21493a078806be711420920200282a86cb899e4ce12414f07d4a23a6a25f80544171c3b7dbe325d3222e8f8ccb0ab8b8e4d8af04d0fe167bc049158ea53a194ce2a6e64219616feedccd95d71d4fb82669bce47e12ff8ba0aa8864af592a684aa57faab423e0ca91c4cd684e3a381b8d9b8622093ce4bb27ba9fc8d6464562a8382ded7c7caa720c4c30838cd65b7639871d1eb8d4d9977126d7a924b34b4b2096db638c30178b929c78e0ba349586d50d004d0b86c7d25f0d0768dab15a3a614adfe4704ec3a762413931eb347ba65fbd3c4c4377460c5ff5c21ec7795164f502ee858794e4d93111aa733fde9b37da4226bd500391a76a1b4a9ce7b8180beb7033aa635ed3bed5c7327dde492e46f25c59330ff9fa47dcad7af68b71df6556cbe38ac5396ad7bd651fc647ad7534a31c7ce89e044b1a8f6ea073fdb508e4f3d78355e66e1eb7a49daba7ef4b69ea911c3b0417565488a29a470ca03c20a44d41ac82d84513dba5add2666714ace0804d2a1cacf087186bc0380631105ff21090defd16195b9768cbf6862aa0ebaecb4823e05e0b8be277ac23b2ea1f02b814ba32adb4ba09b12a7cbc5c2f57857c62a8b528265635420757582ffb129954076f65b96a3c07f84f833eb8c7688f216b5b248c88e9f1f23c93f1a9d797463a76dd5a19a0c54358d0dd5d42d55045cfef49b9e7541f0f99cd19693f699146a1f50c385aa5c68e3807dc498879f625d0580bbd089574fc41abf713b878f8fc5b4b5a07f10e1602958b7471e5881263121e20a620dee21188151bf34baea87c138fa1d262c281e006e7d12dd7c93e2f0cffc5533af3016acd01fb792decaea29c854b7d13e0673ce0f842bbe640d0b83648db92b53ffeabe16a963e2fccbbdf25bc42364b718cd0c6efd22fa117116655b31ed6d56490b086d0352ef9c000fb3a0fc21c88a15c06ea00bcd3b99310307f585430ac76168a23f8a1db581e7e2a00d67b616cc1680ce9f94c4f94472fd8d0f21c8138a1b5e9ef43db2274f1f80c7d4af5d0b50f7a45953ea318b7663b1c375519472f279be9e45ec0d20bef1a319527350333d18f2e11bf360650cb9fce02ad1ea92c245c69136e7e83feec3d275eb413efc06d81e5d1eff0cba1c6740232766c6bc55a2aeaf507426d5767b1403831e9c7ee6c8e0381a47a9db06f5102f17a8e847483649020459e1c9d468cb06966910d656201ff3bb9276b84ff29da05068afffb546441b46794f8d86634ae33e21306c92328e099954cffac705c3ce37d8aa758a5db359da4d63c31bd0db0c5f0fc3f048f7684b71c7ff0dc73b5c14e1cc57a449b87dc0c510667f1716150dfb3576e1c749326ab354ff02b41993bee0bd8761fbe903dcb0b1a711f9ca86a4cbe42cab2f28aee47c3d0cb112a15cd8d666a494bdc3cf6651d6ef5c4be2e81e9c14c704a42334639674405e91a0db8d4a980b0a68ab6c3cee03ac0f94f925997dfe61d81fbcda0056ccd10eb7c4b9a64bd5f2d7af6f9222e4721992dc2337a3e2fec8da99d39753c0a3a3223cf3e425dea32391c3a9d47737184d452e19e7809b11cebf889ffd0b5c5f9e25f2eb309f7f35ad1da9bfca2cce314485269e095361f8e9e00106b2dac0a5441068a562253b8029d34970a0cd5d8ea88cc383ecaa060d57d889ed36b9358a2c43be91d98e703dbfed4e74f8d80c19b60cdcf2ea536c335f7a4cfa3a20120d0c09391b4a15f83a2811c6cd485ac962b4600a7297b9757276065d2760ed1ec9c8ae9e1e4998ce93bf5103d73291c51de5220cd8be6aa04abf6d99a3a10733abc7daa9fa015d752faf328d26b55ea80b60d4f85a5cd25d53f7509954bb6fa1d97fcdd9646811973254e31c47ebcadc4a18f16ef1839583210830ab3079737e86fd4eaa9e899a3c71f2674c9e6fa9c2622c96a3c17c41e250e0d160f04032eae3b02be5674aec881fa2484aa2d030c5c6dc1304314bab78ef8fc83d0432e9951873b561b6c4488724ee318e7ec4643d07e069bee6fb3d5f23d0483eb8a952899aa6ba788cb6ed906d19f71fa5162473e7bcfce5d3e77522eb81e1945e81ac5b9299b87530a4722a87901b9438fbdb4778e0456f96a1bc7f8a44ab07fe17a186489161736cb7506d165c269494328e8e1aca7fd115c521bd2ce8c5933f6d176792e077125334bb7cfedeacf1e6eaef1f52158cc64d9c0e7f6e9da351217c65ee7c1e1d973051fb2fe5910ec5cb7a6ab6d2001c11bddc5b8314d129d088355fa5223aaac87e68b095e9205ec7399bd465cb0c255247ad0a0ca3f265c66decfc9b75a287f794d522a6203e3b1d3d6afb32fd9a17cdf45d3251211f43ef028a97b23f92e1e2e1cc4dec84b5d488d729c964799a4a0ba17a86d64af2a8c297e2b4987e4db652632672c26ba3d0125a94991210431aa40b349c7e0f075fed053650c9fea0e2c74b2498cf289c2d51194bb4e47904988e41c8a291ea8710dc8d86759a1c9996af2b63cd3b85b4836932eaa4abd4a9a337b5811b0a759516ca7d708120987370e547c8a6cc09fe600e6a6650a5985c619b6c5fc0741f2eca9b82cee7114da9f32bd3e7fc2f8167742814710e52f79b1fa74df1928136c9628396bb5859ede42764a7091a55dcf31025391f0afa20c7276db794a0149c82df79710532f03f441dcacd8cafdb6531db2a1f45e375e779d5a1c4713f363b631c70c6c9e883bffa3e18c4f936169f7578417a205a04f6356cef7987e49e942e67f9bd1820e6bab4a8e83b5d8578a061af2e38b2ffa0520cf26da1e06c0cf053918a0b91c8fb15749fbe57893ae664dec11f80f94f28e02f6c9b983564a07ab959f0638ad9533033a53979d2afdfaa427c2793d10e33f7074d47f44f210ba19231b5c86f07e363181b6a792d60104f9afee2b2f3444143d94495b10e6382fb1c6636244cdb6f44ae35db7ef58486eefa9218208b159c6c95ca31742c511c435b12c836a4591bcbd9ef434d7d058a9f1b38e74efcdf8f5d0778e6acbdd206d953c66dad10697701d3f5b5dff8a151c90f33121f0b630b1a86aa927f2b54edf90fbe2592cf9339723d0b8b66834e53c69f675054c30855b87657b9283bb5ddcdce15216f492a6e6e20156559c4e4fc12ff0b61119759647ee18fa227c969e407a14847a5380341141998e62d21136f113f0fda85e15a8232ba13a484c5cfb149d1f4212771394458516e440ae992f4be1ad193317c6c157327b7873ca133ccee9cd2b4d9440e88684c176481c1dec3dc9a3edae93e4fe7484e985f3ec89287c0faa37918f79011d5a893977ff0484e61ca9fb64edbcce07bed87d548353b849fd2a4534a80fb1e3ee41dadbf1e33261bd50861bc0933451112ec653178626cad4f4b18f43bd255962e35f00b108f633930954d1915461711dc50ccfe5d1d320042ae16ade4fb924862cb39f7bf63c2b16f37d73e8d7c699ae5b0ac42cc8e59b662c5048d4db7c635bf327ed7dd3969114d17af81b6a90508fa246df5723941ef4c84dcb821a7993a0a8e12756166d4ce52723e9592dc438b337a3e26d0b2a48acf46f7027700e8f81d464f84bf696d9de733589c8639171f47c6569fe1eee15768bccd44ff3dd373f6182cc5e2c938adffadaf02c4f59d078cbd71682394c02cb38a055519b2989c852810341fce7adca776850f3a5eb74d700173e40e9ae90c476cee1f494141765ecfd13598608c6b2641f885ac4c0c35884ea210a4cfa62360a6b67d1e792253e8db223121008786ad98b4a39a19edaad94cf586144f669e1942cb8ab0676051b9fe034b8d8fdb335c4384cfc5a12e148ba247164a5e7a0c90961a8615d1918064d51806b8589c686b4f1b801835e382ac23fec34c4ff560716c299a0ea88ee43718f6e91197c48cad7e5268ada3f64d1b0d9a5bf20368ed52e25f9d88680a3490222c3fe8e8cba48a813405aa6be169495552dd160e435a15b3f93f86fbd3c4542546c0bd7d8db2f980df60ec86c696091c0943e31c8b96502428d6145394e3f1692cf766e05c3461f17d2e7c2b2b17d441b0f5af15044a74019cdae9a98560ffb7babf1f5d2809b8f3baca86b917e901f6b45424df96775da5888badb91ac2514fe51485e1442eaa7e1df4b830bde4135d1e4ff0450436981731b50669fbcfb0c5399c826cc298110808396759ac6ae6fc2c5b6b67539998ade9b0062fead5f8c28bc6c9910fe26280417619fab0bcf13712868c7e57bc7336f447a84a4a073c7e917790887a52d76f60602b3dcdb20b22eee4b784357809ad73d61848b9f05d20150a7a9d18f40e9ed200f80d0c4c2fc73e6dac74f233955f981b356ad030410a284bfd9a586638353f8a4762e4615e40ad8e9b74c8b5bbf32ab1159127a2ee227589c897747e30bea4f3e83f6203d02e3e8b181e82293f7e78d5b9d9e7f317a1cc25f925bba36931aea82563dd5c7148d9f0ebb1efee147b800a3aeed42baeee4064f555ff77f0c294da13d0fe42212ce1a5d4f0af2c86302b814a0e7270717716b7671b24cd95ec3951369267a8e6303bea8230959106af6b0bf05d659273261f8b978edc50c05183a5afec7f43b697c2936c16a9d0fc88e5cfc1baf455f68615a9caac4c9ceb05c5c7117b449ed0ceeb6acb77fddcd7226fc163144cc94fc2ab89a518e4ad087c1a0a15fdd6a1d3252508f95e2cc8061691d8865c7af744239e713dc446a15da842b084548d29013481ed41d8cfe22d58c083c11089c39f30727e8e282b3818b2c1888834743fcc69214846039fead93f9306f6f2044abb882f87f5af8eac8c0942f5c28260d1e445f566d8aecbd42db4b1bf775b696ae64571f5723b973c73df3b6a9d9f69485d0ee03f1ae06051d4618179151af5dc25eb96b78e89ed2db3298e8b065b2ef79bb7d1f5edcb8c9e389c79deac594f033c01e1e8594acda4f59f842d77578c47edb010c54ca5a9559f0976b547a5614d2cbbbc35b8f4590d2bc13d05f7d5c30503222c5684423656cffe96effa52e899624a1243111f3113998bd57069eca51d7f44ef90c98f2804e2022750c830f2f2cc6849a870e3a67d1607703c56a6ea0354c12ad4e8aa2b900404fb7f284bdc8c7e3f65f303f75fbecd60a896cdb54d218984e131418e946048b577b8dc32e68503e755c50db88231c2caa421c3673f7818fcf6dda66a8b378ce8731a7ae064f5d8a33a6306569e6f0b008c77c9ee4173dadfd732637119b78193295341818dbb700d98a1f6ff7a0184cecb2b1953abbf7b19db28805aed471e0a9281b8b08850a6f13de03e938a1a809e16ddc49a6e9a18ba4f68ed83cd317a1e79a6579383830a4678e9a5edaecac32cf5a99a88f14411628afe240187dd656055d0e3d70d29ce95e4a66ee997e22640c6df9fa57d741273049c6af87298fa44885167528b4e0fe94eb2ead33d9c8615637c4f39142ca3681eaa80de9f19349feabf16d10a1d442014b03677ebf4f743a2edb212a81ee4aac9623e74272235283a855d111c22e5a618dac847f9efd4bc81ef764dbaa763f6a21ffa5436b617989c1c28fd0c8fcce679382360f440094e24497e98fa456ed54523d4f9897b5d04c22049f640764897f4850e2f5f8dabdd3cf7f41b39382b7fd798fa96daa4d1d4bc05df5c5e783241849da45b30126e41ba37dbb129d556958ffa86dbf7419140b4aa423f5948280bc2d8089b2844b24d4baa26b99eedd18d7e8b5221188b9cb7405fe8694f6329d930fd73c359338830dc814fe501f236e564799e9eac32960e7a0b15b882f55a9e4e5be44e670cb01ed23393da09ad0bce5f4be74d61e087e0a524fd32f89d4a85a4cf555826dd5ac5a02c20cf9001ae3f740fcb8e08ba59bb173b45bef40554345688fe038c373722aac1cc2d6abe457a14116118654375346bfd22964a0f610d855cb6eaa413394175e3fb53eb965583a1bc49e71a817b174127c5ba0be62eb7a2ea8cdf8fcb1b388b3fb4982fb3958177c63358bec88042d365990fded193fd34059c268f68f9056579ad1c437cc2c9889344ae3e9edc80505109acc59905234fbedcfc987d222a62d9613ae6d862c568718f7fc6400fabc4f886dcd121da142e24ac74ce1d11c938a69e89a85464bf3bb12c8cc84bff874506dd4cb6fea853a18fc65b53cb230a0127864a525473c5d0588c6d910c48c7c5fbdca92d02fbf043da5f2deaff8daffc098ebe5814d8e04a794abcb35d8b30b5c03c63630a5f1baa21a26b35e14c12254c25413bcf303d84afc3c8996336c3077ef2eef269c7e75c22f36176f0c307a6c24816e1a88e6783f2014f11055af738f028d904b60466e3e75433ab5f7e1413536c5bf9bd73305fb998ca8cfa661f78c76dbbea254ee21dc70394cf76cce36b38307b3eba6ca3184334d36d771c0ed26da877bd4fbab17f3c843c3c8282ec4b65653e69495ddd509a964aefd6b853b340a6ab8d2ce2fb4ae8348f6a82864d97dd4a45a848e546309726c8bee7188005d2606a089a13be31174864a92642971d354a9f2eef69363c3117dc61d2fa87f1f6f35f21924cbdbd39ad368a414419544ffa634e920523d1f53763e72ce69b69d7233950ae10e98175d678d95d9b2ed2d5ef8bb3af8d7303ee547e8ec56e4d02ecab1357851db40cd4f45e247814f5b556dd028a375a96aaf69f927f3fbd4ee181e35ad45033c81531a1bb0129b3ba7fc6094f606e2947a8fb6e760fd3eb907f2693e4fc1d4bb78673af1e3fe7cc34c87ce95b0b0cd06241c65b8e5cc9a9009f04428f3ae29291a0e4a4dcc594c36767c2dead46088ce850231c7458f17e79e730edceb46d7088488b2a16dac06c79af39106be9581279473cb3247f7c0e4006f7991b2a1bd9e20122b523cd7959473b93fe84f992cd54b012229f0d87e261cf1b860ac4bbb6286d434292fcc33f186b5bba2112861e81e1aad0beff20b80b93c971088e3723b56c99adc2e0b081cdb58c97a633cd97cb01ee9ff09a71e259d067dca7d094b35edd3225256d87a34dc32816dcdc274fe926fb5df7503824adfc6842bddf65846b917075f937c9be26038788f59ea10bb258f646530df4a1d7d0b3f8531c6e34fc7be6e0081197b80d91b144bd82100e7a3c5036799f5511acb0e1698cc3195027893d408e20152ab14790fc8a86ad1dcd882996da01a4e823abfba9928c96c86be7c9c976276a92672a6bcc4d6a4781199565ccd45c12f1c0601d30d046c4488f85fd04b1eda024815227b27c2795bfd9bd122444a7e6e6edfc35a16ee7f68e5f09d4da07351d4d9473233f2706b5460e25faaa8982ec4c4958ffcea4d6422073685507600091cef6576e8bb363d9609393ba7413b2bc30ef9d8eefb5d38b8c5b41ed7f76252e8de1a53d2065d6a253a5931cdf0acb81e1d629365f0ccfe32cfcab3b0abc73dfc84dc9c87ce4d6e474206557df1ca700e936a5fb41eb30a97e7d90c10468452ba4bdd0635381b4ce10677a87bb04851e0bc62d656857c5fef29e0bec53169ad2af153a17cb85cc0409dd64694f4fee395dca9cf83670da80bb5256c76a70f792faa54a614b77cc33a5137a96dcae458289e97b4903a0a584cdc6f75188653e5679d4606b90fc0c26700b9a72d4257357d6154f54eae4d6a513bc29711c43182e65ae1e4787667a66668187aaed3aafc20990e5f4b0a0d194a6cc670a308c44cad1df587e738ebdb2201b89cbd6f3d73db58545c76afdf9c92e349112aa6f3f07cb7f2ee4d99d4b40b25226327e3b6ac7856497ef2ecb93ec5b105cbb4855ada03b71629488f4d1e0373ea701cbe1423f4b594b423407345fbfc72a9e7be9e3ebc9a6f0676d713116ed087af167a8ec7c75baa39926b2b629960538187abf4d5d428b66afe96802587e9cb828351a6cdc5accacf2fb405cba3a1f1bf7627c338e446130d20f70d51bf4348ebb8c241e05f0a2bf02644b58aa75b2f4e18d1fa42689259361c00a756e474db96656de37a1a27131a64a2d3243b2e7401d8bcb6f9d929531deb357ddcb82da368be1558a89f09ea16cc020d80525865f78c5118a73c89b5775738d943f4f65b610c1d997e79a2e416d5009464c94e9bece5fa69b6a908b8959943bb6add1c68b8f10ea0a5b6ad04b0db8059fef42bb80239b44a41ded60b6fb481c062acc10f9365cb64afb81b21b7b570be6f44f4bd6af62953cbe04158e7f54d4771d3f387fe875643843aa3965b9ed6692c52def11f8ba3f03d8b11a9e59dcb92aeb436b2fa43d689b60883aa9c8dd32e6501893ffc361ea39190be59200a3e2cfc22538847afdc843316c448d749b92e76cabc1ca14a6972d5d0d3a15172aa3e38fe9cbc206ffc563da393a450533c136786050d7b231df4aa86538e14b06e41a832799273a7849aff92be14ca81f223a70cab15f475d3bd11286e1bd23ae71740430df489c7286e1832e089965cd5419c2261b220a0cdfaf553710a72be162b9b62384542d55816db2f48b93f7187ec6f0b2dcc4ba2d87ec3bb3a7116ba5686de5b59bdf08bf4ed8a4c92b708713896bd4432f3b7e38d3b1ff7c288b7dc4d56581fd0bec25a5f0f003a7c4c7145f516590c58bf83a64c2badc5dfc436c4ba3192870c4ce67033983d7977be311b2f59e0fc95c6bb31f5612bde868633164ae2eef71c09d071419ec6c36fe2069baaf0629bcca61e8a21e078307bc05e7cba69ef55c1e5e6fa44e8f1aafe0b6ac6e0f884b21b9f21b8516f64ca23b9a2b7579f35f0af889795c31b7e97a9a53412dff0af1305bc96a7919742a9ba6e9a3ae2ad07f075b3185d481a4fb214a33776b791ae4384b6bbeb1b85051371cf72d1a731e75401dca0d5759e054ebefa7c4fc65714f3b8822b85542b02c3cf36db553dcea8326803c5e325e397986016f0b6097b90acc21621acf9ca4d4c461ef762250ba04e1bf00ae79ea29601adb96f858580cbd05918775a2b9501443ecf976d1a59133aded8caa4087dc99c23fde5b60f2687b0264ca35bb903e777f549ea738396b8d34393ad5ca6e19c893c9e29a94fbcb703ef67a8d51b6f0354da655b7e9cb20be88ecd9afe6899cbfdeb9d46acef672b204894cca961c8d8133e7664e17d636176b7c02598e087a4608ab4321638b2141f1befcc98cc359893fdba65fa9703139027db6e3306ed37d39df27f1ec89c116b03cf238e4f6f0e08762fd89100dba8366df4b8363311306cf9011697131ae480a9947cc133d09da2e14cb3d0d8c18265d51912ff79048bcb318b0a3404b7a60ed0f70c3d5dd132e7ad7529c7069b81c60134c484411c476dc68d65fc7b22f9a65ac8149ab6e203712fc64ab5e7fb66d554d3581de61fc045ab9c96f8abbd6a62a8ef5fd50a3782650be070a1ed123da48130b11b79e8f3823b121045afba208a6ba39e7eb8e70bd703eec2f7d3b9582f2e30f2c4e42109071e0a8045cdba647519f7429d5b911782867b075eab59143d668030fc016351fb58739c8c8f584fff62781808f2b85e077a29d3af8baecb487ff6d6dfba5ea7a57e0e044833c8975cc447b067066c7ec280e8c1bb2923e41111415e3c640a90510684d604fd61a5259b3abbc11f7a107d7459b690cb145fa0015c7849410769f0bdd8f63856d584ee558e2703db1622ed4473c2176df06a5385360fa8ba4f5f8f14d179f2d1b607e2fe998c7e1030c13bb7dde0e15ed5d8acac1825f0886cfb4040c8c15a6181542b5ee1555d04c0053c3b54847cfa890741a097896a3e1473c770427474742be3c06f5790ef2662e4c67d9b8eee3da0d0ff2fddaee21b7bbdbd1572f54c5f41f8778081f11e5c245f973f69ddbd5db77eda60cc7706c62cf6652cca27823f6ff28677da88381b3240ab3396ff277e3d0ac29868c1cb5cdfa6377666ffdcad3cbeab0d8e5e02dca0d48572f0570052de73452ddba69448d103088e5f478337b4f442a091f2c048364f0f5796175baaa51f11357f33b6c4ed9b834ad918a518592e368d806426f4d0580ce250be92675acebc95906a81539bc844d6f6e4f8d0893a4cf0c6333a85e789820fe8999974ae53fb61d53ea5093f79889a837b3d0d981bfe517279fc675bc88161d3474976dafe4029d8f6325005a18f3b4390158152219016e51aa545923f5259935424910385312be18f12c96f886b5da77000438312bbb6ddf3712863ce12f3d0d8a83689e0b250a5b659900a61c70971fdee6849809aa85254de3e048371906b1a433a4e7aac209db43825a4ce6aca935efbaa132d2f63af4e6e1879e952da19fd469a6f2015aa7134f52fd3796581ea2ca50b0cba8f002a37ca1f44dbaf1884367433d3a8f15267d76cc67eb024a747ea13ae17c7fe48c3f9d1e277cc24029627c837c4a25349b469304daa67d5db102623ba5709c584f01b18746c0d8a636b9afa4c0629242a45d5b263549a6451bd3be7d8f60e61087f2e391b681b220a0b99e4f343e25550992a052b78ed9d4d1d1fb763e89736a686d0f4f08e3cde722f10156fc97812376dac253c85c617e594a41d18e85903f9ca0929e03ccc621ee0aa7237ce099c9c2eed4efe63e03e14499edc66db7dd17693f8b8a518d0b9b4a0603fb9b3088717f2b0e6cddb17f21ce39a68407cd3ef3273319990f0d28f336e7842f1d09d624fe40eaf26c807a1b03f724500d46cbf2e26ab7e790fc5abc3003647b5cc39052aeb6e0ea9a0336d878468144bf409246d0520e8dcab7f4c49851d103f28cd7fded94b47a0d1405920b0f621ba6635699abb83f96c423da9698b11b11a8e22d60c3c1c5083c821a6ebcab42aad63dab961184a14bb907885180c4aae055aae2f321cfc179b7b161bb993d89066f2683ae9c3f8bfd65d9bd4f1dfd6a19c2e6e01410561683159b87c3cbf4bb93bfac99328353b562641fff8415054e02451d60ae7cff04f86b5ecdb52af1d649498d2b82693bbd6429e66100dddfee790d07b93e88d6878e57454687b909fa079eb089820ebec2f94e06b7bc818cb98c4e29f36d0060f8b05ac583cfb0482f4fbd5315a674ec96fa164f32fa84305fafd8bf8caa6e0a18da7e9c387154700b94b2bf16288ae4c1199acdb6e14da2b4ff6804440a235d957beff6fd45143ae1079fb2a84ac91f6198c0d8d294bf145b88762984a81ade2b346f3c01d060e15ac39e5ccbd50694a4f12eec56230d1cf72512d98086051760c421ca572a8cc81029145d94d260183ff66075a6bcfe198e2d248f5156af95c47ec14810e5362b03a728ccd430c836f20aa6690ab2b9ec6d5252e6ab414e3431675463f49e928b78f0860d5b85796cc6853dd77cf4fda302d96907b04857e1843eca7d46a90dbb1fd05f773acd46c1452a129c588b3f1265ee4eddd9254bcec19c278fffb28a2092cf8869e4029e930ad47bb6c09333f007bac377456562d8c7a8b4d521f7aac37e0489f08e34f3330b1de3846d97121d613539649163c11e81bf146814c45c050b14193984f54694c8d0e9ebcfc1f8e9e04306fdd4aaf591ed79544e11c054529b22f51c526ca5430cf37a7670c1b42f45e302b9efad70a23a1501156b51d4cc322f2bab0a1a36d8410362cc9f4d4797cce69d8501fe3504a56cf3196b40edc9de2c7f82b2de69f326e9dd7518c381fe5adbf57607c3b47f0e66d66f208b4015edd4360531e140470b74aae00fc948dce5eff6dc72979a8a1bcd290d37920dee1211a0a1e63af58f79f7e881da8a0344c41292d051d134867c5676489a1f16513bc8255f6b8a538061a38256693180d319c19810b9156e1741b53a6406a288af899384f22b1714c80699962c0315e75064511a9c133aad954c3108ac4a7bd3e5a6907ac3937d0291bfd3c689cc50f3f138d27dae5b2004b2c892f06696b293c306a2eaf534430a24fc4b1252e0cbe4d52738b780a6390d7ce59877f7cd6b3ef484ae78a0020f94c90f37e91ac3fcdfb7f932ae6b12ece3fd994295040f93035d9c053d8fe5cd99aa97c411233311fa93e57180ed839cbea1933957db9540505a6f1bc50adb44275fcd0b406c837e7c6c12f35abb8639342d7adbf6654dcf7bd66804fac7ffb2f1f2cf6b9f4daa82d0c2d64348a974d6679170cd338ec761e2407abce01c2229880fa6e39f0d6ebef8d98b2bd879b3fd26b2a9de8f931c1219d9d516a1b9772e4c6e7d1430990e3c3c3a65111e52aee5a49160f3bd52d79ffb3582d107190c957577e454cb252a1055d2a088ab446ca0636e0c31ab541337429df3918acd558e3009832967ac5617e8d4fab35ba0e88371c3fb67f3afd107c193ff1bb5481edeef57a4babbe691e1441f31e8fd85b08c31e538130b17b590bc2aaed3039730e726eddecd6519f9111efad6314f245920ad33d2e3714b0424cb24da3553142d3e0a4f19fa02e51a6f9b3b514ded773af37472ae8c070834a21b8cf0dd1bddca04578580949e843e512ad8b5a0d74ad3afa663abee0609ef168600e46674704973ead788fd41f7d9a23180dfdb4e5d612af4c3b992032446fbd1c0a47c0248982359e42cdda38340fa755c19e0d7a62d57b18ecd9509d6c5bf51ce89b5d69197f0dc39cb8b9eb3ee6328ba19d596525f92964c1e1f9adeb0b5f6b3482001c7c88ced8b407f84930ec649ae8f1f622b367294cbbfdde2caf327a981a0c80b7a7554e5dbcb38c735d55e101dcef2e4e067c84358a333437fc6aa2159ec056ace22fa0d25b9649ad567db1ff33c49815d0ac30e4f90093546eb3c68b30cca2a4824f3cdcc99863217e29f25b082dc694c6745aefaee021c1cfe9e7458d586bf90612f0826229028d80cdc321e0bbf0259cb14c1b1994473ec0c65c064e5107806257492a64a9c3c74eb56b1cc76f439a079cce6392b6d80dba6197438851234121e48b20aa5230cb61e3fb7e6f3f75c0a440497a7c61ab0958cc35131935ff1a9817c3728a6f0652bca438e83d47046f88d82b1990ec2b00b77775cfac6ed27fed040948196b8d1fbe14f35cc816508929b07a9386885e670b2328203896a4e833bb9b7290cbb01c51e8ee4fecd466c534081d39c37c4461696e19f7e9be627e60f7fb30c7679387ce845f107593c18b3e08a390f5440292e19df193ff9d768ba26d8f33bb0ac8e84a1b56f7f01037d8cf4a2ba25dd4d94fb9b8163305b611969322c56b39a079607b493ed06a3391a6eb2a28d6d87454217d39cc04fa067f6de63e8a4d2c28881bf216c9503c9221afe6bcaa2e42552e18e95310e4191aa2f417c84d804da9ec54a987f585ad7e01183e8bca24c01a021db7208b8827a0c399bb73a6fe0f1f47b78925ee4905efdfe2514b02306a75335049e5087a2839ee8e167eed71cdb8aae22728e628719677e891ad4d5fb0a9dac0616c88a76194540251a22fef54f0af5a118dd28233f9f2307e663b6cd75395d34e35f1f2f0ac461fd72a44ad7303e2386b5a793938f564a734648849a0942723f873ea979bffb9a8accfc15b5fddb1ee2b20756640bcabc31c91f1580a6f1b9b3a24dacbe3f43bbf0a71343d7801bc105153d1f22bc277f87d591d394ba98de6ea5dcea9f6b809d5e90c1478256b7d108081d9858bc431a5805cff2013e15a820925d585747587e955fb3bbca7940022ea8f77323b5b8b4bb19f08c9febcc4e4dc54620da11d8f7fcfd25e4a938089855eaa9a7ba9dd808fa808824377e7eaab420968009e44a0bb5b993b093043d19bd68dc016f57429a7379850e293938cca5e007cd161e10e4543bf40d5478ce50e62b50bc9b905e7743e39f560bf7a85ae82efc2557d4bf78cc140dc3d20ba48fd5f05de006b907bb6f125aa92881b591684a6e2e62014cba497fe1ad95e68925bd073a88bcb6a7d88e8c9435bd39ea541ec63e1b4d092fb610cb8ca6461eb6038d06d5d07f22fdc2607c99c8d58580c3d13a82a6d8591762010c8b9092f2bc57d0672de75beee6c4fde28b421426dc5f2a8b72216f516d8c3a0b32e1a822067b5a94d733a5cc54e0075bc5ab95f39160a109f53e529f5ace605255ab0d7c468ff6b7b350e61775d757dfe9050d73ef508b27af7c390ee2f6ab7a98dfdf6ef756791b08235d8e535b986a7f13284bd5f241314e1a3ced7f3e91949acbb8387a4463a86133cdcc173d25812415d6884e27f02cc0b99f6a310ad9d2f899f280a48aeabd9d5b5eeba2a1bf11a4aedab497c511c6d2028dfc5e7ab31312abb6edd41787e3493d117e7eba0bc403356d74069cc9d48db649c598a2a0c4b14570b5ec7a457307f8b0f15352209dad9fabe75e3350745ca868f82d5ed0ecfd7ff9bd9e7df9a2c2ce9ac18c7179178efb84fcce5acf02d9e65a8568654aaa61904f420b078401975628a48048507bb4c872d799a9a9e4e13d8a513aff7f1ccf8daacc5d84e57ad57a22c947e41240fd0b87f6ed5c282cd973e895e29e5641fdb645c4214bb821db20645a076ef65bf0e5a04d1bfe39c21cbd667ea3abf60f44c32299df98557b52ad27bcaa2782e91143b9278ff6096d85ec0e135dfaaa7d795693e55dd5648c937b77085a869b64685264c5835f6006d5dc2846eb546bba071658fdab43012bf5466322877ca68d84858bf04796ee10ac1037dab9a62da441c4a1c96ad120a42356230f9d75c58fef8853189d71c96ae5d46290de8d1c8b4d32782332616881bd8e41b5f7c19383c3ec12d7a609a7a2ab0f71b264b513467f4155f63f1c0d00bca37643e6fb69fdb6a1912abc021a07a59486746cf5341423b1a7be2fdc5f2177ce640124852f6add88193502a058c3f69b86aa1dcfdbb16a5c497c521dd25c5c492f50c5d1c0222e3927ba577961fbace8c91d650baaa95de1788d2dc106efae452b7e6e7ad58fde86e72ab81860167ac786dfb763863c60530285956adb3046c42ba88abbb34a367c11f46394c811f890c07d90ae3ea89dade1c84046bafe6dcd5092676daf5a033923f03601d0f8075d70d8156544693e50dfe04c7c223fdd18d6025b38c6a64be7007b3a1ee1783603e7ceee60f7679fbff4c9f47b9b2e428e7e50f4d0b1d3636e0ff8b3b848d53e3f6ce7482960b7364efd863d92bdc81e77b0034d7e6da01a45df9f3fa8787632f80b1c7e2086664f43cfc29321136f5f4bbd9f674ff85bf03281eac4a1e9f34d8c98f0d381424c161df2b63a911d966c66b51417c25ee2eb5483fba769b65604b1d5dfbe1a8a8c5f68f6bf1efd506e3145d22826ad2ed85a7c30e8dfc2f72f1a9e32c5f16238805fc9caccf3402e649f0f80d41ff56831b2e9012503271b120ad70978c96ff3aed7b07e0e1a8c4982b2d904af856732552be3d8b23c5b8d18d294abdddfbbb616a1cf0cf5d67e03ff1ab1421abb5251f62b1908d4c03cd808910f95d35d1d4d2d67117584f558c69bfb15b2c101164eb3086e3a9dc80042837ce0b20100c1184f0efc72b30a47225961d5bbd98bf0119a68ec1c927bf97c7aca8bb9fc82f2791a12ddda09e88539ff14e56810ed247c7612703ffa1abfcb5efd200fb6c9fffabe80b2d181c551088bc4101765716c1f4c3f764542a837357a581f2faa157e5cf271ce8c05ad82b26b7aa4e9753aa99fe0ef749ac9459f389824337073791320114a049e9c47be855b05d804aca66ba40af4d8cc53844b2186d6732a1a2cb4f2cc2af2ef41abcd3684be5e892815b09f607fbd69fa5caf236959cae8040e92d34d50897354599521a1b97ad4f878b8f2c11d2bf8a70a363848bbb46b8af38c22d203ec25dc5fa3cda81185354c277712eefad867d32629abec318845965a53ed51e0a891a76b525bb41dbc517a8b12f693f1f753a948cce09c3f031e0ca98441d6e9237adb49f920b4c7af736927664a2917bb53e64acbe0323c94bbc4841956f17142fdafde85294a617a4f43189ca37744b977d8099946fa961df772c241c65d76e1f1d98bc28fca810becb19268e6f98aa093de29751622ed488dd0e5d9eb2e3c7992288f198f3bd35eb1c375a232f6e1c9dfb107c44f384e6ba978b69627b096cf7edb43709d3f3d730822f86133db83e9bb10ab602a15ac089dbb3b461b16f3c5b91843890d18d893974e7312299c12490386ba27b4d2a02e53509240ed724f582243b675878a073eda224a63f429f346ab172868188dc108fc3dfcbc40f7ed5275737ea9436fa349cc5ce58f807eaa738831960ed20daf33faae5fbc8b63d8f1fa07d42988ff2d3e1066751d0f0fc8cf6ecc7294036c62dfb36fb7fc6f1c012f60e62e3f4df6d19b51d7c6fce2b1edd0953da5d1faaf4fb357818e829556cb7785f902ac640819c6b584e0dd32582db87816c4daf858cbd44300aa318f6b6f99c623f845c0d9b80f034d6b206efcf70a69a7e9b407c9279e0a1da86813e1fa530a605290899d66b1018d56b264d3c0c63367bdb8e30b4e5996aee67f858757fd9be0afcbfe6acf735066df3da5c231fea75900ca350c85440872b4a46a155f21f1523c7813d556e65c84541aaae60bfd33efb5863ed8e75bf5f1c0aef44b6c95da2f939b3a62b058ba5bccd74ed4b15159f5518b2da793333d6e3de2195d459c5d376c39cc24212277c5bcf166d56bfc9f50e03d92f6563085ca906f2745fe49f66e2ec3ef9a67eb6b41fd4c77a15066b57f88acfd63876c691d69bc2ea4c96722ed394852abf7753ca6c010a19259c74fa0b688230c210c1671b77715f3e09990851ac1b014d30a88376461752b39514e78d497599818c2af39af324bbbf32f26153c26134bf906576b7311f82054560bb24aeed7a159fe542d8412b0f4da1f3ad4de02f1463ced26b81fe33354c56dd6b33c66b8e5fd52d80ccc8e5940165b1f65caf5299538c4ad7eec699f4e44e80af42ea66fcaa8c69e13adb3aacfd2fd96ef20e7178b1a3e4662eea170cb87a34ebbbd54dff7c9264b67c87c5ab27743ba53b1022206d5c35750dc046cf3c250b09fcbb6936821ad7e1465e0ba443c100768a2400ba677724b2906ee2b11a7ee06a13384a216cbc9b5783d286b6549f0afad4e7f8f0b315443cb345c2bb0134a4a04dd275cba0c52eb6295c3f07710be59e7ec2b4ac37725bc32159c7639183ff39ec182085617147918809f6727fd5b6811929bc870db8c9491afa9cc0c6826a2458c7aed9ace74a19f2c07b5d78998b179be02c0953f9e50dcda1aa1fcfb74cdb85a5f44a0f31c33076788c36c86dbeca5966282998a61f732699da4c463ba0b56bde307ac2565700ca9624444316d448594e4a0b4a080de0437c5753eeed607cc855074c21885ce62751a8da5cd0066c9d29e023e07b888a6647b7219242779729d398a6142962dd274b89ca7610e47d4efbee32b9a985248cd9f723c912c422d25d38498743fd5d7deed7ded4693d7ab7f7b61a1c169b614d975400340932e806110e55daafbd514fd77e7a7916c6dce8e6ea768544804174edfd1c44c7b5bb37085f8ae5748e87b93aea521d7d9952008fb7d1d5444f6902be0ab938b12480a0ee5f7ede11b22a794e5d543b8c5bafae6ca418bedb00af8235797f387aed144b04c4124e992943708771d080de2a76d8018250939a0a60051da2a9ec317e83b220cd9afc14ae40a86a166acd04d21f74a3de6a3574c386cd03e0933a9e68594847190041d075db4ce052bfc03adff4178bd759d96e3977d43b3096e805dd77606ff4b95d52e679d27a16ee00adae62e8170ac494ee30606d2c10c0f2dec5cf432bb6c3478235186b15887627e1c35f50a3aab58273a978f157e464d41166a01ff83181a01b3930449b0005edcc90d46c8500e5f0c306debf6d1fea5981b13d048ea297048e7b7082b83657386686a6d3de52e07d70b7c240b212e7a39022680614d807e1ae57bbc343aaa6ef4408105cdb423cc97ff745a763ca3c99f5518bc3462f602deb001f3ac514d7f8e20872d21e7e090c23b07cfae2ece19b0f9a8c5e915f4a7709647033ce2c3e0b1e11bb66dedfcd21818f795b8de4949dba5ce79923c84c5c01e1d4e99ae0f0965c586619d230d23747039028b775d2668db972ac95f8ae0a24053e94d521f5ea166f1e8ed77a9c490c0f5d008a5cd9b3bb84eb5acbf151c7eeb212fd385f65b097166628d04c1c60e49ae0a74693df2e63e182cde6a93b4f2059d807608e5f999d4a74e73d31f8ded10196fc1c1f7c83a085adccd0de03f9e2299bfe8ab8b620ecbc5d694f3659679c0ce5377d761c7e726d83cc240b6fe97f0020b1c11b4408da965e1cfb4656f5675e907ea456dfb023f03c5cab2f7582a5a0d09330404e70f3e48484b9e1cb749b9b955af41e24f01c06ce01c789d5eef99b1c1623000e3375dd9c9f5ece3ecb56c82b4fa11dbeeeb4a07f8db2eba3f1f38ad421035a3495bd6dad6b652847d94a85b6ae8df6fa488c136a2b47f850d12c35c44efb2a7d12350fcd7e06d7bce8b3ae353309cf70d31101f4a876adcd4a3c08013091926f85c388bb0094b042457900f118312eb23410609c10e0c845802d48cb1fe2bc1e812b65078317286919e02a576bc14bb853668db09480bee99bc89c4cc81f7f2b47a7e89f0f638d978debfb88d04f3cf619ba08dc4b0ff2e187cc36620198b9256d9742b0208843b604666391b0f3fa319ce6d522f42555475dfdf786578bd88c92cdd03fbccbdfe3f35420034f42b4d90fed9ce202f131af7410e12a8fda60af2860686a1b037a51c3e3849b07b797cd42c44a2cbee983a281e824fd7f2125a4e4c7d3d3cf5bf2f8a4b95c20209a71a64025906d169a3600f4c8df80bccaca4bd96e8a8c62b14c685dc51f4f007e8a674ef67805604e8ec0beb36d43b482b4ab4898407690bb5fc11f0774b72d11548361b769183b29b90b035aa75e1ade321eac87378f12d0ef035c5a026a83429248101085de741ff76805f00040135c905350a22bfbff3fdf67e53f3f50eb4b2ea3b091efc8c10cb9a669fab98b6c9b818f086cb09fb0b3446fd73216a3d6604244f6de7bef2da59429bc081d099f08c337564d94e82d124f3be78dc470a13215a659c59cbbc1f2fb355ae2f4360ac2e45692d0169a62eb9c56364d2a9974cbcbab80403128bf2dc72888d4dad335dd9c73bb4b31878a39e7dc8e51cce19873ce5fbc2c6777276ee397b393132b81b112d8d4e57dabd465d9bd21cbd0bd808f874aab9885abdeef56566db7b2b2ea7a5815e7be81cab49f524a0d021bc1405508947233bcea77a887b977e54f79156740767261de1c3f4ff37149bbb7dcf34f09536fca39e79cf90fcb6b027e3dfc8fe2efa297d16ba7bc52e94d8a0769cc5881e146401246921a0f7ee83f2c749d011febfe6954766f9ee61f1cabcd5f453b9381b28906f9385a77b2950e7d7651ada344db44b1a087a260d01f513f9f2683e814744d9bcc97674e479498012ae57b9037346ce0ec49a3c54f11a8a94342bc84e901442e88dcd7192183f073ec30b11405140d42e1a21286d0af94df58062289a15328cf983c297dda9cf274b1e3e5080f0ef85cfab505b629f3b6a3501244727c164982fa530904205b3a983427502302eea21aa075d1ecc62a2f8a07ea4f91525400e8a328b4ca07b9881334f3791178e4650c06452d452d401545ff2e4ef4087d9203ed71c3e70941c9750c45aff28c3085032881e0faf62892d2610a183649fec09192255f1a126d42245a051521040ed0f5055327276591dede1f3d892ab94fe84e97224c0a197ac4d7c1924991f4a065c044d3445f28344c149abe1a495bb8d065896633bf882a51344fa1f077bea099929cb78326fa4c720089ce89fe1a3d089a95746876fb3e7a08edb804279a1f092345af2645f3222f2eb4c9525c9e436d60de180293c7ceadfde1e7824bd10d96f8c021060a7d487b510ea25ed0428cfe925476b222da00fdb82f994d18f4f5e47c64a8a411a28cf4efa1e456649125477a28e159b406be91019411142c521ae561f113bde0010a439fee4a56f421e4e80b4284e5501408dd008431fc4cf4e55c100dc142bb248a22896280e245f31e23f4f934880e008d1394c7901d9dfcc7033851bc426f26f488ee1b18a029021d8267cf10337712ddbd210cc232c90d0c5eee13127d137d6a14cd3872922868d7cfbd9e235cd0dfcb547edf7ebbb2756f566d67cc134f5321502ae65d8a4bed4781eab20498c4e743044867ea20d2212cf200ea86163b53889cc900d012278ea220a0ab5c22443a57ca5a1c41bfefe9408a56d882aa31d296fb7c803e203c71fae2b16caae6bdb371f57a6b4c1045392467e73770ee1f97a0cb6398780b077bb5e9a12acc7bbfb749db7e98baaba4bdedcadd2443b75123d9597431ada28b6eb1091d8c6dd4d08ce8a8c7288f4cca3c46a83cc6c35899c708c7303151a5eaaf73a6a6be06d7ad0aa90e48e1aeaaaa5099f5e6f1af8b6d1936e2f4d7395350c68e53a1f2c6ebb676825ed004eb046bac76cefbba6e1937a0b1ba83ef9b1e633eb0328f39f29ea66544592fe7dd80b3ebf6b23df7758536c2d025b2dab57218c7f036c25c0fb34e9fc1181f77c19be5b2dadb745ddbb58b69755d5be2dbc5dfcdb4514caf89101b5eadbffb5ea153fc3a0c5dfcb930233211d7f430ffda271a387f69892754b71fc54ad8c5baed3074b1ed2ecc5a293d4ca1876560e5c789a9b9498ea237e4ae0a3392fdd32d0787e0e20cf1cb8d60286e75de53db568a65599665d9766d3534231931e6d77f4c19eec3741fb662e0f5d7d75fd77df8eb8fb5aa6d5015dbac53573bdcdbe01aa43aa04d59e1ca0aa94cb46ca73a00aab3c2b5433db1ac50090765d8885735f88fe5d4572761f693f5e861ee4cfd67a787b9bb59d5ad661ac1b693d25eaf5d953d8f66dbac97f3b85098f1a0ec9bfc0556724348943f43766eb631c77141b277a78bcb6e8ec32392786f3d5ee50cace46e7bf1eefc4def71372dd47a5c882eeabc3604b835b3fb5ea28b4a3c3ddc5dc87ed3458d441735223ddc3df3d8f37beefedbbdc5032b5fcf6d4f55d7755db5d65a6b61951eae420dd56d880cf9d19387d7065666bc7f6a5266bcb50b1e7007a5c2eccdf162b675b390f7eeb66a7f55b7ddad9c34d56c2a84dfb07b0b0bf8387fe65cdfbd55cf6ab85fe9224f4b5321f08a28a5e6cc25c5da5397b3fd2daeaadad7becad8f1ee69c7781566b761091a2c18586915eb2b0f6acfdcd5fa0dbae757d5fe9a585b3b5015a641b76b0d79d7bcb3ed0a02dcf6b5ea63b815025b37b0128eb5e64a96555355edcf735f5f9f5ebcb78c35de3d4d378be8f1f0f21e6f90dc08757286dcac949a2eb6d0bf673ddb78a17fcf82b29c36e69713e7a7d03f4e1797c438c6dba5faf76ce8c32a188f6ddf69e8e284feddc536a67844398b2e02fbf7cc65f974710ca71ed5bf9fa8236e63a6abd2567109379805e961ee172c0433ddde90f7bf04bea7faf71c2557d1c5d5155675a9a092aabf6e50497ca5de5e1594f154c134620c0b739c1ee65798e12804f3980947a90e1f8f461b3a45f63141a8845f2825a4a1876a0ccc514e3dcae98b1e3a093397a0ae42d761b6f7dc897ebe4ea83c0a951f0797d04309415d450f7516bd4177d8aaad8353ec6dacb2ba722504d5182aa8c6ef5470f97a710cb6b7addee3c56e87b1ddce421b5d27b73f3f6caa28a1bb5d842e4ee86e2fb18d16badb536ce385eef6067411aabbbabb4a18d895400a5dd92b74e590aa2b897155571e63b743052f042d047393bd410bd9427411aa1fab84ca23914aa8240e2b0895c34e41a8ec40a050098c122a6129a19295ea4a388e928aeabfa384caa83e15cc721e4bf709c1bc440f759710cc48a482bf1e151cc650c11ceb376d64a137e86ea6879aa9a94705d9184a0f757f62fb5350c9c241e5e33905956f480924c64a2d8415e6257a58054916f246a59f08bd1f84db38ddc6ad5935a1a317d4348973bfa0a6c9076d9a360d5c1ba78dd3c6690bb6705a382d9cc75373bc6d1d38f7b60e5c001d5809ffbe5832302f808b89588db1f6d0d2617f30b0aa5b49d18173ea23dd696f7f61fcd7056edb56eb586b3d4cf9dad7d78717f30978cc6fb0597f71230737460f79efc4384d5341294ea475298fdba3c1450f79163de4ffff99cb979753420f391dacac8a3990773b447ef292dc44e8a55dd3b269e3b469da353d3bde8f8342e3df2990296ff7d27f617e6362a49d8fe037763ee0fdf76deafe84632d6c6add35e77ca3793fe71b31b43086db3f56395f59beba7cb76bde5de9b2c2189b73cef7d64a13b9ebce27d83d7daedd461a7c1936e25428c3860c609c85dad5c1186a4f53b11d6315da88b932a8d4b9a3793fe7bc956cdf69dbdfc456f2e67302ddd59ef2a00c601c4328c346dc761d94018c55e17397c831bbe35628c346bc0a63f4acaa42193b8ef1fde3d76acafcfa3ffc0bfed8fb06f5deefab5ffdaf2aa44aa0ed6e0c2aaeb086ee545a618ddd43e0d0790caa7bc6b17f6c0db6f10edd3d77adbb664ac1761e6cabc0ed99e537dce08e1063bbb7c136b73cb6fb8e1063bcefdf8b63f7dc7fc1b65f77eb636e5f836dd0ddb3b00dc7ee6ddfd1c75c2174f756d8e6d6c7defe4c0b6ce8c6b1fb8a14bab741b7ec96c7d8ee06db7684186bbb5b1e73fbab836fe74c5409f0fef615bb6761056e5f55fcd82baca057d0760d077fb36acf63bf2db08cef8b55610c2a3574a75283c32974cf4c5409f0aefb8afd636f664aa1b59ae3fd81d1c26ea30d1b16e8bebb05b9f3ee761b6d6e7d2c5da1f6b45b20b4b1a3b742b73e6681da59e12ab440cd3d15eefedd02352fb90e7e1638ecfd89817567b751f7d7cd8db7b971e18e311ddc9177f036372d3242e4be830bb568190cfcb878b092fa88a697f8e6bbb32ea4d8c9101c9bce6160377701a53448e4e36e5548143ea31e74d8b91e79f6fe5d5925c43fd7834c8f2f6adeef41139da8f6feff2fcab3ca2d44b991248a95bb499449b17257d163ca9d65bbad3117ae5a8f168427566a4451b152dbe1e5edc5bb6b3a55ada2e2c7c90719b48982684ca99bd8c54a5d254eacd45cf4173ab1528f59c7941a4e2ede587679f890c1216737274d11e1a18995596f4f43759bf62188f3315e62658e8b77967323a76e6fbd0c65122bb35bbc95d90797fdb6f6ca776324ae53be9ef773fc415914cfb9edcd5da023deddea4a78ccd03cc7e32e6e8a7f8ec7a0385703ad4801457448a092be74b133a66fbcf9900344c8ca8f4350c6b4a23937bb391e94549eae39ff3811fe418195bf38f79f14708e8720ad3f53d8447237a82c5192244e0a902845e686b031034987063cf034a25f12b8775213beb63c0dbcd43d2233b61e8ad74474f19aa8cee96c043d447566de289233b6b26afc733ce6f07879fa8b77908df9f3977a29ce5f8af7a7113fdc08d5550dfee2f7b79a66bd3f6dda9d5df3affdd3201cbffb55331389391e6462cd44a280fcc3efef37a5ec33154048d0d3a550f14611577d9eae81141e8e4fb782458a067451aa8ae8058c7f8ec79378c73fc78349ac04c6828cbc7578fc70f1f06873db587ad53c6a15257213097aa37972cc7125cdbd38e0de265e0c5da973d22242e266b023c80b66c78e57cca52e793d16d12174650f123a774a1b37ca99b70d98f7822c3ea0e848a247c71037900e371748df6b44521a46ee0586f2f4690185f04279f316e7ee11e3fee8a0ed23a5d0122f45e4da3078dddaa2af103d48772e062310fac3a60e519c570335ee9d0fdc362e6f172a21704026033a397cb86272e478ed546a0739ba842848a011f0b81c80e0480e4d19b7afcb9bc8ca3ba5499323717000a9844307df5e194873146930c84d8f9b47c78dc4e6d561a64df4e5111657ca13178b062fa1a890974a94470b89cf6514298edd9f42ef103e47eab8716e5e1f68de35615c365a9e4479db94fc2162e90277c81c14ea1e99d44a6ad40a12e18e9f3276a4c0797ba4f93106ba658429853678f5bcb961fa7ae0e8c3f132a1e42ec1eb73c8092037cf9d778e9c406b0c9169f73c70ab5401c3a41dc1884b84ef87ab86afadcdb3b4c1a3172ed18b45d0983c7473dc2f7321c071f1e2bab9f25a71d23242e2eeb814e44d7269477be9d29abc408bf008bd4af6b88de8ceb4e173c6f502e67d02b75982c02d14574ec94ba4c8cbc15e0f97ca5e8ed691a5d60fa457cb22974b21974f9fb7889d0ce2b493d6bc7465de2d5e166169f1a0b887945829f2bab1d4c3cd6229472b82a5770e92eb64d106855e11fab83cecb47d71ded6bc7eca1cf2d2ea6179df407107299954e4c553a9870b572987abc4526b07a9fdb3a8834283fab81fd879f7c441b2a64899378b17571116770e0a25256da4222d214a3d5e1028e53064a9bd83d467915ba7d01ba60f0f3bae993866d6bc5fcac8f1e20ac1f27a00e58a123b45de38bd1e7ef472b8462cb95b20bd4b16bd1814baeb53c88e5d1c286beacae4f0f29ec1e2a6815249c9db4191966f520f2d937244b1e452820467517ba8d01eccf6010166ed5892e3269973d19c791189a9c205909517fa2416f446d25bac64bb9c241facd0aaeb2e080b8aeeff2ad59e73cef907ebc92c9bf70fce6c865916867f3fae2ad338a79bbfc17a98fb050fffb8f0a78752b0aaaa733ceae24fd3746bdd97f62b022b7f3f0ca4f6c419895d6fb4ca9a5b75201e891bb28e64a5b55c242c14d8addf0fe0748aab07f3272e93481ac44aabf651a420c4a32e3efb04a68a5f2e2f2f4f51ac043a7af3fe30e5d1314d885f2e8f8c223a202b2622cfcbdab2eb3a85893502af793d5e152806ccd3a1a6404a3f5e05040219fd36a441bf2aa0cb42baf3833417af2d10d88790cac44ae26b97a6ebbaaeac2b07e6f1cb411263a5896943e2e0fd417a735f483d5a770ccc14bfdc23be970cecc62ff728efdd01a77781601516db756f2ae2e3c460bde689c3302b04bad1476660f8d197560a665df6519578851f4189d75eb5aeeb706ba6323f4471bf1cc90a7eb4245655a22af748c887b1f00f8a8f33fa4f59e2d40f98aaaa3accaaaac84eac0a30a6aa42e5aa762f554d215555555508a77c6b20f12755e508d213586de12a965d5d0fe0347e39478dda468762570447825e49626efc728ef0b88995433abd497c1191454aee388fd4f2491def18451def78d7811485c02ae213f5de81502486558426e69ca71c08c72fe76883154fba1d65c0eaad76ae724742524717ac299e5eac24d225a2938ab3d44fbd5184de903bbb55390c3bedb94b1df5307b1003e2287ee1c06bfc72788a62a554d51591c50b21cea313fb948aef7ef400480c0f4d148ce7e52705efdf784e62e5b08a568155786f4c3fa058bfeb93f0de496addbb42516daaeff2fc58a0b02f82d5f8e5eeea008dc2bbb8a7bb2d4f7754d8bb27e9dd12f52e83f56e08976be42850ac24fe04bd71fcbde049b012e650f1cb3502a1919a58198a96086708d0a8479cbd3de40632b204d132e50d98bc3d1276126031dc5220490ebbbdab1f7418b408b9b1e7c1d0ded3990586dcc531f0fcfc5892d437c383bd33707e10a8b3e407cb1adf549b246ce0fcfff3c9b19466840a74f6870a2302fdffd57f4ea5d8e6d30947e971a275f5e1282b709413388a02386aed5df43366dfd087bd27cdd09c4bb4ee44eb4fb4dea14613d944c228eb48b4feac41064721101522aa031c85e5c2457fb1442b434b736ee81f3cf461375b26fb070f7d89f622491641565616047b76ff3d0c868203437981a1a07c0f737bd8f32c99d6bbebe209e3055ab3f7e75932cd451246d913a9f621f8e1c3f387a1cc602811ffdd89f66423ebdd89d697c9b49e84f184a1faff339bc901f41a643d698636019a59673efb734d2df97c22b7044f3e9ccdac6793e94c4b43aa39b5cc924d240ca7d6b3c9746a25cbd0d29c271e7e3af634e493f9ecc85e23ed59966c369965fdc95c7667926956b6643beb4ba613ad895c53639675b3a419da04cce61a5633e964839facfeff7b98beb14ca6d140aa690888674b34144814046029335a1631ebcc7b399f4de49a99d740d440d641f40888109cb191c1c14ecc0dc31acf35b5640b275ad6191b59100feae257b5f5f9f30ff9d726d92c7dcaeb3be574426eca2c1737fe7ee31756cdb229df39ff94ce7afd39011e22ffda217d27357fce394dbdf4ab3fe7bc75899cd7ac73deea0074d679d5130490b39ad7d799296b6b551dba3d73adb990cc73d66e8ea473fb69e67a277356ea9c35ab80a6d259ab5ce7453a6755e704f254ce5bb36bd2bd36e509394b6077de20ebacea9cf56e9135775b2ffdcb20d89c5aed0f79d59a6f6d57ebbcc1a99829e05b2f899d423f7f2d25f33de4a9cefbd30c32a1fcfa6118fefc99679d1b655673c859a75aef9cbbb25666f34f6bad5500640d5a739d893beba5c066c84c5015f4919c6a0a99e79d5fa7d0ddcdfc359afadd45feb13fb826bf181bbdb39af99a99f246a2351bb6f37167ad79d7bf7698d39c5722dfc240ccda55591d96b5eeb3d5dc6abe27c842cd96f3c6caedab33bb662da62fe80bdac2fefdd929ab5a67260e6fae2968adb39af9596eadf29bea9ce62663ce39672a6d627d5b9dc9d8dfaa730299cd79ebcc73896c4183e92afdea55a779679df5ab579e77ba7336e69d87b9cdbf9c73877c41bb7bcd2ccf3ae7b09cd5095a2a739d539ec1f49bb7be90f7d69a6b9ec1b4ce1558ae73ce61d98276d79d39cb73cec69e811c580f9b8034efdc660ecc6d1bf6424075210bc0b3d6aa5e931996407c4be4acb3ce3963fdfa6bffc08d8ce740a3ef0e384144e740d0901d0348421d283048d0af08f279154eea245520e9bc009cc06d2080201381049c28f15220bdc44e38b900a904b28a08e604a805866cc2621cc464650cbab938285f2abae8e3a0d8f93ecb85a50b582eaae82b415398579dc88180265777185cf41f863e05f54da2227817e990ef3cc7ff7e16c3ff4ff1ff153cfc3e9366cc32b4251b536e12fee70372320703ff7902abff2ce3f76697c6d0cc0c5963c330a9c674b670a2656d1892b1395b98256394256194653d97c89c2ba519808a15260a3021a245012654b468200e40ff00a26f182e936959676c6433d2d626f87f6207d19d6c4bb467fbffb6875937c40ea23f9748b52c3367336bc97c816c9a69d578917c229b4fa75659d28ceb9976a6a5c6b64c554c6ae1e76e4ede92911fa89f369314801b3e4a52ba9b92a38033aeac6f459387313b0f589050b4d2c2e68c4b862e32967732d0e344c780e55a0c4029d84049d0e2a1670f93e6193b3c20409265236b14893064d853ba6e508003810b1f39ee87b54327929d004f435a4e128e9776200ddaac0c406420023b51084a797ca81c61b43c323243a3c490ffbf11965a0c818e2c775101763c62a6f99ff1f5f6ef22359fb7b0fe2fb4417f3389fda3e8ff2bddffbbd11d92c0f934d2a07f2446a0ef3426f67947937f0df0ff46f4ff66ffeffe7f0ec0dad79065cee459d2ac8c0c09763416bfbcbab8b688ad910cffed0ff5f9ff49b6e633e94c439e399b59664c35a286c1b63a88de6cb29571e861cda4d38904033b8219c18a605f605e605d605c605b604430b0e3f1683c168f5f47af63d791ebb875241ec18c47a3d158347e19bd8c5d462ee3969168042b1e8bc662b1f855f42a7615b98a5b456211ecebf865fc2a7e7d7d797d757d717d6d7d11bfc0bc8e5e46afa2d797979757971797d79617d10bacebd865ec2a767d757975757571756d7511bbc0b88e5c46ae22d717971757171717d71617910b6cebb865dc2a6e7d6d796d756d716d6d6d11b7c08847a29158247e11bd885d442ee216914844fbffb58739dc3f0da2b399a5c6b6d47aaa9d259bccb2f6bf818779024f8696d6649221ffffc2ff5bf8ffaaffa7faff0aff6fe29fbf71fdf31d49b634e404dd45b339c2455f22fb92d95d6c18ae219b33966c6161cf34366752002eba59322d0d59f6ff148053ff3fe15feaffa3fe1fea1ffeff76aff046fe3ff0e16da38739cd966c4cb27ec6ec4f3666673e5b3cd1d2d69cffeff4ff4dfffbfec1437736b3b8b28c595b58cee4b3c6938d590379e2fffb7fcef2ef7f8efbff071ed6d07f67336b2beb2b8b8875fc7292699d6d11895f5b4832322e2e33e3d659118b8996e54c3e91cdb22c1a49b3255a8de6d389f622d9d462be6822cb9afef58dff577b58eb7736b39e6a674fe6b29ba199753564b2f974f6174bb41a69cda4d992ad27613cfb320963466f329bcd645f22bb5972d9647b269bcf65522d99b6a686d69f68c8e6b3bf58a23d9f48b526f3ac23ffbffdffccfefffadf64369b23fe3ffd7f0aff2fe3e1d7fb4f4ed06206b2f9f4f0fbf9ff19ccb4b366720266aff1442b43ae21cb9e7c8ef2ffcc87ffc43b9b595a359ac826195a9a5659d3a9458696468696a6c5b67c8145836d899686b59cc15cc658b26179155b148f34b088595bc7ac2d2de7120d09a32c8dc97c6aadb13d9d6c4d2d32ada41a1386d6ff333dfc58ff2fe1e107fe9b25d3d09067dd997cd620ebfd8cd9974cb427b2ef7ffeff3f1f01efe1610d81be9b2dd998671dabbb88a0d8d5afb820709184f1ec4bb49e444beb4f279b5a0f7b2ed15ad0403343365b349fce166acce61993d3a6da67a0fc07949f5039f4bc468ac253a069fa5b33f08972275b7cea17058b8a01949f6827b855c01cd0d601fec0bef03ed51f4b823daaeeee0074d33850760c548a45b4356e0e1ebd81a0291cad8007fa7b0acf405b60935e3ba45ae99b9b8497c2111cdc07d429e814dc584f5a2cf0a90f474a007b5c3d6ca7ed818d02dd01d8d2525d350b74bf6a057ef56eedcc7ffcca259112b03aa056505d55f993fa49b5c076c2eac0be8443adb0b9e048dd40075072b5de62000b3cece7a43ef1bcb13600788adf94aa570656aa15452aa129abef4ef96fefadf7bad9ddba557caf0aac504e29dca7555df35eb917d7e299a5b0a9a22d86140bb4294bf449e554ac081c3c100e618bfe7d7c87f6e8a943f7c60ddb0b1543848c180bf1c1c85cde35d2d2003bb12b040f2080a26f4e5d08188210911c11fb2481f2d0698346cc162a5a59198cb03d83389b911ce1f9b9533707e6860512b078a0010450144002c385a90970656e6ce9c0820a5a58a8622e3134050224430ddac73749120b08a6263ce569c3260d1a31613c0802e482161238c00002284cd099c3060433484af4e143a7cd1c9a3353a2f4d091c3115e9e3b68cec075698326cd4c8c7862c213c150a0377365c87cb132e447d2ac85042c2060c0c4880e611b9e98e8cc79b38192216d4933090c304162448717c1109d3970d89421f3c5032b4480b4456080890f1dc28cfcfb44300408ce1b367365c8786085c810203fda08b588804508adac3d4b270e706afa5325c4478f0618203ef9d1a3060d2d6ddfe6d397524bba128b855fd1563495cca5b7383167bd589b80dd2760d055996aadb02964209fca13da2828a80d73a75f13cc54e147e1ad729fda955557adaa69baf3ab6dee0071a06bbbb6bc892daeedb6f05b1145216487aacb1ef9cf29f39fda82cfd84cfb89ebc99104aa36e9b5e5574c168e1d40dd057e66fcaa29aabda8520578563da8817e3821e22987e62e3fb1611274041c1ce8a2390d7bc22016d5bcc2c65acb4110290177a0aa8a21459b04a7311dbb80280f288596819fd44f6a002424acb14e5712d4c3b32dc00511786d37531a835fe023caf226d54276ea8a1e7ad272c0d3c6ea1d69d26f2af6b482f103921070c111950e5d126e069505e7b082e1f8c0490138ae04990013db98ca4e6020077dd9ce94e460e272ca0b54ed0e2b8562d45dc154976dda4613056c0b2e0949507735c1a4822d41358284db96f8a930f3f001748a5f155d352c8a4975df114209a6405db6c405b21ed074c369561350ddb483ca01c4d8cbd66a604ba8ee4a8149446bc551b4535255192a4ce0c01270800032445d366bed62ea329a48b1fef85a210ddbc5f6c73574b1b55d5dae951ec08f6239a5dac17402b165cb36d8880e40d69dd01aa30040f17ab1436ec5add42b500e4d5c444a407aa1b5e2554e5bdb020e689656de671b8bef9402a10445ce30975b3aa08be3b2e54a140c6e5440c319e32c59e64202160f34b080022a529c30a044d8b16b986100013a6733d777677728048801841528ac4d409043654a022342845990f07b553938325aaa508902c549930d14186ad0ca529264858abebd3cb207ee1aa169c17c221204ed0c440777ad324488b1dda3c74a9530b0e294d4ebb26b159598302c926608a030c0048911615e5b1ba09e9a983807402ee00125e8cbcc960eaedc606001631128c5ebd0415a00c48762bbaa1eec20d3e58991223d2860c2010741104001a283865fb6b42b693d8ed71b6944fc901e940aca7c741e8da7bdf3abd375341d0da73dd85b740caecc5619036b21ad62a2a0a7b4842cf5149542e9a70cf326fda6ae6e5956af6baa79de3913f0ffee5780836e3e8d0c89e2041cdc4232d3ce5ad44033d352635b12fb07101df94c4b9a259f4e75c4f05301fcfff3e18ec03b9b59cb66f2f944ae69359fce082070375cb9c36105bd02f7caa5a0f7b07ed529e8bdf7feff1b347cd761c40ea23793309e4ca257f7d09d4fb41768cc65763e8c81c3ffbf40237bbef56737ac3f91dd2c19a30c6d895c4b2e99285cf42513edb986405696053616b8bcd932f94476a7052166cb246c4182131dc6f3c53299d6cf983dd9649e3d9bc8a7b3275f2c914dd8c2f0d3c8904f36350b4cdfc3cc9264bd861932d94c4306a0c96c3647cc60369bcc313906fceb3b14ffba904ed345dd0528ffc97eb26811cb4cd9ddb261abd83d78e85bc3eea177023ac072c50ab183e812fe733c4c01c93b9b596c4eb4e68b13dc699111344b328166ec11b1237140a10f912f85a8ac78d87ceabaa63658e2634cecb5e380bc58a2258188be20575b0a9aa021b19de50f96d5ee011571800403a0424747d55ecb32ec7bc83ae46d90da0a1c82955872b2e4b7c16f775002471121d190ad0d99ef4e87e10e9ec240dbd5de567b60017217466c49853cbda8cc04274e50892202b6d3749694d8d813858a080f8674982a7beaa86051c88b113f5a09a12660fc1cc1400cabd35124131e80444150452039fa4c4e9fa3c40e0b1a52e4f409b73d48538f84312138bd25c60f1a152b129d99f3454f90c42649130a12262cb2e43d294ad478f501f33140943c08100926663468d2a2d092dc26030e5f58543ad2c4cb5bcee2e64bc59c1b23117cf09189ec806007110894cca969cb33beac30e4a4c61fe00c1b99c5a39606055f2a88a1e399c79ea841d120c9dd0c03d2b2063eb9915985b51f8acc32139100a05d4da13ad4c0ff23b9082c501a936121c98ad793a30d0ba40296649f7f335c00258f3edc68a922e29f302a7bb255276749838777638280860c53498423bc5eed4e08c72858124161fcab086201d71c0a14b158fb3fc61d9943c19b558297ff0bd1106034f4dedab6ffad85c6a2b92c64bf3e4488eaa4c07d739688edf579a92366aa0142149257797d1af88093d33253b8c9b1ebb3004e04d68cc853854586faca2ef100f328c7c369f2d367e28a959506684e2276eafab20cb180e53810d04195d327a54108426ce828b891458e8f12085c065020b9b515068e2f10062c9e1e1a270931bef0c97d2062ad48902a4ae864e1b3a267a5051db767056f51f8824c096486c91811c60459c2f70c446986d5da060bccbcf13d6004850e1f43c86450c0075f8837707d2ebcd21055b4f10dc083ac459a66c3801e36f85c3060b454b9d2071df3bc740789905e11406b1c6548bb74070a96b306985af0916476894c9f2c6956159049696371a94991184ce69419e08d91159774ccfd11a1a1821d10c89cb814a3cd4d5da010d884e820e2528a2ec71796387c7501e2e1d2d79d978b648a431611bc2e5135d941078423688ee45d5dfa3c4f1d7106c011d6c66a6f04214614318f25902050b517470dfc560c80a5058c527b1ee0f000552382307318f8ed19e9408e8921623454a8f5f13d0a02910ba864aa99505a164346194a040000000002f316003028100a88048224cb812053f61e14000868c834503626114d0581280a82200642100661000060000680100660280830a6d20b1e40e5463e7904fd5aa929c8d05be7c07b02bff9833264ab8c1cd7afcc95bda7434395c3c0fbf27ef18012a03bcf4efa285f0d604cf11f982b8deacd2797c8a76a9fff81929a17b267d3c1d4da7b60dca9f9d72b3710234a2bfb11b471985dda5bde1c9dd68da73297f21284e05febe56570fef59dcba3708fb0fdf967b325f33b711a586bd9b46adde6ad074fd30c3187d42f2d166edff823e6ab1b177b8b1c768add890736cfdd4de4a6fe8c587a163faa9e44f27b829a62ade8532f104b81e9695b871e4bcfcdb77ea6940f06aca8b5281783e2c76695ee600141f3dd0ee323e18019beaadf52907e2446fc557526061f1e603517fe2c1ebb87debf91d7456150a5ab312d0d0d17d16fd81fd8c5713a3f390ef60900b036a47b8b27305a7f77012fee99a601df3d261ba7bfb1d72cf30e85789ace4df4d370fb73bcb54bdda0582c5d973f635a5bf6055a9125bd99f55277d368e67b0d6e83bb877e20fb68c15b430d5a2fb56259a7d627101000a7326b175df7ff63585bc6151a91e975f315e78a65eea2164fa37bbfb1d72be9167d91f45cf88fd8d14dd765bff1f1ca0ddc174b915222de0f4ebed414ecfb133aa05940deaa4c435b9169464c61983df519f06e34c16e1f00751a11ea17b0e5f3747bf4f57bf43ce347f7822c06d217d7fd1ba78cefcff1dc341744cbe885d376d527ef5fb739b9b1b2c35eadf478f88cf6e48997ed0a39bc63d99fa18147914dd9f4f45cde32c1de8e9fffe036d344d7d8946ff18962afe7dfd274f82b51eba352335bcaaa9b73e48b0d1cf93b690c48985f73bfac56f2efe6a4fa721ccdb31106684e6ef1510b7d0c358bf14abc41f48f4ba8ea07ffb02ad2afd5187e4221ff1bdafa755d6cf36128f0af7f98c04b5fead6ffc87cfa4fce4b557c0427d9996c4bcfb853cfb53f5337e315a9cf3189e253e9ccc434da3961eb288ebfb6596a861c7f598ab252f5bafacb528e785fe206b19ef48c2ec3c6424bc396d5e4b0d6c44de0c863fdcacddd9e88c802f2b29922d5f79fb877bf47bd0efe853cf03acc5968cabdeebfd0bbfe8b7a28d0ff41c1fb59605a4f14fed8af804ba15e375e51bd16886d2c1a3d3b39770d12130de7903fdaa53cb87bc4f0c4ab9aebfd3fff9b59673620e539cac60f55f97df849bf6ea3e961e620c59bf761841a7f2f7608b940543d0eae2d0e2e1d3b7896e3b67bacf22658ea01ad336bb8183548112b08b2b08952fb7f604834cfddc3cd9ffdf804f96f3145f613c94dd4e769dadcb2fce29003d00ff4a2e5630326b3f3daf56fe0c907006debd5c26d74b5be45629645f56fb9bcdb8be92147939ececb9ba7555239ae54d2718c72e8412ddee1d5882a8e0387a98772d43d7b5621778c5f2fd56364c555ca3d1bc17dff3e6d0d3b2c25f99a1f135e9166f75d24f5e2aaba58a36b5f47b197687e7bcd53a0dc47eda862d0df517ed3eaaad1daea3af942539e054fae318580ba839047e0a9fc3b6736f001d48bf78c69c217439fca48d7ad3cff0fdb76a023ffadb98f331a789bb404eca2a2597e91e4c1564d666d151d3cd45cb1f05542260f2a6c2e8de5952c1cd326547f7820d4760616a9a14e86cd75f5d042d6e19a40ac996257982b997b119ccdedca99bc4b08b35c31be75868355d837668703e6c6162dc15d38ce4dcdc28fa67df33aeb61a751fc9eb4dc10ab6c2fb28138dada0dbe760006bf68a5b8fb4215eb719db1f08b02d0cf86df1ad71a081a5d36b78400570bee3ab77ab09fed3f5c8733d05906db7dcd491e21d322a26772e220205f57c9be48de399469db95b0a614770f8b27839772b781eb7fa0b52e107d9c6c170ea762f6c131bb0bdaa407d527e0ae11165a31db23be78493b69f81361d7f6358099c8f34df6f41dcd8e37ffaa68a5542c04b6966a0584c2faafacb4be5d23cba3dcce35793ebdbfde383dea08fa7f1005a80f4fb66f89aaad57c1e2055983e17d0052f1a74d42331c0278ed5deffae83ffc7081dde3de650330ea44dcd7bb3e1a891589fc5841c030abc5262bed31aa097e3c71908a17d8fc36e854fdfb2dc6880d02f8f8e20bda08c33bf0fc16725025e98a06b868be42505182b3736ec5eba8048e62914b8af39d1fbd3b33da2a8aafc613a6a4ed75fd5aa9b0691b7cde30f0cc6769f23773825e538e9644a8d7fdc33bc39733ea59db21dc42a447f6e8e8155ead21d11af7bf8fd75579c2dec914818e725a8990c18e8d53eae8dd6298cd11772cfb57f22e8c570f36c38e8cea84ed1fc89dfa173f4c90e75ac4fd27a55702d9ec8fae57c654a5a61b78401d10dae10dcae313b3944d7eb0c389b81632d23591c223b3a5139e295f9a404f6530d8cb4f8582c1e338ab7f1b938baf8e8ed51ea8da8b64e791195ff154219f2b91f33f636c63b90c41541faa3c00c710af82bdb1be78153a4111183d8bfcdd7e982736499fbba52f883b6a39aa392812a1b71c21b4577a87a1625c15effe790ea3d659be1e4bb51a0b037564fcb56317828b693502dd807c0ec204694c6c5c43a9fd326d0d43283fd9032d255e60474bbdc50cee79cb64a5bdfba359e29310d57e6751d5f0ea93edf9677f346589e608e9d119b0745d3b0743a4d999c40dab630a1aeb046d0fb45bd2c44ea3e51c4d761000b77d85f4366a9711e3b85748f33267a5faf550958fe3562a55d717296081f20e79275a99cfbb8652c8661330314177b0662b791d48a277686a0e0c9c0b8966f83f489c08658e5aaf3838d8ffbed4ca5916020d8e4dad905ef3070aa07e06b1666316d693b8322c4af3502a3cc8c1f89ae9802fe52e0ef96c184cfc6310d30ac980e378f497c524941c84fcd0ffbf6625f439e812a541767cae3399305b883b6790b70b2b89308a1192eb071cbace6984c60b8f140c4b7a452cc99ff596b8a74bd498b57a8ddee460f515181bddfb37094764440f0d419a6481f16024fd6fe6ebbb8295e4473e6f467f4c01ee083e8ea49e817ae2e974a524fbec74014325bd28041624c84b980241de11c17c8658ed7da66fd5254101c2beabbccfef05e6c39955bdc25f75b8a64c0da3f87257987f2b44430df19e1cdf158e5ba09628aa44de0990a77e2d571a705e5153e16a7c7988cfbf40b884e0f574d4f95fd43003408cbc0ad35166a1654ddc110b430d430a7d60e2c7d3f4a676be101b4f1e7104343e5fb0b3c30c6176fd1d8204eedfc94678c06ae54c9702664d5086dc38870ce7f1d71f5c323a464411e001b43d85ec22f4ae980928eb5f368ca7a0d4766db81a463cd2e2577666129f7d10c4f1391d21f4a6f5e74146a046723f17299122f910788e6070cbe80d56d60e7e2cb446cfc101b2ef321d631616c159e6b0aa945dda677d5c7d28cff86b39c3948f5cac3db0111be7512ea6f43a46f7ebad44c7283fe9c226229cc6cac8fafce1abb10feca8cc9be3454b0585cc9f3f3d8fdad83b98c3317358db36f038056d660c60c848a06fbbc0162fb97e796723ffa6ec4cc50a01eed0de6170325513ddf8094c5a89f5c8f41604eab37924361f1061117ba95287ac1b8b47f0f83e680d6eb490c1577a52949f969ea97079a65b69b1d01ce6c356e90754f2163e272cdc11326b0aa54b04738d9f9f67cef6da196ddf6eb02c3fa175cf6ab942a076682c1da4e91d47b49627bcd4cf0075f0ce019ff0a85771011e16c0c6ac8f8a460f2757a1349714a0b77b9383fc884027eaa9201fdef8b403b867830cfa252517f86265bb90ea838d077bcb12f0f1be36411d1542f08fb4b443af35c9c5ea25d00dd49df0e49b296d42a6a7e8671c7533065f49fa23f83ccb7139d2eb009a50680f3307984cfa9910d5d06a6f10e86720fec894a2b62c5cbaf0a22b199e1b57389f57513896c78a75955ebf886413753308fd176db0521eef100d396ac7e40aca1a492df60229c6744b792ce8c13f93e912e178e45328c78f5ce789614b68ec4aa6497b410fc4df31e50294e510813d6a6d00fc3a808c97fb254c811e3e007b08ce9a6502d567552ff01902f6d08410df18be80586183946780cb1dcc21aad9081da4f1824774eb135d544e88779150137490c7238c51cd8fc41cd556b0e344bbad067a5d531714b4d91ec15db142b86f810c79d52abc8b0a5be8419c1fba029fb66b83fa9cf8c70c3227e15f1ee669f871802cd079f41b079c73400402ab8fb80eaab0463c0185ceb43b41991b61fb0b42db5fa355b5f5d7166c1cc1a78f2f2f4b4134f73007b6adf657048b470f510f8298ef0fc31c62787b128e9c4b24b88a866a2e000d8f1084959107d1b8accc33ee016c45f0367800365aff07fa6758816c5eb2be2233577bc1167e27dfe7cf8970ba576dd94fa17c1a5c48e825350bed1e18cc4881c10ce0982f901c7af88fa7bc422c9e726570eb931a7b749238d51c9a8d7e905dd0d5b2623ec17e66472ab1829356abedc74c15f44476a792e0b83c3186992b907e4d2ab6dcca9b7648fe2f64c31293f820e455f85deeb84562a18ec32ab3a0a536233959f78e0f564abe07113e49c9ee23e21936993fb434b84adc7d4d67dcad40f34f241d0eb1a4fd97abf0f84e4892d56417e20a49c1f733e46dd3332bebfe3875eba7b385d75f4fec7aea4854faeefda09df9d0f64be3d131fdf77782601cc6d4cbf1e89df211baa73157cd091d793a81bbbf5a658cb578bc84c9073016add5dae1c713de09145b7fe0214f929ca11abbb24dce50af18dcfae5591b643185d1b17b523b26d73a76ccb7ac13d53199e80823579d8e264196f7a84426b686da26e8b5d0bf8c090750f8082c55edc9519b0dc7f4c466919b4ad973cb62ee172484a3733cc44dc01ca60382052c8ec87631ce2f455ddf80825cd18f6157f8ce0d945c57559e10ad4c70dc718b5a07483b92319f2bc764f0b961b098db88ba7477565a1e5e54f33c02f91984ee4c0452f238e7d222324bbe542a295b689e0e9d27a09d53d840d362d97c2c02514bb5ba7471023ce7135dcc6d332c8c9a9a1c31b55db0254e1f4839458096c3a15e8080318c87e32881a9b2b729b6f8463c9e022d9298c6d61b8fd310de019dec9886d07742c039c7652f1aee2eb87df813f6f8d17089d46193e68e85966b381280e06104414bc9d408c5c57047ae877801e0743372e76fd9ffb766b22067fff981c4cbc3e9a1c0d57accca511585ce90916167deba91da9c2377ed690fce05e30664b25768db5801b8f5a8b137bfc66af87cc44a20bd59f927b0f634730267230b8e9cc8f0fb1b189e0d73e24f52ea233246732bf105492d2e2f5b5a222bb12725d123170061db7b6e36295336b49ff8ba57e621fc648e1f118724270c3bf871ed83f37a0e6cc22d0dcf769100746745a101b5ea79852fe0baeb2a5a30c849403f732d7457329bf2a136f2bc739b7a11bfb32530f582e1dcba272f7af230b51864adb7e5fe1b217fee815f54f4be605e933ce08011e2a9918ab4222dc2c555be5d54f06f1a27a383c54957a6f4631c29d52753d78ef86da17a4f42f5a5077fee85f501e1d7405f818b3bd740bbee1385fad824b2bb02a7c46047ae826b331ee13e05f8300e61f5dcb69df1cf74d808401c2a3e0d730917e07420e615ad42fc3d6387f08736f967abfba90c7a5186f98795b15c1558012fd7034708f54f0084b3f498406b52c687242e856c90f585c955f49dc09c3c21d9555f15940afb985620a29b2e449f56bfd6a80afccff28110fa3dcf1cea372c388d1a03863318398efd9d39821c3d7211dd67eb093986dd71509ae420088d03667420dc85a6c3d62a21f907b17baf86cc5130cfc3d7ba0c94251a4ce601cab58543fbac8da80090ff546f83c8061db5a8222f4c54d6eb2ac5e49e1f7fe24a07df23b1b47fe24d30cc8b3ed08ece303c2034cf4e5c51e10ea86e5454106bd977060699a0442efbdb282d84c945578296a9481ff5aa9d521f0ae3b28ef2a31d272ea44f7216621c941c95572010ece7914b9dea9d8f4d3592947c2614e18545fe3e90b7c40a1509b562619cc8b00e47b87c16a4d408239d9483678cb79d48b88509d3c224126ed2ad5921a6db66d82d4d65b9c97506abd8db8b328fd39b039b6c3c616ad49c6fe427f11e02475e7a82181da3123a666c8dbf77516661b687d867d8c7411e0330fd11055b4fa278f95e60ea6417ef9fb964c85c3634a224f4531a0a455a59eaacd3fc23f9887feaae8ddf4adbe9088c905aabf170c5339ef1636bbcf6e1c68693fecf1356d743432e8da0135685aeab8185b4910d48413c603ed581f6117b53da9a1dad168b48b65d6b0a44d27d9959f16a357c9a300b04cd6c0165aeb884c352f2fb27c706423dd57fa75ec3a637c9dacf206afe1a5da71a124bfd1aa7b378b105ccb792faa85ba744fd53c43cde9f0010c4a559fd152b2ec0e816029c86d0020bda715212f9031feb9c3e4a201155af8e4dc27860130089786a0ccd909474f8171639d2232e10d1c3f00a815d3e1b4a096b29d8eed74b86494e75a6ff85606709cf95ecb08ec1c2f619f0db576c8f3e3b25efb7410a6907e273fa62bf6d8aabbd7deae3b2fffee18fb6940d0d3fa93e04d684e3210eac286d35520eef47997732e1be70b5591a613e903e4a9797477bd04566deb1c35aaba62fa983759e7ac5b54c984c5e5d288245b0353aaf60ab634576c98f77edaae382370b64bf079b5cb9e2da6f88239f4edc60ca26405c29d9b594c7d249bfab7cc3b53fa183f1920238718302b0a3477e6c12d9153f6b6e6c317a010603a3d5d1991040ac1832b2d1908e0ad74032b38860d76db59fb8218634171b190ba35c8eaa5b10b519520b93121a9879e21a820772abbf20bb3803f8a3fe36c4e24dc5b81883c189606702e6fb372890e1a2a4ab115c970b441c68b9975cf9e0f50c12d3865f21254c1a0a2cd7ebefa955232c1112cf829ec88f803cc955d0dced5a9066e7bb872295eb0918fcf37c7a9ce7ddc04591b57726dbea0ae8a3032acc095798166c300036d3137708f2e839ea73d7da8a6d8dff768b6db82c2215a49d7e01699ba0b63bf8852715293ee8d67130b64b636d8d802f2a09b4df27dacdb4074c0ba15f72f130cca65e23158f7a37d356f6b59ebde652190ad334f7544843e2b17c89839383f3aca3477fb9e5f6cfb497df4056a42b7f40d3b443c6dfd2e98da940cf3bdb3ce2c4a34938b1301f741bfbd29adcbf740386e8250bc72dbfc2ccc1176ba6b27b929a70d1f0691107e44fbb436497deb6e36a46e35d434e63e866d3c57979916165e99d515c299de0471673c5b135cd89117a57dc34707b4e310875903a5461eeb7e9ff3bdb00029b017c62c84cb3cab469b2eb8c35b263adae9add798df1c7e0eefaca41c04003669c5c71447d80ae78269ba13405f3126181131538e6024097cace3a1480b8f2e9c9926b5b82a594655d7ecfaf4cd3c2c7b2f2bffce09b226c9a77ff849ed257edc3a6f0c2c167c8454cd5afe2d906515ff2cf10784b0faa8260a9e7cd01b964842a189786620c15a58bd5f9c1fe77e02859a90ad44a773bff8a333d80ba1cffcc1fd07f058e127955ed2fb04b4e76f11ba7e7a1fc2146889105601aa7ffff37fb5ffcb87ff77ffcdf802f796f08584bef5641b7d471d68050a2558d4c05582905974b97abce6ff94d8c763520732c148a5d5a22a896a91dae996cb114502ed7e3c3671d3094e267c80d32b6aadb68284bcdea37e38fe02ef559155c25379841532947e1fedcfe102d4b9296f9f57fc52fc91fc1a334f6556094266716674abe91ecb5705324544ae1169ac8da8c1f0aff42b104b40af8e9ffec2fe85fe06ff02db9b721784a13af8261a9e86630962ad626fe175ea5a9544151ba364a3feb7ffe497f72bff01fe43788c980617b49a04406196262325c20e9d85b2598960028c9da4e7f92962512ac82a9a460c3ff51a60845b20a1c4b952a609d0a8c4b466d03368989ddffa6bffd3f9a48b6b47f8fe05d32d95b7f8373a96ca6d0283d4e15c44adda80879dad0bdc2b224205b08b04472e6e6e652f124d538915cd355e95ff5dffe97bf21ff0666e95e9df831ff44fd179ea5f255f96dfcd3f42328958ad6f8979a29c1a935e23f215592ac2131f5b9446f375fff066c693a0ca2ade452d8fe709910f8c6be350e3de8767f59da5069f52f6c94ba6ad7effeeffedc9ff707fe6fd0977a6a8b1f9d3f02bfc4aaedff16f8961cabfbd79869620e950140495ee5bca906e9a555a6c2a5cddae5aeb72df878f8017e49ab0c0159b2585bfd0deed298da84ae521faac05cca5bfed7992e2ed4afe0d7fa95ff44fd080ea5db3afd71d3f465463f02bc84b6ce3fe61fd57ffc8f68329082291c4b2956f05b499b02844fa26c03e2c596180377c9519de92fc04a935a088c12ce8afd75fceb7e74ffb99ffd3fff8f301948b1152c94bc5205a592abd5a63b33feb969607b91f8e67c6709d025f595f67f034f895bdbf617bca58fedf7371096c6c310402506548150f27a33184a156b937e049752a50af2af81bb44145550965cacf6bffec6bff447fcc7fd079a12c4580f7f0cd4a53336ff8bbfd8bf8049231163070ca57a45c8d44c880a88cbd2123211d1002f4963cc81bd52fa86c02f81d4067f88e692b135ee87dadfc0ac242a1e981c50a706fdd43f27eb8aaf401ae5d284b5799381c69ad5a44f79a5a6c43dd83f7b5dcad9b1317acf8064f27cdacc7a033557ce7e7fd007d63a4a7bccbc205a7181c7ad3ffa3e27c1db5b212e0fcdffb2a4f8dcb3d0516ef1782f410a98cab37866cdd0e66414c2286025669a6c4627a9308ac9053c8b0bdb44b4eb145be66506fb30174d0bd099c11f741342921a0233843499d9b8f729273c86afecbc1c111e864d1c9658ae0ae492172a210cf15a80814629ad39a832e092758af3bb1c4ab1f33b7ff54d0e0893c4d804bdf9a5fab5246c01f863fe4f3828818b437fc1a4646cc3bffe974da38a8d7e04b5d2cc0ac96490c461aa8112898cba42ef846b3ba67011d9eb9d7ccd41f1e9940e4e2c3c05a34dea701e556fd06c8b99899f14f18a9e3fa89a6b7de77afbd31c55b03c24fb4bc2d9aab9baa5e8704699591a1d91890bad709c9373d6c5f546a0fa860f540a67c4fef9e1ed147a8cbb427b75e8a09e0ad98ec5fd1506bf5680b3381c400ae38e73d41414d1cef5d1f70ee1510f908b71cf54d9486fb25dcb27cb8e5d52fbd5574a29d6aec890a7b3339d913a34f320bc3d2c3800dafecf19858cf1ea249143af596a7e8604f619a234b3b05e606210cf9064b928cfd259c1649d0d347bb406761802f4ec6d9fcf2048f566429dcfe0d286f613e84c7448f3536895e64f0353413d663b0cfede59067f350c8556e3f4c7941f181a6b598cd424e86e9e2bcda12389934188b28663f1dc09b7e14144d13fe10e0425c9fcd58070ddb0121caa5e124f0dfa89c9642bcbe95781e96ec27fd84df06d803912ac8f108759281f040483cb7e1c6e9a0c4c2c7238e5a592de74606bad4d0866b304a0c495fc44a1126572b7dd4f9c7c1f8eba836bc64c43b58cf9d0b1069e518c9f6afb02016521669eb697f00e1baba2ed0e8ca4ecc6ea20ce420bf52f3a015053c35c1a93234e16363bc4281f19e1e643e428f3073e24e956f1a037b4bc925d6560f68b9e62974eb4c82c2eb6129e2f1950eac5db4bdbe495c90a7127941b902702a8d3233c62fd05fa949ea8a212d766d00f6c8c5d0a80b2a7634e3cbe7c2a5699b369b6bca47cb981c1ea109bc74c373517f9cc96501676749727a9c803cc2b205df12fd4eb9c4571b6044da46f8f41c6a87af54ff09e92d40dd58bdf05451510899191ffb08bdcabe69c037098cb8029330bf01fba289107ec399d16bff93df91a845c07bdee4afea1f4a4c6824d45e4dac4ee6d8f9c861a6b58c285a65df6a6a60ab831fb038c159261e1aed74cacdd8d11ba0aa525fefec23e5c183b4d5da5d58616d780a57a07a4eb7c3d68b77382f20534441dfba1a053a48e8188ef0f014bb93267d99a6db85f037ac20b00e606556d3ddc91cb46940202be7ee95aa1074d5d82eefae772b3487a53ee66fea994cbdca159708448f785733e21e556c9af3873f12b7ef0cb5c395e6687f0c39b9c7c9018540253632c9316aa5195d8f6ea1404859cfa4c3a54de77c3ae43c50589f8e1a75b8d0b0a2a76166ef5b00e193b6206c678ecc96f4d50bc934cd6e3c9c5a1dd6608b02b70c5b7a74d3560b898964a61c07dec87afc901acc5d716821f5c1c4bdb7daf045eeb53fea647966f9e34d03677294c89cb66e43da655d9ec201c91de31244ab0b6f80ad5f0a628ab5c5892528d1dadc2e93eb6a05c60f092174806a99f79ea9e6a7d25f909da0a5e09f3d056866fade13210712d0a3791862476f1e31069c82e00334ab2c4ae8aaecaef805e1ac09c047d55ace993acff78802942343f1f4a371af17adb1fa8111736b140429635a209e36725f6545154f14adece348b6d89f324fa18c9ff6194bc5fd883adb90ed16eddc51e2d6f9d9e2d03b1ede59de1645740630222d60bf07697444251f033fcda4bed65e9eb12a86c55b53ba05e8972f9cd0a4748ec41c9b830b6032ce9d80618e064683f40214f8eb53b99c4e0085823205a3d54885d317e6084e4fe0bdf01e2ecc8119fb92658d415410dcc4d86df79db6c7952382c0483a3b7a0a8e3383cd2dcb67d49ced2ed2669958c8dd392560a843de143ee3858599a2ea489ab502f1cb9662a8a8f8242d687745c9a79ee08d634aa188ee6ba5db4c7ba0a54cbff2a7dd3aa98d9d5c4162c6e042b7185108de657f5b78687b24bd5e7f6bdbc04d3cd1b929520b0554602b0c4f30de2f3c6f9a3c1f0ced814ad2a0f0b1a4e59afcd8af306121b876e95107310a3923521539100295424b45bca4497fc03ddeda861f4237e06c054ae5df8e7bbfebf09b298e0333bbc3a21b58d017051d85b94c5d1ed2317b7db447c24c0756ddb07e52956b9566e7968ee5456e5d0d678d08f855c4d1d9de77cfc2ddf582f852b1d533fed4694cc17eca1995859c0cae1d9ebaf429263fa87b33a4111538227daab0eb709fd632f11c5ef5a918a6ca130f5fe5b591dcc0d5e6ea9847f447cabcfd51181d0ff176835c65a22f93508d0a1f77d8b932c0df3fe84b5d0a1dce5f00aea9f1dfa0af58c3a0d10018fd130becf90ce90ba6ef6b8dc9a239001d25aeb8f988b9cff8df943fac33e8b2dafad85846eb5e0b5c9f4593e1f92e91de80b300c3847c24939a72e84b78b9e0b893fc601436503b54764f26588fb4b4016fcb92e4028db45045c049b54080cba3d8f4126f224eeeddff971eaadd34f7c6f492b4084ad657044193a244d53f0e3a377ea9bb3333e6afb6c8f1031a57d4c080ceeb2d1d6e2fa892b0120a2ebdd1a4a445c065dee56c1254192b7fc2a90fcd21e87441e5618f1d4fbba2768ccbd3ed6bb26ad0cd8287be3061d106d795665e6e31a1978a3e09c8a5e6e5f97e5248339c249d6aedfef5e8eb7ce966cb854afef3aeb52d5db7f61292e49dcb6da7abebb136a4adc7d1d2a073a0a60caaca1b2c46d77819a4a218cc383327e9a941be74b1549f2e4325566becd97baa79f6373a5e97fc92f9ce803be585498543a20ad91261d575ce07f610fb8fc6a1d6b1f420707185293c1c1fbcab3d986915c17dba2c5c2c1fa40a6c300430c081103744880dae1ac16db9b2b0831b4a834d22ae1278cca28b18773a984ebd44707294c26142934f79e848fcf14b7e87a86edc62aaa8ab3dcd9f2776c3d70cd7f2efee38abc58836bb199a24f94b627190c174a3d33e6c8e64ff00d5f453ee740a16dd6958f1eaf10ccc60bc51a72c54657b27d3aa5ab1e4fcc4404034520ad16264885c7426d07245479eae6734f3fdb58a9d526f87f0b68b746c82a83c5126363a374b3402cdb262edf82df5142383e191d2e5bd552dc4916557de42453e98019984452a1a9a403316bfc6c668b5ddb1d491406bd5251cd7749ac4d73f3446cd6c7c52060d506ed47dc3f8cbd601915ae71149d8151813f4c5881e31863d1c1221ad6c61d834864e67d8204376de9f58d05e18aa0a83ae003cb1e98aa2e269147701c1e83474d82f44f8086980f3e1bc14a2c7a303d9883969b63a1cb816bb4031275ede16ca664d324c33334ff3bc81ccb9c52f4862ec18209754235abd65fc4876ef2b7660c630457f14146da6b380ff1ac3a3856c2f1ff12d8cc175229a7e5d3ea65b70e7de1beb444542181096c020c1239428e249b0413a02189817249a5312f44b8ee69fdcb20c051e069e88ccf7973ab96abf66bebb9b1c434027d624a06bbf203d0b3777a574dfa0b73273095b8f6f9986118703efe4b573d5a80ff48cf46b1427b9bd1f23e4aa4cf9bab38d16a8111800d24ac7edac924b8f4b0c31c88b3648888060839f9858632ac75ee611523d1bf646a0c9240e8c8712b3883550bdc7ab2753f369d8469c117e711538244c3310068dc10f9a6fae166520ed9409e2d01e41fd6736541cb9fa89fb09cef7a20b56f21bf0c4abb740107b215d474f73303b7be652bb02677c587018d450e3f4aa40d40cb0c4a82467257a9c09791d0d302ff7809ea2492ef6f31e8257686510778340ae208e0b16daa12632507fdb07b3dada891aff44d43299ee5d4d03d9201781b90cf060310c833ccc702d1052cdf903e11ab0ecdf851321224d949c8f140578e37f8d51e39dd92b939d03892243a1bb8591506787b94ec682855967000cad0d952dde9d8021970c28a12db8ebe683edef9da10f9decb36ddd8f62a0a3b026fb45da4916759cfaa414980154be6a84772f41374deedf948222f979447d7a3c2bcab6827fc081c184f781f8c6b9a9b93d34044f6c83950823aaf55ab78212d13163f5a2ed7400dcfda7db7699a0f1e728682c9ac200768b4d6a4774eaeb97fe535bcea3d5490756c4ce6c4c00e9283c876a7b320f5400e6c00a95f324bc35ec06db65faf4f13c5fd42d0e37a281aa7e7e041e4ae13d3d97fda10ed17e26c7549e944191a8aca1cc714d922264b9e6f3ed83ed74f008a5f0c962cc9d13d0ac40bdec4f15ace0c0bca71ed2b84728ba4c7c620548436267e8921b0b086d76fc56a68f4da9896c4047e6a62f6d0310db81f2c51a16f1bb4b1c0de1450ff12a667db3f912886d2352665f0485fbc2c78c24d403e033ab1a77ed42eace7a1143bdeaaee8304757ee865669922df69a344bcda3e10a5c2f0f83f13909ff82f9dd546b5acbc027d8118cdb96dc52f4d1918aec806e914c163f3e1b6c2b5e0d75afddd0183a0322dca3da50bc3fabf8319663255e57b3d3a86fcfc1ac6c03f4b4aba8f8300219aabb86cee2820170f327293ad01886e150506740e92a207388edffbb7a0712ec7018946ecd6b2b83458e1e65929a0886c37204b5c04703b804e98eea31bc32bd904472d54a6c5bf18210c52477be2a210ba657c2be744bd14caf6813b7707a03fa1a2e3ba27abce9b4503820c64301b9c87415a6709190017681d06f8bf4c906e864b27aeee416a59b43789e84f1cc49f7cc474e201d61da29a0d38132de04732a2d7c7bcdcfc8975ca2826cf18a5e8cabe2bfe5b1af31e7dd5b2bb216f67eccde786518a66b8e6a482326c40dee2b207b147fb74713a72600e7bbc3b86ad07ced301c8ce2e94f2a8e6f3b893581fc89c6dccf33eb2eb9ff642dec8ed5698df8ceded69f0584a75fa3677ee5652024e320bcdc4db24350b21030ad6dc74da9dfd28d660a83480ccbe68d6c020b2d238c65a89cf52742e0f0a08e2543d02c039b4ecc4d487b125425be220e27e958bcc8a2874d5c0d7dff41ad1f14c4afc624571d20266214cfd052e9588bb1e411038b7da250ac55f4e5aed0af1a082be7de357b99dd2ce85ae7a776d93d84c521206919529934cc052777b83ee7f4f7b657777ef3db2e180f357f181e8c0402d05f21caf24f31e71be98fbdc9f1b8cbfe6178e5b9e8fff62f985dd1706ba73c8f9ccc2213c873c5edcecc6b01b037961ecc2300ba03b873e1e9c63d78d45f8e720f007771e2f2c84dbb7cce7f2f993fd8fc303297afeb2eec7c0b55027bf7010f6e1675d1e07c3bc708431fc2607e5f7bf5f867b3d8c833091502707671e73209087410fc7c010068cd6f5a1eccac03618ee852fdcb97ffffbbf8f63391b94c31706064378f66561be6c1117ade37edb7f6132390cf641359f16f7798cc57d1edef541b545371679807df0b7ed641ef7db76f397611f94151de0f3f1319f8ff72fd807e5302cd479de8e03491cb41c0459fb3d8ed7361feb5a5a223e5a13f082c6284d55e3d026ad45209fe59f3b2312830884ce1d3275eed097ce1ddad25a7b10002dcc90ac7386469d2b044a6b7d8ed02ab4266084560bf9d1b9425e74aed09bce15da3a4788059d23e448e708cdd1394258748e504fe708fdd0394230744e03503aa741071a8e7f9138b1177665d7cd25f301c46ac50680b4fe9a600330add57c3e3e16f4ff05b206b6db9df928534b5b83ecd61c61693ed6d4763b30b698f7c6de60f8725f38c3efd85a3094b9dbdd97cd95e1579ee186ba50e69fe5be50e66f61bd3976d9f0e5e11b7acef6e2e01c6cbbde609ffc7f1bc781213c876f99388ee41b4ff36570dfbf65bc6bd0214c8dd66d199418fcc1a08b0ec13038128261b015829910820dcaa3750836a88a0ec106a1691d820d221182090a41eb10ec4d08b613e413948660827408768118adf505bc102c67c10a16fc09c18ce89c05432c08b5d621582013b40ec13e0864279099d6baf8b76d909d83ecfff1980343f8057b61570e025f37f45cac82371580015482d6fabf652309a4c7ff3866b87fa6637dbf99ecf385c57d1e61656ff099fc7d40375e83d7e637088f55c5fdc9a2b5e66115f0cf5a88e667d41f135aff826f58a3c62ff8fa81b3aae127d5646b9f17d8b13e7f661856b332fb44d17dc8b4f605bbef2c03005d767fa628b023bb3f19149ca1e084fc83b2ec8633078633e2a00fb9cf17d424299302b2cc674eb6c647448e4f95d67a8f05fc7b285aebdc9eadd95ef775c3eeb2c166ab8fe1fe65ac5931f1a0a12da6718881262606e8d0c0135d14128f14f07187090739a8708042256a0d6b038b017accf861458960519532ad8500e61a914874e066c66134d1e95db1258d15395a0b04d94de0954212002f0cf979c1a88760a38570096924ee21e8ad908c12085ce5beeab8e89a80521d23ed560223300252408b1318e090f4a060d18c00283005ae1ac8608b0f2b84b7cf0c4f9bcc12f5d1a91e52720430aa082835193c164da0b0207407146f938b2b25348dba44a2930339dc080228e1c20938af265c809c74d2c20391a900c4a001a4f0700584b536436404d9a17c50c82466064d84ab2a6dd4f058c8729403c31c274bbea91d23407a307cb08183a94e24d614d924568746033cb80604f948aba2db1397250949054d3eb54901079f371e89de381e1128a8711845c2c6ae9365511120d906b5b12f4027ce28ad558ae00330696a2091a9d46197c6039bd7577a00c31f53094ceac2ae714324538888050b1a503c4824e3cf056d133e7c18d0da7062a8f2f2060d200c6b2856ac60135998fa657c88168a38d9a822c48137674220715cc992c944154e8666f74818470f181d4a521093030e01129c01836528819363497bc1c59335a60c11d0652c22b230d4f1a2637eff0f08fbbedbf7679e34adf5240dfba0ec84cdaf8da62a411820219199952156bca6344a130106405a21f8090f4a65114ac0fc20eeb75539a12299c4c1bdb90567395e0284df5f9b12b7c75684c810214180aca9a5fd403beb61c663878e1c3856b231b13019ba2faf5331607471bd20c0c5560b162b4e852a52a0386142ab048911224e9da23a22c1227806c6c55dfe6eeeecfedc1808060606b6e563f84d96395ad80c9ff5668ef60bca61a929ab69ad43f05c2c35bfccc53c17bb334be233276bebb0fc8171f37c243ef21e698fb0478e1c617124c3005a430b0da2d67aa77375dec8e4d689bafdf5c2b292ddf7f1979368ad95e81c9d4a757668ff9784336438fe4cf8f785f2ecb261ebf66439d6cd7deb4e1df9d5bf87fb17ec83329d9e96cb0f3c60c4a883458a38d05a274a0091d61a83d687f40f8e7168ad3768c090d63e1fffa9c1f05afcdb362856838a0244541d502d9213468e752e0639635aff706ba0afba56ecd065bacbff1ec338d680f8ef5fb7cb3fadcca7a989f5f0a90989670efc69decf7f9661b3590e92f9ff06fb77855347ebf9fd6e1180a0b5d6fd82ef0f79114cd15a97adf97c7c2c8236ad232b0b7f6f4df7c232877b3de6f177837571af8f075fd70c8ef7d0b9b3fc63b8f5eb8affc26d9c87adefcb305908f65fd717fee07986c542d70de3e0fcd9e2f84f0ec2716a305918c34af82fe42110066b93dd387cdfe2c29987b01cc23efe6eff0dd611eafcff71b07ff97579e1cbbb171c7f26218c389f8fff18465b16aef091b61609cfc274ac8569eb5ad89a96235cc5c23535203d42ddfb8170b8c667790ec2610bbfdcebcbe1fbd5e2deb05027cfee1b7479bc202f2ee6f1fcc2ddbb32db0bfb388f17ca5f6e5bf87b1eff1fdc43d9150b753c1e2cfb78102612f278bcd04918f29993855ea1178d9fdb5303f21e9f8dd708c33034200cc3ef73813fb8c32f8fc70b435d0bf3646c531db70c9f2f08ecf53fc7cd6ecc81327e70d7b1c1deecb2c990012603e7b9c70bff570b7541d88db7e1ebf61f03bf6fe1173e93d53852d3456bad7bf12204f3eece4b6e7f1caa5992e68534711a56eb100c9e355587064669ad759f7d7bf38a2eae36d849d8c50506a6d3855e06846108f7deb8e6f8cbaf0b0656a575c68406c8b4d6bd6eb89321a381e08d83ec4edef97c7c37bfdfed8c9032219cf171a64c6b6d464cce4c93288d50c8e18882b70c5cd05a6378c3260234107e0aa15a6baa02c218f101460b9a3f5a6b2a2e9d5474c500d088a3b5ae325b0bc0c4e50517515a6babac2f8b3c9159e00f23ad750606c68ca2fa00ccd40dad75d5a550083479810c09a8d61a0033bc00caf40e29a1f4f56546879e83401e10064ed0ff8567ef7ae1d831800003bd187ed9e6377cfb6fe815feb84c254db6fe08756e9bccd7e697bf60b00fca4dcaac91e1e332e419f641b599fb80bfbf7681055aeb403d74328f2787330f43ffba6d08cf21129eacc633e2baf17e0c8fe9d4c299f778baefe15ff8dd5efe43ddda9b5d6018dadafb7d335cd00586fde092df5bdbe9782d1cfe1cba1ffcc125f16fe33fb73c9d777d3c48a8fbbe30eee5aefc737f31dcc61f28f31e0c9479bc597e5dcec643f9bff1770be1f08df9f7c130307c61d7c3bd196e8e7bb3fbf6e7b70c2d2c9475b1ecc6bc2d43c203038b3fcf881bffe012790e79bcdeef3a5312ed6dcc15facf16ff5ff87b72d8eb3313f707cf72b0ff98c72e07865d619c0f0672c96e0cfcc1bd8bab854399c70bfdeff5a01cb66e0c875e3cef865eaffb35fbdc201bfb087565bdf9f7e6b62cd4f1bccb0bbdfbfd1bf7721ef49ae5e0eff5a1853a2f9987311cb6af3c07813c9fef7bff65ce7ead3dc2d27084bc2cd4b9401ef2c47863b20e9ec3b898bbf1997b1d8e5027bbf2ffc17d0ec22e304c880f9d0be4619bcce7b6854339eef3d0b940ae16eac842dd07c6c508df78acf3ffcb619ccfc7e1306ce158d75d1f1b04ae6661637fe6fd857b5a18f2d2760ef2421e7e7776e13e77dd42ff651808f7c3b8dff6bfec44e7ae98d1b92b5e74eeca139dbb22ebdc959fce5de13a770588ce5d29d3b92b3574eeca97d63a805c860e1ac4871158595412d0392a51748e4aac73545a9da32244e7a898e91c1553e7a870e91c15113a47c54ae7a678d2b92995ae387400a1250593ce49f940e7a46ca0735202e99c9407744eca023a27458bce4971d3392944744e4a013a2745d43afe71dc0c3ef3bfd7f94f9bdd9f6bf6d9b82cff5f0cc7f1f7053ddc436bfd466b8dc18dd69a0a02ad35551bad35151632adb50f18f4585e6021a1b59bbbf24278c5030cb4d632fcc657eee4aeb0d19ac70bbdefbbadecfefcff3ef8ccfb601dfebf40decf048e71cc5f96c3808c8dbdc1be2f16c7f95e1f2e77e32f10d9bf411c56805656d890a10a0a5a8e756caf1bcf729cef0d721f5076c91e70604d862a3a3450d943c5926c83461cfbfbfb834bbe1cf2d1070cce2187e35f84cfbe4f46e67db08eb897f780e135fec9d4d85e97fbf703e1d6b72ccd4cec8ad61a86ce4d79604a161d82b1b08f0dcf7ca8ed08c92933b41cebe20ff4fdde2efe3b369dff4b425d5b0cbf85f0fcc2b292109ec3f8cb5a6b35693490ff1e98d51c45d467e2ca330f2ef9e167e5d07fad75036834ec836a695438a20c817d506bbd0019adb506fd99b8f2ae85e118d8fe78eeb1b90a8cd1bb188ee30fcf3b607097b63310adb518306afdbbeddaf87f0303dbf9df7f10fffd1b84c5daf817a2b526a1b5e66aa14768ddeadc133ebf6de7e31d0ea3bd3c39b52e755bb7bf7c7c07c79fc9ee855fb8f9e784a7f39e87fe87be3027a9930e2fff8a7f9346b92688b4d6211cff35a1a0c999d0461882ad50b496d16484d640d6c0c0e41776e5f7cf701fac27731d6060dd8dfffbd980725b877d6271b3ebe3730cf7805b6bebbf37a6b5aeaaa252d35a43915df9bf3f47a89f68cd3231d3baf6c761c87f41368cf37d37577e7ffbc9c6da64bea6f6d3789cf9efe1b1a05a1c042f9030f8ac4de66d325ff321f770675fb087ff387c3e3e06c7bfc80b9f8d91bd7087d7cae035d9ff7fd63f50edfff859ff40b5b70c0ed03aa673703f9dcc7b6e211f5bf3f9f8189f3959972c7f9f8f39db8fe3cff5f9ccc9421ec6f077e399c7935daf637bddaef1b7719f58d7af37bb31cce6e35c616f4dc7d3c183756e87659987b2ace3f1d87eccc6c6c3df5b434b4d35359d1bbe610bff36fe8373db3908b33c7c83df36d90d435dcc7dfdfb306e8e3f108e81210b1ca929cb2c52534d2df47fe62f9b2b7f1f44f7f5babc9743084ba693f9cc7d4fe63afc7f83dffb60ae5fd823d4c1f19f2ef44199b321984c40d89bddb71f9fe5f0bff0ffc1720b7fb18533fcc2c162a1ee3371e5afd70d3f5bcc7d21e74c5c390c02a3b5af1b7ed97d0b5f980de3eff6dff0c770fbe3df9bdf6028bb210f5b37e4fd1facc60b756465dde5610cbf6e186c7b32d711eae459867de1677b331b9b1bcfe16767f80de117eebff0fcc2fdfdbd39e4f1fc9783a1978d351a318ca3adc33e312e5ef88171b3fce33fffde6f7bf8ba3fd97d79afff5fc8f6badf8de750fe6c3c86e7d0d7bfe61e9beb0b651fcadd9d65d7e70ba301cffdbb85bc2b8c63bffc63e08fb9c290b3bd2e10c87b843a1e4f2dd4f92fcbbfc331702483e107c6cd3cde1312eac230e640af50e715ffdefcd9e0399c83c0d0e7e35dafcb15ea66f80d5b37ece20abfece338421defc7f30bf7106edf70e6ff2d18af85baaed90de1597e01e43f2e39d4cdb2eb0b7936d668c83c99fb78bc189ed5c2005ab789ff4bc2193d99f7be1c3ca3327d2a7c58be5c5ba9b64f5aeb0074aead84b64a6d87da0c15d24038ec72c11582c908bb5c84a1978b100c08875e2e42ae3004f392432446be877bdb98360342b02eae998740389edfbf8be7d7056b43734a0429914017ba72e8f5baedf7614fd7c3f0ed9f856dc0a4f9b403fb5252448731dcc26f68e58c25bf6b93f9bc83fdcee773db76bbefe1be9bbf3c7f598e756c6f93d5a71d7e61404163a883a7aace0ebf6ad4f86cfe5bd139360d71f1fc7e1940fe6b831876f3fbdd601f9465200dd807d5641ba1971a8d18c6e163b9cfc7c7c0c036ec5eb8efe4ef77eff7deeded5c20df016530b0d6f7e51ea6d3807b6f2e077afddf86e1cf73a03bf35a38f4e205e7ce7961d8ffedcec7dd39febfddedfce7ffb7bbf7fbfe6f6dd0f81f47d9ff6dd0d75f17e8ceff07f700035b7b835fde894be2573818ffe21df8f0fcd6391ec92bb133b413b32b20b783a1d3f1e62fefe2eff32fef857ff7c2bcb08dcf606040bc4bc69383bc7f962b0cfefc1b873bfcea927d9c377fb5b5d7078f2be4756d2e1fbcf06b2c6cd14227c341de83fb6187fb317fe3e1170b75f2fcd9c2d9dded42d90dc3dd7fe133243c0fbbf147fb057feeebf60003e3198963c397f7e630fe9f05fef82c7f3ebf7865def36ac31f9685baf803c3df5fb80d767bddf0b3bbffe5dd0f8e5b784e5229091f2df3b71c3a97a4974444b249d715c3ae1bef7d56767f39241868fd06911880840b18377bffba41b698872fdcbf0ccfb31cbbed1151476419f68573100e5fb6d7f5225be7202bc3bd235974a9ce1dd1a1ebf2f07c8484d68dd2e58cf0d15ad725ffdf863a36dde70afd37b280ceff37de9b8df0b4d619e10a7965bcf0f7d68a3c0afd8fe159fef1ec72853f3806ca41b94814add9ef734578fe1711350fe1f97543ffe7d7fd1179a4b5eb7fdc432f1e2f8c63c3b7352260b4d6f0ecbf1bfce30abd9ad6fa86ce11d1a1f373e7dffffebab2877f4434681f7fe3b197dd78967f1cc6cdee90425a760bc386a8d16243805ae7dfdf63737b210ccff20f32842be8e11086e7708886e3cf44081fad753c1bde07eba0d1fa985feec63c88f7c13d2159b4d6fa2d9cdfff8be34019428668ade3e160f27fdc0ae1fafa31302ee66e77dbff7eb12027f06438de8317fa2ffbffe3ef19d9fcfedf0bbb36fe8fdbdddb7f67f97b30b0b55c100f40fa0081cb0191b1f6a907fd8fdf38c8f2bcbe8f75f9ebb6f1dfffc9f28fbde7e1ee8d83ec2ee421ccf6ba6daeef0512ca6ef8c64116187ce3a0d81aa2b5596bade3bf1e07612f99c3ad6b29d438508b5303a296aa9dd075bd71b0d7c3d6e7b2ae2cc3c037d8e77b5d37de0b67f89dbf2ffc42b84271fb7cfcb7e17773f91bdc7be3855fba1e21d8970e48daa6b43f5ab7e6c347daef31dc86716cc881a4bda599691d6f86e19817ca6e0b8731f705d97efc42e77ec0f9b1f6830a2d4e0e8d4a0e4d0d6d4c6bfd43e77ca4e0c38c8fb1b313c274ee4cce0fc6335708fcbf2dfcf3cb631e64eb75b7f7167af1ef0b671ecec0df7b731717efa197cc7bb27f83adf705e19e1c86bcf0ffd7fdc57ffe9e7f1f0cb3615808f6a59bdfef02ddfec178b3fc3d2ffcd2e9747dfea197ec25c73feee5712c07fe18f8df5f1884e3bcdcb690c36b61fcd9d0ab8bf783f1cc0b6537e47d37d7c777c0ffdb785ca11cc3a157e8e57f5b877d61dccb67ceff4f27efbe8fe5bb109e43398cddf9fde1efcd6737ce32f4f0d463019d877bf3ffb5f0fde69e4e37f7603ccb21d8fcf2b73008c785ffe7ce2e54b58d2331a2a6b55e75ae87969927333866484c9d33e3d2ba18e6f13eebc5439987df873eeeceef7f837d9f0d65577e117f37f7f71ff3fe8f71f0645e6fe6dfef76f16f79f0cb86ffc76de8dde012d717ff96c70a3cf4e8748e0716adf51ab6d0f178e17773431ecafeff3794e3f89b8fcf301c87712c1bc6f95e388ebf856cefcce59087af1bfefef2a02cbbbfe7c3d2e41796f98fe3efb6eefc7e5fd6d661b3f73381e35666e33e1c3c9ee156998dfbdaffe376cd9779777e83f08fc3edebca3d99f776f157a3b5ccdfc0783be0b4e66f613b7468dd93ddd76d77846cafff32efedd05a973dd25aeb1ff7b0323e40fee3b03230fafd4020ef85af8f325e9ccf176c65f9f71efcbef00dd35ab7d0b932516badc3742ceadc07fcf1f770973ddbcbe6febedcd917fc8561391d607cfc679f9bd3217e1d39f82c97e391d6bdb0cbf6ba71d0872e742ec71badb5fff3ecfed81c5572dcf86ff367c35e9ff9af853f8e4d9a2d0e0243b6d7fd6c0c0c7f7f7ffcdb1f8770fc997cbdb21bfeb8009dc3a146ebf8cc7df8675786bf5ef8fddeed6e502d8763d621e1b5c97c8d6724f4df15d3391c24d6495aeb8284311cf27c3ededd5537771ecf703f84e710878d1ff4f120719f4779064732d826f3b5b53699cfdfc3dd46dce711f7db72eb9ad65a17ceeeefaf5aabd6f178615cfcdd7e18c7867e4cd7167fbdf06fe33db4d63e748ecc4b8eecc60b5a6bdd8ff3fd99c7c1de7908bf85706e2bc7fd36362807c3109ec338df8fc32f248401036c5df3710603860ece6d7f0ce43e1f27a1736366726342c6a8726284b4d6693a271604a27362a1d6f9ff06fb7cdce70bd305fdef75398ce19017e70302b9ebc338fe0bf690f73d3c367fb5d7072f945fc021dbebfaf7033f99cecbbcc7f6f25e7f8371507ebf170f7d99ce77c5c5fc7559aef0b3f19ffb63205a2e6cd40f8e5b9fcfe511b6e8f76737fc3f580ecb920b7b91bbd1496b1d44e76e8cb961e3465a40a51986d342f9c76e70c98fd774bc1d2ffc329050f7e560e8bf7fffef07f71fffffe372f8bafcf7e3f0fff81bf60ff4017f1ceec7701b7acdbfe57218f7f2dfc6e799f7641e82e5382b3b4b0b13a28bffcc7dfebb3e367c790fd7421d8f17c273f8f2f0337163d886ce15b080d63a20ffbd507e31bffff7c2b1ebf6fe30cec7febe2c87b15f88fcd938306ce1b00060ae8013391b8d56f40f94f98ffbeb7ecdf01bf2d0ffd60ddf60ecf3b96fa11c9c796f0eff0c9fe978dfc39d2787712f0fd95e17f6e11c84e7afe68361b627731d60606b3d99f7becc7bc0757ebfdbb7b10684673e73323e73fffb0bbf722cc7ff17945fb8cf6f101e6b7f9a3cbfdfed6edb06651eafbfbff01918d89a8dad354a5df397c3389f1c845db71fff170e5b97cbfbe0f0f7d66c8081ad791f9cab8180d6baf8cb41fe0bc3df5b8b7335d0b4d6f93f730f0c86efff6c28f3de5aae06570dadf3c050e66fbfb796a361c1efdc070cdb60780e6739a824ec02039bbfcfc7d1b890c87c07fba06c240ca381800e7f6f8ebf7fbf1adc7b13b246a3f5010cb6c1b0d9db7f6df87c7c0c0ccc069bdbbef05828f3708d060d3858cce7e33d374305ad350f9d9bf14617d7bafd03e5109ec3f8cbc110f641b537cfff2cbfae97cfc77b0b876dfdf77233ccb46e86e1b4dc8c122b740e6c140c18baffc2feeb0d869f0d67f87dd760c0d0b9c1bf1f8771bef9fd2fffc51f0d8c841c5817ad8bfb6d6fffe56117efedbf3caed09785cd5f9edf1fc7c1aeb526750e0c057ba1b556d339f503ad75bb9decf6e630867f6f4dc7c3f7bbb96c21ffc1711bbe2e9b1bc321cfff97cd55e3f97cbcf350fe71bf2d847d500e7f6f0ee53ff3eeceefefcdc1b0f7c71c78167ff6177ce720700efa0003ebc95c07d92c7f36d7eb88bfe7bf678346ff41641b699046ff41908c2412710c67a4412370dfbfbeac87a57cb8cea5642e90effeccdddc18b8db8dba52277eb1c13ef4d38f8333ff6dc8736be385bf270743ff7d2c7f7ff859fff2d0e7c61f08f743d90dc3afb8dfa6b5aea1f50c3030b3b60efbc46cf80f04baf12c83fd64fe4683ca470eddf27f493823fc6c08cfe12ccfbe206fbf0fbf8f75e5f7abc9b14e9edd9feb03b311cf72e8d5dd99cff20b1a6ff0d8fff177ee1cf883c7c0c0faf7701fc9a01cc7d1fe6f77721c07bed9883f9a2ccbdcd777f107c2f15f5b877d6cece3333cefe0f8cf060d38fe13f2775fe6ac8d910cd280e33ff85dfb9952e0f62dfb9ec76c402069b3cccdca98b83efeba38d6381f1018f665de43fbfd598e83dbb76c7ebfdb1a7fb4ff83d5fe0b9fadbdc16365239dff6ef1977fdaff318e2ebb3f3030f9fd6df00c062607673eabfdfe721f17fb3f777e83714fe6bd37982b57e4ca0142301e2f9c79f8673ecba1ff333018c7f0189c18c371bcc6c5f0ccfdcb7bf878fc813e7cfc17c367b2cc7d6364bd3e73a0ec7a398ea3917774f099fc8990595a6b1f070303dbe9c88d0f6839d6f9b80c193d060283b28cffe3712b830dca327263015aa7d3bd98bfcf6be63f660b67f82cd4c9a18f735fb0fb780f7bb3fb16b2bdee1bec6b631e7e21605c7e793c837540f71badb59b9eb1436839d6f93fcbffddc57f76773f386e77fd036519fdbb89a324d05af3744e9cb5d6febf6cae2cf7668e26c7ba53b7b76efc82dbd7fdfa3ede651995f8db7f01a0e3648397570d1b35c22e38ef72cded37034b8b7dcced370bc330dc604008e7dddd67f37fd7d6793c8766273298d0193a845083461883ede95adf970188e155a886ce7945d15a7f0fbf6108f63ddcc3ef6379aff31fbfc1d0fff8cfef0fc17cfc8dff8c43eb00d268ada1e85c8c4f5a8e7540fe632343ebaa118462d0d0a1426b7d06a34a0c0da18dd0bbbfb6ee0697c810b5ae22d2f5498e757fe66cae1bd7004c9adf20dcc31abbb9fdbe9df7c101c898651fd6a8d163b90a0f91b5b2b130b4b0b1904608e7ddafb58ea2735c80b4875f19ffc7332b43fe3e1fdc6d84eff765a8e2dafa05dfe80301145a51a9c390492203401598421d4432c4901421aae4459707c659892a253c60d2f625040435a22ca980852c3642b874c40a05e990ab624a17391543cd985be3d25343d64846b380d6818845262f5a21a50112817de2f302016da44aa8a31d21349170e4882df2a58015155951bce4ccc8f1a92447ce199d2d222cc20407d1272d86b42e60633267871d11f6190d3203b4dc3c64b1597101100845781479a094d5183a183cba52d1006b8a4c070d1d544a9d790b6455c9c4df002d0536c0d70d0f64f0022d9081cceaa4c16b03318e84b07d20a23556c2c0c255160f846878c3929316cfa1f3a5041a044e784801cacd16a739687b9aec78f2859d22628904454c4091650b04a4c4190bc47accab15bcd469b1c303362abe2c9031870b8ca8137d9c600280cccaa26d295189ac01f20412e102194e4ad0a0902e9852630e9c25483e164bc9931f3ba40ca02a4d6d65297227050a25a72eeda47c8961ae50404d2418794a8dc88268020938e8c0028ca6387846f033a81496078d467de14063936c8296237914387d0a1515147902c76513e6bc11b3d6620b1889123f6b93d2f4e5d39613c6dca67ce002881c55722b1032412600842d679228ca194af1c883b3351944a72540e024b0c1254d6f48cc093e1afb32c6e2822b20631af6a8e8030290a84a6707530f5bd855d56f672646850244da3f9580a0c0c1cc99121d0b30c152620d9126e2794b01aeb3c4939a3817d28440c76615046960a44b17232e7ae8d84c392f535457af813a0e38d00485a3213e5294a8146488d88d529818a5f17a818b054fc884c00387c30cdbaef2068a10114e3462e5be24d056c7ca05e709a31c0202393222a605256dc5c486830a74fc00b1644c21069aacf10dca69539b9fc18d9805a1bc68d9910ba5b64321ac82c648458f81241cd66830c122c5294cda833c54fe0c43c43273232b0fa632557404484c4e83d65410cb4a7485a40a93451a8b130cfc287aeba28ab3c144210f44e12939bcf023c50394245384371160635e5f8e34ea5cbe1a19c9c2e00ea9ab4226a0029cc0f960c3e5cad2db222fbc13b216dd21175490004d1c1f8b02e519412542d0072ee0170485da81823c40db25870acb74ab42844c151210ba1cd9c067451036142c8480718142c3a28083141c0b4c211c469cc9d1617971c7440a69742c4824946860b509c2254dda0a228c48c0e5eccb133311050842d6437b4cf81466cfba038a18a769c4244a6d02ec3e5910dc2959738a880b2a408cc08402189e171a09264f9a4e1920c32c4517074820e9b48401198d787438348ac71c12c0297197fe14192190086c800e7c092004c401a60e4d813a5aa7309d593065cb8f920a1d40446812f2048132ef253674ccd2a91219e0c0518075c8cd9c14d0d2c29cd1b3400800807cf124355251811139282642d49c1c06b2ad1e4de2982d50d605c3cc1a06540935bf0c9638560802240573a3e01f5242441192ead19a43e86409830112e81256e65a395f48e80c2201854d671c4550438bd2aa5149c7984bbe34996eb490c21b6e662b84a91e1944400b92e947a2315e4ed0b244257708c888870cc7276c09592c44879e538b921627acb42216f4f480a1717193254816141d7ec22419902804e382ccc6841fd6d5a8660c0765321cf5b861a8cb478b2d4113554a1624bc31e34287030f6f9ea419f324c2eac8a609c21a34b83ce24081294c8a405d9dd9a186412632dd011034c41cead4e182135f6314ccd8c085510356befa5012818922115bce889131c3010f98502185f090600bc0d2e0646cc80b536234522032849a63e29f88c40260e40b0d953c17f0e1c0824e181a23b24ca04db69e5489a2e269d8c4e58d88f5a6839488629b199528d3ef4aa39000145cdc842a51c8c76e85260e585a12a48ca3116c4cc020849a02a0b0b0c1093d2fe00c0081b509900ba704a276a74184a8340d0a8590224cc607b72f73800b7400c565cf037302244a176d46c5a008a8b4a148232f2a6082208e70eaf4c56541a74c6f9d2b40e7003152b87005a723ac94e3c0251e3ec6b4d04146a250240dd494305a4c8046a3003853d4a0203cb09446e9f2e323830e2f9cf26306cf4e0505ee0a5a2c03b3d700dd1f452a85a9127c05186d2a7180cb980af6b4c0c3b440881e79641289a71268bc1dd2806b4a012267a32b94a03b5c4e30440aee409309243e9428f50105349b28590a419019950e7254300bb1814cc08813a06cb26306d86bf4499003b935a1f880d86ad52c6842e404380bcc4ca075e60f113e6035d212253028cb8b26d5226b8798430331a524398aa0270207b28529571a0848c3287748048e15e11d365f5c3063e2460d4454ceae00d5a04a5402861261c264213255c37ce4412c658643b8284450ec0a78c8c70061e30032756868ddc821e1c084a6381ec0a5d8a0890b4c5e2c20caf4821311692884c023b1e6854e280085a96145810dae24a920020f0e4da5b25c22f366a371403891408a2d7c96c832c27e81642929c8c9222204122fa9158eb84804eab340e5e5d30fab6c8e0b31839a74e2f0a20a061c7050610b0a313a6cb0a4d19189d0660d180938d2de46f4b8e851cd5c5861fad1a3909e0e2485c1ea23001242401467f6cc36c06278281343c142907d848715338482525d223811b31a310ac9a63947ce26812054ca0f095f2a02f135e204214e385cfae0c166e34a180429059d2e7452e072d494f8b109cd97107c5f4870712010d7590b3c39e63e232028fb14836053c2a7e0d1892f3e245c8e950054f0a2a2ed83005d4408f1a834c60da1a1039f4b2558c4bf3e745401a2bc6119531d20842284092f15e84011024be4e533094d905eca89e07068c8053e191bb47c8a9121066f82127f28c5b9c08cbb34298228648de224fa14824c8920250c429c7678d228560b0e2f7270235c0d02e2f3036f211a25c9070d155a46a826000008930c9eacc9b1421812bc312b8b815118c858740129199084cd2971414e84ac044e342676734a0046b001a2536abd80c28f4f2e243965c095970b24194ec22c322e24bd90016534940e812ba5b04194ab758f252b811608f5d3600e013dbe68684db009b4088453465894d518a191a527b223112610b9b92a93a527009f27cea6df157755a791091e425c21494365d01f0a38611173c28c0b88320af4b00b306244e8cba4367d4438c026f88118044ccc09067df2883d85b04e48113569cc4d9910046000e2a2a4817aa205b7cb1895c39b2e26dec678181b091aa1ebd36812211a571a35d9c1592268ed61712914e7529a9ee6aa5409202f384073020a39330eb005d2f812c48402d2c18921034600eaeaa010931228b0c04132c901321fb058a5fc78505dc8b18391a73b4f4ec4b9e949c16240c29751f1d50f279b0c11e202840288d8c5d27230744c88db524102a0507c1528826d8067b4ee303972259a90c78546242e2541a1829dac2285cadc1409224a8608204ab87449900a643ad48099a0630dd345021521ac7931f0f985e924382269e0c46482124e7a8042c313c2463e4913c2034f85c284f0368a5cea81c52794294c70c65cfcc27268db125a8d3709883a956285a21f5ba5568912949478e18609814c2970e6c926f584102360c912c250a22de48825e290092e6ce5f2f87c935e1b1ab85690b4c29f0a71d2ea9323c41827e2001ed6c268c0a3252997c519b94d2a65c1862593c2d47cd151e7c695090f193cf165d6ac7087aa47013f22da4492271a3600fbe262916286ce214b5c3a0bd356b3a650dc871107225cc09b427d55e5059e3c6a8c147af3800855885c89a24f646863c21aa9c10091d6f80ca11ed05a44db9e4f63471a5862401e34001040d3fa0a050d882872c2e38017c0dcc030dfd0fa84048d582051088e842f3c65d4f40905874125353c3d1574e5c05308490ba856894ca20eada947d4f42e50838e3c10280e05a1f8142c2b9cd981a265e1028c3c1c780043c49a3b29a6b059c0a418c1674c886bc25b2d8a14850416294571a1a5095cd992188f09f5981203cb974d8bcc707ad4c40aee51a61b9a040424b0c061e80c6f8e15ad41f0d1a3078057049e0a3068ade20014b268b0b39566488dd6256c1051d5742941d2075174885da9854c8e563481a375ee8526192ab0a48859b42e0169c9255355a834ddd021e6acb067b42a29d03ac7058617571518315a6bc18dc541429327d43aebab8ad0083b5888a2739f1465e8014218ad4d685d6a64090534adb3eaa080d3d3e586be1a728987400dad4d5cf0633b2ba4b588528895476ab4be1a1487568aa24fb48522a0d45ac41f0c5280d11a4b3c83e98646a125868dd62374b86dd21a4bcd2ed428127dad4738aaa23508ac455a9350a235882c2a6e84d05a6badb5d65a6bad754e6badb5d65a6badb5d65871447419e49313488021a47894551658330248083256c8e1c8d3a13a6803546d2ad800450cb519a08824e2f4c40c0056a67cd035c19a110200b747a1045f4212e425654e3c88300429c3075eb8288d259c40eb6126841f279af8d0a1e50e1e11a71ce89e5779f0af12dd3920c8870f9e761aa448f1670d062a4484a1f814278c072588dad30682b60dcc17263af8be466f592e05db7cc965f82368c9154bc967d8a0c78b212c714a3098a4b064f92153238003d8140c2841080656c6de369980e1a44319d04f1d2d12e6e881400c104bb916bcbc989329d803c8811902594ee8a193a5ee3b6578586a125151d2c3a9050925b4b03c41718acf88322d26c4012f4712a59c183a8c0279e0e100355d6e1aa4304180b6101444c2021362b435901fbc41e08683062536fde8e24181068076dc3034060c1a077c727ba440d1da89f3ace7a41493510a19a20694de58578b314b55689a3c0ab4224a09334a626a2af84244a2421682074f255052a16604d6a64f39b4aeb64480c082f3801010187868eb40012fe08e090e38d4090e047b48e8c1ca90810b7bba20f1db46a631683e85a52059de98b481674035bca8017bc009881e6e593be4b8f96f58c011b7b505010724420f184023a4830240908f46fcbe164d49d1a5410ad99c0cbc3cd21ca0d01147c6060b521bb40d4c301397434761d483c706994680f1f040293782842c60c19a0c951a009406c58c7556e34a82139e7c32a463010d7e19b07a5c8099627cf8000ee224d1ae0986ad1155747aa8d0e381212e5e286eec3102c0d78941442ab139a0c3cf09a2b6c060da1ac05db540c2eb86060304e73e6e91be1470828308b80650345160187550420804480f2478382122830c5c98c100ae74227aa03032d3c195971966d030770b8f29125730096b7a16a80da6007035e857a95403598742d0f2c7c45b83c595206519911a0098b1e1819c18575b99cc0442559ae3488316520220e36126eb552168940a4320f5d8238797652591a265c80d9e3da28c0f6b5c90893353640216b90a342c4a7b82c2c560cf13395424ee8010e2042e18d8503c808189298910aa2422ecd0e23a2183289806083596448f09174c3066528e881833c02f5c14ec30b1f190c2420127044c4a90990429683ab3c654282a489bac125c468d9428d5594365032711985e6745547862a100328ecc68b904080588296ffc13885044345590e98253c7c2e8b5b7a03949a46087d351b3844a1f3c15eca97102191515328e30d1d271c0a5087874fe00bd310f8070520611da6f089d1242f09de2e248b09040a20bf059e8447a402d8b8d14e2f26c4a31dff051034e24047af4949cd521c18205080d6ff6952d851858c4c5967cc15941466cc0a8c6a62f261754894b6000b80d22c54101e84a223685b4041319518b1a954220089602f30d241d8b26194f5ab0742509934623685ca1cd7fdc4a2e24a1c0cc0b164a7041c121eb01043a55210276f6a840a990c6131601275c91448e7b3060a9a8d0742c94083b6f362170ead4d7c3444a98950c6b299c38e2018b09a09c2a8005a4150864cac14977d5c72a94041f4e610912cae3820c1352402282ab8a0a291238e5aaa96c541736e956302101043232a5a7c6de68d850888925b03b573edc4c428184345bca00c800c82c91af93e5000220b48580c52709870e9f1a0d2892890e6e3b34f020021d5823226129a44ff891d364c8d31fb2b2441cce368999a4c5a865adf8a3a40dc8911b672cd8f409718302362d2531ac648081078dc7e1d12b83a6e982dc1423648b4876b06a70f8b062c7080ca4106c409d32a183232210f56054c21e42e6871287e840d904009e1350788e075c5050959653d1e30328ab2fa0149d41a4019904d6cc215e846a2790c99ec6a89c1a55034e4b0725c44dbd4d63c094f0813fc582592339ab0a272ca8b0e8136aabec49012a54ea202408083a2602a1f2403ae778d234ca17c0040a902823880599c5872a4494ac61018b000fcc48610199e703a7043e9cc18e990bd1003b40a4219a9614cea8f892ceb2948192e58c44062e2f071e7b51401a0b18812829615304208113475c4080fa40001c3db023b216b03050e40582094c708b000ec69d9f89cb89116e56319a772346ad91892410ef8b04146a55ce2e39e920ae7748901e19a97a046b6dbc73c6019b4e23402172a70695169144945000123653784000899288a20b09262c3ae1c01793030b11c08009f2c2d6a7139b305c6552a51010ea905691130919115857562276844600e2a951e9d3a0155e90a4c0983113a2288084a8d20044bc5648810656133031574c3ee00a3b6810f2a7c488cfe58007d06441d232727dd8f08ba302890d4f4b06c0c0848f4e4f24c04a244096a6d20b9c017e9457572ac890c1c3021773ec4481044347da055941e0165c4861474689136b3ecc91d89668c9e1408a824c47c02875436a6957543d7271b034092183d403871a40f201881d6a168568e41a293163d400a6ac842a2381a20c835607ab3b94b6b070f5e4456c26c996993597638a8f181340dac0d323376bb41a8dd0210411d6120b0f80a901c74b075a7897326725603f66f2f4c8d3236bb4d289021402b1517108ce94357c05af089417ed9bb63a5b4050da81c00321e40921fd02a7a66bacc704241ca1383d80b00402079e208548312bb65474b844058105222d4082e7c5e4465cd70f44cf0323b05359d27ca06102518f0705974888615d31144889061e706011684a92374852a491e16a226385c392a813ccb2903663a9d0405c0f345cdad62ae1718106920a5a46d8b932620d064f386090a084020068c0361467dca0d182a3cb519e34233869f040972b585236090a1e8a44c02212de14fe10e0e883112c581540d85864c7ca891d75dac4030a9de0e4429d471eaa8400414762647de10e1816d6a41e9a0400218026535a443800f29481083262d6f00e0941c44ba941e16c8e12b71335e20cf131f5158889130162b0189201882b087c294034100219395e5cf409f5668097885c002e16b89200203277ea74490002115126c468f064ea4d2aa754035a2ba8d862277845c3ca985d570b5f8a298b1e2530019db22c2e10b430810abe1c04b4b0e281cf0344246979024381b79363080a72dab43611302d0782962c82272f32630e055221ca8d96ccdac0738d22d803c8001b753ef1d0a058a065460a488735177222793062029c4f6327073401c28b3c64ba68d0414a3e6483040e427461b1c1923580e00d46d04515783823a8449d184e3c6cf0da00510a0060704298b114720318a0e3d2c088290b468c9909618092b7103948a42410958229208a10522031b06695c8cac1a9d08342051db688b041d38d8aa828b2a6c00b07dae67cf9026eb844c501148805e6498f34686116d7a4cdc04114376f249e84c93a13254705194950b24c093b584bd1c0c9001b3d645e3861b453c22602002200c1c252d8e6ce13383a0e18c0a64d040e6c19d18981378a508820229259112e3e4274f0a1c82445ad0a13903360282dd1b180f4010b969d9c072328022225121708fe804028023d238e6469883406c3d20f1d181f07550c0b14a4168f9c07a118c0c4a4c4e221005230b00af8f472aa121745918ff7410f0317fa885064c296362ec88c6095234e80292c804570828aa639676f2948642584f020018f21b52b4640443f0048e266832f8dbebc68483e1091a3c3a98f63821f80484464d9a5609989478b0ab0b140cfe41525d08d1348c298b9629bf2028b0a1bb0a4456f59aefa18b026eb02f64e4f93070e8c3018a3444497961613db0f924161075e0805fb6060060f8f913b95a555668529a4684420a6cf086827fc64d988c46f2c71600f170f489550e681169cb4bc802105c64b87366fa1515e0fe075295746cb4c0b430869fa35ccd888a281c6871081b8d8284fa446b07140958770030f1025505013d803830f112fa218026f3a4c729433a9bc90c6040f3a4a842e330268d8e02684d82517d84af00010959587ead08e01d04aa835007683129f0066492baa6c9c00f3808a37560a7530020201d2ae4a104be4409e44200ca9a199428750061ef0d8862cb8f24393f0f3a60a0945e2f6e840618a86e806d38d149cc24070629370a1e5c2c9211a1d880006449a19371d3450678428e44d93c12a8616891762086890a148082ffc76e0204425d40829368813c5024acc8c2c671ec579158860892087ca521e165e6863b201f0a04482c40805845419614b3f1c4ce9a32545231150a030c0044129805dc049500b3746db654c1bac9f4cca91a760a420c80a3fe188495846685834aa470d4d50d8e2c0e9da84e283124e1c02e0e41dc077610a88c9c3bad8d1e2ca1409612f0c3922298ba9139f0009390b22e2c9192c03de19abadc6035e26cc59d1e9448ab1115a3bae4664186080f4c52325ed5598123c01df1d747a21ae31129ac4a264897a4049072d9388bcd06754a3b4024468227840952f72d6ec18b0a9824d8d4a10b2e9aa0b2e8674288fa71e4bde7e5cf9d1085496dce80b181b6cba2a2502c185a64c6673b4162122e7b31d798850ed8039254a8f7282a0a8e1b2e3c9ce1e351c584113a393a6102aa8c8324e8271049396aa1718dc328da02543939202c4cc2ba4e824662703316acae4907c62c3f6c7108904d404a06340934bbc1041df21aa382e24e1f3029196d611147025c2102a2548440b67bb03856a54d86aa442545ca9f10c9f41a72ad06d3224c31307102dc8b151c01a1598406934e98713402b1022414a480f455b8044542012d366d2d712522ad0392bc67e1d60b64014285a83e3e00dc41444e5065b2f3250e3e88315620e49d940a243097b61cede23c00086ba4899e23d5234d96119f0e8c9d6a11f33663827a4d48018c04fdb016c678d4e6165c075e8ec0f86111dc0c001bc8045970a1cb9c5a658b401802283f95107506d323c1db0f601501c0034c032e306a04184a552a148ac6969b485843323c4c6746c70c20ca038892bc7eceb0c1514581f68bde08020391cb08061c44eb572002b06066026652222878a88202f66a8b0c12162023d7ab2769f4650d9214731bfe5d109df5914d963c20941270a30c5a8d41c2f5715000a0527810a0890fae1e6d498085de244e87e71234a76dcde20b83a15a2910c3226fa6850d485cc1931595f14781289839f26576bab943d225c10c20171157cd09103882e2b8220965c348ad5ec3cfa4a408b088c381320e43050c7ccc326341e52519550088ec899c0a3d29c2f5a7eb05c243060c298086464bd48f2e8910a6d6dfae03921c58d1f104cb4969c5f3e8838be74ed48607315c12322b92b824ab84fbc4ac449f2b4f2d8f0220df1264e3368d21e158fd24a688406d19d0c25187dc99107842d4d1cb03d60b616302947ae45992e1d0080e646d01ad48993839359082f289f00c05105841237461951a0c4254a25d03426645880800d3b04ced800354946a8b0824593061b32ba880a56f9a095258806049ef9d32760121fad08622dc031655348a849f37a021c43c00a35453110f53143428a42ed9e063af4812378238885402588490251e3c62888d00a563610202e120aca9021121dc01420c781724383cb23b40c3cd19891e2431e1f6843de2b0e7438f0a2dc433bb00c2dba130625dbc04d8f1ba7b4d8a8f030a209d9241632c82af52344c0c6083b206084c24890acd9cd4893363d3d5ecaa041bb8245242b51cf10a92f4d7c495c5895b6260205233704bdf016c7d5448e7a55994a381167cca9e0678c100ab284f510b352e3c015a7533b160052238807a1b816958991b5227e20143694749193250519839130204431714a77c90b00321cc591f12183f86d0ba52b284ece1e0daa91698352ae24b62d1320caa274848d971506e039f983415ad8cf433121843c34dec9f141132354db4121400d5c898892e4141d38323b238e3786c28d4ad691d02d10034072601b08e1a376e9832d2d3c5c62e0289cd6ca9a9c00414f0401bc7275481dc9c04e98798ea71059a8b664f02784245178449dd87305c92d0387ea84b7185a58a1d4ac40c2a5463e30d021a6f43e0895e301a94a190f3000a02245e9d081142b5a219228c214286d03a1f747980360844cbc126e5428c920e080b8324cbd100114d98d52f1c156a3163e898ece241154420051140b202e91c970a4518a2b17f8517db4a9808116484cfa4368050e1070c5300270e08b2e8c8f8aac18a44079f82281973c5abac4a4181100e237a3c91941094565441044110aa1c510123ab6182153e38d4171c2214881075eba1445e4a490d54bd77647ced4b8728852b146c919d183b728fd94b1a30229af16aa8c98c0118a90021f15a72828445170aa0b140f74b9d4d344a8ea875b4ecc20395eac2b8f3c5c0026040ea514c141181013da8ce0f282b5241488384a5427a47410878d831329dc21e3211e3224c8059d0d0e116117608da063ca0de00d3d5311d6b6442594108186810aacca8000604528ab910617f4dd8995ea57c5041c1c647c2499c12df9835ec5092fc508ec68e3a482960f877264b1119155028a2f269a0c98ecb4a955404212071366ba57c10a838e3427ae70cd497395e82a2c858a1123589ad3977800c947a52dcc42e04330299291217fce742822f4c0ba492b54a0dc18b414749db8242b0a0e10c2e620915182248d0c1235145c96d446056920eb4329b215dcd428dd5c92b536b1e05bb5d45e506077d0414a0793be8ce040194090dcec641225166e3a680123ee840cba8a02211040032448a71f19b48522d1cb11a58d9f12136080230702024e0d52483285084f118cdc95204e713439b244438801576f214831208408b24d901112453af4230202081eac8154c284055ee43010f6e3890e3b3c78e8d8e243056b4702211674d06652a41092a4d092430208347460b747540280f3c6501f468be4782061458bc39a17070c8d497ba10b4b1547572920e079e992c0cc4f0b6efc9025b41d1a8178f1c290481fe0504dde80a097502a9461042817e05c023c5290e4c1861b5e84f409008527263b3ee09302853084102dba73c21b2d587a3abc18a80002889f3f95aae0524b9011ac8f2d6e321ec5802187841507aa3871508091f003e187033394901456890489188345c6c1961710de0814048905f1ca9ca510d1886a0eaa8402ad17723ec4a302ad697382cb1412565636de1d32207c4c80c2160f578eb60f351ebc33af29df152320139489302be500578a74d0960a021081a3530b49262ad5e090820b0d56f8fdb2487ad4c60446573e025ebc09393304031fd4c2c696c72a8b908c3b40bea8bca19c84e97441d81a0c265217d0c081bbb4438210444170278c00eb95151ab12d5cbcd0a9b38156219d0beba12cb22a097ce9e22246feac6102d667840dad316df68c62e2e1530a710c3d32d44333400e0d96302e989505211327c90b2723b05c213028e1ad8f0a23726a2860de4142a48f0425586801c21b372319331706a1c863a76b97531b7d2d3d280d8041412405309e4a480aa18387a94e82d25c29b7ecc066979ab41245929882004f0eb60bbe42e052356b60536187850ad07825c2eec4a9cd1974b5210a080e183853a0f1c190874e1f9895e104011812826e6040e710083f691018da59f0d1ca14292c0a15c9c923382fd00cd863e7c903612084082af3c68b1b2543c2f448e3c007011190f8c273034b821f649e26a8e4468aa54f6a29d430372209260560f8053a608dc8e9619190678307333e9c447a4269caa4381e178c38b3a64318b116487ea420d3c5386ae00e0a2a867c4cc0484ed9a6ad4183c900579060c5483a603416478da65da615910eb985111e42e1c0f181025a56a0941c1550040ca9138c2420f348cb9f18ae1547893e6c13c5b2aad380a620596656a80041a5ae27178c89a862e6ca9a1866a83801c12c408ab02481132e04f5cadca408fa1df25470a3d100a3aec89491f2c5e9aa0494179f58d864a5909fac52504688f3637622803aa164e3068c93892694203902d4674f233a3d29877e08f0c0958ec326021a899120d317d50f155810049015e7c592d845a594864f589bd6918f2c5a8e5260e04f1d1d42b04090e6c182035a28c969204a8d9e130e38f31052a12c1d72b208314a4f16127a9faa70529f4c5c79c7e2689d963a9d18f36ac304cec7a227c0070ae4d81e8c4045e082261f97b13152326c891082c90839e864952853868170920bfa34471dbbefe131aa395aeb9ece4d5af3554142d8a475088c42b81382f1625e38cb1ce8e6f918246c07f96921e874dc414970645d767bfc8f43e72071d2394822e81c24453a074990ce419aa3739014d0394858740e529cce41fae91c2456e72005d13948623a0749d43948a1d69ac3002342e4ac3068f8e520858abf8ceece309cd6c2708cd6eb33ffc96e0fef73d37a32d7f13d7c2607e1fff2cf83ac27731d3feef308232b9bf9fcdd784d6b7d6a86e1b41776fd5f7ddc5477e71fa6b52e85a1ed05193e637bddd7e5bf9f36e6820fcfc5b4d69ad2d85e576bdd82cc674ea6b52695c95115ed5d8ef63f0e9d2c035dd707fcf1ffc6e379e02fc886dc0b560b7572c803bab3dc7b9bd115239c11c2086384ffb73baeb0460874e7ddfc7d33ba80eeccd57dbcef6a84e1fc7d3264845eb31c8c793c5ea803c6b1a15717d09d43f8e50abf0225a936b08e238ba228996e919ba5474f34ab4aee932ccf91ec632f59d63cb36ed2fff46459b634fd57d3d2ccea7dc95b94ede3e7bfb72603ebd9a6e7274bd58b6629f6922cb37e6e922657495e9e6cb949b82db7d6726c95e974313cf3f8cf6efce732787ecb74baf8cfee0e9e5f57a7ebdf8d14f759911dd515aa0556d26ccf4e927fe422dbf69424b3a2bd6fb2554bb635d9b4e4620fddeff7ede077a76eb3efe26637e6c0749f15d9e1a052606dd5fdf9297eb1e5fd34fba61a439e676a6e72ef739fe216b789a25b15b9e763dfeab849f44c45332a312b79929d1cd95fa667e7248a26b08aa2f766bad9b32d477593650fdd3d3908dcb5f982acbab596a32cf6d1f26f36b615646b6c8bc7d6d896ffef9b0f9d2efe6d31ffb53fed7bf84ca7d3e97429ba4f736f2a16a8c2c4b31c8c773b2d2a0456d62c4d5444bb577f0fdda9da0f8e71e8743a5d3ccb678f0acc7aa2a2ca55ef49df59cef2b187ee72e7a0fab2a65cfdac598ede6cd3961cd51ebac5f1177cbdac694ba6a44ff7b953966c4b4f8ebfe07bc0da7bd9cb2ff6d24c47966cd31eba77b23bee2e68aa92688a8eaa68a226ea3bda7ad3abe889aae93f4df58c8acb2aee94dc645aa2632ab6a2bc837db9cd78964faa2deb57c76f725164cdb2e4eadf2d8f4acbaaaa7d9b3c3549d6ab2c6fc51ebadffe4bc6bfd763fed5e2596d6f349ee52c1b1d4bd444cb3435c5524d51b24cc9b6f7563dcb72b3e589a25458d6f454cd2f8eec3f55d2dce4eea17b2475505d5955f193ed17fb3fc5d48bdf7ce1df0fee3b1fffb9ea68525959558e76be51311d515544e3e2f9fde62cbb66aa4a55651dd3cd9ee6e72c2b7eb434bf0f185c13a9a8aca6ea7dc93d5a6e53ed232bf6d06dce3d18cf6a7ba7f3afa59ab296ede61e155b9e9abb2449d4a592b29a6adaf929f27293661ff7f96720f7619ea3e97472fcd3f3acc88e8d2acacac7d2447f674db51c53332d832c94b5f43cede5ee9d9764b945b43c5df6c99aa27f9fbd97e488a2a64fc9cd9d6cf43c4f323df798aaa4ba5135454b72e469ca96e3d9dbb33c83bbf9d7a2654dd6fe53536d47f6dc25db9e9f1c49e0cf2559035671b3aadac74dfaf4147f2af6d0bdf3057fe266b2feb4dc9bb33df56737cffe7be81e53b1841b4936572c3d7fc1978c5b47f273d5a3ad499afda725db43f70f8e5b559ee3eff6c23d8d6799845bd9cd77da9eaac837aba6bcc3ed5bd6dd97a3e97470fbba3bf4acc8ae7d5b4f3eb22c6ffb4ebfaab6ad3abaad9da7a939963e354572dcff9f637025cb71ec7b1ccdb125c5b2935b6b39b6e07c19dbcbd3743a9d4e06c21b3d2bb2f3f37a9a5e4cd94ff674e4deab7fbc21af9ff333ed294b9a69a976cf9e64f1ea51534d5bb2fc6749fabf13c9b0256bcb79dbcd746c499ea2deff9dcff77b095c4f136db798f6b2f3ce8a674a9e67457647e0b535779a72b573f6a769e75ba7f3f97e17cfb3223b22ef3a9e623ba2e2e665ba37ffe3cebfe568fed634fdac28a669e9bf68fad1eca1dbbf4138dec595447c2bbacbbed56f72ce966459feb3ac6d25dbd37b944ccdf34cbd3802e19dba83b21bb399664a56b1f7cdfe3eeef69725e7ec4e7557de4f7f926c3ab6a3c88a9ef7d07dee7896c9dedac9f1b79ca7a968969bdda7ba67b0ad65fa59b434bbfad39ff24d9266bb8aa7c8a6ea66fbe8d5dd8e690fdd3bf9af5c25f7c85bdffff8cdb3e43d749b5ede447b8a726f8ee4e85ff7ed0dc26fbac1fa3a9265fb4b1155392b9ebc3dd975f75135f91749738be5ef650fdd71eccecdf53b72dc9bed75db54beeedef9c937bb3d3ba69c2d7be8f6c1aedbcec7f0ee8577e3a896266f65b7e977f93b47d57efa13eda17b47ee73dcad9f8fe8b9d393a37c9769eb77f09cd248b28a651759d57b56453bf7a908cfa68a641d4bb3fc623ba65b4cd9f434fbf73b78dea17bc691f577b19f5f3dcd5615cf34557df26e6ffffedfef0179cc66ee71a36846563f8ae2e9c516557b2fcd8ff6d03d770ef7c722eb886e7e8a2c274b9ea2aa2f7be89ee53fc7704fde99bb8cc8caa2a9297675fb92fd7c77df43b7b8df60e0ce17fca9678afe82afd89095f74d7a923577cb8a9e2dd9b24cc86a9aa65872df79797ef5fcec9823c88a8eaa6896688bf2322d555eae6de148db1a730059395a9aaa699aacfa7dfb4f4fea585b51b29b6c79b662dfdbb39e6fb5f514dbddcd54255bef3d67d57896c5356d25d1decdb22c7de7e6473b0cb33a7eace9475bcf9ea2fab98972cf8ee68ab69ee926791fcbf3a72247cfdd43f78edc9d387cacdef49d93e328a26299eeadee31ec6ce56d27f7a89a9e654996abbe876e798e3970e7ffac16d06315db7624c796e5fd2cd3ed3990d499ad9b6f5364c7913dfbd8f78883c72ab6beb39e7fd524c5dfa2640fdd3e1eb3fdf975779e03c71dff599d7f2d99ee5877cbd553544b34fdbc9facd943f71b0165abf87dead9b19f6ddb4d546de530f3acc86e888e3565dbeec9f3b7ec6749bf7d0fdd33dcdfb105d9ef7767cc7ba38cc6bcd74bb51cabf8c79faa65297ad26c7bf7e9b8c91d8e3535f729fe7f8add647df7bf3c83bb2fc71c58ee725d3b4fcd91976cdb49957f76eca17b8e6196b720f3ed5755ef51ae8e2de7ec2e1b1b53b13cbf67d1b2ffcfa6e73e136337da9262efe5f9bd5a9261ed38b6e74f4b4ffe161dd5cee3466f3b2a96a698fe1445c5b68035f566cbaaaaefadd9db917b1babb9c9544cd19d8aa9696e8eaa698d75fcbc24cf36dde5464771a71b8cc69a6ed597663a72f41c45f1e41e069bb1a2e5a98ee91f4bbf49d43449ef61b0951c4fafa21d35ff56d9946f7154d5f56c4bd5a7bb3c4fd5644ff4f7d0ed03fedcdd1cfcede07997a2e9ba4753f5e2e79b25cb14f5ec0c743dcf5fb22d798ee2389e6237c771c6b9a2e7389628db55b364cb72ab6f30ba4f7355773fbf79b224d95356e5fcfd6e7f07ffe018b847b0b23ccf5dfaf1fb93dd636f7be806c2b1df65a4f93d69a662dbc5b245c533b8c47d833bd93dcfc6f52cd514fdadc95954345b91f7d03d077f7b4db9c8f2563547767b73ab680f9ec92d6e31ed236f55f197e8986ed6d777e3de2163ddfd2c45b6ddea38962a7bb63d74a762e65991dda85b4b72fcbd14bd79ee361d51afd3f9743af53c2bb263bfc89d92244aaa648aaa7b8fa76aee5e965d2453ef51b2e45cfd4c5c39d86e8963d007fcb9a3f90bbee4fc6bdfbc56ae9a68598e7b979e93accaf6d00dcfafbbdbfb4c4d998db17ed224d5f13cdbd1dce426cd1eba65f9a73f86c7621e841dcb7d9e15d9a130e0badcb8567f9eea897e913ccfd4fc6ad94377fcdfd7dd8dfb9c7f2d79be58c7b6454f12ddbdfc686f5333dd280163da76f46479175bb22c4d93640bb29f8c6c73718e6269aa238a9ae3689aa3b8cff26fad24a976b23c4befd38faa9df7d0fd5f78f706c76ce9588b53f46d9b8e9c6c4d53257fda43f71b1cb3ed360f166b67f9b953d6b72cd943f748ae583de9fd275bf6e4e3e937da7be8def997efbe5bfcf7886eade5d80adb1adb5adb5a33dbea3fc81b4f538984e5a992ea4ed32d8adb5453751c47ddcfcde7d3ad25efbd9b28d9fefd53762c7be8dea11b35cf8aec7ea8f8c1f0cee7e3dd4e80142bff6a574fb6f52a29b6e8e7bd2eee1d8a95445193544f7e7ece4fd4ab3d74c7b3bcb596634ba79b7fad4ef763b8cb7c4b1c83bbd43c2bb24372623d511565f71ed3b1fca7d8b23d746fade5d852d3e974baad35b3adddebee8070ecc7329e816626d66e922aaab27ff49cddea667be89e7fed4efdb31b0b31e75f4bfe18eee559919da835962031228588ac10284060c1b87a91933f788031408a0e2538c81b04d010c0dec1fd131950009002c3698e5661155509dde1cf86352950fd64fea6439142ff1f7f316c60538883cec04b9430fd03cdc7590f331e2bd998d88d026cd4a031034c4dd1d32cc9117da4b5a68241358a83cea04b9630fd67377ef3fcc7ece8b603db01e5d5cc7ac0d5daa42b040bbd4230ae35ed3f88ec02c36417c87b7390fb803f976c03505e7b83c734e80c0060903f1b8f3f8f57d3344dd334cbb22ccbb22ccb9224499224499224c7711cc7711cc771efbdf7de7b6f711447711447711447711447711447555555555555554dd3344dd3344d53144551144551143dcff33ccff33c4fd3344dd3344dd32ccbb22ccbb22c4b922449922449921cc7711cc7711cc7bdf7de7b6f51144551144551dcaaaaaaaaaaaaaa9aa6699aa6699aa6288aa2288aa2287a9ee7799ee7799ea6699aa6699aa6599665599665599624499224499224398ee3388ee3388e7befbdf7de5bdc6a8a9e66498ebbc7af4c13b902c340fbf857fb315a9ceff5f13ddce3bbf11a977d5fd55699eb587dbcc366656a71b31b04087cf62bfb337fcb62cfc55ed8f55c6c2c4d6eebb04fac37c7ae580bc3319ae762dfc767b51f07e7200e9f8f8fc5cdeeee3447f86d6ddeb1bd542d95a7ea5a53b15a53712ade77e335246b9132b51dd94ae663ec6cdda5f9484b5b77ab1910321f3d7643c27a70d9f71fcb6060ea36cdd1ff99b371201c03cddc3edb0bfb6cb0f135b4f5fd6df05a1ab4f1fb73191becf365ff75d9de75ecaa2c355b0a72ac334d5d466b3dea2b728b1cebcaed16bb5fe50e128e8bae461257e399ab5de54a5cebe2a2d25ac7f9c21fef28d39a4a07558e3732aac8b12e8eddc9320776de7ecd2f7fdd9f1b77d9f53f99bf85b00eada970c83e3ef3b60d966d845e34f8cc3d500ec203c87bfc1faf89e9d6abaf425aeb745a537951c1e8a2e2baf27acb1898b4be8ac1a6a95e501140e5826aab85f6651f18b716f77980818de4150c09ae60d8e9d2e9745ca1d61a88d6555aaf6968a9a9a676d5856607947f700907faf071d545438e75b3cba6e3dec9f107fa7e2f1d77f08a6b0220fec3b938766060fec3b96d18c32ddf0165acab1776b41ceb601785d654274c687dc6cfdad7fd6a5fb78a8ed6faeb2b0216cd33fc429163800a1a7266128a5b214c1bb400d305450cecc73b05521f2803c0307109c317304f1cad917062511ce9091b383e76dc3062e9c96f120f383a01ab27e8251a5974f0cc13ab13a5d432a2ec38e1404ca1235d5dcc9c2cf096c1ce8a1a1b9cf4306d16689199e144069a9c160ca8514d480d10848fbc02a126186c88a3c120186913301ab4b4295a6ba10911376238486061a1c996150e1a60926167c0a7360e775c48cd0ce84346894a8c03701860c52a5395961a8c0c4053d4c9042323c900ad4ceb0a51449230c164058309af1c5098dc41cf262d7c9f89015cb6f0d23930131c02cc88f225a40b932b352fb45be400e34228096d7863d2230e02306966842954232e1ec04cc61b353b8e8625105066724a5c0026c4101d61810a8e511313b823091b1c9a04b2e08123b681638b00ac6b4d053e70e3098556e404917a335576a3d4d4c091b74260650699f0c4ce5b026c8833804602de76566b5f2e5001be712972e322e34c11371520c560d3c549965ba00b282961c2fdb86961c3e4a9d0990cb735b51443d4d8e8e2168a3d996366aa51909355294b21360015cca3c8d4d99b060b825120f81cc2fcd8823b1cf880125a1d0ec12c1f93a0c88168cc2374b8330a3ad5e73839a470ea608106e620230e80cc4c48cc61c1259358281462e680c20905844070456e34848438716918e43595266041685e24fb180084215e93229f96c44546dc12c9556707c84d761ce2443b0ce8d4c28e1393790d6911b38296980d14c09a31163e88bf4cc566d280525b22ca942011250e0861c9a0b11481892312967899a04f290488b42d191246c60e0b1164498b372e2ed093ae8028c8b099524c09400aba08c81a04670d5825ec447626cb18100d5862d09f28858e40812f9d5120d14f0422d88e9375c916560d9809ab414f062a62e0d504188cb6336e70ce92184f7f9c78bd9230ac70d202a7f4b639a3898c0b52a0bc4b76a4517a81adcc3b034e0af22bfef266587147d016411d7e1d007f67d644f8094342a65f1b411c43623e3afdc61541c0ccc82d7ca5aa5a8019c507f511d2c017064c1f0652421df890e44132c84b8729be17839e9c1c6a986d2a541d4a027b186d15b4650102658f6d7edad4de1658812b88d8a6645327aad4ca011b94e041a1fc421fd0a0040aa65007910cb2923225215c3a62858274702bad71e9a9216be4c209290d90080c943b6b22e1c8115bc4bdc1a7921c396775dc0d17b03199b3e34e0f04362b2e0002a1486f8d8a065853643a7a6d3db39306af0dc438ea5165f140888637106c883ca400e566ab139b024b242862020a2d6c7c052f755aecf880cdab8f134c00905d695970810c272568d256104bc9931f3ba4da03949cbab493f2a5159205d1041270d0a165e141a3515f38d4f89e545450e4091cff7c4afcac4d6ad3a792a34a6e054226fcb3471e9cadc9213e8939c147635fc63c098044553a3b9cbe8e0244da3f9582f8b835449a8807ce93f99c9729aaab97a643aa146488d80d53fd013c7038ccb08d1df82581b63a562ee86050d2564c6c147500c63728a74d6dd845ac1916294e61d21fb03f0fa632557404ece62e14f240149eb203d7006cccebcb91461c814226a0029cc0f1232b7002047de0027e61102f05d3ad0a1132553c403c2c677274585edce1a5b509c2254dda0a3c142008590fed3961a76912a5360176a07678466042010ccf8bdd932e0e9040d229dee9004e89bbf4c7c82e09251d404468120225490419e0c051808528c90c00902f9ea446496e241140cd2f83258e85e4832842523d5a41246a5a395f48e8608044498d4a3ac65cfa21298520d187808c78c8714736a0a4c5092bcdc891315074f80993d4e4081fd58ce1a04c3c12a3871116ac8e6c9a20ac61e482333bd430c844c64897af310a666ce86224881116454c88c40260e4ab81227bb24ca04db69e14993216e95164041b133008a1a600229688d021024764ec02221ecc218f863830041ea2b60dc1d0238f4c224141c8a22e42ca5412e2a3081183b41024831d620e0dc4980a8240102358415c203255c37ce4010828e0211f03440140fc00b9928642083c126c80fc90a482083c38360159cb14028997d40a47d624289be342cc20276b4ed672f4b8e851cd5cac61f511000921600ab5492c04d94778ba9a1b1f6a7207351b74bad04981cf51d3b095e6018ab434696e5a99a67f1862f2434ca11fbcdf0f1846a8260000880168a1d02c40dbb2430382b6a28c8f4e3e2610e1438a0f331f6796cee474387bab7126e6cf06e8e1a8079b1e6f8f34540fabd8c5d27204cc14a108b6019e3161b680593793c183140f0c40f00013018f22613cb638d8f169479f1d5676fc30e12183279e811d6598e4b23b47ca9a94e12833d311429c0e08b88e58938e1a6d74049083518e0672f448c841e23085a3108e0446e0d8e1e8e2b4aeb05ab06aa95ad7cc35e402c38bab0a01324e647ac8a290f1201b1b612cce98db58d85807b146626cc47e62a8585558a2443f8c8ca32a611d6b5198ae4a941ba394dc1894e5c6971b3788dcd82ae0933ea283711b7c6736d87edf59ae3fe94a7a91b640dbd16ac0e8281aa8791a4ddfd0a20e754e6bfd417bd003e80e9a83dea005d01a74003a83064063d056ba4a53695dd5ab12c086d6555654a7a85aa09a43d54653a9b91a21cab1cef31f577da8e25055e50295a82b114db42ff88af022d6b4d6bc2b11a51621e24a84060df6817173d6a5abac488cb21069adbb42302f2faeabac08b2d4bcbfa00fee61f85f37d87f2cf3df0fee2e8f67b8df0b391b1cc741f9bfb0afab7ddd2f1931c2180ecff85938c36c5ce19fddd76d43f9cf70704918f7dbe27e5b178e2d8ac1235fa2288ac1235ca10fe6b17082c48b07c60bfb57c2c2e11b7360f87d6eef2dc4e163353345ebae976b17f35edfc9aeaef57d0f19b4b078575794a8b4a84a684d45426baa115422b4d6e5191475a1d70beb0cfa2ad784a7a193d61a89befa70eafb728fdfc71feeff82ef07f2dfdb7f67f70d5e7d80d3e1d5872373e731eff5ab0f3478e1fcba70a8b5e685f167fbf3eb5e7df870f521038f17c7e398b3bd5c6baa9cd6541fb4a6f2a0d603cc958739d65a7bd09ad55703a420c7badecc8132e266392dfcb17d39eccd1c4805001898d65418ae3a8cdaa3437410d3ba459c63cf0a00065d71e872b561d495000b5c0960a51349e4d97066c2589334b811f6cb7b804a0ec4a0197190333df8156585213dd07a915434900115408400c8a28303350b0cc0138985123c3a38b70fc32f000e6ea4025d068c0a41c34d92033e596df10910e308dc0a192de4442b986c1039f3fef980851d9e5697422534d15853eac89793164ec0d0a30533c28d107c4a9df93240a65821e703690348d1b9044a3d420333f54dc08da2323b62a0d5bac2aa800701321a62ac6001a3b52e306a64a53eba8ecc701e0441c2e6018e007e7c8ed4a3134800c38be0ea525abc620d3b81892e2322524d541ea864e084662ac22024ab4f1db216d2d86c284afdc8490a38e55cb12e16150a33612c803726d4a553493e99584443ca0d37ae84531a05143e3874484bc40d91aded43c8d3678048173834d818708548052b9d0aa501c56984116aac953a3374c2001534e4cce4893856542ef8d15d9a4514c2b4410b305c24fca9627b614c201430b0c07ebc5320f5f161c8cc950ee7d48e2c286098b884e10b18286f46390240844f36c08835124e2c8a219deac39383e10ea7338eb0e0f8d871c3c8086578a64f56176471128762120f383a81aa2f561c4aa43933c0182f232ed1c8a28366da9cd13082591347701e8138514a2d23aace1e3b01683940060b051029c4143ad2d5c58c48890744f9b63a915041e22d839d153504a834025a069f5064a954024498360bb4c8ccd0ea1109556187079998a84093d38201350a86882e155288a0c2118c120304e123afa041962b3f5c989e40a1b41662431c0d06c1487d915e08f72cd09a8de534686953b446ea531d51cb2bae13f02ce1460c07092c2cf4c054621093237132010a61858306986448f2c09e1c8ac68840a1d306b9360e775c48794c402a430170e002a100821619252a310e58417901fe49079ccac422cb2a5395961a1d78e170408a1d8e4e753acb29ea64829189f448801a7740882ba3858356a675852822490808ca98820257a8375859563098f0caf1c40a8c2ebd406aa2287873859e4d5af83e0aa44658e60329ae6682cb165e3ae7adc053a192abc4972a1522049811e54b08974329a883d472b4316376a5e685768b1cb89111ee7ef0556952e3444968c31b13b32d311479801222802f9d08306966842954634690026c20ca0100a802ac01cc64bc5123be4076ea033b74e0173861090494999492c09a05d8988de0238022234c88213ac2029598a639670e688a7d3ab09a98c01d49d87a1408894f50c015440d8a04b2e08123b6998d043ad1c20591b6c811118075ada9b0074cd9b753c84c881206619d5068454e10a91b607b2c50d324e7a72a45d98d52530346ca88eac2c6e6a509013424c0ca0c32e1491d3a3b07ac98d0c284f9090836c4194023012e4af8682008951660bc0aabb52f17a80067189140814f1d308ab2868422372e32ce103124820bce02379218d0020129069b2e4e42a811e10a217da54c47968a0b282961c2fb3050ea81250c008337589460c3e4a9d099ce92a7e2258b000cc8b3106a2986a8b1c165139a6be32dedfcb22427f6648e99a9466a3296ef159641015eb4ac4a590ab1c1272b56dabc734e0ddf4e0845a6cede3458a0a4831f2e8424f9a5202007c1e710e6d7fa89e4e607d69d4f973a6939f00125b43a1b3480d1e6ac8a0f4d1fc8c9f231098a1c6846077ef6c456b28a4941ae3adc19059dea1e6464b082a22f6677b04ce490c2a983051a705282461f4c128438a392c58803203313123e8438a68a17b736e75570c924160a81a03256e4ea7c94841302265038a1801008ac34c0a84bcfd6011ece1411434888139786018a072c18a0a8c18945d555a50958109a17fd36aa6bc7d4da26aa130c00c210af4561b326545053a935085c1196c44546dc129d691285c0153fc6670debec00b9c96e439b2b3b370d4c70260137143b0ce8d4c286c30658a6c3defae20368c46b488b9815b24851618e0345a808d1c01456a000d68cb1e081992e217a2c55c949b351988acda401958680a4405a5c34954c4d30c2942011250e90e4c6d2a9352b7868fd2420c65204268e2c99a0a74c8f3c34b250604a31419f520810690b014450d6095e9e149841228c8c1d160248042294220811e282ec05106f5c5ca0275daddd1984094b0e8a458654c8b09952cc262c533cd080a2928c3c6e25ba08c81a04472d8d9910323c314b81050c42849dc8ce6411cb24a2ac814340413bab9c1690454e544529e0c030c107f4ccac4a0cfa13a5d091276408c0808d59962ca78310671448f41371488e0a2b3d02c011f4c94dcec9ba640b8b8616095c246b80f42cb8a2c56ad093818a980f542419aa6211566ec8320106a3ed8c5bac888400ca4d65d79a9c25319efe3889f1a5839e3448ae8059716602c30a272d704a3c726431f5c286286659d6194d645c900205f6fd1630c446c35873b5238dd20b6c655c7c1232e7354102e961024e0af22bf6e280960b4b6519a4f04482881577046d11c4410f9d2a7a02588489cba6ab3c5ecc191cc018704183244da41f34fa9c5813e1270c099902580b8d4104464a0b83444410c790988f4e2a06356f8d22518d71b15604013323b310638527012c1d54646b28aa5a8019c507555355fc980092193557469425dcd0805406217c49e0260326963231c202d387819450074d4019a14049213d568228f22019e4a5c3143ec5cb880803a030c3d388414f4e0e354c2d0eb4904082830b435c40541d4a027b18063c7a7de0807fbc02a4502242292819e0ec2cb0f3c4118929656f480081b2c7363f5012113985c584ea832b20de1658812b888c324b4220f25088ca8d910a17175c81e09319571e4f20764032030a8d4427aad4ca0102f48131e4031ca2c905038040a1fc0a3adddc6373ff5b1feb3136b6b58536923a1d109dce14b7864818f0ac9f4cff264bef4d3f96aa6fd5d2080609d69eeeae7a9393db73d4f3f6ceca8eaad95bb3ab2269a29e7f3b2b175b5345555644b7475591acb39a24ba45b64c3bdf18e8ac68ab9aecff2ad9fd9cd5b37fdc247baaa448a26337e5aced1f7def6639eeced93425e3aca94f3939b2e63751d47f726380b38a9efb3fb2e8c9cd92fd698f60fd7efb13e52d47792759b2bf59c7de55f1e4bbdde52ff9c9ba59cfb28b65aaaabdfc26df2287601545934d49d26f6faa9d6cd5366b4b6ed177ffdbf497e9e9d507561535c7affa32254d7fb2aceac0aafeb214cbb2fcece8476eaa6c56df7615f5beecede937dbd335abff662b96e448a6a957779aaa59d3b4aba9faf92ef95677798eb21e0c69d6b273efbf799aead8c78da60dacfde4dd54c5536c4dd697628a663d51f6937f9f6dcb8aa569a26756b5657dd9c9961545f2a3dfccaafacd7ef224d53465d5b29f0cac63e94d9f96242fcf322baaa29f6dd3d6ab5f4c51b35c60354d5e8e6967cd52dc2ce949322b499a252a9a62ffe3564d5681959726574fd29b3b2d3dc98e634e9f961d3d4fced134010c62d0ffd5f47791dda379b6fedc2a190c0998fb2ccb8daae8586e74244f31dd23e7bbdda778ee911549df43771acf400c61565eeecff2d69f6aa98a258ac02a92e67876542dc716fdad39825949d314c5913c4fb1257979ee2f6b6a96ad88fe727bdfc9f2dc3e4e6e1f18bcac5f44cdb29fe3c99a665a96690fdd9b07860356de7bca9a7d9364c98e5c357be816f73862e8b29efcf3942cf9388a6a1c0c5cd67fb6a4c9372beed67b534c770418b6ac1c3577caa6a44fc7b6554f5410062d2b3ba2a5e7646992e8e87befe2d8074396b5b724c9b69e244971b7be732e18b0aca82fcdf63c4d13fd6dda92a5badb59305c59d9f42c376bfab28ba4f79fdd82c1cada8eec2fbf699adb4ccdcd4b9d6e87a1ca6aa25bf43c153ddb76b2939fcada9e622b72516c4d51355374eca11b9ddd9febdbf566e0f77018a6ac232bb23b554f93f5643fd1543c8241ca4a9efc73fe3b577b3af2b66388b26ed41ccbf214c57144f766d5532bdfe5264d12fd7ca7a4799a5afdc9fe9665fb49aa65efe32fb58e9d54d1b46df9de7eb7a47f61fd9d9fa769fa9da6233996dc8595144fb1aba838aaede66da9b6b09628bbc9f4fb749729675352eb78aa66db7e5efefe51d54c47ad66fbcb315559b19f9b1d7d296ae57ff3f4ab261751f4aba268c5c2bad5f37fdec96df6141dd1b487ee50abd87f5afefe559645d97f9ee6c880d50aab58ee5464d9fff9d9c55e923fe62cb9bd58a9b072f1f42c6b6e3555b98876aea6d3e974e7560559a5b0a6e466d3ee53f6abe6d98a1bd49a9e5d353bebb9f9cf8d967e6c4b44db1adb127d6c8d6d89675b635b628fadb12dd16c6b6c4be4b18566b625ee3e569fd6b19b24a9ee9faae3f8b958b659a1b0b6bb97bd93ac28b65e157bd94381bc3ec6fd06772f4c6e57253dada7efdb54b71f459f929cab27ac6949aabe54cfb697a2d8b2a709682fd5f2fc9d569e8edbfca57a9a1fedac58f6d0dd9bddb732eef3f0f10e9ba15bd561c569eb925895b0b2e54f37abfe7f9aacfa3b7b5664375b6d5a51313d3d79b229ffeda98add4a134f4db59fbded28ebdbb4ec94ac32a9aa98aaa86aaaedd87bcb7be896e1338a15a69555c52f9e7d44cb76a3a719cfc04b9ee5bbf47d73b245c5d3f7d0bd83bd48c25a72d19bff9ba267d59d8aa3a596f354ede8e9d9ef53b3eca17b07fbbdc57134c06a84d554595615fbe9493e9625db7be876d3153dd5713c2bb2abca62556945777bfa3f7a94fda7efe7d943779cef85c9bdb566b6b5a354a6657ab2275a8e7e8f5ded8fac44e06a49eeb1fbcdcf74cba3c1ad1f0765d8d7b75834b8b54bc73876378edfc7f2f605a2d339b29ab4b6e948b66c99966869ee4d8ae558ee1e5621a4e51e51933449f12c45521d774996ed57d52feed49fa3d89ea3222b492bdb72df8a249b8e5b635b6b66261dab48eb177b3f59cf8a228aa25efd5690569eb69eb3bea7bc5551f3a71c583d5a3d29b2bb6dc97e8ea2e72c3f0b59395ab9e94fd5b362ca92bb6cf9b9f76e9b1a58355adbfe49b36c4fb4ecbfffd28f1b9015082bcbd9dd7651ed67aad3e9743addb8777b8fb2b81b597db0f6f1f776b3e627c9df6e51d4b135b69563dd1adb221b4333db3ac0ca83b57754153d3fcff1a7fe1ccd9ecc756c8d6dad996de9743add6e9fdd45ad18ad28f9d59644b7d9c77293e269feac3a58d59ff252f4dd8b5c144f75fe6ce89e7f0e9dce6a116ba56835799b9a1dede58976d5b7eaaad37dafd37d1f7f2b0eceae9222cad98f8a238ab23eefee5e183d8bbfe75f47d9eb031dad12ad243a9ee5b9f9fe65bb4db5a76745764bac10ada4d89e696fcfd297ac6f7f3e1fdfb1eab8b596632b4cac93d5a1b56c7d697a93ffddbf798a630fdd2f2ceeadb51c5b664912b2da60e56dff9b1c3f2ba2234a9a640fddaab80dad28fa5335e5bf64c7cef67fa2b12ab47adf53f28fedf8fbe6ea1e7ba8ca8015a1758fe8f6db73f44c4df134450dd616fde73fcf2ffe8d8ae569a219ac66299ead49967e9fa24fd1eff3f1b1148c15066b5bfe7d8e6d6755135551ae0e5ad9913c77caa6bc343b17fb2e8d95a095eda6798ea78a8ea2f855de7be88ecf0bd64da6a3398a67ab6edecfbf47726765c18aaa253f5bce7a15f527e959797eb9390eb10ab4aa9f971c3d4596dce2fe63db43cf6deed38755052bbb49b435d1dfbd48f6d69b3d74a76c5680d2b11ccbb44cd53daa63298ae2399a6cf7bf44c99d6eb5547b0fdd62acfeacbd244554654d933dbf68a2bb38d89f4cacfcace537557f7e52e4223fd13df6d02d9770567d56b62479e77d8fddb7aac851f3881505ebd8926929aaa427cfb43d47b1876ef81d47372b3eeb58a62dfb53534cd951dda6e9f718b4dab39e663fd1cf77f9bd47b9df3d74cb13ac6c2b9a6ddfe436515564fdeea17bf7f23efaacf4ac24aba6e72ffbb97dcbee3ef6d02d9a4aacf2ac67cb9a27cbaaa6c94b13ed1fcffa5b953dfba89aa87ab2db9b12ac1c4db738a2ad3a8e9d4dc57d67253dda5954444f3565d15f8a3d74efe2e299cf9c6c6ccb97e9b6d6726cf99d2fc72a3b5676d6dfd1f31c4f922c77ab8ee7efa1fb85d3d7c7b8c7b6c4750bcd6c6bb4aab3921d1d773b6e2e9a5b6b39743ab90d867be446c08aceca8ee4f7e629b2bbab6257c51eba7768a95acd59cfb634539ffa737b922cfdd943f7eec7c0719f7b87959c95454d5ffadd8a6567d3dfd3386bdf2647d14df6ef555f6ed30aceca8a6d2fbf58b2a5f94b6e8e61ac2258bbf959152545526c3fd93fd94337dbbb3b379b5fc57dc6eacd4af651dce2f9d154257f6aba59c9ae8afe6447b4a75bfd650bc1cacf92ffb6ab68bad57ff6df663ddb944d5bd18fe4f7fda70faca4a9f24db6ada9fe534ccfeec0fad9d12c3b9a7eb36fb52c399b758f244a966727d553ece356d7acde64d37244db2ea6bf77f454b3b2ea3f5bde4d53454b56fd669ab5fda37aa2e5de3c4549ce36b09a62cabf39b61ddd3d4dcf12cd6ab69eb7a4c8cd9da2e347c9336babf6ddd5fec93f9a59bd17f946377bb6ec989ae41eb362602d5594abe56fd3eec956657f997534c59444d1516d45d31c4bbec0facbd68f69cac93355c71325b3aae7998eec48f2912545b154605579a9aae399fa323d4773ab63d6f644798a7eb5ed6cdbc56d8a59cf726451d54c4dae7ab45413583b5a9aea296e93edbbff745b8539d57414c952dce23e49b52c55d5dc27e97929a6ddb442603db999b6e23f496f8e6a473d98356d55f3e4a5d9a6dc8fa8687e594551f4e43fcb543dbb7992630fdd7e47ee1c565ed63365cbf64445732c3d7b76f380b56d4791a368898ea73fc7d677594974a32247376fc7f254cfce65ddbf73344d4f161549b6fd7ccbca9a5d4549f654457224bda96a594d544c7949a6dbaba968965956969b27d9556e92a61596753cf738b2e9d89aa3597295ecdf0d87d5959545515265cbdd72346579db5a5959c9113d49b41d4571244d936dadaaac693fcb342559758fe3e8c7d68aca4a9edb9fe4c939df5d1cc7d66aca8aaaaafa4b4ff63f76be776b2565257fba59914dc73e8efcfb6f1565ddaaff652fd594732fb2ec3fb5a2e53fd5d2443f57b739f26f6afda249965d3c49b46cd38ffe52eb6e5394dd277a7e762ccbf3bfb09e6a6baabf93acf95d58452ea6a9dab63e657fcaf76f611d47f68b672f7dc9d97db69ed4caeef297e4fe7e735144593f6a4dd594e5e9587216edffab5ed49a72b4dc2dcaaabc455913f52cac9d64dbcf9624b94d91f3d3875a4fefc5cdb22cef2df945d3afb08a24ff9bece4e94d72efd2abb096a3688a263aa63b4d3d1f7d0a6b6b8e65d97b17cf91e4eae841ade4d9fb1ffdf9b7ffb4929b64db71abed2fdbb4fc28aceaf6e7688aace8b7dab29ed6ee5b72a727efe716f9f627acbde49e7756e4fc8fdbb326acaac98a7f5447734c4d92e59d56758be4398ebb253bc9a226a79565f9374bdfaa693a92e7d84b58513235f789929df7d4ff74d38a724ffe53f5a48ab6ac886a5a3bca77bbb7498a697a7293cdb4faef7d697296b35d2d4b16d39a96223bfa7db22c2ba69c5f5ac994a72adb7dd9fa93145512569625cf8d76d2f45bec246a69dde6dfdbf3f16cd391fde308eb6659b4b77cdc63caff28f24a2bc9b67ba79b4455f6ff9f525a55ff5112dd5b3d4792775484b53c51f3ff511ccfb6355973d22afe6deef1a3235a8adb976a08ebf7bd2ccb2f9e9f3cc5534d25ad2849b6a928a66cfbcf2eaa68a49545c75fb6eaf669cb479485b4fe93a7a6da729125dbdfaa8fd67eb69d45fde9515454d9d4d1ea47d2ff163dcd12154bb36cb48a2aab9edb97235b929e144f10d64ea6e679b67dfb9434cffec18aa26d7a969f35d196eda907ab4fcfdec59ea623db7a9265b4aa5b1dcdbfcb53fca2efbc83d5143de7edf764599eaca82e5acd3f9e9f2d55cfeeadb2a8a968ed68bbbb5892e76996694a72b0b6bcf5e5e6ff9f23ca96bb4cb472d29bbf8f9c24b7da961dd1aaa2df2cd9b654d5f4f7943db4f2d1ff8eb61c45473155cf0d567473bebda8aaa4888edc34b4aa6da9fa7134cf56fd5d25c9422b17fd39fe969f2ae75ffc845675b7234fcb123d3fbbbb066b6b6e93a3a4e7e4d89edb67b0faf17b951ddbed51f3f426066be9bbaafe6e8e26b95b4f8e83d66d7abf59727bb597db6405ad26f763c9c9ce4f9645cbf48275b3e2489a626b8e64273d6ac17a8afeefcdb98ab22537cf406bebcb7324d9b324396baa66056b4f5172b7dddc9df42a0b683d5bf3e4eae7aac98a7c977f56939fa8f9f9497e544545d2cfca51f3a3a54f51b124ff38f659559493685a927fe4256ff7a6603d5bb46d5154543d2f3bd9f2594996454995fca65a9adfec7b5611e5a8ba4b932dc97d8e7c82f53c7fcbf6b33c3f897e3dab4749729bbe54b76f4d36cf4aa6692bb269db374a96688a6735bf4aa626bac7f6fc1b3d255839df5c3dd3d634bfe8db3b2b596e56e5e5d99ae6eea4696715c52eaaad78967e34d193acb37235f5678ba263bbd32f6e91ceeacd164559b41cdbd3343d2fd9605f4a9e15d991557350b16ddbf4645b92b7aad8a6bed6ec5572ccf2ab6417bf674fcfb2c13e9257c5194bd21453921cd17d8a279a8e232a7af1f3df4b516579fa7f0fdd1e85cdd8d86033207ca6d3b1c1be2a38eb78a2e449961d6549b2157f1bc16ab224cb7e4efaf224d5d2ec556fd6966ff67bb114b7bacbd3a36e56b25551d524d1b6f49c145bb30a827393e856d17d8a292a8efb24535525c77644fff6e9d8fe2f92556d56dec9b6e527db53921dbdd9cd2f550face7efbeaba4cab6e4df9c25d533550eaca9e8b9dfa81f5b3135d12da661aad8ac9c64c7f2ec22697e9e8ea6a8a66acd7a6e92fb12f5a8599ee3f7a7899e15d9f5aad4ac6a177fdbaa5c1dd9d33cb9aad3a53b2f5569d6d11c4db5ef5d8a5beda548f6d02d8f635a5503eb68aa9f444b16f5e348b664efa13b1dc7aad0ac2adb3ddbbbc8fbd8fe911d7bea3ec7336be95374b3264a96242bb29e34b3fab6445b16fd67dbb6e63f4506d6fe39a98eedefa958728fb665d6f4937f8f5de5e93ebda8f21e8a03dd64550bacade8cb71dc6a59927f645514dd5245666d772bb264dbee8d9266c95b34ae4a81b5353767b9dfdbf4ad697a33dded71cc6a8a9c14f9888ae9b93749a262d6afb69e97bb1ccbf114d99474a02a81751477f949ee4bf51cbdca8ea73aa62accda4f93fbde929be5dbe4e569a50a81f5b3dcec9e6c53b1b3676fd7641ff0e79a54aac0acbba77da7ea49aa6d59b6a8ffafcbe71f9b233916a9fab272932dd1adaa1e355bce72b63c4fb1cacbfa79bad5126dfdd93947fbd94337dcef8dea4e5775c0ba4bb5fd6aab96a6c8db3d963a5d97dd9f4e27eeddeeb2fb2bcf39555d56b5dc251f4d9477ce7a7217c3bbf9b3953b45735471f9a36fb978b622f79b2dbb8fe138fe06546dd9673aa22a3996a2989ae2988e625ca56515d9111d39d9a2bd9be327cd2cabd945ae7aceaa6c9b96e65615a8c2b29a276992dd93e5488e7f2c7f0fddc0b8598ee3683a9d4e97ee765a7565445375a7e8b84d154dd3722c4b334dbf58b2bf1dc7afaa245b325065651dfbd8fa928b9c1ccdb44dcb35b3ad37784ca73bcf8aec76555556d4a32c99fabe4f533cf7d9aba8ac62c9f24d6e738be5264b54ac9ab27ab2f423dafefe772b8eecc7b135d696cd7d812a292b99723165ff173757cdbf3bcdc2c47c98e53867b873935551d6eecbb11cd5d424c7921c59d4c73c98c3c7753a9d2e0ad5a9b5ede3e9d5d6e427fb5bd44c7be88e63774bbe99a667fc67b7a432b58a9f8fa867f92fd96ea26c2ab635b615b6b566b6f5babc37a6d3e9743217b550955a53555545bfcddf9e665755f585d5ecdc54c5ad8ae868a6e427a95c585194a7be143baab2a3ef24d9c2499ee726c73d8ae27992a3989225d98e2a99b6a378aaa4498a24a9553cff57dbb1733f6ed3b37dd46a8ae4df28fbbbdaf2cf7256444595a47a92a829aa656aa2e82e4b12dd2d6ab6a8ca53763cf939f6509d4ea74bcf78069250a85858f71ecd8ff6f4f38d8ee6cead2d3d2551855ad36ef62e7a94abe8e979d9f6d0edd6b66bf9f7edca1d84c71ceca1c31ccfaa44542bac9f655bf47f32ddbd87ee1dec5315d67164fd2ecff49365df23bb7be83e5baa14d632ddde54f9e6e736c5d2c7ff2c9b7b86fbf30b8fb1bdae3a7e0fbfe5a702b5a2ad9992a6df2a67c754ece827cef9687a3255d56fa2b0f2ce76be59b69323674595eca13bf5b4a2ac2fd3d63cb73755efd97ec2ea5bf26fd6b7229ab2ede84913d65165d3adb2267a768f969dedb492dd24cff3243bef2df9d994d3caaa68f9772f479635b9d845201c93e9744038f6aa2a85aa8495255b143dd1adb65bdcdbf74debd959cffba992be6cd3df514debc8fa3efaf37bb41cc9d1936b6664a655fd25cab2e7f84d74a3dbec98d65114c7f68be3ee7d64c56fbe09505d5a4d6fee4db2dbab5d3dd54ec2ba4f91fd5ff4e8eea96a8eded25a9ea369b223b97fe9dbf68fb0fa163d53d3644bb29fe74ffb9ad9964e07e43f54fc425569e5e7ee623b8aff9fe9e7bda5b4b268ff27599a7c1cdb54654558cf9144d5d477d2ef1265cddd43b73a6915c9ce6ed17bee55b6fd240f611579d94db58f67dbc7d11cc71eba7b7de67197773ebedb2495a4b51dd5967ff4ff74eca1bb728e6179dc54915691f3b4f4a72992bef772f49056f4b71df568ca963f35477f3e5abd9a8ae97992fc77b5b3afcb7fbb72dba072b4aaa5587ad21c7dba559697a7badb62a81aad247a8eaa69a29c65bd49aabc87eef32c800a847514d5cffb99724fb63e2dbd4e37770efc75a1fa606577398ee647fff85b53344dd383d524bb289ebe8b7eb39c2453d364b47272444db3edbc3c51b2a3a7699a1dac6caaeed66c5b2e8a6c3fd16e9ae6a2f56ccbb13cbf17cd7e8e1cdde21b2a45ebc97bf9599445db9235b937cd720e1507ab39aa62cadbdefa123dff1ecb32d19aaa2d599a2aaa8ea6a9f28f9665896835bb789e24bacfb68fe9c972f3d05aa22c2b8e6a3ab6699992bf353758bdf8c7932c53defff7a32a9aa6a17514d94e92ac888aa82f517397855695b77c14ff589afd8b26df654968f55d55cdde72d42cfdf855558375e4a479fa8f9aa7df3dfdbf2c335851d69ba2789ee9e6a77a6e5e9618acbd6c7949a266dac791b7a9690e5ad54d96a928a6fbffcf498ef6d0dd9761b8d747312a41e22f7f698eadfa5b53645bf4822da2e8998e7b9ba54896a838a6a427476ea62cfa4d3e9a68dff18e7cb9ebff6fef19767381a6112a0bd6b41d45d6ec65e79e3cd9f3075a49cfd15d9e2acbcfcfd95304c6ada9bbf40c5505ebefdb3ccd6eaa66daaae56906d1e94c71ef360a68155bb54c517554b917d5162dbdefbb38743af96443f56755bfea51d314d5d22cdb3445fbc0b8359d2e6ed6e9c4bddb71f3289aa1f2b3fedd6e96444dd32ccbb41cff8643d567e57e1cd3af923d4dcb722cd914022a0a5692ed22da8a22fac5b1ff72e4b39aa53f45f68b7def6d9ee9ebf6769f892b8b6ba8f6acfda76d5a7a76a79bb39dede214aa09d6933d473e72f4ff132dc98df6d04d8a0750e9595b3445cb51f5e466c57e8aedb983a46354795672b325ca5b96454532dda9a8d3a56745766e547856b64dd5321dbfda5b11f5adf7706ffe3d5c60586a874a82b5f56dda92ead8459feefd4560dc5a581eaa3beb49a666da9227eabf1fc5967b33b8b5b6c59ac12df33c2bb2f35476d6cf5552355bf344d93225c9141e3be950d559c9b67fb11ddbf2ec69a97e332e9edfb5a48392dbdc657a8e2839a6aaa8a6272fd1ed5b1655fdc8921f7d61974ca73bb7dbdebb7d6e37f202aa39abca3dcad19645d3f26f74a768695b62dad6d896f8630bcd6c6bd5e9743a15f5017fee49d500959c9555fd3f457393a7bacdadfe386b6996e968b263cb929be42a9cf5e42389aa9e731645c72f8a3d74cfad1b7b3382b57fdf493eb62adac914e5bc87ee1f1cfbafdb5a33dbdae58e80eacd5a6e3625c9ee4bb3f466f9bf9b758f6a8b9e2a8b8e9bb3238a42b0eeb3fbdfc5d4b7bb8b632aaa56a8daac248992e9e6e617fb886ef23fb09a7c3c4bb145cdaf96ea59f683631cfd03d5743a719fbb7f37520e9503eb1f496ea6243aaafb34c9330d4da78b61a06ac6331009159bb5f46c4b727fb29fed7ea7ed9ab5a7db973c4d47d5a75d24bf9a755447d64c53f2643bab8e684fb38a7d3ccf5145cb334dcb9adca844ee6986438128c961140641c4503215490040013312003030241e8f074452c1a438673014000365a6708e4017c9439128876114c44010c420640001c00040083186ab5a0d05361e53843f9c10aee8bf507ca0caf01f49a5117dd4f12df92c5be91f602c8c582d998327c61fb9b79c3b9eb8fcc97426d0eace2b6c4b0c6e8f31d7bfbe6c3b080003f3c26eccf22662378b7d1dec57071fe1c879f4dc7bf38931bddc46ce2baa43acca2397114c47ffee4e950879eac623aa58349b6eb24c2951f99284bcef1a318b01a8ad417d2bb5f9347e16625e8c40452c1685321d83196456ee94180e58674d5aabd45bb7a2956867ad4c6dad2aabd7aca764c5d78a5899098f6eaccbcf7c3473a7edeb77d08e96c1729207632fc5c56bf2465e677d7a2bb8b0467eab7a62efba8ddc835e636b3e0d62d9a00c8d3a8c1ad488d922987cfa0943a96176158fa84f93cd27f8e671178a9ae3ff5036fb88df04460057985a851ed36cd34195982afc04f1306e51311e5a9542bd5661392b4787342bcf4a199da67daac0a8ac894c2d5a26d0df2d9572636bcaf0c6ac01de201595fba8160a1933795c2a10c518aa2d5ad2badad132aa4c6ec262548c8c64b0b302edddf77ced782babfa2e08d9888316e248e2ec9a6ee73ca2f53f2ea9c182a1e53e467a11fe209e851b8e97cd38c9608d91589ba16035f76e43675605c91468e2c29427fcf1700da2c32225d148ae96b19ebb747447ec2725309a2cad99da4e0c95b1a811b60cc555f189c135db6077ca6ba9a108821587af2838ea30065fc88c34b9896bfc4fc5b3c39483a8c26cbbfb461b967ec9010e494046d9e0b21151f578a992d05ddf026165182b8c0db0fdac9a5716f76505416b949e698532ee0fe56b57ba8d36151cd04a563d269a73eccde91d0831f86f2ac87b4b57d48518c2f021bd96ee814cbfc4ae8673525cf1d48302373d7aa79543a548c2b2de03b08f2d1bcc8b9a383659803612f590e09849972edebf867b1c143493246d615530fac1595de0a6d05b6fbb746dee7fced73e9c127102163a13ae9df3af9dabb32cb691b5a37773a68fe9a17b7001057d33752c7871d489d1d2c4477f9885c64cf78ee121b83c313d32b69115d8395c881bb42ff90952187e5a1095f95da0b2a11e2291d10ff84bf953604c3a303f6aa1e93300efe653ab3459372d9314c099139ae5b15d28a2b85767d3ab3cc47a92a502ce5e85156f4287ec1e3fe47fc339e31a06aa30b493016361c837b20ca5f3187637276cca8979941c713dd9c98a4e818a60184055c4df3bd4ab3c05abf8d2f5ee081c110d0395ab3c03793a97ac2f1d9bd344db48dda469cd7fb1eecb360e96585b42b658bd41f9fb2285a6c4bee2ebcfd915366d2bab6067802375715ced3abc9c8a1a1a54a4d714e07b063e3d3c0f6dd055820b434300b3a5c5f542cd937b91bb14a6894303f3e103fe51f34f2c9357173c84d471d0a78973aa183783a303a2a639249bd21099f70934d4ff7bdddf310825ab86c362971f32cc39644657888c142376c213dd0c88e3de4b6988ca4627062bb7b3a85a055435e59c011da66c8ce9e96826dffc7c2cdfc7864eadfb33e60de0f613b461415bf25653744642f59de1e747fd35cdbcf4b7ad040d2c9d6ccc2f5ef9a66098c5fc13b9ee04d812d8a7be58c72f9019431635e4f407683f2bf46c31ea5d22f172400055cf28b24543ce7dd14faec29a00fb4973497d5a2568a5de69490e8ef7fbe58b9378d44c29a85ecfefaf9234b4fb87f834ddf60a8b24dccad038e773b6f1231e1ac519d7146312d3112548ca7407d88c2326584c263a30876ccb444b031ee54ee721c346f2463dc217aa5c85fa92ea1bc76a4c97fbc110adc7f10db6be04003595e90e8d363c223fb47cfae3cf972a57171fb1c3a717d0802a1b5a99635e3aa48ad3566ae62c1b8e7de00362ff7d9cc3497ae5d8a7bde383fb6b7894c02ae8c878fbbbaa302b92a540512b165d4a1eb84aced63a9998293aee77ae7ac0b581901085351d9ce4e22d7d76bca8ed6990aadad1e1452da0852a246ee4ac2092d2f009203d07685b457e0f902419f84f03c17702d1d206751844e60e16f328002f04f0c5f1df2140430b19e7f1513ac3ad9be2268e84437c1b01d8df5b361bc5d59b7511e2eed772454728e59cc10169f02d882383e5593fbe6b30770c648a4244adc064761b6f4b88bdccb6ebd2996151191d74b2f30a0419643c3f00390113031698a67f8fe1186266ce87d1ca2980b8ad02cd82514b72af2356db35ab853ce916e0aacd254b954565ab2f97bff4707f4add083466b16b7aa92d070cdd5a95a3da058a86371d9cd945248010aa00dbc5a353b960c669aaa21081b153e0cd40570dd1fbd410c7fe3a0b33a4a01d91cba86cd100fa9273a51302f5bce5b602a78a234bc879c019a32338cc1dd1d7a683a110a4b8f1d80f877b7a5120124b5c1bcb6839314785700479c56043d3e9c427c2f638b9e4b5ddb2fe11c738575ec255fd0e79893062df3bdf3441ed339aae44d2fcabf561fdc647e83f7edcb8526d420f24419f2d66daa6aa01884f6affd91b4fdd83b02c5035dd95095a9b82f4075191b86e267de4f0461b88d2a37be567c53903b7e362928d90b1d6d8f02eb06619e4308f693dea4285dc98174e9004b234b24faf89cb408f0cc53ae55a2294c700c1b1bc63220bf7a6d3a99ccc0b370bcd742616d52e5a82786fb11741303e919c1a8d48f088020f9b836a873537c3969655076a36f2886dd1cd624b34c4a5a9ac2a8ab291f1d2d6bdc665d2890d382ded57eeaff726b3c658bc18662f1bac9a537f6313d19befc86f22909c9ff73958ae6989636167a17b94f8cb38fe4d782264ec5429f46304f5e41bcb41121a79ca165d6182a79e2471d97af22591f7e0222a4e1d6d0ee2ee59eca2c41f264e8e62f99578c1efc46c7bf1d470af457eaefaeda2931f9a9ea80db199f8508c0e75c31c92f41fb54c8514271175639b200336cb14650e0329161b86157654ea18429c7f8d289871eaf89fa18c0f70363675c4fca4dcf0e0694df42d09c1a637444104520f842584a3e975d09b1003463595ce3c4139fac1744d445ba6286934d890201488b3a77f7ea26315ae7bb907d460ba3a73b7f28d27ad56e1be974340dd4d7133372a78f544f72a1cf6b2078c3695c87c55b9ee0739d4e8be851d8c454359599157dce1fcf71aedad158365356c9d1507c5ddcf7fd768dc5a3558ec864ab0e2aeb87df0628dae5bb807cbc1507756d88a4b9eff5aa3fb168660ac1bea66c55171580fafaf2eb1ff7b10d94648b2e54aa1a6dce9540fd4b0cc86f32890968352204bcf7ec9340302d0f3332c7a540b6c3b40d2baa1a32c32fc4998a4aa5bb5b61261045bd4b6d458a3d192e019f1415527eedb876d09943708a7d4500dcf0655167f2d89d5a860cecd74012378caf1295f4108fc8ee90c5e238400a8ec041eca78ad587d677c85a9a78b624703fdb38de6accc1a5f2280f29168d841ca79bc4c1e6bfc8429eb4f4e34b08a71fcfda1071c83857952c8e108623c997e2c1466d3d37866166cae002aa6e6a4820b75d5b58ddb90467824f0e3186ad63966dc7b8ab77bf42807a7347068eea1c9a5a000174bc8f6e70fd370ad7f2b138d46f36449d19568783ca308f89305e21105e9f8225164c20422a89b0f05e14407a112fff8aa1cb00667f9f668614a91002288ffa6470a3def110216be3968655f7bd59bd2e62ced49b657f56c9964f2a6931e53c4445e228352229766ac9b2d48318c43404551651844eff83143b7890a8c63c53e2c29f3d82c7407619c1817d80140539e4396136734983429a1b2af7c89d2eae70a6aca260dd26790ba4030f6f38382c3361c58063ea0c050e393c8997587d4903d00919822b96400b4197e72639c8415ec18c3efc5d8c546e8ce5bda5bddc7d2897cc1791376c1eff23ed8ceeb01194844b70ba76e8c1238a96575f7136191c14ef4263b2c46a0793db2dee93527b7f1823d44d187510a5cb3d368acbf51231e265efc753788b8ad77893768ab5e751312a423f1e752922704a27f0777c13d4298d31d632cb933a399007fcfbc6569cf2c79eb640c8d9f9448fb5a6de382d58949727b20bc5e14acc71816f49063762ba12e6631f1d37f00351f0910db6e71d5233fe33670b52276c0d2cb56828017b7b0eba072db75177a138c6605afa378845280f46bd2ad28527ba9a481a9461a1c0440a7a1049c3202a8e84ffa0b5e5018d3537a10d3a3ad2db2050b25f91322ce5a5d712c6275c13aed7ea0780b62fd2e1ebfba9cad0a314c409deab2723d3185ed256720ad4cc7b3d6b9b251750061151362b310bcd56b0fa9894b9871f0ce6bb21b5abc8fa037dca83c1ad1deec20068d394557caa8b904fee8124c0448bb63aebdb2e34b001e61458c8227e501902989024d83dab078b2bfb2e88f9c78086f14f4b4ab33a8d6e9261906c9ed811eaaa41988fe94b51373313a06376e93a8abfa1ee4365bf6110611c68db867c7ceb90f30ce5341d2f92f9e81c6e5350cd45505119d230234f4077373b73fa0d99c12c03208b036b4676c66ea5f07195fb7bb2b02f684e088b404c0961b7ccb00ed3b9675cc13e9c24a57028d36fe576e668f72e2a59f12afabd8cbbe9e3c4b2d6ba7fcb4f1433f3dd2bf6f29fa7a693cda540dc2baafdf759c6c0c6ab7806991fa4f476efc6e7299d48432c5201cd6244090767f6c01a456eaf5d17d3c2a93b27b632e6bbce55419cbb8023e72ea9cdd2a7ac75b4eff0ee5c33c66031a3f20fee7c55571cc95c05b397b35028e091969998c9bbb3e33c570ef66039b13f2b8d0c77cffbf4e0ae8ad2532d29d95cff21b2be801d75af7523a1eb8f4410bf5f540e826f5c97969082f25b04d3131441b2ff0d280b06b8999ea3bee40943df725b4b2fa7ab326cbb6c467d2625bc0bacbd80c306c5a5f13843ea2c616aa45070de0a5c22d064b15f5cf1efaa46f6a4669d0a0a74314411f81085e2b43da132b423419fa2ee860f40aeac9e30295522df9a6c61ab8d25d58b4a63934e765a4bf974302d2b7387234f6c327af37603a336b980e7a6a87adfcaf7b71726f0c49e94394d21d220a2d77919437a1ec845a0e407f421b40e93b9832a84d6d4f0dfcac4db1bc2b320efb8635a3246212efe67dc9a12a62d9bef0f77a53bbbfb5ffbdb981f8cd295460ac84b89cfe1f321417700aa64e40518bd9a5a98f55b5ac83be17d2d676c89b84034262dfab2e8ed6626265acc5101ba6ede9d779744ea4f8b61f5ec9173b9000a702c029ef3738b8bf44a6262d47fb42a46a67e44c215ef044e80f1fb0f3e942dbb49a03ff64d2f74dfcfd5619c7070c8ba90d327482b394820b0e6f03e46a62b344f122254420809febb64adb6006a9d6247c98e3d096b7e3f0a19b79d1b200e4bd80f8b2995f22136999325b6469243244615bfc39cd18790137227c31d90e8fc06043a6a3ae4dbb9201407d5262ec9c864a540facf57cbc480daf17abbf0cf04fb2ab2d8e6fd31137d7eb223f5adc6a0b7d84cacaf2e0bc64e5e8019436f8f78c42a56d30d68f12bbd45162c5f90e48cded5c3d33c73003980621c9ae1326af022c7a6e8332d2cd93211a76978f5724d6cc5e573e87b61c19389b192409998ade5c11ab24e58a0d9bd061f33873717d68f91e5ac4a192bd760e3de4a8ebba59a26e51ff83eca43f6a72f4b8e6801239571030087c434ec917484c9eeae843359da8a033d5f4aa8aeed5e86725fdb3454fd7065230de65480dcbeb42f7014350561446e8f606189e3e1922913defed7fa4fde50a29b0c610a699ed3105f1970e5f092f0721cc638f5e6e2af75dc41c2dd501510178f8ee1a90e7b7e9f8e9bd35b17dd48be7c5eb162c24ea191223c5d0e697665f589c9ffbfde7fed1eefccc844c7be6d268e03e6497918d1cdd6cce1b2b113778e274088f5be5fa2f290fc8f5c3180833ca8aba364b8362289af1daf5176799123989517c513c6cf0891d8a111a0c52b86401210ef8b81b9337de1ae7ea87914c16c13c40bce9e17836beb21f8d74bce2e18acd0cc8abb9e91c9c214e57cb64a8b354b82244365c3428da9584823edfab43f5cf1f53d2530804bd2a2800d841a086aeb5682f2f1a2ce505fb0f12de66ad724b6f1fc7b4f28829716a1f22881ed6a44d4e829218128053f163acfda1d8fb6d3e1a6d1906bac4ba96ae709e937d17929dee24c3a03e94cb42716a0451b77d7ff1111ca50d711f639cd1148545e988cd67a595e0d304e5a8a29d502e763dcd1ac4d546e7eafbe927bcde2dfa6cf2bbdbd1db927561a9ac2ecaa628259c596c1465c0c3c3e4031fdf00108698eceff5ebacf6056a1425573438c03ef6dad24518a342b1cd8edd45c386c267922be47ca06877efc17fdc11790aa51ca5b34404fc608f52ce6f377f98c0764babd9f2582ea7b18c84352d1226f33c15877685a0f21bed1f60dc733817855e6dbc49b4a196048d4c73b9a4c295d7a62706e8d243df46448b045ba0e32709b83e5999420e97a6be52be47a819c52a972d057e6e00c2b8d1c9fb8fb32f590dde697b1cd44ca334e2f6341606ae9803a945bd02cde6c147c3ada9ad0da72517e7b664303931c009b6342e47e5cfb4d9134e3e9e15f96ea5d053826943c55b008aa20abe8d4660280c2ce66bc0a3548f0f1df65aa5f37e1de79f2817664ddc08f650916887a2e0bda033e2ce6b804e0dc993c7bd2138122a52aa284bb950420a6843e04c01f927308c896c7d44869597c44d590db9e5004e1f435f26567c53d26f44673e9497ca285d460f270d745cd7a5f948ccd35c17544539c127d52c742c700c4c486222cbf966729c3c20b4c4324a723964e304c549b42987e07b5f360555d8b220ec749bf2297912d737fdb5c405d34eace74e27588acc6958acd737188f7e0688da3dd0e0ac4e83c24f9cee5f5e67d06245d5d06d6581bb6cda985496c19e5a58c90a5779d23d5cfb15b83e4dd3574e0c85416063f40ff2e24ed3e3cedca316aed1a2aaf00564d0e6dd7a4fb92bc88cb2ca10fd08ee15c75bd56b1e1c2bdcdbe7142a083fa101f67e34f8746d04f55f6302324b2bf2fbfd4c412e503db925d431c8153fc8f866003230ee41337e0b1a0d8c4aeb46070ee699fff7642beb02663685946ba8d7733c81c48650e1b82bf36845d0cb449be3eb4ac28c7e2cce811d29ce3e4f7f85914684f8aeff8e3619fd0092e4c7678487632c1d7293a3819ba284c850f5567229ac24213b93844b0a1c10306d16de1238a04be1fae5dd4f11ba60a6e7c6f483b988651d83eaaae716425f1f0008d48421dbd5417c886b30e513baccbfe186f533a2aa0e6f4e95c6908976590606ec46a6795d0d2d95ef135389a850b52138cb98136f672471d332ea07d88cb402cb6363c88af4c8c56d1d4910a379f692acba651e0f7173c6509327de9afe1a301b910156a2f02640dbe4ad206e8788e28cc2a6394b8227933e0a7e779fbd252be41861b79d56d5b5ac203a6e4c75fe13680b390c1ad2d1ae099a74b4bb6fa5a35d695046603ada0d0d1d6d9dd4ab819a8e76773a1ded5483d2ef4f479b52f01f4ad4d146278c72951a1d144f813ff6a1ed0d863fa73adafd0daba31dc9504abb3ada5de64a59aca3e51d32c6c40094e419efc262eba8eefac57554b79f761dd509a81ad51caeb5ea375e2e1960479b43a14f1476b4e5a38a295608838f1ded1acfd1d1ae0a293bdaedcbec68b700e475b46db97b087f14b4f9d3febff082f178835dd9eb808eef49a87480e7dd0cd33effd0ce3fe1eb7f2f165602529fada025bf1716fd970ec773c82f9cf20d80b57b5e79d7c3de1be5dcc25f5f93c157a0977d80072d72e72caa43636f64f0710012856b25a7360ff47f23d4566e98614a75bbe15257547690860363675e6103a5b92b903f3e8041e4723addaae29e59990c3f3dc362233f9536b53101ff980ea088b8fa09548f11660d5fa17cfbd8177942fe61c9bde60fccff35b28a3720ff0f18755fc770f5a1d3ef02ec951d8c6b3981f535c33c5b3d8ff8608fb0f825f0eb0b523d4f907dac72a9fb83ff6745bec215fc9f1386eed7b02e7f68f65f0075bd27d1eb01c9b705d79a7f70ff57c8aa5f80fe3f61d2711ddbed934ecf6b109776f23d1e217c59e3a8fd03ef6f9d4cd53d88df739a5d6d9710b44484db47db2c6aa321db08d8e2bc96de1a3cd5d0a1f6eb8407d33aefc05d1f216a7f02e4f11f36659ee3fbbb24a7e13f8e57f3cc7bfc4239e78ca7ef1bf09efef9b49d8fe0f79f4d390ff8ffddc86cf213ffc52ce6fe3f509d39e1d7f3169c673ffe6dce21fff1655fce139cffd34dc3491246f140f67caa3bc3682462e22ff63fb411abf800d8d74b514719f3d6b333ad47e77b5c3f0d4a511afc1cab756906027f46d797773867c841dec5172b980fc0293f78a8d0fa95c13aae2d11845ef6a1f9dd041769b939a3958911946341edbf1be638c6030a880e668b41674da9f441b67b8acd5958c267351de03da716446547c36210fb24ab1c2fb29378ee377be43ed86efa1cdc21d88bb6d32e8f9785d3095bf8eaae9a2f42febb1302bcdb2e005ff26fccfb2391b7bd07ca9e51b94e80e7016d1174d0c40ebb1aaa37f1a1d92a5fcd3d0babb0c9667dbbc9d28b300e27fc9f5010cd0787bd6506d834a84275a2f7e85f2c0f077b1f77e8f70ca48a007a84499a78f75d65c0ee8c5de8234bfbad7ee6df6ab9300d210e3f5540ee841f2362a8dedc24740b6cde18e84ed5f95d78edbaf54f3be571e23f97a9d4227838d5cfe0eacbfd938337921b1d851791f582f9c303135064926ab7115ccd2670117aafb92657dab0a42e267d5ead7f7dcf2c86a4e4643357b804d4306d0a82d390f3aa7164c7c8cb03ceb0c176098abc2bce4201b1d3523b3d4dadb270dd1e4da1a64081ef94d08d824f64a5b4a13715ec2059d6d4f15132229cbbd6fe4ece02413c093d2ae9b6c708f25c3544b48193830793fdfc8eb33b2d1f7d07d03cd91acbd253c1b571491251d4dc23b02359910747c3ac42bac2872c88db81faa3ce5132b20dafab73488a4a55c41c1c0bd9583c498e013c5483d6c89b2e6894500f6b28002244f889f6b5328dacdddaf90116c79c7e2b28e6b9ae55b08e7790e61e6e4c92d28eb720f21c79979f8c811fb97c771114dceabd23dbbe7fe07d0a0a4f985b70d9f706bb379223afbd61bbefd77f6b45ca65dd6d4c95bb26b30a077cf535e6b73ef3ec471fddc3032cb93bd2a1e01679047a73c4ab9e39a8f1d975e9437cb6f140dbe31d2d3a914ff4b319fd820c38537d2ca01d3f6fc0450684d283823c2bb6883297911d9d50ce3856b3695b0db2744755b2837a231c670b2a3ebad94315cba46aee89e6723e81d370026e45e2266c2bd5823ed3b8ec602ee62bdf3a0e29cfd948b6c339a4cf381033189ac5dc123c003936fa40f49052e370a15f1310adffc484d56b8ac2fbb2a2515459747a82ba68fd353263f017e971780a04045584d02d44da03a9455a7f3549be215d327a4d9c9cde0a050c95c20a99525ca91407a912122b4ba895cf72058f5e812d58da2896ae92a557b34ca06839285bb0d02d30854bb972e9a4d22547bcec54bd1c600183471a86349431cd49997ed0325d6a664439f3ad67101134d08aa64cd21459d344889a7150354f750d5661c34ad9b49136a5b44db5b81958ddbc903780fa0698c06953381d9638891a679ec8f9ac72d09439589d5350e8b4c10f171fc48e6ab5c3267740d43b6482a730c55348f284d23c2b44cf47d58365b20781f02941f974903eddda6747fc1caa7ed0963f34f44f0301544f01b592400b34d0b10802860a82c8a0be3aa85d2114a0846648a1335a081e31045743d5caa132d1432514d18624baac89a089222e55d45216f5d6456dc268a7323a2a8d506a23007154591d552a8ffaf5d1d08d866320632816058f22d07daacabc0d55f3413f1473fddc96988b3c2789ce673ffa1d36216698b0d5f4fbb5157a1d061d9bcee327393314f73ec827e710c80cfe1966d8d381dae5b308c8d5a3a6ebe98319d1f057e1c1f88212073e7691b31b00af0a5031785c90dd81d56faf3346dc822871c7cb8a8f4a83eccdb5996f41dea84624ee6ae3787dff63bcda18e610b9b603c3597dfb1fabd9f2b384f20e9a7ad7d5fff834af7bfc8af4ea6d41777ad7f4d2cef4ab759e870a1497534d182a7123bf8e3a36e0bf3bc8fcc9dd43eff34214619e7aa558219903bc96d52d5729d24dc21dc6a066053dc768fa67fa467fa33afc16c142e95b07ccffa590cd84498a98e128974e9b4339e1c8ad0815f3f6d2282be9646f123863190ebb68303cd251c8a718bdf0dd6ac19587b93d6560de13d4c9e60586f03afe182dbe2d00965d9ae8e9e5b5758aabb22afa83ef386e2963ff13410c7b9225740cc5705c9f878c231ad374d7724e71c7fa34cff01dc2bcd19114549101dcdd8c7cfe8cf5638c19e62525b197f12762b702788cbc2e815905b2688080178449153bbc536b1a807a60d86150d4ba0461a7f337abde5203303a78381866186e3ccec8a0a3a323386c080361b0fdfd1fd24216fcfd17fca4a7871f01d0b615c65f3d0b2dc751d630f9cc7edcd3578ef3f117c11c1ed94a4846256dbd430582b3c587ec70b071a16c8e9aaf4464a490f0ed4cfe3170342357bf10c0e83f4ba94222453f2e91e4cafc04c5ba486840d3c0370629380f85a37cd35bd414c2b5d9e1662869ae7bd752d818d4e0790757e03a6758ab4d8f6d8f08381b389bea035925ffac7909b49606c8cc8e3734fddcd8ce8efa30c3c33b36e2583e5f1bd0b09161d8b4b624aa0743a2b92582886ba80366430394460f6718ea1a16c36bec8702e21f324f6f12985b890b05c57d81bbf33721bf19dc0f40c6509d15812db5f9f00443efc470259b1b4e07c81caa6b7560c1e690be949276d27b800cfee1bba8b544bd349afb713fa7f925c14f62a477b0797485a3b15d05608aae24b069d6d3cef44409a8022c4de630697744cf0f2d6251ea10d71b5c4386708b81b28c167615c5a04e989e06aa76e30d2bee6070e7bceffa2469e8673b1579b64ebf26700620b8fa199c2e4bb8c710481e64d8af019422aa6fd632922a44764c34b4bea9262e106b747e5864d06f97802e26176aa232fa11861820201e80b7145e249d20e4647ffc5772f9571a6716186aeb6af175e576d3eec68ba0532dddea9d01d5e4a6e0df624efa1e2c550de4c9d117e5885a569c985d74e47a9c47bf49985a2e504ec408e6437e73f1c379653d6124251ad8c675ea4a2a0122b292c89f101fe0428665fb2d644985b65d0d9da5710bdb17b9636b192455c5f31766d73f2b660c2558727e0e8ac4df160d437709b4ebffe670ff9154651cdbf8f027fa916f720b15ee70af4f048258b3d0000744404ac919def86da3e9ff10615c10383b960037488fdec3d31c70b33cd57a174ae3058fdb591ddd3b77d77d31a1a317149a3a532e988835d1c82feaaebb70135bc534c6a4cc98d029cf65ded5a416f1fc54d06328615204df8702c725534927cc45f97904e187adab9d191a22d5f9082a7df99d77bff435fcb3741b4c11837b1139a78a4884797f171c6def3caa6ce0bde13c76f3c3fcbbab869383565a3a8e5cac8071bd769561c812109dbf301d2c49c3dad06dfd575c7485d82f04f4ed675df049dd43d7b7c40b6851989e96b89ab1bbf193cbe536dd490b275933b0c1df0d3894096ab0dcab671976899103f3c10430b0312f7b12201a71b95dd113fde0d9b42ff93ac88a9f3fe1e468963ac7b57106902c0cdfb6865180eab430896c04c23396bd0dd7568342cdf1c075d232cf6f8a3b2d272815dea999e458064fd5bcdfdeb431cb733871f6950fb94ee8927def3471789c8599138f52141d314cc5011f637eed76fc36958d64094bbce42d63246f2d1b198082536d9f4870e008666167b6859692f24d2333ca61ccf8b9b5ec6bba54308b9362d673b6983621ae6fb0b6e36f9508eb8800dbaca93caf4ec044ce6b05bbfb980f1245e3df66d979925f1510acad238bd64cae88c401087894075a0f9ad541fb8e3a9974769f785120ce681d40957d03d37d7e1ffa52ba4514915f3c7801496803da7dea268463dafd5af986b9b63046aaaa21d0e8f28686ccab2c3094fab813b24d944562a38fba49722cd1748eea5212038e1405d9b4fa3f7af059079b6712f10573b4773012db5feb34c55924d9fe4504105ae0b40a039e260fe7014734058fb10e8af8e36b01e916fce9687ea93b39bd7738d02e0965f36f59e488fac31ac2e70cf5a71be9c471374adf830dd69edfe805444305bc12751aabc66bb5a63bdfc54ec81742c1ab5193e1890467c3793eaccb98694bd68500e2cd3a0958d6df54135d18f967b4451c3c976bfabecca57ac2c7a6413a387913777960c455ffe0874fdaf15681f13cc3f712a1118685a32dcee5bfc8ee63a04147d02df80b58739b57bce7afab4fa7ce5079e3266c8a960826785d3c4495a05c7c8d26193c85648a458ca0b4a6c8228f4e16b90697ad475524f5fe3a89be3f56bc5c33ee9fa5eaace52843173e81ac5de625a4ef331fe6b36952266a172505c67de697e9ae734b5f4cdca15e42b8db23a586d12c51b544419d3587a59327631e32ad491c327863090e7776d2e7cc2c5fbbdb3821452af833292739e14d642311b27e285772e99e78f2686382fcf20426b855bb8214035d0a187d11ef83f10728540e54999b9b735d01e3eae0a4b8b2ea7808ee05d8e16e8b76d61c19eeb41a9e67a64df508fc33e24fc6b0de9fc419d116b9fd41925ea5be15aaad30001cce701d78abff05906b81ae4757704ea5ae12ef325b0390573b62f0910755be074c2f438e1fb1176d32b5833c96f25c0372ef27ab28ee410d022b423d04e946c4270feff850e4d9c711b44e7e7f46a49dc2bb1fdd655aa56ca0ed56189352ac2901879e4ec7b384343714d43b6519e2f8e776c1ee4746fda8ffa3216b2cb9f5de70246f45c3aee32f6ebfd0dea473d88bb4c1d70ff52e5628bf76d1e0d9aeadeda462dfd00d96a4f1e8cefe701361ff908189cde14cdef7da7a9ec798a25d5c8d07fb366e71fdda7334eb7dfe8edddc1bd95309c41694dc7d6517aff3b7dd41ecb5fcbe0ebfcd9153142f0621c8dc4feef8386588dcb5e7470d81771d8e6a03c8a4006017692b5e3c5b06650325c933aab45b0836825d61abe89010e64b843e223796df599f5ecf50c2abb5f566cce081ec22b38b41b0dfcaa758b818be45d69d47ddb5e23a05f0989f4c23a020fe6b49d56c81dce13462e74e52d211dc23b9b5f64bcc1d4c5f743b581e1fb03fe0cb1907cb4dfa3f390d92e0b3453011c53d70a61bc989a73d4cab38dfdc612f3343dc04ce44fe811cf28787db2eecf5d19cbfb62853b9f0201d1dd07f32c9af0aeca7faa916b3001c05e5af2dee5f14f3167f4a0132ebe233d18f111334aef0faa5ac755b36a81ff9108aef84da05c27da2b91169d89187e171948817c309f170ff38e3ed8f59d76491217a3126e4408243574cebb113068907bfb0c84155f911965bb2978a0162f9941a0fbe7c067ce0ff5e896285d3f827d026267388762616777865df1eee0ca4401befc2a47a07e8b387a2db68c2ce987ea3443c8b39cd4e3b46308b67e9e8d927947009ab692380d5defc0b4863646bc4f234f2b96add45d0663bb3b09099be9d18f4916b783e9007e83b883ca3b43f00af2f4107fcc1e97b9bbeb196c22b73b15bd6e4ce7d11983eddd9424b3a928083c0a125e2dc9001f49b30a1c50c0ac6f92b5bb18a9bff39a49c63b7a52380545a5db1ea2079c28151282a4f8cac27b76ee2fbee16b8a9ea1939668743c466504219e839817e4edaaea4938fde9494388dac996111f17e0f56b47a28c98cfa861423d27597c488fcfc3608eb27f9dbba7e9705f0093ea0c96a28085dee742cab2841006554fda115591fb3dd7751f55f4aff4f09d6d14b153f7731926e59ca451a691cafb253b3745b598ee8ba2386cf5cac35a4aa0a3102d60e9c503c48141562f0cfafce9afbf8c605c67b45c8646f1cea985b47e28a4103e2cb40ae7e5316e4035662cfd049e4db8aee076d045bc3cbaee863d37f8095a0c8faa436e3eaa8b635e96fd26f5ab01eb3b96c2093ad2112c3ce97b6fa19a5a8fa375d16b0661440c42aed5df3dc927450ec6ebd4bd5fe8ea0bada96646cb964ddf0a38815b789942325bf07416a64dc56bc88877d0791dc9cde02076194fa2f59cf1057899a4aa26feefa5b90dd47362e8c303529546866ff8253f280d903eea37a460f4af954ca36ebc1feb592e8fbed1915a39fe70123fa44709eaf9e497c97525173718a0a95528c2f775c5a12c6c76e01a157f6b8b327919d0546b20b649b53340e07ee126fcc12466b82ae0d0c0c65535929d14dfd5ff8ccc2b2aea012d58fbf50a6b9d96817025c24db0246d4e303add08236b1a5b4ed21fb9f6d59fc6819c62ddb87f656f991b09a964d583612a9083d79afb50a1eba84dde5e9451cb91d3937eae71b07138a920666348375faec46a4bb038c08c481b2c01ae6bcdbf6b01120ba6edff876b9db471b79549a8376d2f1424fb0b86df8ca7bf80798b95149d9b353c4d11d99a5005469fc4dcefa4e0f27c95616f4af59ec29ca12859da1d6aa4e24f27f9a207e9966a5c5a9c60a0f87d184c8c433414562472256dfa8867d19b2778cad20d4452274961493bdcd32a8ca6682dd027254fc84bdb259402910e46e1eaccbc537d62424a20691804d0c4a7d892c7f5501045ac82772291a0e43875c7a9a07dffc0a2737476733b7d2ed946d4f69372dccb208de506e198a8684742cee0084cde1b8c04c0ebb3016ded9210d5e600d27286d45a08e04167ba822266c2f2146add01d86896015b736e9b38e77cf37f02ebcbeb9187980b953d6101cc37b8a3066ab117b0891d93b9738d972ed120901fce6b87fa7a5ea221f96fd7209c8958ea42c7316842467500382df7d663f9cae2bfce4f48d728f909f6b3a75a2313d0631abfd0c5ad36cabe6bf2646bbc8626046c4cb66bafbb2247207fc2e39e9903c35394120fae136f9c2f1e7306f49d4f7300559c4f7b3d50a172209a13231a78b2c54ac09f30188dffe7120832542bd8afff6f9a028e6f18927125b201dd144e7e70fac6953d0b32aa57d7623d5b315b35ade567f6d0e86dfc366d0a98319581c364f6d37a28d04d77ff39b700c92de32e87e8a014986f66ebf36e112b4a552a26be400ef0bc4942b9e9b629753fb8a9e45d1f11e6902a2bb103ae63762446c3caa02add8f3197d40b7867516863e122fe83ac22c9cd4a456335341e10ca141d09fbae085a6b3a74aeae6a1d22d159e38b48370d0299503d7805f666138078945bb20493a1beb80b778a3cf529d66aca5d98e48b34cda3a6541499aa42b737c4340dc46784cda2b01baece72485c260bf6e1b2dc1590912daeccb1a9e8cd35b738dfc79606bea1d4cfcb9f2380a9ade4c954ec307eee91b9492d68dfeaac45a38b59c8e620ab7a88c620181f534b14711bb96734ba88f89f8eb775fc0ae4d0e8615e7efd076e86b8362a981bbb9b870cced818c5d6835bcbcd274d8cdaa70c9631efdc98c4a9eadbe995b09359dae68645ab6b37ff9311ce49cfbd0c756981e8538375c66e3b3c1b388722ae4ad83f97faef4fc52fd27fdcb219be875667a244635f9ea8ea9e2070c21accd558b2a0b00e1fe2e98c1416a612af5500357536f4124aed864131b56e16d7ccd384d26f5485091bffb693aaf78eacfe9cac7411b6eec64a4c0ff18d21102719709047f5c41037c7fccb7dbd528029f832a0d5bf5cdb6135277e21c1ef225f6d484e102b2608faec7af94ada74ab7bd3e3011c9b18cfdaed2ce55ff9c4db5d224a54f6e7079e78b077d8d9b07d9e5c095a9d347ad795fb667a37200f8d8d2a0fc2263cb97019d6fc19d6152fa4b68f3b3a645080ad7bdd9970f75c47571617e8c6d19a9d1383dc0880a663e2a8ddcc73c9d9da1440df3eaad34ca9b099ba180ecd6b6ef59080ad072e1a97cad3804c11eeca2868b2602612651fed1ac81752363ec2e0098732efce178110159124ff885aba694d81ec688124bdb71d3facc4b6997190c75e9c7bf09e3cf2cd576f3042ab0f60b3f2bdf18a2b4fb336fd0aabb3eeb8c8a88ae6ca1de8ed7bbfb5748f95ff64959b073faf27e51d737ad88bdb1b1f47fb1eb4d9ef1028c65bb07d4f6782115a1fd23a93d9eb4993cf4ddd955fb6a7378cc95081a6d9eea503ca48b022a23a5ccd5bee245ba031835376ed1dd5f54bb46828c18a50e15138240d5018f51e3e72b950bf614451a03f10eb5e95f06214db2541bcd3f0fe8695d6bca618fe7cf90e076395f5c84dd578a60356c80d4ae4d1660af45a41a9b3a12e256048ea8b2021987002c51149d12dcfaaeae761c203356c50b92bff3dd509daf8b450617f8990c63f79c3de3689d0876989a3750f94d86314055fa6fd31c98229e79d47cca2bc521dc642d738172fc820aa7d252957e4e54e48f86c539b415e6d1d140af99b56e7366829c4bff56903b742ddda107ff8f3d2251de4452a0a75f0dc3792a1ae3bea92d6a14802d944b92e120912d21b3d0cfaba12eda0eb60add2f1a92a3318abd9e1baea334943f436d60b511e4ce0d4383f6baceb3312f2902406a429d5389b197fecd3e7f36bfbe4069ac993163fa4d3e095c0fe7456de6a47a21ab4a528eafe982e48861536cefbf5537ae66bb73d991a904921a9a132163678e5cfbdd3b22d81fe7a2ad2e59e95722b371008fd386aa5946f823cbda125da6b80bf61795503b3e4a1fbea0a55fd4201ef42e0f6a38a3c1e779695f844c7d38cff78ec933a27ec17a13623fad8693c5e3d92ee0b23b35366e1cccbd5ce6bb25f13a39924333aefdc15118dcb6be833b9ed315d8496c946ec621de82fb13d354d5690b239feb072b47d573085c3b660f186dbc6e1decf6c21e9f2be46664d0a4e142457b39335f13c250c1771a000af92147b0b11bc7d07881a0881662994dd8a4495006968abb624df6321eb3cb854d022d9acfa27961b4498a5d89152f244a600a5d60d1071763d4c12b9aa452bba23ee589633a703a912c92ee03b29cafe6f504f8bb2b90bd138286006b0b0e262d87be82db5f5efffba9c2d24545935f429586ee048cf7ca2ae0730a6e13ce56d4ee5f722709c76aa75a00fb472313b0050f287c8b28c0cbbad0306beed8471e7a8e23e62f7195213e90bd18b5c127571c189c55456513606b3237149124a4cf91719a0859fd5a1d6751580e03d1899ede85c027f4f0761ff9c8f97119e5613c1548f005d3406e80118eadcf5154541290f3e9892db6c56df0d58a673cb7003a3fc5f65d1d5a562afc7ae612fc5b0b8e37225206ba564a498541f7b3c0f974255e3754a7de423fded7220517b88db0621597b3216b05d74afae4116d1ce00c4e0f42996191feab770259d5bdc81c29884cf96e7f4aa8d3fc51effe796a26010acd0f2ece258aedc1293a7e5bffd7098a220f9c7c2252f6145a7fbbd0b829f4eb54774e322d88f44dc23fceb3f111a231d7e3e68e497fe7525fd7415e93ac79d3a7be89a3550d0955272a577c37196cbf5bb93f34b866d0a122608e325be9c0fad12610a75e88f6197a7016e59604211222b64f84efa0419169c5a51ff0bb7688f379845b009f32613f9e170028d6e14e32f596f23a6274186d0c06084a82e68035ef7a18682e893b027e79172bfdfe4384857226e3df3caa83e24897879a5033085b69ee410ee238b260e827dedd52777a76910b8d0ffa300729ab74a6bb185cfa446fd65fef82001dc688f803ac02d6de04077c504e14423d6010ef13270a8662d83828e383ff50819c025c0f4ff41ad3ea0a502b0132787a1d9e1448b5784e215bed4f794c8086b4cc59fed6c57108c85f4f82ec38c1ee40cd27c4838f9465d7c7238a121acb7c60cf0c36429efba650eb51eb114de4f1cd1bd3be223cf2a02eb9d339e5b342f00d65ddbdda0937e969ca097f3408ce2778c0b7c507522e791d79274cf8f5371c107814dc87284e67c684067833c8279e56f0ade412f60eddd8cbd59c752d7eb48beefae5a145791fb1f03766b62f089118e39faf76b964b43c3fc02dca6b27b1e0feb3673752ff06e0a7c884f9fd7b5c9f46028be1624ebbfedc53f3d22f08ab426b8de16fcf18d43286e79b3787a6b05bc9cfd4e0ea15682777f95c4430157b9570caac6456a3e3036c8034508032860e372fe592bf4e3538a30f05f9f9cf2baddce0e724007b0ff56682a0643eb3accbdaab8595d7c80867d142a155b46ddb15e8f90b43c7bf70c00d87e19114f823e7ce2311340f7836d559515098012f17e70884e1d0027df7783345291bfffca50b89e205f7fb3186c6fbf2bc6909d8c87f39678ce69e0708ea79f3cd889639740769ab22e01e14f77fe8854bcacd17c18ed1c81af6fa847a4706608f0c91f79beabed33ebaa1c8c1e7a38e2f0c8c7ff54def00e9468022515749f69d5d413259b7e3d61ee8c6f8ea65e9cfeb4fb3bd9c8639cc7cfc1ba9eb6af43a4a1033d276491c80502680a7d2fe2468611d6cce664c06893a50f926b20e21d2ed7f996aa5c15ed7c4594d499cc7e52c9cb6d2c8e08eb9cd15fedc1b8e7bec8c7e75c7a910488f8decbc0ab66e429da40d723dfc432b7bede2b2849da64ccc2ad4a2f9a2c15cb971b9924d41de3be2de463ee54122fb7d863047d5388b4c1678582b64ec9a754fd9c94f003aae95b1ee40df9c187515b8fddc2ee79f7ccac1f8cffd9ac452dc988d3549173f38fe583f9058c2c27e9dc763f6e315d441cb2b490fe30c8f7e99a8b57a47e81b44ea10fc85384993d1a0ee688582e924e0823fd7b6c4c4f755e92681f7d899cb698725a98d3e5bfeb24315cc8d54a64be230ef9064d44ba7d708b492d852c6be6d625696606a6716b080c81a20a198ad31c81a3cf1a4e0fc85f235e51b51b6d005072b56ef98eb15c9f648a3ea743f49568231b4773beb1cbbbf411e6cbeedcec347dd785d07db9aa817c982eec722eb2c1869f9576591a64782f9461c76cec636b63ada0549bcf8eee2396b7c89783e5c94430a265cb36dab025c2fd2b976c58ac5a660da34615db4df9090f21098d7ad251ed901d166980c950de4b61f42f5d2131fdd658704adc4233ca1439c6da48cf7e169adf009bb17b0ee72010456ab3f5670406ad88682bcf2f2ae30d3397522c380fe15a7979e238761d98715b5cb31c3ce254be2256fda2b27a99dcf75dc2e5d20c221b810a6ebd1201b509b153f3bd9ec928a4c4638ca67527b7288eb3a14a231890ba1b62f33ee17947e622cec2061ff6060bcfd60a232b3c66d662b81537db32fe408f078fbbbec7a1fc0d1d1a93e9dc381e380442389220014007c78f2dd7d257598ac810b9027c2b0d85061f086262c41fa712b7009ae1bc9967b0539138e237b31dfabf5fd2092883d45c5f7565c7160b13a533ca90edc61bbcf5d9810c59ccc8e93d62160eafa1a754e46d21dbf269f311e311e29eb56bc4087bada2f897658a30e3cbfc3617a0462a85eee0a00c4cbf3586a5465bc7f9ae2ee86fa060ebe6354515ea8420cd036aa2c10efcc9cd24a4396b27f861afaeee03271a1fdee2f1053eafc441fc09454a480cb01b5704f4e81da7080f209cfc85751a30203e33fa85a8f86d4500fb99ee51e5be6919a0d3b7c74b5e7551b8bd3d1d2e8938604e6a826f2acae255acf2a2c82cd0a7ba1e1a90ab1e40770b2c6aad4d8d5bdaf604292fa648add4f1a17a572d7a7abd5cb0f3419d6deb79cb0e7e3584fe7d50c40629bf2c486dafc75f2ad14be40920bea3d4c545882311a74f818b817736234e61041353944b1199177f7d3974269ba3a7bc12cb1b1f02f13133399154d1e2f4bd721cf27615e3b6ee7baf58fa6d8c2d42f4846e5aa41997b8086b865d4e188481e78211d27e488e0dc3f1942116ce7023c144e0ea59231759032960bc58e36df29c8b96190f226b644ce1b91b8ee9efe082991363ece1950d19158888654777800ff8926a09b23bc2ab50d58c23f22de5219567682fc6e49852fd563fe4b89f8c1eabea79ecba36aa32669014f76b4f57bd12a0c6c5da62bc68cfbbee2a8186fb24e7aa94ef6f61e782ede6f5f3dbd7c4dcf7a29d73de5ddf9ab4bdd23e72eeb8ec1863404f912944831dab19c1c76fb6e7c7e09f9efa2b432641f5333d10e4c766ef7266e1cc793a13b380e5684e389031949e98bbb9a47baed582c2b1530c5093e54c4e956565fd1b532bdbba4b3f5627b8ae19dd51a350d5f823d9600c97e87ddb20d89cf858e2219b0411691260499d632d753a0a7594f2719fceb73940ab0417cd92a241a9be3e9741fe25d2533be3321fe80c5283fde322dec46996784f692dcad9e420b66ed5f0aed9618704fe7d45eeef9f16ec30c5c345cce1bb3db74f2ba176697e4ff91713ed0d0ce7083ae1e6072fd1b9d562eafb6f32c8a1c8fdc11c5cf5856415274d9d8c5962547c59fb1011080324739a63915fc7fde00c91fd966451c31e984854de507865fd62134cd5ba22f62fddce95c88177d0d6e7ec5c1aa5dc9835d2ee8c375458a4e8ea73eaf1ec808f6768f86774db527826cba9bfaf5d50ba1ecf82facc06ebbfbf805451e423db3dc70d40aa2cd0f533bb9279a354dd16d0e8bdd0d17608e91a18d12da9da8ad054276b0793aeac6efbf75a63189678ffe7daa2e20f227a796ffd8f0b2c96701dfa5b479c106f5545d3a25c7c5996bcbb3e0f8a8266ca4304ed382d90ba6219424644869bdf825f6c61449bee05510179882da60a3e3d8003ebc809a36c31aa010e7288d85e8f17266072a91c867a46573b33e4818f17c7b70c850de6fb1bf630744836eb5238be89b2d68b01b29e54f04aa897b26585dc1ba96b21e1fd7798cf0114ce8a5ba75d86833160e659ff902bec0644224af1c5eac946eeaca7c860622e8d26973dfaa1e7d38b1c31737708373412485bd49dcd7a70924dd5deb3b9e07862618b5e01802fc1a1d4dbc5ec4a9d4789cbd94e21660cc1ce91cbdc3b9df1205c4e86f3542bd45de58f43e7c29073f0c64065aee3c6f600e4df6c0ea19c3751be07aa3ba6ae09fd3b502e01aad2229542cea8889ae185ab55021047b3db06d23645890f2f86decf6778ebe75c00749652b758d56d5119b9a590edb0efabd91e724eff8672dfefd2da391f8449f60a00bc7f7d0f86637ac8309cef7e92ef48bc616a793dbdeec256638edfcda78cf4022c01b1308d29683fb730b4bc123ea0c1795edae7caadfefea9adaf557017dec2ef0b9d21aa2be868f1c0b9b6564f1d783b09a50a5d69b17271f0620e5f0b86da19290ac65cf3ada99096e338c0df008323990b40124a91f97df45386f942567897172d1464f4d35fb46575d1eea64b5b7674d77fc7ea9667b2a47b71451888f6d649fd052781a091139e71a7bb67992551afe56535e9c90b8522e2031295e4f06329731af0a453ffd8ed36162dffc4f007500f176a46abea4f0f775cbcf3c23adc7b172a822a97adc359de64ca83b72969e91dd955f34ae11157511a6333cf07e8e42ef5144da9b9dea7849b21182bf2b091146f3a757accaf2f8222b31347484e6e93e12b23f95b0f7b2c0be55e43462b80cc88a74d0c761dcd539c08c2bc10ba7d6929628f1a992e73bc9cda25efd63d1f545482679f4a19417698a2c07aa8e9f975118424e5b0926301937412e4a8e9c7fb42fd86d8e6b662b213819297706b384107036ba9dfd5552c2474f0cb955330c35700b4ef26a4c50308949f1ea559d61174edf21f0b0718adb5c8fa4980308cb7b980af1f6c49b26272ddfca8e7408fde209d8c6e89293f05473973d861e0dc7cb379645e252fc9bfc75f3c4374c9e209760f207585d4b8adde19bc8d3ab04a605a3d57a272cf1ca00fd4b3c3bef89c2a5b386906f60e57f4b5338c4fa1941ee635093d2082059b3076ed61c4095ed1201eb6d7b6ec2d1c8c800c7183139cc4d6acc94f4f81c143a2ce67b4c8ddee12bedc18711ad6d2c1daafc971424bf1d661d9aaa2a581dd15ee807c25000291e0336f1301216e45d9ba104f5d67940455ab0f9a155158d21b080d4e00dc065154d0cb1aaebccfba66295ee38dda5df719a2c57e93b8918e4e06ae81cd15844adc2bcea50c56e1aac9735d47c52c8471557ac121c339213f1e46886d9fc70fb89637d68167fc5c291f55e3d95e8f524840a9a42d092867162b4e43a8a8c7eb3a34d4563c60bbee32ad3d8f949271cfd566cd05ce67e4a603d27713572d31b3b569fcc7da62c43d87a442a1458b5a3351c2940a7b0132936a2ec98635ad525a7c21994944cf6ebcbcdb11c8b22ba7a9779b9e8da0aaaf710dc6e833f5392ee03454ab6a793552d72a29c2f337ce268104f477e3f7ce2d00554f50062e710d8a17825734ab8a36826a9189b286df9ecdcbb2859ae94556ccbc46b326188ae7a06d90a0c43391f05bfa46acb0b048e18a38b9b8fe8ac82a64ef20d6261311f5e371ba9dc3d3af7c123e7723829ad777c46cdff17710ff97a73bcc0c5aef2d712771efb8f6fce1aa14f012231d2ce3c1d776871acb64151a8e13e4e3c7177565177b93589be8ccf52c923b05df9e14a54bed6e649d23ad816b1eb7628aaef4ad14f9b9d3ef33b65d4d74116f9bffd8b2eb84163bf520922e129b1d65a6a9174a5fe908b7210473bc33df71be0673f0f6735b7420851617e86d52de9fdcd456fa77bf0832b6a700f6d1e8680bea783cd1ce5b9b6c6a996cdfd7bca918ff09b18dc2d8b2613a082a5643e1fbd6163e5b3b9ab40a791505e9a48adb8867056ef467ed812b3fe9ef67692ee91f3fccd7273a62a6d87c8b96ed706383520c146b9e5f9881174c00c2d38ff88d2bef392f355c94471ec50dfe28c434de859327a9ea4c83461930eda775534a2da0aa6c8f362fdc1fa5b2f787a094b439f7308aa0b3d66339cad12b8d5850b4d67effe3f864ad81b2f7a4662520764f167eed725bf69307154505a9b2c7cff9e3ed94522ad51f2582f0cda523355448bd8fb0f47f30dd0df6fe4034e77690ce83b275f7ef72cd9863c829ac4a68e91078d42d7cbf45e91379f315a9de42e899d76408a023d22913c93655acf698a6a57bf7e842548eb0e7d19fa75eb064d2908ca6ab8bde14b2a64bd748585a0852e52d6e5caff02d557a5b8f11296f52067f6671191c293649b25e74483394cb8dd7b560e1ecb50bc926a462b57b5a9b69f970cb7227f7b480b2657ebd9f922bfccea68218f09744ffced9dc5d6e02797b5c3a6d8b6f304550eeb37cdd00baf23d6ed1067cc3fb13967c27eba383a87d55ac6af9e56f3ef5e313e5f3403e257cd94223882d7e645f17739909a40283e51a0dd51c1ea93fca96bdb84081abec4d7bbbd72f03614b5a71781ed876029ab07a6d74974498776ab9ef8e325f7f91433ebefa6bae7605ba1f8dbc7c14f248797245d7baaa40ca8bbdf8e25d4921e814abb2e19304c80cfdb3d35ccf69e863a0a46f4c64578600c1bea2349e20923de9165cf2d372d8eab6676bba3d2454ba3a3e2b782fb8c1e5bd6cca5c7e231e68c13d379c9b18d3b6987c9522bceb35dac42c81788e4387b1bff77444809c275f4db900aeb297d4a8b16732ea4f5629e44f7cdbcea50e77dfe5fe1282778196c4b62a0cb11ace66de7f1eb1308189d91a7fba54ac33240e27ea0d87b70354f9bdecb9d39face85c11faeebfe306c3d6d86b79c6e1ebe3e3f42e1b04981a9664979164fcdd022909dcf696acf35b31c4519198f4e7917c15986b6d927da77d49145a2dd288e827f88d30fec824d422e4e9488d3d4c02661b19ac192fc1583508a232ee9923be68d2aa2ed081f21c6dc1f781b39bfe10abe650bfe53cf1563f43d770db0b4b3079a40d251b2b60b1561f9144a1ee82f63dc22976312db6806ae78b23b9f5e45031f2a0535520ed61001f6d72e3f56a4609f6b0600e37988858ca60baf5d04de8c92b046808ace57f629167d7e92dda82c1119c34a8b0f646248efa42addc86f2a2fc3950dc57d1ffaae4c5d528902158c9b877a4440f4aeee4d1910848847de10401d2b8303c20340cde53a6f516e98e5b742fe1d704b2fcc382100cc3fc9c5036cccc621d5428677c7670de632596d84a69399d0f23f46029b5bce7a4b6b756cd7d1a2487e1fea5152ff257018c7ebf54d9ee47bb71353fe88f54a29cc0c02259a0c6c7f9772b8db16af897ac4cdc4e7b611d0d63c880e1157887160700c2b04ea818664d9c9a359d8618d1066b26cee358057be26635419fb037ae62bb3bbae7efba0e9b62e8c63c2c0643e65b59c4219767eacb4bf33dae0e7bdc279430c62d4f0566d77b393b52ca1db7914d8d3b37263ae8fef221f5da662b17d89d06ffbc882e50d6cb4d9a0f35cf614c63e2a87b33739729dc514ae967783d37f5c29e2ff814308d094fd378bafdf900708b8d6f9701b6bb85f55517af1fb68fb9203b0cfd1473a6206f3b0b2a5cb2a2ef0a3ef616f66b772847f35124e474aeea78b720511fc97309cf97a05f9fdd1fc8086e57d747ed1f023f7b18c9c6138b701add369628746a109e42e765ccc1b9207c4e664ea5f9c39dbc5fedee8e12f10b0ef800e31cbc38f3bfaee211c1f1f34a1e54b37684102c5a6022ebc045f0c3330b026c301000000000000000070f1add9b77183989da494eca42925408394524a29259960310034d77e471b10349e10e9c40aa70c6b0c1f0c8d3541b69ad0208349dfa9add3ee34e2511a63308a5df709717297652f051a62308ba9ba133a8fb24f2a1a61309f4ee9d7e45c0f553118ccd529e9d264e28f591e4bb62e697cc134fb245526c493c9f1545e308531b326ca93f283d3a3c7491d34ba607af7e495334105d9f1a1c105d3e9b86e7237df82c9c9f1644b28a5828945430b26f5a871a91ef6ee940573ca397d9c681e972e8f05f3376934f46287e91c5dc1e47359b9c7c37cf3b582792dcf478a3dd94bd72a98346133bc77b54f788e0a0697db278fa92ff9b9a660ca964c3e6154a5640bcb460b405034a4609e91b3f29e4451309f944f175c2f4f9a0e0553df8a296b0b4dd61a9f607e3dedd959779bbc9e130ca3f4c47bed523cb626982a6556f27495e3932f1aa0c10493acb49de76769da231a4b3086e75b9813e20927751a4a30a9e9aeaf858a574f9246128c79e99c384facb4a29a20c138e364924a61a74fd2ff08065d9a3423d64345d3311a46307b999ccae6647fb118e677f2793aa1164f9c1231cc647f722693d28b762695255a00198649e7937aaeadcbd467b2c23079268f7a3f1de749f36098e4b2099583ce911ded92ad0282fd020830ae9c46b442a8dce6fe8b4f07df50353d7672f185a972d58608d9d01f7d2f0c4e1095467430d7277c78612685a5285f1ef3efe65d98e4c6eb899e4a79e75517e6dd9799f1732798281766959793f2e44ae72fe2c238b7a2de3341bc2ebd5b989d9c33b93b6fa93e1db3854e50a1945e7eeb662d00a985b92b657c1627b4306893b217e3893d6a3f0e6e16a507882c4062b1134713442fc7f5e84ab67ef03ab841c6292b45c6d1713d78fc38ed031058185b54d7d68dbd87920ece0ff4c131818d316cfce007193acc0e14d818c346da8183043fe8f165a8c0c61836be8c1ce70536c6b0f18327818d316cf4f862328e8e3b2b80bcc270c1d533a97abe556d3380b8c2e04df09c74b474f2d9a515a64e132a3f8827c9c78e05105618ffe2494fd2b99c4cfecf2a36d318b32f4d2d6b11d527ff93d49d4a418d6c630c1e931da7071055189c6049bbf3da1694c006482accf2a4fca430d304d327ba716508105498535f8474e2f8896c924e6126b9e774b2b549eb51640a930a6a52bbce4f3d132f85419fecee1a5a7edc440a93501ed749b7f67ec64b001985e14fe54f2a9e83f44f021185b15367824ecfea70a29350982fd5e3c34d3c5708d57c3c0828cc6482cb67dfc7bf3cdd27cc44f179628db2f849c74848d210403c61b04d2779aab860aad4096395b6fb89c88c561227ccee49692f916a429ad0260c6b9d82d60c2727711e4d18bf456cff5975d2e45c268c9d5d3f2c4d6891fbc1845974ce8ea2e63b3c9914328e0988007209d36e7b9235624bd49cd8e900b184396c7ae794dc94021b8054c2a493ee4cce04d3fa7f11258c7eed57d2643c09f3656cff7b75d2419b92307d34cb297e5f90bf311249116a497c5a1cd38771fa6041eb9e600637120824cc048fdf7e176384ae8f30a587133aea879f6fe808e3577db69edf9df8ca8d2c328e096c03208d2894eb8855d414195102b2085398269efa48b37bf74114618a6d7289d5487dd23e49844194d40ecb793394183b008208736bc899d5a628d936c821cc6eb6fb045df23df73408104318dfc996e542866c4b9e3a8014c23c6be27ae5b3cc7c9010dbd7aba59453de92d6cd7db8db384b15cbf4255bbd3ddec725c8208c7219263fa6a229a509578ff7711700118429592e77d27d8cb3f50361f212f39d17ac54680908a33e99fcf269d27d3fb505903f982b3cb756d60bd274740c1bc607103f183c799347074dfaa074d2460b4a604307884709120d207d3086cee9a04e7d5477e68331d763e535db4a4b0eb20763284d0af2bd09ebc19c449eccf07c4263a44c00c98349dc6d67f6f37be3c68339d63529c4e2d92713dfc1942be5beda7527857e763067cfc1ccc99ff66a4465d4c14ccc0c55a53fe598b3d1c17c9e4d9899d3b5ce379b003207d3ed87735289ef4e42a86477e560d0fd334b3a9f6772284bb67477f02c7802481cccf76492e851d58fff0d1fa7c42afdfbb8b2d1021070808c63825c103824d39c9c84e9d9e5ba07206f281037d4016903f2ca4c6962c52ef14948387df80966706307081bcc332a9cdca39dfcb9e335186e9bbc953bf22ec906a206b4f6c5f7a4476557b29d03240d206830085519f1f9c4381d4f903398ad2e45e6cc7cb72a1118b3108098c1e8840b2a67b5139b9ca22a903298b7c9d1ba4922ce629f8090c138f2a9aa83d23f221609c8180cc2532679e627bb64ba92ad1dbc0f3198d308b13ca33cb64754b2b5e3d78084c1f41594b4d204a5d79d976cede0918307ba010693cebdd4d8aeb4bf720c902f342b7263b992975b69eae99c820a3ae5d393f1831f3774f04fc6e200f182e943eb7f774a397b0148178c4f6e97ffa45268513a38d8c12307ab00840b66e29b677230b34c4eb3b760ae2aa9223e28e5a3c4662e00d182416772fa3bf529a527df593056d2fb149f42bc2d8a3a41138060c19c536fe877abf7ee5bb27505a3e55bca3649c5f8692400b182d975cf49f3e4f9d3ba4ab67cf05702902a1409154cb133b9b44cd59feec68e837c4cc12017d7928b26bae8abca46002205b35ad21dbe9f459354ba648b6b0565e8b0f18115d8800248140c5af4663599f8ef51ab92adfa64060205a38a25a9a1968293eb04f2047367b5519aa479b576910188138cbbb2b7dabfe77b3190269889e99deffde365153513ccb9a5647e9aba151986016409c651eb27c99b85fbfd1b0c204a30aa76aceacfe9149b648224c194e4d65d49eb79bd27fb06102418fff4276522dbc692264730d7c5d0316647e87c2aea06102398624eacd59d5dfc275a0cb38e1384a97b79c95889610a79a29fb8ebe96b2fc330d5ede512316a9af0248561b02e4d4b7d6d1e1707c36cf9cf5cb49d4e4194c03055551ecb2777edeeacfe8531448fca648fbd78f12fd92aa37f80c65801090f7abc0f03c30c5f9809a327a4953c4dd092cf2231a317668234119e5b4ef85c7e7263070a5c80021b637c8084a40c318317a64e5ac73271f5892df2714aaa1e8920593063170627599fa6952a1dd3a4646b478f32bc5f304317eb09d39694c6a70bddcd61462eccd9f57977d5093a97c3770b33703199ecf983145139f792ad1fe8c9f0b143042424242476258037ccb885c94b35a97744a52ef1ff383fca3081eee7701c38d851c6845d30c316a6b2266852d507155fb47c98510b5a98728e0beff4514fcec4c38c5918af65f45fbcf49d54b2309ac9788f1f550a6261f6d1bbea23fe2d952702113c0f70fc3838f8139090f82021f901525898e293f579fc3df3925f6190b91bff5bf1f163eb0a8395f5e67b274fe0b7ad68ecdb5d2e8779685bdda76be78f16ff1c74a85498c10a33314c7e90e939eb21afc27ce1e420374f9e9592b161862a8c153ae69e94b29256a930553d31746f6b6e57c8460b4070e3060909090909c98d6ac50c5418aeda82acaf0fd1e2e41426b591317b499beee4f5196698c2e84ef03cafe404273bd91ec38c52985dc7ebc27acb697fb003073858404232c1d1e35f5086bea0078fc98d1ce81309490144318314e67c82dcbb888f61395ab275e6c3078684440c3346614ed993691b7db195491285f97b2ed738397d101763166684c2ecfba1e6f24ae9c9def95ef0ed748fdb0b333e615627dc292775be7ca76ae120830364e810018e1b64f8b8f68441fafee79a4ef5a559270aa184e7fcc96238612fd9ea81e346d572c21c6b37efdc3fb8fea9644b6d13463595c48a134e9354122ad9f281836db4000424243e74e0e3fcd0818fd302eb13cce006116668c2742a96cd93946e52524ac38c4c9857a459f44ab7956529590f086106260c4a7e7ab18f159f4f26b4987109a488586a51aff2799223c719232d13332c61b82b155d51b9640b7b7056208c1995286306254c7bafe86e4afebbd481a3c407090f1eede051f0d10e14ecf880fa9831093edb74a86e62079f37478e83f8471926d8f181641f64f050c10c491845ddc9a153e95f4b4191b05390a7e5e4b92a3d06332061eef0eb393ee58da5f01a663cc2a0c39a8aa254ab9778673822cd92ed7aefca1536c566467f6c92c5f5bbdc6046238c22cc2c8cb0fa9f4b419bc108c325a19e2e7aeb724abd0748483e95198b306592f0f417545a30f9600109498ff741424242728219dce8314311e6ec5af523f5fad4134b84b952bb982a7dd6600622ac4f65b56651fd3b842947bb9b7e0b0f3559c9161947c78d1f387ce0e041f7e0f103870f1c2f589b610883b036213a4d6aa798e5ce2884c1d7e2499644e9bd77e0409d4108838a5ff6de70bbf9b88c41a4bdc49426978f5410e651eaf19f4c26d5980eddc0c18eabfc643923103d9801881fccf8438e197e30977636416e5b699f6e461fcce460f719fa9fa0bd590766f021cbe4b4132a6e49873471ccd88322f684ddbcf8292f93197a48ab93d4ad4634b1732501d979fc91f979f154468eef41c6b2881a4c414b3e9cbc2959172fd9bad183dfa1d50191349874892af5e99eeca4f496fc3829c0921f870cfcc10f431134989e245ec3342d276da792ad1e7fc52c720683bd8e77eab2d3ddd11044cc60d452ed57ede37b4154065389cc9ac907af4e960819b89272d5272c591d3d787c40470f1e93de31982ec74c91a7ba4e4a411131b456692e9b25b9d86dd1e4f2a7790775f249ba1b8884c198f541fe2fbdd89303834997f24c1016ade509639321f205d36892ce9a62623cfbf68251d4e7638eca4d50da2e98da2e133da54aa2d2850ba6f37c59b2655e3794b4cad882a9d31343c93515dd467c3910d182a9c9a49cf4a58f28ff30070f1e9e3944b260fe14f47ddead2609ed460f1e3776f4783e8108164caace6e9c14db73e9125307225730f6490fcd26ca6ef60112921205112b98ae89574d9e51bad2edc97b800722553007fd79e475975c7974e0e8f183abc70f1f2410a182a95ccb9ea476369a1cc231c1e10307978f1c387e948101912998ecb7a47e366d4ddc908a48c124223fac9ea072493776f0c0f138e8c1636257d6e8797049240aa673b29a302d35d77743c1a05410623bba966c74e3073d7e3014449e60105d7f55224c5f6dee04f35f3cab2cfa548c9136c194827d1edd442786bb26c20483f58d3bb1e3473a493986c8124cdb1f679c686fdbda2bc1946d2a3ef1f6f249b94e8249db7fbc52a66fd2c98920a19456595bb4b562d76cf97867bc3f696409d38a1cc19cc48a6b05d3d2d1f42246308d909f7a4d675bfb5e0c3349df747b5b3e9b9413c378ca4497388b114fb1611845996e3b99a8a1e1496118ee444bc4b32bba5c42826126c5be27a8b8f15141280418a6ebbcdc571e47e964fdc2f869bfb227dd31efd417c6921bf5cddcfc2484bd309daa2708f11e3ef6897961dab514cca2c8932fa74276614efe95752cc5105dec4ed2eb14952705427261def1b7ec9969613b5cb2d5757cf8c0f163c7d1c13ff8f1e37b90a165fca0c79d6006373610820b63b8e7e9f3bb54f9df905b70b1bed36e3bede56d8b37e6a47ab5854b6b610aa1ed65d343fb6aa585d67276179e1e2bda53d57aa768e56bc82c4c22d3b36979d2e7266f9a16021292bd513e32441606b9a0c9eaad7d253cae8d8541554f6c7d3d58aff53db00c326061de7873f560eacea458b2b55606193b44e0a34719639090a04ffd0a6545478d5f3a93a58f105754b1ec53dbeee45a6de3ca095ab55ebd53ae1516c20ae39ffb2879528884846f94adc2e0966fc55abc9c5c5955186e9de8aea7679af1242415e69195e4f678d272390a861054984a6efb789a9c90a2e715424e6126b555c51ab3dc5f9e348541ec63ec63cb74cc14520a33514468b3d8ee52151b238414988d6ab668e5aa8eed4cddcfea16d493924a71c8280cf7a4b8a52e3b8f685bb29584105118e589df1a9e3429c447570921a130a84c0c1d3ce85d7652261d8510509849f1835b3015b65fc61e8684e4f4f8904f540af1c4579ed4e2e77afeb48474c2a49eab3653b493478913c6b827aafd49ddcadd6fc2a4b5a1cc83f6423461caf99d2b564a39ed551fd8818263448464c2a0c3c39a10cf1f634f0c440826cc49ddc9e7535e415c5dea12e60f71523b51448592d5e8106209834e276852dadb154d562b616ebda07395d0e4fc9ad5208412e6caee217413e4843653c9d64e216412c851bf5df34e522a56b2d583d323d5102209e3c977f0d893a5ba5bc956614248244c761d1e3e4af7894dd8c618f6881048189e4c7282588bfc1e613891259ab4f53bc2ecf21fd37487d907f10e23a411a6739195940ec25e8430c2d85bbade92baf527935f84593f29599f52543971fbff809a220c967f4d7cec574fa726c268d5613c67a96df920c21cf22dc9afbb1cc2fc9ecdcde74a2c44133584e172104ae333697c3da34148214c39c5d3e424bbecac574218efae8429199572c93c6410e64fb9c572b6ca0eb328224410a670a32ea51e7dd25ab34098b2dd563ece0795e40710468f26c4eadf8fdcbc2d86903f184e8df6b1707a3f18de49caa277e92f25bd3e7021840f5908d9036321440fa61c476a933249cd7930a9585e9749eae59d38976c7921040f7f3839faa69e7c884b36ef606a62d9cffe958dc76807f3ea65cb9f9e9cd904ab0a21753058de6e3595948a6b69081d4c4ebed46499cbe4042173307726e7aae09509aa7a2ac3478920440e66923d497ef47af80e6e1d42e2100207834a2a3541c7940b0e216f30868ae6042dea74684fdfa80244881b4c67418bd0d74eb214d236189407215d2f09fd2793668341289d27d49eec93cb3598ce42e628a5b34a8f6a7ee428039f10a206f3cd5693df2f8ad2416930e7a9af0c57bd98734a5a820841035bd1b5aede2a77c5aa7b26937bfea46b7a8219dcf8117206636832c9928518b91ceb159090a01e1c6206e37e95dec88535153fa40c069dee449c4a41e8f7a4103298f4ea9c5879daf967cd40c818f89c7226093b4f27373931e8a44cb40b6ba74eae671012067390e2fea97a5561dc103098da62e71c3ba4e5930d18215f30134b3d59de2cc5c9db7bc120b249417a6c92ba14a72e989d64329c942fecc732594042c2cf830ae1822953d3a367a78c11fd168ca6e348efcb54bdf6b4602ed32d2ae54543b2600a3a54b4acbc5a9f3f3a405f32462542b060b0acd19dfa6359ce5f4d845cc1646f63b17ee5eaa45608a982e1fcfe6c54bcb24f7688102a9846f5938ab7d1e420b639844cc124642b852d71e1ea49a721440a86279eeea0e27dd2c447f11012057307f53442134dfcc3bf2a9b8440c1245b4d45c83427956ac288902798fab28715996a424d8a978810271873bbbcb53c9490e926ab3f8434c1d4d10942e7fcba9cf7434208138c5a3ac890cf387175b131c6183638807d085982372a8b9097523221f6018f0e9090a4f7418812702db12cb1309a726ea96405293a4b2dc75b1f58810ddb4448120ca6df4bb489d8934a1324988976a59e4f0e224b4f2147305dcc284dce97b53ba71023187c4c5b7c828c69dbb11806ed6b225e848c18a6d104974f6acacb3a350c639b92a5a2631346fb424318c6d324b724bec2bb973c18a6aa4e4f34d5b0936603c3a4214ce6f9458dd1f51706f57e266fb52f8cf296e213eb777d3ded8541a80d2fb5781ed493e485a9479a7a7c9457b9e02ecc66a2ee3df7a67a902e8cd72de79ec358a959381726f3e4f9dd3d783cd56d4f30831b3b68e0c2b8276efd4eb08fab148d5b184e56d4cb59d5c9227feee091630c1292dfc123075a5b1847765ba6d8ec681dd1a885413f395e58ce492fc642831666cb2d9a5495639efec7e3a0c76981d3988599609a3c6b52ddbb453872f8485b1fa0210b2fc9cbe93341ce637e3462c1899ccdcca54a4bde96a4010b83b87688cc274a061aaf303d99d47add39676be9b8c2f8c41dd924717ab709762bcce4718f27df89ce2469b2c23ca746a9707f25b3c530d0588539ffe2c8b4a05785e9bdb3bcfaeb933e8b68a4c29c2b588a75917591312acc418cbe779fd2dbef4f61eaaef4ccf190b35f328571bfa45956ab9674520ab3c810573abc79b238290cb2e67bce6647dd671426d99da5cd459e26499128ccd5a14ed2153f74527b280cf3963d8fc5a9742a28cc95a69c742262f72efc0953eccfc90952c4fd42ea0993d615d3a2745ff85276c2b8dd8427673533f9f03861d2f9d9b68997740a966dc2a477fb4a6587f5f5451346f7f94af11562742e65c2244c48bf6e6278de91d7083430613af9546ff29a65d0b884252a716301342831091a928804046840e2110fa0e1881b11a0d10846dca0008d452822110ca081881b0da0718805d03044036814e200340891001a8340000d41d4a01188ea09c2f3c2e69678c2340061cef126cee8a8fdc1709a2cd292fecc7272ca0fe6fc356b3376a6917174dc0b68f4c1a09ddeb6be546e27135f3e98462b9fd619d33d279392ad626fa0b10793d4f6f0c4511ab2a387a2910773b252b225f27a2c8f98061a7848e3333d39f1cadc42d0b883d93d85ce3629f7c59d255b3f3839520ef6a19da06107a39886de77f83ef9f992ad54038d3a989468dd09edde23322fd9d235d0a08341a87dd27d55aeceb92ed9ba44038d39a493688eb01466591e3c4a6ed818253f70c0c0478e1c1cb031860d1b63d818c3868d316cd86841096c182c9534e4607e93a9a7da33898359c384abafc7e160269937b1c42fa4bd7ddeb0d68cc8b658dac68a5bed7be714c693e54ce740c30de626a875588aff639f6c83e9d356ce53f5d9c3e768b0c1e844db53ffb7b74ef0d660329db57982b65141466a30eda755cc782f11761acc972fcb92ba8706537c9213ffac63a8858d8cdb2bd0388341ebe6e86951972316fc781f26061a663093728a53323c645955051a6530ad5912356c30f58e88d24dbae4d95983312f9b4e41ad0633319fd47649cf9eb4d360521d4efc83c8a5cf6830c81b79619aa053c7f60c66733d799faac369926630a97c26978a8965307f36b13a5c93a36c886430cf795fd6cefeef87633013379ea0ef89180cb6fbbef949cf851f0653936259de86275513184c269d60f3e4d199f8415f307a10236d4bc80ba6b8d6905e3968c289ba600efbaf3ebb9c3331c405e3a8f3d1232f7b50415b307a48d96e8234ed848f164c4a6aa513eb2a15ea2c98c99968f6a52a46648d05d35c6fc5318fa6595fc118a34498e50f321eb682e1cb9cf0c4b0dc1d73158c3aafe56a3115cca67da4090b6dc1894ec134be4dcee9f94e45550ac6b7115561deadbc8c82e172e5a8fb974c7a775030eae52667856c8f91dd130c16544cdfafce0926cba5c46686267faa5c134c2a4e294f376f72376182d147de935a34d933b927c012cc3bba545c8ff9711301946096b9a02ba29a286a2240128c6bf1f3a1542c27c809800483722fe52674ccf79900473068ae58d27572ea94096004e3c907bda5ccd4655c0c73d0bbeacbe498215b6298740c9317a43669f71c86e1443dd68f10a7ea3461184f39995827cb60986eefb4e8933239f5070cd35d677210264296a57e618aa52d478f71ad4a992fcc04153d6cdf7e8eb9582f8c224c98324b73e293092f0cb716aff267b20ba3cf6c9bccb0f015d285595d7d8493827261be2c169aa0aeb24d8e0b432b7e169ffb166672f4592b9b6d61ae1cfe5a39cb5a98c9413edbc5da63678b16c690fb77e2a4b33007a195823539ba8f280be3c5bf6c1beac40559c5c2a04b597e15a1c9d74e60617c11ab6dc2084dfc15667527a83539cdbac55d610e959ffbeb694cdc0ac39fce113a0815646b5698c74ccaf599133bceab30cf38d94a98d00ca9c294d28b3419b9105953613c15ea579ba0499e4685a93b9758d19e73268ba7308fb23c2ae9fa96ec4c610aba3c569cda26eb560aa3766b5dfc6ca83c29ccf6734fcccf0d1dab511847e97ad8cbbb6f52ca88284c41757b34d59ff259ca48288c16b2730a427cac68662628cce6de39a8b327d9a77c667ec24cccd2b538625bdf2d9888114f98f37fb8263a5994e93cb23e8c74c26499e439bd33c177ffc57a015e61f1e0f630c209e397e729e1f974b01aad726413a626eae9544243a8d864c263b243048418d18461bdc4af6967b2e878a222463261b44c0c1bf144d13022463061f2b0f57c5aea4b182cb8d545ffacced78e58c29c6a4be99adf327d6ba4120639d22f6baddfa59c8be07694c138420953f65a3f3209b3a98d07a5e74b8f76333022094d075961f33339ef91c84c43ce4be52d9c5acbe5743a46a8caf1cd532390309d4a6d2145a794e6a547187e7427d90bf788234cf1929e68a57c72aee848238c622657edc9fe3a278d30c228d6644b159b9473feecc8228ce9c1e694b7d8fd058d28c2a43c4c29fdd604cb151d4984d14ec6cffcee49c1671e4184a95493d35875955c267f08d3c7139b143fed549769c410e6f350b6a5561b7a21cc39bda8a0779d20ca9d10c27826bbd7afc9599496066126cc255d1a5af2ab6e41182e857137277fda883a1208736782980df9d1d37d228311409c296fe9dd4c2ea5ec1427c52a51faa2936487f80f8e09d1393f2969c40f861ff55ae1c2f5c62a288cf4c14cf8d0e749a45ccff57c307faed8d31d915e251cd983699efcc19c54e99dbb2bd9d283a9e3cc87f1916a9f5ec9165e1eccd9b6ab542727255b3fb8819582113c9847debfb43e93946656b2a543ef60106ee9f2d493ea31c1b6834986565fc8d7533deb60cc9db54c124ac8caa12b4507535f68d2653b9d428f58b2d573309332e1c967df27db49a564eb0c478f1f2807638d762d296fb560240e86ef0ed6ba1d4f66ec3ee0600cdbcb4dde9e078f1ef88191371854ca9cce139f7ce1623018718359c4af597ace78ac26dc063341a89cf36ac283fc2036989b9c72ca534d1cadb0acc1a03e888e3db99bf2e44ab670e8d0c103b10b46d4608a6e2fb29fe245dbb51a4983c92efe833db9c3823dcee74009821134183e353b589c4b9a24e5108c9c4108236650148c9461840c236360d6b6626ab89dd5775b0a553a767ef266b89846c4709fbc15b15cf115c14818cc417e59762cd10a4a5eb2c523180183f9d2629a967ea2fc9af60553b60edad3743ec4a5255ba54a7c9c12138c78c1a4c9b6dbf9c29ed5872cc8910307094848ba70e300235c30295b391de4789f0acb1626c5cb9ed32d590e8c68412df574d1708f8f1bb9ac7be2ab96f94816cc5af245debd9ba8961ec182c944c9eabe353946c847ae60de0a15fdf24d255b3e383a9a042356305d29f11fe34f960d6dc9fab8f1c3078f921baf86831d387080031e3996078e1b64f8c813cce08619a98241877082080b2afdfe3d18a182f18356097dd9a4654b19998249eae776d593edc9b33826132c117ce0032437ac0b2352305ab26d6295767b8bad1346a290089d573ec90f1a144cea828a9f9d44479c37f204c3c878522755a22ecd1c718229057d554f2e193a8ece4813d40eabb3ca1ddf61a76f6485911d6919618229f6857593ad9cf553234b30bbe5bedced152efe52c688128c23fa6df14f990af3a88f9124189ea0b5cb091e6aedbb1124987348fbb6e0a9938ad9c811cca56e5d6ff51e4c87478c606e1597d3769fdaec122986d93fafeb534f7be98810c35cd16adfabde4b8e900f3e042424c9075f8244866150960956e724d3237213867954fc85caa79bf89548304c154334c9c2bff3e80a11448061dc79d7badc9d9ab7f68b12f185298f3ff16ee4b346e588f4c2acd64e76825cbd8e7bbc305cfd5f9349aa626da422bbf081882ecc9626dae6a154aaaa447291db8959b6c865d75ced2cc2d33a447061d2398f7eeeaa9cac22b7308f10e2895ea6f5ee49f1c187a0c7f78047096c616eedd1a5a216d43cb15a189edc72af5d56729468610eeba432671e1f72b4b330a51d2768d1848fdbbb6f94882c4c4a54136409f924d10fc7c2182af5d8ec8e1a6fc29c3e7cb0054460614a999c899a99fd7165dd1bd503915798744d4ddc9ad05dd1bbc2b0eef23ada752bcc5967ed82f82ac20a5365725221cf53bab4a2c82a520b96292a96b32c65dd9fe8e7669d4e08e5231155183d558abd8c4f62f353b2c5031ce93cd06cd5980a839bbe5db1fb59ed302a4c3d2a45c6ac6897d44e61d6acf40d9dd7fca445c414061db67c3f257bdddd4b61ae74cbb29eddcf4f2385c15b2dbc8ffabd744d4661b4fdd2b14b7fa6a78f28cc9d72d2395a3cd3d5c9501c76a529727229a7c51295eb93a6d4ae9f1428cc9d391af2d42a8793aa2f887cc278e2b14eca41fc53d09290f08d3a4f985f437df22d39aaa3df097375ac5f102785f7707102b335ef72ab7039f3e73ac6a4c8cbcc9b30a9ebd9fb92253e054dc8d081838484070e114d5c260e1345227209e39a05f9fcd70465991c4b189b78fa2e869c5135b512a630d22a961a35426a8512a61296fed209bafaf103870ec39330694b6ae3b33e69de3bfe442461ca615b7459f49dbd924824cc04fdd1fe891aca9f4c46200209e3c9b5931e4daf28b98b3c82add39ab1b65699addaea4f995571334d529d8a38c26c1e3f6baaffa4d14f6efc60c7798148234c622bd49334711e4e2fc208636982dcb0209fd5275b8439de9d1cbef2d54d452ba20883ca3e273ff1e48292221f7c49f5104984b153144d4ebefb41c5ae64ab5806441061ca97fe9236e1bdb75f9f5f0109493adf83840487c821ccff64a2e9d34d2a0fdae1f1e306193874e0a30da1c5baf3fa9051abb6b251214a9c74d727c754b235e17163070f1c2b788423070bda062285309cb8b4d19976a32384295c26aae9e8af27b5bf83878f11bcc8204cb1ef4d8912322b4118948ee8f58cab11627b1d1c1da552241086cd5aee70b520ef76d173d0f93aefd5ecbf0284a9e24941554e1764fb3203913f18ec9ea42ea5d845fc60d47631f92453271e63681f4c840f26b207935f3ab9c9e904c140440f2b10c98339e7fad2fad149497e13113c98473c298e13744a496dbb83593cf54b8afed167a21eefa3c7490109c927113b982cafe67f08a533e9553688d4c1bc5fa53dc9454b5f4e7af0f8800e78f028811a44e8601cd1179f933461f22d14fc0e14e4096670a30522733069a2e9ee79ea723085d2fb9ce4756a92f2c70e1c3c52e081921f386020120783a52065e4c935417317818341073b3d2a9c5744de60dc8a22264567df27fb6e30c869ffedeeecaaff44da6070353516f4dee90f8d0d29fb20162b46a8eefdc78f43061044d6608a0313c052812e15402b06813a0995f67f0940a09014939403d00f1f65f40009c00fbc023502e0a3c78f60c0070e324e8d1f0020000130400b00e0c38793cc10c0f97112094800ce8f9378f4200901c0001430d2e36fe8009d1f3e6edc30000002808010a3890a2e6b16d5c9a479802395813f76e0c0408d4084d40004193afe60fa68ed972f9a05970c1dc80fe634c249792d75cea4acf5c1a4ec89a6674e1ef0d0a1e375acd6e08369439a9def8cd249aae375648d3d1c8e1f3cc091cab87163a4861e8cb35a7ff7e2a5f2492a59b62ac34ac7f7b8c1031ce946ea52418d3cfc800c8487911a773027b572795234b5718e3274dc056ad861a4461d7ebc0f1daf83460d3a98c69f4c7832713c07777b94f8c83107934a2da2c4cb93f41c8e1f394a7c9c1b373a7fecc0713e76e0f8b135e470387ee42843c78d1b2235e26018ad8ceffdbc4f52b21a709838b289dbef961a6f30778cdffd190f423c29df1b55841a6e30895813f2bd435a2aada350a30d46ad13f2e4a0fcce643b35d860ea3f7de288275ab6fb7a0d69ba7b5251a347ae8672d5659d55dacad89235a57e44e86f870635d26094cfe4347b9a24d487d20bd0604cff1816f7f2ff934f673085373337714eee93570d339842ac89a6f239c982eb653068828bee1dcd3af7680d3298abab72b217b313bed51883f1744e77a95454a5e4d50b6a88018fa246e549a3b1f7a81186529cdb55f40a76d1bac3cdc9d504bd4fe29ff0a406180c7afa53708232f9e95f2ed4f882b12c3f5eec9a93aeb43276f0a78c1d98e205e3760a35db31d652b31a5d30ba7dae20f497935c672e98843639889986121f69c996aec00835b660eaebeab85eb916d6cb2264cbb65c230b6632298f562b594145786a60c11cf652eece3a9d72d27dd4b88241278b93a6c3c4c3044ca861055326bdc57ce2565e12f601a146154c29a9b94c0e55499c675430759011ed3ab6b36941428d2998e34fd9a5a56e6ff1310835a460b47df2ce8d9347c95321428d28982b973c39dab3146a40c114f52c9d99d5e84bd71c6a3cc154397da9d1ebc4e850c309c670729375e64e5fd651efa146134c993457a73c9f30c1f4f9ad9af0969390d4a1c6124ca5efc9724d98519d82420d2518b4849993b4a5f00a261212ab1a49305fd6b69445ba3b70b80e1ec84a65500309e5b79f080da1c6118ca62642563a530d23a871d6ea31dee159a925f4b357109fc94ece4f7214a100c530f68cf020d28393fed2c4306727cce55bdf6ed86841096c8c41421284020cc3d8e19c7c9fc9e97bb4957ef8b841868f3c53006198e236f9cd4935aa43ce050886b9637a0acf7d0b000ce337d13d7bccbc00bf3028276596872d27a5130be00b7350651523eef39d30f5c2143f7dffe73d93c9a6f1c21ce32e4bbfa8e650805d9853ce1746978689eebc2e0c1bbacf5c9852f49f115ff117d652005c1874ea2443e54aa19a74dec2a0cad4750c15ab6486b630857672ac54717255c5c628402d8c26477bca7a8a7e926861263fd9d3ceb79c93cd340b730a6df966be76d4230ba35a9a76b2c6dfcf680588056f17e3ad6dc32b6c6ba5ea27a7578e25536a016061509924c2cdc91df7fe2b4c59663d67c7a7adef0a637c2a79293e9e8ea25618a47dce27f8796e86586192e5bd4d3e7d9e2e5f85995c41e9c993dd8f8f2a4c99b47527e7c396dba9307b5d56ceda57ee352a4c332a8626e65fa5bfa7406c9b7c2ae86a0a535898111dc454aa6829cce13ec72feb2c97901406f1eb7815c7bea4381985492855a3743d69adc94d4461784b2a9b9d6e120a734a7ab3db64b2c8cb4d4061d2f3eb049d1fc47b36f984719466b686d22c5d4d3c6110a2d35c2686c5cb4fbe13a63ca1c474554ba5fe3961182d117d6dda844198d69ecbf9d27790264c1bfab9ec7274f578264c413e418b9eae1c9f63c2b4667275e204fd417b0993c5d2de9e3217ebb58439550a5d9e64894ebaaf84312cc8ab38c1f3c9ee2961b20eb21f463ab1fff293307be98c52afcb2937918451e382c9f14ad1528a84d9ec094e8e16d6dc4d9030a578961c0b5234b1f408835a909b5ddbae8b3bc2e0a755ef924e234c1eeef2c80e23cb3d8c30933d87fd6815f54bbd08d368c9502adda408834e499ba86fb99f3925c22c23d6e266bb677f13220c4e72d14b9d3c84999897639c304d0c2b0d617ef2a58abe3072ffa542182c883db9c5c9dab14b843089103d55fb64b2782a0dc2a05475896af2677b4b128429abf43d69a2abf69340185493549a5ec87a9e00616ef12727bd2713d7467f30fdaf8fa54b7a2a457e307b93e4b2a9e75fd1fb607213d254e90e1f0c9ab4a36a72c3743f7b30ff293562af5ef4e5e8c13ca28496b11444479c0793d0b6266e8407f39e50a7562a554cbe8339c5d48ddc68b5bd76305fa5c86d49cb048fd6c1d8493c44ef988598743039f1efe594e5f3f01c4caa57611a1bdf15948371c684de12ffe184e26010eb356b7f1f840a0e06d9ee6b5f3a256db93718d4925e7f92d2f784dd601097733c1db43698edae9d9c4405b1c1a06add572ba8e659680d062db7922e4ed4607e52aed0de7a6d9a7c1acc379a307b26341875cbfcfa5a7b3ee46730953c157488bcee9cbb198c22ae1d9cf04d8ccdbd0ca6f0f05a51e33c93632783298b26c94c53edf7b18fc1b8bd9b9d9f5c9633591783e9842693b39aea61303ab94f4b7b26abe9d0c1607c758b6dd67ee236ff82495acc8be7eed795b917cca934419e26676cd6e55d30ba09511baa2ce5cf840b26717232b52ae515ff16cc2499af67b15254f0b5600aab5921e26be14e5930b65ab2b32f2768324158308c99ed5d1cf17e4157307fe9ca5dba154c3aa9e44e7ced2696a70ac66a152543849349a3a682e1e6c9d157bddf727a0a061517da39a8ab8d560a867bd3cf21b2b3f48d82419d1eddf952f98ea060366d2746e8e8134cf9ed69bfff4d987a9c602655b4a4f15d134c4167d8f513b4a713134c5d77eb95e5464d5b82e19b94c5e47abc5229259873ac676772932ec5249894ed25753a99133d24983249e52d0b2aeaae728e601ef3544f2e62339c14c008a6d22707fd9e2f8639f4ccdcdac891ef13c3ec99ecfb95acf3b7340cd356ac1d6fedd44d9030cc7655aa5b7430a542c13059a8509e491e3c38716098c9229621d3f244efbf308baaa43229fb3439677d6150523b7fbc1766f27607ddd17f63535e18543431c7c962fac5db85d1840cdb914e982f952e4ceaede5ce4939d99f0b53ddef5b9ca57161aa7732b9852929cfc96abe538a932d4c32942693c9ebc42d33d5c2bc4ebc247fbf82b2112db0ffae66614a293869b7e262998e2cccd7a4d036a74939b41d0b73967ad063173f5f121626dd4127bd1ce45798892e9adc1ee40a536f49ff0bc26ee65698ca934719d3fe996c312bccbe4d9e1df9c4f3ca641586cdd11e2d8e9600aa30d5c8de92e61f7e3a4b805498c64b45b75a2ef4bd044085e9747e137634e162de4b805364a265a96253183febc8fb2c4a59d8a53049bb7fabebfc1b2e294cdd39da58f2ac548fc26069a46abcebcd95a2308b1abda749ef7f2686c29ceec95d9f8969b20585499678c9b4fefcec13a69cd63f9f269e8fe53c6194f99c49dadb76c2f8e4b1168f1a270ce3224fa5890fa3acdd84c957ce4f9914d5a43561f610594e9296b3c39f0973b0ee74827d34bd13264c428fdcaccceda0a34b184bbce7d6ac2a95895bc2203627dc09563acd953037b1091a5bf952b69430e5eef49e5f6d9d4dc2ec253c2e7d0eb5eb48c2f89ddace833a12e65ef7552d592161d01d6adaa2d44567f511468bb26fe5c4babca93ac21c324293edaf520e3a6d84e964ff49e1bfbcc9396584b13a950a1dd43cdca78b30e91e6d7ba13a4f90a922cc2a4a4d9e6c3a29516922ccf1d3fe4be9cbaa4a1161d4f268ba3247e8b8e821bce42bc283aca8218ca67cdde747b55b9342987ca496878737fd3e214cf597ef4a8ddd8d3f085338fd653eeacd3b4810b59b081dbbe4098441ae53d0a5a53ac293803087987acb0915d772fec1e0273feb9977e9b7e907b3da134e7df8272d66f6c19c3f7a2594e55c9a60f2c14cecbc14af54ecbc61eec1f86b4df4f839e6f7a51e4c9a619d82d8cb0b5fe6c17cb2156c5645c67989073349f6e28b977730ce6d094f2ac413ae4b3b189ba084f86a29a5b4967530c50f39b91273f2694907f37a132e5798d06ff939986ed63f68fbeeb8931ccc6e4ad493135d3e131407635c972608bd155c84e060162bf11ca296d5446f30ed5e6a954a5f7a416e308ca753f2b4c5f34c4e1bccda6d3dda276ee2138cbca8744fa5425124108602a160200c0605d16f3300a3130820203c228cc6a20171ae91f30014000449382c4c362a22321a16160b0682812818088582a140180c060402a150181c12cbadac531b88692f60b7412702b5d3b3bf651b6948df1285a274497839b667a41517e85a268f2eef1fed614a13cec94c4608e29de38646d06b7a99e63bb41ab68d2dc0bda9d386838b05b68e9eaadbec4e94a67678042f1a6bbe1fab19daf0705c4a5b66e542c49e6da351e0f0f9c44796acba0cedca67a54200778689b86f9834e93d1bc285c133e99b729db4c73775b8213c9de859931e75d42e157097682e869b0818d9f9911fc52dfe22fc3b8a47183d30b0b8f8768e5c1f39d143c8c39bcae0032108f3c524d0eedbd0dd08547b9dd68de8cd82452237b02e1a4d0a70f03ae0636a3b5a7504cea3cc7a2feea0b87f366b862ad83997a94e24a3d32423a989a44220195f49dcba198d98695dce1c1d282a0f820ff283a24875e859af9ce5f2530b4d485420eeb1442d18c46aadc06d9b492568f221f9c8379960eedbb3369c00f7696b756650d4e9011773b103a9a5d48791dff0d0544f9b4b223c0e906cb245fa71cf8b63407c186838c609d2a6a14cb0b7228b1349dde4d8841ba2f1999aedb09a2ec05c382845ab63a8357a17ab3bc8feb52103986ecd34ed3e2d9d52c2cf0f374d10f187c1efa859f7d440d178c690779ebe5382f11601b8dd04529b227b43d640a90975aa1e62b7a20302019d5b25318bf7238d228b6e8030694917c0a4261f246358edd1de06e68660138aa1fe10cbb2d56f9fbca7d4d0a865e74a35956c54d8a715ea44db7b557a7a88fba059561cd3bbd3e97614d645492170f5b657e5fdafeb908989e063e64e52212d82fe8edeb6f815134a1fafc388fe6f5bc9bc52a24204ea99c9a892bbf4fce725230ab0e5e6f9cfbfb68d7b187fd328b18924fdf80ab9f28f2e1fbbfddc094b2fe2f04a40f2cadb1c2b1d23c5cec30ec0c1b115ec0d18d1139478384411359ebcf96429be95d26a3ded7e56e679f30ee1340442dd609c14d83454102861607a1645708e5fe73a2a980da26391373e940614070496d6d874fc1f08fc36e8fd8d5e4f443e045c0a1c6a7767cf058d11fcadf2ad09910f806ce2a95fe5087e6292b8c765cc05ee73b93ef1c51f30ef62aa1c75e46df55f68a5bcdaa9f1f2b1a5f5a8a8bf96fbbad88bc3eefa27fe6a9dff8ef3f79743f3fb9eb9d615b05958607eef30d6ab3b9419f821ef1030dd5b9d20d79224fb7dd4fb7f03cefda3a8f0f38260991a8b7e06e89a91a9217c4fefc8e93b7c4ed225bed027bc66160a27a6505edb81b1c1a4caa9d5ba36a94c92420aa5ae544b99aeca13e0c338df71ba8ccff521bf63538520c85df5bacecede91bf0578d87dd2ebe2b5fd68ff612f618f3b2014620704518d3a68407051c64aa3a42f4129a717e47a3d29c7b6ec03e0a7ff2347f228bd49611071a9f62d4de3f7fde47f112c7d06b01c516622b585507963d3ef71051639359853a39f4291a51722993fce68f4444890bb6ecf944cc5541949131b471075e09ef3d137cd488d94b88a5e2527776ceed3a9e38ddecdeaca101dffe3e056bc9217d4e20446dce0e88a184cad1d97d068070d797a7f154c3d22e0b7a50a4b69d9ddffe099b920c7edd6b94217f8e23a331b01b25fc03abac2c7dbd60b541ec19f611af5c1ad866bdd0b01f9eed125be48ef63559505e445d95c0f838b7f42788aa4cb216e5e7d989c4412fe2030d2ac626e8ac56c4f9acfdaf08dbd4235f38801df8617d27acd28e7d480e0bf549ca774b9492e9514c4950813a889a1a2beb4e710dde2d3dbe842d5983cf39e296a8b85c844962cb0b80421bd533a91d3ea144f8da993ba9268c58202a69939b0501ed09bcbc52d1d26c2ea911ba1d119a9452ca440e594fdc933cdd2642e99b8a813d9280237fe747014cfe405bd4eceeab148660680d16fdfb975a52ff8c5790ceb02ecb71162279e4b1fbf1c34b90587e4d06e7dbe64a8f5aea53d28c776634e4f07f95c5fa8ebfb04dd5ab391da26ba4b7124ef9289255a571f7d3c29c620ce95edda50321912459a248dc4f93cbd2acb746a55a48d2a3a8ff7653507425f9b6e9175b6fa54efa1d745c011e892ee6e9902d85e740cd62922f35635d222f0cbeb9984504a97ad5a93477bb3d6a04075fbde78d63e035cff9cbc65682353af07e59c5a90cc9f2992c301f95ddcbb8a9c154f994ad77ad5a3d3be659e041a3e2452c2039f36247a986fa70f129bf8aa8f6f7f5074a6bb4f81357db5546966967806996e635d01a76b7b9e44c0236fc59fda817a8992d220025884073515b01fc7446fcf39bc1b16d9a83bcbc6ddfd93326dfe161f87216719252f92e1e39b5d5a4de5c5a9e633b5b169dd5331e9618e6f4b860af1cf5f858d477b22a2fc3e1ee29dbf28b683c198eba57b622f86b8b81236afc6c01076dc94790e976b8d738d5c357972a5c16cdfe2b872c1bfb6ca1ca1cc3c6396049b82f103826b375542151ee1653d446a4e1814e951792e8c201f53ada3efe3d4e0ed8bae72859a1cca887c9a0a1b9f964e6b8d238ea38ed0e1d516d44483c962d03ca8f51b9370f3ae1c96366944daecca4a60e18d2742d06f8393b6f1a0f5bc429a7c06559380cc255b8e6155d5c4575ad488034f482505c4824c00b0727ff784cca767de700fce9beb21fc0d8ce55e9cd54b7fdc89d9418e411dc38ed82d9978847361be06dfa54e687ab5e0750eccb605f86d295dc98006e4a8a4d381ebd53e33b0e9c0321d0112bb27640582df48ce8b7bdc8ed49dfef20d8f8671b0343b00fb5db2ffb33d225ab98fdde6737d6167501759932d5386fff1e45537a8312c26bbd7ce2d4b62eaf994aa20b22b60cbd5099a1a34c38476d6bdfdd716e91144b864e6ea11627e00188f729cef13221ec79da10eae186bf855df01ef98a4cd579dd05891c3d1da70947e516f3bb3e58e324df17a63bc394f34851c504979598dbb9c00135cfa21d6cda989aacb3f03d418bdfe68212c0e27001f00f2fa4be8939d660c196f04594b9b77e34ee91f913f548c23ce308b427e2f9ec09373623dae22ba3b751b0e13e83d616b220972d77b03bd48732db081a574517cdda2131da1cdcf1d20be542417bf97a5279991dac421fb0791a3559c03dc3eec98ce4123eb6027340870de5e6838f6534d19f506075ac6f4d5ef532a28dbec4184478364d654d378e3b11e971adf9352e1495c3eb474a4f8810abe84e68a7789311f1013f5b9e232df1d083a8ab7fb3078a0971ebebe4e1fb83004a5882313e5b0d13c8782020ba851579ca80e04a7a171e88a033d5dc3e403c986392845486052047b08fbb36b860752d05587e1858fe70592b8f0983ef243322e8bffcd5ac2b34d1365158b966f477195dcb4ea4cc3f17e77bd76a8c03c796931e298c5b344e1ce0ab69eb414e049fd9b33acc028ff92e69d2e847c8503dbed96fbe104bfefc03975bb5edda2b91b57d96ce50344606104f9586bbdf4f0a4b77080da42e52c00e3f9add22cebdb52dadb06773cba51a0fcc2e72ec9b082e14bdec64a63939e93bc096ac606e218d675b69a767a9c8dd659fa736f000eb7e2d7deb6b75fb7616ad1af51f3be9472e17ac9509c7caffe2db262e45bd2806296da52ca5a6ac73cda821a3c4da4b5678737c38736ba7c863c28940b222e6dfdddc8331b465110ee9e10244dbea2d85c5a803a80057a8d1880e8575380aa0fb39331e30e7d430545308fa7390e83b25d171149b9725d2fd32d36be7acb8b9e04362734820eafc0a286ac0254f41af34048f9939b6dde97d82f93b5689769fb8c626482960dc9df5f93ffcce5033282c3422914ca1187e3f770687cef4619a54f12db44da5d729fa4e2ece8e08e274e4643bb35e9a8df0fea820a38eccbaee801f3198eae8cc27bb7cdc97f6178e88fda76a45064eecb8b490d46c9c5d527bddf74ea3240e166042606bc5007aeebf252e3f7d75a36f30f56887decfe10d594b3c4a8740a1f2039e9ca9e39f2c7a7fb8b015ff304dc105e4bdea6a2db7fbcb94d92cdf9446310914f00bb813911741823042d10643d457ff2b337781e6f8eed1029b7a8697b7c099196927aa00dada49ab74fba7013fd1cc3a76ee5365c9347c7a9fa7c22f71ec43ace22cbf38e9766148864b3f271c3ed702466928692696bd0ec3f8180140e899563b1f1a0721d0d56230c2a92370d369a9d98a3c872fb4b3c6fbe358125fe753dcacdaa40be357f92ce278dc29c4397bfdf3bb04e2a223e25f0f135b0af948f12add35631d7418cbf99ddbf3dfb59a107f9a9c7a14a71b7fa43dcee32d777027853b97caafa9245293ca4356b6c049b61f76d991f3de29ce63c687b7f60188542d1d30e9e0656ea6beaa88dbd253232b5c2340a578cdc727c9ff2e78857036c19da0049941aca95ac24638f891e320c041e106a42dbe56e4c083079423f8a1ee52178062d7175d7a68ae036967219c1918bba3ae76e1b29d314208af05555b808a46406ae9127307e749b177874fd25bb20ac1a0bf7ab66b326d015dbbb3a221607f679ef4bf94dac16de1bbc33c306a6ec4ec817dd595276fa2f45511b88143a5df063d6fe0e0574bae545f02a3ae17336c1c4e072add3082223ad3a37b6cd0da1553eb1ccaff0d18ec8f27619e100984f4efec4d397874e2878d5117e5b07a00571f7b8de02072e691de11955a162c48a686cc3568b4ea03b0b900ed6cd8f1ad45de9dda8ee04b4e5a0b5aa5aff31d81c750a2cdc8cc12dd06c3b722b3188781daf07a663cbc38800051f6d15bdf408df7b909b5afb8e51026191f8a118aa6104621d9a10c0abfc0dbd44109dfeb842c8f8433aa1afb5757aa9bf3560ba57699022e26b2a8e24f51998146f2b0bdbec5fe85fd74901602eb6935540578a09d490afe2d8a6846ecda80616ef1451ac3c8f8699476f55936b7362b25918c471cc16364d1925cdd52e812172820728e262d4d3fa674ea1801dda6d3ac7991443693d08be66ef0a064ab7bdc039323be4b94971e86f5b1840b7eb85c1b1f344c8faec8675096deb89ce31796782695211e3ebd0c1ec1f3bba319d4d1c345420f62c3d5dc23d9283ef17159c9a903f40543b915bde9dcbce0a95609080895c866e3ab30b4b96b67217fcde48b727cb205515e504d29b1d6b027bca65601e1e13e2148667a23b3bcca6d68d5f0bf7536877899a231b2d09d5e4ad9d6ae2a80dcca4cda1d4ecaadb6c0f4714bbcf8e5a108f5170ab8caad0594a6845446899d0c5c2df02e8f47c52e63f6ad835497c1cb217d7e1517c406b096ab9971b9a7380db854a80f94f0b39f2bf02a0e7b243c6fc133e053d171c2f5f0409a9941d4595db4c630a947a0b333753e55c045f0b41f825c7908eab22dc2a9171388001e3202362d315084fb25e9194158180741be0443e5c8a83d0c9496ddcb10b3c49bbd4a5ff0433a2a417a795a1bef4aa487f4cc4c9529335ad75b334756fb0b9d2fa12eaf8973bd9151f7436749d855466250420f0942fd1b0a4d271c42e4a27b4df01e3e9a2eb2e5b7b8153361d94861ffa63ed16504dc0e5358cd38652b59b13e0cca1ccddb2dcd58d07947696db6186c47c63f3aa4eb141dce7699f34906dbf0d6f04ecf75a830d3462943514370f1550e1d75140156cd8e272169321ad41afacc08b127942598587621df4a740c9975c8069aafd2da068ee8a3e80a7f0f164d9ef4b82273489fe9ad393713e4c5551bf770e8aa6e464720b83b63c8ae2a1c0a16d04e6859224d11b8e1731f2b0a8dba3528ea002315074004348f18d751346b7ad3d8b6ce4c777e31271efb46c57f4100c4d4e28b0bd4ecbeb84a80e2eee0e308c74d9684a8ad07e0d629cc9cd6da510a18239c27297104a586c16942957307f4b40c067583a4559f0d5731d0fd948346e39dc2a05b7a30eda77b03ea9619f0125094f295ca4b680efa01da16f01fac656ce83a027364846f0aca1ba32cca2a0ea03695a5d7213ebee518624527a1310e48cb30dd027733072ae79fbf88d5645130ebf4f8e8d30c6df35fcab95a2fcd5be26a9e3ffb29898680a8fc4adaa810e68adb65d48005a1d0c9d27d2b69ddc1e4cd704360042ef8b27046594b3250ca90a5074164b15f937714a9803823eae6148605ec98d95efc57d0e62dc87138809798c6c03bff37e44ac567f2ae30e27487be6d242d035623224edcb5f7d8ebbd4ec407dc92dd38d208c14bbf89fb252ad940c5edf08590dd2b3ab84c90870f11e4c314565facaac2dfb03abc4e5cb1bf20fc5ff3fc32841bbcba1bfe0b0ef3767d96f59013b9adba3c5e79af66498024316d6d6a5c58d7033e91d306abdf2bfeececf65f3b3c3112010dde4eab461473033187872c322da5fff9956d517bac1320b0f5d1ef1cdb996c31b71dcfa5082eacc8c6817d67657a827ad92a693fe7ec7c0a35dbb3c8ed9329211fe0753a037c1432cf6470a13ba44046f8609453b4425a2eb307b9a812601392f6adc6eaad7c1efccc0669406334b644c4958a987ec38a6acd178869e86746c8da24fb2fa0923ba46f2e7d37d8a7cee6503ae287832557a67e3ab3fe82e994f4c6f6a6cdbe15614a23271559fca73d5f9d908f2bab798f72ca1437fa2322d51e57e0c13d4ccb62ad9ba605f21c7b353ffeae87401129eb3a7fc60c2c015f7820480228b30f445af2bd321bf6886b7b8697803fd41fc433e0d7aa0df1801a4f936302a7c87fe3f311f4d4644038c2e02957b548eed4943c0c8c9e9fcf1260a0a174b14a9e2b1c37b83838c81b6f86401d1b12542ac2f662cc9f424d017933195c9aa836b8e9291abc4ddf998bb3b99403d9da8be6f08d846e16d6ef1e5c73154f627806f9040e9853eeff83ec602ae55bcc36627f8e2b17a6548c66d8dc4651aa5611bbbac0eb613b29ba18473106b787a76c154dcfbf9740f72566484d3a4782579520483af3dc0e2ec7df31c27c57c18d00a912393d31ca2b2bf28b9463ff780c4295920740eeff3dd258a104f886683f26770a2360fd855728d00cc0245780a78404c9944ceca5767484144af80b23893cd429d6468ce5d6ccd087464f83c862737e5a746e9ca6887ce779acbb1209f997da020d0de105c7c9852267c3ed1f576191ae3f20da0214cb06253833015567a22e0815c1370c1dc3832b4f1e77f1491c4b74bbabf85d051a8fcb39d1500cdcf6e6bc45ed3ee770971c66d8a854b00794f26550c9411e54c3840a063747504bff3cfc66b0c175c3809a2124f043c13c82b882adced5ecd7b56353a26e1c2540560cc4e1e01ccf9e0c8934e5e00a6ec13d6c9cb08c16705f4250724659587d106f289c6fa3ecc90e20310800b6323b9589d7e1d2b3f230c9e6ca02d019928b3c21e13f38541b87f2e608230a0e4c161c24048d0368e387c4d71fdc4409a2dca82494049a6f7c3d757f4494109dc99fcf94c6dd2ab2ccff61533f31136da70853ad0e1b9715e57fad703f31fe930e1c5cd0cbca0baf4ad53e4ef1ba00db088b7d1d1e39c469253adeb29ba459227418b068819eb96904a240e90f7b3a54a2b0a398109cb676b6370ab86bc4197400407a02a2e5a26452e5aeef08296481afaa0a2fccf306e5a9d88267352726ec7d38a438ef75b50234719b29f83277d1fd31dc778cd381a481a6bd198c3b2c3a5378b094d0bcb96a121680b55ca83cc89a1236ab8777220071be7f85b3b0ec83ea1e5334d1017fcc818b1f6411c85552d53fd3b915611bd7359b14466980b72f80e769cf6f9e9ada0bf5a949a8a3fb441c1aec3637e80dcb5834657cf1b5c5e6ca263e31121429e94f1d2566045bff99f87f5d64c4641737057d8061fc65109615be0e9913b80923ae766324c5c73d743f8788ae852105d0a9168ca47b1d6a28b7d9fdc87ecf2c84a98bffc6c75be7057151cdaf46f887ec85d30ba302fc5dc1deac9ebdb34d493303530422fa3af3750350f326f9ba76fde21ab798393796533fef51c3dcc2c7ffab00d941e1fd13f54a8806925a86e2b303abb94094defbd96adcffe01b6f5d9cf15396454b6216ea5b3832bf99703f9dd3ca0127964d772a7b83efbcb727df6e53e873780de8930f15c9f5d3900dfae38918345a121180794ae3a142e26b3ebb3a781858f551f1d76c6ffdefcc8e4ec797df6a85c7f5b84939af957eda68190da8a4a0be756e25950babd27b8b8cde606496f99c3b58cc1d820abc7c03574e6c981c6e7c798a0e755c8674004f113a401599e7d40b803c9a236d484643a262511ef46172ad7621a7b5c731a2cd9662a3ea7840d29a0dd3310424faa60199e15fc4c2199e6e9d0bf42bf35e640234ad2574934b1a39b75e09440d4a04788ae6e6b939fe17997060e904765ccde109d35152a54b1278cd0c174618edebbfc689a3b259a9e746d768ddf44f7ab9ec7a40b3af72ca5c5acef3508f7acfd19bd5aa5772a83b02ea45f22c7e52fc6e896ddcfcacc509a7bd1cf03c405cc1310071cd1fa8e0f58a7001cf3aabf0aa44017787f7ec1deab6396dce01392c96d82f47730c31d1b1a5e6f30e81890374ea814ad05d4cf331164d489826673570b205827c4588fee606480af3180d9b9a62e77008031d7f655cf502f4a17fbf81b41ad29d1adc38c95b970f4d44150f87b7b457acccacfc7879e23002fdea69e8b50449ace8f4cd17fe074c31062498396e862df39d14675c30d76f606472899665aaa20f0834f8f81c6886496fdb2aba6b240693921242b4c09243a02400aab791b9c4f7ecf8500f5ed2ce0db05ea5626d256a9e4b520edbf6519b21165cbc71fb4d364f5051c1f81f9b5b11ed298c6f17e4fb77bbadcd3dd2dddeee8e68d76eb387f7bb92b37d1d7b17f160c17072b9ebd1c141701c1d2f4b370673170a820f9aee32d73836de0610c25beaecf5e7ecb599a2d95a59ca5a2f650a8878b91b90fec89f2db4866069b846c5703e31a4c2c051b89a5b1d6832515f4a60a5a2780f5a641a488febb27e64faafd0ce686c779e8100132858a5420814598934a5cee22319b3d882a4ae879c7f0ab4436e072257b7d0b8bd1e3dd7323b5ef3211004d438824194d42227f9166641424140cf961086929c9ab72baba077b376d867835d88016c7fef11e0b026a6f6e0467bf237758043041095bad4cb98c3e09e1342b8601766994bb78d6ddf3f6164a5f6e89069827234e35abb7cfd8bae5210285fcd7e0479c8497968768a10313f3e80f08e765bc9d5e9d8a4be6e9ed5b0302dd676054b16bf6c8c7a14585b2d26ecd4f6a538118a3640da763829db730789c1d84eaff934d7987a3495c1d5e76460ba277226d0e3f6a529f45702d652378fa4de18e0e1a108f917be35f82409745d64d07766a115e801804e4a473220ad167c7eaae7286682b5df619c8848600e1b118fc8b9985dc9f5b983357398918b02330104d50f4168b1844c7a977f181857faf41def0f6ce046813ba1d438147959f72b1caa09fa1a5cf2a3c8b2ae38a845013f8ecff559629f1f58d78497cc13f7e4a4528cba4630731941a8c753ed663c113b2062cfe06c4e255fc20e6f2c6aafa3a87d6dd9c0c921f7023de467308366d322c1bc61336152c0830c22d54434ca76ac0571a89813dce737b69200b27078fa6080c7b5c29b379b9bea59bdad6ac79921012138b9ed38fa86c28a3acc1fb6dfbd3ab811e0357e6c67ed3576f8e948baaf83346a28ba29635f950941746f01dcb2a8d6e56cfa43f504aee8ecc545063160d4df8391406c50320cce01144fe4488ee33c86a31265a8705828fa18be1d5b82529336d90cfe408564f97a8816c70994884b52f5c4213931a6b6e754b543fc2d75054c7b8ad06b6912618e16eb61cfb2cbff56f731111c1dfc23a1be3c0d0b68cd28150e250a16713605ac13f5b10900c83ae1b497c3fc5ef555170026ae46a7d11688a246d6104bb87c0e965c542c421a0564c4df5245cfa4d01f9e8577e0a766a7962a26d3cc964252123dbcfc39846db13108a53fe65ed90fd882588169d79aae6ed9321adc9b9e2755e42096a237f86b2f150b5155a61d4d344e95b9e1b20f63ef2ad253db97943f1b81ca94cd6734417ec78cf9891714b867930f1c75ca229fbcb0b6d1bf2716d234999fb53d3748802e33ef4f166eb21c1181cf9b97e65081256bfe75b2146e41cfdeeb2d911f2a12018c14be73ed7dbfb571d7da6c15abae250da485c0b7e972d4700e3e016a63f0303127081fc465dedc61df735484b363aa7db74afc126858a441cd69d8a8d40b754525ed2c7d73c48254d7f1e08886d180f450d00acad96145274cec0b12c79bf7973f7710696d508b0a571063fba17b040bb5cb338264865bd12d899762e8135a7aa58b25483dc0bfb2baa9b1b83826131d352a8816bd49c00fe16db7d00c47c90ab4ca827c909b1c446286807bd2aef918344e770b433b06e71fd677dde02f6bf332765b64e649aae9294595165e60f4a0d7b297259f9094359a0e7454d9d93f4d141f685ed01374b7c79ee51df549b8204d843f4c40e4247b22da922aba2294318164eca67268b5660ec6ba00bfcbc79ef13b25179da7b4ac5bf3d60ecef9052f6148db2bf043d5e75ac3322759264fef4f182da176bd14b027ee908398f218ee0f2d2f18d0c464ad2e1935f048d4035818ab111b16c52ebfd648814932e271cac8a0102bedb1a12d5715b122bd6c12e94b78768debaa5c3342099535bf8556c265b89942cd19532b0e311822bd68acf33f0ee2af84dc5712411ae9efc8df36af4f2583ce404556769f1b2d894b36e13921c47619d4661fd91b6f18af8706318d1b048753e36ba127c71fb056a14d639a142661b77d3d270d5f1b1146b0576d6ba3ac2549b1c568a11f1a2d5b70436575660b5a81ce41e4a9c7b76458be1068eee86e76ff652d2e964434f004a001df769fd68c7669953b32d7502f18187c6fa6dc9fb9f5c668c2bf0dc913a6fd9dccb1fcc3a7c0976af4ac937a89f66a3cfba8c7cc39c633c2cba5ad5b7ce0efe7459f7d253411471c9e9c5e9e2ec981011badafb892451f4fad043a654d24047645a41c36cda479bd8f86a95ca8376ccad332a8772fa66e0ea60a543da649f241354708359e88adf9fc91159c8402bf32307317e6c35bcf05f74bfacbfb761e86ada983c36add1c0d31499992843787755dc55923565fc44cef91543f6a5fdc3c1db3c58e40a049e6f0514daeae60e9c98b9cf8ea732ca4db03dfb7c3b06238bac2a876613bbc4c0f7423e3166fd768cae92c10de2744a72560eb65e0ce68e130713be98d57cbd95b8f4e013825882e4c450c5659cb10bcfe382c6282a8c3bd7841903d5a3915643e0115a013b8acbe6c6dc5d4372d9d31f2f120634bddda39f6afdb716a90359ae78ec7a022ec31a5f04e2ed0573893202978ff39aa3f42b18cc79af1b98b90b1411a439454027740ba4039cb42ab4a729e0034f02a1e06a040714da1c037013b31183d93f8b3c4e59a7d2a245c82be4a9b7acc5eae798a7095cfb7140e4c590f14da3460719c35c0b4473ea659f452a17aa367812c8a7b068eaa0a4d80601b7eec2d1a3022e5a6c4de39c5b349d7cf0a19159eb03cc4150b41628c106809b4ceceeb9a8b115ce31e1e12fc5aa78907777e302a7c6408ffd8bd384ea29d2f39e00b68538a9ed3bd1b23b8f1fe073727a71881f13461125fb1969a2146fc3aefd0972ea87f8739536ccb1bdc06b94c9ea03f05e5fb5071c51ae8f6326362a2e19c602ba12811e9f220dfa4af617a073feb0c9507f6f50b420a73e04ecb4c59fae1ddaf8826d1bd6888a7cd59875e9ed5fd32eab98fd957f19fd0b4c0789afc055ffbcd3427358e5c6e3fbf22f03845359ab595df6cbb120ad8cf5d2810018630372e9b23663f043839e0bb99f4db4f14c9ea19568770d060d0ce16181374b163713a948929ffcef126dd34a5463c74ddb04868ace3eac2c331802b930aede61ec40345b4da63138eab74c19409eed18ffb702c9a064141c0e080751666a93cae337e3db34bdaf866f1ef8701a59e96250f2822d18e14e8a7e55c71c6aa3cc15e34a76c09e6ed920748694437320191213b70ee5ca14bb0478238faa7b9a23cbdd8dd856652ca03fb010b1c89223e7fa60da461364be6840eb6d44b0b82c9720d7d6fe8914566ac4c0d56ef4b2247e9a02629685e3e0011b67ce843ad319f878a9092c070e9dca2e665ff8578c3fdc96657ec7890cdd7cc36be3fefc1aec83fb1cffb18fd9600692707e1f0a087ab00b4eee3d12a87173006cf080db669b9c8d77f9220c045d88774ee45a6441515afc1213c6a6ec071fc544f781458f405309ed48776a06ec19fbb54369054ce8b449fe588be56f93d3d4d9b87bc8842fd88c8727fdb945f81887d7ebe891647721c6a20ebf0c6b309484bae5c6ea57bc90a0016291c71788132b5529937386def22bbe3ba6b886c1c8c33040079e01f9519f16b9376fbcdacb7e1b307bf60bacc6fa74bf5d7957c8c6e556470b351e823e00e3b2e4fe7ac584b45f24fa61ea7ca0aeb9979f4ccaa05e7a327aaa99e4b1ccceb29b595320bdcbb97f357b4f0e45b4a4c592d9da3d04e67160bf8a74dcdd973ffbb14f006e6903da37a0ceb89afe85d22bc0432d64c66cf861391dfb41e51ecaf875a86395b5012b26fddd3a371891617137610398c3894a75d7582898e65ef5094230d04b35b6a843f0912f7979", + "patch": { + "balances": { + "balances": [ + [ + "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + 1000 + ], + [ + "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty", + 1000 + ], + [ + "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y", + 1000 + ], + [ + "5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy", + 1000 + ], + [ + "5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZcCj68kUMaw", + 1000 + ], + [ + "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL", + 1000 + ], + [ + "5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY", + 1000 + ], + [ + "5HpG9w8EBLe5XCrbczpwq5TSXvedjrBGCwqxK1iQ7qUsSWFc", + 1000 + ], + [ + "5Ck5SLSHYac6WFt5UZRSsdJjwmpSZq85fd5TRNAdZQVzEAPT", + 1000 + ], + [ + "5HKPmK9GYtE1PSLsS1qiYU9xQ9Si1NcEhdeCq9sw5bqu4ns8", + 1000 + ], + [ + "5FCfAonRZgTFrTd9HREEyeJjDpT397KMzizE6T3DvebLFE7n", + 1000 + ], + [ + "5CRmqmsiNFExV6VbdmPJViVxrWmkaXXvBrSX8oqBT8R9vmWk", + 1000 + ], + [ + "5Fxune7f71ZbpP2FoY3mhYcmM596Erhv1gRue4nsPwkxMR4n", + 1000 + ], + [ + "5CUjxa4wVKMj3FqKdqAUf7zcEMr4MYAjXeWmUf44B41neLmJ", + 1000 + ] + ] + }, + "sudo": { + "key": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" + } + } + } + }, + "id": "custom", + "name": "Custom", + "para_id": 1000, + "properties": { + "tokenDecimals": 12, + "tokenSymbol": "UNIT" + }, + "protocolId": null, + "relay_chain": "dev", + "telemetryEndpoints": null +} \ No newline at end of file diff --git a/templates/minimal/zombienet-omni-node.toml b/templates/minimal/zombienet-omni-node.toml index acd5b121c6744..55539fd208620 100644 --- a/templates/minimal/zombienet-omni-node.toml +++ b/templates/minimal/zombienet-omni-node.toml @@ -1,8 +1,8 @@ [relaychain] default_command = "polkadot-omni-node" chain = "dev" -chain_spec_path = "" -default_args = ["--dev"] +chain_spec_path = "./dev_chain_spec.json" +default_args = ["--dev-block-time 3000"] [[relaychain.nodes]] name = "alice" diff --git a/templates/parachain/README.docify.md b/templates/parachain/README.docify.md index 47385e0bbf197..0d6071ddd9511 100644 --- a/templates/parachain/README.docify.md +++ b/templates/parachain/README.docify.md @@ -144,10 +144,17 @@ export PATH="$PATH:" #### Update `zombienet-omni-node.toml` with a valid chain spec path +To simplify the process of using the parachain-template with zombienet and Omni Node, we've added a pre-configured +development chain spec (dev_chain_spec.json) to the parachain template. The zombienet-omni-node.toml file of this +template points to it, but you can update it to an updated chain spec generated on your machine. To generate a +chain spec refer to [staging-chain-spec-builder](https://crates.io/crates/staging-chain-spec-builder) + +Then make the changes in the network specification like so: + ```toml # ... [[parachains]] -id = {{PARACHAIN_ID}} +id = "" chain_spec_path = "" # ... ``` diff --git a/templates/parachain/README.md b/templates/parachain/README.md index 15e9f7fe61cf0..818fbcb693d1f 100644 --- a/templates/parachain/README.md +++ b/templates/parachain/README.md @@ -146,10 +146,17 @@ export PATH="$PATH:" #### Update `zombienet-omni-node.toml` with a valid chain spec path +To simplify the process of using the parachain-template with zombienet and Omni Node, we've added a pre-configured +development chain spec (dev_chain_spec.json) to the parachain template. The zombienet-omni-node.toml file of this +template points to it, but you can update it to an updated chain spec generated on your machine. To generate a +chain spec refer to [staging-chain-spec-builder](https://crates.io/crates/staging-chain-spec-builder) + +Then make the changes in the network specification like so: + ```toml # ... [[parachains]] -id = {{PARACHAIN_ID}} +id = "" chain_spec_path = "" # ... ``` diff --git a/templates/parachain/dev_chain_spec.json b/templates/parachain/dev_chain_spec.json new file mode 100644 index 0000000000000..0650d0cbdcdb8 --- /dev/null +++ b/templates/parachain/dev_chain_spec.json @@ -0,0 +1,108 @@ +{ + "bootNodes": [], + "chainType": "Live", + "codeSubstitutes": {}, + "genesis": { + "runtimeGenesis": { + "code": "0x52bc537646db8e0528b52ffd0058acbb05de71072a1750106868947468aa038157644bd8ada3180b1f7c6900a17c97643f148b47a13c9cccc36f5c256c578e62213a2ec291fee88cdbbd6c0497005907dd7edab3978cf6e001fe2322ad114208d964efbde596016d1a7e15e515a7f77eba7ef3184d3b564f31deaaa7574f3a6e5d7bd77ec2aed5538cf929def41d7ed2be433dc9c83c473de538457d473dc9ec780e2ac777f889fa0ef5b4a37a3b7eaa72ea15a5987a8a798e1cbf89c991a35e07cbfa0e1d96755915cd513303f3348a2b6be6cd2b4724d188f1ac8121f1a494f6348bca71456ca6666652124cb4aaea5d1a950306d3662aac0686e2c91689c61c2fe6a87953c7877631895e0700cc0e968e4e4ccdf52a93e927bec954cdccd497b4f912e6c3c74fd67df8a014457abd09b36e8aa5d24ff4259dd224e5d05eefa306e63e0e809f660e0093e9a7eba61d4c07c04f55d4d1b9e8eb4b31c74baca3a3e3e327ea3eea494707e61d003beca0e327ec3aaad79b765c870e3300b8e9a79a9b4aa51edfa19e4ad761879b7eb2aec34f5acd794c0cd7bc5e47878eebb063c76fae4387ebf809e63ae675cec34fa4f3e080e01c003f910e0097731d00701e7e9ab90e3fcd9b4a3838f538a6e3fc448fe37eba8ef31d6e6eaec38e7ad2514f3aaec36f76fce627ec37eea7f81d5efa497bc9f5d0f989ba4e3df5d079ada71edfb1e3a59fdeeb4fd579a8271f3e9e534f37f574f39898e3d4938dcd6fea09e73131bff929c76feac9e6e6363f61b5674dee3907800e188eeba8279b7ab2b98e63d8eb4fd66b3de1d0e1f5a7ab3ab126771c07c04f567d624dee00a8271ff50a67babe433d59d665ea49e6d70ebf7eaaaa11573d59d771eac93a561dfb895622d6e48ef3538e1ac49adc4bf5a493e33deaa9d653fd0ef439f5a4c35feb69c9f0a40d657c81c11a2980e243a360c51428b8010d2fa0220a3e54d0f7d4d4b926775a4faf9fa9bb26771e759f69bf4347cd71eae986e636f53424841e9461cb1935b8c11384e0533394851458408111aed08633b6f0a9f909a6d2acc9bda69e68ea3e530f752bf5fe44aada9adc73a8f399f6333fbdbac49adc67ea2947bdc2f9500173997a223da65e01c4878a771cf504333aa99e866870050e5768e0061b664883cfdb2c78e9c10c5090851b4668830f15efa49fb4fac39adc49f534aafb4c01a8db0fbd3f5115c89adcb34af34cfb13d723d6e48ed5d355af28f950216fd5d301c010e30c3a60630a5e70e053d52bee43058f22acc1076360a10a3794c1870a3ef5d3aca535b953f524eb3ed38fba01e9fd29d6afc91d56ed99f6fba12764e21f5f98879c63c0b523d0b6a80a2f7a8b9ea0d11ba9f95b85dea2276af445e2d2369bbfccb36ce2ef2f973d21a3a9cb33f5f84dd2c925fea4904b7c0168d4b75916a1aa7c0fbb2cfe729874cd59fcf5dcf28877e4f99077e6f993a2db97a4db93babea2939fbf2675fdb259e5f6f0a4f297a4410ea888d6f29c05b12d2bc62517eb36d471ca4937d8db7bd06bea9b0989efe1ebda8c085537d8d4ab4fbabde725ddb66d934b38b13dd1f3cba5f99e5751a053c34fbae13445b725850d815ec36f260456c8a547974beed341135ccb67d0f6ee6b5d405807837189864beeb1b7e8c9172dc325f7adc9842c97701c74ce3977f8e8de9de3bbc711c2cf86f5c6483c7cac9b6b3eac41aef98fc850735d2ead8bce09e15ade81f19be44b3a1bf24efcfe8ad435a4db42e023bd81770d7fb94c08c3efc20c81862af190fa0043938f7f7799905721dd8e741983741966083470c1a56dbecd106c20da1fdb2f13c2df4cd6cd84402ebd2c8815e2dec392c28e741b52d743cfb92ed7fea0b9efaf05a26db6efe18db936677bf3f7cb3bee7ba6d70e40d3b8f48e71e97d3321934bef3d4c006d93e977771ade817fbf6478071e8777e2df2f8741babd597c0ff73053273cbac9d06cf7d9aede18e1bbf35d0d721d2ba4aeaf1d80b6bdcd75e3bbf7f0e6bce79c109e70d1dbecbdc63cd067a7c0d27b8c775cef6978677bbf395b7fdeabc3907306b87648dba2299e5ceb88b42d92828ddea6e19dadd47c7ee9f1bcf37198c7f9f035369a2fc33b3c041ba2d81fdb3dfce8932c18efecf99bc3ce4a18514019d2f8dbec778d77b6f7fbfb1fcf932ceff0efa5c7f324cb06fb1dd62aee9325f6bb0cf3b0cffbe49d2d2242a3df31de79fdbebcb37f87bce3febe39affe00e9d541c80d395ee2dade9c4d6bfee5fedc37acf9db7beecffd671de5a28cb1c93b9bebf7fd6e101fb4fd6644964bfb577bf862cc51d8d80661e2bc3410d54e7090ae8739fa3a7322312eed041b6d10268ea88114d0eefccd8e4022a276ef619715d188da1de39d6dfe66451c516f0ed71fad578721760c85ebb96fefbdef373bc2ff79757269bf5cdaf7f090730cb8ae5d9821d050854bdb904bfbcd842c97f63dbc38970634f72d2222eafd45da9ebcb3f1ddf7363ec0d0f8ef9b3be4d2fe727472692f008defbedfec0857c825fe8fab934bfbe5d2fed1e512d7e592bbcbf897c31e75cd74dbd1c9a53d67fcebb9e511e4927b0ff7700ff730538c4bee43ecdc141f2f705fa0c20a2c1f577cdcc0070e7cb4c007161f547cd8c007163eaef001c587153eaaf021c507153e4ce0a3043ea2f878e2e3043e48e0630a1f44193095c1f486e90c262f263498cc6052c34406931a4c6e98d8604a83690da6361c0e4c6c98d630713191618282a98b690ba6334c523089c1c40553164c5330a1618a82a90c93134c4d308d6162820909a6239894603282a908a624988a4c5a4c58309961da62da81890aa62b987460fa812907a62a98bc3049516aa30446e907a52194805012a32484920f4a442841a1548552144a572849a18485d21925344a59283da164855251c909252da5314a53289551a242a909252594cc2831a11446694b29083db694b628e5a0a445e94ac9052515f4d042c74a0f30e8a4a1a3c60e6ce8a441e70b3a5c7452a033039d2c3a5ce89c4027063db6e891860e174a547464a0f34407073d9ee8a8a0c71b3a527a84a1071b3a5de821061d283dd4a0a3049d2d3a51d059828e161d27e82041470a3d9ca043450f37e858d1c3043a52f40082ce143a28e861a5470e7ac8a054a5470c4a2b28a540a78a1e5df46081ce1374c8d081820e137492a073049d26e894b1c31a3ba861072f3bbc6187364a524a534a2cd8c18d1dd6507a410905a52c766043a9053ba4a1471a74bad0d1420f327aeca0c71a3a43d0f1410f23e80041a7073a42e8c1861e41d0d9828e183a60f4c0418f317ab4d1234b0f1de8044107063a68e884a153d4a3a8c7107688c30e6ff4c842670c1d12e87ca143041d2ce878a173831e44d0c14227073a45d0f981ce153dc4d031428f23e86421c78d9c3572d8c8f192a3869c34e47c21a70c396ae42421e7083964e42021078b9c16e4b02067053928c89192a3859c14e440c939410e969c2a392ec8997228e4709133839c1ae4a820e78a1c13e46441add02de80ea8175407340734091409940caa058501ad42b3a02fa02ea054680b280be80f680fa80fe81794075406340694097409540b55023d02c542b9a033a046a045a045740c4a043a042a0615020d0205020d8382414f40b1a051a8144a027ac5d9f81aa8148fc3bfdc0d77e36f502bce064a44a3a053bc0dfae460f812ce846b39962be137380e7ee549380f8e84dbe05d1c069fc165f02acfc275f1177c0b0783ed52be824fe111061e62e0b1051e5ce0c1041e61dc3ce1260a3765dc3461c7971b29f020da11871d6ff0b0c18e37f0c0526382992976c021e607365dd860b1d9c2060736576c642003071c41b0c962d3021b1dd868617303386507155005335b5c1b5719aa2e465f8839818c16150e2e335c6ae8c802e78c1d5e70ce8063061c2ae0a001c70b4e156ee53ac3e5e539c0b142056a54f0183ccb2bc30b03ce131c2970a0e05c8163050e163855e09800a704375b8cd2b8de70b961b486eb8d1a146862a059c3b586111b68de7073e5a6063737b041824d126cc8b05182cd116ccab069820d510d1c627240a282b9c0a60b365cb0d1420d0d6c7e10d385cc1b18093d5c4042416506520a749c81ad60a60995171b29551aaa33602ca8d080b5408715b0292429334cb031414d1162a6d44841d2414d09b434c4a8a0064a8c944b0d346cc4b880b4039217338d992755969a2968d240fa4253a3e6494d153bd240ca81e685182a3129a8b9c10e3668bcdcb0a0868a1d6aa821aab9a2260a8d1b6aaed464a9c152e3458d0b2618646a7093c5cd0a6a56a0ad2166053bdc5023e55a63871b3762d4d8a006063bda40d3069a366e865033a54607352da0d92293031b16d8d4808609344b8869814c1a64bacc9c4087183a863073c54c17332d98d162a6ca0c0e666430d383191eccec6026cb8c0e66bc98c9c18c95192e665e3073650606333498a1325383991bccc46006cbcc1639ce90438d1c66c85186992c666630e382992f666c20b3860c1b326ab881c20c949b2d332598f1c18d143753cc5861d3868d1a6cd6b06163a60a1c2a70a6b001834d1a3358d840b1a1a2c60c356ccc98c0a5c0a120a709333fc8a1450e18d8d020c70974def81a325fc8f4204608314190f102270a3558cc4441c3861a2b64c070c304191e686190f1828c169c163365ecb0c28e2c9058109365e483982e3161a041834d0e766021a60b3161d49020860b8d0d1a1c342f5a170d0cda19b43168696865d0ded0bea0b9413383b686d6060d0d3130d8c1869819c468e1d6a809438d186ad6509346cd1b6abcd490a1860b5a117894e1c63063859928e048c38e2ad8b80087186a7080c30a374ec031851d61d80106d8c50e2ae0e8010e1bc0e0408b030c1660aa80b1028c1530558021018c09604a0033049813686d6864d0d880014373e3460b4d0d30593429c8cc60071a68de80e982a4829119485e483db0f102e907a434b82fa42ca42ef8046c022e0147611230167c0543612bb80aa6829ff0142c0547c1442e0e0e0eee0df706e7061e63701aa4292331d07ca93901098b511a466cd0a881260e2335466a188161d4069a354665b881c1680c232fa42b646e20138398178cd040b26204071215a32f242837536e5e10c3829b2aa337485590a220118de2b0630da333e4b401c260140556c20e34465f8c92f03366c0e02d9c841928ecf802a6021b18e8b8820d159b2c3026604bc0b4c8c952b951bda172035665860c3365b0e9418c17551bb02c482b90c9727d1985e1a605571c2e36466f18b9e16a6386614486cb8d8b0d3159d0b871b50123c2b0b871c10d951d6be048810718d8932b0d3bdaa849c1c88d0b0d171c645c30f2c2f5852a0c9517302bae2e3c964002838712ae2ec8bc31fac1a8078f02298b1d62d891c68e2e231ecc483113849c17ec88028c22e70d58458e17bc363c37d8d82047971c2d90d181ab62bd5113858d159b2a6f8b8e169519151572745031a1ca41a5831b21d87031f3859925b014645620d3821929cc7061a60b395acca451a96193029a028a82192e3628c86923e74a5586981dbccbc76033a58a42b5a5820246254706d80b9e466584e986d986d9c6688dd9250605a336261b530c323290b1810c0ee61a325bc85c91e1428606da1b465d68e0105345060b8907325664bab089a2668d1a37d4a06163458d1a3653d49cc18604355ab0795283869a376ac06053450d1b6ad45093869a38d84851f3851a376abed494a1a68d9a36d46ca1660c3657d474a1c60b355c6ca2d86051d3c5f4c514c5c482f625e6063361c400616a617e317b607a438f2aa4168ca610f36464059215d20b64884665e4c080bf2013850e2c8ca4f0d0808308a326c8b0c066059715d7183359187121071b72682013a507514c16e00a74aed0230e31698ca0f0283154d8a8c066063367c84c993163460b3353a8c8786ec84851ad51b1015b503a410c16b02b395bc8908014831e5f7a64a1b483ab08980d7af48007144653d8c400c710302cd7153c9e50a9e19181a3b0630c334e98a9c2d130e385992bf4a042678d992dd010a1470b4a55cc60a1c71370ec600737906640d282b40549062418f4f801a90a8e27603dd08922668a1e5ef4e0410f2a3da0307aa29385ce0e669e7012cc50c1fa82cd0b66d0284da1a3031d229d2a324f7a487115fd0a9b13e8c4a1c4831e2fe8b1860b4a8f2fd00c81e482111839d67035f48043b586aa0dcb0b3a6ae8e106dc01fc026281368042803180348037f0210564c1b1802680258051a0146f0d8f8dc786b7c66be3757963804f3ec553e399e181e1a5f1bc90f30636039781c9c058e02bf018380b3964602e2c060e0357e155b0193c90c050602af0147824e1569c0a2683ab3836b835700fd80bde01eb80bbe02c9c031e5c9c1918081c06f38087131c1b3cb4300c38082c857dc05a3832b82e3cba70282e0b0e0b6e0b0e0d1682b381b3e2b6702470519628712a40e2441317f080048a8800041ea080230e18a20146882812c410210c58804f27806707882005702f2801c087c9b272b95e27b345c52f4913243680c40924922c8104c9e59e124b92204162fdf8a17531d26eb04d9278008925486e341b6c50922649983881242849076a342c9b240889a0264d9868926449122236da95249034c1812545942869620141492451a2a489050040ab01108d064a942091841225482c808b25823ab0248992196c92241c40d2441092a0253ac04412109834196c13254c20119444092449927040478bc136c181254c20090193241f000a0112445cb3b2499258d2c41224482061c2894e92c492261c10b4e4034d249144ebd0b650a289249228c143d3629b8420c90f92264d4290c4c892269868d2244904a806830501134e3493243f4c9aa0c011aa55d924414828c18412249c2049f281241f7840114a24e94007902802005a16dbe42789244b24e1826d12b4240993244b2c61e21a95254c70e0466bc126c14493254c30097a220992102871c20924903049a28412263ee0f39344134a7c7034166c131b6d058b04121c6dca7a000912414828814450922160341588800925483011c4c41226726829d89f249a50b2012449823e9044922792e0d050b04c9a5800a649d9254d38f101244a2861e2031b40b20489249030e144072d49e2810e248980124994f88092249630b144024830f940eb6827d826499808628249134b047d008918cd04bba40926829060f20125900435a1a40912269c6824987ca073ac4ba2640348042df90007900425918409134a2c610209929a3ad5a0ac38810d557a87ae2d22226a37f46aa415032b06ce0947411c381c0e1c6e06c70c0e37338343c6f7aaf79e7c0fc6482db552be286516474f071d59e4f738c237619c713492353570882ac66a5d15abb82f4a19af78c55d1e6fce18ad68c518ab5d1821001edc18a17bf2c569cdb915dc2897b7aade7b55ac36ee5efbe29311422937325f96acde5b19256fdcc7114227217c33ceb952c278ed2edcb72f6e55c9b7d735a7b4acf72c4bca4aca4abe2729f8284bca48cd27234319253679e19b1624cd595df03db9f1bd8d7137462bbe387763dc061881d1c57531ca171df638ca7dd045c71ce3d2e878d1c118df684679cdddb86bbdf7de7b1762475c2877376edca54f2e8cee392961ac62dce91c49ee1659de48ad06dfc6ec8e95d130b9fb56ca988d0b0363bdf7e6b4a6355d5555525a18c7aa0aa2aaac0763bd175dc59615a394d07a2b1f9cb02656cc1cdd6426226d6c6c6ca27bd1bde71c84efc9f7a494529268dcdbb9fb6074704aea3dde7590079eaf46ae83724238e19c12cab970230f6f61deab647ccf91de93ee41685996dc186384bb9079e7db392b78e1c0f9a0744fbe2921e9c9f7e67b32c6dd3724becbc1402865f5a0940f3e3892f24129e5830fc607a5e48d713f1a45e89cdb7555dc8d52c6eb8a2bdf1b3d2c0a99918f012528618c3ee27737aee427e5c605bc08af17a384524ec9f2bdc703c2b911be95efbd7d715f7cfcf6bdbd9e842fe2ec24ed257d64b55bc98652bab70ba165cd39e79413c2eb82d705e1b3acf7a69c70c239a584f34d4b5670c6a86341f8668cf0c11877df655da427574ab92b5f26b3ecbdc71c23748e39ee4219df7bf0c1dddd18472feeaeec910b97c7db179f93efc927a58cbccb8ff7da477af15911c65dea8da8f876219c1cb99a0badc9d34929f9addc1df168659edcf1de933ade7b239a916904001f23687afc56eeee3e2abe958f92f2c5dd7d31be1737ee7b72658c711fbbf79e943ce77cefc9dd0865b4e68472e3c27d3b63946b4db8313e29a5dc95b2a480f7deabe4ce28e56309df8310caf9ac673d381f7cf04db812f29c8f6384735f5c5e8efbde7b706e8c31c6f8762bf8762194f0bdb7fbaeeb9af34929f9f125df7bd255154b693df99e65bd7d524a28e5d3e4be5db82b9f7c1b17eec6dd5df87657be75d1c10917ce39777777caea494bbe77c19df0aa26b44893a22829e7d35e94d082d2c1ddf8a0f55e7c6f77df7b12ce39ada7f3de23c928233b860ca384f13928a3847277a58450caf7deb3de84ef714e8c71c604f4e8e1d8314c74314604c88d314ab91ba33cc084735e534af976a9b903103b902e9e30ca18638c2e0631c00f393c2e3a989de8a28c05883e448a25845042082d2819422863b446d1bd17e32e7c927a52cae836fa78818f2a4f6639044c9af040120fc801e8b002084282499225493ac064876c0790a44993244c9cf0401349206192c44913492461820489264a24910489007e760a3000279248d284a772800926499c203180000a5080012891044913414e848049123a788089a0251f6052653810f4011d49964012b48409264e2049a209131ec02180124998245182491348760a3080244c3009f2c10489209d1a6af39304121b50a2a4091f03b0b9d971539303875ad20413414920d16409124c82900431e1010f3451caa1dec42c49e2841348e0f8b0a3e389264010b463a70003c8f1c30004806400497e9050b201241d58c2c4124d9058e2d80a208993244a903c81840778d074a00925483ab084890e74a0092535ec811d9b840907903069220889249a50a204099c98cb3c914489254c2059c204124b24c1010e345109a04409133f4004d04407c80e20a889267a500289244c4a2148f28324e889244b78c8212849077402b0026812d484892792f8f8b1037882090e2461e2049018264824519284124a9858220924414a3e80040792288124e8892426274b923c6d05806449134c346192c449121130d10409079a688289134892740009264d04354112f4441202385992e499b22d784f7cc490131a12b28e58212121292434343485844e436f8786acec882137e448470c392121f88e187a43424242427c84901312127a51484848e83a0dc1d3d03c62c80d39a12177c40e0d09090d0dc9235668a83a42c809b9237648486868e8bd2384dc9090d0103c62c8b9235648888f58212121a1a178c490131a9a47ac90d0d0103c42c8ed1075c40ec92386dc503c62f7f1113b343434048fd8a1213e62c80db92386dcd0d0d03b6287dc110e8850dcee3a0f34416203a45f11bb487408ee9c093144b4eacf6e915ca6c5d5f757514bd7f25594bba2904bf34e34eaf2ef405c0ff33c7069bec4a5f9f9abf4787c60d3fcdc7ce8f96df656ea26251fb8346f8516e7371f86f0b08aba66d77b25082d5bfe72fcdc1b6d437d53e431bde13ba11ac8113576d716bde1df58ae6fac2ac45063f58a150af0c9be4557c2e82dba0284aeaeba0975f51eae1ba4fafc7c3ca4cf6b8fc7f9ccd33c1e98cfdf01617dfe82a1cec7a21b089cd0d6152c3570a36bd0e56116ddacdf5c0741190d822674459bb8bea21d86de5c773eec43a237577dc024bd0ae743faaf971101f32b621585ed322144c0fc9258455f6f06011808accf08864eb15fdcc8aed1299c0fa9de581588f4a5af40f1c127fb14a438f854e17cb23f99444444a33a05f5c527fb05b18a726f8684cbe810dc5d368436c46bea330e458e67b89afa7e1d10ae1d10fc6d7431fd7977ed853498e3b7a8065c7a8b6ad085e6cfba29c19abafc80d64114852de56bb0a5065dd4a04a0da218a1a4fccaaf9459116e285dcb5343c4a6a8a0811b5d7b8b6860060dc8681ebd4534e08206539af61671b1465f24d85ba9e3731c10ebdeebcf3b7ffbe385d21b4ec76f728877ae3fd910fcfa473ee2747cc7a21a5cd1340e086e47b95d06bfef9bd6f19377f8f19803628bf05f7daedf1d103cc41b0248effe70cf0b291dadd00f06444d9b7f91805c475737e73223f05c372db8e86c88fbeb68857687f487bf35196af778a0fdf2e54b158ed4498e1ef2e6472bb4a34e72f47209d65783d8c85d3f3efca33f5c6f8ab8c7f3377763c4c51a74350cac7410f7ab1bdf89ebf79e47fb51da1c74f57e283a7642165ef4bb258b3a7e79c751eb6ca5df2debc8bb75973580aa1fd07a8344441d64591603e1ec88f5cdf58fbb352d96d2ef2f03dadcf93a7ccb2320f7a5e55df6ead9794b7455ac38ce18f955e7d73ccb6eb2d8d2efd66f9e6551d4ef2fc3b063d908bbae1b1d3b5e84a46307c9797193655996d19bd1cc6e5efde6a68ad1c166e8b8b9b9b9b9a13a34a9e333d7d105efe878fc25c3e3e6b971c303cb6230c83bd68fcdad03e9cde636b7dec33033f26efddaebe1f850b38e533ab2e7f0ec57466fbae09d9bc75399100cc370dec33a30caa408b6e318e9d88e8a91b0ecbab939cecdb123dcf25847989bc39bec34cf7ec176d3d5d0cdb5cd373b62eacc86d6d4d0e44023152ec5cfd0c7069760e8766a1c743b69743b8de8766a17a9f08e7b6e74bc7b6cb0191d33abde1479ff91777f87a7be591012be0bdec91e7f655976cefe1e76747477d35ba4842db2e09285972c885ee0e5055c2e4c83729f63bf75ebf5740c735df0ce767c96bd87abeaa3571f8d1e7f8d46a3d165591809c3ae59944911cbba3e6f59b7ac0b73d4024a408fbe6d8d4e815c8f46cf8e987a546f8c6477cfaaeb824bf13fb5dec34ffcf5b456a29fd27947a7b34eb3c84fe76226648ba698d1ae88a95d956270299eb321aab6ea0682b67e2d2780e897a3422defe8f644bb9fba57711f2884e87fa2874619a610f9bc3e592ec07c15f7f967dde0e9fac8cb6fb1cac4eae6aa5bf4fda2db135d7db3214a5dbd8865d1772906ef548f5bacd2f115e52d5c8a1bd6d179d1d151d1e2798bf322ba2f3aca74bcf66fc7bb2e1e8f7102fe08b322e8ff9715f19fea5fe963fd916fe8864b5fb8d66151645969789befa7038232e245bfdbd08dfbe6a36f76e4ddc6070fb49b03e98d026920bdd98d009a05d09bd9f0409307d22e3b62f38d3b73edeae69a7f6d36c400bdfb63ded1ac6e437c495d3644d2a176f4e623bad99c87f8e83c78f0e0f1c783c7af87f1e0f11ed6b41d3b76fc72984677ec386742768c461886612eb0827387e988c2a6b728095dfab2c16ae689c83aeb503bba9d36f7d125dddcb38f9e1a988c519c0d2491b0933221add1edeb0673993bcee53207c4cb20e249f30d0be7391ce739e0e0e0e05838d7a10307e73d7c1c1c9c89536f8cf038761e187691488e0b1bbab9be39b5e3d87760df0db201699c6f37e75fd990f71bd7389fa4a70609c3300ca33a6276c494c3753c87ebd858878cc148510a49870e4c870e1d9464e9f8cb8e985a47bd314273d2694efa854399c0c63909ca60f20b8c442291dec3a39615e9a47a63a4e6a3d760c7a1b39d15dec172e86832d4926ed419f019753dc2b06d1d14c835f60c732d8f43b7dd2fec37d4e63dcf866ed750ca0c2ec1d350e90597e073a0310a75c676b2e876ea2d4a31526aacde1899f9e83375bac125f851cdf17aa3ce68f8e90675860c7554346eca0cde71f28b18eaa868aea517bc33daa29486bf5e17de8181b1ac67434a6d75e11deb9099b558e74c88752683779cd3344dfb663dac5def619be3a2b5bf4c887647b7618e985aab4c0697e09d95abaa5c75585da177b487ddd76171dd0552fa476f11152e2f80d23cbd452ed8f2822dbdd35bf4022a54b650f9a2a970d13a7a8ba83ce916b0d18233fac281517473d7856d36eb76ea794737addd2985721f8a395a4ff44296cf7201e6fc7561147eba0bc3aaeaf1f237eedb437d63645e9ec8acdb53a3e1dde6ea766af7a1768f0671bbcfec88e9fae8d7473546e112bc0e7ff516c47dcd6c48a9af6f26e472f22ebbe392ab41dc236c5449db6cf98c3229927dbe088e675f2645e6615e44fb7c5637791c75b9943dab1b6c98ba01b9ce3eebb65c92d7468e66749b448112d0a3edeb3ddcd7951d31f5556f8c58af6ed5d7854bf0d5ad2abc531d5a69f86dbed1f0157556b8047fd930d1809830d15ca5be54b49ff9ededa9c15a6c1c15cd1d7e56f70f681da4b5ac57a0dce7c45f5871420c887c84609722bcc0083e5084e034e493732b80f85401c4278b7b8cc23b4f0dde715da5b7b7e7a5594bc373193abc2e3ab0164e800e9be3a2e11d96ca6454c70527a03a2abdbdcd8687bc73237f05ca7de8b7e19d15deb991b5088d9edb2580d2b81edc23b5591edc633684d6ef302ba29efe65459c9eb307ac8f0e557b2677973d607d6a9dcfe468dd6772b7acf4ea20048774700f5e48fb9b2e3cea2d5a8117bd44546fd10aa6f416ade0c96d9c0b34ee2d9af246efb58ba933dd2094fb501845d51375218ada96a2d4977bdf82b8df9052bf5f99ef61a69b09894938a34bfd4e0dcf745228f799d884721f7959379880594ff342d4970bc0f08d66ba9f747d363bf29a5f811e45970bb06dd1942e3a889badf0cef61153bfeaac7005a2dab141054f36a0c18d30189764dee3d985140af4817fb408cd077ef77d5b2bf43b1320fd2a10a9afc067d2c2e51ca5d980b677bf96c2ceb091131ca4acb10db2bbbbe7f3deed570893a5cbccc5f5741a3efe674fda1ffc6deb687f30fcc5eecc51f47289efaaa6039d9b72c1daf1d279d1fc7937c49dcf775903985d038cf0e7af7554685b94022b3dfabcc32e87bd4c08567fae67b7e8c627d54947bf46f4a60824226ad261bf5ed247949bf41e1e51d717ff073b09ab91bacbd6c18c2ac625f98dff336f7d9bdfc75f30dbaa8fb2067cb346a3f7f0053322eef357c45c36e77c0fbfcc88f5f933da1fefa4fdf1881aee8f87010d1245d1a46fb037d8a3932813a0f54244d441385a7b10d6dab1c7244836c6047b906ced9b1941d2a4738fae51201e4d3a468114e030128954777918d10b624c37ebd8afcd8830f621b5497759111783a1289a5499e97646378cba5f74ffe3be3de94618aa6a5c92b5761c39d9c6b5010df616a5c0051bd0626f510aa6ecfed021cbb20cfbf52ccbb2ec9b93bde1baaeebbaaeebfae65c6c545555cd53afaaaaaabe399519a85f4ba76897b116295a864bfca5b3fd41319d5ca2eea82f75337033d0e0df21ef6c53cf3688103e451ddb20d437870ac3b527fd52c21b2da30438b40cac934b24ea9ac83690fb92ed0f12b63f46cf8efdb25e7d9eb27131d0e4e3369b74d2b10dc2273ddb20dba433cd2189e18a7fac6f976c23a0ba9c966c1acd503861b5f5aa6e423dbfd990eadbec1f25565b758935eb06bf4de643c8fc1ea6cef4820e722932ff1262648b9aebd6d3fcabc2d6c1c06f516f9114337a5f84e60ab91429bac196745382357cac33b8e2a3c63bb308fcc558a4102aa1f1259f864bbb17c4e4abf73c476568e205a913d7913a712da3754865fd9197b06e410d37ace3af34fafa6beeeba6c83e414a5f8f0dbbf455d9f510e3b9147fade37da3753dc407a912ccc99f8a02694b46aa84e62ec3258b52afe8bccb8066e3ec8ffdac5b619d5ce21d58b1472116ae2211c6d8c370be8721753d2f4f9d332193d2685774e2e023c3fa03ff3e87807f93427a48dd3d1ece393f2b7ccf03c29a214b860cf98e0f492bf328f5cb6544f8d42fc65c46dd5136458e68f8bacbc31c123f65a5e846fd077efe827f4ef091237abe8737d770d65d1e6036241ed6f99ee74eedc6774c609dd24532bce8b2f7cb658f43d8e7da86da9d54445e3aec11bb85bdba6551195a49eac4b513d7187649b72f5eb70c47ae7e7f488ad51ff94ae39deb14c9f5b599908d301a97ac0369ec2e13e2ea36d48e6eae7fe4ab6f10d714fdfed86bbc03a4f717d5b8b4b32923f2d545b2b0bb0c8979abd270698f5599e5d2baaa6e435d1da38edebde76174b35e5975836dfdbaa38f37d7d8372186c6e85d1eb0ba591408b3e8b67b15057afdbe59b4a731106cea92748b779ff4c65520d75405405f0e9bf4475314874bfbf8cddc2917e1e0be391b907ebf9c83ff1128ebf6969f5cb7a0d7cba5ea32bc63fd9dc6baaeba5daf7e51f43af5f7eba2ae2fec2e835caa7ed16d8c5e8ed7bd91dba6e872a944c3030e8ed6ae6ef2cf3949b72b3a15408362bb2ac32596757b826eb3776e8429e570e99dfab9f47ade524837d740eecb972f5f5ad2a0d77b472797b6625ce26f16abc6253e26f39c12ef6c3f1cde79bff3b0415e0f592e46b934a0ed65b48b8c7552685b34a54a5bed7e3d77416c3788236a1d682e038244719f200488e572a1c29868546958d83a205c17347acf8e677df69603025e98b5418258bdae876df6f2486702c2ada18dde867a63347a739fd911a1e6ea84bb034c5cbb5732967661d8cc9cc0d1b6452aa8d240a4767f276d908543bf4b07c4f6dbb837d9ef5426e4039a3bc5a7b22292c8fd5aca39e7e6730eb2734e9646eed4936b770847d4af6e8ea8373ba235fca34e60c3f7ddb74bb71fddfdb1775b5c51ca11da4fbc4e6f71fb57a4db49aeec87360c77dfadb23ea9e2878f3c94181f7778178577641adec6070fb44d49c703e94da6b705c303d6e789bc8bc2257823b4cd450143c39ff6da33b551058c2dc0f0820b11918fbcfb25e9fa384a047160821948218321f83ca9e2878f3b94181f479f54c13ed4a124c0c75d522809f0396529c2fd44c433c1bb2da72c455097f5d4f34cf0543d652942ded5d39f095e5620fb4cdbec6d8b8adcb7ddb26bb0620957cc80851b74410a6f781f3ad0d6676805650c410b2d8a70060fd6f0599ffd06a4881311f9b828bca3d3f04e0a33cce2b2c033e5904246e3f41649c1d2419800196a78f78bc47f5911ab81de7b146a18d3e85781a8cb6f42bdbdbb537589ac3f4aea92cb6fb06ea77e8f541e1ed927486959afcc78287f946c1a2d1f97ec13a474fcc60d655db269745c828027451deb1528847ab082117ab08211eaf644bf3bbadfcc023d58c108dba8e123bd0285d0a6e1537d81de972f7d854847096830c50aef6045075a88a0139b465f21eac10a4620920878523489f609522c29360d9faa126d1a3efb0d6bf85d20767bd8b6c8045c3ac8f515a2992dec608a311011f96c85426856a23dacdb7b7573a25f11305dd8420de2f0648898e6f7b0100f459636b8de3767db2dcdbf1e75bd4525f84143aa6e434d5d0f7b450ce407ded934de79e76b8f679e7f00206634df07de71fd32211ad0e2e179670302e407e639111111910fbf8779eec30f827766f31bb0415ef3714a5046f3657278c775f5e54203b92fbd1dd12f1bb268f4fbf530a8056251080d7f454c664280d0f017854930967518bd9336c8abbb3f5c45a23d4b6f47974bbb0e46568c4bb1d270c995e08b8655864bcef56ee9d54188872443225ceba2d0b6a8045c74ec2d8a42462f0e2b0ec23aebe63c105c43f61262bfa418a3acaeb7b8f8ed04b4cd84186a08abfbe5e810d7d351d743cee900048ed4f717d30dde3dd21b23113e521445b9531445d50d9ee3292a9d8cb16eefb0fe9020ca18a59452ca28e5a57ba43faefa18e30848ac4879d7ee396bc050ffec7ee3bbd51c11ae27bfcdc74bfa332fffe88d91fa33ffbefd3049df1dbd58ca5f525e0f73d48362bb5f90dad9e4f9ee30de0971ebea36cfd5496cea3d7c45cc51ea91727c747b9ff5279e4f49f9d6703948b77878476f8cb8cbcbfa13efbe7d316502a4a3e58362f365847818c63b40543be196e72fefcc1fea70d6cd9d3a5fe31dd77c97096199e6acc81611111135d72d9eaa4e62cb5f117bd49da9fc6644e0d9b9babdc7fa03393a9612c54a6f1109cce88dd4f000e82d22411af0f192f42d97dc726983cd97741b6aaed326c65f314621ef3d1ca9ebcb6136778f94c28c88ebf81e9ebc7333c4757cacb37f7c8c112b9050b383f54733c2fea0610dfb22c1e8de6194e7df701dc27f7ff22efee7c94fdee19658f7700f0f39e7b45c3b01ed2245f1023660c8d5c92578f88b440448a8e1e1ddd735ac1bb896afed8517c63b3746de972ffdfe13c4dd012756bfbb285a473f17c5b5bcf3ee030ced9d0fdfe12f485dbb0cbef71e76bdd97b0f27b9de975e9bb36d51bf93a0ca7b0fc801fa3d5efd7e001c1244e907bfcddef870f2ce0f7f7e994763d3e3c3bacddeb47e5f08b35c82875c72dfb43ec4dc36db39f7222cb8f0389f3de632fe6643f8b04e8c77788b98390957b0b1ee04da1699604b3ba2fee66c8ea8f9178fc3961ea1fdbc6f6f8e887b8b4c5046bb76e7cc8823eaad9b23eaf76b3321f04b9dc0de435a44ebfda3db50bfef06b9296235fcb656687826401a562052efb7fdd2db3bbfe7d1656ca9cb427377bd5c80bdcb80deef9b0169fd2a0e97dc7bd811b114cc6878c9b3641011f9ec892ec83bf2fb297a7fed0619fdba86b08fe8ee0fac6ed781b8e5afba715f77c7e826d498ebd1d644a8afc35bdf0d527db43f4675bbfe9ebda2bb3fb2ba617fdb8e02515fbe7c8943c363741b1ae21abe872fba6dd11f589db8962fc22dff3223f05cb7a1e66f5f777473edfeb221f2a35ff4a6486f9208f6dd1fee3daf87310ae4bef4fc88de6444a85775735ddda29b6beb97a49b6bf9ec303b022fbf9910ec3d7cadc342dba22bd26878f96b2fc6362392fdaa1becebb193d9a38faacb8ac096bfaecd46751bead131ea6436f68b6eaeafef06c1ea865d4990defd71fd7acfbbe845df371b55a0d719dde4df3733022f970c2229fa222def5cdf43deb1be771911f7a5b1bafb63bb2a90fbd2d7e3afbacb43f52c77a2fd114fd14d0e554e3eb9fe78a820fd5fea54cc5cfe079f8a9a8f1e80772aae70a7790fdfc743454d3de1f8b339153c7e7d3f1f0f1540a8c8e135dfd33c1e2a66ea69e624d26b604ea29ff969a69e60725ef3d3ccb1934ec5aba739afd59376eafdf4ea69d62bdc4ff33b0ef3ec7bedf15061d59375e65f35f5544fe9709d33f59a9ff835f514e3b57ab2aefd54f3eb54c0dbd4938ccc71d4d37b554fd5e9f513fd550315ddb0aeae51c8a6ead75d36847af5cbade337c2dec318bd343ad9e46e514d86a2388fd622100bd7c3fbbba4db504b7808f45ae80d659c9de10b5dda476fd117c4e88bb4f0bab05b14420c62188659e7209656851083c7ac28ef143b2c6e61241289e44624128659cfb2ba5cc2ac03bd265da87fb05fd7b2acd6cd310cc9b96ac1bb91d36a1037a96e4d86b6e532a2cba5ac5ae75f565dad6eee4d6289052f44716ee383b8e525e51b5d30a30b64f4007a8bba2045d6b7f26b8483b272bfcd91ba7cbd35664386a07b757b2defa8f0142951dad520eead46f00e94cd49a9402ffb7eb3986519a5f131469c357b863ddec607183fb26eae3734784897d7aa1cd520ee0b0d1256835e5f751bb2aa59a70c9631e5822e107139a32f12e44728da69699dde222e5ca434017a8bb880465f7bc1de5cdde0d24deba1933bb537c29ddad78ffe009d9afaf6f26156e435a4eae61a7ebfb9ba7c9909a18450df4ca3e112cc3779fe765d3b7518ba6ca2b2635fdeb13eba46974d5bd023d1e5d2a8cebac5ab6e56bcac3fafbe334ffe71978f97b1625cc2feea88e30e28ea9377a6ab2c59b722f0f2f2ce0879aabac8f33023423dc2530e8b4fea030c6de3c32dde825cb2ea16f42aba5c8a7cce8850ff99959275d6c31bd1c35cd7726f0c2786247d3f5d5394bb6bea18801991f907b7aba6b6a9bb28bc539dfacb84506234acfb1c2e49798cf2c02579ce84505586d2388a734d08215c08a957d5e8148418a4a81105435130108632320f198e6064240527cf5314ab1b2472690d27f55965888ad0949eee1856a5380a56f3eecedd49e19d577d7352da7d7353da61c05594bba03b452b633fd0369910c0cb639a9cd829ba69fdf8f4c4a4d21b3c4551231c1407843b7527a5ab1ea6b22358ddd845e90d7ec3f10ae655fdc8c5d49f79ead5371c7594bdaaea06ab6f5684fa84a9233827e5a0b81ea8bf8cc87ce50eab115ca22e8d5983d162ae8d60707c145361ace380d17e552e4ac3a64ea2aaaa5edfa86f41afb153dfeaaa3fd6e7ab6fd7af0ad3e83c896e63e18098f390775e5bf318e01dfe1ccd5f6ef4cdac096444d9b8a813192df12082d831b60d10364a4ef020028d8e4ec2c2f530ba45b710b4259bfa88fee00690201f4260610e0b07046cea93c27ed4ddb65e9afab6689c4bd42faa844bd4b19ca62e1d160e8859b711f4f20e0f318fd5caaab36e8b46539675231c16948b6255b33a2c5c0f94ac3f4066ac70e149635d6d218d2d88d1f35cfae1cddf80f4ee7bd478e7a9ba1599a74edd6547e6a9f88afa05a90f3034ea2f1e6644aa5ff5873afcabe2ff788878263e9067e297686898fa9bdfac58e7dfe3392332ff5355971dc17a9e88eb7c20d5f9aad42d1a9a0a4855b7779e548d3d0ce9e31291a005290da4b7080d2eada561a361850b6574017a8bb830a51d978e553b771a1a588577dca5944e1e6644f8d6b7270b2015de9942a4168b422ebdba177507f2789c142748cf59a1ae8ba755b7454ab6313b6954379aabbeeab870d7c5efbdb7d77bf273ca77d51f9e90050e88d1a524bd27df7ba4bff7e47bef91e69f7cefd54dbef7de2b6481a4ce8a7c4f4e2832bea454654ae4dab47e2ccf5886b810b2d27c28058a96a76a95e607fba240d49737e6835578e7ba93771bacd2ee1bb4d2ee8e9d73ceb9b773ceb9731acc0384b9f00e75c7aec696d461f942db9c952d04fd6878475e12db8c88a32ee9a6f5e3d3135b5073c1455e5b433d1d6c79288577a63820dcb9c22aaf02bdde5c5b47a8ba412abdbdbf6f93a9f43bd6f3707e7e9b6ff2e5a7bb1a3c409e297ee3347a7e79879afc37e733f53c53fc3c5bcc7f5991d853bec329ae07f9e72cba69bd392b9b33a3e3ab500a972490c7b33d8f47bb80587f45db1649acd2ee32224f107d1fcf11dae371626ef3d54deb4ddb68b86e34bd691d7f695a36d22ccbb2b2ecd64833523dab97f64ba3f2189dd516d8e8eb070d5269d8178569d43a56551b7b69cbb22c6bbb6559d637bbaccb0718daf68aacea9b2baa2a2cb149a3ac8a4545b129fabcf8c28a08c4687952182d3ff2a2e5332c2d5f69343b0659e07ac85ed12d045dfd925bb2caa5e5dd29fa8ac8280a4119676cd1d2f2d7c48e783c4e8a138fc75979a6f8a871217da3197dbd3c53f66dc296dbf3a2a50f30b4cd59690a9ba2cf0a97e46176046beb4ecae3795cb0c9cae371446b928f56dd46d077af8b96d7a450697952dd68a4b4fca86eda3e07a5e5b36aa5e32f0cc328e4927cdf984bf337eed25ce52fd85775fe1604dbe2d2d2c6c7185a50ecabfe54a73ebf05c5b6e6d4b48da6e77779764d3c0475ab6e41b1ab3a7f317c3cda46d0d55d562476752805b2c001b1421ca452d1398b5c4d55c802d7837498d361c3b5391b900d321f4ff5628c91eb3674ca125f7cd72902d16d107ec091176311ab630fcf7d26f848e13913c2142d723952c7338dbfa4db20af5748137ef5aba27926788b6acf04b586a73221f1d3ba29b20d6dd17c6983e6cae42d3fd472af0a83220b5fc87a65244b1577a06dd11965f41b1201eb5585a2ba7509ac4f55b3f023cd206085027cac5a7a26f82b3bf2ce752362de0a05f854af6ebd49b3bc5f9c1581698c69bccc8ac0f4fb55614cb5213717c845bafb037ba34d021daf11e6f83d8c81de2ed00ff2dcc1b5ce0bcdba7648736c05335c0e34ced25b6406199aadf4164981095dea2d8a821b8a044de1a5b72131646f1f4041127a492e07da33426fd113c26847466f51196c340fbd456554711911cb095c1ae8082f5a3490d0a8b7202f6c81d45b10962d6264d05bd00c647079a1b720127821f6d6e40a3ee83d0280206ce9ad49185b7a2711d1977ef3950bc835f52b9ea24d5c53aefef0dd1635e18ddeb8e3993271fb233e6836d07ee9ec969571ddfd112d47f717dde4a32c232b234a67b77e456b7f0c355b7f5936641ebba3d82d9a9d3323d59ddbb8bafde16e1da31b5f4e6603bd3810f57ed2cdf510e5580dd7fcdcf80b9b1fbbbabde7019dda7d3a77e70e8413c59776c214e41253ce01b9375a1e5277a2b5124814456f7c20ea8d28beb493d9fc8a6e51fefdbaab4cb18db9f281e61329a268264e645777427593a1aeea72c9c1f3064479e9f820067a2deb4d1157d4ef5c3927dcf017f3f2c0afde976e15e59899eb567defaa53cffac3df5375c89e5f0436ac3fefd59f3defdd9d1cc3e5ae68ebdc90e3035c0e8bc687bfa2b9241460b3e38532e02ce00703e237c080f8cd38b0803e420878b8102d03621d638afdf2e58df1ea3e530d34ec0160efb8e47e622137e49ebb428c86b7a4e8a2e12b2ce0e512a2b4fca56509551afecabe97a1c1794e8977e4e16155617f18f6896140a7c6ae6f5fa4ebe7c608f5788aa25ed56c888bb246d4e7afcafa7665d521f3d52bec93ba96ce7af5e39ab2ac2a3b026fd59ff80dbec950cbcfec087c85fd471e9eaa9b3c56ab0f75a4d92d3ad4d75063242ae976fd55f4273bac4ea24f9e3c79427a15bb0270dcf2cea78a5d016caf65753e3d6f7d2ebabd0d613e88a0657f2f2b725dd62dfb556fae53cfeacd55e725b54e4dea670ef12635ab7a5592de547548fcf5ab36f6ebc7fa268f5c75f74705725faabfaba5ebedb88f677da2e8ed3d101f7e3800ef6c0bb9218baf375c1ad0dc977f91dc16dab6731811177be977e612024d364c6f54bf3797f6c280e5b25bfe8406617a232d979e59d4f08f26a16dd30b1168b0b749d4f0efd38b59a480072175548cf2ef548c51529cf8a4657a9826c2cb8ac3a55817c20799cbb8dce7dc20ef425cb53ff62e23e2e09b6e6e90b7e1f4bb7bf34ffec9bfff30e5fb7bf2ef5d526a21da28efe46fe05d73dfc02ff2913a59e15cc2c5871b84dde10671d5b9ba40ee0bc7fdb1707fec14d73e1e6e10f97ddc20ae7de052bc1b1a0897e2377b1bb605d61bd691c77afc0136c8f7c77cfc0fa6f91ce6996c9adb8d0f66f0c8b0693efe6683b88e574571b834ffd8d0c371fedd4d25b4d1f337d40d457d8b94e0a5b748096bb40c755d551c2e4d8ac6fdb13f5cbf1a5c1b61b40fb1daf1dbc743ec8e5ccb646bb42ebff4a24386ccff4cacb748096a74dc1f923fbf946fd1c9a7a8ef7b78eb46fdfac55bb75f8c5dd4ea573b04f6adeeba2a5093a1cebe81e00d70f8d24d760b9629587456afbaed6e4fb46504abdb136d7d7faaefd6ea7a8eb32156effee87910a3a833d556fda1ce1b457dd26d6a2e1cba1cccc12581211f3e14ef54bc6a85027cf82cc4ee2b0008757a6bd771ab8a58f1cadc2f74ac57269d8f74023d58a1085f7cf6305e8142689f20c56756a27d8294edd4b1eaa7d397db2748f1d9ebf4e63c285c3b7256fad5c5c2fa15b19115318ba5fc9cb7ac596a6b52778b62979492bf2c1aaba43b633624bbf547e7317abde7cdeb57108fe8e682b879beaaeeaa754767dde072c90d916fdbd1ec55f557b7eb7a947a474afdea167f69efe187d5edd498f5b4e50468f52444fa69b900efdadf6176a4d46eefe8758cf235ba3e249a05c3e872a9ca8648eb179def79d63c64ba05b1cb8e983a461f3cd09c955e6705636982a1a68282b8dd1fdd4e4c9cccbf07c14bce8e947a13eab83d01c4e34894420952f436147f458c8a2fea7414f2aa9359291aff32233afd2a69237c91b703e2d1eed56da8e325dd4e2dcf99119dde9edcf8ef711fc42f3312db226af717866b4fe4d5cd7516c4f62e0ffbc4fa932cef5745b788f9626cb9640d80d991b73e7cc56521864ed0f30890f5e2809e9e24de5d17479b9094207a7a06d1436449030915c1780831b445cf2143561ae808c7cc3c1a6d42562675d4c4ac59d1f5b92acca2eb53d1f5b9ac4da88fe08596d72a7aa222f0afbe0ce88927524441d44ceaf6383b02815eff3caa7e80a683685abebbf7e30e74eaeddd18d942d0fcedd806100d68fb08dd66ee172fc5b8b4e7a2018d279fa93355f9348fe74916c7c798e7f9f0952046330d97f6586fd111cc00c256bcf78e5046bfcbbc0a1c00bd4547d0a297f41e841072e9412ec011aabcb5829023b41f081ffcf5a8135a046e3c1d23849c0979408f3fc8b20435a898d0006264c7fd3db42bb5bb0cf3b822840164d48222443142180e7ed5681e028de6bfa2379aff487473d6abc37878981de1bf430a81b4f56e39eb45eb597fd8b177cbfabba893d904e6a198158a4b622f333231974d605aa00ae7330f4515ce47ca43218d9887621a314f41f7aa5c65f4fb49664562f4fb8bb72e610e7349a798978f54c8f289df0d425d5621cb477e73471e1150aac7489d4f45b30029c05da159af827a96eb44964f3c3ce9db455df475d125565fc768116b74980d71c7817da33f4aae63cffeb22174c9f5ec38e836d430ee24d2358a7db306b84a721fd19b2b1b838d31a28c9146e7f4168d5134861737a32c93d9cd35a2375716332a3392bd88a883d888d5efb24e31eb69c8c7aa5550773e31a357a05058bdded3b19e643cf54a56a1182f7f129a9715ca7de4a1dca7aa4233c6c753f25df1371714d2e6497b22d69beb31deb97a73c53a64a53913f2ee629c42926717aecdb9987421237cb409641792611cd971e0a87926bf9911d971506c7e6fc4090a9f1d8a601f0d761150e470ec5068cfe19b1591c3b17a1a5d8bdfcc08ed39bc87d5e877370434fa7db198155173986334fb884251f3d16366019b6797b4889ac3d4d3e832d5af883de7a791cdb37aaa3936a2eb4343a1c06ef31c0a85cd6b2e43a13861af7945b7a1de1c14da160da18cbe225d6215b13a46ec32bf2e9759607499932814a3930e434fa3c35ca3a7d1b56f66811a197a223de7181deaebd62f0a8b9b59e004f3ec27d269be591135b7a927ed190c85c2e650d443417f128282c7715ce627ed3027d1d37fb2a93fd5d3d350284e27ecd94f390e534f343fcd3ce6eb9343a1389db26b279cef38fbc4ac88d1611e3323b4d7d4d3900fe939d093f321d5488b18c5c35428b4c3fca2b0974d00bb4d8522fbe854266b113587023be938e829fba80a4191c3738cc0ea1436c74eaa42960fcc332a64f968bb4162dd80f41528a36355c8f2c12e0f4349d768f69e9751e73301ad9eb067b58ad1b3c8faa324beba3ccc7bf8aa1b90ae6ed58d2bbae4b9296c0e534fce67870ac5e9e6271d3fe17cc7733e29758c4e61f39ce7404f34cfe1972c8195de27489125b0d2f2b1a110bee88d42bb29b42d628297de681a1e7ed3e099c0650965f410c418429416e30d31ca20c217437883086f10810c31c410c38a185284e045085c8440465f1816e90d558d542defda6aaa4b6f1a5497969b464b043c29825ec842d4f254cbbe4658a437f114bd89b5c843a30c53885abeead99b15e9c10a4620c2b222087852d4f2095246cfa927e773d2a166a75a85b22af313ad50ee43aa423c8efd558847155014e043c5900fe9a4cbbc871dbd41bbefa050ee03731d14870a6597f94d15d22ea3695528ab50eea31dbb0d3d0df9d40029a07fb6753d6c5be4059676ce878642b9cf699e7e82721fa1d1b1c33c077a9aa942a33a0549e6a71c5508c7652a94fb648fa950ee035385705428f7191dab5390ea3ed3b6455e58695783e046a15df580f684dee2152f5ac96ee90f59e92a8300f6ecdae3c92a94eca35a4556a75080cf681fcfe1670681ec30d71e0f4c8532ba56ab18d52cf2d9e1a9ac01432d63fd91c0900f897d468742da3c392222ed892dba891fbd8afb8c2ebf39d1b2dec45a84e6be1dd18f27ab5064c70e3f1d10ae5e6616385176ec2f2b02fba89e46c74619663334591b835b374583bfd649d1b6c88b282dcf9308cc8823609634601bf174f0e4757c0948c7d512448c2722a7638f8e37a2b443c71f613d7e89c7f3248b55a34fcc01191def03efc48ee78177e24b977befafcab73fde3ef95dca044801cdfbe33d48f6bacee11d20f7a5dd2365b20f92469000f168f7a5400a68b73f9eabbb3ceccb86c48c88bbcb84a0e83682963f410185d0147aefbc55ddde7f66dde410792ec27583a2dfb765dd5cbff71e66ea7edea13742f09fcb8cbcbb3f2334bc433d1e075edeb5a4eb4ed12dca22464b1a5be39d95757370681987e561efbe2c0f7b29c4d018cd44cbc3be8765118e825fe4e9866b5d151afc6acf69ea411001b354814dfdf1736e5a6f6f4a7edb339afa11bcb3fdd0d4a9ff40d314f51e2a88c7f324cbacd495609ee743dd882e4d1d03bcf396f2c2e5a0296e9a2e9a5a8d77dea9cbf00e3c45ad9767c5b5ee8906ffb6a88b2abdcd8e5fc75a1751f8d1d0e04ce6813eeff0f5e3bfe36c90ed771d1bc4fd3d085c4bbbd0f6ee7c1c0784e3efd37abf696d648bc3419476ce7d73dcce0db8d8c00cf88da6637c3c90a943c7ffa075ac1d9fc33cec134f83c58d8ec7d16478e73dc6f82d12c19546efd05b84458c7e5503ac5715880bc07c0f0769d62b8b3a289a556f8a301151bf0b111175e58391f8aa6e39da096c25d56f82d8aeaaf5d841ddc317df7510e03ec6fe1aab40ee4b76d7d77565234cc38103064b170c731c9aa67146048e5f10c7af8883846374186d34228d7ebd2138c9541497f5e51decf37b6cd2e944ab3e2dd7ec3a081a95e192751068d5e7b353dfb4ac01d7376bc0557b5c00ac0370c93a457fe092f5eb87c7c3003659b77e68eb87b6be1903b864d1b4f56dc968ebd7c320efdcbc63e1a2b708cb959e9fbc73f32a9010db41b87eed7ac239e7af65b7d95f6715c87d19dd35866123d25537a1be3e800d72ddb2fec7e37cac971e4fccad03714060b77ec550e78361c732a503d05b74258d36e30a1910c3e886fd26bb703c86de6430f426838181191131bf2476c5fca2b02b468bc101536f3012490b144b1a2e51929d12b4ed4d513fef6c323cf00ef59c920c751c29a59414109ea2ba24a0111f199a0c84103a2ef009da26d35006f283d7608c31c226382b681bd6525e5e93a1c1b9dc4b1f605077ee55986fca577f1e9fb27ec5aa386c9a385c9a308a39e79c734eaeaa0ac8962b461a1a24685b4ece83ec1c33f3813837b8b4f185196d70c1861b7df1b27b232920e0daa2f1785a92a082b6cd7ef32111df7382b6c9748cc7a1d178071e0a7786b6bde60de2fe736571df1cc74697bee92d62a38cde6c32415b2794a14a6fb357c8b504cd7d896cbb76750a9afb0ae07a18971e14b0fe66ae091aece1236b44514395be489bc3cec60718da4d11f82dd69b22f1f5ca95592511e8a2e0043374e90d6a4e8c6b3906da16b12106b3f1850cb42d5ae38d866ccc40dba2358a7a8d301a16c16b5cdacb6ceb311f44d0e2619d5c7a6c826b73b648d4ef970f17687c771e98e749162cfb3bcd658d1e7b7f3546af909e915ed49b63a39fd67323f2ce4e9db30a9f18eb04fb2b2ae38c34bcbc37a04cb1d24518646c41a30b6483449f6499f04f0cde817ffc99865f10e36c08eab06eb51f16b119b3392b9809c1ea6e0fdb65848d4031ff3e37334219795ff33ac44699911746dd6438d38ab42706ef5cf585c1253544697e1ad4687ecc8a68314688b9ccaa40ce48ac4f0c5e76551ccd5714c8f1ee1b6d50f4acaf687fbcd347c62bc2fe78672adaf6bee8f7febe7861f08e189108e3f9dd20bbf4bd1f5c3b00cdb5bb30e75c334481f6367877be0f688664b0062edcb0862f5851042fc028aec0862b98210c2dbac023450b3229b8d081a2284a0a28a44745f186a6b2a61e95f516a50107d28bab372c0a2f5714695404e82df2e2861731c480d45be4e50c2019ce0f349c8bafc9c46845ebd6377315886acbda660fcdec930981c77e5dd7e2854a933ea22e1332faf53221977522d92d5859da377b151deaa791ee28e93d8f44e34714c3e816f4e094f272517a1e10d54fa8ab49b7a514f7ead61f75b7de2b38efe87b9f936ebf37dfb32a10d5b392af287509abc6a51f08498e5a8308da666d179d6d51ac2baaf519bca081e8cb177d456cbf704f51a6f9fcacdbcc693ed43dec054afbd05b8406297d51981c62d69b2235cfe1351f6ac99910995f4ee6f866947c3828e932745455cf81ced0e51266d1f8186a553c1af4a9aaaafac55845b7a0d707e012bc0f5c82323ed036996df7927678add4f0380eff18ba8d43866e432d23c35c6f88b8f3877ad20dceae28ba9d964b33c7505334883b078e4fba2d1a8de315dd9e681cef793214e312ac649806e7bc1373f81cdeb170d4ebbaae3b47b5bb36290f43e51f1522ab11a7dbb361e6a41a06e6f5758aba3f0af3cd845c9990ec3defd2641a9e444794c8b6bbc625868ebe86120e1999751de5b6e8ec8a627d5384fa7ea8a518da1432354d6e73c6615c5c7b5d14c5204d5b7f7f0f02fb861528c1085108020a78b0059fea9b41a00238c812c614607cd184377c2c9aaee87c26f949b5679243a88a09bd216687e58a2cf4f811ba2dc65f247704cd09378c7ce5226dfc6d067a5fde28c2443fd85dae086422cf2468b6db1ee083681ef38f40b725f8d87004cd45226c499d70f35b38da85ef8b0b1e6aa9a265b8b4157289e3e7fee0ef6d1c0db42d72634b6f58f3ab0db254741296fca0e407222cf96179c741e924f6cfa33ecfd5af49b131fede90a713d7ee5588f109aedd207cc2428a2074f185286198c2e7a500063a2863074668c2143e3bbd27c2881f5e108f67faecf7025b78d0acc9f1f4cc4a079a1c1c9ae3e4f470c931804b058003290b7556253f3445331d17b0447105175fc8421184f0a40554c8a8c1159420062014a0095a162a0ecd15ebf33d4cf378acda23af276f30420ac800842eaef8e28a1bc880069114a440c387f2618a2874a6c0a243dd94fcd0f30cd8a21e1e1a023495465358535278690ac687a2661dc2862ca0c002327421862e3ed4e7e319fa410d8a70250b3710010c67f0a12a0e4d457d00721c9af978727808a287e6a599de223350a137209329225c313cd892960788db13b19167e6b041b1e1354ca6084461344c6fd117327ad5d0349a1d2092fac0a52a8cea4655253fb4bc0fae08da06a481544ce39d01b43c0def6c11a45042cbe3f00ea9e5cf3b5b8428ccd0f239ccf37ce479601ee823bf35468c1c7ca1680b94275d7ce4e7e34940165a0085229ca0094bb0828fac5a1134ac447364348a7189864deeeeedcaa0fd0084799c8f23723eee383781b60dcd60a575e82d52030ebde5501c2324b2446680831e08410b21bb4160ef5d7c26990ce18e17223713128fb33f98470f1c6f30c50d48700315dc208be8de102e669ee87056004c9b7568be8e2dc006c168ce7000de89f1f2e71247aa637b6086949b864bfcf800cddfb09d6dcf20e2000ce09ded870630ffd0cc4260c9624d3b8389857bb0a685fc043eb1d2efd7328f93f24cefdbfce100381b647e7fb341aaef6b1608eb7b9a0d727defaa304f0f9bde31363d8d67eb61dba1978b7eaeea2c8ff3a2637918ca8b81c984605c7a0ce88981714ee6f02028a785a933608b7aa81a84d3e2b4382d4e8bd342513339b43315dea9c23bf231973339b41c6810378e071171e0f8e5b01edec1f177066c86831473970981b910d235da73368492b90cceb71f6ebe09c0e69ba961cd37daf035ef7074f8b8417e78e980e0f11d3abec3e1693608cd61ddb4226dc3b02a33c4fe78e7411de07a78df4189e0d2bb0eba0335824def34f18f47c7e3d48dc74ddd686c686a2a374d3d627fbc8f6803f6c7bbf302171b84298f33b4cd4929cab90ecfe1fb78ea67be8f873ec77b9e48ea5a0e15c20653e440b7a90315c2062458d3f60cad54081ba8604ddb39e846d3940a61832cd6b45d58ed9c092159b23993d0080c143264a0e4000d2b06ec8f87c5e06098da7384264c210b900134082e916a8feb82f60e8b30a68231957e672a3358d3e260f6c08a0d84c043bbbe1863ac6a598455a6e3488e4e8d7ebdd15f26044a39da884508588e80a50958a600e5ec7d7411eeee3322b0a9eaa8c8c843c32cac69bf30cba0ad41c3230496d2eec4431eba877b38527eb2dbd379793cebb33eef6dc15f74f707758bbaed817a45dffed81fd49d97c74362288f27ae89fafb6e4c44dd4bbfafcb9a3a947ef209971ed5d1088f765428133765b66b41bfbb156c0fef0c0fab93e2a2540785071a1a1146f0ce110d28827736e70506b6c7453f216cf085356d19e61adafb3e1e216cf086356d6f0e4bbf477ec25676f05d706dcee68a1afe7ae2c43c4344effca4fa00435b37ee88de84a24971547a082f537a8bd410a3b720de9c9ab6d1fca5d26e40e01034b84156882929959617566f82d8c64e641bfbfbd2307a9dab6ff6e4a3936a8cf13bd68d660c5681b6fdd0f0f03f3cf8e72a079510bf321b44a7e3371f7a7f6c3be808e2446bf91ccb339f295253730b84fc9ed9a2da6b78b73d703ba6dc8f72cfc793634dd0079d1f0dbf7d87863a1afe7a42dc1ff7c5ee923a99fd3edf9c1790c7b332cabae568f822741d8fb8caec0ff82dc15714dbb9704bf08fee96e0a6a49f8635c3e60a74ea577fe05264e2c3fe88dffd5180edc1b9baa50a1b847b2f64ef7679280ad904ff983c99af62fa50da0a209ea24f9e54719f5d026c393abe0a203ebb02d872e878982d40fc059b1d1ab48da6636512349b4990267764b814b5fd11ef28ccf610cf311dbfafe2407b3ff3d3e2da9c0d16f18ef59cefaf1c6afde22fb48db5b475f7caaaa8e51cdd6cde92185c1f2cd0ae79654228ecda20dcd14bac2a07ef68754889b857905ab884addf380a7c858edfe52e7484505eac7583521ed48246af844bf14b70295e864bd7a516e621624df5712b336ad5c23b6554f48c86f678d612cf654cdec23bd6e3d90c3e8377de671a9a1ada376683ca9814a16d3dc7a94c08bd454b6e1e5ea39beb3a6b0e97eaaf875d9863ec8d767fd45bb4c25cc65ab80c2e68d7e5b7edc25ab25aafbfe7a0db0e46e392abc2a54869bd92f37e6f10f1ce9b53d36868be6d976dbbe4d40d08e5814b8ee693e693e652664278fcda2d9dd171575b9e20a0b43b74c77e9709e1f1201c10d6e385d820d5e38dec4e7dccf9367976f8a605c1f15d1e6a4df3385ee9dbd2bc45777b98873c7a7e871c7a3e87d473e39e2e9b13328443de7056b0d4ee99a03c9a9206a14c086556e786063fe976dd7af54b5253871d74a8f55b6155555575e21ad66ddefd5cd2a29f5c85adc8508bea40390c2ec5ef40590c364548e78e237d1259ef8df7867b6f70291e4ee9f846c74328bc53cf93f3783805528155a015c805c402bb805e54080542815178474a55372598bb13d8f340a49e35876fdbe508e9b33a717ded310f7ae2c43bf07979a648dfa587775cddacf9431fe4badeaa3e60a0f530a5cf8d0a854dee0a1a759cebf83bc7f8a0277887e6f1a7270e0acee39d141f30d0b61f3afec75dfbcf73834bf141ae1f1b5785c52b71023e761b47d67b98720e652fd814b1748e6facc5119db8145fd127b814ffbc48ea6507dd5cefb0ea12f3ffcd6dbe04f30c111111114121222292c227de09def9e38376a7d633343df3dd996f26c4e63d7c0aa34c0aa554189cd58e7817c433c54f58b7a969b3f2a8100a97e271aee31b84d2f117d0fc12bc53931da1f9bccc8cd07c09dea979bc122736c876fccc8cd000557583f9b503e737d4a6862ebc92f319ba6939e8e69e74bc0cddb018bad1c4e3a04ca8de568b46835c93ea01b8141f441c552d99164c4b4e8ddfabbeb869eeed3eebf6523b201b1059e29d2d71c91d16f1e01da25c9bb3b197965fd7fb2874e2f1cc5ce6d8f7f1e4f8553343dd9ade73c0f4bb0cdd356d55bfe7638a76ed2547df2f84d1b814e1d9ca0661cacd64f0ced372f95881e6983a31a833e5bb217c776ec050337461f0cee3182c8ff64cf23436f80e5a9680d0f2ee69f2736ada3e203213c2bf98ba6e1cbf1e763deac470d13111133111136de9113d1e6c4def32501a87a5e45099167ce7183706c7b56f3cf43b323a8eeac4754c8d54b8f4fec3a72054c3c29c143240e991a384508cb953d4e3e2b870e1726de93d1ecd75f1d2a57e8e4b76511411eedd1faf6e4a72fa06c835df09ec7720526f407a74579d383e75ec5127854b7209dee1bab95a0497de596ea16da5898da813834bf2401e8f2b6293bc3c8386467b674a64fb55178616341fb8f40e844b0fe670632b64848165836cf3c85c9ebbf082bfe03078470c2ee21d35786703c265308fa3c297672dbc333a4d1a1a1a2d1d15335a5e8612f14cf2d488feb05bfa81cae339c09adecdf0627358968c7e98f3814bd4663eac681be4a246179b0b238cc7f324cb1695c14b57719f2c3f5a9ebb601ee723cf5678678bd4e0e29ad8e32a180bef54977fef3313c2af62605d848fd7e1385b91672e78677749773880b4ab18c85c3629366160be4564a0d2f22e0c34c4f052c43bdb6e2183773627a5b5f0cee6a6b095c7e3c433c96327693c8577328d5e9139896eb337b612e47a549590229d783c734deff2fb36ed34f2473c1e998a818a42221e55e5d9cb8871c870fddacb61bb9a36b701bcb3cd20ae1351841118601e9a67ba7e7d99268d921a7d01e96bd7ad40dba2314ce955a3b7a80b969e3d0f3748bd79c5e7386e94a39b22b2dd21552f8c4bd777c2c86f519729bd1da0af5f3f00ef5c6700eff0af07817d9b94169a79ac6e73ce6f9aab1b4d4f9abebed0369abece1911d7fbdda96d34db7966bbadeab65a82b8de00de79bf4e04efc05f3d7c9d86fa7519de99bfbe39173ba9c6b53f68d46111942ebd4555141555b1a59dd0254aaf13c2c0a501d0eb843078e9ebe1e4f41e5b9e5daa25111151143ef057cc84c04a6d571bed5cc56870769908d420a4e2da9ced796996424b185d1d3ae10b58ba3a9caf68bef48ab6bca2c7e3d26013cbba957a7b45b16eefed15d138359a5fbda2e65f1083991078ae81b6c166347a486cc7854b6e68548dd8ecadbee150df647836ac6fde1d9767e2bb1ed6b8bc330d5537ed2dfce5c6b041809c17782ebc43b9ae0ef92e0dd765836c73857189aa33c2e7452ae17259d0aa6b5614750a1312a9ea0e48c3be1648dba22e4fbafafb8218e41deaeb8870dce8f94da6292f3dbf613c4b3cd33c4545531490259e46c1cc51d42b99b92a5244d5e79d087ae2c43c3f3cd37cd1132d3d774bcf6f6e4a1fc1a5798a2ac1a5798e15035c9a5f08537d9399ff81fa55a7b6f2072c23f2ae7e5198ab7eeddb2e4b3ce979254ef00e1472053b0f8ea20e448679969f53e29d88d559d5d9f39b33eb8fb322db8052728114d1fe6017e17b0f3ee886b4bec1be30ecb22a3831ed979366689132caf7de838f0ddff9eafb7876a128a65172b9f4de63f898bfb0f1205c5e47064dd27507c0364af9227c6f578361808eb9896be805c207e183900c65805a3be62156a3352e31f3ab0bdf7bccec1c848f9d73ecb8bc4975d19232713d3fefe61fdd221214f5f528f547a1c486509fbb1007ca23c059f1a5a42ea5ac7642c806694b13d220b24db4fbe3c908218cd00dfc625c09537d837ceb70323cf50b5e105e867722df511f60682ec60b89d59ccb0c1ff365139c93526a5c7a6e9d73f03133919cedbb314a186a93e9278888647c80a1f177001a478e183d23c69510c208b17e0fbea88518296a2119ad7129aea4a08c10c208d57832422825453d1c1c33a0e47e11bef7b8259e5f85b082e9f7625c18e3e156ecbd0b7931beb7709f4cefb1dddd7dcb6a705c7250cab91366b9b4955ae3123c8452c6287abf59741242b8418a92f03d0fd23cb80630bcd1d782410d314ce96b5d09b42d12c393bed69940dba230b001618c736243a6dc0ba68cde7867a8e3658661311312376e8c77285969306a52aa6251e392a37a5ea4325c72bdc9f04c9ebc33041766e7e4928b3026e17a62613f1ec82622f2b0e3163f9d11b40d12d92d51b0a9bac5cfbb9f78c218efbe7cc964bd29c2b13ae186fc842066222222a2203682737cc7fcde61bf7a6304fe1dd6cdb9e8989d730e3e0add019aef200de22e0203858c1934bca44a0e000f1bd61b21b6f9eba47345088a2722220a8ace0d61b6921f1ac2bf2e82838b25a840c90f1deb8d11d8ee10d625502110dedd3d0cd2342e693d41f51c7251b834445da8f9b15aab3aeb9755d52346b8730e97e0a9ca43a43414874bf0929e4bf04c4b42d0a6c63c2727888888887caab88fc3c211c01d1e3bd5e8805eb35053db100b415b78485538345d44c116bad802942c40e98b342fe918f210a498c3c054390546d6cd3ab5382c0be60e0a4c8e53168efae31ef31e7651fe32223f328fb9d446d5cce3130c0c8cf59e6705e5701898ea38602ad0eb1ff999f7700e309f99c901c7ad8a43c2bcd24ea2913945a391bee5a839642ea91c2c98cf54eb389e832e9770583586c6d04d9309030ced477bec6d65aa5661de80228688c0c0114ae0b80cd54ea2d6294a8aa91b8eeaea8977acead5477f7702866e07f10ee9ef4f3822de71ee8914f7c441e11d47fa48a38e54ab3b3aca2eabaa8b7626e82df24295be18cb285c20fde45d1840240378c75527e5407ce01d6a7b82e09dec19a40e081b65c43dd20d0ced6dae37d7c3e71daa6e433439251e7827bb16dce86dbbf4bb13bcb3b92a41ccb33eef4bf0ce1669c14bbf2be11db759551d14d7037c0f6fce082e419849b84d190feb564579aa14ebccc1233158e9f716a1e1c5f2a38b02b1d165dd82de93429c33224a29448ad1f36026c419e1b088512ec83bb0de0807c508dec1c245e14cc83b725523b8b44dafc07c44af40b90fcc2fea17495ec18daee92d3a038dbe1e16adc02a8f0a4f89d549e1d27b8fb1ba285c7a16f6f81eabbf3d150f13f991f4ec9adb468f7fd407185a55835e67eef214f50186b6d522550ad64c5697d437ac52f2f22a52d59ff7d859e5ea2f3b12fbc9ead1fa90abaa1bd6526a14e6b2e75d0f939846379aae70bca294bc46717cb32157cdde38240543b567f4fa15a7b8a8142ad44a84a11c2539c614a2a1190d0248431460304820140b0724125514861f14800db0ba4a521d8ad32007520a21648c3144404000000440442421016d747caabf79b6e2f9215acdcec28f42b7b6b49587506530ba256ed8377e4f34254995fe253a84176ab659bb5169865ffe52cb7877633c3387cfd24d618da3cb7736473bdd004496f45eb6288a99e169e94d2325192b52352483481ada88a4fe87e8e14678352ffbf7215663587fa24acecacf854a63507655b7adabf8567e4c3ea388f32d70f26cf18c0ecd7de17628b175326de0ba356818ab7e4fe075ea483a53b655580b5fbf6da94d5ba86cf74251aff5a4f915c3dea156a29af9a426d41d4d7647495a909a763fb8d0a5a911772ec561f6b0d1b035846f314d52f6db0a16ada0633d070cee899eec892afae1c383d677741967d0135a38faa78de37490c56de708261d23f68edbdef61d310e27acbee8f3726f95049f7ec2d9141fb2f627df95bf1043853ea337771e6b087d3960e8acbdb4620aebdafd23c39d508294cf97acb0416818b680db66530fd290cc4e98e81066a8363716895b5ca03de82edc880e3ea0ce6b78617b9f567ce40689763505b0321664646111ceb8a57107f04f4303563981a789c8ae5217f25d6fe8f5bc352dd6774cedd0c9588b471fa8ac4ca6453c9848f057fbd0221caaf612094f30438c698f70dc6563fcaf87249dce84310baafc46b883694ac0bc310e9e0746d70613106b0a097905c8b0f5d5fa30d9ab42ee76055d2d6fa1829572d9850a54965db6f4efd5d983ff485b312ac04cf0710048305e1f0131574ed2fdef8c41b83c42dfb4924da07e38642be92ad34171c394e5eef045d87e19448b7e95db33f0f17b283f9deec9ddf019d1b5c8434286737060e05262abc698492d855e012367b43a0733dbc4817168e7a19b84ce0a58c66d248f6d743c782a4454f1487d18b0d87584f0caafded4a73e88a0ae8d225dc086180a21b7647d2d8aa0cb6a686423cac2fbd93d7b105b4221c30e1cad5b2152b0b77b46c36b4c4f1c927524425b0d1e13dd31f372560b9f7ffad726584b86eb3da780a1ccce3cdafc94b7723745c1d597ffb28ce5c85622a79c2db5697609f4a3db0cac4dd86cddce0bad803017197591bb81ce8e34fc521bf41412f80cfb8161e0c69f55d713a80701bfe90761472e68beb2688dc10e0e5cf5324389e1440f04ccc545abcfd153299ee833e658bc73c1b9a3ed367c78db0d058b74f0f258b8ece5a72c7ade62bbd3805a94b251ea846729edef10e53075149389093c15d465bf258b2ee7f7434047e90072fc17fe49120b561dc3caf0718b2f81f2cacfdf9c9b7269841b51a3c85021036a2ee7bc22fda6f19fc6de01ce4adef956df0c3fbd918e80e08843a6b3fcc644b528122ac3b2d7c2b46e39e4e51f7b7d725091c8db9f5a1405248ec358355b9f1b3480c1dc598efff8f81ad104173a9f63d680cee535d517e353f46f4c44ff96ca6bac81e48c9da4629ef853e31a69728964d319160e0ee38bd82d93c64082ebafd093571dfe1d93924d57527439dc5e8beec76454b851fd7eff260659a4d4c44372e3cca720e54cc15bf33adbdb050ec29937d0c2f695c31c9e9e5dd131d65121f94aaaeded14f004badb3687736b30dd2645624bde0da5d5cce3e163ac0abaa07403e74f37084670adb9d8c7523942d69adb10429111cd0bca35d30e6e8cca9b874ab7d5053e1bd0ceaa3b85d04860f71ac34c0a0b9d8d1a8f501e7ca49d50572c956d5a44bd6c4a5ce13e486aa7de8f9d966b79ff57709f1d52c79a016727f1d4745e883b03780e99a82c8e41fa38267817e36dcff788a104624dd238e876aee9e38aa53ca5dbc2ceee93dabee80484de40c92acaf806d50712ebfc3a2a0bec5849cbc7e65d5aeeaed448718a5e9d0e105dc0336eaa5a8acd0c19f72071e70434a54b15a047154c34ae522fe79206e66d957918dd830345093ce9f090d76cc1b90b6942fda32b80048e4e34f9661588552a72fb0a51c69f6d821a5645e17b443031647621145505a2bb07b4c9b3e17d58e261fa1238d149703b424d35a853e6df7124ee499477099ae5bad6f6aa2895a82830f2bf6e52bd838eb6c189f7c97e462042265eb7b0119443a928425c57612a3b7f8923a2f81aafde1fe1440a5a8f2bdaf3aaa803c39a03aa326256e2b852a6c2005760b46e0ba901c5035865b81e229c4e218ccfcd0424c472464d814fe6c66065b3ff79e2c24ec4dcb4e4da62c5cda5adbca41313c4866ceb1238239824725fa60a0eeca742d249e2b14c44973aa2ee6a36033f861a39bca65be695e7d7cd175d9b39ae774479d3b8521175b13277258df14d915e9146534631599231d6ac258f1433ab5b032da296dcff98ea1eeb3b2dd24953cb967764111f005df04e5874aea718f24536791620c388658db57b1486a0d5bdd9820122e9c292a9e081a34b916d0f22ea1a3189a581709506e3a4a1e0302a961ca29b5cba362a6498f3f0efd8539643dc175463a8512dd898bef3f4342f166cd763b1efecf7e2b33ff0302c2690fc75ab9c1e3b9f9af9e6e532bb03a6e30e0cd76f01ea6dbecf6e9687c5ba321d8bd06ff0db672450355c100b76fec956136d1b81e4337a79ea6dd205c6586f037d93ef42058a259f617e574703f2f19ce2237562c941d11c0243929f685c455a54a83f3a15f87cc4064fdd6702b54696dde0048579c8c89c6a699bfca4d064994c4317bdd2592c5e0a2b7ccab28611964a599e108b65c2f4b671fc7c3938badf049d0d37f160dabac2c10ad05575664fa9b714b2b17ae2cb9e7226edff4a930e7d000fd01cd140307abd64f49c75dde82e02558b27a7f41c7fce6a806504831011077ebe36930f730adafe7283fe387fab1617d6a35d5534920840ed7e237ebd188eef3fb2a4268ee754d81a81eed2b7123872f896d730f293fac77dd3fa1c6848bb4172f62bac5db3a522a3ca7b8db767810817bc1b0537a8cbe3dc7256e6063540a08ba0920b44cc2943f13922e35025fba821fc3ecab8959d59aa0301c8c75501f23e12380c4a2b85d100e06f231cfefbac44a8c7695cfac7a1955337493c8d7dcb8f073d6f80a9a00d07e3b0c0fd405a3e0ef8f462a9beebb19f11f6b41dade4e911cc2e4e61b11d871229b799d5165198d087924e0c02b739e3f189c1f49a8c0afe0459af4af2231b50611647a3af73ed16e0487c8569bfcc7eb1fa6413f98839aa6d74b732291c79124a45292b3c90e2b8af2fd3b32739c44db9460f74a0ecc7fab74b0fd58cc2e4a8178d965bfd787123ea4dfa88365d52a8200d2a4bb807e80b232ceb73f3a88505bade0d7f203f7d10e942138d1f184f1b789f5e4bf03cf74c7b494677eac2499d1629ed8b30d1f33135e856a1fc75b9e57b6aa77120d1da3536eafd39defd3a3f3486a607d29253198e2ab25b04d4365f94175d6b1bf7269ac9aeb2a7297e760c5e25da94dc91a9bf1978d3311c7af88d61f8fc183270f0ea1b68d0bd8703a0b3c66a7f73ce608944bfa3de7ddf94cdd2afc314028b819a99d2b407a12cb4e7132ebee2b913330d3f6724ac2139cd61963b4279137c076e692e2f486d8080511383dc95be3d03173eed32faa8d08783c032b0668edec92a76153a1068c41474ec91e5e0843cafca5572559c90885882912ab84a2f12a9f4f2993a8ebeb3db5cdaa153e6286c4925e5ba7388f1db14c224db8851454686dc73ac1bad240efc72810e5052481fc1c4db6e3072b6d5f746a7b527783fec0c9ade24c03254f2776debbac45d57012ae8ff80567943cf1320010afb823bdbdfe66bb77418e05d3a9f9a93eab866cb7fcb3aedc9b614bcbfa84f5132a468b4098fa74ed8a5459d940186768dd993f91c1d0e1b3242bdff0f6c319b0dcc4a0efaaf36d0c1a7212c1276fd210b7d578e8836709e94f21dc08373878797766dbb0fe0555c7a503c3953aea54c0e5cf878292d801b9d6c0fdc6b03f277b47f9d95992c859cc5bea1815b32f6c80bfe246e816f7214ed3ea2a635328eb09688c1270e432b9d0873451a77598617a789c96ce10d8d92facb49ed8a6a87bd5256be734b7fa8d8b6f5ada1dae4575330041a4b2170205e1104dee902b3c2c8ce0baf83d6f180759b1eb6a1cef58b44c37b86f8e4e46529d58978059f15d5df0d7c2670b112f708edff0958511f6579ad3051ea49c3f80ff9ea0e8a017e7710a443ad3c3db2b4c51969b1b0e26913a114d3e5caa4ee0c86a7866f2c673a0d5cd795a3b080f2429810b04b302ddabe49b83b80e48e8e06e04fd7005083454f664aea3af06ba4466bfa8cc1741518ad0492b2203d3cfb6d0de3588903ef3a9dfaecaab5258ed7df1b573fca615083ec7842e3f1ef1a0e69a6d3e812a9535e839e118428c6799307faebfac95af6e89fc9d1f28b682e81eb05d63bfdb2f26b395a6dd7ad1f6526ca31cae2a857f85e159b0d1f2eca7a585347accb9153eb5377794a214a6a2270a886a32abd80aad67eaa3270aaf569aaccf48fb5959bf24323e09926b11ded0743f27546bb2a63b2fcf72a52f440ef4163e18347d6254cf8851d56c8f70a542ae48e8d6f0a701e8ac25d21c9481000b426be6de4a11f7f2ffa8650a68a5d8846ce02feff051f8b47788423ac0f254aa0187eee792e9d691cb33d0fd8f45bffd2a7fd8257ef2340af7643c818c00e55c0c1146fe9fc97fb7bea7143e9c12d6b2a685d3186b3600ef9aa21562d380b742bb66598d02701529160aea8df49d641d310b6ed3836b004478e33c136fd2e770e5d63ece13705693a5900b4a87ad50054b617f55ba65101152e58661f69ebc6633d1a0eb282875c8cd66602ae132d70da3cb7b0a82309bc66c26cd7c10f133ae7b10a02b2afc3dcd9d8a04d56025dcd6ba988a215932996b420aa76e4d247923ae78d91ff9e38e182ebab0bd1c880dd476c3a781ad91d6449b9ad98190f3861e0227007384fcac11742008121d7d5a4d741799b6dc1cd92bccd18dc6c1e72ef36dfe3f25db6a929accdc9119ff4ffedd8f7c57b624ccd9f7cf0c88433daf5882b929ae46f709b8aab9043afd320e153ff07ff197cb02508525a203fc5e4f68ea0433ee8edda62c5e24e0b7d57b8eb7d9c36efbf779e94399a0c7a9feacfd76d41dfa6f1b9db11af99b4b055c6c53a0ef969e5c18414dbf33364902d84b91b5e59e1f091e4091e9a3f1d03fae2f5562ee4f22512afb6a90767b951aaa12bfc85bd477ed5ec416b46720a40590d49b8581123d16a079eb0c1c85dc0900c56fb99e20226daabea63d79c8a2896632da5adde5a81688db995ea54b0548e458ea3c346b3441498ba0a414e291a4ced41c7001490dc9d7d1aaed39631714640884dbfca240364a212312573c6e42ff02a70c8a576147023efaa2c0cb8df68a75990c2d1c8af846b594dd94509d1fbfced6aaf2f21352f6ace16d3b182deb5ae1f96d1d216d517317b2f6dec7ef4ad8233fa9a670187d8a82c2c927d6a184c4a2c2caa7f8cee9642e58c352eb1bb1dcb3e4a5d6edec821c0fe69a31cc840f143bd3f09747297141ce9985fd70e120dd68069c093e92900c0e62be2e2c48209dca3a511b9e5f29aec0c2985b850c500973b6f66e6ae6eff8c1ca6ddfc21e9ea642af66840bca6b358026b91f9f37fc1a6c577f98003372065fd5298fc0af614e09dbe20d824306defe5af2e96afdb670fc5f28502af7ee72d463cad8b8af80ceeb9cfbab30f6cc12330ed9cbdf5890e1678f4af499bdeeda98b0813f4e7e6260bab85215f3ddd8c07fd48c285bb6896e361e00dd2979c7a704eb3e1b6cf8f29e89c487603a86786b0e4e2cef7e7d085db587a2a0ad8fa216c2041fa7c714fddc066c8c2cd8d062eac3687538475cb147f65c9e8ad05f195ee8c177e0ffa7bd2d1d2729b2dd7f8e28a0cded5e8676031a83fadaa790a35ace81d8daf8ba79bc574eb0d3f8bcc813c3aabd88e284fb3196287a2ae76a3858cb38171a61956bfa4d211d80a8454d5151be86f0c3b183298075a6b77ee038abb380c9897c31c464eb4035c217a031bab6c967611f359c686c6567a43c1e3c692331b87246449d06efaf08da3c857b350a7085f31e4c45070f6dda1e54aa172d6cfbc2f0dcc8b158e9aa270b2420248f9c391224df8537ea8e665330895dc6afeac6786292a5bc714ce4e25c21900e64f8a363b800e6b99dfba4a68d32c516ac085b6809509d78764d002a20a77194aee48901a5a3ca41c64c041c90f13ec8d5e137808d106d2a9bf437a67f3c6b08f2130fdf87092be0f4cb01f83b88c2bb681f0bd9b2970175c863c1f707b659ef90b2d040ab9fbdb802f6047aba87361be1b3892eabc4888a226a35a31c166d119de2c54344cc77bc037401c6efb4d86fedeb26c7134674c66f372e33bcc1bcf18f66cb128527d0130f4b994e5f905e642355949c06f7660376bf96b3f1158e8b7459b900ced0eed29c7af221c10c9afdc1981b20ad1193f767eca175f42c79a7bc676f048e275921dbd022350c014fc07bee7609a26986adff8a8389f7c563e6ba29bd7987cd81ed931696fc38804951fd4f5d9c7195152f8d36289061d24b16ff0f2b8ef3dac153661db9ef117265a2f681ccc54c12595a2a56a7eecfc8b355029cf080cc8d81691946c421492055705150c2c52aaa74c03da0f97d9a927715f03099ea84a2b1051f628b88be5642ae1d5880b82787ac1ad98f03aaa5e685e2b001b3566a34b711de255ad45966100818ce26fca564f6dc88430331e218784479817e10040ba8350dc60f2060856bf97af57cd872ae70e3c8083f8406d28c05ef8ce0c16ca9b72d75eeddf86ae31b6a0b3ce60c1a0dd7bd520a585df6e28fe1a9d65d8d80a2b519a37cad0ebbdf96e79e28daeeaa57154cfa69915d54e2895aa276c72ebf6b772f3b90a20bb1e91481d0872e812f0e7ae56ac0a58ec70cceb1780cd381e8992a7d9ba2812baf784393cc95c5023cb7d2cead801d99bec0ea1b34b708ced4e72f2b88b6cc1caa601148a7efd1313c0b84e7c4310c0f15f08fe8084f21df64299ba7534299b93dbd1613b5be4c001a66bbb809781b6dab07b51d501f4f1bf262f0a2a523c48a51a7f0d251bb87a9c72e35052de4bfa0a8c5df04ee528d55555e1f98fac82a69ffbd7f01868cbce60b7ac63f0f877d53338c4aa46b5f07e3301569e214ecc8e79fa1342f99cf450d478106c6cb3a4d839a1ef304b89bcf578ceb3ab078eee2e60e943ddbf1f9b0667d348fd913cf8770577cc87060849719758d641dfa0fa50a6cde99221f7418c64eb809aa3f41ee122c79d598859a80056d48f46419c6bfdb3fee45abcd2cbb955b0b569ab972839856a6f3491c6bc08bf6a7c39aec0250599eebe57e6ebd5c5b7833c85aa29cb9b805d14cbc14ef13de5f229f156134b3b05a1b3e8c5855e89aa078bc9d6eb3fd84e1f9ad05e6a30ef4c944c21f0b9e60e47b80d7f1aa8d7cee5b8291a3ae13471863a9b47999dd94945b1bfb1f6061608ad6881883afb24c12f52dba75bbec8ed4fc783c558118671de5ba53d85eeefcd0a34064550ebdc6764fb2e77b54cffdfcc9245455505b18fbd4bde15103abdb6887bd9731c5c138b5ee8f08956a7e39c90d219f632cb87e683540cb036b9bd95c25f26bb22e24a1f090d52ce666ba789377d35822f3df53ec57deb4a8895f71251f6e754702c838aa442e3cf2447872cea0f265432b0b83ad7704325968b88c8b0765dbfd78e23d40f66c4d2980823dab1b6dafdffa64bba038ba39ecfa26d870909cf8742fc3c352124ad89812f6a91719b3c5b74d48805c6854d903b415fec0f59fd27266a739a43002a67bf342cabbd80e251cb556d4091d86bc57e432ef76af8216bbe3aa5489ce97617118a4e9bf7919efe0a3a9e554fa4fc183c233aba217247d1c7ef3bc79ea29c9abab93b74e65fe5bba5d07eae9d1d94138ebc917f6eac3b9b9239f710736aa43fc7a5d44416317d127a4e8259ec8f48b6b794cba869914c49118c46b7194be24ab09aba7db0a49bebac458eb97aca986ea92e42aa58216b9ad37bac9884b6b16f871231a326dcb558f49c54519133e168111154404407aad4d18ec939bd6dc552a3dd960f9b02a6293031de867e0a310114ddc51c9bea05f7eb7b7357b78e6e75b780607bd51788449c5143ee2a5370427b4ecdee9ce4056d9640571c59584e83fde48f8b3cddfa152be925eb03b988bf4462c79a06bfaec20fad06f206c2a815a93c81c02e6518d62f9118fd2abf295011ba6d909d85a08be14920db7bdc85faaf84f25ee3915835c5f97622078e11591c27e298b20aadf2cf6afd086feed330a0b02da13bc9d48b8fb416cafd2341cc4999258cd8ad2aec66313c0237232b2a4448483d7095ce1064b3022588a48c06e7acba529e52c76d29d1fc012544b9dbeff0729f48752da7e9a30cd6606b1784957bfa94f9199e4e380a4c8f230a570d1b33d5e0ee034b6d782fcea90751210eb0d28a60375434cb063e973195542ad7c9e6babeab19477a55333e1298c889fe4578349215ee661f7b4a2a616077016ee26c81b9fe06c184650d2caa5d9dc2cb701192fc39e7eb9d3ba9ff13e883fa8509d37c744b4395cc7189d73b2f015d10fa19918a4c9b29d78052d1ab891ea98d0278c1fafa27f19d726b1f64f3e9ca9f977ded883989265aace1319d72906348521a2dd78368e4da957a4993ac78e6e764ae7419dc713def91a79cc08922beff70d19b7a053bbe8dc231ae895ae304750151ffdd94c853983921819250fef069a993513d4ddb2d0ea999e9f3a970c1a029fbb14a9a3bc12c2b9f9d8e0c94e6be4a8651253793b75c93f8667ebe4f6589fe14d051f771034e297fa430fe81138ea5830c7ed1969936e1fd65b5f74d3aa1c650d27d370e2a0641e10688622e6e8f32e7d49357f258aa25723904f6c0e236eb6b23fe12879db2260d03819607e39f4df4cb2f0990079e3d0daf3e08c85d0b0eec5cfa0058544fb2117f12db6a16fab49f8a7ee528414f5bc18931df77814e4968fb8518b7b8501616e75c399ee32f33ae1b0603d1bd9d3a4aa23febd7bf2112b0f196bb8b68a3c06c8b0c0e08a6831c3f22beee3b873c46a711115383aac79a73bb18d03e782d13d493389c778093334f1641a2e553a7f45d636c83a494d36a31886ddf35429a9a01c80fea6a1f8ef3ac442b5280afd83f4b975e25f93ad61c109b0a73c25ad7193c40ea660ac542afb734f55653e85cd75b388bbb88a70beee1b1feecd11acc0192bab54fb9917ab6d9b61ced26b2edad32112fbaf405adbb75a96c90b70795c785aff3ba66ad8f4f9e3ab1e41bc282e31d502eda0e1c834078c41221b68a68ccb64df72ea6a2233b8e44ea0d5e28afc19edbf0d1934b7de0b4229510b638f835b7c39149e34c87222e63060ee2de62f02a59db3ce646d4451bbaff1824e5d76ccb204e081dda1aa9e279291efae4049a831d4ed5c42556482ea2391bff6fd40b1ef891a99a5a52ecdeede36c94804fb19b604a58a601eab8840741b8bf60702dc53f12ad23a1f9ea813f3091ec187c50b501a0592ba52afbc789927f7f7f4d10ba8608c1548d81dc710c9bc22c5096cb3350cdf288320a7b291918a7ff97c7de4b6f50ae77ee7bfd25014a7c811289b13f9fe5820596dc8a839d5285ed8ca7d6a97f2d08ca54a482f7b6498b7eba5fda872e907928bed4daa5840037a7e94bdb7f379fecc4483a54ea28d28f60811fbdcaf82aabda01eb6ded9de4da4345c66424be00104304756db9db375fa21cbfd3582143c8d3401cda58acca6d557cf89131eb3b977644ca1fc89b2ed931f392b96684f1e06803ae45fc4984697684fcf55b0c5bccebef93ba637c7992d814693d88c80014dbefcb993bd30db1d9a53d3ff510dbefc6ed974ecb4de260a3a2a418b38c0d55b695194e10b5e49e2ffd448c800c5499023b92551fa240974b5ea0bdbd2e907d5b2679aa90358f97ebf2c61a7f92b14897f819bd2abc7a423e3d782fe6d8ebe2a02cc6ac60edc2c7522f8d0e41e35e18fa6ec681879f2f13c5d17ef44e755f98641aeb0d760338a9727040257af1d79e716dbe00b28cd70775843e80743b8ba460c8e063f6736012d7c1bf8a2d00832b9ab93b63cb6bc54e93e886897a31d7b07d5bad71234c83fdc31db2d30bdbba7271a2e9ae3c72fc6c5b057641c8d8caedf7fd04ffc72d347efe566501f86bd1cfb329d2f2f019d92034ec9dbf3392f44b1484516a48e7081b486fbd9f83048a58432124a32ad0eac08d2e4461df8934dcb84610c13da6de5473624fd9334eb42d0cd32b5ba9348e6343b79b02dfcad82d2c94767a391d64bcf8df092e26b4594493840d2121cd6199655bba79a5a16895398961228a735a15a06f2f3d5f150bb38a6d34fc69953bdcf44c296e2ec401ab068aed556cd47d2b5214f52727e45a31d0e964b09a3b00c987473f255a186ac10c66cad87bca8ecd6f2e2066b8ccae9533f28589351010469365e2d3cd366603fb2c4d99288d589329891664b0f62740cbab97dba811cd86a98df3703da6f1f37dd474074d4c27f68d9b781a298bfb4268483f040418641d566f59d03034c0678e18218eb748e212d7cb6543376dc9d41dd9c7021f0e807e12ea9509dbc87162b6472fd302d7f1f90bb2dfc6aa8f7d5b9f181828f762cce6f15cc7288cd88d1e168dbd5b316421b617297110686fa46322ce6453d628aba0916e347cc738727f54a0cf1935e25e0eaa2fadd59b6ac2689bac70fc1229a28875e5905bd3ce96d805a47651287a00cd66e970a6c5e4da8d9276ab720aced82b8ab6dedea8090dff8fd2be46d606caec4f7aba992ed0e8459155536a0a5d862a34ebb0caf6f6283f8a9c0317423c351d1415981ca66f664f4da5a2a6815c1938210739b38abf34ed6521ea5c6567fa33680ef7866ee87e64a9ae52841bad283914f94064715a4afe9b0886bfa27405698f94fbb934c6a80b908922c54b05c9d0d05820e6e862f9e70da5f648c08156b06a431f480acb2a140e16c78f00dc12d4686cb07a9e4863da6ebb6c4ff7270ab5bf9c8ec538511f4343f1d50796fd68286ea5055b8980f6d15d777f872b4bcd8aadbb0baf6170fbf6cb017d6f01ffc8ce582a928d7d7f24810eae35f2c9f1e54fa283e636f8c21a4f2b854ac0d245b1a152ccbc3fba7c104bd6582f0ee96ac511ff10fc69533151928c58c1d44ac1b7114ee28a5ee65a438eaf9f435c896336f82d20c6eb170e591c66775f993b96155396aec3bc0610780eda1e5db073119647cd39b62256be6eaf94ac91d5f68e4e106ebf77751c4b45d93d5682f8e3d1bcbe2f26e5dda2030ccfbea49c9ffaff99c5af808834763bf9cc958810f988a81481a3bfd2f022aacd1b4526871eeaf714f88b6ddd4a5952f6402dc9a8490ba87afbfe5c319e07637e1db95ec45620afc073dd77a2b288f54024799e7f1036d32c6ed8500e4b583bfeab6565beb421439802c79ec5cfec2a86efe0015c8f72e56c3dd14fde520227589e6b3384b6d8ffcdf7f543847889c8f4ac26e806afb2b9739e70fd07dbc72a9040f50bbfd08bae3c21967d4727db515fad5a6991c1e75297fa9a373bd9d62d7e57894b91778d623e2616e7c8f2c54dc896f28cf4a07987b18afe17816ec840ccb2f323c449e095eee9537bb83ae0c5beb37408ef7322fffccf223e3b26c593a6c2c7932d2d4a5fef5249133c2481db05010f8f65f08f2422023360888c444543a5e0558cf85e302399faf1bc2d6192b6e57fcbdbf8b4aac1e38a24e45408c7bd819bc20079bc85bacbaa00c333a40b547a754c24ad89523bf564f282522077b1ae46161363d6f7b00c84f7e78e12abcefbe61c9b2b081f4b121da328deca77c326da33e0aae8c77a8c8e638f58ac334a8400d94f5086e09b5e151adef77e8ecd2404842dc5728df970f2b93341a73d432f730c7e4d1375829ef0f93e2c556d9db5a0c9d7f88cb24cfa76fbb82c62ea9d95226a34e80567bdf8c84fbb74b3a571a00dd93935080750e1c1070db53e918bb678c5eff33c0793148c7f484a01cc39bdeb46dde6442b1eb399189da609c6e1cf11316d06a3b8791ab24d330b355c453606608a30642626c163939199342aaf56dbe5123ea60a4185e5de4862a6a311a9378eb328f4b39b2124ff95c3c8958ae2f6cdb858d4f970b72886b988ad691cb6aa5b7fe9cf3ac09ec80d6e7cfa5159aca51f89663bff25c499e9d05ddcf5938556650bcede37d926ab38bfd4c4829bd28a8216f37a73148581160b951559ef7d169d999e05e13b1a33ad1004350c67a1fc3fd20f7416f743e452b85469646416913d85111180c1148e2ea5e8e28bb8a93802cd5c0a72ea0a76eda5201d3ebca6fed442cb6a5302f04dd698c458812bae919fe5ab2bea519723b903aa933d30b33f2f89271b5e94cd57aae85d9cc489486837981966c9efcc94754d17f2eb2a33ddb9ce8cf87ba2cb2c4342fff9ec2f7c17cd16a79272920e26f1e6bb5e73fe94edb018ca94404a0055d828789d512edc62930e5a97dda2da7663634a4383e598af308e03286d988174d4a541d26e8d8b1937b2cdcb5f8b10b20999b12880bc52e54f0e56b0a66b2405e748b73cb1fb9fe608496389351ade22639d6dc0bdb9ab59ca3dee8a979e290c404add05e9d8b86987ab032ffcc7dc5678453348287842e6405977e5c164a04674612d32877190e61c94dd7fdfd123c688d82de784e82c719825b2df724a7803bdc820bc2474fbb46d433ea06faa959eac8a18a95f4403f1e51c9d4ebee7790483a743d22c6bfac155128faa57077ac5955206d370ae072e3c2b770783b3539a6a8a1e439f4087cb225ace65b0e26605ec0cca66c63642b75a6a913362447d2d17ea44a1753b6d94f01819fbecba019474f364b710cdfc9fd75d31caab1918d3074b907e44312214b280eb4e0a0667cf4f385c7db38dab8b5daeddf0e01842c0ded4fb023bc22ce54b8e93d0b28621b26189aa326347a6bbb9860e3e467395c889266252b7df0c93a3ce4e60840b17c3c867e107e65ceaf070f3db76211528b41a46e0e8be26796f930856ee5650909c7bd0e671125e3ceae4dbc917a092d89dd9268e20212f52b7ff060527a1e6cef6e0acc331222ac151414434e99dc5e09976aedc404810e0f24c948c149612a6403b594ae8bcd2918645a9a70142a15a7d36647897e7055b32c42b97d698c6b6d521ebc42037e3c69a1162d4ff1ae4af29b9f34c0440ab179be766a7d44aaa13c3b306f8358a61b1b769dadfec950e3357271bea6f99e79e4b774ee77fa28ca03b9bfd83d8dcc523b88e03ccf08169fb8a965d0b7b35318e9d9b47bbac4d390b7020247294904b8549996b29e9ac267b97269906103bed083caebafab7ccbb954f508942ed5129091b242cd140d7ddc507e34085c34afda3d03aaf8aae893c0f9eb766ec176b1f8494d9ae120a6903f6dcf1b7401306e788133c4a5512225c3ca87b7568109c7b37cfdea4aeb94c38cabbe0ec56aa2f2573855e441a5c803da683ca772688ed73b839f1adae706dc3ec4f3b10594020afb078b965d53d4890007d66a0945e13b675a0e26c2c5a0cd18abc6ea598105cd2f6f870e0ff48063b40c26475dab7d6c6533396f5c10259dbd28d6b7ef8be3a2e98d0c3f09dd8d2872f5454fa40860b6fb9b0e1850d69577d95b75952f0e964a5c99a2b2cd91e3f83803f8f34c9ab9dd0de6e689829727f008c5a6e0447fcdd70fd5c0acf297e923a4f318467c346a74d6f4ed47b00d690a246e52d7c74625aa68d3b8d0893c8adfc57bb8f075239be2800857970cdf45d23301aef3b28bc7d315557ee42ea6d4d3ce5f122c25643382908f14325b85bcd66eb0d00ceb28681efdfdc5cb7f851b88906d8ddf16d550f26b4765da1ca7951dbeed96ce48ea295e6cf041f175e11f592056580c053e02133dfc4c6d77359c32b80098e37958fd23c348a819b280e44d2ea9332cea8ee188af16f6f5a296e723ac9a086adb44275ba17675bf35378290b811d6670109356d238c7b271702c3617c00339e9d5c6dfc4817705fdd1751841b3d796be907ffc96e7b3a4c8b9babbc20e3f45691cfe86ea486b4d1b504de1bb19542e68287e0639ec7658a7238248a25a87f7b8b0b5988af217d443bb83ac4919d594d3d310681b104b768efcdf2f6505c5f5ec4abac010256e8e43a852b877516f374501606efa80264da82c9954dd89e661a6ca9f2e8fd195530ec24b73a4f6dda6c833adbc014b6e946cb60556b903ad7b74305d10a70c4c1994b89ed95d89ebfbea79e208d0412d9ec768b5f25c58f7f57a4f616aeb63b64f7a7407cf605d03339a20cf38a7d5f61608ed8533100f4f44ae4293bef1f4b1fbd6e2b48848aff3e8b2e4961414882c5749b175bd72c1b929c939695e1b62fe6a679afff877174ea6a85742510905ebd6c6273d4c86a3163cc50195df9056d3d37474e3f41a0a0caece713aa73186184381c8746d451c9982cb8c0394a05a5eb410dff4bebd49c700ac05651515f9cf1ac44f02197f18224008738a53b2a0842f5d5289443d220e5b98800eaafdf34ce380dad1b680a1384571449cf40a4f038ec8e0313a50b04d8114b46d74bc54fbed36af3c5362d53e883571783452cc46c8faf88d8eb693194a430876b8d0bd7ce22545d143f54eb204dfb97d174f23cd2ad9b4f368d2474e7e51e3241c584733052911b9d0b4cdc6c2e10f64550f3afa5d3647fe7cd397f98f012e99b54bcdf91f1c31a50145451ffb2984f0523279307ad3a93ee8de8deda792e37892dc066d64e1a8ea682b1a010aa66e6d63c49808210f6802fe9933cda692e26578739aee53b73428a0c136ccbbe01ad1c4da52e46dabcbe609a41325c9dc608780a20d49ffd684a2cef8373eeebbf78c2a9a1251e31d63d8f1a19b180f31443069610878b13dfda6f87df763bb8edbd9e7b21e30338a6aa5a6661c5e43c7a93e9c36c81276a0a37f88109e91838743352f2a6715aefbb057f8355ce7effc4da113bff6f84c2a1dc624070550fe02508ad927e683c0f7cf04114d5338b0484adedfb3079ecc208fbe9e65ab7c88f6c91a221330011feb79d34f49126bdd9340585a236014a4a56b1e17e4eb6a51634a5d7de4c068e86fded6a38feffee4f17275983cd4c0cc33890ecbe76cbf68e13abcd1c2e426d312c93613289573099ec3226a9838212d0703bcceeb253f7e380d6ca98ada34be507166b4f66e2ea11a2051c20b2d8f13d6c1e96655d547892ddf6ff27d485099d14b2b3ca4ba762fd67defcca2af236c9c7e5c38f3deb2bd889ff77016c27945aa8fa487b6cf5e09e5c42890794137cbc33e9101c77311b12aae6bc98c145bf7b5ee9607ada67715c95371f2a1b0415f9626c6a19fa4c7a14281d4a98db50b6c1c5bb4501c286b3e896d103e0ab6abd8288e38b2956449484a7eaf8158cd29f6af42f46aedf5c7468109c9f498069cc7e3fa40be4fa8002690976d5678d3a0e49d6f518ff8643edb135001b870fcc9409f84c2bae23c1550008225448244ea51d6ed0c70c72e9804fbc3b140f227a98dd5ecbb37899b1209da0583b3d0798413bdb0a8e65bc2e500d1773ba4deb6f9bdcfb80cce86ec820964570d52b0d3f5415bc6f945cce4a7976b80492ec03d956140ce4c99b23c45e84967ec4dace7b76012dc81b68c590da455edf2e3c298ea688d43184499f443025992f85eadc8df9159a9af10489f8902d340a443c735c16e5823bed962ed7c5f496da9ecfe9a85919246fb7c11e352e4b4b9cf88c1f56bcc662b8c7159761d8d8c1b8c9dbf085686d26c65a0a3840d8d925329654a3d1d1f6c6907ee45ba8d85b0f1c573612c5b007ab460fd27070a5843a60554cb86fbb3ff2cecfe9dd826a396383413dd8fce51a571dfa5ba74695689afe418ff0415334eacfcc43134de5f3ff0b7008257678ca71c21de251180bf41b13f856b5c32b8e4cc1b65342e258233a878a982c31dd7f607f492b6d03edb54d99a80566c288248c0905ec100fdfedf36e7ca26bbac5e5035ff97a6c16b6f14939fd29db65f8dc97788a65cec9429c00e57c33bce7e645faf802c363ed88a5c95b9ef548057e5dccb4071d971a258610bc6947a2a273bb7189aa6e067a66a6acb374fb2fbab0d0b7386691f842c4005532ed2df61aa4b5e3d11e16e262f6b6dd9927447eea0b0ff9b001fe2dfc391233efea1edd4741ffbf80813cd607bb7bf69c2148f44079892266a179c30484b050417536c6f50b381eaeaeb52f6b1477e20f01d7dae0a6927df8baa454b54167f77c2637ec278a6accd3add36c152b4a9f69f6043815c7e9ef88e434077664707b9995b90f11bf12a8a1c48150300bc045b3d1e8b54ca5ed56fd412cc8845b4261d2407c6106fa47453d5c5f0f234584d391661586d6a32eeab54862a6c268752e8b81f44543e43017e7f163c8916cf8240ad5a8ada5f08bd435675e235224c90953e64c7a82f3467797d4a168b4a2de3f8f637d3f86f8b38b77efad5381c6a8b1c1c77c244b1b496c63eebbbf145bc90f4f86cce5fc8da5c3ff65aedd2f187223d43b0114a648c878c1bf08308ece8f696117dfcb14de4463465d52aa9000628a8efb590aa8936d7eaa44d58f51b585774d38a6ee1bd6e5c068635bdbf4d185bdd440f1e08f960e77d130b1ce6624b1b66ea7159fa79912c80cc931c623cc8d2b724ac68caf075facda6eda848ae684cda6567ac42b085124881f92d2ed11a89abf2e7973934e40777c877ee82dfa719d02526cdfc4685eb88e9043d8fdede75a226195edc27aaf379180ec7ac12d9d75428d5a70a8a5c7329eb6027f744b039e6bad55e980f9cad2dc76b2e2a0bd3846c6c0750410aae226ce0ff22769e1d4ea928fc2f90b1a0e459ebf446c95c8a26c98279c19fdd50e8c92435c758ded934dc378b1e6c5db26cd666cb5444ba07a2502aa514559ea65dc3f0e3111c15001bbf2e363caa358ca6cf33e9bf3f15774c4332da04aa4735e99cf9284393686881436b334e0ae09720f88cf1c4833e9d3ecfee81febb15c6f327ddef864128ecef05a064f91f66caa336cfd30a6aec5c55e5e637159ac02d95dd64ad344c1c7d581a4c6972d060b25745ee65babc350f0b21e0f126c6f61b881700d669739e51388ba194fe1293c534e7bdc902f72407336e77357b4d58fa974eba5d232e4c9e48777026108620826486104d58a0ddc2378fd7356bf28ff38106c79657c30473f754972324d5e663f0567cf4e0b5033590c9808c84385964978169f129482138931360fe46d62adb8b54e94eb08b89cfa73f4f0a123753e048fa9ac26220a39c45eade882232d5e56b165bf4af7738642fea88d5fe9a39dc846515246e85b1870eb1f28000c100e600129e970b9a045dcd31c0944ab40a7d3277e8a76160854af1e839d860d9d60b61bb2584cd39c4a0632c5c590731004e4d4ace91d29ae7dc3d959e1411d2057c6dc8facd52b30c85ec3e35d95afadb043418dbaa680c0260f35a186a102c76152b2dc4776d99bbcd984c08b773c91976438fc383db4a64dbfc982d8e858b16986fcc1fa7c5fd1170ff01c65a12f3d92accb14d93f71e22932e1d61300040611af60de2126c765ee132f92865d15c68318762fc1d0a4ad0429602ceb4bc1859e31ed3f7cbd5ceaaf1c3da24f190653d1dc486026b5dd1d946cc85eb81928083eee8481bb66bf144043b161338f6ed10664bb56bd9e9fc03ee2710f7c505b7191b88f8560bfef221f53c39f9379d8071495d6d238046dca083f5bdf50d76a2bc784370f30e6d0b25a5783b15880ba49b610c61e1cea994baf31e58dd71c6dcffb3f7576f87b07e5017a833caf383507edd48a0ee75da01b0fa22628697d34ded6a03aa28a55f55399914cb42dc5aac4cf5e3326196a021acf6775d5507d1f92154fd9a1bceb1d8bc7fbc2366973f775fa729c9173bbdff7c8d14a082a40dcea71738c161dc7612fe6b0469662981caf9883c702cc8693512d52e5df18fcbee04d05887db0269e4e3818ae53c5281482ef2da5a13d560b798e48052b1a34e4f223a6002c06d5d6b43a8a03c17ef2192711aabae0eef76f658485fdac14124ca500d0c60b13bcc3ddc12b529d1602c25dd358ecce9232ac6b1f5c5405004110ec8412067b37ebf3b9b9695c727164789a413c80f7b09d9f8d07557506368fdfa433323e4de8c569130471ed5aaae819279740102c39342977752fc33455af01a76ec0dd3de4a0871156a95aa08e9c1aa0925331958426e4de214b7e52ca105adcb342b970f4f051b10b46fe85d16b04ef0020c7cb714202570fbeb913b478f7dd1b30a5bcc8129eb44f12b3a9d93a00449ec7e345c7b304cc2dfae2ffa91421695da4b55a78b020366530a1fdc654b70bf22b2f078f9ca14ab7fcb4fb18e3bd208dbf87c5fca81bbc1c53366be8228e14682fb53e09d49d17d5cbd0b026d42167b8283e1e2f64f2ae7a2c4a06ab50661b4a05014a682a6860e5bc85abb1a0f7d4e7cb05efc6b25545fb640b49cd5610b0a448b44a7239ba723234b1cf0cdd1d4a5cc90f33ed4c290942565e01e87b78945d42b08681269e65316a66fc9bf95a6d9b4651b584c86eb3f2017167fe21e3aa56e542149088c01be219958e0aeab39936557f42ff6b37063f0e069f2ca1366aaf10c15c34bf807cc53b09487c2a26d41b1c706895bcd0a108684622e8afd0e9e7a1e8a9590c0eecdaa152a944bf8555888f505abb6095982a4cf54f94346fe4609b8657d705e168ea71df4ad348d038da04941ea11823f286450880da7cfe2975aef13db45ec65892f137459dc47e3af57aa39909808d1816c0a034f8bbfbc74ec36a60141333f8ac2dd044783d86f910409c4e6f53a1ce39d0fec4c47a7537a0b3c5453479589cacc5ef7c1202f01294471307f83047649872d4560bf8dcccacf206e39e9c0c39501f3a09b3284efc9b91cc6e21a0269c8fbcda36d5b640fd4587839076d0c87cb19f8b11bc14b1aaf6de407c14ffe9ed43c6386f8c571c877fc1dabc4ed2f69fdc0137779ea93c70ef7b4c9f778689a36bec6de3800bf6e1ffe9a82cf70a392ef28e6f49825dea02646f886031e0161a392acdb231fd5b634c1bcb37159a434cdc6d73a8a9e045ab746037d7bb350c15100cff802cc7a99cb3b6233416c7005169969207f578e4c8e832304278a8c58635828af5de6bc22a2bbe19f048c6bbea0c68741ae9f6e0ca4028419a2fff6338102192f69df02e9ee9727e54c3db8fbebc45500d136697a48a596a829c0d1db0d8e83b3244bf01758dc082d69de8f16f40d36a26479698acce0a8b2a09284067a40447706c3152a2153c01c3ee38b63ddf9c7cde007a19a95c4727db0e9015a08b264f9e22a3c5b3d92a05185303207f034c3ae43dc2f4fa0f00d4174eb2de51c1d4b72f7c0bdde7e6b817b7bbb0901f3a4f1ebc0708c09a68a81e74135c336000a00ef41dac857947accd1ceb25144addd5d25c68585cf28199842caccfbd0cf16b3438f6fe2268b9626d00f8f33f61d30777c5b23e772a4afdb54ee923a192d626ec6bb44168efd609f7e8e538307ca0c54b0ed3547e05b1fd72a37829cff49b3518441ba9f690640ee2af846cd07c705797220619f3fefcd58dd9ac3bb9ef6e79f14e639403d4beb48cd596bf9fe3a9fdb571695d2d76af619e28b2f07d8bd59a218ff8300cab447fdb83d48b092a95803598a3ab3765dbf02a306d775ccdc185fddbd994b015e8bdd69821cc86dad344a2082a307c1a1ec07caa1b9c543829ca501b9e18ce818d940d4e132d0092d4aa19a00c441f8c3270eb607125199ccab15d0258b0f78cc68dd14922bd297f07ac4f66bce0629502db1f2fdc77d144e18f70169a8441be6e3cf4bee1f34a4b0a84da5a8b3d5df2e11876f248068211d350faf991a7cb6ea55d7080164980e782a5fa5e3aa540509e6255a674ffd434480dd572d7a595932454262221cb6ad66e10343c3214dcede051410a3bf5c5e9feb05369e3459dc0c240334faeedc625297a9f1c3e72478dc548a8227881f9ae3913415497746f64958e3597ae2c4c69c0728ac9220501da99a9ea93b383dcdb5856f98092b07044b1c33c35974229b2d6efc9054501f3c1875683e5694df301da6d093766f5a0955586801c3fb783d69e604584f06231ab067e9e43e843df22dd1166649e1b482a35700cc70ce433befd522fbdd37b270de386376e867d646ef9f93541bd55c177f5513dcef023bf87623e67cb8a13e6e4011b4fd2d46c1e9367951007a9eefcb1b1f714b0d60c1cd032053cbb5c7844b0e7341c8ff8953fdbcea625b0f3c8bc33cf23d9a565a328b1c89549c0e01cb177e95445cb2f0739615033de43432e122d565c365f5bc6d827a524415acd63c88ae9ab96ed1e6d87cc0a87066851763b0a0a64c474566a75780bef4ea1b02419cfbb0871be1ebdd85c4cc8d3779756ec888e5c70b590db8bbc2655abeac642a91346b5d01ebd941a14c29a922d4985022ade5a465eea0929a74e1b9c3952181920e59588e21d9fa26b7afe39dab0e8e16a8c4d7e03e28b5afa63bc5f8b8da964256d54c17318c546ca4d49c7ee5dcee8a803dd8ed5ec1a897ed1e1b1d3e81eb92d12095944ff8f937a91295f941daaea84e68ccbab3c41c0dcd2e92dd0a527dd86105def3cc426a0b68b3ebb08df68eae13d21da512b019df63ce573c61353ea029860358bb8b477069b35a0acf66bd7900ca65b4a6ade12c83cad700fe9b09e14ce7aef49fd4b25dc6ea1a31cf4df72c9443e9c6297ae40adb1cfbae2b39eede0248691161ae141ff84b44a874cf358906cf440069667d16e4bdf90f1837fc57cad32e03eff4fec2a5bc7d638597015df8cd9839014976f4bda5512732607b077546b57ab243762323397d47a16f80f8ebf029b54355e7b506d39b46e69c5c4375f21a5c23a866f35d890c51200ced73a51756fc5be881337ef401f973748e3ee5b802a4a2b3ea0203ef6cb8728ac3646a8b43aed98020ee92c0998e65895f07500a0d2e2b54a6605fe1cab1a8f5a70124d92959b04e0f1c80405181a593e3db51de6d262cfd1003c2ae9c8b08b1a3034b064262f31bb35e15e97b2883e935daca1ae04c83bc44a9765d86d46e2bc3196c1256da9cc7f83226e3dd1a1b95bdf7c0bd3326c726c8f0ff1dd94a465e4659fbcce65ae649a08eda0d48b2d10f77087c13a309b7a257e59d000fcc4e715e9a448bb4d8a42629c7a642e557bc90b271044dac22c6a2b4a14a412756a5f2fed2856831f3214b997b45a807cb6dd085b80d55471b27524832a693bc01aa85e3aa47b19a4804e06800d94d199bcd5c7466222a92e2de3a92c7ec2d11b9931092a629f1ce5c28d5ac1725983ad812082d3cc4671b78f1d057c34322dcd838dd7edf921d478e6f6b72dedb3c416077f04a4f3a02d05921d2750d993880c845a6dab14304bf43bc5c0348fb775ae3045b7cbb70ca6460e0fd5eecbae7f2232d5dd7e79d1e45e4498076f3f36ad1c349f9a54a2e8a899349e7f867e60a5e0f458e012a9474fa6542d7389355b3afb66a34753b206b30bfa7c63366046c623ae00f6b66ca81a3977768f1fb425216435e7def1b4638494a9a0344b7948d0deacd055cd0cb1cbb26cdf79323e1ef4d58f089fd3dd0526c10f0f80fe19695d4f5c4015d3225ed9b22720ad062ef7044534a91e8cf55f8ecdd8af25d2678c88345cd9b49373522db8c26b9040db829bdcdfd67892a12af18f52f844bfbaa258884c9e108e34e71a13681b19904fcc52f3f9123b4c766f20ceb77338352d54b8d038553b8306a02eeb8aa3e47eab8a42127d6ad3ce2ffccb841b43db956a8e247d5bb6465cb2d8da71cbddd66d01dd4ad9ebef4dbbbc2afd5c320115afe7c8e1ecef978b672aacbfb5f41418071cf414989452c704cc262e9dc30132530b8d1f9d724f4a7542e0c7ab84a336c0886f820d85b86766e68bf8d3c9396c31a56a5e98dba0010a20c90c5133c3cca92eab080141b6342032656a91a079a3ed6ea8206ef27958116cc33ca276fe225f02009489c715c0f5df3f495b716338b0683629617b89dc1ebbfde41d19a3beaadb7728b69cd77de8ba2f44acac0826692911624648214404b6dc06840ff044a307c75de4c2ad6a7786e882d827c06c958a18a56c4d7f1d973e89bb25e46fea3a5b131c7934418ffd1c372503938045e27713f47bd4bf990f8590f59c1b5c387b3630c78705e60447c97de09d615c9e83e16ae77ac53d8cea36ace7db40b881e98c8d3a6df6441602cc693ebb27bb6f788a58b76ae42ae5a3e29d0ed409d4e477681074727ec5bba666222002b72bec7c3bda6fe73d59a92d3cff3706a26e61030d64bd3af7501420301a3cec2e7a5dfc9e3280f209d447386a4b65c603971d0f34fb4d2e71935b83cf82f7db9a48302e1ae7098f57948e90d018645e1bb1300df90aaf2c845624e9164f2fa7b26bbe8d5c7395f629650539572dd083cc776f94aea3dd7c590a88af53c289383c80ce7aa92d16d15ae860de8d2fe87e48260727a0846b97e143c6e9a50aa864934d090d2fc0a0f09ce68fba8b3d45e36155b12f9dea92161f1180afa9d6bd1f51346bb009172e58c10bf57a8a64caa5304a2c0aa6a58f184185a81afd53e6a56dbc585f422def85ce8b9b96fb52bc1514b21200ee70e96ee6c4a0d0cd32bd858c8f694549e76ed57e3baebb9805eebf70160ec8812820a6f1498be3d3839effcb380b840759ece77264c7e8fd6dca7541659a5c017bb9e77d00ac375efc8895af4030d325218d40c52a7211481b61aecd36e6ae6416a32f174cbdd27a844305577cd95869e8e0d4538967e259e355b0e6da27689763128f14e690e48da6861b59ade51d8ed46201f944a2be550587d455393f7345eec3ce624ed970c197bbe9469c864b4972667842b89679072a896ae03d9310b17050817b211b36f841c5cc08f129b7a847f12aa55aa78da3c4b55e9c01bf81b8b1fab4788ea46f7cd23176c90a576919d055ea5bb4e20bd0ea00f727f6a044cfe92c366ac4ef037d931fc67abbe570a0ab8f861b050d9021b86842bd8d122c9278f8a0359015b2afdf8dffba66e1996347dc3825ecf6cc24e74688ae44f75fbad06060c26d34820882b8de4dd9c764a08f4029ad14d20ea5500e0b1126c3db9811157f06c52265ece1a066047744142c8ad1c73a185e4d51c7b7bdfcec8aed96f938d07a5b813ebd694a46c8de32d096a458e8873d1bc177069f0eb6d84b1bcfe6f4e90f7296c4b200ebb6c41b7d836b25807006aee4a3bb4d6c616bc10d724556ac413ced8fbce49d7a45d5220e3a181b1839c4785c99eefbd165881952c2d0d62b3281c2805e858e6320b7105ce0ec9defb31780b2ba43c240fab868ad2120697006754e407d3e0d6b76b0a9a0ae176f5521c30e8c151ba7d73ed31579c480395d7cad2dc4dec534124384cd6bce02069ddbd73e6e68ee6849440c4db29213ebb1c37dc4048ab1a0e85072842be06ccdaa411b07ee68905ce7420dedaa97b9d4deaec425243899ffdaccdc16d7953461d498039c42bbb7feac88f984ba391148a3addce08141020d8f82522a03f8b08c035ea1c36d1e60b79af9869bec180a2a536a98ae86b252b45eccc829ac0860013d58e9183569468f57913ef3415809e12868bdcff4adc8ec02fc524062f47e1805768a61cd93a52cf22b7796b8696a990d474c4708848a2ece07da5a5c62b32390b93c078f249c24a232e55025718f3832f20cc0349b31476479e7862e943600c8f872bbbd2172ada8d64ec4014b9ac8eb47c1a0437827b98d124faa549feb2a9ca020a6e3c1ee34680629c52e063b849abc20c22fd19a49455ba51524a35d0e399e93234cdc74f6b0f1915db745e327b392fb7d548c53454ef9858400d1575d7d1634f1043f77032dec99fb48787758096e17b7843028d1b945c6fbf0c002cb68abd5acf4dacc7c319247f1ba3dc443b86825ef168e9cdbe8875cbb3589d9e564d18b78ef6c06256d512a829dda7d217d6ca2c817ecae6f69b29fd403ef17a478dd511c9dcb91d08f42431c8db31b9b7ff273dff6099165403787e3ea46f7f27bba07006c2ed582f8732aacde030e8375e5e034d79874b61acc97bc573617434af8bc252af07f161866b91882b6e8a04252e6cefd1858242e62bcd598dcae0b8ab3982dfe1432bf506cb544979b4924dc049b4eea7d1226d76850925e6d6c7752a53aa7c4ef65d0ee9aec1a7afb38acbc9c8f51d453e504ca7734f70d3633f222bbaaedf0476cef43e562864852e6f885cb4e2bbcb24c334cec13c4d2e13b7dcdfca00835d1c3680fb1f2ee689df6c89894051bda614a55d872fb9749e231053d608098467375141a3e60426098c7a8df994b5ae88a8a7dc99df9abdec059f18d6c151849719a0adc07c3119d199ba51223106e681efdd4c48884987d63fb7d9024da8f2fc2166a960aaaed7536db49b78982ebf0de63f05016696e5ec9c703cdbe06658395172063c3c7df3956aee5dd466935714f4457b8c10749c6651b45331787a1c6ceb3729c7536083d447258770c4f2a1c271bed82e4599747f5c81debcb8ca8880903a22ce282c5650f297841465603f853ea73e91a8167b005d1d4c6b4eb71538aea30d1190e41335ba858dad315c678a3cab54c3247fb84b7fc40c6a85bffa684c02ec60a7176c03eaf12df7478b2ea7a8d0adc6355950f15cacbbba5f359f43eaea0899270ee589fcca3efa0eb324fdf0ce95f0da8dcd06769463735650463c143698ceab9732d70fb85f6751b9c586d6e5861460fb7da43b0f47ccd3386df07f473410a5319dcc52f5f1817db43cfa00901514b492c287d311ce10180f53dce6cd9b677070b51c817f539cc8aefdd800f01dc6b4d7db3e4f4fa0d9f848b44e73b16c0dd2bac24732281d4fd7ea99a0a1e331461a827a05eb602b6b96e617ce8011afd3bb3dc942b1888b1b1e79fccbad4ed7714b730b03076ee3ad295fb6c7390cfc4d7041681545e03104141a219deb04ceb65c583f891db56776063521cb33f5d2f89ddc36a4a3367d5602e5d8b150de0f4839e8ec1dd7afd27a2162acf8e40eb89bdbdf523cf0ff3a00c9e5edf39125bb466121ca67ec905593852ae6b79fcb02fa20fe44ecd9f8eb010be8c1157b06e07eb9dc736590c2fccec0af8afe8f7c6bc6d818ed002843b8145a246047732b04415a318906800083c452e9ef2975fc8c3b38f7d79e19d063b7fbeda2dd129bf857d5f3136f1f943bfd3ac95baf4f24a17a451fa711ac94a568cccf8c247fdc964af19c4023f7c8a498dd84aa51d4b9e5e3e19e328d6e6ec5a407265966eb1b3223bfcac91e32f101da601907d9b34191317883b3188e543bf0239ba57cf9cb609b71768de95f2085398b7d8f628e07e68c8772c1f78a6400d30a2303982f51c0c6d7927a461d3192882256d75faeb4f3ad6cb2ba6a7d87b4d5145ab532836543ee0463e41a3a7929a5c80d37a03662f5dedc1509f6bf0a6f440beaec1bbea0f62df8ddec8d07df4df8e651af723b8a15c9bca8a1fb31c6a93c3767cfd751c6c9d67e1911772068ecc980745a71b2acf0c0fef0613327576bcb7cb7cd61a33cc9c0f6f984136363fd8d67c681cf80da6823d0761c6f2353b73ccc6287be7017d982bdb6d0e176c69338bb111cdd920ef91dda30eea172f44836898f9f214ed5269382b3e86141cbf45096d830ae31efffbe5ba48318bddd8556e89613cf91bf9362a3177583217bc19fa1c8147db4a5d91d086c803f033aacea4ed0ea4ffa7f4927b32b0578228284f24b1709e2e0ce009582b71d8324ef671f6d7d27bcdcd7c325ddc61f4fea0d19eeeb67283c7cfea86f9d51fa04030e04c2f399b36d3ec4ae7df48c1a979a6734fcdb11719d3f304bb7cc0549d377bac76e01fb254c6b5b864d3a95066fb4faa8c064b26766b4a0ca3ef44eec47b4d29a169e5c8fe8f69ea7398d4db00a945bd61da929387244ffe1a62ba28fbcd9ef4b507eb0493c1e9e5693e0578cf0bba287776cdc64b8934bf7b01710801ebb4bdce811fcca629375bb9034536a06008a4e5f2bbd861b33a75185239aadf71bf9a3c29676c08d844a6b57271724fb3e4e0a649e2493028264907b10db7bd5fc1684b9bc2542bfe5ba20cdd541bff21663e40fb4820c6053b2734a818c09052e7b0b7b2f37f2eaf5e5f82d8744de3a7c3a7125a526ade45b16b4f2dafe026e11f1cdc4de1275a905319683d71248cea2b8a61c4925cf7700ed0bb93f6d33fac4958d7606d4b1dd8a0142f3de46532eaaac2914177640863d436865177ead0401882680ee5b5c125ab55a080deb91f139164b4cca3230f3870efaa82dac42d546de35239402317b066a01eecdddd2ab475b484e2e70a8d2cc7aeef4915fad82a4a8b4c936989b200bb6cc2142bf6783fad5a78b86072c2cccbf395400fc782ce255489cb7244c868f8b6370278a8d70d14e2de3ed341ec2ed26cb866a486d9e69c0e9c90c56d23a7b0f4b349b6e1b3048ca0546c6988c721c244a8dd042165e47c2a253c3fec10fe2844ce1802028b4bcd95dd8610e65d0dad9c436d332a6318c317e82586565a397f0ba4b789ac11edb475862bbf5bb6917c9c96b04164d3d6292c330837a834a6e2f7b57d8771858475b21f775f023bfd536a26463a61a87498d20e50e3eed2a0067797508a4f54549a87f3be837f0a1057df718c62a6ca61561e39dabca8d7c3a5d792c7834b4a212366fd7d5be5ea3c3aa22190534f0250d713aa3fed7050155df8e8aac6beeb9b467e6508bdd6d86a58972f7ca6c84f10fd7c768f988de46d71b651a3a2269df917d81490df14a3da2c85f38d29d6f090c9576ec5185bb13b9054befed9fe75ec15dc1ca92b7b6fde2f518e82dc15684e12882beb8e21ae586a0d580089b2767166696879cfb41ea6ee5398df1b85fb1e019657c8c96279488d3eff0246e928fa51d41140b4e5671ffa5b717e094ccb9493077e8436980ef410a963433a89a519556477fe33f044c62588e8b35ed4b551ffc2cd395883665015cac01f214618f4f222e88dd58e6a6621f9bf8b4a83b69cd769ba7578005fd04295212c4699a41ca3320216ef2fe42df4d9a2f3e0f5db8f38d3b6ee6ae0d41ee8189b0c1f77bb3f1a470a629909c90b3e4aacb50bc364f1d5a5931286bf29e5dacf442e428cf6548acc0260c2b08eef9ef9bcdadc2937b1868f113be22e5ba03bd590a03c6fb39339a6bc3083002a59b49744442d922abbe90beffb304d2f067039c09819f412c860638401696fb5304179e6b0862ad49446ce8e3eb152c3a0ec5fa527a4d958d0828ebd6b07f7eed2ea258eba43e0caa3b65a8300e34a1c5b262c8cf9912f957267cd5cb4a8533db128ed4ae4b8079d9e5811640517718f900df7dab14232fac0cfc0e13409664fdb5632d6f1fc0920024dfb00d1100531a72eaecc558287fedc8b7ebe7b5814c3160232974a4b0b693ac2e50f97d961d603b80d21c105a99dbe8787800274b58ac13ed633746820d7e71f0012926e118fcdc00f38f3e3de40b2cc745295a329813c0570d750cd38a8bfbc948abb4ae600ee29fc45780fa98f65bf22f68a4b71883d92a31cf7d7daca5e87fd90db8eab790cdaf5656cfb7b3a2986e9bf70f5c7dc5baa382b6ff916bb096e38594bd5ad36e98cd375baa3282cb3a0b92fd1fb28cb7a7b8363c88dc5ab28549a1d4a5be0d266dd2ce572268c15af13124dabb5ea606773be0188a9f2e5618fdbc151177ce6683d0d1b721f183140832bffffad1885f690aa20e46ece3466cb3f320bee8dc4039015e40a7cbae7dc6319f7974efb604d2bb2e050c346e55743d0aaf074141507a74df037d8e9d3d4542f401669c15ae5fac77a8bb96ea0b17852e11aaff88c1de5cc4d8e4cd48ef350069183f189b7b277232c881a7f80f39745d9da32857cf50df53fd736b86aa76a12e4c85a591a41ea4896ba4441df56c715d910c0e80d4e2a1bbba16dd53788eea52bc31a9ce086f1b300b0a8d119370437151530b2e6d6b3318d9f17834b38013165258b98b707b8a94999701adac3a27280a9eab39d3ddbb271a43825013e209c68c59aa5eb3b83ee510b2e9667ea86bfff5ac36a7bc1ad57a1eb37d783377c0496fbe63c4c64243b9cbcd48890b308b2ad60ce7ae29a79b79d60674eb583a19d291c6ef09e3e2de24d585aae84421ccd46a91c7e30ee02a900941f5b3aeb421019ebf1da8c5078c0d80c0d80f8a708408ba062b0d5a465635b4b961ca9eabdad8c58b51c080ad8b5f0c27b4bb7fe85319565bb6c6e5bbd6b2aec39e610dc035c208e129b0d35425dea1e6d61ce9d52ad66266e951fdcadf876f38f1830c67a1ea60665d307469d87ed90876772787b894208730d8b056369dd65d948d29d7a28a61f6abb35ca556c2ca845532660a134cbc825107c732b72162d309f029abe5938d40411ef9ac45aa68b1590691ccc0136909830dd4140091c927fd72534cce04f448c1b5cf216fd4188344832dee3b5fba0520eb22ec4380f018055733ab1f71e294c9b49764cee7d637d45173060491484543b23a831f0ded02f52cd10e1aa38e97749166d6b0b522657678d88d16b6617881663d8d1dc5d48f79c4864811da3396505991f10fd480a44cad4d1c1f1b4c817954af87262cdfbc0046f1d4539f05c3ad1ae9b6b9672eec20393e4b6a82895f7d8ac4f61e0f6ad04e438861afe115b0029c50e0b31282bcb455972686635972b37979d946dbc9fa4e6a5436f78ee64f6de93589fc703dfe90a06b72c0cd5eab9b20c43bda164706db613482ddb64a5607f93a128921d54873dd167f4a876c29c308ffe9879714fc7a913adf082c253be2d538648062c8d16eb97ab09e475c8ebfece837f251c35bb493a5fdb3a157869e0ec0e6f09f301f15fa4c8d29ac71177df9f91da6289c40f4aad75b5f6e40ed41d9de80ba3a1c85554ec7c6fe494b361885938d95e17e7ba30e28ff8625f9c5ddf882be7e282669250ecdd9fa7c9e72e7fd419510c895b12968d60507e130daec06a3423c277249af5f80429c8ac2bdaba2e2956e99c59115c6372bfc1fae5c0f6e1896ac0cf4b1b373a0788b680421418341d76986761e221083e2fec2dc10e9cce25f215837940578ed00dbeda0dd09823cb9d76ba6d2d24025caffb880eb06693f3c48f7d6ee978c701dd24d1601c183d58962fc588bea7f00b57e828ea4a74f4a90a88b23d2f9c0d6cd0f04111c0c6c316fc698db72a6ef005e2182945c0bfe3414427e03960bd985222f39dd44a8cb3bb31a87e5031c5c4828f5134b4274fcde13ec128e1458f2038927b322948dbac38d76f84d5d77584a150c10388722a78fc7f90e04f1dc4693664ee1f46b403a7c988d1980a6960ee939613a0f1be330435336c9ff093669a2353d69c0b2c8b2fca074f804b0f6dacc8bc403b750ff17b524183fe534d1d6c2aad06a9f5d8776b8b2f1fbfa65d3f491d6246078c85fb74076e72cd4db78ca9f1958bc8074a1e6d6f146f3ae9f88652bd77aadd7af11aedbc7028480927aaa8a237c4dd76fd6501536605927b945ecc4ffb5554ecf167d5a8f58724c8f98c5da6fe6985e3a6e7cb449e99075cfbd6f9c8f2cae047ce3b20efdb3744ae93d891c0be7168365b7ef4fec4bfd8c26654ae7c79b370e9edfe19383c4bc5ed4a41c9ae3a9f92de6f895eabf217cc5eae830bc0c67d606d8fcb5268b8b3b81b4a025d2516e55b52c6f30db25432a2eea21a8729c055375e7537d47f069dd81393719f256b80cda8405a325974bf803c3e5efc522fac037ab6d5c32399006772a40ccece3954a0add0feb620665336d5d5358a979987445b81dfdb3f2c82b3903165d0bb7a2b54f99dd445618be3e5c04cd98527483f930f92a70299afb94c10d66c2d015e1e66807294637cf0e83ac84abd15a53c86e3e03265d1d2e47439932dd645618be32dc048d3dc5ec467261f3ba702bca7de119577e3e0cb2265c46639a323b5fa01c0e90af818b8a3e173dcd30f50ff2d15ce406a69ea72c7c2ea5448b9ab9ee0c2d79ef02d2504d2095ba94080baf4946bfaeab2525aada13fd4a589584a5eb162e5eca02d53bf1d67773893640636491bce42c10a540c59fbe109a5767b9158897b240ad879c05305f09d9868b82aa36d3e953de496cd3c2a2658fab4c7a3818189e0a6c968e96d1e91f4154a645a2d3881dfb9e13012d5220c56a3158253c1dc175e77d0d5ce5b5d959606fb291d09295448b1ec48779bf0de38275f31577c624dffaec620387ac8f3f18d5b967d45becce68a010a223a3d0994cdeb240543de1c856b81753f91e61b78c08b35f854518ce7a40bce7c683362e6165d0851da4cda8ab97fec566f7853f883f683e05bf1825b657863205e2f41bb92fecf57907f37c1cc2387d692a5d1f8e8f33f90bf0eb243b2ab96713b9bcc0da6e22135c8b53e1ae2d2780a3dfab836362476df5d263dd062bdff0780216985a464c8b49127925bae0c9bbc485473944f23ffd915111914df5498b6c679c40ea96ccb7286d78da14a6d1a92d6f233d975e3d90be0d8ea04666ce746819d5866807543ef3adefa80b8379bfd353132914b89cfa73ad0e244e7b3438312d32f8bf926ecd27ba299efc97bd5baca0e0d7f0d387c044277aba218a02eef1791ee1faa3651dbd2648ef0319bbe0857277a09c5870bfcb9c3c75c932358ac02e1a576d40f7829a6dd7dc228e36c1c0967726455465c2028752fffe9515ec33822e4cd6ba99de0ccb0868e432a6eb2f4ac11b59dc3b21933c6ee72d3ac19c2721652595d1cfaea995c1db53211aaf491bbe0e049c15175103e4608f24403bc404336edcee67e61e4be8ff76a5031f379e72c64e02358f5d3c8387639815c7a5d0a5eba6d8c3445068dc195a36d05cf02c282822275e31b4b58ec981ec01e373d1a7aa8741b5e1a383b1e50c8a0990d30ad6a07f98e80d80db060931c97f327b7c9cc3d89e2db529c0244bf4940b869716b9d2ecfa4aab88862c7b0784f86a411ea6a01b381aa6fab28d9b24510224191c91980fff9f0a8a8f55d772100cdd8118f0d8ad58ac6edcb6fc4b6055f87a87f0994d47301595e1962074e18e4fd788334df417e9284c5c75fb6c395d9f465aae6e525272e2c0fb2f2d6dc5aab352bc89a2620061195561d7b018bc4748600a5dc268eb5673555240427d2ea4f7a4b656521d2785065aa7adcbe55bfe65c5f7a763a0dd65f1d81421c8ad571ee4c07a9d5c128b60aeef69a6c21f25f33dea1bc0c3bfa59cb33e8bdbe33db1173caee0e4f7a38ff9ad70759e30367eb4859ccc590e264e70adead1cddfe1a868fe053c5e30e8fb5201d8d093fd00b29eac1f2e43ab1401e1b1c47ac80c1699254f3adad9bf7aad6c7f7e10056fc0731c75a90fc30efa0e69276707b5690e5c138eb1ff42dc8d444533768c22ef03125054e53437513e46fcec70443dbea7809da404339474f6e7e3740c6ad8a98cf9fc6447543df771e2bd98a684abf93a7dfe91635ca8c430d313b9eaeddd234c407d77a806b3c562a10d83c51d8638b35acef24e91944894716ece6302859b1153e23483c2899b137ddee4e48d010986987914ab8b45988299c9684e96281445afc93687ec06958af154bcb3e1788b91005cb2e0f850a12e7eb704ba4476a1a04abc82a00f685098456f168fcf0f7b3281c7ee1d8134d3f198575aa83d3c122ea641c9b917440c384801636046d9379fe07f4ce9fe23cc80e21465b3950a39ffa19e7cbbbf81e8a597e0b909ffa18ed87327eb6d790023c403649a1ea0e4242c3930b7bf4923c4840a72f7aa69f23ebe3c0a928bf949b1208d4cde3c9e9360d31aa937118aafa5cfd18c7d23ee19e2422274fae23eb3ca388a57422bec448b810a48d730024716ca221554f3262870135984a1d7dd2ae35bb8e868c2860fca38fc917b342938951398a41ad9d9d0518166db87c789c84ad46923b49f621db24e02666add6da0216c17961bd10cb817873a78a8dc3a673f18f3d28a60219ffdd44c994cf24083d0c6261b535cd3244fa6a322505e24e05695c535b7f1d2d2ae7a518d930a6772858f9f54096bce3d91b65008e87ec05bf9e3587aace5fd441786c4cf03ebdd0122f8f95cf38a5eed00be2e193319f52f0bddc9c464dd0e391fbb8e65189b371969b9c0cf69f5a442477d0377e10392c002bd4116ce81a557492bd4475527e51aefd58c246713536601a27dc3020c7465433b33e19c32ad54fbc939d7286f0d67f665649f8f0f6827ea2ec74b9e725bcd57b166fd623793c87cda8c40f6b77c9eea008da8080447b163c4071a1ed1ac5f6a18bc0d20b2c5d237216d080c65a439342005566365837a56e1dea4cc27c5da22e49acba20ae184647ee53d0dfe4976a0ac80dd81acb552fc2b566badf49e98add5ea7bc65fcf993f7704db9e5e311f20915966c34952a33cc5497f2bc2f46f70c899624bf365d1ed03858d5c94ccdc45b33ae9faf75bd707926237809581322b7ddf2ab122ef67106ec9a7f4b31128cdb3662dbc7dd44b52da30f62657a04aac136f93829fe699645aaecdc40d30af06670d7d887b862be268ca022f3df01741501deef93df3c6f5e4f72ee22efaa0c110fe550fbacc9eb4ca9eff6c52048abeb7bc5c3054324a3a8a9c6a866752ee2b87d2e5a56f9aa09996d60fe472df3bf78a981c104106d055bab645e32a7c6990030339c3868cac0f117f8f0f99c2bb01f04f02828fc5bae5a781ad6c7204cb28ff6fadb5dd1a2184904dc8de5bee1d6e0b400c5d0bd8bbaeba66cf60990f0784cd1c1ee306ce2870377054c1166a0fecaf5e9c2b7d7bd787c3f41bb881e7ed89ef9905849738da55354e8591c86614deea9b0b3b56eba5ccbe19d19140305c730df892d8bb2bc8573779cc8b4211852ae59b68871d056291880079ed36d16ccf70b1b61c022cba30557cb61ba80115269c68c1028ad1ceea5f3b60085baa484194273ec8c14eab5a2a2fdebcb28145b826277fb86b60b52b3490c0b0a8075b312c4223c9d534896dcb1bee0c348156d89a0a9bb92dde02365bdc378d67164ec02b709f83f0c8082b539c24f922e405a2b21c810b3661587404237833c518638c32ceda4cacfc9c39362a9967c1f32d9a2f5d302c3a43a80342a08da6c1195e80e69569c50913224112496a219a92044977e9a4a32b509c0811895b225211919224427e78eca8a1e9a32b509c4ca149640b52119154229378e982e5280a9436d2425cb654298a4bfa533244480f1ecd84ad6122767fe2beb9061c2fc7a59d58f8ad5bc2396789855b4a596235ee3b8390a0f8648bdfb4b7d7c2d86bc5b2c455cec43c17d08a28c4e78831ecdc9b09cb5d3484b08d8d512716fb16b76e296789c543b17689b59ba7c4f95a71013956b172cf8d730bc76fd7056863477ff15f7c8eb8000851c81e57b8de74f3aa7637666f256cbfb51b24e20caf5cc13e2d4c7f79ac162ca6b798de3a2f4ca25736d8caa663595de97c03c3db348e4ecfe4586badc531df2f61c67833be60f9d32b1737c7681ccd48cff46b173bbfac8868468c6852703f5324185e23a231313131313131313131e9191313274553b044c9e2040a57b3608947fd458d48c39ed81c8f302e1a297df94a5f5efa622252fad2334d4b5f4c41a624a6a168c52414642232bd139eb0f3c62bfd59e92fbe67d11136d32b589aaef4d7c4fef44da65784f44d365df1d13779fa54c1f1fd1fa62b7d73852621997269096262051b63484a96fa6549159842e37ec605a6d0f44a7ffddaa53f9ffe70e8959ee963a9b139ab22dae5c8a7bf964c0fb4d1cfd80f0a17a0011fd0465fa75b893ebd82a321c4821bcea8e015dca71d0bd71205431874d105677a450b11bcc11cad8b74e322952af91396b0794ed930d212341d9d94e892707ffb76f20a4769a39cb2af29a92ceab70a37bdf993932ac113633895502438d7aa65858ae59545b2ca1236c72db8bb6ea9fd757f716b4e3ebf060e6c0ce1270f5f2584284829bba40b88213382906c8655f03c8723870baa9eb7c9b9d137d94d6972cf3478721c9e1c57a27f7ff19dd7f23e2df3f20a03a960673178248b30c29011829292bc206d2608af5871c28448109216a229499074493aba02c58910912d4845444a9208f9e1b1a386660c8a24aa7074058a932934896c412a22924a64122f5db01c4581d2465a88cb962a457149543244480f1ed0063cac8136a08d6f474bb1b25bc2f3cc609afee0c41047204a3b76586cdb120ba7922f5eb2609112450e49236370b15285492fe9a02f5ab24c911287927c72b4b65e62e39d1146485570801300e10511119e0821070600e930041d9200a38c140451a4e2bed9c00a678b57567a5c9295cf6f1c2f0eba48d1650a234bccf8b2937f03e70ff7111c8c8aa0b1c31726453bfd2a7118e08225d2188211906e10849d05e00c88b10313297af001d1183bdd3571ca065bf0063940c2880d9268418427845041061940e0a045288914c4b01516d9e0108bb1a05d7b5f42ad65768ea5c3be748859dd21f6362fffe0eda6cd7459dde5b1b7b495381d296fc6bc2cb3bcf907fb486a2a79f907972a8eea723b0fecaf1ef3342f7358eb2466af79a5abbcd9334d54d76979b19f2e51b75e77b37cbfe471f8e461a6cbc2b494f4b06bde4f63ecd24e793d97c4c1e13b4e5c28afe226c26688bd9fb126c2ca9bb19b65d7a3bc895b32fb766cf57c2c39ee74d3ed4bd7ec65d5b3b263d7cedde4b13256e5b8cbb3340cb234f9567fb2bb3ead7aee98f6ec72d32e6bd38eb1b6cbec37b3b07ee66076e1e5b0c6d184e363b6389a5a4bda3638c46eb15b4187f62c670e6796848faf919747bbf0b2341a1cdf8294c6dfd02e4f56eba9ccb09576f8b8c5e7d82e2bbb3c955e968eec956e87f807cbcbd2ae431eacedf264b7c6cb82f76a8197553f0fb140facc4a151eeeda0d5c73593947c279169587f5d6dce80f9e452f4f8efee079f407e5cd352ddc9775651adc5f61df542cecf268e7b0377659f5f418e7b1aeb3eab1cbcf6b97552f0febba3caceb2dbcc2f072fe66d675795afdc1af700bc3539e792107a4c52ac6c69a81e762cdc073f1cc0b6f3ff610563ed3eb03b160797d6ab0bc34c80a246314c1c6ba4e2f0ec21466ad2d84959a09ddb16082095aec56b018484cc2f5402212ae990d1b97ebb56fdd454b749a387aedd2cbb0fcd46e8e9d0e7a0760d8b5409f9b0507e07a813800d76f7693516b394b3334a6e1c2f5986c81de692f2fae303182afb35ab8a894b93efbb63517bb17aec71cd3c0350d9c613aaed357cf84af3bfbab2c98f0bc404c40588c5436d8ca7d84af6fb1f4cd3be589afe7d376f2b8d4392ff59317f1867941222ee104296ccfe9a89f8efde495f015cc31d44fa7735ee630972be6583097c7ded3b173257c6beee49570d89daed3251cf593977ff0e9ddada0c35e75ec3147c13c577c82b9ac98cb63551775b19b7f30f6d375e4c9ab71d8e95065d44bb8a7b350e78eddd365a12e8feaf6aadb1309df2267ff191a807f22ce40908855aa731c09df82603f9d849bed555d1a223ea5aed33cdd6fee232b2f9f0e5fc3cba7ac49076eb8133d34c41a643e84759dd8ee44de4e43c4a56fa8f70fa8cbe23946fd18f5d37f2266f1a05e8f7a0d12f14fc429d53da10e1b87f598989b2746dd9ee5c19eb1374f7c3aec1b7b14eaa7ef947f54477d4379d987c33daa4b03ce3e1c4efdf4adb954ea30ef8ea73ef52de5a1be6d3a5499c3a7a31aa7626ebdccf1a81e73617f8dad97edb5cf6e85c6a8a7bcc6b083395d75b9fe4ed7e994873aaa1e757bb6c793972d7c0bdb63d4911c96571862160f2cc2d8ed8934608fffc12e0d11f79ce736e0f820d6873880759d88471a943717583e08dbe78146387efea22dc4275c3ce102c7f3340d583c5f9627387e06116df0831f3da4c93cb07cb4aa95cc5b1f4ddf945a5f8f92f7160c9d27f318b05cc9c880a56ae5034babf2c93ccefec0b2723f58ce083f4ae5235017a5103a08f7eaff6a6a6e148ac2e6283405c90a16dcda6b8d566a4fe1b25a592be51ba971e2949ee94bf93964afd3710a51dfa0dea7f763152112a7c6dbbe1fa344297d13f37e14ea9bcb48e3f488481a270639d3efbb412f356e86bd40f1428451cf42701e82e3e9a56fd2fbc1a74379ad7dcacb4038f50de362bcd2619732994c670162d3351d0847af243d9397e5edb7e64a9eeab063411e06973c200ca3b28ff63a32e5d98bb3f6723d244e0e9ce9d79c63fae91275daa98092cf399d0af62754614f5fc1744f270cb17ccec1fddaa73e9d4e5cf6e1bace769dc99a4c5d3ce7994a2593e912964c9cc98461eea5ee264fbee4759d0731972dd63c25b8a1e05641e2c49dbecacb153c2fd4e90e82e9d216dbb6f50ff6fa53dfa00e0f6f34f52224c151077714771813c77147c19c3b6d9c973afcd63df6748f87a9ced46d5b9b3a2f6fef3853d7d2d19da5ba3a58dbd501fbcb38d3b76f26cd43dd7c2a8210ce2b9cb9535484208c5de6eb4d03ca3a1562ba9b1b77573df3f2ca07d6b20a6733d480bb9f9e6b56e3ddeb551e901a22ea308ff162cff67818ef04292ceafa7030be66d72e5471dd37edaa504f3dbbb906abbed1944a25556bc755dd55aad20a0655577a77962afbc0dabb7498bdb49dd55d9ed69162b5803a0830ea10b7e69d6eb6d787c3f15b0bb2d341b9639ce96e80c33d186b069ed2b37767cdc053e2de5d1eee66176a87ef216c76eddb8b7c6a342ffb402c38bb34481cdf41d8eb11ffe02ec25e97f770c44eb194c506fb666a8f8f18d64120ce9ef3568d76d7b66d6701e2ed4058f3f2b11683f6ae875da4cf3c201cab164ce9a37cb2c1aed0d2a055f18acd5856e196524a89c1141e58ab3fec50e6eb0fa3e90f6bf964d92a4fdc72d20b33c916581cb3d51f5da9563236187bc572b8f0b5a4159b73708c715e213f3e60cdbc5c83e157b36230fadc1fd00a0e187230fccf8f168e41861a9afea80b5491c837e8c520207805c357085544faa38f423006f9c425120d1bbfc18a5d74ca8eb5948560568e5b9ce13d818d8707aa114584cca271384e6661f38adecc5a800a10857e26a185230dd66ea6361e08e7648ecbb183478fcb836166e802dc2ef4d7cf2bdcfdd56ab5dabe7d6ee7b6b9ada08d7ee6dd6b7dfdf537e9219e17fdc4f4b2e681f0c4f2cac82b6ca43d6c120bae7d30fd41b3105983606bf2c4dd5fc7ec422e5fdf878c1d57f19a114b8f0647cf76cfca69da6bdf34ce764da35e24d233fd13b878c205ae9acf0a6797d5c2c4f513d7cba207c2ef0f0b1b0fbb7a1fc2fa90383a72a69fc485de9c1b3dfa6b231baf2d36a44bcb2ad6ba71e3c60ddc974dec8d9e53b6ac19320508603869e5607a912e2dab5896e5f4e8997eadd66689720496be8448b50fa4808c2e9660ea93733424955c5a56b1494f90c2c22af8f45209bb7dc171beb5db4ccb6ae821872bbcf8c02a78566c03d00ba637163512137776e5e0597ad4cebddbce95e6fc36a7366776ceb39b767f26ccc13df34eae481ad9605fcaac68e6772dc7751c9088e975b34f0b6fdad7df3cdd9a7a2d4c8f29bd5e6bc01448a4e96fbea6bfd97346484bad39614e176909da26870d4ef7b6691bdcef21b7daf2340f76d88fbb66b5b51ce2a83ab1310857088fb018ad6c3999a6b339fad39ef274e48c7694972367b4fb9038a7ab7d87c4b11786ab5d8d8bf69fbef1e1d337ddb5ffe89bd2b50be99becda4b1e4d7fda3bafc6b3e94ffb2667b413a4b0f008cbfa9cae650bb61c18600aa90f5f2617fa663b0c8ecfb6bcb9e633dd7c180c268888ea30d95bda0da9c62071ec86a421994a5e703fbb90be29bd3fa46fbab7e512e3f1e84fc21003b4814307cecaf597c31e69d28b4dced0652469bbf4b65f5e6ee515be724a572c26e539a9bd6ff26c3c9a9ccefb1036bb66b35ab369875570b74a2d633094e3922489c462cd850b1697c8211864975ec669af583f12a148bbbc6cb7cb0f91b90f61ebb35789b3dd96b2a5c4a4ac38d7e05c715fc861decfccbab780b0c6a557e39797e1157c7d469a1a7cd9e02b27dfc0d7956115d6f5fcc93c1fc75ca8e7aa7ae6b0c5325787842bafc8d037f947e2124ec29b0412de8fa16f56f5fd4d6a4512cbc1320bceaaa8f338ef43d80cb3f4e81beedd989ce912cbfb8136e44bf07e90e0f9783efa83a167e46bdc0cbd60197361bc1eaa94b7c3851cfdc95b93a703bde9d595c4b1724666dacd1667c13ef00a9d972ba1af28e0af7c7d5eb79ac0f6337769fe99f82b00c2ea042e9ed06095655996cd1eeaba1eeaa2da38a9639a147cf511a13ed2432dd4477a286b232dd437b0878afa4896b55066e54599cc2552492789f8ba8d44a88ddc1eea0f7b093d94b97b6197da7573e9d8755d4f4d6bbaea32dd4c4bf767ced9dd1f89b1be2cf4c44f8cdd9e8c8bd247b863e76e4f3cc518bd97c679e9cd3d84af3e1251b82eedeb6ffaacf075cc870c784634726b85b30f3c0f24e27837b0c2f3f660cf0ead7ca78276d9e41401a1ef9caebaec5650713d535b94ab12e774799b1d6b823af59aa09ef26ccfd06f1ea75d47795a2b4379a76b5ebc8eb434d7c9ab7246d65aadad3dd4474a7d7ab2c5c8872ad7afc6f9e40cd7e9d8307693f0c4bd44629fd8e7c490be884957a7c295f42f92662fc1ae2f72240e0a75639033bfaebc9ae94af087a1910f5d7ec0820780a1d10f508c7c90324404de2447894c8cdd0e925f1a63377ae96f22ddd82522dd98d41f7d8e49d84f677ab189e3ebcda6d333f7922af57a1487f251c1fa7ef753d68e7517f64caeb7bbd94785eb3bd3f551616e7bb7bdb3779f98bb3d1be7a3ca3e2a4c0f6ff71cb7503c1fbf880be04e319769f4ba3409638f5f441430f8fe647c772b488961e3a05eeb515eed19ecd19b327d735d791983c4415d5294077b4662eedbe6c19e2b6819f5ea94b56229a5bcd3bb3a92cea42f2810892935b549081b44c1c249a36ddb9faa3ff9d933af6a0756464e3ae79c387a398e5f5cc518e361152e4d7e68e207650871c616062022230729507491044507105e93b616057c81850aa22843a8c2041224c38995a020d060280850ec747ba1c5511845c0e0a8084ac230c10c92d03285952bb6ecc01c0c25191ac268684887631cb3638735de791368707ca5ade6fae6463e67c7be651ed759f6d91f775cdfd95ef2d9cdf59a40c51e439c27a4375b2c57f27d83757adec048563119c92aa6234de4e09c83e15563acc1af5e20994952d92707d363d7a6bf786b381eb0106cf4b6fa5b1d0901de70bf1e60751e7c8051181af190055bb1417833e76fcef0f1664ed9328d3416d0dd13246298461a38857e869f2650b1af0e6819250e4d6794179d737e65349443b6334f736dabbf99be3b0801e7160ab609250ed01354e0400854f4c0871d8481831e5e90eac18b19201bb448c208084f6450451173de264058614406972dacd0d2c311a71ea2b0229130b64829438a19536c6edc15f4208ba1297c2082085d1c31c2a9872230540728d882031758b2e0410a3248d28509bcd8e20724768088886ca1011713d8e0cb961b1c99c1912f6ae4d07c363760150086463b6881613034da610a9cbf224ac61746bc18192132bb7910e27840422f02786882041eaa441e8448f1f024eae0150b0f2cd87838aa3a3880147d8b80293b2cd941079c3f4c89ce417f919383831a83725a61e000890894747122a984a4289840019431bcb8c206305c11860c5a904608d08842c60598e0106505383041d00bb254a0d5d262c2a256c300d2791d422e7248638a20453d9b9b43971ccee82cae19b30c8e463be4d032adbe812b0c8d8afc00a730342aa205d39e30f811c30b308a66b0e58b0f7e52e0c30ac4388285cb0a6a00037d81133e5c09010f64f000c2e009237cb8820b1f6ad0050db6f4d00315419092f403282b94f1841256b440083082d8815c54e088a5aa017170c02a26d420f71196972f38220b1b8848820766f0b0937b09169cdbc91738779496a2831838f794ba939ba80a96ef2412e78671451130ba443186103bf2dea0785eca4507a2920e59ac90e63c800e46b074982247a70530906e7842045bae08c20c84b8081d2100402938420e51b8a0044690a8418e0e04e3892d704002285104310219085191123a5a012101060f5dd8f0054b0dc4d891f7468e0e10910f529018e2614a0f44d8915f12276a60851734f8418a1db9859012935013a81bfdc59c1a9a9cc689413a5d0c70a639aa37d8600bc379159bce4b29e57c81c530342a62a5c815970b39a49445aac0524a9ba21dd5d6c0d0a84893223ee05c8354a4480872d86294c3d19178f44c9411c38223870b7d13b3d8bcc231fab4e2bcd15fd4f17060612786463aa0b163cea98319784e3ce775c18acdd00af66c7a263ec78e2ae3bcf2e66aa48311fab6bf7aebc2d0480721e05c8334872e4af194b229150a02534a841b7b98426c61acdedc562c9c5fa00049891b987012c5931dfa2a7126f00222b8702246098696ecd0dbbe59fd93f435b7b3b0795a235c915e82b0f31e40a874839407b8a1cbe906692a62dac088881148b924f100e7e845c3397ee9a026d24690269e770221c84a0e403803ca1435d8c9314a0c708e53b2e01c8bb0c439560143eee478050b968f4e248e0a38fcb04596242c402c212550145fcc48810d4e54a182c64e865eb4c03906b900e7288425ced108169ce310921f12e707891da2dc00891e8824697c154dd1022124509818ed4018e00cad40911962c1810ace304bb793a1961c89e301a41e90184561e2c350af404a898309b094382c71edcc9d15c4a0982e8c30737062900b376cc1270c8d6ed88206cb1d0805e71b386bd9cc6b7303ca1d224fb0bccc610a30beafb0d986c6a6631109181a11b90277181a114101ce1f963b5c6d445b58660a10b6f8b4f03571bc8282be3c750ca198670605d1d010a210670cc2406e10ec3f9507042dd0cb2b25f882250b40f8e2e46d1a1a6311bb6e7ca16508e0c4d9878b462e4a2fd803ec3127a53a72420abd6824c6791867cfd95e14ea41299df013c2f9c5b66db0fbd03ca45bbee04d7231c6fc33f135b1538c520f52f8ebd1bbee1c8abfbc203c8885f493f4178160d8ab97fec0044babe508eb8e7aad94d2d9b1d0a348a742affa9397b1c1d8b3ca9abcd23b8fdbbc1c8d703dfad14813d96cd24ab19fde7067a865762a4c1baf22208406e86283f726607b06f6070b6003d99ffd51a197f9ac3773b862385ae670ec9b7f02b7ef2f1b908023c14dceee4f63f89e0c1ae1922829225424264942a7d8e173421b990a6787312976f92222c1f8454c9a5e4cca6a0caa60a320b0e0cd540452aa51af87e67c0d953612085fe1b3cf36125198b45301de16c20e7560c77aa8bf4931ec51856bd2490222a1294453685205f6ba4adbb6148cc93ef372cd0ecc959ebfaf52cf2b9c7119d7a95e6f351893d6b36da66dd3346dd301a18dcc9a5237abaecf2a7eab11537ae6edc05b96ad32bb71f9f4fa92c6a934edf1302a2de5c5671c972a3dbbddb56756cbce71a594673ab7d2744068c3da95dde4119b0e5394404ee124157b1dfb3ca18c8058924aa568666d2aa5699afd29954ad1db6ce25087cfb89e2e76967ac771dccdacd47d0010e6b8a7386dd3ac77bad9747d569b111043464008718f4fddf4ad71eaaa6f5ee7e51db8b3db56034e7ddbb6adc653b984e252271487f27ab893b7c2db09f5edd260024ebde35297a5eaaeea9eeab69b7d608b3a1d7e3b79d9ad678a3f8759dd7bb86fefb86fe7364dde2c97d8eb32071f72ca4654b3bdeb38ae765c677af7f6583a4a3f6de7bcccb287d807e6e4eb65d9cb53baa7bb9d335d1e2dbbfdeee64e65519ac9cb3eb0e99bead9467a59df411b99e55e6be79dce79a6af244e8433f234dbae3aec74f8c0a5bac168b7d74f9ebd59ae304c87baaceed853bfba6f2aaf5655ee0ebbced1c96e7bcd732e8bfbe9a6afec519712421ba7cbcaced2de7d7bbdf520b46182d046775696e9a8df58997c0e6843be0673f5aea08dd2f559e538ca5edfbde6b2b86b975f6d10dad02e2fabd32e2bab677197efae566f66d58ebb3c1b8f96dd15b4512f4f0e68a3a60bc1065fe5ab0ef97eb69d7cbf4a0f426e0620bc9dbbdc2e074ca8d9ebdbebae7b0010565d562ab5c973efb60de63e000867aa6fcf54dfaeda6a8599cbdccd3eb0cfa5977de05abaf5b20f6c8ffa16f36cb195331486cb590e2839ee3bb03ccc555e5e61d53b2ffbc0dde9b06341f56ccb3eb2772ad4ad543de60140b73bf7ae3b07739bd4b84eab6eb6352e77d3e5cddcf559e154ea74f30a9bbc2c75942e8beb4e2fee1dd7833d3ec3acadf5b07b622985ad979752d83ecc595e9f156e81a2ba86b1d55fcfa1979f5fd1ed3d5a84f0b2b05b2d636167d537abd60a31ecfaacb0bcacf91ef9be7e7999751d5ac135997561efb94e8fdd13aaa014d66068a52f9016e7123ae890430e52283e9142908b275c34b18a4da8a00081e3555080c8117790f89fc9e2819797f00699d80756c1124248bd9686332c81b0d0934e60587e17dee885d52c92b10561a9c599d2274c92099b395c92dde4e8766958a69add91cb9ccce5541577e3cce1e66a36557d333184373ea6bc2c9ef92ba3b0f140787b82524a4be0054228693e176a6a72e0a83a1459d856f01605becad7b28136609423288ee61230d4e74b31f7789fc59be9731f086797c5a33d7b965dd36ad7bd7655c77b555d970b58c5eacd31715cd7756b66d3c8392fe7fd2e4cfbb61a0375715e89fd8cc988c9431d29eb849e183930cc49027a3e3918429885cef45495526a616831699dabb3091b4f5bad1b3957172d847968a38188609527b04ac7c00598c2101a21fd459c1f7226d6d8dcd0c191a3041b6ca9685632ef1b088b60dffa68669d70be55b304c378601bc530add6d8c014380ceb30ae5a8cc3d80ab460ddc7fa10d71f585fec1a426b3ec487b0f41206d885fcf4cdb13b7fcc0f61a54e7689b0d963ddf767fae8af6390a1bfb652aaa91ae7f9401bfd1aec587fc19e7f7cf8fce81b6e49764c3bf683e10e494c8271d7071ee1eeddc41987c11dec34314a4dc1065b3a30851fd0866a55eaeccd4c65daad7811c3d037387258d19161330277cac1dbeb162c33050860b47cfa93c187adc1d13734351267a523b78869fa46d5dfea93d06e29cf870c66d81c836052d0189148148a468670c1db7ffc0891c1874f4e8f1860e0b123870bb631215be76556494008211216caac5ea0b275ae6c6a68b8d84c1b58e11ec83aeb92bfe4b79fce102f2b7b8f6461c7eef6f84dbbf097079f7937566904f58148d3cb3ed10996598c60401bf088238e20b2b1ce82f37066c76638eb7a0fbdfc75e9e5b30bb10ba511d2086944740291e21897f9301c2f292f006883ce2172c2f929e594526230b48ab8620c2782ce4b292576d15a6a411bf03fec103f936219c3306c5e727b023384104ad56338113f14534a655653aeebbaa48450fa607db03e9870cec9d5cec1884c1a43844425d006bc8f150fab1f3e5558815570e4c83922430cfdc1d7d014d1c82a6c6c9a5573b24080e7bc30bd2a29987ececb1e6ca4340a4522fd0dc5a0fee06f4c129198b4a0c24698054bbb6093c4c6851c5d6ca4f9ac9873ce534fa606243c97081197a8b62d079e979e5462e3e573f0ac436c2c9e9f89e195e166192f8b275e16cf65f5cd135f1ddeb0cbc25e6f90897d7270f5b1c1d981301006c21be5b853eef6e80f7a3afdc16b5ee6b9502f7950fa49ef8e88b7186d3c16cf84dd0b308d34b0945c943b888225cce1ed4fe3c41c7e488529f8689c1c7246a20e9f03070cdddf969b13ce6a15b7401bf1d58bf162959e41ea2f1ec68b59e44cbcca8b477226feeb1b1a228da33a7c34d2374311097d33e933b62d5583eb99b49366c261dcf559c5a29417a744a21c9bb229a42c5852a829f6084be30c992271628846b644225ab84c49b222a4bf78e14fe2e4f41138cc6eaf18c7611c876282c50f984f0e26c57a114a94d5aaeba2442791c9290a759ec4714964d2382ef48c90c4a9d1220562895084724c92a3bf2871e2183d23b79247b1ac32461be99bedb2007165d5436c2b3684045bb279e99b18244b2c1ec9dd0c02bcbd3ff9c90f21f18fbed964ba87a469b56a95126f77d55fbc70e75d78537937bc9c21d81cbf60f91cbf745013e9d237d9077ac1dbb9f4cd181b77799deedc378eebbac3ae858ee8480cbc5da7391f78056fdb8d4bfa8bd149641295f41793c42124fdc57794539faab0e92f5e47ce441c1a2883e1558d53a50d4d8daaafdb45b035363937faa67f5dd84abbb96a3e1cae59bd5f7fefaf8514055ff030340e0cbd83478f1c9c9a9ee91d3d731deb0ee157d7e1b9eb496c865c7c489c5aad95135f135f2cd0122f0bac1f3ff191081d5253e7e50b84f48d8f0d8ec7d13987081ac795b8aa993e31ec34d3fd99b8eee8af73f47324ce8d9ee95f55ae7f122722692fd74fdf5cf7e1f3a36fb6254ca29308a56fb24fe482b9d773bf1e93f4cd4582af2886b882399fb86462cd9bf83ab76a1cb873c528b568ce39e79c331681c3d087cb028a8dcf3418d2e739e79cd4cb016d401c3a5a64569209482985f273b6e59a48ee60e3bc19a3973d724e7a3f6883876cbc4c7ff0f491d2793adf43e7ade90fd2c820c34b78fa2c25bc84ef9131cacffe0e69ed1b7921ce524a19638e18f6fc01ca4c3f630f8d3176454305aa739eaa0676d4b1d331af84d33d61d8392faf30c7711886612be888b9fde92898a3b054ccb517421ba8a3eee9ae522b8c9d2e96ba7daa86548154412a55e366ab524915a0542a14cad640a17e3aab464cccb5d5aa6056ab98ab2e8436ec6354d0c6e9aabb82366ab62244444444924a8d735e0d18eea72dc6c3ce79aa9370ecda63a773a8738ff1f20ac7c4c4a06c0a8b5171dc75da07e6dedd0a31c7ecb19f683001a37e4a71a9679bba2c7b1f0084519765df837aea363531eca7733f79d9073e1df3b20f8cad0e3b1da7abee0952d8d3f55961eeacd3638e5deeb24e972775d453477130920a0af5ecb3c2a9c7482aa9189d8e81e1606e46a5b81a354858ad581f5242cc2595981a3703e11a3f7979854f8fb90a9f54313fa96eb6f8747b986c71cc515ecc61e394707bd838b5040ff60ceaeac8182ffba871d537554f8d55d7dd03806a740d2f62d5633cfb1a5e5ee11a971dcc7b626e0f1373fb98d738cc55b7468deee6809855e33da9c7bcc63da10acbe2493de6a90bfb8bb9a7c35cd89feae6155ed9f8082aa104557345db9645e7c6ac3a382ef3b56c6a6c726a8e54b65e66b5b564fac3b04f48512a038d2dc3e7dcd7756954e6a8b0a346cec84f4a79b9da6846bd69e9d4e8f46495333ba09c8971879ce9a141e2261dc09e61d8f1e011838276301eb06807bb39e40c91098a26b57309173c41e38631ce48e3074f1c81e71349c0009dc048680c29b64ce922cbce54616864821be00d432313c4c0057d9986cd15f7148993452b590765595a0829bbe21439d3df62f0f1233a8951603777629033ad1dbbe6c51dccdba15dbb20c88eddaf7a3d2e4b021808b663b76607ee6c5105b9a3ca99d70fa9e1d6901df38638218158060ab208dad98e7d480db786ec20a841c38ef592762271b086d209c8b2231ec99976d25f7c0c8a4272a6770891337d96046a763f9604b0ecd6c899de7139d34bbc762267fab976141fb60a5388bd03bf994a38410adb4e700c02a4c7e9f53daea3ae4293eeb0831d762c1661c7e294180575e391970eea6eb4526fcc9284c4c5470c3dfab307dad9e6bb2b644974327fc420ebc52451682808902012381dbb04ae776701e226f5417a0ce1aefdf41e434cdf7e7db3a7f6f441e4810491004b0299f6ec70a7f4203b4ad7ca99780ab024503aeaa80b82eda5c39d21403b2c249133f127af97c89978ebb17636e94500ee10c95efa163b09f4d87a703d4cb785e44c7ce9b2e4bbfb1e9233f19b9743ee9033f199c7927747c5ee7779d463f5dd512367e281766a2801c9bebe4c1d5a5328829106dc81429145ce225f9ad690f352deb00107453a4ca1c49323951646b058c28a1ea680a20c28cc882282d3b4a9996440d1050745187242231881d18327380c0931831d7ca48c2394b0924391d08ebcf66b411103e716942d3665a1d1c2591e0a0e4a3e2d3cafb2d7e7756d1aca165c94cfd9c1661b9b1b47b0250c8da0ac00670b8b28864650aac0f9c371f60462eb4b9e31e9e79c2f10713122881f9e0c71b143801e6c11060f5ab498a28b31ae542c93ce89c9a41e3099810e6c388108766692171011850e9840b2818a9d290686820a1456e0d9edcc49299d5048c173b200cf6b674e3a390c8da09c000a12948a235734c1c4e419f2ba949248765d997a59916cf29a457cf0c491eb091ea27c95572606d16b53535b404e424065882836d8c11747809a48a20a108e94a00649a2f8004a116c31346ac28c8b8aeb00473a11a261b32db2c1bc1b5fecc4d0a80922d8d8268230af1d6c0543a6d5c418387f2470a28b133a28aa615310c7065ed08625cfc836385e2563ebec410d9c5755c4da0fcae078d98418d0054b4411060ac8c0028b1fecc01958b101d20faaf08208507a20d392181a35c1e5cb065b2cf0d891c5f6b71879f4b7a33f187c86f40df61efdcd1864f0f1e34748df6097477ff377477f2ee068526fab67e66bb76c0b0c9b393ce7b7bebe5dd761875d17ec1a0619b175a1a563b0c1d69c5a7c901a1c2f10138ecf40b02c9eb8a406b81fa406f7a561da901308964e79bdd6983b48ed7e8d4062a489ef470ec3b9ff13f194509ad0a23691c50c0086464d34698208f916ee96a7c8bae087d8fe58d75da1f1159fa37164517f070f3370bf470c52705f8614ecf47d7c801b1a3d61821be3f44eff82f1bb512371883891630f61e0be4cb2181a3dd901e71bf8852208dd26587f25b17b9d5e28034a7f320923268898500707133a9ca8ceaa992042e74ad2c06a181a393103eb481c797fb071c172909980a19193229c72e2123ab255ce6b25c3246b19ec14bb3a72265e3a1902cf3971c0229dfee29c4e4680e785e79cf3caace49da97182c310dcacb1e92f263184c497262e88d826d7d4f41739d9a48a8c4116075fabbf263f682206cead279757ab7761f39a57371246ae26b0d65a6bad59a6655ec45acd32ae3f4ae3d5a9209b762ac8d7caddda1fadb5d29b7f66bdb24ccb328d5e5b9c58ad57bd31ca75cc9bd7e7ecebaa4d54b18b5e1737ebfb83efa8910003f6c26c18b075aaece39ca899a1ea9c3608b65439bbbde2a86eab67faddaf557261b30d2cd2c19123a73f1b4dd334086d640e67d44f2a4824db7d7b4e3d7b76eb658b6d2a762ba0aebde93af274e7cfc4a55bb3f66d6e55d3146073b59d1de6fd1c1227e66dd4e40a6e6dd3b458b30b6bfaa6ce396394a93aec28d0542202e6556fa222e244aee10186f17680551caa2b16769e5e28030bd7371908cad30b6518e90f6279b2fdb192e066cdbb42e37968748228586636ac7004b013df3ac2f19f0a76a2d1123b08e0061a296105c7df88c731bd178a40447a2f88a1c57b410ca48ea7f10b4510ea0fe22bcbb0290c8d9864c1474ab6ebdb15e487c3401c80e30f1c038670eb5800c2e2290167a02380c01c60fa8dd64b5a8fd1eb34d6ffb930d641b04a8f35a5f44248d32b4898033cbd79af4b4929fccf85e97307a1d73c8540621a78ba00e3d09c739bf3d22eedd22e4d9bd183f53a26df52e219f1d4e475acf6f4a516e51958cc1b10c91b10e5c0e616f696b20b4b995869213b8f767ab3f61ef98bcad9237f95a08aaa71e64e9f9b82b39a6535e3a9ef91ef91e7e4abd733b97a795c578304841042284303a584524a08614d92ad0636762a74a7c20c43866665556fd1396909421bfd1ac536a9cc610a12d39b7d5ab8e2c0568973d30228bc30e3065e7248b243c14862496acb0b7e10820d8e8ac460477a31cf98df7a42f843215713f577358d58ab71bacfaf240ef7b9f54b3be4cc843dd337ffcc8cd273374729f41ebd391ef37a4a44a11f9b3e7a4d25a2d0d7e6dea05e4e7ff33b244eab67e669f0cc15572c4da5a9482cb5af1e30c11e864645040d91046f261aa33411d69d6e317bb395ae62b18addacbd6a1a8655fb6aba3db1897a503f1d2b71b287586bdb0a96b256d3b29b4c16bbea3a9db257a94ca96ba99b75da51afdae312a9953af9544aab267bd535694f3d2e91dab567c76eaea7d76f26efe4e5d251df2e4eca600bdb533ac519a2ea4bffe99b6ab992a71de595de79d8abd79d6ed6b06359f638d437dda592be894b9c4059c2c4749b0ad63d7637d3911c966544464640c113d2605206344de576517f31bed26ddba89c81cdf6aabfae2d6c8d3f97556e9b1111494446430835959e1251a08f51faa318467934ce457354ec12bb6ebdf0a2a255e2082ff8040c8d8eb8224bdf3c4a4bb4542a9524dcb6535aa22fcd6dbe624de51bf792972d963d4448d7e5eda6ee6620dc954adb6dbc2d5d4796b65267afd3a6d239eea78b70d7ddfc33658cd9e2bedbbb084d9d29e6198070e95cbcc914df44b77697e7bca61251d8346fde8c234685114ae6f59fc61886196184045b301623122cc12ed090287d33055e1d89c92bce9bad74951c715113494cc326f6d94538cb09adbdacb5275851eded4b38410a79ea8e851eec521c986376621d0ca6eab0ee98ea6d2a5d552a3d65afa9d4615cdeeecfcc541caa6b0b4830a4844b5fd7ac4d84b3bc8ad0823f0c8d8a9092c548491423254b8c8a18c3cb1668488d517a4a9d5542dc4470361196b556550aabb0ca4b1cd5adf6a6a7dea8d353a76f5eada89b23113eddecc3616b2add2eddfc33bb8b492efb7019767bb4994529985ebea9c405d4db835df4e6f8799b4a4441de9a535f36883c1a67ee089145882a497cc036954c121daa8c41ab0080a1911063e089a191105c70aed781d4225e34d09b9f8fa97df65ed1cb10d84c43536393c3fd06e755b8ea996be67ab6755bb1c1beb9bae7bc5876737bf978ce7e1f83b8bfe0ce80484c6990775eae9b6c1bb0c13d4476e2b75f9e0d4ee9f49f76d3a90c0eea16e7744aed65a50d36cbe07ec75defdea7e99beaad54fd5dd74f5eed99ebf6268fa6e4bd67ae4f1b4ca1576bb8ee206cdb062fd9b3e74cbaaeeb925daecbebf2225244614e3aafeb535e179d74ced958a43fd84e3d0cf3b08b1e620c266148b16318f6eba2f473687af2f2178533497f545e52d6da55c22c5e452449a224094c4aeaf20512fc2249a88df4d0111410f9018c5fac3056331c1f61dcae86fdf44d9f410d05dc29b8e1e886ad2fd35f92cd325fe3e89bbe4c0bf591a8d35b5ee1facde2678fdb27c7f9e078edf13ef0b65da7bf7dd6bfb178eddb8dd71e351f983ef3f20a4f29561e06c600b71581fd7f4cadd866df48381b273bcc1c9eef6caa629788046312d69714dbe990947db3cf87672665045cf026b99652ca9ef4537ab39bf661ddb6799d96724e2018f7bea1f43d93f4d7a792f36a07a696a8d40bb7d5911c758a6864000000a314002028140c88c542b17048a84d7e14800b9bb0485c409609a32489511032c610638c21001862c088c01041db00d2ce0db5aab155e92e0f0cd6f8d23335ee6b69d5bd553daa0d0008fc2f5ca24db56abe6804e036f44b99abee4f39365145bcefb86defc23b24edbbb357874bb506da41306b49afb34c1ece1f9dc9e16f6871870edb00bd1111ff56cf55e383c6dfac49eb2b6cb541ec77dba89c5b8bda6763f33b9480706191310eb137d41a097afbced082d914197d01a38da76e4833917f0b15348e8c66eadd3b13639a1db0de347ac074c5b5b3451acd5d8b9ee623ae0ede1038c3d229648c0835cf5fc04628fa0e668bf2d883e257060281a0537ab82bc64a86bb9cce58e1068f161de23e1bf7040d8a2442d5bc8e45ab2d90bb4b824a33253e63b8d43be4b8034e280bad54d826f6d6251a914fef4eff0479f86246f196daa64e521d5337c567064fabdcd16222deb249a6dc5f405585bec4ca7daf8cc4b81f63e1461f23ba41c7f64851ec988968aa7276ea17b24f326a02b2195c7121d42a1309e48b9ddf43977e26ea04987ddc942157954e16ee68fc9d01ba5a9a93f6b30c058c1eb6f46d68a774318630b5984819955002c6bbae05c045426634569b94371c4d8b94cf840110af23f5949379aed832fab5f15a9a3f42c66792bb3f5c0c0b7ec5359761b74d3e3c5f23f97f008717f06df2813ef7bf92cf17f6b6ca94eb6fd0024a231a5ec1232c22ad3f5b7994a539d4080f137d892fe1157cb026be48af7fb1c2709f7f995ee5dbaa43f41f85d2beb817deaa61df6da351aab23b090037fef154b1f1c307d57fb3027c54dac5b470f48da3116ac0a1b8c3ce037d5bcca89fa335f2adc01074603373c6c465b4a4750ae9e0d57e65d5962131a2b6e448dacd682551df840bd91f0393e547d4bf971e22dfffad2ca5c8b98f0e96feda664dad3a03333b9813e01f20d39006b310aa72f2217eb088e09d8e4e35d44a9f7fe1057457447b9607147bcca5d7492e7568a8bcee48bd2016efd87e784c562e131f1e5b26b064b352506a0ed95d296a70c1b6e5e1544736703df1215d699c13d5c2505b662c81a0b144eca40b831eab3dd1db831c713fb894ffa65c764b486a4e5733652235a843fed13d49d7e357501dfe22471e002e42971afea815f8a880254e0c24b12b3c73060902172fe305e35c3c21f07803148b4be8cf84ca255fd76331859c75bf340bd601b7187d524430471eee0a8ce793cf01d926ec5da21c00397303c85d1d3c3ce98d9f24d083cb1425ee4ad050a0c4ecf61785d83c3f77ec38e5b3037eca8da95075f556af40934e8370b8e71d8c12cb9926249d6c5621e1c74d8c6818e33de9c14aa1ad6779371b2704a630e7e16645fc0f7480ac8e1c98a43ee090fc756421e201136a7a294be90fc314ae45916f9e31e31a9235338dcf50f5b7a594540d0278c8348a0401dcedd00f316e4e846af93999e551c707868534300fef41b0d0dd52b54e975b4066e29e9e08ca69c4c683ae989c5747a05078423d4f2796a7348a968459022d0a7f2798a62c5763b9d5b724f6b1c0e75a002fc0232634d0cf0306630142113750808eff39e5537d2a50ad72e3514046c12385fccfc61a05bf6a89faf2565eec891993bdccf6cff993200d587baaa0a7d05e36060e65c286a77d248aa95ec69d1cea1039b800def51e198f68a9d0ed9fded2668bfbf2e6d7d9646b60c4c338c92f9c211aa4bf700924f02f03aea0d88ebed02f15e85bfbe694faa60fc02ca9dc26654b1ebaf2fa113ac638b6081726236f8b289957d3bb138cba221edb6f4f7e712797bf83bf4cfe2fd20d40b9d35e4cc632f3e88d8af93e5a43d4d053d3668536daabc1c624e0b5aa0e0ce1d8a2bf3628ee8a52b070491144e1d3739064bc372d7f908a248ec970eeacaa8e1d11a3fc5febd0883d0eb9b88096140fc7925b0af015167b0d79b38981907a497d2e269a881282aa48de90e75f79010d2d6f088b09f0417655545a5ab4bb8f256dc65c733e5fd0bac8cedc86a7d162f3f6e69bc034f7b8065c4421194bf9a664d6623dda844ade3c45665dab34d68bcc75cc3a7a1773d87e41bef7faa0f0ca163c7bbf9ddaf1f45480e728a09e44827d3865a78b8734ef8592383cf7876a7012eb2512b2c069a9b1a98702aae0cc4d2b10331080f1eba6fb3728e904b0a86e33da06957a5584b4e5f9b0ae4d3d4c00ae210b55b0e64fb7b78d44091cd18a8f055e1bda4ecfa6cfe47506409402c9907cc4192c92902c71d095c16a93e658c91e63d5c66e3c9c8bb028172ddac509bc13e377c8082c9236412578ae1b66f59a1af99f3e501cc793044514ab574fe2bd3121ed62b526df0474b90ffa489692dae934429e9cc8ac998ec46c57f1adbb3b9346766c378665026e4b2016a5740c1ed8da0889976610b1f341c5a04cf8af04be98a10e703ca7b591ff92e8f783016f08ed00a452a272878dbf038a490355b7836051d9d0c2e55aa1ac0ab63222a0e446c3cdfb1e0cd6104675bdce01018688484c3c1a49d34c2a6a441a497f94a3a30060a5f187aeb8488fd6ae638b6c135cb26c2442e963a42d840df0ddbe1a0e9c2f1e7c7c651d71c7fcd283fda16ad1c68988787503b7dff0cf31d2b34add687c15a4e28f8810bd81ad49af059927af51ed1041721d2f64e83e7e50435bded2adb5d88c4cdb0efcf34d77302a69f1cd7e6de14d65c5b3320177c5fc43e87b5918afddf7e6d8dcad83f7d1898e8d536b9c3831f0ddf1341e511058001959fe3660f02a31a473437d9efa75bb3740d0e49037e21ad0aa1aaf56c33220411ad4e608c315dc208f6acbf7e244f114aef5f6c4818a21054da146331c93a261c7089ebac90e881be8ad9260e055acfd22635057abeac062b5a52aba76df04ff0122ab59b358b3927cc5124d0c10f6d15fedd16ebfaa62f690aad93f0bdec9db139c7f6da21ae6342d05b11bb5a43231e316bf5c459b8b76d3cdf186ae6071c0dded54bc0f2eda50e3002c74a0e1b10986407d876c761431d8ababe722bbd79074afc94c18af377431dda60ee5dcae2ea54567acbc925a22af11bef69536d5c0520fffc772df80ff9d378c7f893a52f814cfb02128a9f988f7c6b6efcc1a64b5907fd1de2e4a3781f90e93a3a0bd3f9dc184edf7ef82e6fe9076bb447985bd37bfda0f7469b04021f5863d22b3222f83a85986644c029c5aaa533af14d738266b486802c77ce6f36b4f88ea183207a6e73f2d68ce651cfabf7195406dc8d42ea4c50872bc732357c900e6174cff6016acec61f7750cdb29b4dca5d48e5c8634c09d6486504de643aa98cf753523920f2052f4a3d612a235d2388d881b8539444e15f677d8473a06f942e08cd8afde6312e95a65344e1fac77dcf80b5bcf804d642189d8bc8a07d5d397eb093294c7d484c761e6e543db798c2ece5e4e12065034fed2f4de00e0d40545e379e88585175dde8cfe4efe54025a5fc86e2c1a5e958ca8f8921b5468151a419659e0de68f5c19510f1e521b781e77716131554f10ee642697f0af8eae0049c30dceb3b53c8b4075a522c0ef67b93b0d82fd6c64cb9c03146920123917b31c1b87a4807427b02c5b17c1746bacfb1046da0b15873e22be8647d950550d9c9cc33ddae3010f5dafbfd554737cfe5bea6f93323100d77233ed7fc36a093a37f6726e8ce5b73d7693bde4b7d1d05de6be7d1f5c88005c865921bd433499d18f8a621b60030cad06b5d61065274a6bda7bfed73dbd313b57140c4c2b6dcd904caa1731ac235ba885176e19e2cbaf9efc795e7e86221cfbdcc1456e1c9bf4abbf8610c2801ef9a481f3f4e365d5d5d9bad37f67c82ca08beb8579696a1d829d96bc6acb5cba1c5f522eb63bbe30019cdccee52650c079a2a5c14c568c369556f407e93f03381f3043b6a55f04ff44bf186641df16deeb075520dc0b5e55e7432fd7840a8a361e74b732487c9a6fad7a60b61e0fdb23bbb4417d02b8e69d09a8dce3b567a9b9b46c27b55cad41a3e15430a7d81e6904a8ddf37c13d2379e6f4ae947f388bc8f4a60627ea520dfacf8ea5a949f516674871d23b78a7f43f275819bdf91e68f32ccfffba34ccad8b3ceaaa17739e1da9746e49a10a81b954a310b3438d1ac3e1891fd01d4e0508537dd513245ef5337ced9f6db9e3fc58efbfb51c88373b216091d4273f67bcbe66733e4a20a47e4ea0de0998080ad7ab78ea151170b9966dc6930395895b3df0d67b3fd382b67e5cbe902326a55ac8a22f540eba6e38d2bf3ce023baf64247209bde24b0bb007c09862df00c7650f085b5c8d1b7f90fe00d862d1372dd282a3c64827f3fd69d744ac639359b983ae1c53d6ff2674321fdd1e16c546b35972a3bd782e8bc2d08227bec559d864b33003b674e5c6171c60a5dc2595cca5cc4dc7561ef68c2e34dba9665faf14ff780f668be0fc8ac5f37ead312b6c50d2ea3eab3f683e6c32535581fac42a6ca45027b00b249253f132bf022a70e3b485609c07c5d03c63c04e4e1b73371363313c5566482387f0fec15711264825e52b5c49e79086faa69f80712b2c9a2b99559260a183e734ddfccd34bab50a2e5a45a3968e611c3d181702894548792fd0189806429ac32829c968d04011a96991a7ce5ab5611e06c091f3ff63ed627757502ff05724d799503a86aa16db362e6362aeabb45a12e53aa1889405e932df3aed0666a772340a8543743f761d30b4535791610ce43d5920773a48b936753644db7ee019ee675e76494783b9980105c1699eef7bb56290bb6dca1a80430d70a4ffe1b802d02e578af0789b65fe09d521d15712b41a4be2a479ca7a6aa109dbd2f8fe41fa8b789345fb468822dda7de8ee8e682b93e7adbe51fdd4e194f13788238ee505bbba6f3426c81390a926c2a78e7ac9a4405a29f53e08f25a19930761c398af8b9f7dbde213fe1430965b6096af7ca93a05f1984ff7e149b80ac9104c6afe1500ea002f419b6ee825a3e002c5b6c68121cbe8d01afd1607d4cd55a59d82a1def194967819302a4bcb9462fa287115c3da0463ab8932403352d9a0722c1943e616d1c84ef9fe4e351b1ae94b3d1fde6d11b1b01516e828b74379602ab118e75610e99f90c719e2f7e4a2035cf38100f03cccf4877b91abba904cacd1358c925f78a2da571a4818996cbb01ccd8046e33e6db34650a053dd6c21e415a0d5ae34d312146cffe435ceb4e876aa32937a6492067ec099b1bd69f6d82607d844e9a17b67cf3d105635bcb496ded220bb07e66cf1165fe5b8dd5ca05666f224acecb9819821284e4e644af0185fef1a13a6d0dbbff40d3677605b880b06bf6b3ff59860df7199dad28eecdcd408bd6d88ee424114b344ff06da38b8933ea756cdcd579c9b9318f5d54c9cfd9bee3325039cd43c3630f765920bff43956d6e20cee36a778a53dc380c88cd0d124e53f232fc2efbc932eaacbda93662ddff843e8187131d49fd3bad9cd7d879aea5655d11e5888a082a951d8414bd2d165efb31aae18f72e7497e60e7b252ffbd83797f1c0591fb8bfc6373a511f5fbdaa6c3ce972c5e235a9572c81ca66b54f4b2a6f389bc6d9eda76b8cd0cc554d40f961ae8e6ce11c0daac92d5b911f2d08dcfdf4601ba119b51c38dd5b342e2ae5b12b9cca0e8d834ec6095003725e4201e88b42ed4574d5da3e2620fe7323a4c42e1a6d4f1d4da7f160446cc2647040e1dea45826ece31db83b5e856195905e5e694126e72dd7ab1d40ed04ab8292350a484795e6ee88668830251b4009de9521197276549c747317af27345e324abfafaaf562d752ae1866e216dc64f6d87b5bc56fd964466850587b2d10d28df54d1b643385c2abf584a103f71106304306c52ade5179226d4efc2c14dc24e4894fcd7268c41020d6e12f66744339b3605c4b01b71ad15cff82f4573217bfcccde031e256d231b5b924087b01f801bad7bd805588ec2d773ca31a45fed3b334c96f6fb97fbc770bd950c98f64ff87e8843aa12432896010e59a3d0da0a2a0400701dbaa5e195a2788a5a1ce62899fc8ab5ccec289783e3202f3500c4c361ead610bd81e04c4bf1db62a6632b871980c0e50789fb9800d5572132885a63a6d3244c7b01cc8c9da0eb10ed1ab00bdfe985904c09bc917899cc3811f4e308bfd571c701b0bfa2b337893f86633a9d62eec5425b9f012a3d220dc29bf1dad8d5ac5dbd000e1dd5e00168b96f40cdf434a8635bde8783e7d2ba2542e9d96cf8389d76fa49037f078756735b8f3c754e8aa689765a64c4de25f49d7b6a9af4f931ac2d81aaba9e46c8a6e6d492793a467122d157fef943fc197e70813195f97e6ce3aa5ec2a4dd2f32e0585e841944f6c0c22f262c9967297e804b903342f599882b18cddae8acfaed673b44bb78a83d8361204ede0a93d4631aa64e663f8033d3fa23a4657f3511d3e8e42d718116eeb35c1a9dd8dd080ad49fe97090f49995b0910aa7c360f30b27cce8bd3f1315640eba19549c584c50191d13bb17a69c61acc2757840d1c36903e0e417979c59181dde9c56ebd925c4be76b17645b78e97e49d0de9062eb58509074e133fef7950a34e8fbc88408c7927f936f22ff8cc07a300dcb680ac41da83a696d3d68057e331b87b1fa2710804fedb79790d4cf7859ad4377e9c514f99b94f3dd6378f6b56e216642fbb17e73166d3912b20342aec4bd0218cec1436db7a5468e966949457fd9183fef8162552f9a56ee00c6f5016c055f5eb51518a23a7142c4fe0391cbceff5722af3a58f55afa9753e51f5ce61bde31458877fe8a603eaf3ce31f03a93b7264a7f24f351c98dd53a8f072d7e6cd231e59073e7d08027f214c287910dbeb73d1facc4ca1e64b62412241296cde98e6242d0f4ba28845589528b424ff9a9ba3623c4e76fdef20e3059f3f41fe9c951d6248a35f7766c06f99403c8c16c82e0fd7ddb1acb2460a5cf79787fc21ccc6a243fadf34687ddea6ddfae9339223e0f0fc3e310473b5fb538e9d2e69b0dd263f5b2a56e45da52e658d0736c29736aed48a7a3d7eb21d0c742ccc9c176ad658c162fd06f30c0864eff5946d76d7cd54c336c5f579b6514709cb1e6311210cb58d4b823af231428ccad6907d37d7c8047c5a2407b241525259402bcce908e77782e44abc551a67fb0d9f08a9565731605dfa11f8845298107aa9dfe32e9e0051d8214ee0076b57718dd09b731bb383ccf7a9633ad47d941c1129c745abadbedaad04cd9e3ea8d7a6470bb5a73603354a604e9136fb4d8f087c15b8c1414394d66bbb1ec92facf6609eb5539a244e3ff1b57aed52e45721d9ad14f077a3e672c777f840435b5f0cc034ae4e8069789fec8d934d9a13ec808d588e663a10ff3b4a0efdac515a1436db1105163d22319cad8ada5187a53147a4dc2c6def433702b42c034757208211dd3de5bfc82ca2117c416a2a4e24ed284aa974c2d0cea89909c291e54ac1844ef8e552c52e27e2b04f2d395481860c1257fed87ba05c3ba88c89910f637bbfa53cc34ccda2dff4427956e9d746825dd93b087f75c6f9cc5f6ad383f39aa0fdc607d0ac76ee7dac82a628fa52df85807e3e3c7b9641f52fdc84ff13eec7d77fc640fb7f21172eeabfdc35fbd99a15effc6f9d88726f8a5165ae0ff128ad1b0b215484f9a542d6698e1a6b60b3bc70664e7f6c60eb8c5bbc042a06d2f1005b719768e0e72ddeb33d7d6f1c85e82b0b77b44b70fc4b75a54babb93ffe1a37420913fc669e3ba5f0c780f1743c399790f575bab863b5a66c98a43d7bfbd4c2e0a22bb24cb0214bc5e45c200e042b4b502574af270660eeb77dfe2dcc26435459ea96c983f04b60425a923774c26050516def34667efd6d5f70155ca6ac34b9746a021da8135923553fd67e1049e645572c77403a78311782ea14a5dc882fbb00285adc824e7c734d6e31065225b5a8e45dc5ba6f44b5324338f6f91bd498fe369b9de367b21593bf21c44d2b1f21ac30ed55886c88ff83a5eed5eb8549244e02b1204e66b96665010e7f8009336177ed002e79df513a539b3bb121e6edf15a502defbd4bd5f995e3a99cc6ae2c9f47f40434e39bff0717b47979698ddb8b6c8fcf35fb8f31d3c5a21aa3da621bceae901afa819f991e4deec5d9409a55d2ed9a8b2e29cf072eea9c4b7dc5c23eee1012078c0666905bcb37553d9330d98964c99a0073afa82a4af2dc5cdd951342ac264b53b42a26cc95d17ae446fb0fc919a7d38ab8339cca39d3494ba98825c289c4806e6bc1e7c94bff2ec6c5a1dbc2bd0f9d49befbe17eee8e013cb141f845a532ec23e537c8ebcdcc87be830989b9a18378bfbd15c290fc9c0a03013c1e5c886bbba29ca2d32a0b67e6d6f0420547295f5e545fe992d83d853d8801b891e88397f4558b026a5ea02aec45c4d81fca66cc50c84db5ac69ced8851351653e9af13bcecf473c94fad69bf1abb5080e7ab2d50c52c80f887835dade0d52848bcdb0392dd8ea682e3ca4293c116a3b2441a275b7b65a140592a49359cd17ababf15dbc9bd3771cd8955b423aec4702e6e02c5c49deb5373c24eb6a27f8f81adde6a29e6d885eacfba863318077287bdb04b0a850c6d07ec96b1bad29115ba8453676b1d116461845de66c7ce4b092fe275b4f48d34808948812f41a9b71df6493076c2475cc0dfae1e8de9454cc8464e1bac9be24b7536ea3b622c9c1fd3419eba134268f86e828eeba825861dc2b69a572f1437b6b8c4537bba5fd336be395ec9b42eb4da134bb88a657f181cde432b2d1391c420d9b1fb29336cf35a16d0f21f70fa199ccee3a5294f1cb3810682ffd9c3625fb6249743c8c48b18972aaca70b0a91a8975c474b03347d7792f63c1bf4a6d4fbf62e4b6349910c141bc8861e7e9ec91c6901d4640b45cbae4e95c7d090aa809243bb675dc6928ec1e226d2a921debe877a3804dffffa063d9e1f70c85ecf7ed95bade7b0fee958a3095c98aca7cd221f845399aef64207f9bb3e21b061502958004d953b018ce179bf6553f208302fe1562bbbb313db653c60fea7925317a0a7763e082755830c3a4f6d9b7326a392dd8df7ff70b948950d8cb966bf64e01f686e143dc59581ac0d5b12211b27983e08ce321155bed6282f459f9ec729f2404cd43a03059b790199f03b99f2699aa55572e7a6e30ab40e8efd4d06f23fbeed8919ab0041c2a67dff3b8f9b4bd2f4bb5c929dbc8b13afbba90d145eb2595ccd6805fd5bc63689e63d535d7da3c4b5913b5fc975f21fe2b04ee76cbd71ceb657da2732263a29f8230c4d388d8699dfa8a7ef07e4c5e6f98188b01a9b015a15074e9032be23eeac4a49abb0da786c8c634ae486fb16410d5fac740fc7fd8af207046cce167d67f080804a272147fc0250c1095516164546a254d34c4b05cab6cca1a8739e8b52c08e598e2c4543477aada006914fe71a5f61bced74905762bf8d7c09ca000f72911fef335dae66ac3a1e560cc0902f0338be2c62362f79964c13b5009b8fe870907a83dcb8c96bd1d07dc4399044ecb72ca34e12d6327a310fe1a7af34481811eac097e0758ca6c72d2f6c96b9f0a352ecb812f2756baa0e5969f028da2f6a5cff0ffed1e0446ffc974988e2be37ae0be9e94e018b58bb4450c2b43d1f7bfa306ad693221241632e97cbc831155068b9a8dffa8b27c3f4aeda1764821b1845568fd05c7132b083099781ec15a425af29366b714aade531800a58cda57d098526536871f5d1a2c8d35156af53f185573337d50de0d4540d5f86c58c000c7900cbb1619abd7c8f55cc51c60ae3b63d81b5a8a7fb1fea68d313eda207f6212e62c1b630e00c55dae23840cbd56cc9c7dded19dfc43ac540af83af0d77e7d397a06d9970e1990ebaf641f0cc5230c635c236c60fb53ef99636ca223495cd4aabc452e0f646f5390e5fba5eae50d4ac601f6826cb7bc890c7ec975106d41f6dd0cf3293897e7bd3297924247c587e0d626af4c5aaec79ab904c618ac22c6a379926a5580f20a9700aaf8a4646672a54a0d8e98b0608fec76726181531da34343b79d5c239657c61f706fd14928ca1921e663fc7f49729d5ca552f10b38e5a6cde2a93dee269c3c2eb0415079f3b9f8d74ce3b63a3ce312c429a9005c2a0a5eeaa6af2bb6dc22cd2013771e5d59f784c1e58fdaba6a08a5d3729ed3d3df11164026f3539b092813c808f58fae92427fc8207facd7fc74c214e9be411e85d952c9dcd4a590c3afa98f1693192c01bced73923f6397e339bb363a35249ad4ba953110ca2fd84fb552561320edf5cbcd0af89718d0b53024779510f8a97a40201de62a5e77ccfc3d740d8da941d83d50e302bc6ce48906193ecfacc72ae6eecf812a4c8a519b3e2465b8a9a4005e289c70b9222cafba0c07fb25d92177013951e4785869f9e5d09f9a62c6332d9ade0dcaefaf321f732f1a859e90ea52c759303a5e5d7d8e8992526f51e2a27f6d1b698c140457e07bf6a5d5f59bdd4dad759c454be0605a9c284757b128659273be6076e5be95424c8587e2c1fb5468fc57840e35e64cd4e26b0563074e1df4ce75b0bbff6dda037eb68055c4e7824539ad95a7a29365b0c95570879d6d674a75232769d67f9b85eaf7146021cba0a003e9f8f717ba3c139e46b1d78112b7bc38f2e2d2a116878c45bfd4fb8f8f08a04e8c95fee6a2cf3e7896716f71b53066167cbe35bec6b5997d3f9c73fb56f15884c5856e243e21ba428be66229cbae109c3d6f481d9c235364dc8acc5d3c4452c8aaab7ef51833d2364aa1c9a9091f2f369ffd50a8450beb79606f5648ec03f820890d66c5f134f8d31ca51e79452e11a2e7f13c027dd6c6c52d4b9f437ccc1174d5a5263d372bd1f7c0c9579f81253331c91a41afc0e01d07df4e6d6317ba7058328fc1aba14238d57528e644e51084e3a6c9c98d702052d0b0113cff1ad150446919ea02e635cbe7663c4530bed97c9a430ecf2f2c23dd3a78dbe98537f48d37c2be1d2c443db6685df049b1588d1ba86456aa796c61416739fed66cc35a8ea9170489402bd80c1335656564fdc2812e18d83534aba1e10aca2442a8e039b6d52e991bb8d3ee838fea127be6a3bc925a2c46cc8dc80e8bc7508359b9fe8360b59b3045bac727bf4466503d724c46c70b04a4dc786215320e120d718073009538a681844c0d7afbf108174f7c2ecd6d9d50fdd4717331d3cfe101864965471acfb2c94cc9b7e1c514337f5696ad8a40cb297ec9a7c9e8f01dee04d38aab6a3826cf314299471ef21ac81c1d8ed92625d08cd263487dfac694d4223954c088561e244510e0108fe933b5253d3f402d810908ed9808557c93320a51178750a7b15c9b00804e328583ff90416a10eaa6f719a04c11a159507fe1a8c497dbb29fe23ef3e46c1cb9a658fc337c0df32c084902600c1500925bfaf93b4f51701142856fdd564eadd11e4d1f141f9a44ef98911e4b0a6752b07adf718a197e6262f27536d1078ee247b8e68ae892379cd40b6ade53aa5784bcfac088f48c0f44ea874f1a7d8d40429156e63068a575a0ce6422a4490aacf08bb3b637bbb540da9d8c0b30682025d2e597e8569c8575eb0ed62cf4bbd4dbda8f11a44908be2fa554f65bc6674d913a9fb088b636f0b8dd7112b4608e453d89d0d08814b80a9f3a2b65cc4a1193a05564b6f6d6acbcb5776c7ea1db5b0935f8158c02c116fa461cfe97e3ce98145ee281c1ebc41c0353846482230c05879afcf65fb5fb7a83ceb31ebb4f801d0f855cd9812f3ebf6cee7c59a3a3165891b8871ced779cb247008dbf93f16213cae38a67f57ed3ad5c6f5eabf79bceea7de35abdfa065a448e0adc5c624bc0562fc171772a8620c1166718d9921d34229d3264e17a15324070d20a7f5f52ced0a50cb6389606678ef7ea0c0af542a0299d4270c63d8a9b88f76ade0c9ad30dcffc0e01d93c36abcaa679414a2990b2cc1d988b496d02c58c6b29341756d344bfea7bc5288dac64b14bfc83f52bc6fb75dc28bf5aee41ef8d4adecdb9649df82706c4cf3207550411034bf9835e3d68b6b29fb83666f782f3f38f50d259e717d13318ab5f64342af530f6abcd8888aa0e04ff5536dce8ea1ab5aea20bd1b01440c3e2fcbf73a4ebaeaf7ebb24447ea0bde724a175d51c27f81538b1f7eeb86e4a4031633c199cc93493c62931d3d4cd1cc968c252a29b94706c13e40495399f4d4e6a71f32ddfa03e8d9a10ec3219a76032f03677b0d9659b76a2a295830c0f487720b54ffc84c050f5d29d753c1adfcaddff8d84cc41b8a5a0f674966660a262b75482ecefb7e6deac2b8248a44d537077c1780acf34f94a11a0ce33a1aec02dc0a9c6c4265c2befb5d964f5526ad4ac745050278e9585f4b65b35987dc38556e8a85533139cac82d26ac3f344c90365ae48336168a0271fbea5f65c066401cdbb0d6a578df4a3e7c66e0ca93b282ebced72a253c2a5c3e965926f011a401c5a73feb23d10e957049cf7f8f7a28416bb91a10d08cd6b98b782d3c6e54c30d47dbc228c7a65b58f4db1a052463a65ba55d91e3c4488e84831794cafff056a184a15abb5045814de0167e8f25baa3b9e08aa6c2fe0f8fe51dec9c29f9a103417311efa474f95b2bb11529ab619d8affa06b23c3a7bfe40ca5710b1f28f70371608b54e46c001d1777510400f0660e7a86c9218747b907d0ecf31c18e9538140bbefc2b6cb80c7ca9eba361ec1799bb2343847c71fbc462b969d5e8601a6507c697a8c34504a674bfd8c3f2dbd7f4d2c25e63981118e5a9b5148cab4c0dba60ac527272451725796714f28ba29dd1e87494805039310179b5a6d28f2387c63ea876fe0f30a22bca682d2b38a61f4963314367c0933246fe0dd20b0ea2fa4980ddf7dff60224000394712827635052c6818aa377a90f94f176a72b99af0db2e42345eaea33a05383ba35e07a60a2ec9e8d42f53fae7d71295b3d304ea81b2ca6bd3555ad3e9a41041023b439ce1306832eeb516114821740433ffac2087587e441d1620e7304a8c98d16ec574b36a8ca9facbe005746fb15a20cf64db3b8baf99bfdfcc5e288f168c7440687b7d016c47f31b43a34acffb8b6cedb3fcacf3ee8f7daf9120da0110d38c9bd7b7949228f80f10faadf0b739db80b448c972b71c6c8c6ee70688a2271e103b59e7d26319becd1d4beb3d3fa4d53b2dbdc7e89df1aca6d6df804b61fe4600187511680842d2093ccfdc55f0d6dbf0306169b1a8e05a5494cf87b75a79a3c1eb4af3a20ef5b481ab899ed939dd981d2b634c5dca8b575b46e729964a36e44f6f2021d45dfd74f98e0d377c936d18e7d049f1034304b95ff2f4a3386bf8cb5caf1eb5407a289143cea9119cd910cb80b352143ac74e51010f32c43a9405be971207e8d5ea5d90a4def7c639164f6a08113782c9765ad45bd2529640ee440a0483e47bb7632e27fbadf54d647166185087f853a8e85ac59a89c162ab82d54592e54e84255ea85fa4456876f9b4eabbf4dc2d65aa7973de39c24758b35b6d2955655a2d8f009344608c6166d8db37150cbbfe6564678ad9d9167497c41eb65a5f505a7514d4976d9cddc2b84d1e2389a052e6714ba49e2911493d04faaf6c7e2f0fc6b8a21928b1e11e160ed61ba99fe6bb13222c4b775c9f6455ff56309483953c06fc773f24e342a4322754b2d8ba12587979048c5bb816e93cc14bbeb20e2d283cec318a228319a3dc377bccbe03a501d1ee7a262b101da978a0d429ec75fd28e8680ca4e072c6e2ca39c86fa2a2e56ee8679a83341fc3a57bb5fe3b792574f7d3acc85a58ad063cb6ddefacd40816e0692f38ab55ffa095034afa139fd9aac0185e901e18b4090cbd2cd11b78042930b8b87a7dbc6e15f19e086a6afaac4a8f972043e2bfc1ea938d4ef54e744e6a68b0e1021dcbded9ccf16d60023c0ec6339d8dc5b2465ad39ab64c98598640de5236d2fbad0a0a37590f40bec5289e8a6f0f9e3790b689e689cee33756e5a402fa922501b5dd50259a6a47b51c81460f8285bdd8f4eac4387c646a23fa7f001b5de99062dd06f91b40ce51a961c8d013f95cd7dbcc4df9b9386f3a34ac93a9bd541be1b5c6984cb03c93c6a89df540380abbf0295826eaa5d75578e324f56033c1ccfb42d55671b464d1894150b11d9049d84face17f1f95d9fd51da25c535a144f9d34ebccb8ba53f85c9f3e21026184fa6107c38afb059308e2bfb58d82ea1075bb130d4b9592c4a666da1e77c681785dee78820110590a4a7d28714af3e28687feee51d66430c91321586ba880b4de7a424046e7553b4d0a508654e0ab390432ac8ea61adf80498b1a75b590e47e9b91d519897addffe55fb848538e6b86981b17c5c0de8953828b27e3001051f5ec9876d3bf2445abd520dff466d46eeb53c51b715aba61d31b6f6952434d7d4a4615cd6dee56f51647a7a227d54984ff7a0307c1b5fe66ea05fb94c028b55355723d54bf88e6ab76fa451f1cac0cdb8157a8986adc86ba60ad9c980c4d3dd841448ab489ffdb857a13f5aba98f257c26a2834d6273c6c9dff4d672fdd447a66dbbe7f1f6875892046c552b1484a4b79086b26bc4b275182f7b4af0342fd7ad09c5382592f3a71c0fa587392d06c8b0ec706f59521c465bad305813ab4709ad1b50a76280d886dc42b2a1dbf25a3a96801d8f87094b29fcfa1cbc3fc29e064c8f297b6edf45c7771e81efa3ee5baa545e6ad2df4497940e161b8e84789e423d4f7b1383ca7972719664a242090113a3e655105f88c63b23c95442aadcabf125c37c308e65f50e6dd0983aa521bcfe520216947e040ead640cf4f74e1b36d43b92dd7d7c54d724bf6be03267bee39047a34d565d839ef7d1ca4e3d316f455b2fc37c314d98c4422499d9ae44b83a162bdec1cd74af6b57737278624234bf0b6fa401866d41cfdb2a04ac79013bc4c1543a42c2dc8f5d0b9750b2d9aa31b7bc2cd1b5b36bec5d83fc43a7d8180cca436c02239fce09828e26dc57840b8ac7f8b11374f10bf8e372aa47ff5b98808e4406ccd51962f50215ef69c4c9faeba627f466ee847dbafa206d5ba4be65dc3c4d5033680bc5033e8b5a8e50b4b6d4e14fa681b9f6bd904e249d1b976b68ca7d85ce1a9a28e79cdf540f2ade4572e76242a778aab564bfe4c2f6b46c5f96e01c78f8a9c0d1a5f2158f45e778aa0f777f6a5de4be39f4981848879e3972a92dc7630cfd1a77f81a272549fefbe0b37d0ef2e243887f81b7852a79e5e697683956c8e1a946c8efcc982144d0b773a436056a2400f751d0f90ca16677f75f3edc328543ba3fcc1b8f4fc6cbff0edbcc9076e9b8d76d6cc7cc9c3ee2b6d495963a53e840f37ec065e37b27c6ec5a6d7d9d3c1182d5aabea8118413c97527b1a535b67332b092f9b42873955da61e19aec2890f70714d4af3beb345793d58ce498305800cc72b80481098c4710e16e3f4c170aaf954792322a4f8dd9b806cbeae5c43d75c8d59463b4f305bb8e4ca48c78d4ae91b166344b5963cb885d67f694d86c4572c844f02495bf1f904c4cc8906ef90c7f5ad8e13746011314d3884787b9258a2b7219926e5e1183f99a315409246f9f02bcab85fe31db59555587ca2e98887ffea0c80a4727b0488321d0ffe6f44eed2aa4358b224172923821f93aa8842aa668740ae12389ee8476b971384b316e0dfe9016122d240c0dac34864485ad45c5b4a818770238a91b6b0ac873b20941aebc153a8fb2b165b49d08a7967efb6dca7dac2c5e06b27f2709013dbaf07518aa3d905d2d0ba239374c75f5a63aa1a62b110cc4b4c40e50481ab9984a6caef63d4b82ac55ddb6dd012271144cf2a1bbd1861821d429d54957433c0fd6485afc08f582be8fa36dc28daafc18be1ce94b1df06f924d0c66f30ce7864a94c2459d04ba35c3218148293de1e31cdbef3aa56c3fc084542dd0eda828c7fa3009c3a9cf38d7b0ab0f86409edceefb1e99ad5a30591af98efc1eae92be8b0bc9542c84a01995fdc11f9bdde35a3cea2edc3e01db9c2722011acba7aca22cec02038519f01f917a332a0d60a1f78741eba4093acd20c8ec80044a567b5203615ca9a4a762c7a0cfd3b30f0667bab213202c019ccccb8c1f065b3fedb4de92abfd2a9b8664e015000f79f0b6fb7676f857da0f58530d48403671cb2b2d39c9dfe79ff66ba07a9266a76b0f4de766fb8697883a2e5411a98492cb96834acb17104b497872053efe0d4775e1d240131d87fd5225a3e12447d111810607caea7e3e7a856f1148b1986cdf15748a216568fbc730f2ba21b4a6af6680a68c761f183aecde7b6e798c35e64d4b1f52e0e6b5f19c42593c81847787498ff53ee90e7f7148b5d395572cb42d0d6ae63968dddbda6d8c31937436a0022b292aa9050899b5a80efc33e23f5fd2011e96f28d136a0fa8ea5fcc934921ced468fc60c2c0f2c0866577b7c59757348052253cc7d0829fb511b9f89a7b5728f571d67bc37cd00379c6e55abbee81503c44e600db401e1fcc39338fa574a0bfd0f7fcc1304ad296a836ce571649d135c0d91f38b1befaa655ef0b57ded4e589e7672fc03abd75fcd51a2c2f4962ddc155f1c257fc2e9a55a05f561774e71b30d60eda3f0f5360d649e2e83fdeb6916347bb617eb4838a08d0de83a1400eed347f43ea2aa7ca58fd39099e83a6a122ee41794721241ed4ab1cc7b4c1cfc28cd469a0b8bac0577961da4eedc3167a6a5617b77c99cd499631f66f9f1fc6e0f4b76174a0c7533fad3dc58f71d67370318a11e0821a1ba0d51d17be575a9ed440fd4b395301bff43b044d5953a15e8a1f4c2eb8c2e2b9448c0bb7a6ca08170029b203062879c84bef8bdb43c76f554791462a807bb2b2aaa4f479c2cebd1b263785ddfaf760a7a4e4c003397492d41db5b9ff67dd36875f464d9766666659e2c98da089fa9ac3e1c4c0f3542b830c8d1826291f5e5f8a15b2ed95a574a798460e765b2def2654882ab6483221c5a1268976c1eab7d6d35936cfb555f357e26590fa83b22bf87d6d47a775764b90dac9a5e71b64dd10410f2abae58a17a3785690898f1e2231cab7b95d5a3a8b809de0c6c22e864b73bbefa1f9a0cd494268ed2e0baebb549dd31006b7827b1bf9f498128c4fd5632c8132e9e85d3b0f1bc3f69cb9f8e661a68b15b56d69ede8c1f3a290b515362a1654d44ed382905e24e74cb1bc3aa893d35c3d8ad3941199167ca857547d4943e2f85312a32af2a0064125b99b6c865e42c13b7e05756ca2c2d41f353dc95f3c82bd5713ea5d05d86639043d3471bdecb0a831ce534ad4b3d7364891035fc2533c5e4849db9c08877fe28686644df6517ae81a3e32501ff8761d1ed6ee5a5afa3688b3253fda82f048f04d89cff7da309358c2918cd23fa273d4d649e65bb9e65e601bc313e5fcc23063bd907544e0a8d94ae02df1cac9106ba62e1e95734988aada9f55de4b257eccd6106f10e1833eaa1778fe327e8913e994fa2d7dd1ea0e348241f2d205c38ae67dd8ffccd4d438d2c235fe84ece28b80964e7b35720131882e8d9126f8f036315c4cc0b00b4a00455074ca61f402f64cd3f6b65d55e2e353bc4c1f36f327e0b8f2373739d00024def7805e21aa4a0fc366a667b14502660c844a5b4facbbf06041524a074eaa784fdc0a19c736e5f5c0208c3793120df347e1f3e0537cceca74101383967a877c0eb5b5fb7d8d2a14d68d2e5fa66f62782b0b429d4d83e318cdcbd16a2cb75379a7d1444996c0e239ebe264fe03b851536bb788b5cf3381289de0c3aeef6a77dce82c08825610593d62c026056610a2b0183f82a5a658375cf1ca0229b30e750dae4d253648dac9a45f1840548deb594874580e81515552ec91f27a072dd6afa5959bd21b9dbfb15a744d3fcb8e91b1dd3129f1a1244e20a2417801bbc7ff777057319fb4f97b935b56fe7ed640e79008380a0ccc45eeda420333be3ddf5475d37373c09273fca51496f16822980553395028cf1038c19350dd3a634b97312dc34bb1a21232aefb076285c52851ef5cbfaa44c80a926d5372c59eb0ad05d4e9e87a118d22b9c85fc366da09d347820fb025dd10c1a1e7f15d53b415a39ebb95ed57a589a2ef6a7f3b65db7983083117cdc784efcdadc3e772d661f0b91ad7d29f44a1ed97220297aee38501c010299826a55738f2a809f6cd1dba3f58b82d3671d040fc55b9421d9fce1451cbe9e1f2dd35c58ad1137a3bab3514ad4548bd36cd6f62404fa5a607c5df2eaac2f2011a4b54ee841d14da68735acceaa4019a2ef0c79a361aaabd3a588c36e9ba1a91e436e124b5bba5c30a3d8f4e5bdb4c89c3f58b1c659504ecdd392aa85e384ccab4b24ecedaa81319d394bde15bbdaeaed8e970dd0e9291a54e55a3c3a1250f26e9859ae178822db16d66f06f371cce9fb529f059731297ccf055b9fd7e377f3315cdd817a5f6bd3011bf1d668db8afd300e27a214b44b00b0923f660d92d67e2010a69e8310e433b049901c8c5d4b1294139ea8864b230a5b8994dd783127b96696e36c8e59db9e408952475176c9bed567bd40c174ea5c71c120d31da67b7302497d4e04278c058672b75e287fd0ca3f14284201902cddecfe6a1fd60c395e21d3383466aa0bbf0db657bfd5e76576a3d1b76b3d6feca5a1e5201d44ce72dd028c0c5a1e65db16da90e47f9ac05c3e87f0e609197a42256b7004ad10b01526a8ffb03714c4fb35e2ab4ee8251d387224493a9e6ee45b5c5b52330202625f056df65401502bf309f16569f4d256f597b3483ecfa8060c63706e1a40a894064ac38f7f90383c54e37c7cec80b50618c862606bde7d5005f78803766b8369b7ba8b2a3d771f18e28e2568144dd3ac579952fe36ccc55391660dcd90aae7531f492bdb12fffb05d94b6731230256c8b705b7d078ac54201bebd34e9a2442dbe01417dadc43492c05200577f88eed43559771ac72bfcb360e02c67aca52f40e476c11867633a167b4374581606d424d9d6e3738f4ad56c1e2c1acf86e458dfa6b8e98a58567f565dc7a03b6b383261934e664f8b3c106a447ac46f59222165e86cbe885425da08db1567fde61e5a1fe4fde6739ab5b7263449eb2226de31222fdfb9292eed64e09685922eeb5d9e07b0f5455198e00fc73cb8b84f903605339aea2d3327f72f234b51b36d538a43965cf99e19bc6e6d5f77973f0d50b8dc9155977aab6185ea7744b37ec009c6b7444e6da92b3763fe7b380920b0fd76299dae3e56617cc5727cdcb6869d4d4014d6e2deca011a179dbcaf4b6f33bad37345e4db66dea30187a6f2c4f740c45a0cc54187f836f92647b10181392b910c292758f24125a770eef307d84c80c5884c31879e78467b3ab92e3c80f479881aa1fb56a8c0d941d4f271d174131bf88d8f20bca741d3807054c3c2f56ee0939b8bf7b37f5fc1adb5a16409bcae4b29cb3e6c07e84456b505893d10ba935f8c376289141fe6660b3061ed52788b9959ac9ff7cc3ae292319f65dcf8269a1638532ccaba9daa84cb3c1e4248bd602feaf513255d963883741806929baa9b35f0203137264d45cb4a7682537924d25c45f4d6f55d98a8e9617501c1541a3950be569685f0223cec8ea3773407efa5a8d9971016258bab401f2b06cc7b79cf06833c5ae510941662c3f8398fc6b8ba76ee1042ee4edaf383100781457d4ab2f70a9d8a43a1f3092f504d4e2836a1fe9c5fb6eb86c40650f037db7a783819adfb833fb6b7ee4d8ca6ca887f54582fa6c6382d25c957a065f199db63483690802e9f4e60b11ae8150f3e630f13b1b5375e32db4df24af4900e87f4dae13822da7f78e7a3a83696e7b868aaf2884c0216351069dfd4bef9617883487d468296ad555285a354df279fd5549b82832c1fec8b511be1ce90353122e7c3ca5995d5ced2419d06e5436bd43672967494cbfd3280e59f8cb36a7e8d4f73433c87efff10144b394484b316df19764916a1ccdc1c1ae3f059685c864f2f744c62dcb92b1e356f88b3730dbe292c7fb24fd3ec87b89d697727f06c9900ac982184271a9487efd16caa93237107d2bb849d1a5b91a031fc004499c5fa9cc8de898ec2503c000c36a80100c2ee280715fc754e47db9b3106c3900e0de1a017e239b01989c12a922d474aade8c1c70cea148d9587aff3757f552eb65b59a2d011d664a6760477787fec6f9f9aea11bc8bdf464a11833097f832c46888accd952213f1a6fd7e714cb591e673b5b5298fddf9ea0153c29f13935f285bb1fd46c4c317cf87d125e852c105b90116990c4eb2ea240f8b08dfd9482c6bb9292a2a69edc22fdbd6599d3a4c7e7fa3c1ff0a00c7910b4693d51326e61ec946be68315294dbeb0641a2d09b3605d1969508e209d232e6353282f21d20f63ce3b6d0918d333d74107808864965729e6e0108ea031220fe7e8ddfcded47ad1cf5bb1e3298daaea49d14c30505b6a12667a2067299bf9f9dc88385d13b93f57602c3ebb52e4fa39cf6e2cd8b2a8ed51636b55024c98dba3c6eb26ca1f8605f706488874b7edf91beb0c3684f4f3f8eb94a5c4627479580edcedf4570ebd3b15b5153d6e8703dd3674f5dc6041abf861cfa7fb41cf91671a586f17945b109912aa49bcbc46c1ed96305b2c34b6b8ea1938ff9563e781f47e4581fc58bb0ead8c0f25f033a3e58a71fc067776f69f48b915a37ad45c9db3e2c5b6ddd05f300bb9d0a733874b1b5177b2c30282888c94dac659b10c5891d2b801932c135f56054b3ece596cc70ebfcc2d50385652018cf285e8a8a06eca8b955172b260d44f115d1e7181b529b806c14c821cee2fed528f3aefe5dbf5368a2f9770d8634fdb92634ea4df6fad11ffbc7e993d5bdc5aef566367ef6e5509df1c68a7d4abddf25703aa1132fd8b52ebcf092fd2e22307539d86afceceed2185bde4cc192147227cdb31d72f662106c067d713b463ad848a4612da6ee52cb48b68fe764e56f3a78e0841ed76d1fda4235b4206ef5d043b7c5be328b2dd32ee990c321c1991a19f62eeed3cfc6033d1fb3583056b1dd299a9f3836e1f5a94022db283c641687e2c883695b6cf410c48a1ab01b9f43a3c35283004d23c403d1174b2b0b14273d2426dcaf98509a60cc579edcdca763fcc4351f468efd2af1f278cf66340463bff77e31a29ca941ceda44b92fbded3dbd64e13d0182e1746a3722d84f77d493c174fbe4f540e0742f1df8e15006842e640c2e193416e0f4c242a95d55fb73e6e34a8c1596a49af917fdec01b9f1fbb492069dd8db601bf01bf0c90093e4b8b172780ecacc7f9e1bc14c0c601ad92dfa420a7f8b1b93f138f5906ba86a996c3b02c40a50d369de3c1050677391921c49d8a1d672237ba4c9bb9a2f4303dd478f0b9b1c362b701b3dd3aa68553728b2d121142fdd1e0f97163860e9a11accfe590b43f5f0cc9e16d5624b58bac21804fc316f7d0dd78c06315e9e48bb3686e619df2709bf6b76b79e95a0b3fd604bafddb37ff942bd2b0b78c363cdc34b4904307a87234f81be56b748b03e8dce1042529353c98a65cea3a828a20cb4ada0facccceeea8875bd03d8808863b5c41b42731892ae313fc12ea99eff2cba1f0465e0dbe9fa54fbeeb8e30d69985882df3f16f753ae72dcf11fb475877b7ab30c6552b45562d4224875c22ddf68c113f9e2b74090766d5ac3e5c87124ffb5340817d417ed3e7872a8579fea415eecbe3edf6e6a512d00d635d333c0317a65cf0a60c903a2277c0046219b04f45381700e98b0b0834c4a20e2160b2b5432e3aa9aad003f32b3c6c4e2a469f5011382a90b53ab5349458a7baa8e2ad0dd7b4aeeb68361024ccaf8f1ddd8ce4c346125e826cc5e62ad554cc435b98adfcc444cbe48c229d3c56067410f9b5fd367fcf371c70e65b240d1584b2a81a5c00c140ae84cde4c061bb6890290f65b722c4526193a2cdfb6455c42acf0ba8f08634641bf0599f807d73120ac546fb81657b42083c77eba098e76fa3cb805b6c5a8b9feb4c36dd7809c53239e2235c1279c81a31816d632d496b4d7ff15a64aaff33665556a4ef37bb0f4631e0db0438e03654743162d1cbe0a28f87053e5c8350a5b5037dacfb10c11fd5681e4faf2177e2e42c7d0d5f0a3e37e70d908ae01a65371d313bb0329adf03e28634c0b95929bc186f318234c2e3cdc42a1f9e9bc1b22012882bb58036d3405ff20290ec696638b94131242c79ab0405cd07b60977cd294ac51bd522d574dba5106535308d4d21a8dcef5b06db8bd70ee49b21faec706cb4f5e1019784b306ecd7f6ce3d8b2de1fed3cf3482be3cc367753c255eace856f31a6fcd49a0d4f4dae42a2c991595a7bb5fc63e306a82dc9713adc6469eef85412e2c0e7c377b2f40f581728174337b69f723651a17c0098e71516a9afbbb97f00bb600b98da8d4cf1fd632da7e5ec404d7980c9864d6ac99be756643c6dc1b2e562909bb25311a4a1467a541c29207bb11911b2d1a7a7aba693dc4ba36c6c1e0aba705c3d702e2fab6ac9bb6fa85fadc63dd179d80b0a9f5f5065498815ced5838a148d7e582a83b03e5c84ba790708173441797e64d028e2dcf746be419133b90add304424ad611c246420c72d271dee61a71d96671398aa190a05ed48766b6fa33b1d690e4bf59c34dd4bb36ab53908e932086d360d94d2f89a464fd5abc1f292c9e90a5ff09452117501466f67f1b0a3b33e9a08df165ab8db33da24450054ae3a0a18d827533865694f5fcb7399eb4806368a7ec0b8b735779abf09865e56f8288af8f781e0235c2726226c7c5abdc90e847c74a54e59c967a65f4194c2c7aa7e68c4abf44080fd0d824c9454fc6cb592359a89b2c83433809277c56a01ef560bf64dc10336aeaede376bd5af40acd9b863d1d936a1c5558b8c5e9e35bc7011f0a64e2f9a16b28cb0c6b049c13cf6037046964216b249ac9d272faff3896a5b38a3664dd28be5de35c4683dc1a9d9a545dc926a5844fb11eb025dfc4b37ef768260268adec8fa95f1e4554daea3b0fbef727e061dcfce51ebf1f9ba745e2b5b4222bc36d6de6a09beab3f8826b8bc3db8b2844868acb1248252292081e5e4c1b7a956f945e55a40c44ba1440ef970722c6b50fa77a3099682a83482b0941338da02fb8daaee8f0d6442d41e5a0d7ce3c288ca418478264f8115e5e71fa6967064a879220a43806cff42e9512c1d65d6684e0cf07b5ed83b0be9c028253c35eb5cb1d60facfb6533c46e66ff31b91f952e28112b4efd99b964b3f4cda53857294ee88a8da29dbf8947e0bc881f68061aa8046284af26efbd5997876507b0785b9fe90bd02f263710cab6c245a8f170990a1379142c122f6aa1fc76508257e19c3b3fa85467051e6e3c5c63b42ad2cafc8457af34116f6d9f71225f4b14a1056922fa0569ea56c2b8e6254cf3cfdad854299961ad33d82f013ad179f2585f5dcf725eeb758fbca472dc2337c7fa3b05417f52acf57e6667b94f9fd3aed7b09656ae5ec1ad43af930c34e4ff25567d3ea6fe7fbff3142338ed443ff82c782e20d4a5de3e29512868d40e9ed76255cef5937c4b61270f6ad467d33be64d2e9ac9aa37fff655608d1f3a74b60a9357a9c28fc7d447d2628422b7e48ec8ab5a7aef917b5eca9abb52f48beb75f417ff19217e35b66b4e8a533376fc78e3ea7f0e353493e17d13f5aad7fa788fdbc42df850a10f8e85ee8558bec46754249526542f5e0a62d1418d319b67b94686e479701b57cc6e36bcf40e3487aed90b16e953503c8d340a0446a1cc85606054c82e8c8029e4cb832a0347319e0aabd168a6798872735c519c46a76d23153bb61f00d3b994404993010d636ba5f9e70e7e7dc6aba7f0095ad48253201658090fc7129fbc26cd581f2b9fcd9c92f74adf6e0acd6af7e9102760d4506237057f5c9b338daca81e094030de19d561c492989fbcfd873bdbc8fe4d749b0fd419ec4ccecba35fc51b4c411e095a1bca21f0c046ae4becf8e534ba2ab5623fdc34579daadaea5fb759ac84bc3ed26eabfe23cbf520e8a55860375b2b0e083aa16df2076891d2091cc8afb0939bed4957a5f502964fbe29207e8fb71e7b302f230c152e3692edeeaa8f9a54b9371d4a7e51329d0d47e064c1e42ad8eac309791af4af55030a4acf87672a00b9814afa19c0631af86a13cb30153fd118d309f2666c47d2466d5e61bd3cbdff416417583277361f7eaa1678d5b9d00c461be1db3541b18024f20192efbfb64289128a994968e030f73df5808185082e01f8636bafbd4fa3a17779c37f75d0d00f285245224bb3d0c8fae3527ab25c001da1219f6112d25ea33874f9d38340b2b4e04e26012c15c6b3cad144eaa5ace620703a7ca353523b1221659affcfd1c8ffd166721c49390f1fcf95995975bde33cf61f61be19d02b23c29bfef2cd583305baa2e3b0ce58b50f70d76d7155367e60a339112d31f2c13a013e2eeb73d03e2e9e5443117570d4c731dd5f9683d9420203d341dd03e4bfdbb3aec08aa1285b87076c781ac384e320d3073855587acfaff3d3a36ca86886ab085ed5ebfca6730427d945b140d54b59a635fdc0ccb55362f294c56d06e2ff362c5a8a61512d242bd41d081b3938ef96abfb6ca527a294070a2a71e38280306c9a6cc602ece4f68fc6f304222119ddfb8217891a30c0a855b1d81030e3cecaa9f79811691565f9953d2fe6efc8a65626ec5994cc1403c0ad6f6b51e7eaaed543fbee0135a04996074aa6ae6a6df419782f64b646228dc097e953bd8ab3ee19acb2d0505208095ba8913bf1aa765da839ac17b105252fdac531174bb6e9ec26f334fa8a50553a4025385ecaa6515edec8091bdb8198d0724240281e82ada8871533327d563b5e397340e485210a00d8dc57df84d09332b87d03ee24db295cf2dc6c5f0cb09cf26e6f453548042e80a1e85f77a8e48ce70c84d067daf5d9a459992e6b46a9402f245abb05f002be2b70fd44a79d5090d4c281512fc132cfed754724024c49af2579a5771766fd3d50e1d9a6fffe5157df7d33c88d1a99e9334db9441af298669cd46d116534d5f0e89c326b918ec32119e686b99aa880f63279bd813a1f134dd7ca4e32f716f1ad479e39de953da3f937c326b107cfad9af8293937dde593e73084450b7269fa5ea5a6142d50357417eff649ff7fe30d2fd0a8d4fcc4053a779f6e3bf3fb4a3b35ec20184969a03e10d513d4aa4505ecf6a5ad8f2f3eb51dea20a1d01be3583e9597b06094b22a5490f7cc15c959140bd18331c269e8ccce71bc25e0ac3cdc31783603afa69518876237ab8d5e333d122e36379f0c2e719682899d402e6ad93a13122d211b0a313467f72cae60c63415d4a0bfa87da4a57d201ef8e919d5dc7ffb03e2506819cfd5d9119600af0048b34e3f9974e084853813b52ab91d7b8582a1690fdfc522419a33b8b37cd4c4ed0d92627320108dd70a7f6c890961aea82d33a215f7c36472cc2fffda86ee15cf3255abc2f4306205290e999e6326dedc1a45855104ca5ab98a47a722979faf69a1cee86405e7ef7cab790a9e59a7ed7a2ed1421eb5e29b114a7b3a6a0b54104c10dc74e042da942b3db2dd16e244c4b48e45e08cec1981b4adc40254e10f79a7733ebe01d675ebfadc3ee035a1a48a3180c9908f13c0b690edecf659729528ab4699a1d2646385fce27cff922160ba1d8474b0f0193b9f079ccbd73d16890f83b43e4c9631a33ef26ed246da045fe6871f8a3df82c1a611e87a574d325c6f6a82eb7f675cd5daefef8b2722e1b4a973b1d61b77e389b455e3971cbec645d8508d2e83ba6e917b6a4b0ef926675796a5d43fc9b3c05ee60e181368d945871faf5c0d9226c34448919405ff55ce10da4c72328a363080be8179a85e48221cb323195dca7a3c943dd9d17b3437ad48c4a1fdc79c9cdc2c498f29ebd57d3794b7da66cfc453796fb7b8f766f6fc030fcd9a1fd88bfc756f424e63cb007c01ccba462cd9f77f4cc359e2d833720f586b689b8104e94d3f21be0fe70cc1837137c043f805cd7de17928dc9394b6cee51101fde896d3257d99260afb3cc3db770242122627117d4a72c004a5b001780a0ada5ee309de6c4f2d37b64000ac24ab9ecc8df49577b241482eb332031253c0473b0121e5326972c44616cdb5436f79802f3b0607ddcef6809e13e0084788bfe0ce38bf148b42601f65e60f68b5c542e311b2e78d04e7ddbd0147567762d0cad656431994573649c36c42f22adf9affff911918d484058147b1c0b3cc6b74fa4caa81291bf7de1543d9ff133d4177ff045e5cecc455a4ac29cb75279b92a2fb816f8ed52503948e994b8ecd90ce53efaea5702270d29ec0a3e60b40a49c89f3326c93de11ca53e00973541ecd35ef47c63dd5b567ecbb04a08cf343d959192d7e5d3b8b680758d51b92249373109e9de2f00e06b3073e37a225d4a428d002379f86abfd60d0007347267ec11583b6a0443cdad021a2453877bcd989da5d623930418917c11b56df9fc0560159dbec313804133ea812940d5117ba4228ee106b340396743f7da72b37501f93b78572fff2d600ddcc4aeb4ec821ac5536fe31253113f918801132387c0c7c2d19db59a53cd1061855c3825b4502e5d4185acefb2212fc8cbdee898c59c017c9dd3ee07843ad8d0400e509565c817f3b71c3fd57e2795983286a292ea75c27d4d4f8309e4abcc06c8f78dd640b775b205a0d78ace853c0a01f44cca6817db76203a0b8cd4c0248d5ccb6f63ade3576321b08a739e36fbcbd8e1a328bdd63e02ecf4308f3b89e34f082756bf27bdc91f7fd08ee1d3c791787212156cc4fb3b3da0f44dad632fbf29f8baab082593c7a9d9035887f04ef2eabd14507fa3367df365059ff70780a39083631d247ad713c025cd1ef2f4063a0a5bc48c7c8ee66ceb8808190dbc1963664d71b6378a3830b3a140c0213cc0e1356c02682091f6fbacf84c524d027bd2638cf555f9b5dafc74ad1c52b766aff7956cc7ccf7e6176967387b25b4b397728fb0e62578b72ee58f60ee5dcf9ccd35ad69d5a462bc6ca6d261271938fc05e8bac635bdc604edc374a0c23986ebf142defce6793d632ee54ee9d67ad1665df99fc9dcab9c359d5a28c3b473936ed18ddd8c04de2185acf98aa347b6cd7401186ec5fbe2d5556e08ac618052c1731780a8f95014955eecc29a4f77db61188105198611016554a075bdc57f2ed1c889c696e8e126fcf8a2b65957535657c8f6901bd9a71d59462278d1796baf412c73bc9d6a59f6c4892f5f1367585f7911b9198e585c330af30b610dd1b00c3c5a615e360196c42a03ffe377b4dea7594c778aadde76494b9151dee18b57c43f457982bfcec3af3c08b1a0bce6e8d3610a749685a67e05e07c60e88508acce7f7e62a32b705b2fbe7833cbac5bf6abcfca43cad87980c9d2895ee9be9ff9993b2d9d86ea570d916102a39255e69f6dcca7fae5d23e5b47bd93356fe323cd515c99fcb1177700db97254b3908e0febe1ee38c2870f77b772b8dee0c8905fac19cf9b98b048152cbd8d7653c7fb119dcd9fefbb4cc97dfcba79c348ca87059eeee317746a4f867dd3c49a1ebe217f0e186d89bed16176a644c82242460139133c98ce9df4209aad7f67898afbd599a2a2fa7201b2cc655cd0ce0bc31d6ba2b20c4cb1bcb16a22edf0ec87a4b998c136db72361775c6a1f0e90cdca853f996c5786802b52cd2355005c8464cda84f00ef7f9d3b94a29f1019d9e340a399d60b782fde28e7895f024e07c5d36fe46601295007a0127390d49a4696fd11bfed6d3d8907434785978401d9b91316edd8a3d8e9e681b338098b027838cf1324c457e8519d3614b0d1d6233b2c4df3fb189d2b12708617120e11b78ecad69b118f2c0f908ddfb3fee2fb21183652f324678ae9bd08721a3a8032409a6c1a0e24a62c70403862f6011579993faa7841c2e4aa27833d0cf58d113809a31d00a8a54a59c6c6cd274fe94aa143d2fb9bdee4d1aa86f56d4e91fc676ab2994afb5a24352c7f36726cc89446a548c944fe835c7b4138690a6a270d81f8a458c60c0c04fcd880f39ed33a50f5358639fac8d18d0ead7cf3320d1858f27a244626b5547bf52dd9092b260786dfc15578318d407b4c88019617dd5954585c51542900f02c6aac3727c62d176c55f0be2132d342dd1aab5efde42e18f21b3658363194daea9cd16d75f0ed09e3aeec5c20b5724c4d7940780ede321fbbb67196766f1f25c21d451772de219e2fedc66c9e8c4da42ab8e6659650648f0ab21d20e6f8952a2b01f5085db3d4cfe9b7abe7417f196f8ab281d8a2b0bc906e1786c09aec384c0ddc2dcf6ecc55b20a4bfa0d8994711d88efd0d215f69048d4b2ab8bda9869dcb90a1bf4d14fabadc68d66056e7d8bf2ae79309aa10455e0e5b1ff18671872d0a2a95c8a15333fd9003fcaf4fef5deec851bf767702e54c2ada59af2cc87506b323620284503f7046d270e7909aea324f9c497199e60fe76cdc021a9ebbe3ea593c41914f2079a1478eb70418b4facccdb282f3e75723aedf4a18416b1eb5567baab4d09cc09b2ae441a9260ec6ff87c551053aa3a7b05098ac306f1c7a07502114cb586292650bd2c6626a11df5b6e153958a78356951ce43f808b37e727a54cbb147da39297e6b894125262ec206a9ae2ca648562098bfe093dc6bad24fbfb9444c0cdeb7f1e7506fcdae321fb609e3d80bf1a15bbddfa0ee5fade06dd2d70f58229f38de99c6102077d750bda66e99c92d34658b4d6fa9095b6ca2164dd95253b76cca969adc42a65bd4d416eef65610431e2442c389902838642987985883102b4979896d165aba6cc80aaeeea6b5c02e7ae3d8b336b165edb2778d9d5d535bd8744bd949ef1e6bd6c1e4b5161d945db0de0772482388b419d21c263e6538a370db26e4abb06b2062c3f2e52ef480c4f595bddc771db53b8a9102757137cf1530657d126aa447ecb30981d92e70cad6598ad72225210e8c87618241686536266ed1a5056b5d20968af623713fb29b9152af0d0c6fb3b8368cf199eb6be6783606cc6b6c6a9f23d26e9480446f70d2ae88e8aa080db6e513ad1e151e646e81c2784f0c3adbd1ebd9a7b33627a35f4ffdfb56f1c94cf5f072e1b90147870914872d15c9b8c5a94c17203752cdcf0e8901c0932804963c00f378028639d33c59ebd146339be0b7af823cfdbb8d38df57c46b5e513478d896633305407e4c896a6cecfeedc3a25df19023b088c751b5fc9cd5bc45d835a8a77a4ed4cc9b34a229f046c432ea78ed1cc4db8499866ce3aa8f2099a62000d9f3a451558322076783d98a751e1b44ed03a0c8ce0a1730290dc4bc40bc323d5b22e06354f753d4edec4c406ee01486f172ed7a0ac48976057ebf0bda769ba36f7d73700b5a1abbcda086a8e51f7cc6d6b1b10e17620eabbbf792069fd8881f80ec3f2755cc29e0cfdee7262db854812765740b09c77756c9ebe10590ab5725e32cb7a9558c95d4bdb8d223fffda05c3a1d41baf99ee3c1322a2e15725d701cb0852ae53eaa07346d8e9c09f802265d149d114084620558d356c44684fdaea7560978d283031a0714061bfddddfa75f2f2cca77e5fda687f905d12d53979458179dec9032084663857cfc5d94ead50b07bd457403258c4eedbac802511b1d2c1d431ad0dbf65599c9f8043f6594a2b81fbf1cf3524cd2e6709a13f19c429333a42d25ff19abada451ad2e850f5cddd74108f12996b25ba545fc1cdb1f0b6e19b741f38ddb7ad27845af29b681324212ead56cd633f169f8f6a5b5704183a529bed3e1839daae99840fbcf98235a56a887cd9677714a7203bdc3147a6a83b271e6da8f7bc521972c70bc8f4bfe9b7de3d2a971219c700175d298b63e49829b919194ad5f6adc2b38d2dc98a318b9f4988fcc30440da14caf2326018a46af2ba117a4f7ad7278af77b109d61dfb474f67e29bc355cdd33e5147f0de82f5264cd0c4fc5327806ed781aaf0ecf9a16f66f07c5180be4b2d6e39927f58845ce3eff308a5cddcda4c285ba833af8288655b177ba0a24e0104fc79fcdade916b45bf11cc6be8513c09476941c63cfc42c22b0155530fbff00af36784a1116451dc1c9f11fe28592460aded951691b361b7aee5dbbcb31532f365362a8fb6a53fe5fed02088ccb647938fb5235d2573b3a0832067ee5f6ed036893fcc2c1ef440eef7a812829f2cc2da79015a1d23e407cbc6a9933442dcb72ede6a3bbca421742191350fe50d254524f084d274b1263f454cccfa670708d0e63da65d4ff3ecf954dba451496c94143ff4b1ed4009686b017e5b9d296876ca6bff62c6ff9d813b9cd1467aac1a574647b9166e224df09148f1a007938b5633c1d3504d4967de541077e1c4c629b7cfe6a27c8f1f3d315ecd426093a6d90f5571727c505f6cd71cae5cf6ecf73dc14c7e7664d81f5df884e88dc08816f461178dab5242fed90dfb551acc7885163d4c835ef7036c7770128bf31db345bc2dda1a7475cc47205d46b6ceb05df801ed84a6f99eb7dcd420c3c996fc19ac8b8c6022c099707d523f4e65810168d8bac3a88fc6fa2e8d8aac1aecbc307a9ad66d0aefd19f56fa1803b791c8ade7222329ae4f424eedd20433aa47a544d358256165475ba9f0dc24f00267d4b01616f557559cd286b4b3ad0c48ba555c160fd2d1a567bfc7cb44af3e99fb2dfb05faa777f2df3f3a09b9716f906a765d85482afc7adbc495b3a1bce65ac1a3e6e3c816555c602ceeda6fa5df9a803c764016cca082ecece7efc3b42d2a3218520b32e93882c6ff61504f559488c47f62891450ffe94500d89e701bc7427a4a92623e1c1524b8771cc1bfded191543dd5834cb4c4a8b7f1a8f9903d6699f7748deafff51b3443c0f3bfcf65fbf144b089f00c91dc16c666ee0c2eb5cb2c7fc6686182fffa37a558ab00618d4e2ac6471b3b93375f4509b8b7fee67e037cbeba78442eaf0e1995efffc244e656a3faaa808cc0139826357d193c0bac56f42a9cea8981729ada1b966668dc426b8aea33c150f9592179a2d0fc968ba75ae7bca49a5241c6615481dd5877316f7463fcb42470ab09c293150292b981923a6424b25ed561784689aad08026bba99eb159a91c498d391ec7eac41040b862d4ec4aaff7aefd4ee5a4d5267c46ddf5c7425e646c898987bd226b3ae46c80ad4f72af3524279ae4c4a82dff41eece7feae8fc5950e08833d50bd8591e2fffa80aaa21cc1dfcfef4895d42c2916708c3877dd47f6a02a3b131cedd41e57b29f17a99725697abeaf05d2feacb51b2f60f6d333fa41f052331812e9c2e9563ad410ba82c68a52a62629b8b7b7394c6e11867cbcd1d7c977f54dd9ecf6a5bea17575de48b585f59c418c88655d6bb9cc392a3f6bfe309ef9f1c0c0fba7364308ea502ca918e19807720f0c91a44736b125357526c50ee20c9553d5ad941641443684c281d58dfa5cfc4113ecae6dcd801816cc1306487f8a6dde755e8531bb1de3e42ba6e37dfeb1bf67c0198017aad051b0fe9a45ec06f31f9206c3ca14df322079f4e4b7dc744e27369c774ecf3b445236706aec11a1df8264e75ec1333934e955d459a0c1a013049f55d3e2a47fbe8025aa9e2712e263b880320f025f5c96debd2e09b7c48f91ffc738c9b9124cc504227763edcf5eb4618d31969e7bcf154249415b0d808a60fea442fbb1ac8f6546f0d2bc7156ae3a52477c1ed8bd09f852117738ef9a60af5c02cf4d99dc696be266120f4601cd7312a7da2e4ae073986bf98fa4042033d69faaba2042a002de1bcd02576f3c1984544d8110076632e005422c76c237078985cb7381114b9e8e5065ab19bbbcbea368ec713de90e180bd9c9d28c247c021c29b3df89575943993c7d8395d536dd29e88e7bfdb5a935ba44bea82f8d7042b559d434af043fb79c546744a119ca1ae1ae8947228ba055d24d03f8eb79a443545c52973b1543f27350f5c2aa2db0a60b481ec00c8e44fcdb18953a0e017014ffb476a1d10db0b0bf742767bd94300c345f3705bb1a292447ec037334e62fb39e9460a0f752ce43a80651c1a8f2b319880d439f72eca04094e70f6376ccb6e9af0a82304c7ea3bdc21b3211342e109cf7118bc90cf36e17be998da3b541697c3a63b6f423538d2474b78f43becc134bc4a82d8f904a07176ba614b6d49765bd9e9b013f4e53965a23712429024c8c493eeb4d3b2b611d24d31e96c7859d42558d6c88e2470fd1836722b84b92b945e34285638f3e243f1ec01ed2c6d273b49422143bf96f1296862bd7a8ee810ea099b9c79cfe2ff153451449190cefd00b8ef982532671ced6e32fde9ac5b4d61c23e30030019e3186ce997a4d29a55318e0b87d817a8b3d7e5576bbe9bbb59842d244fb31362f5a2b4dc6d2271960ba10e2a7c89831d47e70d565cbc1884a4c6ec2a9e0074929ab9ae4459c8663fb38ca05d3d652c6f63f9d01363841fb81f96a3f3f57d42141c5a2750c38df76f603a80df6c0350717f0ec89afe7c229b6e25a9c6b0f3c7adcc9a567450d6c1451d2cd1fcada7a2687c00a301a6c8b2ea069aab560dcc489320e54104c13e44761a8d8399fd39036efb97e37724f540c6ae92380c346eb9a28854ee49ce6c4c8071e20a4390bd8a962112a83a57cd5d21768025e9be9e3337b51bdf2104dd0c2282b2cbec8af543d4f88f37dfb38f50c322eea7e6872cd1b107c1f715634f3ddf082da87f6e514247bd618729b85803989bddaa14ed50931db0b0f4464b0b7f16d2f430f0cc381bce47e58abe3d667e640d82800c4cfa2346f809d14facdf017a7511555430d0ccab0340206d840932a159614e6ac9cb8d47604e6f685706e4cc84082bb1122b9468b6b0433d18df71353057c342ac414585252c340c15c7072388397dc8fe00c7ad4a3f04ef8451362252ca24534a52ca1401054f054705b5088dbee67cd7ba5cba17a4f7febe1a380041fddfe14967256a84e334faabbb09ddc5e2e85e8604a2c0cde808ae6dcc434d736ec172c48990ea12c9a7046d5841014c687454ce35a3a000d40089ae1a0742e68d6925914a0b694d0052c5f2251462c4c66de1802d9a95dc8e9c5d19d55a3f7bdfb9ebe70b8d0f2ad0be024cde7fe3ddd0b0839ca6bb2baabfa45987993912474021e4784844cebe5a6894fb725cb01d9ce3078639780790e7c856d31d5356b1f65c602048663c5024a8da871cbbb816853bf38a2e99cb2052c6202f658626d380876f86c4879b17a9dacbf9289d8af4a82fb5e7a8e892650e13736c8e073c5717e6dd5fa057eec3be33cbb22f2f1a9362d9b11034cbb29f65ba30cbfec8276c0005b68475cc696f6635f07a4c89f6e87859de9694c49378cbe3e2a2aca63df23af3c8ebcc39e77cef9673cee78d9d803d73ce79b1ff3e73ce2f2e2c6c685bce39d7698de475defbff95cf1b385f393cdab635927fbbae3c676700a65db0fed1806e39807ff0fbb98efbdbcefade3be79ccf7f7fb70deef9f7d973f4ce3d34f6cc393cfaec670cdf9f60991228318127b76547535677be31a08aed1c7b03912215d3f696fc987b7ce9a35d7bb7d2b262563d09a06df907f982de515543b446abc184b62a06009c49d50700ce6c5aa372a33d323c91d6b9a5d78ea4b7dcca4b799d5b16b07de57801bb726060d3f25e57966d5bce7bf7de75ffafb0b26cdb72debbf7aecb9be41770da430303edfb2b68b01d2b7be10220d02e00c1430574cbc19dddc88d723b77235b0210ef469add1170ee0860e8c8df176f77b37bcfeb1cb3db04d60217fcbc47ded9dd10fe7a07ef46c986da1d299940edce425effb9e5b8e0393ce0ce9b0e815cffdefb9f3d3ad0adc7faf141ff0d7763b71b9de55a5c49d96a60008d9dd485e9a86cab514003b11c0981c5c717f325ddcedf8d6e67b725d2edec7b956e67deb21c29af159cf3b66d59beba7f7c6d59bef7de1edce30379cede7bc77c6fb6e36347e05d3febb69ef7bd7dcb733a5f77c79e3dfaec7bf79ceffdc215fa1d5806a427eda101457b7c8d0f42b8e08177de34bcf113cfbfd5e0728c946ca8df39e610fefabffcc79ddb0d8f165c68e71c6ca8da06ee6c833f37802e81f3969de776beb0393cf0e796e303bbfe1cbb0df2290721ef2826241dc504444731152f42c07ed47f8e37452bfcf59f7ffeecb167cfe7cd76ac35a0f9bc59100396e61c16e812386fd9f6ef4f02ed0b567afffdd56e3b7becbdf78e014bffb8f3a61041056a9763b7c11dc26c6206314db037ccb997df0880de51cc28fde91dc5d4b2f91b36ec284ef44aa73229d39c8e3709f2cfe0f2f5cb2158b7eb35cc2cad54843b6a648f10e8b5c80637ed2e81d8a02bd37e83f32572e904e41cdc83bcce9b5622b6c14f9bc0aec15559a694d21e1a19daeadab26c4f4efda6bfa192417b6fefd9f315426af286839bad17a1ad16116979344f7a721798de51491d659b049544b49f5b7ad3a29024f217d9c301499996bf80f8a35d7a4781a8d21e1d76433e5c3bdbceb4eb67a07ca131cf7d1e2103ab01a88162eeb309ac860a321bdc730fee99620338c7c1cf21fb16845b8f42e2a3fb2ccf1fb028f1664db8c9d5fee00568caab2c199d931daca554c9e2d3533daa3342798d6036d48c405e2a6f0206f6f1b83def15792103ac88ecb1a55c44de7307ba03bd7274a0e3b66d8b9c13d775d8d9cec10a9b9bc4e7bd15f156e87db7e50d437c54399159bbad29e4043583f2c1d17dc8a2f820adc00632185b964b12ae25bbe68a32b557b95b89ea51e9289147b38b2394c505cf784974f44e8379b0b011394c45232c66eef06eef631ec39e05939069c84494bbf71e8a43fb0bdacb7ccb1d7a247fefbdf7de7fff36ec1ed0f0ecc12a8292bc0ee91df5d634dcafe7de1bbf2ab47ccb23d03afa39cda88991a8fd120c914a9ccbc7d25ced8d68ef8d4617719cf1811f4132bcd7d2f0beb76edb954ab84a7cee72c475357c2fb1e07b8f7b1711059d8f63369eddd9b5e3bf771eba1bf3e8de7f8d7ff9802546c01990effa09b545b562b304c1dabba97b1134047a7271022392112b3acd2ca6f503feaf17d3f262512f36f5626bdecfc00efc30a0da2acb9a4a0193a9bb089be7dd4c5b945bdd89e8cec750307c77488c4417c15a975a74ad78335277b5658d1e12f3ae68ce5b101ed99e58763cc692da73519d31269e479c5568ac849fb9598061af46756618e97c10d526a6fac0bbcc8a6b6b8bcd04540cd622b9837321eac979594158b54d8f7a6c274620170f2552e29a67550f26667aa27afa40b88e2b21483fde05724a133b4c23c7103e7f032b4f422b423c7f781e289243b2727291f2f23952e02904e3833504c3547734a1e6732a7367b4c23d887defbdf7d88826426167d18562c235d5fb0bf23a801d920bd9b755ede9bd473601b5abce2d24c98a490542a255b5a6c8eeaa6744e8a9e69942d7b2a307a44453e1537e4879fe8e4f43897be0be5ea43e9c4d9fdae360216b687aa4c4920b904950f5b9db79cef5f9d47dced24b8382f568157b8a526772bc3be87c588e402a135a4beaa4e0ca49f6b4e95ac2d9d0bbebbe5a406c70239750ce67ad25fdafebf6de7befdf65fadd06832bdc4ac9e2923bba5973a866171efa68b0925d3cce1ebf0c3cbaa4b619b6b1293a9cd704088427ae65c4c915ff20bd9c6fddde27c5de6ef46ef225e9deb79bf00399d45cb5ebc959f71ba93191b679c35222c236959a294949c2ea27b0b8edaabe1db562da5309d3a6c905049c6a81ab50c15b9b1e5a0a635cc9b5ffdea1dc0319938fb2488e7882d17c9509756528b1d6eac23ee0157d4016730cc2514e8dc1d54875f1d1dd7837b226d78464346681074678e44683f6242284a829d5489c0a8ab4eec4230df0d843039df2be530289d7ac795ab171805b12c88c4e1288504bab8316b92812506b2fd399173ebbba77bc3f2b313ca6f2ed2fde23d44864927c446871322518350061b654bcd2c13b8143d4e4e1600dd1aac998a91b1e0c24b48c72275653ad112a60f9c27b4f7b1876d2d5ce6a975927ccc6f1067bc2019f5016e639fdcf353727aeaaefa445450833c2d4c34bce90dadb51ee518e162da7a2f56651adecf2339cd8bd78a186702f3a57dadd23fbbe7115ed8c5dc6febe8945871a135a2479375f7552f3d465ab256484b46de629f0bb61d8e87c5f786e12e5019b367af09e8b0b9c2f789408ff5ff0d08313a2548211ebcc9272c7a4b9bfa7a109ff8cfe9634922e020d32cd2846e2c348a64e816c2d75bdd1b06cf8ef4bf7baf4fbbeefff70376c701b3f919cd4689aa73a680e705a6cef1b7ab34f8837d5aa80739b53c1fdfcf7d6d99e1b4546c6ebe21229890faea75afbe8960f5d89aab31b16b4e659598c6682bbec0e4e6267726c56554dbe95299bb00ff2626c888c5e276c867de6fb001b9f05fede43ccc4e8de7bef9f03e85ea8509b74ef5426dde1baf7b312babaad30a2dd5f7dc2b4778cf24185d49d43e3825fb86080c08484a4ed6b8aac25e50ce43d23c60049456498736d0592945338c8b6ea1c8a94d71fddd9a21f1837a2de1c53dfbafd9623545943217f3eebc90a4078ac5c9a797923f3057ca8391e9ec862d6845335239b8407175f31ac454afdf2fb1d2dd90ae70dc5df211bdcc45f64833fd513a9179b95e83468010e8ee6c829cd0455064f7bcf61d84c8b0deee8742dced36625bc7f61b787cf358ac05effa847217c5f59865a28e178b5c9248f227275ff82c68099edc01ca34430e14d788ef154f4dcbbfe0982527e3d0c3fdf97424ae1fab5d7ed50bdb72e13015fc767d48e8895a80b84324fd23a362f96aee7fbb78782f251b59594db4197c9b1260f49dda3d8893baff5842ed0b75b406c7023ebada5d7e8fdbbbfb7644b188594620b178cdb901e971f06369321a1665f51d194fff8136eefebce50ee027b4587a0a7d632e659a244b335a56375613f8d7167d313a84b57cd5f957ad745f486930201ce5aa088284522688723b92733af109c17a9d6074e9411e18712527adc8d262d4406d97737cd12b960c10adb4c954e0c444bdc24fb65f480bc2b91455a6822c5d1bd1393236d3369f23affbaae4bd79599c615d78de3e8bfda5134999d94912e6215eb00284b1bb2249229e9a678dfdf87dfa3c41d03d45a5f9c9449ef0a474b002e4434ab9c181b3f9a6a45c56e12a755da0496dbb8c1dcf5bcea3d0eb93ff960835b38fcf509713eda65bc2f861346050498573349533811aa33eac20df1a8b8535695426e295318cb1029824468359503e2031b5cc9bd889dd0438a0bf68ed55286145336e657a502e7332b28c84c0ced3b57a8a318922e69f38a488542085974088b0e84de67a9dcb88bb5a4889a702cba960c6e2a114fac9cfd0bff72a522b66b0c5351d855099d9bbadb4ce170d67edadec4a6bae03a5695ee730830759f47c8206b6455d9e0fe1072d01ace72abe1e5a8c4111c0ade1850591529e5fc68c18b367dd3e9eea5aabaeee9b0235640bdbb426ff24dbe9e8a7cdb60ef5f5eb794da06f7c7e0bdffb4a70e7b077aefbd07c631d04fa9b4f7403f537df7fbf55db7dff37d814012fca44dbf358bfed480160f4a43cda1544cadee526d69711b23a8e11a115344e274b76b3c09ef1b9596b4a0d21821af4b482728442c6a56a19a1b8124660603075561b8f5162cf8154a2dcb829a598598f5c7b3d4e5ca801a221c1c35be2ca1a719f8e1e9bd45959c6afe3e5d940deee95a4d17d7ed8adc3a05f07cdf7618665161833be67360b9913560d7c367f1bb1720fbae947bef719f3f141bdcce273d273d7ebd5ff67eddef2bfcae86bfad16bd30a1142fc474184f6d683f87c052fb79840c6c096b86972112e788b9122bb5afa7d2def93c9a1b50edc905c22e8734a11a108c0142ce8ad34fca12899a37a7b324c61b622556fc90420bd2003d3dee45df99080f9c144e1454ed63b04e982803319336a5c9c84cef0cd788ac1a2493aa0c09487728395a566355bdbb9ee9c2f090fbc4be8305430718b1961cad19a6b99125b38b1094549aacf2c062be28e258ee1a14be7734270a967808b4b020c2d189af68d143d994a96caf85a62e84f8f4333a501af14baa0ee8c904510e5569f23c7c25f9fbb235b1c13da5c5812dda02eb402892c9ae5b844f7495a5458ba85f4834732b65567221a2284be25fbdf730ac14b6c1fd2b94fcd34445dc00a89343b5b89fa73aceb8fa6e5db704725bc59214fa54526aaf17c2462eaae5e7a544db7365eebd5184b75852294d595ec6e41e9032d74572f5c64257fb24daff2e2389eb7d0c86932697ad2b201ab52431866e65e11b4116a604616183dded77d98d3d67aa30630ce0bd0c06883ad7acfa9270e3b5f365e49b7e548028ca0c84591b4bf345168161efd18a28bcec3dcb5e407d4009bbecba1ea58b097735e3edfd4f57d5d7c41dda7e64c840d17ed05b2a844a7bffe1bdf7decbde7bef63254f5d4b574e1d9652ed9f67667c7734342a48c6a607c6980f1e58b850a3127b356f255269eda9489bbc35903926bf231f930a3e01df753350fdffc40f36b8854362b12602f50c1f8e196d8bccb215cf8fa7d0cbbd77d7a2f3e706d402eac232f8ab71b373332a892901332757c4a6238487e62466d5f1ecdd24c90677d394d6af4da21d63473e303530291fd99d8f90850f930719cc4e9040499ea1b87c740e67c5de7beffdf7de81b0876121515814020991c2a5104ae8ec1b6a2cbc6f1c73998bc855e402e242722db9a0b89c163f1fddb7c058668bc60466afc4528a9d0e8e0818a0b21bfa89e811eacc2140d38b337a9a1229d5a971343549f5e4a400d317a346e4a0569461a03cb3fba3f84ff80092a73ee8d8e736899a5cfcad27a0deef7e0b3f5bb89e2eafc2de08c591188dd787d2a8d542f10513dd8a4b53cab0434ed87b988e25b2e3bbb7482568576b31445e3f00d45674ae080c94bb9133ca11c925e59ae4a632e638a3f7de5558e1c646cc0ae92d1573c30a316e428c3011e195c243222db8ed9e689e53bd9725a84e343c411e1c0b74eb87286feeef30102294d35d565446865e1e33a44beb8925470202098825597c2429d474f4fe9e38d9e03e79fe13a97c42d538719b3b4e4ebe1b777c3f58f87330296b39201f5a2b5fd4c6b8e2a223b6e7520da9885445382094fd6bae6bb766176757c28eef6ee44ee52ee626eed83f5e0121149b25963fbbdf37f99332cab7322900479a4ed1920bd4cd15dcef605395b5d467e60c51d256e9805dc47b0a96985ff417febf77a938a912527caec60bcbbf0b8bf65efe33fc72fcc4f38fbff7de7befd55ebc6f61d866c9063794f329e56043f5193eb0f7de7bef1df87bef1dfeee054a8be13d865f1b0a8261d9638309e3801a8ccd1315512766c4f7debb9ec66851c141e8e9e1b8b9643f7044f8e921c980d4bcbdb0d045551fa82a172925106af1e7c191eadbe589db4fb4902fcfa40d0ba3051bbb2010602504ed86c7a47ae246e33337b4a797a563c59237b3b6b1c322eed639b9bcbb42974a59622778754e22b81640039b684c12c54d59e6b743b44e088902416e5ae054c81406027dafd9849d8ddb257d1298af6732004e2b6332a695502901c6f2d70169e658b26362676f6e2dc8ab81d080d38e15647c96023a82c5fd8408ea00985d915864f7d2567b35b51535e285f86768e9dd8400468b570475b85261026090c0fc94e47c0082f94a9399cabb781649aa6e46f891ecdd7b4c8c61b99853d632b4178d12aafddedcd11273652a577b341de0903c00b61fbc2b3be2cc9b0df442d0c886296553b5a78e7959181311000f671632dd85613ffcf1306c4b6c83fb43e9de5b0ec33f3eb11f805fe94000e1af74f496ba497eb563eb94c9078586f7036089814045df9ad15cac64ac8aeb55805fa6ae67d4562da6016c6c77204b942a67ed977cc43ea486daac96b52f03eafd62e96b7c77beef76d2bb19d2e474679194ee9ad91df1a6c8885b0098253b3bb710cca30d205509c1fe7a946aa8dae39237498a788608277a02ff7edfe2ffaeff00571388f1c4254ca4ee657a523d36a6223fa5129808756a003f7ee306264255296c4a361fa6533b2421446fbaf672983f604aeb02e48a2940ffa03670645e6b4030e48b1e132a4d410a8343cf1386e1fef7fb42fd8f443125806fc19fa1d0bd7b12b7ab58f078bfa2b02b490e2b4db04c9755c35237a6f5527b24551c1d0a148989da8f8dc071cc2f6e742e7cc6d44c342ec7494422486caa9882314c522da37e2d4b05b8f3020bf91b893b9992867dbca89bd7cfd0d599b4e3f93b7f5fe8930d6e29d5ed7380d275b47e1921186dcd39cd841e4884cd2cc501b99125578443b82a0ec3bec6efc45ea6e2065f2298b6b450460081c590bdf150d1c2a4754a2a7ebb0a86642f28c903dad95787e10f5b537246e9d704594f5871c370c288c260be181ac01b0b9337a864ad5ab99832c0a0440808531800000370280a0461a2a872790014000827be7cb86c50242c221247626130240603c28030280c0ac25008424118462159cd718d077002ab040ac1a6f48320ab59a286a07912a6b7b05464f5c171e8e93bc0642f57e612ca5c844cd745589f23e7372782460f2fc6a3780e1baa04a4291a00ef42dbd768104989ced341f526d506d40fbd5d1405596b33d139942db3b76ddceca7be6de7987b325fee6280548595233329f5992f57bbd0215e9240551a9aaa2a597d2aabecf2dbd0d39301423a4d7b614d56e5ab3bec5ad82429727f64c17452587b69884248cc11491791e4e2d2de3d4c4f4a709ab9389613bd114d416c2f48d3f0701de87bb03bc73c836fdae21b9627222ea7eabd2e1f53928e7a6ff8227c2274eea47e8c177f95baaa959d9ccd3afbaaa6dbb8b0c710c3439794e146523d850934fd62e995085d5c9bb864f2662f9e641af54773dfb9680b16d38405250115cc65e57d13145f8fb80fd43bd83b251bbddd065bebee523fb8fd1b6001032028840d66a97913358848e5ce74cae6bb7f3d99dbf2378de705a06c07a920052e07542632a046602239702a1e67a5959f7fc052fc6224c588a696571a1395617b168faf2bd5a3d7c16bb572d49f585699f9ff91e1c922557b3111292cb5478133319aa640c362a75ee02d68a98d87455fb43c87524958ad20ba37936ce7d3e0d7e99d3c15bba8b940e40f36ce544df7e43bffd07fa8c0fc9f9ef24a8e3bfcdfc9f676fef65533a13c06f47aa531ff01fd70db318a64faa08997fc0b59cca28fa4cadb0a3707e8f0209198ce8853543b6d5eb2a6ea60493cef17cd921a4f7c1f874cdb97c867c699cbc702f16c92a012194917ed0c3a008acb506b68360fce31d79f0a9710fb36503ba6926a1fcdb4b1595e26045c17f1036c85dd291348deac24275339f171cf8db4c6d76e68ab9bb1823ebdc9502e1ad2ce47061982a9b9b6ac28b370137009973e7e70d7f8183bc8bde4bfbe235bd2e23f38882a2ecc3d50fad088a22a6cff1909ecc4c22891061480553037f42ee2ec6269aa3ea348b077a1455fcfcd9af7b54184a112723bd05807cb60b4a10e9a3b264f9bae9e47c72a7514fb39910724f3237e578bd6edf5487c0f799d84237e95dc3dddf59836fc35352db2048931b14ca24d130e4419001684a22475d1e9d91bff7df4e3f880a69cc6275c681a1551affe802bc32b22dd75bf04a101dfa8c00b000101c4701cd0f1c920324dab8946178974dcf80a2f66512a040c512b8bf3a95248d37306aeaa07507afbab792151bd3f110d2c0ca07eb8314f10ba832891ba4eb82ae8f9f065889c9c9c78010233eab60b30929e3a8924a66760ceac53aca74348e9ca400ce7d71244975085436024887338a7c221666839f9beeca01562702fc889d225cd1e281a1d326c188b74c7661ec22c31ba172522152591b842a955116875b3b2d50c63261cea7769c02f2c7adcbc04b96149500c1560362f66260dd2008fff171fe93275e285006a252d810b0a7a4dca9ab2e8c0351c2d8ad41166ac1a60fd4c816f9539ef57cfe3be38421d1695f10482b3031fe807b5019e5e17ddd879940d7c81bd18d4982447ae7e045d06b6263355be84c3d464811bf105c4f604c3db0b104c5a11dacb12ea496cbb2a31d386b5a2de9fc40548b878d94b9081bb97c4863352924391e03beae9670993094bdadc758b787bad2417d0f8332f02a3a7c4203e61471c3848d7c6c86989e4f94d41a2e92d616ffad665f54a649baf378520678be0fec4a8ed15524c6dd8c4aa3cdfcdba4105793ee8404e912b75a90df73474ee67afac7f4ffabc37e138c4837a85a4b0f31072b423ec054a44c954294c03a1a1218345d14623c756136991b538b0ecc76f8f9be08825a6cf7fe52da8097ae0aad77f0888e19687c825315283aee44c7de2d9bf05276f90a01e9f4fc535483165095fd93afa1754eb0c819d82de9764cdf03048bb4e1c570ef894c7deaba8215f415272f048210c6a287979b41f6a988c9c021eb84f5d67bd72412d883c5866ea59c2de59d02337de47b85ac1ee6e7be05a751c73a8032f4fece82b788fc319a985f5f8f3dd2ed3eaa4a5da712a2cb7c5ce628a337cd1302c26d3f8b39948df6a359a7289b8913c826948ba74526ac8b1d12391433b04d4b846484e8fe22a40f1131969c7c01e4cae857c88f080ed93a50e1dba4584b4b481c5142eccba9e351f96861e650ea98dbdc4d1ac953c5c720ce174f51d424d94d12482cf21a10c967ef47162ce4c86405750c9e8625dc9f6bef5ad964cd6c678b6e4dc20dd02f545f9b40d8250503aba8c5140b3ccaceec56e5246585e4c5d4bd80e8862b64563048e9c78ca5ec5e89c4fd8bfead778724de370a6223c51c109b4cf71447765c0ba50125fb11235e25099163b8d2e620ac980062dfd6e88c0a481fe14153168c9f061521313af6632c48f017b2ac83af61946400414c1a5bb7e75a03dd6d90840a1f745c841e2a968dcf0a6989e60c97ac45ca69baa12c5d104db48221e2231b4dd14e89b2055c52f6df087648336fdf3a586272463e956d4a8957a5328a77c85af52a721c7bc7b7f5dbb5be600250928ac03b91312dafd68c27d4bc3e236c0c5496720526e6e9c9c673571036cd0af60b6ef20abfb3072f2953634da706301b80752a2150735cb56a3770177e14ba3fe95f60d7c3d63015226bb51f8920728c6a9f0858b40bcb8224051a4009655887fac9be8d0bd8017928a979665681e2b284cb592ed9bf71b0a83cf4d190351e002345aaa5000821ea1f8c002e6d236dd644aff9b418b9a9a79c662cee8fa5ccfa2bc8f1892b0e1800f0d9cb7d901b89654a0d4c536e9696be04966478ac84123383ca80c459911f724885bf9bd64e90c0cfd8d33a998f67c6320c4e3f55636cea3d1035ad1f6b6137b411daac15149d3a137299791cc61482bdd52774e74fb995fa5f2fd1e8bc33a85215a8a3ea3fa01ef5740dc2625b5fd979179b0f453a790cd4ae4ee792d45c6aa3793eaaaee985f7404d5d4b1a3c36bc8409f03bd4803013bf253d1ae7ed98f6a65a12d59ff321497c732514a801c303122d2f807c3e1338e5ea0487e6efc3ff5a3272019be25a7cfe3440ec0869a00040da9bae32708f3fb456eef24e5695f3de93631a0a7c2b281b0a076b85a2294c65ed95b9e89642883d663316f6f375d2c8a5477471e9e088c710e2b82dd4cfb50630e23e46b20b4beb8327d9c0e82a5e2ffaa8de33bbb06f54fc571bb95cf2482b1711f4172f419897a0ff12a079f7a8ac9704c4146c5a573dc6b9b5f8448f9ed0710ac3787abdb58557b2d179d3da6648a25fd1016cf45c9bdb352e009d3e12fba4b19e2d9e97c0f569771221a33cf08884641aa89fd2cb4c7b1e8e493d54bfc3283b2893583ccc494492f3719a2d8561119d2bb8ca705e8567140583df111b4c2661e4a0a2c5b098a7f88cd58189ce0941ca6b4ba8ee9ac195d7555f0033d89bb01c1034a4f8b39319cfce4ca08ff59ffed411fbe40c41e128622a1269a45fc44039d1816c8ff49703fa886792250fd13fecea93f513939f10f46160ad9658a2744dd12ccdfc5eda4f485753db5a35d0af4f81fad33273b411e563c31ab130c7412564dedd7a55239bbb04c52849bb5e8d244bc0809920b59b4cfdd21d92835cc60c51817b6044cf6ab7a1cb3ee7f3f0309d344d544011c3a5c029472bc102ae93be9279d24341f6b8874a35911e540f54baaf1f086c73d526c5e11909ba10c1aefc21085ea56d609408ec3109dbdc30075b5d8004a9d568968e632af134266923ba6b86a7088bc46b7cb1038cae4306209d056e8a5919807ef0cecc9218e8b08ae484114d1c9ef66f2034cc48daba6cb43ad3204d9d4467d24078cb8dd5a7fbde1f4aa6cbc9b776acb4b5cbbb67746579527bf92f6a5b8c9e872dd476b393fdfbd484b1c3b7d5ab506d15c42aca03038888a83d8b5b6cd4790f76163f597e28ce516541572350552c886c2fdbf952ad6530bee5460ec23621eaf40b67d132f34768ae53274a20e9331f8029eff3204148946662157dd770223345e00d980bb1df560406b0988380c6f86acc9a51106c148139b23e712925c58f245247352105acf903f804eea7a241e583175e60069a8069be5355b88d7f6e9ebcd3990680a29dfd9ef25b20af0cef36a0ec0a101c3ac39e754b13c630f6ff9d2f5df6bf13c7befa2b25bed8d1af54f969a75c640887f4b1918fdd54a5812014edd7f08dd769463199fc67b634517c9f472eb982ce9dd80fc185e8bd6fc801349d122cd086281005425b717318b74c5e73fbc0ac7420c2b87b51767b5a267770af7bac2fd6fe448002a0a9d2b62447fcaade2cedf023a2640b004010a52be3cbecc8f8d8236be8a5f14167b4ab9df5236af1e4c2b11bef11b8a0ec4fb58f786b34a9193d88b1fe50c35bc7916524ef329c3b245791fb68e80f3fd1396de52ba8c099a4c0ef47cfca6b1c4f329b1f2a517c5745bb88e1a049330c0b75b78b0296a0e7f0827c4e71149c4b09ce1ce898b7ed6a118ae892044b210bd84735215c0a41113ade3b564ea1e25a9d034f7e0b4c344a822a2752e09f00ec14d2b56f50ab3d239c1c49300cd16aa515625d3ac3c041fa285391a552b20e4818bd2c1ff3cfe97c5a29c48a1753c8cf4fd16faa3b1f61e0d8aef2de4a288bf3d92a96d058cb4965543635eedda52cdb12d48dbbfc4a657f45ab3d5c54a4cd2f8b58c506f8cd0f03112cd78f78eef31b6da7b306a4b480019f3e55d35783a5db02d586b331e26333433c157c40e2f8feb0a9e4e2a4c4255b061401f187ba2e21bf06d95930a6aaac9af87944f6ad55f4428a27fac5725c4818e37404d10a69a073ade55e88c1631815aa716a11d5d953c14ae4d414db149ec6e02c4ae52eb69ca9b57ff491efab80efd6a670463df8dfc907825e28875852feeb7f02dd4541a3b0c7c47e694270c82162b03918c678784458af2625c5d7f0be44444b536f0f146e05f4c81e1309da2a401f6a363979fa3723cfd38fe2c53c748542a1c2150acbeeb9b08929b7d4a85aa73a6927828ed165cefe08dff04c029527a89b4ddbe77ceb1ea1fad12a15631821a23103814d920daf39f5c50dc8810cf468865d7f5a4bd638851a8bfd4ba0e5379669553f5cbb2e003add0fa382af02f97020b3d6327afe0057316e4a333a2f769541951945bdfa779055110dfd81ae6b7ac8bcbf362b059d1f6c63ee8cc78bf7b0da404f52fa5c7e8eb2e2f96e3d338af923f550e391368291c12c03420ec66f1f87961ceba49f6bd4f774fd2383249550ede0646bce7259967e519d1ac039b4a2776011d6c6d8429e34c4e12d64fd547d685031dc90f7d7e8a409b6530443af1c0fbd3a48994effdc60ba23e5abfc822ed9f58150ec61fd51d37a5879b050d32d4706de5e71e59382e034baf589cfa8415aca3daa16444cf08555ffaa0219530101014a59ed718546a113ddad490520541f895c6506b4293099d6142b026fbc02425b9766bec3fde56af7c25cc7a42b0ff478f2ddec51612798d9c79c64a278c8ac291cf0ae9f063609e4e03fbecf188c0f00d93fc588b86e4d04557c7f1e5ce8ee24e7ad4bd1ca5351390a4eec52d6788775b3b1921c85e2499e201f56fa8c42dcce83440fdd67328e9db0df20f2e48d06ad95eb78c32e7cee1b13332854d134687fb0e2f8e2a9eca8042810e0478493702b542102ec0a909f178c8df998721b903b746555dd6095880e91bf08526678fa92dbc3408be34e8ee1946cbaaa06445504a2389bc05c5d483904583e0a2d2a8013048248ce93510d4e78beaf335aa2a4a82482ae71625180118b55e401025d29499f24c8ba1cb653192082a34993c8923404f490d3e4c65b8e3c3d4b4248f2b329d43ab7dcb17363723d2f3ecefe383700926a660ca202009d3c123185726c7ec36a4b7c76460d99e85bd1f7fbfe4a1760823eef2a23504d49946aeaa83561f2e590d12be3bdd016ca8104d0610830c174bf399961726a67af4525a64a7c1c394e3c7e68051cc1a1516c965ff45e95f5e1aed1ba2ece648d18f2065f448da4707f7ef10d3a8bf8ac811d681a1363a7cf629398b12cc3e77b43b64d04643adc979eb568f32d5a338a6af0590e924f04b86af93bc49df479bdc243de556a69367796a55941135f5766ae7bb59e359abbfd57ef35953c4b014b4be4e19aa3604130312ffda319c2950b380ada7919f12a38caac2dcadd97562c7cf8f72d974ed9fc64437d5ae7706304861f06d21c115bf29cae7aabcccb6555a53a80176aab4e11a0e02f517d3a789921f541c7cea54162169aab62f09fb945de186a6badc2d6126d2a1a98e58fdd6f931bd7e8037d387aed6b9a9e05f2e12149ce0eff9a0bb7284192d5edb300ca09afeb8cc812f0caeb8266a0ca59a7f935523d4883f97644d93f3e87c275ab8d8d5fece6f7dbcc1b96da702a7226c0b939c828ffa9bbea4f158d99495be2933eff7522e22e7aa30851d3ddb7914eddf56d6bbf8dcbea3e51e917cf4f4d52dd4f70c09a5bcde96b67259ef909934bca3e60986c8df8f36f1354f47925bd09b6ed5b7219909648b83be82b3ce1ec2aaebe669192397ee7e7293e6515b8cfc5a45277c60baa5049b588646c03a9dfab6c1262d78b293c952a41923e38c1a3029cf78a9b7ac173d345f2c4e4141291af0d6dff00634ea4083ec1e4ba51c4a63d72fe1acd3a511f2320c860243eb3ef1faafc6de8601d83c85fb0c0d50271811ed65d5a15f6a0c8154417eea7923941553c05563ac2832379bdd3a1d0550b95ceda9638c8e2c22db18f428007192496b1fda989a92c2b2b4083e8ee6549a26612e8d992c6bd43075f40ee3632e40a0278112371789aa48b5855d51baa9e7d9a1451892b8316e138cb1429d7c200f61a5aa4ee24588360a65b209010b98c5147c45a8795dd3c73953815c2bf6a97763aa0d2aefe8eee57ce17aca0c7303e3b0cd753ba374f73bbed987819aaa69ca4837a4920cf28b7a3b0c740603940b2631ea70203f7420e5a7c2b623be0123213b3c822485c74c9ba816ccfd79ec9def1d29e4af5f453b1ba40edbb57c0aa5426308eb723a5c620529785c8b3bec9f64e75ed09c30d1673419316fc88c8cb96704954f350f49e13fa44f520b2ce9948103bfc27e085a58bfd40ab61791c03452d23fc40e658ac70bec5672f6597e8badbb43d85594ac6631f884e802557a9f4fa4328d6af066fa8bdea9b90aca240e384babc8d2387229f3889648563ec02d774008862cfd04a794a58c6485114b4b947bda19ba1cbc3ab8aad524198b42457e06e9122888d53cbf4b9a919e8f94949949b2bad340dea720c539734bbfa479538c3cb06be0bdedede8313b8cdfa09c6083393f7b457de2f5199d02a60b4306417d39ca20ab5402d5d6cfac743cba9359d8a2b50f0354f96b71c6a912fcd6d255696d91577d5f6d16ad2c63ca34ea55c00b827ac0a96a3c622556e4888ca2ea914e66b85ef4f107bdf6de614796c16fabe67c1d1137377b6447000b4f22323f8069afb2beaa5fa22000e44749d49cbe14cac81fd21b635c6a3723b4a43d70c43c7251c6037217fe36871078540f22e0ff1dd1d2fe9de06b963d76dfc9abf6c4e1986b5aab816b5c563479a7943d66d200f3c7b72369626455921c87bef0da838a486c1c1366182b7de826c1ba21febd555d57a954b66481365841d8d6bd6b5cbb05565a015ea31788aadf232128d4ae052d3fff0a7b969ad6a773cbb8075abf9721db3ec763bb4b333bfdfd5ab0005ef45e6f7e5b58ead0665b013ecd53d123cfdd70ac0079d069fa680d68f1ccc5fe12a4f0a565055b6a784f194add5ee5ba18fce29faca2b3330975a2dc9c6afdd3a89b8b83eb2c06ce4a4e4eb2dcf8583c684024625e2f58e28072e4dce83a99a17b21e7444d26962be49ecaee8213ef435f8ab4c12c7a8422059056da425e65153c99453add23cb64231ccfd702ba029c52f21a6354fd6e9a6edab6a91cf91a8bbc2ca88ff62e20658541af136f57d2b84a865a49e8b04154bb4fccf6e147ddc2f435ad4e11b8a0c15b8b450c7d32fc7183ba4abc4bc701aa0c601f758118eb782f988e99f61bb6710c67b5a2d79a001293ee1d3a165cd195899648d21a52ee664f776700dd594efd727955cdcc5d5f26107f32c84c0ec637dd0b68a0354468e97d8ffd81fd49cfbb855dd0a2197460e1d305a51fcb86421274613a39cba244abefa33f714a5babb5e2ceddc08d34d04e6f3212feae01dee386191a82a0177eae9af0b788edbfc30fc87839962472efc8047b18631e9408fbc9d16a204b4a0e74cc8c2b917574c89f00acaa2c89319e377f90bc9a77a74dc007914149c616135f82167ff3e2d29246b979dc6c38503db45aebb6894dd98247bdcadf0018214f6b32c2975a598a1d93ba0eaf2a7e107dc7beebf6f7a23b604e274d0d3eae5a270c2412777b0148634ef2aa4f25e3828642a69fd4f1f740e43a8f726d4c4e3defe2e48fbdc980d7176b340dfedd99a8aff85e81f55c03ec036ba2eec6e56013e710f3d5c45869a951180e14961b694d74e8140050c136c4f8d1fa9451dd738b34f414e14391a49ab4bcb1739f7d54874e02b7652d0e9369b1a1bb82e9ddb361e12dcbdac836481a18a52a4c4c74c25d07bfb1c96a594ac32c0a0350740c3b9d59c71acdd94d7b8f5a73dcf3285413ece62c3616d08261270bf2ceb0eafd413222d0589466dc202188116a1c6e3efc3b5b659d099a1b08d53937c1dd453a4095b29dfa0c738888fab34a075655a4bd4ac27f58a0eeea0a68de561e095d9b144cec2119f3c4c9c54e36a793fc68204226742b79906cc332f27a33661268067618419a69e1bf4ac8f61fb98bd7ecc538b420c6d3e80a09124b409e043453c1e61d689ef597ba10f2458e82d94aa5b4d756a58afbdefe44447a56b7d923e518626faae87129fe8f0c7c20980219d102272e68c204465c6004acc519dc43e490ff02c798aac99eeeef3756e2b86e68ad650a2da3505e963b6f0e5af053e1313bf8a7eb88cf65de2008f930b37859b3780aa298a2acd081fcb3b3e331d01017cdf44baff2d0c21ca7f343882f255a9ba00a1f79b326b4421b6c0497b6430d785903b1d4caa0b254a645a632d7eb8401fd50845491321521823797d5744cb36d8a3215cb63ce8772f976506ade642865528a6e364e924c4f16bce627f4fe801d3dd8c148c5a09d9e43efedac8b9ff77c8d5650acc858adc96b600b063131ea1e938aad0842e648240914d36d796437b65692aa9316be00ffec9111e3e1d0c45139466b646a6561eb23c11465b12c0080aea62efab44bd33dc03659ef00b09a9c5d15c39498d571c8a7de799b1a627d64621d5d62c4749e399032c11404a77f50d70098f4a975455a52cc64b6d95101393fcca01053f4e3ac1fa806667ceb0e48927faa434f6dc057b60812630fd7624b0db03c00d7da4d6757db0173cee40686aad78ce063a84c4b7d92962c8461fe01d5e17e36b4a3c8bb016a3489f64384e9616843c1e88ada6ee43b1a4c3ee83cb4f9e992e862676e340718e6ce9c9ee83d683e15ffa151d0ce8280adf79f6ff213346e4bc9302ee70dab899433622ca4b03a1d964004831d1a9af7f3420331503a7316825ba66c01d2bfda12a4bfb45ae10a1ad5bc323442a8aad4909ec05c60ceef0f854280efb8e5d8e314722a97314c013198d26bc84aa824a457697800be1d90a4eee51214871e68854a99285c1c8a99568c0784892585302eb79dc8e404399aca4c9d9180748c7e67eb3204634cfc905cf1ae21c3bd497a2866a6f495c1cb60b98a011b9705c9894961fffe45fd077aff4b9c31ae17cc2a183d61014ee5452155ac8c30e9c838555078d98f75b0922e0b5fa644bd308780386148f264a3996e9ef73f567bbbddf3729cbb1a564382ba0bb50736b3975a7ab1dea0621c24fa814f860748f0b3bda75aa7cc220e70e1dadbe2d72f6a1573d4606b665938f1626fd8e9e10a7ef2847cc4d78c73722f26684d1e2efcb9a82b23db84d45db97a7563b75bbf5180d52d842ba7dd6b7b24d2dfa5d9aadb00ec919ebe9c62b3dd5d7b4d7d308984e760ed04db4da496aa148d0fd4e4f3140a3ddd92bac0378d47a53576aba187e37c07cc6ed30ec84f82fc657ab0a62640c4a1d493f119062f0ea4801767123e4a3546fb83036b35ee0fa9b4576b7cc03c9da40199d9eed5d886b988834e3b72fb3ec951b39b221eeeb59f03a1349f623b79fb72245bb7796113413795dc4def1d2baf7b5e7b1cd1f7bdfe08c214b6d20ddcf9a5e6bbf0de3220fbc063f39cd593c2883932a4a4fe9d7bcf0b6ae0fce0f1727d858d167dbf76dc1e78c325f6d6f59a02a8a59acbf886f8ea1e063dfb12801b7ffd990032874ffd988321d4142412c082ffc6fbc360828d02a5baad661ac8d81b96023968d548dd72844921db9832d310febcdfaa06fe63c1b4e6e4d90d5303c6eaf1d58296512650586d5da901933f0422247e96c20a398bbe9eaf626d64953026b5b91656c753891ff689f6df94f41233def871d9ab4a1b44b8226bc9704c281ea38226add9dd6c8d2343e8bc3855f203f67938b28a4ff21ec626d7b46ec115be5ab82ba5bb643cd75efc95943594200a3e9c63bfe3e0a30e567e01ec0cbe2c8f5939b61cd378d19055d9dd310068e40f2ad5f07722abd730df05c7a55edd714ed726c9796131f3a7965f64e14d99e42a00ac5af0cde2dee3b7b858b0b311c18cebf65cf28409381444890c8ba2c763077769b6d05b6746a8d426c7106acacdd427cb979c50d8cacd573a8076fdce343fa870a59746fca009ce371a08acf9e23f74f6fae7bdcdcffb5c296aa8f97246f0b6980737ad197aefbd1cd02a128b1cd2758d12a6f12715af6809fe1d6729b03459d353d36d0b424d3966664dfe6499713c51062fcb41441ba79788078984b07d701f65759279cec72daa14f5a89524e9589616c78a0c12bf48e6cef517352b60a47fcffaab97592a4e0cdc66567c0f9b8d4cddeb9329c7e87000b920ff9cdb5e378d90bee561e8d85f7587824887c3111d3261a2687fa880e51c116fb071c779f4873b054206e2a11b0b40eb868ca6418db9f478d2b1d64b8f2a865bd0716607ec3ca959492f839835724634dc05ee0a8f54456b752146b0bde0710f23393813699ba9c87b5719a5a334954987c250725e188da06b4c8d4d747e2c49d72bbd8c21acdd720c4dce8f942b9aa71c3cc726658c0cc0960b7cd111f1acf74d9e73cb42ba1a46680f71613cfbe3a5670669caf58b76500293a02a9fc6c372010e7a78ad4e868da03106e113261f3c58d6fd3c784ff001206d347edb3aedd86118059f942591a9134a1bc7aedff5a5b6d2d525ff945adca446783ad27b5de86cc32f782f6bf6139b011759b0673ee1c319659cb007eb82e308d623273a1c17765734b0e7fdf9da1e1098c16cf72669568b007af6eb67a948af53b50b3ac233488bd95a524607f082a18b438450d334638c355efebcb6e700896aba514c9061b00e26e2cd331101b98f1fb62b9c3065d72b9a8cfe7648ac3154b6d0ee07903ce044f2985e84b05fc0411a39877e49955a74c25d1a5b9c1145e10fbd8081cc74e2cf804e34a29d5153441fcaa4d5c9dd8c6ffc9119daab21c9097c6b874392535970f2d77c389d705cc25fdc100bb794d09ddf4589e8d09fdb18cf062ef30a1cbcf8f26e41e309583957bc56a2f33ac0c3363aa648530e8e115a323c9129abf92cf35092a3ae98e7c55d77bea5efe17e80c56dce8f1325826a17d061bc70a03840c689596f845cd4507a818172dac05d7990e52fa2dd7efe888734ebd50d978bb9959aa9ff238dc1949fb8139ce3f47b3c175041a0c2800b0316082c005820902d60d820d1665ad92fdbc0e939d18b3424e250a4419106231c224460bde6e7e5d19f24c2738540c6b7ba188403c2ca6c4dd83c88a298bd8e7d840c17a8f7beccbba0dd7661ad19a9c9f3ca09f2ffcd76ad748be675a0ca428b6c01a70b5613eb2c9190f4c844654bf0d0ae02c2aa19c09621579b43cbb54220d2964077556b6bd452e0254da4b521a4c764a97211539b0ba83e7c687aa7cbb169a9b75f8ee36c95136e507e24a25e95153bf028192c75e7bfc68e341c8fa0e19c4a3209c3edd78a431423c6281cdd50aad8473cc5efc00ab60eefd11fd6d522cd407be8a7fbfbeca1c784a4f422308ddd6b9cea5353f76a752d2a7af6b7b82e24d57eb5f7daea8a4f6b0169a716c165665a7f17446111a8688d4218333139ba54abfde0a12854efbcb8d5d8e3636194207f9f65269ae366f30f63f6e52c98dfbf09a15bd41cba3a22ce98f91148219d809c742177d4edc9b17d4f219e7a9fcb3bb789a6112c1c5189433dd14361f5de8d2c2b02c802fdcd66d86e4c7d2ce1cc5cc94ca02efbd2c2e29b5c9a421cc2d44b81fb1086280b0710f536e10eccf02da123e22798e4fca2e940118219b3b35bcee8a77f4820c9c98b7475767c643e77edaeca48b024f182926b31d24a32bb78c377afdc7df208f222f42160e9268065d614c65f763e5f240ded74fe10879c757a25491360f62c8be46a4644326eb90ea4dae367ecb38b768c3707125db6e3774e4e30b003cbf46217b8f60a95021a4f9451e4d2771efdda4aa3b406814bbefc15d87cba527ca0d0fd57a4c09644ae07eeddd96a7939bfd0dde27a5018e0c78a8fd6cb5f5f2ca515eb7440a03b6162c31f0204401044b32515d40e7b1e6549f89f3710a0565fbf7d93d8dca95a1e21a0746d401ef80870d3aa9b8a0d5eee4ed5280976eaa204a6b1e91eb6d7bf0c17de5ef984df5950c400b23f2dc0d0e430aca1d163adccb66c64cd849821c72086bca72255c1412344083a0dbfd1d2d46fcb4fcb91e4a4f3088b69e24686e84046441b4e25360040a234ec596c2f7e302c635079c797044d31ef0e6b2ff9e7fc263798ffbec4a9dfc45d78fadc1924dbc62d431746a7ea2e6506154eb34874f63a8dbb3b7b6a6f8df3dc81eca85ca6df0816b305a3dd898374dcb856411f09f3501caff91b4181f9b1970d8e90d4587af450ad431c9dc6a64511a94d115b4f197a1509ef558feda023c5878ba548b7f56681022ce766169302914ce6beca6e87ffa3f623e7bc9bd014008198779d57c1f3d0cc99af7c594c173ab04003e9cc8f4741c9749838b9a6c9d38161385a1e3b405b6691cd6e45f474c494195d816cd401ea6663784be5d2227b17340072d144b9235fa8cc3769b4fae1930516731d9f5cd69739f78ade4f96bc1385979618bf7f7fb5dd36d72c4f1af9771f84549a0be81f4ea6da597b6b1a2e536ea000fabf4dee05ea4473585a686f6fe1279c02868530655034bb563a8a5dbcb7c1a988fc388efbbbaa70076039892bcf5acaee1470cb39354aa069b87c622644d9b9b7845eccd949a7f8abc421baf0c2eee38eb0e895b4198f58b652191a2c5362e1dcc53735334b31c119e55cc130b8ef64a8ee484dd1f66d8c13108f1c2946fa8201ae596b4e6d2fb772b65dc495ee3b87511ff41196ab322f2553f7b39d3354b45f1b337c8ddc01e6c7d017c366b61ef2c5a94801f500bf1461e1a33ed50348918c56146a530e1887115f05ed590c6b277922b3ca1bf701b6ebcb918ec4dd94d1691ee3bfb6b847dcd836764e46ac4f2dd3ea7010b273d7b82eb079db440de436cb9e89131f26700de0a95f6a7a9cd08be673910b578f9443b521d1f08c9e26b2e61c8ed1c10a23376db854d47b0049393032ef479e9b5a0187c0bfa1a86105e6f1fe09152ca006a8f06a8e369e620b18d4f0f8c8950e846351e42c7a9a850bc9403eba31f411473132a386250296a2bb145e9981162db3554c27e144913a89e0c564647703da70927a5b4052aa92368b16a3c974d9eaaa2d5a7a6ee3c94b274f4196b4e295068f3db976bda552b5106236969aae4abc54699e8234c210b87e23aa3e6076fdc128762de4cfd143d9f7f50499965c2455e75cb0d6c6460811a195ddddbdb96546085907530736ef1db0cbe69b550a462914a576b86684582af05c599551f90648877d51d086e60357e4630da5a1349496f7770a457ade7973f43681899ebfb525262cf199113223046b3e9a8fe6237556ba46ea68ab46eae8babaf7de5befbdb75e57cd0899d1b17546c86399345205f0017d4ae9cf772d0fee08657c9718dfc5c5b93c16cfae3e5f825bc5dbe202f330fef9aec5b9bcb81536f92946ac9530cfb04ccb3bdc5d129097cbb6b19ce51d359692d2179f38700b0f07d432f3a614d31bee74ba3c19f90fd3df3134ef70fd7289045afc8db50e5ad60abd87fd5aa803a2d8b5c460fbe2f264aeffad3de9b327e5fda2278ddc5a967748493dbb3c96d9f29c85afc77e478d657ce16d472dff6e14bfe01b8e3717a95330ed606defafcb63792e4f66be747171c13bce2d6afc2e4f06e7cd8376b136e942539fbeb83c16991df6484e48e734e7a2b5f6e9bbca57724ea22a7fda97b6beffc3a7ffdfa489ab9a789c02e664d773226f22ad5cb96a085e2f48189df59ff2dae3f78d06885def92d93b48a29b05fc94c3e7f4d5742339a75cc969d16ff264ac97b234a79c73ce529e724e08f8d329e536839473fa90ded403dabe65a574f90e8587912dd2feaeb2ef2bf9d6f39ed3f77bda899ca808a73ced3df9728e1168db01314420da97467a4f57adb6134d2a6df6a1fbd3f2213d61a430521829ad55d32895744a29b3af1e8bb4aef29354c9bba6e45d49796b5ade9ace7293fa534e0bdbe45f73ce3934e77c6f32e7bce1d2ba165b576af9f25f0a5bf2ef0ef3af268f655ad7aa809868f996122dff22d1f2ad0e316a39dffc19e4e3bc4d7236792c183f6badcd168ddc3ba4ab9a0c35792c128527431aeb7a04127dd22ee888943699a04be11f209f3ea5b73ad0adf2abbc5ffa0b5fdafbf75277c29dd880a20f2c57030bda310ada0972a2a01d1f30153f1fed404fcf8903be35dd693add1369b7b41c4eda5e7c45fedc2230839fe40721e1a311a05dd01150347487efd1e92ec3cdfbfa99cd6ccecc333afdf26cb768dfe2c3e9fe73bb79471cd66bdae17c98840792f9febeffdc0cf37677870ee44e78d07e40effd23f27edc060942f914084dc62ab56f52cf2d05eda69e5b0d0db7ad8375f1fd0921f4b716a53fd0e9dd6edfa91bd1fe8f927e52e86f3de88e6d1edef614629f706ed8bf0b57e46318f61b3b618528349afad95bbfafb71eb6326b6dfbb2305a838e60a22d6dbd53cbb2de3a6185282c6bbdf5b0cd97ac97d496d44e7554f48cea0cf3adbd03b653cfb7b6f9efdde6c87bd460757868a305aec897bff5092b30a1ed57a4e7ca4ff349db1bba64c0dafa1668e386afcc2bbb134fc6ca9b07fddca7cd34755f09bce8e34eb8914a9d50a5d0f33c88b5b6d6211d5f713b3d448c04257161680985010d0bb3b0c90295454e16a92c786644915810c1c20816415824c16289b376c69472069d9186944d6c71f14091525acb03575c9df7f5b0d65a5beb8d8daf38544e8ac7e7a7013740d0674e77ec099f277eb42f64513d828a25889000022f72e0c26c810b54522c3181123a6ef3852caa95d239e7a4b50c53b5144e5883e8f88adb6940909e69aaf6d2306ea5a5dfc289228490220b1ef424e1c481606288a808233200c28e0b8a0c1600e10914641c0142044324b1928012ec0041074a34218490284ec44d514c121d70432b691ee08afa03129dd6fc818c2dba3092b34385874005622c81448b2f8cb0c4075870e4c2e3072f7e50c52a8b204c00860f212b315c9220266a5084297484bcc00149592b4c1df98af37183a3b3d333c48ed1524e2aa59c953a08e8113968a102241823081414398104910b528b274582080209327c503424a5063e2ed4e027cad0040d846042044cbe38c27fe001126a0f114e8002e268c5044f14d7802790d86aa794d2d229a54c91506c51827a908a8288118cfc03621c618818633ce9e288063f704177500138014918614851061857c0c07ba4522b1a00418f0b50c0c5101524896122c6114c70850888accae800136208a102e7092a7cd0b037889b207082d0096227889e20880461a45a7ae315621d31c2d00b221254b5b60689e0a789124828f1418e1094908428ab11c412cc58800f74f0832b88a2e881112ae27c92f215c7e3d380273f1057fae45329a2a559a7141b0f46483d71c4882f58e063094d6a2060075186948105501323dc27dfbd53d844296fa0c447153655a0aac8a92255054f153eb3458a2950960834931259ed8f67854a1139be4492259650fa522ca105adb4be12d7880f5561a22a9d12ca902a542b162c914ae26805470882f2915346144f68d42526080f9c18a10847e420044af8b1b27d0005516ef083238860f1a02cd1e309310d80aaa41fae39a736dd0a4d92008c52d59eb53b48c115577fd02328cd9d08a206106124b5497518190265c51838c801102e4c2b80500208274bf801072e4c3298a4f4389994d21b271301f4e40cb105ea8a281f48428a8a13760045116284b1832b920845d1dc2f28807cc5b9000592aaa196549944738737307283a0a95529a594b5d2bfe22de18101040612309480c1040c15129794d7c88d9e73523ca325a99268bbc0527050bdf0c20b2fbcf0c20b1963319d380a8a8d204d232c3ae7bc778a1e3a05113a85114aaf1454d268c948e7153db85617afa8ba5bcb244965b2442b9594c0015c4122220029203a789205114f7a3003282b2862858c1b948162a28a16149971defb05115f71466a90e6c217494c0fe9634161183b338c21d0c94b9d400b58848422181581848f12434082912310f08811850c2456442f9024d145132e48010f96b0d97912c613328c23970684180a920221867070c50c5ba3b548e08aab2b0b939a984da0a68c42e839a7a9a55a1bc64d0d03a786a1639a940bd713c50b1b7011c58aa1315031f86203518a9c10095102052c78473069c011aaa9554b757eaa01037e8a36f829be352576204f267b9a36bf66b1b65c6f792889b18ddfa478e8f890a3fdddc66f304e0582e330164e8ec9f44afa39101b84d0e2c76ad2ee1d7eb96d0dfacdcd741dbdddde39ddc66f1ce7e28aba53fa5f0f9736b661ba0ec46faead46b771d16431dcd2a2650bbbf762f72dec56bb4d6cab6f1d4835d1fa37fcd24ba57ca81c8e2ebd6f1dc863b997351fcf433d9cb7a3e55f4f07ed405aa08d18e04afc9696a8758d49d6100391967f6305a2996a4c35a6ea3640507402792cd2155d0f05222a3482f2881ed12b7298774b3e575eefa1bd08aef8cb9aa77a45a521a5212aa9d210a812a2a405554995784a3eb93464523a5552a521a59e9216943eaa4329a574d2d20fa5d69778ac924f2d0d9973d27969f61b06c91d3fc9d736c9e32b72ce120f2df558943e233f2522da5f2555e2a9251f4a21b568c9c761e0bf59f221a26956d951f151d951e1711b5a69c8637999e6a440db514b4c1f918e0e43e933bad5dd4b3f7a530b2d4b2555e229f950abd4739f62d62ef568fa567e4f2dcddf4acbf7a7960ded567f8c967820d65beea8a4e2ca5ed6f52760ef9f2ffad6c394524a292df57869c893d98f3e95a657c547d39ba5941788a8aa56aa1de8e3d290c742e98653502e5011910eba3444ee0469f9a52197feae2a3dffa9a8f4fcadb2a3dfc74d7be8f95bee68f9daa6c244a534e4c9d0bc79d0a521a5214fa5e153e96b9f4ad79f34f6e6adb2a35552a521a59ed2109a3a61886ad472d2ee4d375e62eebd4f654422eb952d2df7adccf561cbe9df6462211e894772c42360a8f454b96c34b2ee7344204a29a51f9fa694d2087304d168042b94d8f63e46cb34d2c7ed31f1d3c4e1a55c0bf3f1f6727494f86942ef814225f4bdaf4860bcf1c89349793a83fe49d74d5700e83a6346aeb5d64a53288d5bb401aa40818268a574bb11c2b78487121f92e8f8392210fd8b44471a9f8e8f462449f47b1bd2efe7c32dc18de18a10f3fe029106a7801b5f7e4b50a0613d37a9e12635dd5ef43635dd6674dcb60ef7c618a312bda1f55a816244a2778d415e8a411afe690300695b5dffd5af6121d3c4a7ef84f07c30598292319014a18bedb6d0353eac1f6bfc18e33f0a811e0bad7fc3df93b62c26da770e66ab402f957f170e78bf421b36c095f9b59477d534cf30ab90b70a7aab64f0f7b351f94c633f7b5ba27f590c8b412a1bf6d7c9ba75ebb6f4987d0c2bbdc54a8fe578e4b1607fc34b76c3720a49cb3029b7af76cbe98705db84ab34fd14f781a64fb242d3d7a8d0f4b31a68fa98ca9695b678e4b1642588d2f4734ccbeaa50b780343b084318aa0e99774b001aafc5621659bf47643708b9a851f4de9ddfcc6b540df8236bc046e85fe74225b851768fa2979bf119a3e296fd30a34751468fa59de55c7234f06cb9b071d8f3c167ac5235ad4954664327247faf96041d7b0c0c2ae61415b0f33ce34f62bd4d6530b5a1b8fc818646b3c3257bcb825139eefcefb50f742ed6aef877b3f1e6ef18cb450291f8eea72fe7467a1b4c9184f3b7cda596badf5aac1b1d6875fd3e191e3ef9ecd43e1fa7e64f8444f20f5c47a6adf4afa42ef61fa748be7acff6cea43f9acf6dd5cef58a6915f7f6251be5d83e5accfe6b1c8778fdd5bef07c6186b8cd6596b75fad11da47e3f9e8c943a89036e50c7648608c82ab703cab6185f916fb797fa7ebc1fef47b6686a70a5f4c7bba153e58a1a64aac142cb95524214ccb153da2076834160101804090fa5653008a22acc8141ee8541200e0cf27bb16743c3568375efbc3085ef85a819175f2b077932347e9a7e9b3063524a4b739af20ed7a54c03ff3efd6d422ebdf530a5f3d2b8334a79fb2ac73bc1eb1d24bd832a1075218e65e19f7fefdb49595047c37795f51065c19c7bf1bd30c761f0bffb7e431d67a1af6edee13e649ff5b2057c04a1ed08388c051c666a6b410861aa3a44c19cebf592c19a10476ffffbd66fece985a3ef3f52dee1da6279874b57e15fbfb2cd104e8882dbadb43c9daeacdd7b350d7beddedff65e597b4bdbe6671b85383a6aabbe0783589665d94c73b77dcbd67aefc5fe38c320f2b2b8629795fa084aeac7876787fb190cf258ee65f154c11e4740d77c8b61908b4fd0468e538cfa04286ee4a7f93307132d1f0679321706792cd3a296854128c481411eaaf4046064a909acd543fbcb2089c46198d0a411694402d55a6772ec4c6a4667562981e08abf495e99e95ff85baeb4bf04924040fe33426684cc08b17a106126098dd4e5aa999c9994c97499cc0881efee743342667492e8fb7ebd50b3ab55d368e8541a75a38143a3de3b9373677826bc772667de0a67843c99faf3de79efbd77de7bef9df7ce7b2ffc7bb798fdbe5034233f2df115f9b52e5932ef9dd1a915fefc7b5f0f0f1e1f766672ea4c4a7bdef7c2ebd767527326b5a3dffb7b30f49ffbcb34724aa06dcd474b8d88f623f59546b4e6e330f7ca20addd3ac3a3f7b5905257cde4cca4f4be345e6774f4867fb1191d7dbfbeaf17f3bf97c97de81fe7fd991cdf1a142d5d5581b4fccb04da9029b8221f5e26f732b94ce6c451359529684343c115f955a624e69ef745631fc398cce85cd6efdac3ce0891f9666553012788e3277a6d8803c333bd09bfeb8caf96844d53762bf7f5702bd3ad906a701ae99df6251552969f5233295f59fea86ba621e54afa884958a6217d25651afa525799492418643e128944224588e36c7c4925c5c60db8029ffe7c54dbe130880aa2543008c4214d9ce82a1804e2c020245274dac68c87ffd0c68c5c035c814fe35fdeda9b901d5c7934b2832bf355724ef9d7c357604d21fdcdbb86ae81e2c05efb6c7bda55eb60783d6a4dc95b33c11a10d792968296d6cbcf5edbb07f3da00661f61976bdede5fdb315e2c02032461e295e8a810564f352364fc726c706c706657373e3b2e6edfe62aca9b5f4f1ddf83a77e58f1fcefa8f77f3e3e5d0a3478f1f742ca5e03cc6081fd67b47e400d632d3bc1cf3fb093e8e9b1f373f6e7edcfcb8f9617353c357a00c466e9e111bd7c6d756e387cd8f9b1f3736914e98d276bd8ea520c8081f413b382d2f11bf4b8bcb8b4b8bcb8b8bbc6f5122349af952efa82111441a00da050d7185b61c6b2687a1fb45d38729380c7d7a617fb78ae3c281b383a1d28cfd0dbfb6a7affa0ebbf5eff5371cbba8bdb8bc85c18df1c67befc5ef3b5cc719e38d7f217e79717181dec3f52d9703d2ae0bfb162ce6d2b4df31fafafd7242acd1e5c9501717cd237bf2c80549f40ada05b920a5adec72e9d2dd2556e11516c24258d36cb04adb56f69ef5d8630c0f697848bbcb7fd7d37e439b6bcb5ec6de7a7ff8b2773d93e933938685b090f6ec512af0ca6db00a17b90d561539d1105eb90d56e121bc5a218a99cdcf2889464aaf9477ad44d2e897b257c9f2c6728a956bdc11167a3235d394b0e6b5074914bd17ddcb4c4295c44f123cda45bb201ea3992d8f1e668b7540b6e5c9d8e7f3b9cf39ed7c1b63ed7eb1d6526bbdc57b8747ff3a5d4a781a8157d46db08ae222bcc22b0d6badfbfa9ad5ba6bdcd18e76d27b4aadd9b3d14e5f6573beb22776a4ebe32bd7b8234dfd06d6986a4c47ee48bf1b0de3c38785b01016f2d4f48267c563445b597c711ed08ea747aba05d108f0f6da40fa05d1012402e4f1e3dc4dce21ed473a3d16f735acef8625fb4cfe9e6572bef3863cd2e2e2e2e8f8587f5e2143a700816412227ab1abc72e244073a89cf47cbe0d08c8989297aa877e1f0bf1042279028ea401ab4a4f2ce61525c5567609709a627bd6f3ddc404d799f4cc8fbb1ef785b0e7eba104208054308872a2c827ea19327837f9a00e184d08487264c08a163e8e4061b6ae01024bad7217c473ca0f860a47de84208a11739ccfd09a1115ebd9ff1aef7724d0e4368dbad5c11066ca8a9975238048be8854418126958bf660cb15561ad313174ce091f0ec52b7f46de95e6ad95f236997eff8994f749f3ac5996452b2572e22b9b412794422716eca194564bf6489d4aedc0f3e363599f41278fc5a23f5656f500e9408489120a6dd49897e384a6f484381e039d40273a3938c692b8d66aad13225b9d3c96e943002051be92c929e0e6a021d618e5eb7832f3ed67bcb93b75e8c8a1e3b140183c9d4cb3ded3b5d6fa37c45a5f474dad3a94d6fc354e5666c1b5e0ac8d31c618638c315a1badba95aa7dd612d5f856ad9b9577c481bfbedd72d0f064ead3231e507ca09f83862743f3b6d56e38406df37e4feb7832b5861b1c063ec705ad3a1ecbcbd5d21654acab05bd871cecd3f0649e86a2777d8aad8d4f832bb256d4f164fc2b662991ae6fdfe64ddf66cc560558f46b7c6adf46a98d59070d31c68a7d96ddafbf53bee65a4999e63ee96b4adeef6112062d5b6b7544d9465fd3b12ffe6dbdcddba9acb538db74c41a9c022217babe8e9a5760e2e9d0a1e3c948a99bb464e0c4c20ed996bda6e3b1643a8890e5180db4bce8d081550cb816e85fdbebd0612da8c311e99831c4eec8c111e99a1baab5d6dee06cddd1b7c6a96ea8ceda6aed3b8185d55a5b6bb5d55a5b1d8410bae75e6db5d5566babb5d65ae79cb3d557e0ce3df76a6db5b65a5badadd65a6bad73b5d65aab852b70e7aaadb6da6a6db5d65aeb9c73ae5aebd5b9679d73ce556b9d73d53eeba2d76a6d0422f25aadb5cf3a596bf45aadf5671dac7543afd55aeb9c73ae566b2df47942f5e847138aac3f9d92f9c4ee1aa7e4c97c328be693978244288fab74ac6e164d2317843f92f2cd59349f4c2894e7ba3f0dd05ad9b5702dc47f47d8b1771f6e631939f571dd8da42527140945cba75aa699ef4f69369ff490d227146a59f7e5bb3f1b1e3e66d134b28edcaf539f4f9e8cff7b3f8f1ce6ee79a4e56fda43cb774aa49cf365b751c1b675d0f3b5d2b4a80f49e92c9a46731ecd6ce20945fb0e347d0938191a22e014603dcd7b3f3d5fba0c3ee794d3e8b1cca7efe7e634e63d7c8b398b687832783a2da4add2357e32afe7b1c489afcb29d138efabce8bce39e78432dfce2733cff9a44215d4026e0185a0e557d47d3b9fe0e8137be20b620cb47c0c55f349cf9379478f25fef7ece733e58d7e474f0696c0b1c88f26d052ef77a46312bd1d48cbf8f3c99399f3c97ce24e89a6714a28adb3683e99502a9d4f1e8b74d50944d80c3a584df5d9691da75a6b3201a93159939bbcc6813810b781b9a548474b81a8bd918ee340e4fb7529e5f5e9b0e5fabad5e01c435dc897638c40af078f13aed0e2df80f1a4e3c718438c10628055fb137548a643a2754c949818fd7a2cb15826b12bf7423a444face7e400d6310596abb16f9104923e44903e1288e2240f8a1a40217da4cf2dc2c29afeb53e76989b3179492963b494d99a5bdc9ff1a7162def16bdb5ed94c4a4a8e019e4c32c9d7416b5a8251f90777d1bffc5bf0163757a6653c71c5199cba2199f5631444e56e394efbd27df290e4e86a78ff3c639d076852b148595f67e97b43b6df9f182407cfaf182000f5abea49159a3d70b0c9051cb7f41460982b945c7d09a78ce7776cb846eb8a65933d7e0d7035ecf1b905a3a231540196594a1e7037ce60e48edfcd4e26406aa084aaf920bc4c505ba4a5af465a2bee3420a115ca00b34033502f74181082dbebf140a08a81405605c30210b203a3f28948e3e474e75d231c6eaa718a5121c0335ad9aa06b772802204993199ce24947a77ac2426147cb9a603f411b1f7da9b4f75bf3f952bf4bce2905174535cf46c7d7ee8a198507f8c7f78f9903cf46d3681e9f0bf514692b730eb3eb8ede3538aa62cdc1f6f82946b8c9879be5792cf1bda664b0431e4ba4cf979012fa5bef7a414aebda50709a87bef762378b5636a1489796bcdebade8a1bb6ae0dc7f535071a5f2860f431fa5746217bec51c832044e1acbf8af777ec2eebaae8cf38e3cb4dcecc5b1c629d136cf275502ae8508b816e0d7404166089c74cc31f8e9f1932a70d4dbeae6615dfe56bd60d8ee862d6cc3e174ddf0bb6b02313e96b18f8fe57dffbdc4320a31388d3d0ea7b18cfdc47e3e96ebfcf8aecde2a14dbae658250feb390f2c57c37a382f472707bcf2d39b32c6086de0871e6fae3f7005aec4bc797caf87889f9e8f9fa095b7e907fe8f864f65a0b9868530169281e6ef62f5d9c4cbe1c54f4fbecc3963bccee7c2826fef2708bf64727907783d64f9a09c1546596bc0e863c1f874709ef6d8c3701186596b2d5de98d3db5f335fc1ba321bd765956093e5b9fad1617a564997d29ef4b59cadb948a7ffe23d1ccfe0916fff6145c812a29a4a7597eef2995bc6b4ade9a26f458ae9f246a844673392d42a3b91e2963a167e43014c3e6cfec494fcb3bcb9e53df51704efbf06f38c0577fdeea3677c3f5851af1828562709897f1909fde9bb9e130b1a41da16d93f66611dad333672c54c3611e85375ee771980dbdc753be83851ecbc3d76d66acf3adcdd9649a0166253095d9c0fac418466368965195a799f6f2fb45d37959776b1af6900386e5ed80ee0c0cdb5167dab7681936a354fa1bfe3b466b9fe5fda2b57cb74a5649f959d25e65737eca30d2d3b7ee4577349a8b12a1d15c49aecfe130f45eccc2daa7644f37ecf258b2bfbe0787c9728c9feeb530dcf258b075dd7cff59f7a28286f4b074477bdab9c4586b29129408976b6d90115a0419b1c4e5b1bc161bb5dd33bb441e2a0f7f61c1181c7063c64f30c64f30bfb87ff867de2f1a5a4fe7fbf49f4f77788e79c7f8515b99c6e51db1b4f9c597daca3b5c5bd19ab2e565c2e8e25e669452c639217c971738658c10c609adc71a6fd67c2be3bfe113bbf578b3e60bd756da438ef0618c10de67d980b4a93c6953f9940daf56ab9550b612cab22c7b2b0d67e5386ef54e8bbd030ae550e5105a659f436895e550e5105a398e5096e5c884541ec264ef4928d1081639916ac861e25b65bfdfea7a6c67333e4cd9b56eede33beb567a92f00ee848ee3af72e6fa745dc36462369aebc5f0dfd27141f9318c6647e4258de6ff5841e8d2f690dc5c195f4ae797a660e9434cd1ad4295fa10d9587af5180f42a156ba5b7219152a7cae66aca864b556523fd4ca9b82437530abf5aad562b5214964bf9942ce5a7cb4a30ee18ada252e4fdbca118ad25be22448afc0c918afc908814f9192a422291e2c318659288040639d033f288bc9f6babb8ebfdc49977f6a4fad8e662f47c3fe3e5dda2e77c9ff2788b7ae39f5f4a216d2b30a1659e531e53791923659a8c94f22979b768eab257e4c96899267bd2cb8c945f119cb357e4b1d0f7f38ac42bceebdf86830f383c8db1183512a594faa0e9c461cb32193b10358d7c6bff86f7d8f3a1760ea391522e18b2d7de6139dbdcb555bc590f89d2bf0d099b77a1441de96fb8a5914a5bf515f9d43eb63d1c367bd286b90f68ec336dcbbeb4f978c185ff06aca614769c3f7f8afc142945f1de7b4fca1c97c4f17a414a08af17e47bcf860b6752befb9eb4d8f56eadb5b656ab56ecfa5d538afe7ba5efd7892fec3d567fbfaf36d3bcbffebeebde57a4e91372ef0136e4803652f243a50c61da9ac161247e09efe6668dc9423d83c3580fbf3e1476bd85af4de60a837ceb3eb51698e1d62c6a507914a140232f5a11f929ae6cdee15a1b4af95db76652f9f9a55afafa33ead3d89c5ba9f3676ca54d65736e65874cd99c5b9939ee1d92c648b5a7dac7b75ab4967d340e3591524ae9434d9a0ce55857f326dec48772a4f4a11c6f2265fc1c30d4327c4b730a701abe1cfb72e00a7c7bb10661b86f35e9a4b3f47380d63b689f9ab2bc350db3f65d0e103a28b5666832d5592b7de72acd9a6b0162928610fa9037794099a76cf15253f2c41d98721d98e301a41ca8246dd915b554342400200023160000300c0c868382c1581c8929cf0f14000f69aa48624a984844e228c961144521438c21c01040083186185246431400f0874ac4ac3d31fa2432aa44f7e28989b6add407d155eb1158c2a3aa7c978d4a4de709e9d03d7d53b8ea17aa024a5fda815d4b93089f19a9aa10fd83b7c90272df9b15dc7da961f418fa3f86d7f97eeae3de94718e3f61cc1e9b06a2c412f78fd341f0fca961899c42ced075fe623e8fae915388c16c87b759619217864a4e1f42144502687aeeaa057ceda9c54dae1b2b50b48ad0757a40812b48d590fb6a2fcac4b490b47df4ca6b2235b0af56aa7d35beb487c84f102b7097cc2a5e3d540d341f7c35756feb6ea3b1af0f970f479abd4477af56227320c22a5d1e9d0595888aa942362bd16767e6125b024b1868612ed400679746540e5769425d6d7c875ba3bb6042a26bb686df6b441b4ec6129e6ae49b4e848111704ef0c792a45cc2667eed74354c7d94d9607d66f0d87f9a83964d7900ee6d4e06e0a6fa3c3a0859f0643f0f2e5419169968800954427ecaebc39af4785226e2f5f2198fd88d631e2eef6fd478c4f285be266948da071e73f83910bd8e9d41d0873ed9cd86de70c4a0fde34c57604069ffd9e88426e6cbba5f480efe90dde356cbff71e0e862d08da2980e2650bbd0bc561f4aeac4475f8a44de4dbdc06a429ad51fd44ed3b9e5438768a752d4e777f768ed1f3181acd86d1d72ba07cd701a9073fb7b160d74652168ae305031038c3f89dfa42570b0bdd673c6a9b45018fe1dde9ddc2f0d4be07ea17020542bc292388d26c3e36acf8529e5471dc352e37d11e7c34115350920d15481596aa2a1496acc6189a9a5f6e4a2a24655123ba8342ca4c468bdbf93cdac99af67bf7e6864253dfba93e14bdbbbdc106d7b325bfaadbc30d0eea302edf02a0d052483673b76c73db86bd47568b110dd7931cf19792ea39cce8a8a40d0f334c7bca97bece081387622f4c16972b3cbe191be9b3ac07e4d838d70b3fa239a6a2ba1d50987d48813e4dd50f9bf7402a41851b36766a99317aa4ca4a6fff9af05bba52a44b09ed5dff005a76272aa55ed42cc13c6b0815d152866268aedad53163e14ab24aef002e8b80d45a0d89ff87fcad0a9d0688ec69953b387067eb73da759ee37227ad8aa3863b2b6dff9f53c75caab62a93e852bc7b557ea0982a8fff028d22a357081507bf0c58595ec6ac0c28dd53066fc917191955bc51b2fd0e224f172ce011d6eb978c296fce1733ff7f2986ac9014b5faf8d6c6e8108422e9b47ad09fa9f371da9f00035abbd6afe49a58be16c1666716130b787fff802524c4b843a1571659cac06b8031a9041802d3ddcb392d6b347114b750df52d6597e05becd7d0fe4241a4ee4d9e95aa10ebaeb33d392d39310b8bcd842ff0cef2d455427bd0672ca523dd9fa984e3699b39538c8904afc1c84e239d2713f4512399001046610373aec0633470cd9e8bcc19c64fb58e262133475c3684e30dd6b2c8adeb35a2f299fa665601f735fb7a8d646ab19c0515c2b25d00266ab0034f3b1c917d46c0098b57a260ac4348698d24f7a1aa03641c0b844fc23ce15e809c61d4af6960b5d7c84ddb6b19a6765a54021626685aa1b40c5e9996ccedd4b7009ed6241b4231cf2318fcc192d686059fa40b71321c821a7003ba7f9e960cfff694b100fa3d9854f9d62ef8cf661d581c0849e70f8dc991751a7ba404d057f844129d0bce78569da906f4dfcdbefaf67797c08e5ff3e7a7558b6a444269563497347a7898c9c7f0d756245b54d65239eec0ebb643b46adfbc904d9b53d842da38cafdae36a83ddf684439c8025c342be602bee5d14766f821250d641d7a9e06f81c8e8f3243f53e2ab5c5b42d2dc86bc608c5c4256ab0149b0c9f092cb7a8f587afb8e6c05d9a95e53881dad59bddf86dc59e48b1e11e371465a070d8ecd7ad2f5406867d1b1666c43e677f5e97bce7b0c7154afb22955eed301635f2e48725c20a16f334b592763796ec734f93af7d1613741122fba3ce5eb1e9505b5e00a5ee3ae6579b74b3b870c7c68d80536eeee9912e54ae834ed29f1090e6f898b29d7b1a8ccf08f3c4f9ee19aad96e800cb3661ede1ed0d163ccfae5dbee54f79075b737638b98bab7eeb7ae49e84fd1de29d10e337cbba87d3dfe39c83e6a8da51b17c22c2552bd162742d84e425c53461521237f3829db5cf63442daaa48442041ca6bbba65cc6f6750a5309e738fe6c8cac859d00e3c22236c5883335c617113aa73b86445b3c88a41dc754bd8ccdf9afb371ac501fa6ffeef23555162bc2084e03814cc4029003425443c0a2d6054b45a8b209b07a39bddde6edc791e5fa32d545da5d24d1ac4a3809816a7139081eb67e3ec8882e8ba563ad3380fe0507b0b93fa1c19472d5e008d2eb7cd292b5b2fac9e9088cb5ebcf4403bad87d236adbc30e539107fba127422b89da2878604f59e6864548552c10b34930ba1c2f91fb29d2d8b94dbaf1583ea93e5310614752d99711063e702b93661cf29742aab4ec3813ea465f16cf410f8ba5f43d6a41b1107fe996c7e7a87587a4e3c39a22cb87e035a73dafe28d8f72f0609684581f78cf81bb1f0cb722c0cc9f31502209b57a88172a1a653cf29f86f48f59ee9b236ed56112e933e69da4674f2e50840b836558eb837bb1d4c243d99dc05039e75c359a96dea58bce0a1859bc6435d193dcdbb72cca231b7e99001796499120725c634f78662d2e5877a0d16eba8124068a2c3688b145c49257d8c05c17d9f18c856ba6395a98ff3566eb071c407700543e80487424a854627bae3379de5c2db1a67caf2ece51a78b671c277fd3eb28dc5a240ebddab2cc055fa112824844a88341317880c9f568977fe8a4d3b5e20ac8bc85885c2005c558ca9607d303c9cbf04e984ab058e9afb15b58bcb81444a126875cd7e3da2c94393d94b3c1ee840fb87b15ee5521fd285f984fd5445e71a34580e41cf557a497cfd0f3d404760a170b8824644a71cf62b2dae804dcf455fe1fe82b06e4055d3401f253460f06f4742be1dd2859bc32f42fcc5b1297fb5e4a80fdaa279bd0d384a7a42ae06f04232386afbd5eec578cfe3da46e3a18e43b12e3b519b722853f8556826d57fe046dbca51f629eba11543dbccb8a9d3496a4113a3a2e9e0351697075c6b38f855c38b2f1235684a55194d76501a50326b22fb5b78e87a684aed2fc6132ab536358da8d49cb4b8a022b8959fbe84320d750e822b8d8af8f605cc9554dc68e4ac4f936e7382420be01de876a78445ce3f49cdb0ed9289a10dc25e8b980cfbb4298f260da09d5a40d261ae6a2c217dd4825122c28837a963af04173eacc5a9cbeb1650d26dd598a9144b6c86d23fd81d9cffc33a2377532d90595a487eb3124e2de00e9085d890c8d9e07db00df399e1d0be05abe4c1112229d04e6c07ce296d0a65609d1a1a732165c3cb9a75ba4c70029c6ad31014007825eaf209278b0aa0a538ead8e4e3b70ded0d3499aef2891e80ceada8c66eca44feea897f9344799d169887dd65a550aef501b2078bfc151b8af43b36079e6f5de76709e5001044839670968fdcd50a49be247a06a5fcf839b87b4f67594d669293109cc3f5f01612e6618cdf00baa4854bf3404eee896e09d889772d81aa35a749dff0a92d218aec78699ad4b896509a3793c041ce27ad3c54661b22cc8533d58456668be60012b4ec7b2414f703613c1b51ec6fdbf548f6ff1fa842295a73c0d3820c54282d1ec74451fb48ec3e60090db4d43aae9cfa64e17c406c8606385546e74eb685148c429c27b49de89b7d350321350d948b28671e64cb0491cd84c18b9f2d13d89818242021d4f875bc0963b83a4a298919e9adffb18b0f9f898f3f17b1229f9f7fe81fdb7aab1cf9edb3779476200af0406e08c58e3c4a7132792d2142d7130afb4d9749df900321b5f8ea6ffdd2b83d3711709be8510ae277f27c4bc27bf628c59b777940c557663e4af5e9a3b414f4386697bfad431c9afb515a1a6beb7349c8be135b95df02eb1d84486d190b78091d1761494ea7715d292d88369ed37137c252da673db279d979641bc1a1290c074b631ca357e5b017334ee7e8ff64d7fcc85242310cbf2542b7ee71ee9074ca422cacf11e134196a4648c2191f2b07894487c5bf37f87dcd4f9aa9b38be54bf71cf224efb52910dc199e5251d53631bebba16d2bc1a40cee2f1c2e2a32e74e98c65490ff74515120c9baf4f1e203e788d5f4a99e52329d19ab99b598a0e1849a1c3e9418e29caac3ed57c2a7b6a49fd538cb187c02f1fe120eefcab80af0c1c3995143a1d4c8adff2cd04800048c0a2c9c9424140c68f94157634060410701510ed25614c2b3e4808c875550efe1af5f7115805402e40d4ab54b46185a7d0160dbb350dc59592589105e916cb11998038ca84363d16f4beb494b93dd1110a5198aa079d2bcd67fad4df3ca70bdf5e2981cb902bcd638afe0872a49deddb2bfd4f2ca292a1d4ef882f509669c8da917eaabaea35bc5e75e2c2207284251b3a3062a804188d739a1b31262d69cc99214f7a2f50aa5a15a81df90ac98645e9aa42ce9b096e95c081d5b46c04893b7e111c874b81a55ee52bc27b85f8cc820c16de04afd331cd08e9efe2c8be1ea70907f259637e17d2482f2c2c1a6380c991d4d367e81a54f09c39cf00d8bf8196a0e434290272cc24744727c32d7d9cd694159a7ebfae647a14b4239f108cfd94c3227e8814628d7728694af763449610830e6dd770f9e247738aae1546a943136359d0665f24765fc7e37793a0e0ee2815f3d9820999c21c0a3955aa568f91e8289b04c7de40d08d069108eddb6985278e62ec2dc3eb1dc03c0d267a6b6f9504b1c2bff66a6e7d4a85e0091d84a04dabf448b1abd01aad529918a2344e33fdaeb44856c881fc4137f1454010e017f743e8ba42a88e8730f8ba9a4bc2939e94bc82ec047b632038902069872024d8d4b913c3763e86898ed0edb26dbb4ded177630cddf90686d9955e8ac009e51c55f90d0b94f6a85077294f5099d672e1ca5e9e0ed81f431540b7436800c09ab02d27732c2e52d46119b1949c9ddf4c9cca56d87895501c9d50f890d64f255df0e91a880e9dd592f463afccae222a35d4defcf50e863473c03c28eda7ad746b1fc98a2cf61863e491d812cc1707a4e06f2b40952c9ca93be2f0869382779faee843aab7628201c8f71d3aa1abc0166a4a846d0f0d23d03e239ad77fa4bd5cad6d10b31e84a0f146f890a8f931745669664e8a55ce3caa7e61a47701d95bef331e0657c7831251a117175e650f7ef1f572fe4c2a509bfba3015a7205cdb9532225bd43350ab3c1b5995e3dcd0c24764dad9b9019af96db03e7bd0486bac6b310a3d3b454803cad070a65796a5606ed0c38522188df6b8297057145cfc91e1a48d7cebbcef5aa800f720354569837f14ac042e54c0037bf381f1a2ad3447ed6c95ee2a24502f3a202d391a842c3b149b556fb981861d1fa322908bd256d4a91c69770823c77b59150d3157718a38454527c7deb923723643751ff2c5854148a48ac6de7eeedc2a36d450e890c4f9658a07657e9ae1e758d801e37eee84fc7199a080760bf0fcf7b1612754253ac76450f7f9403621042633a10d9d48f291419bc8a11f1b30a477e6b3713896a10ef08af1893273d79510e18e3c3b02b33a014a908e16eecd0a26b8323c834d206e57faeb6a419a0ac770cdbf3afc2dc1e9318c149d0b3e7b4d3b661df230bff5952dd35496905999fee6ec25b6dd056da8c31b9b6ab323381c6427c238b038bb9240e78fa1f08ebbbd5f1d1679a3f20f0437b654586972f15dbd17df02967dee07dc93a04bdd6e3c126044d88db8974d9d5d06a1f22e0b1402c438cb238ac97664af4b6d4597ccdd2c03b3704fd6ab97486a07f0c63600ed8f38a80d0d593634760d0ce4634a57ca1e9a816fa842c1009e3574cf73cb3727a3480c2ce0aa2f4385cfe2c30a4605056874708df12620b1fe2f53b3aaa5c12d9cbaa03654882d165dbafb651c3af30175f1c52170f37963f6d09710b9a22c0ed9aa47b03a49c947ea9aa52b481841f754e09bcd90f9f9ef7db0384430d6f9ee8d09dfa78d4695b0380498f47885b261c6155ba7f30547b4a787015129f163e31dc917f10944f1ef0ad10ae157ee9690f1d9327413ead41d29c2f18f990d16020eb3fc55a3b103ed7e951b6ae44e1d46602b2aa57b735a1ca22960a10f6140845df53e174cf817e8b65da1bd655ca039fba0a8eb49caf6091a149ed108e8e59ed718d7530c25136196fc2e27bea8085bf9f830159a54209db5b4797c067a591b2a1ad531635a804a1f805901edf5ab80d3f2104440c0c2fa4faa86a5cdd888fc6bea1074ce1337e43ad406655fc4173978e2f510748a29765212297d3bf3a52b100b1b135265428e32fb1d35cba9ecc8673949b07d473e6837c7e161260e30045e19c53c7d05beae09830bb61f1be7c18c7dd05c960d05cf4de9b059edb15454c77227fc96004f24429ca15f39856c3e3cdc20b811a706548e077c7507fe7010600a8c0c8fb897ef920f69adeefaa1f40f1248d0fc01ef5d38da2247f94086039280abd988883cae1f910498ebb09ef670cf88609e04f529c225295e277bbf3470f2089b9c1f2b23609c37e1cfe4cf94d80a76ec10870bfdc072152866da79eea12ed2d4c61add66a41b7ef8ed789dabac6dca0e614586ef464e30b067980c0a7304a890bea800d60d46409513cd19ee3fca54b9d7e054cf8cfaa6555f20093211f94209fa59662d3cd419825c4234446c0f94af1dbcda84eac0be51a27418d6964dddb28492f9a5ba063c41ca475d8971f6ee449610a45de476eee404e683831fd9f61a186e49daefd2a14d6d7e92beb54fd861b97b5d5a91bd49f203291d0225780592176013b54641459aa3ec8e0602591ce15ab66108e45fc8a9c47403742957b7c1111df85d477da64c3e0ba9e22e096e300ff5e12b1301219b055c9f99e8b7acf3106faa13f40e5fdaa10124846c438eaf2ebaac3c8b08f236eee7e04529625268cadd0d8634f649ef2a80130ab8effef06abe364b472300b0a250dad203e840f25217ab026c059432fcabb35dd90f3ce9d0a8a38bd8d54358958f8f12a540c18f6fd9e6c048998f950750ac173685b38c1cfcfbedf0452376736445961436fb51eab4b5ef3b1a61517abeefc00e1606ac5a47c1ef8046b1a1f933d599e980648b64233beb513122021e1c7606750b496988967d20916b2085e9b2e74f9c087770d077ea12c83a4704267c2785b1ec0c1b6291aa2ef01a860949bfe943927e1b48d1a4fc60a891c00f633885fe20ec75518d21928f877a9d0fa4a2845a641730accb0c1fb2f3e5615adb6c8171f3b9f155817106948c90071585e330e04e2409b28fcace4e40ff1c5d01464aee3058fe981e9d3ca835190e3fb63420ce67698bb39599f28ebef01530080fe712bc2d3ad5e59243745d6884b894305a31448da2a5baf520ac91710d23ecb88fbd0df179509ee42031a5c1117a1eeff44801c6e4da9567766a58481afef769c3a543acba3a92d77a4d4b463ed86a729acb82e98dd159e9dfdaecd2f1015bbd7448da4875a244b64efb6a69b396414d0fb3ffa9ac470f36a95419b2a89767e7396662fd3ae81bdb66bfc0584fe170e6b1421cf2a8bfc5eb3049fbf6866fb3ec12b3331f89c8b71ca31d7030c8ebd0f89ada968bd366d9f7e713bc5a81f490102c3357b966b8a535be9e0e63ffb3a5b86b333ed0df21e1f794cf8f4adc9f2d6394d59585acc396b3faa51a60adf7066d3e95bf1a9423cbd578e597093e8f33e53c12decf50f5696aed60afafd633487d07d48573cf6555a7206ff33ea9f8b2bbbd0bb596d4229712c75ad66015d83485e7d0ea83681318a8e522de2a634a2a1b8d0c4a09a8014dd3b1722a371c9f990cb6375ec19cf84f5e5cecdad19e3b16b20f0d400a1a10d7f5d9a0e8bafa48252fa52000835980992ace5421a0f798681eb57ace4d7adcdfda6404324b39ff65ee68a084db536af1ad85988be8d3f83b3daa0d7fd31bbe1067b9cc1deac1f40cc6eba62431596f9e3b6ca92c28c062559540250aece549fadb498b9be2529d9e2bb8f47a1ff090acb83b15588001b675d5fc2997b60d57388e043cd5bb739591c9e5dd1385c60f1df5ee900f721843de40057d3d36aa448423f79a3fe0ebd5e570a3b8665729e2bb3c50fae6633ce4c67b1208dafee0bdbf96ede4194d6fa216d11929210ba32e47b9af367986d01308559bc868b136a30fa5d094452cbc5bee7c7f9135efabd7b5c1abfbd34661569b2a191c7935db5306e738a053ab6a8ca8ac14374410748ee0d05bf2d7ad8f7bd6e367ee59d9ed11e46a74d9a1e2c364ff0bbc30b9308df8f45e079de1ae66e2847e8785834056656525a499be3d9b90b4f93b4c2ad9cb6c7bb1cde6b737cad2a8802d228ac043eb083964008351c3be56010d3e533bb994b192eb856cd99b1ad568f7854fc56d30105f07fc9442a57520bb822a13050cfd2c37009153db729ba797dbd007c268781bb1921299c6380763b943761735c167d3504fd32407c92639cadf39617e3c68732739e876ab865027c308713d3fc9810a3147d5a0e4ccfab73186984e16a2fa6fd3915172b87500dccc7c8795e80557d11c240193cdb4af4070c63f3fdeb6501627c48354b73a52ad59c4d4aa98bcb0b809f07866be02cba976f40a5ff723a074697366be0f14c2a27275b3379e11c9f84efb03ab9e0aa7565000e79e97b0e18fdf0687562f92b92b0b122824a0aa9a529770dcbab01c899e6f5e0a586a317346ae91172d96caaa294c92a271a5e983612e0342f89717b81998c6c95a526ed2bc6f8a97a5fcf70d9aa4c495dd3980a9ac7a73f09bcbcdfdc08b0135ce5b2a1b756805896244353fd0515a4749f8ef11f24608ab2d88611c43de8fa6a4aadcf0123ea5c87bf14930950441984a8650d905319520d0c96b73a339d701e0976e32b2c523bc1342a672c5838b39a0642a0ba845ca5472b30666d70278f5cea122f1e29962a812cb355429c1a1ca21f3a14a46b708053ca24a4f892af3a486d6dd3448cd1c6213a744475deeb771eee00666989f924e09e5f038133ca284c5e6dd1ee75e5a8236c6cca06b29eb306d12f21eda261e8b43f171ee89e41aeb2974fdf09c227c5ce2e10bf495ee8f7e093633ea1098303315e6aa6b9f46e4543a671b528c2341491ae7be3d3e2c6107710db87a8d73872380c7a6a065a00b91fca10146ab341288a60221f0639eb19cf6f285d391ad26ef757f47603d53a4556c97a138ce5d01138e7b0e9f3b728ad3fdbfd9dcb3b6283c5c6ecce61eab7ac2f4f8b92f72a7a3a5d902bf82f79b23061cfb166a01052bbc83f5ac4129790160fb7dacb54fd56bee0f661bbe3f7cf698c943c7909932ec0337e5afb92f40f5896f581554cfdfff16b0b506fcdc0f077fd6fb8544cf43595d1af1423921bca73d789a28bb412f71a4e62eca1dfde42d2178a6f0c535c680aad41f798bc245a61a2fc8a146e1b6191afada2e170dc1a87816bbe14b095b1594f44c2a07d2d4394b246339e28826bfc0f8c44afb5a4ae59fcbca6ff95740db79a2be3a11d133a910f33fcc14f5ec4bad14235bba147fb07c92f54c92729d857b5948ec5d941fcac2370f87cf24c18b188589a4e2cf6400f25010f94c469daca36e30b84cbaa4c08357336993edac47ef6db813bbf6a9a27bb23114014d4a5e1dc5be8acd9e70c72f053312061a11539295c84cae468c4fff4993407957c1c5dd8c985e8844543245353b6c3e2f69d1a62d7858939da8163672cc669b99edf633864d0c151d74acafa81e635b8ba0a236b2e1507dc10b5d288a478b71d0ecd0e6af161baa5dab51fa953087a69bf12d044721c9a592743ecf760868095d6d0c66772649e58d7da0597aff3473e86690c9ae2a60609e3dd5a50e87787936795e01c9d626f97640e7b25fa23393c32283691ad1fdc9e42acab1e29949baa01091f117d1e9b3e974005789f50d029b6a763d6499e46ac6cbde32caeca301017c29aebc4db3a3ab69249d1730a1e5016253722e988e25995dbeb3e03348e46af007939e3397adedb0389125114093e4e94b56903c454d608f532a6cd4c127491aeabd0aa3317e74941a3dcb05686fcd9e995dcc4af283c94ad333d1e2a198a91409cb329eec1677d58582bbeddef0a05bdbbcf6327318c9ba9280920faec7d0aa4cd6fd0d34e6a52fdebeccdcd7be75341f09b8cb3ffc0503f2da6512efe15a527233200bbf4c4a16b2ccd27586f2941fd160622f2271bd7ba42d8b2949261d539205c488137b0559085350094a7c0be03d732396155863403efcaf60d0c259abbfa5bd846024f952a8d197fdb530771ee7a262f1df0f442dc756272ee5226a71886ea21daf092b3d8c79e0d7c24c2024656612dd64b8e11e68a573fae57629aff21245c5d1a2c86558b1a807f6667d8708b146616589af850434a3ba521b767bbf8a5f5522614835b575c5d2dc332323bd852e79dd3ac0b1d756f106eaa8a655b46740de0e0742b1d0e80d0e0da0abc04685827772d0e081afdfdfb084c3f10b6f30d42871720bad978f7206b13987a7e1c3bd6ed889afa0d4d2b76e7acba34829ea8325a407ee70971df61853d85155defb09567f92b7da7fd011e8090866ae78732e94684f3a2b5d329b242f1eb3e0bbc1aa6b74201cd92616efca66ec38f7213dcb180cf5421889a89a53c9eb7dcb581c9c9e1fecc3af6d8fc980708a4b9e31b15cc83b06d7ec015d4cca1e4f388536af3ca3f80242a8808222b6b1364c5351b9ffce3660a9e0a3140d7fb996af161a0c5f602482798679b95fc900291894b461bf79c3339cc47f1c2d85b3d3845758bd8bfc68587c3feb38e069f64a7eb9dfa873bee2af8a5cf433561076994315189694662c2210cdbc58c69bc1cec07a47c9c002f4d9f06b7c9f8392aa00708d7d3babe58181d0e94cfae898b7cadb579e20ad539028f05aec67b870e3ff045ebabdda153ce45ec913fb283ffc5e750b73afbc2fb53c84ee22ef152e3f53a724b3f2457dc4bf921a61135a4b927f6592aa9949be0e64537d2bc2fd72ebbf72f8f76a13024bedb17cee19b8aa521df7f3d0638f9b0c2de5603056504801d29e77a4170416e5ebe351483ed612a569c462c14396bb42350a90eef0cda488cde23d3aa1b0835d87c20a5814ea4191dd0292314ebeab5e8521fc57f730e734eabe34a08b1cb9a89db9426e5d30d478bf6fdd6507d8b82ccdfcd7a0a64dbb81dafcd203f7caab54803ca121395a5157d438b757a55a592123f8ca3c14a242d49a29111598c744f9c1e22f7127d52e593ff23bea4d9303fc39344966fd8f4436998d4c7c31eaf281bf3a1106a209cb1ca47a4e6969b57b6df677530455c1c605b85f191695d9e88b7047797f06424a707dfb6cb548a6f01565511d256ba5c4d2d9ba60dbbea493dbc1cb9f2f726a06a7fdedd5c9cb7f2532e63fba894fa99b79602906d6c11ffe250803e18fee43495eaafa3caead902acd3eacaf676aa8dcbaa0b40895ebd34960452c4f6b5fdaffb845b7ca3771d81fee1a3968d8beebe52bda22b2ed05799b80bdd7ab46a6400da383a8715f0fe8d006c84debd50631f89c680559bdeaaebb69e65ccb14b746a9dc1e770fd4a895ba02d1d22fbb45c36c1b18e7d4e6ed24b4c717cd76dad518c11f715a1e9b0163d60e5d5e71263c83dbb9bd416a5cd8dbdf34bb38927c1d5f4f9a35006f907242396a5701b1812435ca0a167070d414d46e52a364cf8e45ee2594932f2d8e12df7a4b6ecb4f4d6a54510d95c16efda5177e7fd88b76879caf59292f3a1fb12fc37f91e32b4bc568d86ab4d06ead1e61392068295e8ea93acc1d843bc20f31351a6a104cb5dbec27037cac8f421081169f712176369ee6fa33261f955dbfade795d90a77336a80f38d521cdb2b064db92f196075994450b4922e798e0bdd128e93bda991038ab18d4fa3a36e0c59514f8816d1fbc707cb9f2c6b438d040c782a6502f60970310f87c70b5afba6478de770316e333c640b6db22150c329017d3296f2c3a9c51f673ee84e42e177b4230dbc7e031ba4a3c62a5a05def3c331d4f09816a987865f8f15fb13b8cd6bc4de9c0923b51e72508c69fd855eb6d17d156a90258b78b4a3d4d8d91909ff15e04330a4e2f48599b2b6f1342e99c325e507ea01a3a44abee972fac20d89a658baaa114f7363bc303cf70ff10764d578b7e48921334cae5512fbaef0c5715ebf0456d91c7555233948e8c95b640339aceec7da52c375eee583d98e62115796e319e3975c08d61a431c1cccff630da756a1d7420ae94f7abfec3156a3d3a41741aeb5e5a331c2d8d52887538d0c04a8e916db204432850c3f4046336ab40c7c2ff834dcb6c145c7f0b921987c24e9d548e0a6c6abf095e87ffbf1459910d2d22d618d2bb392e4af40ba969353c0c3bc69e3296a5436d6c8d4d296b88162ec37057e358608709bca266be4a4acd1aab2c642bf6d1a578e35060dcc6c9fb2c628deac9535ba4bcb1ac561bb89c92dfd53da293756fe3881134de351d4b562fd32b42d6342f7e696be6fd3b991ba2be2f26bb0dd87d11d2a4822e91ce1d9e555a1a371c9e9d2799d0bebbd09bf7905f8ea6d822e107127a95c4edddd141f0ea2fd7d144c464671872893b8699ad0620142c3d7f8dd7a7a48b734867f01adc266d86338a9ec75ab1224b3bf1da2e1d2845d69d2af88afc6451ea7c97780b692ebb8ca7c3e362e66be989712654739c0703031f82b562c93412e2aa0a5e570733427db2dfe7da97e56522b33bdac923378df6fde56c27a538241aac00ebbe4ccc82c5d72de14bd160ddc5f0b97f74b6282242ed7595f545f606951f103b9d10b57d22f1f30c13eee76220509fcc586d97ecd2074c23895b4280f8169a4f6b603f0cf4077bbf46ae32bca9f68c97acb29cd79fec2e9b96db05fd76d0e18cd300548d79b96e079d77a56a143407e23f53c7ceade8e7a97c092acffc735d53406c6f7980227ed530244146fdb62c4b8a2adf4e60f607148d932215359b297058784345a78e15e9382e36c575d34ece66dfe2dbd496abdb71333499f8447c2e0c2ed6e2530cd657ecf043607b45d4b82eeb5432e9ac9c24989b0aae7b3c25bec185720903cad38d98ca6371d3f66cbbac92a47bc3afa5e17f502792d9b1e0a27013c0ce1d9bb4efa6309bda817eceee869c232ad5b6fba02222b6da30fed9180139baa0ed7861e5ce8743a6a745bac850c1958d5d779895bedebb1121b4e7d0350fb629ac3c9f23a6a2b2fa8cfd64df89c99a0fb72b5dacd42fc748764a0eef95c7a50ac7123a18128d2774d0c1c05b148296025e07e49bb7202415472051d974f0406ae3d0f01d11abedf2eff83b983c35c254711ee18ffb90acf151977108486c8c28fb36d6735f546f344f7ea8539592769a0ed78e6d9932cb9caef5e39db31b612c69552de11208c16c1669cae5a8b4cedcae1b997d1d913b71bd6859a1ab6d18e8589951f3adae0cad7248ae29cea8d5e7bcc7285bb8093c7747952a40aaf4af7fe6653cabce8e04b5858b8599cd575ca7d1baf083596b11331ca0561a3d4ae89001b84a276d59b50c34a06634732e60af98f392227afbaf37f01b85531025914c93dbbcb9137ce3e33aeca47704dbbb7c46f9f07be0f401085113e562142cda5ba6ef72450cf466c4d4960490fb1161603b0ed2a999967665dcfc7483ee880accbc288fb7d37c4ccafab424cef1fe6156ec1cd7706c037477196ec0c848e2ad785bb9ddbe51322bde3a0d52b74545dc4415887665af92f1f1426572417e4b37fc601caceab6da19fab0421a286e09d7796ddb040b59104bb9a066c00b5424c698386308049c4e6573388e3dd9777ff2cde82fd1c0f5460dcb5e6110160ea643f6115b8d23b58f309e829f5eba3457f4d312dccba8317b55093174bba1c155400f57597cfbf931417e88b67217584eee2e3c3da7ded786ec1995c520212192b173d6b9c9d20495a437ff40d08ff6fa13435214245f4dcb4b947ea3ee5b5447011c3fc9e28fc32e5ed873b1af57e0749d3e342c8f25a2f11646f222e35db9bb00f9e63f248687e6d7b078d3419b8d01d662454b7300801f0383f16a6567ce95bf6d142cabfd37a21147e26165210535e2dd1b0a035f65de860c81d42e4b8585f1919c63b6597b21efc016c13769405d8a44b5dc43d9cb164fd5e45f2858102e7111ff5592fa56994374afe32dc14c4204bc4bfd85e652f608f517e325b1987215402f6173da254f60722275387c1b81b00afcb294cc379e61f7ac23cdc6202834b96c0f74e2bb68de364ecd5d6638009fca3d2be2fff9a83e119c1152f030b7d7fb18de13c57e4042a4330212022e45462eb0c70c4c048b2648a8832bdad438f183938436762146ced90f50187d60f6e404cc83e2dbdb56e08bdb0ee28c6449293a60e88a064367e74f1d34f8368ac01c64338637dd5a5270460fa82de5c72e7c22d30b1e2e0cb3e55747f8957827f165c4f752ee6d8fe9554ce434ecb85cddee0e26028922d75ac62e25c91d7dbbffa33440c855d7d0a0e51b7d0f08ec2a9ec5b74b18ca8859dfbd4a964cdbeb8932256febad170aa1e0305dac12b0f06169c2bcb5e2c0d0d45a4ec54f84f43fc655f534b9488bba3c392ef389c4a6ffb4882120a73eec29f9eb0d86cf050ddb0410ea0454972f93c001bc3099639c828872e7dec183b1d05a02122a2e912a70cf2711a1b4f8fbacd8dcd44babbc0b29d42e5a6e3db34d4bb565d998f4ada61d041a4555897685436bade62022717c8789457156f0eae101aa16047fbee4a96d754fa51381065f9870c1baf6d8195dc2f24c1b905d0899e5225b930a8c6ebc6f0f4dbb23263fafd0faae82b718981568f453448193675a7f73c5ef0ca92dda003af6001953e872db63b445fa2ac9ce0f83a05bc0c7b5ab1b0ac0c0447c9f159b4711a81562b26eda1953c725244dba4f816aaaa55e8729d28e0380e6f20912e1b020486e1565106b5d255641f98999279a9498fc716836a911fdb89f746efdaa20ac66e6a9ab0d988cbf515c0f32b18964378a7775c4945fb62778830866c14d37035ca561dcb46d88df58971bdee227696999aa53c5aa0c388d8e69e65122dee7a16e2d3779cf1b84e18b15f36d4d6ff9bc1add2269ed4f0ecbe75c4c0ac23b2486bff039094e6bcbf1af47ca611a4c5c88ca8a52f72c2a7d53ec7d7b4d8e318cfa263dae41798b18ad08de9e84420c172f3d49db7211f1a4c08a45f9f418b6e1f0e4ab33181c1262e2b5940079dbaa956ef57defcfb3348785a249783422edfcf41fd3608399203541737ee5812983c5df76d38c73db58c348b4a2b147473d4450dd76068d18c935432d3c19f1129bd4effe40ba79096a3fce861bcaa5b4bba22667ae18f9f00c66210c79c0bd34448c391923960533f023d3e897a3d50166748b9da5d88bc04e81382abfa5ae2b5ace780f6abda1cb0906be536f1766c8c29d2ec1c953976e012f2ef005142adb03606b54c7ea2f0ae7fb72f4c6189736481477b82acf70d4d8fc199449848c3f859e3a1676f48c7ba6c1b0cfc98e693b081057d274c51576ad948e845eeaf577b65bd76bc082755543ca21706c44b506a31d183d4be66c2c266eff0dccd520cdb01aa0c9755b820e710599b0694b693b185fb7645a5e8e54326eee41a61ef9119d75165674956d4f91c71e7cd5c9d7edbda39c197ad4cd0dfbe10e6087c6bf0d0625886989bcf990531c888184f853bc4447c8c120ec2048d9c2aa05d7ccf5f280922507c7cbac5a9d93b2cea6ea0d1a182442ec7f0e77d0cf80680d3375fc82b9c0b2148dabd0b9c75d58e71c825fa0bea457af15c588bb573e33fc55a52133ff4e008a32902e4b824b7e0ed1f137b84495f21652b42f6b4d568969fa04d6384906c570cfc314941639c45f497b85c6891ab34f5574db44695a77a9a3f4411278c1cb91cf4c59027c2890eac39fcd9c242166977802ca35b1004064369b58859b63a57346b4c412f35aea64b190707c3bb1a9e9010a0ebb73541887b48141390ce8f44f1a0afb93e8ea44bd4444e9eb36dabdf8f0cc60d69b0195892127dbd24926b1403b597e802ba5fe10932530bd48e3914195072b00835032c0d169881829a838ce425f1ab1676a2dca855288ef9fc108aa3aae7390a4825b158928ca342926d854aec36d35fc01a13b6c12e9d468d9e821ef14bc224c496b7a27b51716185b14e48d0ccf7caebceb97cdad372c560bd30c325e57a427495cd04e8b098c213eeeb4be4324101c6085b12a45e89bf63ec48c4c6151334579e24c0456c8f11b9678157bd99e4267f9e8dc0acf6c3dd0d45ced96e27a6ab00e19b3f3f00f760568c56a431461b1c6527c4cdd94eb2ab358cbea489772ee31bd2b2248e5e2cc131dc10bb6b9cc1a27cf8ed22e53d6e0e4c7161d846a289f088d1f7c3d61b2370d67369bd8b1f833e82e289800e9f44d68dad9f30598da05456519dd5494a99df866686790f6fc0c5c2be8ee100384b47747a922ea4ca49a7b651a9d88cbbcf3a17413b93abd31f1f3820885af2f2e0c18b23b887e068b048e4008a7f5c14a3910772df448dc27fcfb9c5be34701a9813bd789ec0d77cedd6f7a96b54f4750cb02d3889f96d08325333d101f65ad9ea3905e5e2435658b8496c2e5429ed25038b943f44d297f2016748259798f864f96144475763527517d6822d5738736caea26b593ab6ab0b5265b12aa44c4069d83fcc1a59bd308811e5fe9e5cde3016cdec8a8cd8728503a8cf9b14195d6490f0577548616f2523f4f3d81487112a3bd3c2adb060d4727fd36769193b785b9af37ddbd28b37cc6417d72295d7b06b5d7692789e6967702cf068e5eed4c6d28014f47d2134fde54cc3d1e4d63874a60a3741020e1559dede191f583ef160160384809f62f53b1460076545e099ae48af54d20153422ca3b47ffdddbea80b032466774d908980087823ddccc3397659d88c14e1e3574445bcb11170a9d0c71ef70a7865b69f760b5c0a7f99a31867cecbdea9c749721508ca0198d12d42968ead4a679a20c5c1c9eefb0857d9551baf416a9be2bab51c1c501c6350852e40903815e1b565627fb50bce6fde39f5d30cc9850cf2724ffafafb8e2a5727d2a534e3401dbd0eb094143f2687c0946770343d181f51a3693b4c606ccbbc7e5fd835e8bcfbe6d1f1e1531b4c63a4d7e5fbac5c29c22b554b88aebb12281be5af1ae940ad99a0a567389e14fe22e44aa884f40c86cc3fe58d9b881a2ad001ded252188cff2b1531a69c1aab0d0d994bc0ed6cc921633566e689e38094c7c1555174e83f48535599999acd1a4d59f27659a6c1a68023b748d084c194e12063f343f791a66fe9f0f557ae13d20ef082473e547b8f98d7b6f573477aead911a92304376b6b42eeae270c54e2d721fab49658fc032c1e40d746453e95055bb29458b2e6b81e7200087c3f06593d7ddc1e2ffb2962edbc5c62bbd4663eafb90808925caa4053e12e8fbe4189abc3eff68ec60a91ca78ba9768a9262eb38ccc0cd52e371f5d34c5e9510b188b719dbe393f5d9f89c3e0c86674132e932c5163f82d5386851609b110a75abcdc1723e54d4714cfdcf485368fc1c5670e003f2291555f4a2456cb96266b397a4809607c0e22b010b1dc151e003274edc9420c2d425701df096299038555583d16d2ce2153afac46d4d8c643825c7e78f17b073bd3a0ea263d9e0e4b3840c73fb935c469cc27d46b76fdb3dfb9e965eb270a2b3d7185ef188923f35c44c33031bfbab4d1d4a83421a9bd803efa1f9c0dfcd824f2b516c9422d266833cfb2ace70c48419b9163a97404cc0483e682dde0926367db7d4a8d7b2b830e11207f05af0bed3ed051e0c3d795d30ad4165b3034000259546a87f9b6d001cba3c24aeec14ba843ae61af19dcdba97659f96dd39725bb5ea7d317787d47237b818230e009cff6ee4a983c6766642ad54d5897ad7496c61e9f88d20d530494038075e2c607e144bb67b7dac93239bd552f64532d65ed67a411b162293e05cd1a6de66af78652a6ae240171e19a760a77427b7cc1ebd9f2fa2772b95773cbba239a149df7a2b3311fa9385788fbed448b46ca0a63d2bf102f138a5c7da7b7df4ec98e0f10f86536c78cf64be38e4af76945c71356720bfb135fb499fab5a1105bd3165eabb5cbdeb0db0babddd94c1ab8589bc5f716694d4cd98a43c5280688f11494de06d0728555fb22d7797c915b035fa4e4fb2c2ceca43449f137095e5822779be5dccc0cebc5dbd351cdc72b61296b58dca627a56a759b6c4f7757e27b8c3d5d1f7053e51fe15ccdd61441febe590d0cda2c26804f9076a672bc27917018865c8bb340f69bc0327694a23dc6bc9b9308ba2487f2ed24e3a36e5f60b78973c24e40d0f7f139fc3e71a4dde4f6ddb39e0509648569ad13445ff8ea049bc19939c5409d65044d8313c6149f0fcba45dff12d2541710cc2c5bd40d35269fb3d9381d67ac5689c83d3ac5211e8cd0ab5ba073c701663601e32fb7178d23c33e491645778a5b78573731d2511bc64a7939d49d713b218f5bf018bf04f27694e0a04152c8a42813ed429ea2e11d85caa0409900d95744d1c904dec55c51a32c1c7c32da3625bed1d6890b5326b517c49c091436be0b457b19315f0426045e623d7b6489744b90fe39967d7f918e9684983641632d0fd5e9851ba7e313c17c0a8ca25578a53ab6e4c541d93a7b0e4012dd91a69221492503bda864025432fd692be6801a52e1dec2191f7a0f9f833385724ae973680666bb8c5736518e79910ac97b0165da926000653a71b5a985cbac6267d3febd007abe005b1ad92b26a998b29bf1c35884175e3bc558ba5cad7b7c14968a49f034c123bf09f48f5675638b938bffa3dfd614eecd6a99e3b7a38d339c38ab279584ea9eaa83ba934e0dbee154f5f994c9cec6a09b4ba2788f1b810012857349c0f4fcab8cd3c486570ecf6f2acb3e787045a9e3ae6748a5e92356a45f81068471f8e4c0e5e8f9966c8cc38cf3a46449ad24e249cd5e93251b3e202e0e8c8a62bfa6230b2c70dd7c33f36a32aea21f8576064d4faf7144d7fc2941edfb62c98a4a88705e40d91a7395ec571718676850fff3d869053af15a07f831c38a3cc4a766c05f316a5a9b0923c99a9b227335b74f3200f19642831d56849ff040243d5f974e29728a4e824302b08f2231974db740c55b043475fdb88e6c09d35b0ac15a9448644611e60ac652152311926f827960de9f2435bd592ad401d0831c67de26d34e2e4ccca8b78382ae4d86a39fd59a943e295ebc06b237c0dd4efb4fea9f521f5d152b17ee8b845b398ccf4006010ec9501c773823a6f8c6485cc4a06fa0c2e4e746f23e35c462182757aa9183e0633d96877cae1a45fea7c82d42dc05cbfd9704b06396c4a593919598ddd0d3c91f9a497614819feaed586ae4218533cffc599b836f8d3ea74740cc7d0bddf9728308cc44b5870adf46e72d1f7bbb876f8cc5e85c7712a6138f3d621350960d1c3798987b02b678f738715b3b0f900c8d7e4dda1c3fc63c7e8dfbc62bf453bbb79b05a121426016f3099990af1ed3156c3ebf0115a604f4d614437e9272c51281c6d94c3d20cf361e01b1f65457c8678f2736f5ae3f09c4c6a116be2e3f859a83b930878554804920c5ecef1e901f911e41282d0817999ebff6f40353e86cd71b10379520cbd879e31792fe484ad1e811793879c114444edced5fb18ddd418083e35b3e7be117caa03c9caa94d3fdc7d711c22690bda5378f02a41b7a67664a4c74700d15617036db15452140e4adef59777d528bd0e3eced641102a3304457a94f59599397098b7343cea3332785b0bc06c564ca3f220183cb60f23861e8e48a55138608bedab581bc2c0db7d9608d64e5daff656cd1a421edac9463f061aec8fbe18f502bec3daf99b4ccdfbeae057434d332e4fe8ec8995fda6a31211cce9a1f6c8b725f2eac4052f58bf2ec51223a3eda21d81eecd28955d5c8fdf1e57a0a715b79a39b0b64ba54f08840bef98d1a3ef54895ff582f2e8fe2aaec46c48d5d07ea96095ce40716021d6a70a2a15cf660179bc14b55881c48a284464352a5dae6a870d30f3be8855cd8eddde476b31c851bc3adede66f7865f8904ad5275e2d4cc117b7eb439652d1d37a11fcc51afd595d8f0bd838171ee36562a3181f2db2d1e382833adf85616c8c29857b9fd755fc112e1000fb65f03ad08834bca0cfec985b9a52b52d48433f305d000f72416b204244ef7d4786030661edb9968f234b67a7e9da0ef8afbfc73dbc0524a31dcf001232097c47602bc0654d9644c2315dab791e540e2a02c0aed9cad2c36d5063f28f4235ac0140c39a1affa88919d62c80988eedc252a590f373e18f8c94873f116af8978db7817b80a7ddb6e0a904bfa14e918f4747bfa186918ff598688635a0998f2074c4bfee4539a104320aa0f9253f38e67d5d7d742df9fd30b631f7e459136bc71c3df45f9dbe4753147c808bf67c89ba2db66cc083c8036d6ee10f3b3440ea4fbafac336cfc90fe542033067b5bf7ea35923156bcc5f5dfeb0b665aa44c3618d09f0058bcd6235a7262fd6b1b1adcf459db1aba100abbeb2830628687af2d111878366f070c125a7da3667ad650c42a671d7fbc56bfaadf147a462655ba29ce06d861b9e25c120f32b457dd0f5cb47e1b1ed620fd4f5e30b43ba75768b2caecb0b5b4c5c5b545577605bc7a7cc61b840f5989aa589be8b8f59491ad6e24d2020140cbdcba2a40d81e02618d2bd69d77290c74e841c37af72dad97cad905f4d2efd4168e959cf2faa11d6b3995f0a2cc838b4e3dd0faf94ab63da853be4742e396570883b72b4b6d2f8591b61270d9a6c1993081ca8a1729e579148f9983cfdaba14d4b4905cdd5043a85cc991b670e6934f6388d71547e9070f65faec16e979399b8e9ba666a62d5b3817158f9f4a7532fdb2809fec24917b0660cef7b1ad10dc024b822067e348ab432a87acb1163495cc50c948fdd8b6ed7885a73c60982b5e6192dbe6924a919192275175480d528036ba18104d5ba78f8c0ce35f5a6102008d6ae8fff7869bf5d35fe57d9fa6d3558d3a1ca60ad1676a3a8d05bd8af5a0588598a2b1742de5777aa84356ad15b9a05786f42af460e09df4cf55662a825c6a9861cbf3235e861589bcb8e9b54085fb47c1d3ee4b68004a9712878aa2c1496bcd95fff776637789b72bce5c2f7612d2a852d4c0d25eca1464a61544e1d6121eb62f3fb84137a9292479ab0c51a3436795d183d18ff4185039627c5fae7049ea9502bd65ef5c935551034a2a9b12b0b701bd1c703381f56c9581bc6acd9a4c5e1ddfd59634d4e903910ea5fa84f8e47ac13313779f658630fef964539f2228ca5e5628ce48d206be1178a5539248587d0205e05526aa223c95a34344a057ede17989d77a0ec8003d593c2f9063c352495b5b932505b06e58301bd24baf66d90d06cd1a48e65cdf4e0498d1c1a9717759d990b6b3cbfdedf5e39ff95dec954c5b2aba5b2661ab78d7fcb854b25306ba63d64cacc847655785fe3f08d63fb6ecdf4f167d612485793647eb29c9310352de78a39b336d6986642ad2eac6a06c9d24dc1baa755cbecc747d6ff4bb50edcc757198046386e166a9b4a9824c2c50989d0fb3871da496ce4a05a4764913c89b17ca2ce2dbc8ecb9d6e9f707c12038bc275ffad198083a9126b0a00ddb5c470c896987e99da0cc5c018d3965858734b4cf9aa01a0afd8ced003c61f3446a35077b034c0443a38c4eedc646b240c8ec218433bd3f3ca9654f217c2cf60038d3f68e784363234db58cf17d1d8ee786626b2c1fb83a7d129a034021be89be0d7b9ac94e6df8e38cbcdf0b227f46e4a4f0d4c6efc8ca78169745b9634be48365b6212a0b07d42cafac151aaf97dff4992b1b6cb6ce0ca6dc2a9e82b0524d6777a9cc0b41e210daa141295843fc3b5752ea7e21eacee315a628b6835ad6cb3892fac351fe92db7af1c4368c890c519aea15666b8d327c6014f7b69a1727e86f2fa1de387408fb0659e2b1a8879cf9ea417f17a38ebf1900695b36707506172f9f43e18cd38194dc28b8325ab613a71b371fa84f5931f9fcb95b9a2f37b9bc0666bdde36ae4a67ead16179d9242821039959428b5faa36990b22a651ac5c2b24642d3e92310c2b04b4e06408b803b61ab98bb9e9584d9d5ff3b7ca5c514fb54f7422a2bd1d96fc2ff26714408b675803e8b8919eebce5f0a3ff2e5b91574919059ba27c9ae000a6068d07e108cedb48172f6e635b6ee3de2d2388a02ae42af23f5967ec6d7496ae6dd872efc8f6f1a523aaaf240df40596b37b8ccc4fc147a7885d9a21125e779e25865e53143486cb75777b8fa843a77bd7ac1b5de3ef2f7761e64eef7ad097b5c02a8ef51b890042a5db65969ef1dfffacc3048713006d2dd71b2321ac0c7d96404d39ba749937d1e1030dd5e94148a54984811bc3a4baa36867ae43c5f38240dbdb8fc3c807161b836c1686af53c8033043d0a4712007a45efd62a64ba6e7a2f0aa8d5b0d412b296500be469f34205dbe5303f37ca7878001582aac50456668da9b8dd633c43b4f9e99fec8691f580710259c54798a80a330c65a86b14a81194cc518a192de7bb965faf261051c54e298cee257d70185610d671ccabebd70c1a615cd9310b46af655986139b49a920f64c6766dc01c2164e1adddd299b01e77c858021603c541c7f8165191c3133434c638ffc666c189aaa3f939d776cf38f8fcbacc078a5e03dff4a4daa328093cc1879f9139deee3ef62364effad3c9606d7133520a97d312d54f4bf8558aa3f0c137a841e7d2d1bbac10dde52c4e766815c9e19238c0b2e0d484ea0a0eac76865083c6aa58c5b83191a00c9d8115b0c4601712781c78456f81462101e94aaaa9413b38586b80350d23ccac11327204b41ea3f60bf7f4fd044500cb2ad003addd683baa8dd1ef937339249b283329442208a03534d18eaf5c2ca3601ff74294fad1abeac2dc9e60576d4cf32861801fdece4b58fbccb26c95db45885f846a9bb8e3394ad935f5668b5026964c8355c2e5d6b88606d2f2566dccbe0888e6fac3f44a5640bbdf588f134808f43d8acb9f5173cb5d15d382d4dc2d0f819028df6069a5b81459d0e5a20c6ef98b3b26e674e31841f12373d847b0c9a0c4b8452e95a8a4cbaa6b817a6f105b378e7568ddc74a1cc0178b79db9b2553951b95ecdc38367fbf177e37e5d6e78ec3b3cf3045b5115600611427397fa9fa5e7080d934f9483b44c76e21c51c350a6775f325b52e08a93b760c1e2640b263cf3824ac983dc93e27c26bad64c75cbeb78cdf6d8fbeb57865b646c068332592f1b1fcd503676a6b87c0dce145ac51a0ca5ca9bcd536ff13c7e18c872c690f1ef719c8de024becac86b54c549b6c05df73770e93af51e841c878871ece67909d478e7bb177fba6726795267e36f969118b2cbb8b9c361f49adcc212a7425750beed94fbe20d8fcb54011852fda2ee0ddeea797f726b2880eb3a94a5ec82e1c588db82e9cdf26ff9d75c59a7cdc8124a003e9172141823fd8734f1bf29eae27a9cf05bb666cd084fc4443dac3ed3f108a7ebf5c831d46af9580ca1cd9617be7b70d5e2be823837d20f4b0fad347a6c2b322e88dc63966a52f83a2b34cf53027c47231408ae449c81ac805049e74a1cc889bb1e5e227f0c2a66df3226d9ee9eec0fb425231ddcdb8cb8c67378a9e5364582b92238033d537f8be14fd6d999d15d52904121ef0b270f3040305c545d77724ba0da628222f349f1b9eb058e69da5b04c5dbbd79a8c1656781980ec7aef16002f154130ac81dabd0095c5e4e7987d821ee69a66ec9bdef722b968c61fd0380ddc7bad6238da5f719705086deeebe9edf8dd417aedbdf03ec8e7da7b5517e1eb6ece21a268a35e012be1437f3f80aff63eb5345621f12b39a570aa2ffe532bf924dd9c162d1acfac4587c19d7f50cb3f40e63af540c5f7e215bd7c0a122df21544dbb6786dfe86f807f09cc49e9bfdbddc5a461294c158c68477314ff205bd93966e1b4b75f955d0629d951bf28852c34f1659a519e5cd66be8eef7a178f584c516d842a34033b0b3c0f6af8eaa0803fc279f109a0accd42f5530a3338a41eb2b8b0d10db9704d5d162d6c848eac378c3a8c2d7972c949fcb905c1de922d181884e7ca05f3956318cbf37293b4a736279e3db0523b236def50e5480d6327522003c3fd32f491ec78ba223688af186615a598009d35f045a010e8703d817d46e0788fc3969a62e85b168616e34d9b38337450a0c0069781c9ad77217098297f5533777365d21b6cc64d19b88a5d356d75b7dd94a42fcaf32065227d79cd3a75524406105a6abeb6e981517fa5a3ff5ff376817d6d4df409807a07685e124b8c3c2373ef4aa5c016f3add90f45faa2c7e9c50a584ff9fd25f1bd061867fb4392847ff392b20ef17e1e6f917a54d7417dd66f63a20534b43693bc987d16edebb4999c8827e6e3d3e3e662800c0dd1ccec5e9700ce89daf44a38c3b7b4f5cc93a8a64d5bf44377b9eb81155a4576b82c6b4bb4ccfcef534293ee7e2575ccc4538fb5d78db269b0c74b3ca5083686f52eb37b35d0d6a55051e9b291ad7d4d528233f19fe3fce3dc245bfbaaebe038e8bc8925cdab51ae32bfc058811d8e88a23505c11a386f387bb282b8dbd27ce14443acdd44defc4a2de7b623acdf5684804fc6b78c59a625f22bf86c4b5ca995a439778c270120ee7c986ac72a6af7db42c4b5660b5b8cf3679baa3d5e1ebfa8b49571e8942b56497b735b9ced804a5c2b4c2aeabab684451ea05f66e6977a858b27d08d9f249d69feb173f0cb66b677ef5d8edc619662e8a39e2c79b061db2f6b5bf8073c67931a1c84663ddec0ce2df7dcdc6d6b3b2cb4f499bab3908380530dfc3cbd2333aefb36fecafd810ee69e3eb253cbfa82e9cbb9a58541a4e3b6c15505c7c4a7c398703ba5ca832de448fa3f9df6607dcbe87a7ab72dc85f5c70649bdb5e65711da5303b54ad42363ff4fff1c038a9442fa7d589eb98a8c05f80c8fbd6f8c367d01946089bab8be28d53a95dede0dba5966c784bf875a6721c82e9acbfe9875ece6fd12ccfd72dffd6a70d138a8a4ffad7784dbd5a8010489c5e66377002cb7707ee988749a7ac06c43b060f5d6e78b5bd52931ecfbce18666ca00243dbfd3a1865125180bb3046e3e30b0232c34c59e0e8bd5f3644c6a1df92e4b245d8b3380d6c95693c61924a4747557059c2bb09993ee39085af0a2d281457eed59dcc25e2c264115c8eb0e9838b64b2148cfba90208e1696b3acc09c15974bfa390549b30f1a0776416bbfff0252cc59f00cebaf743c4ef7fc7cbb62a4594b6575d1b971402225660e11d9ceef40d9547d9db3f6703ed956836a11ea53c7817e3708cc42d89c85e75c584ec2e52c2c9c989e9c1d7892962f179c92b109286391f35218a27651f6405d41073d8455b8787dba021aa6d4a61ba5205c2a1b174f6bf67006a2dc50fbe3e2bf6dd97c35484f4decd989b89d0305bdd81603b72dda2842c2d4f4e5544ec4121c8f6e15a2cf15408b032a3b4ce79e07a223175a78c721f2ae846ba4bc0be8b04ee2392a92bda483febf6462133ac9c041119c145929bf427d0ea4f26a505f8bb9405779f9346b1e947a1576c9ab5d1d3ef4d0f6299aecdaef289304287d2f069430e0d4cc7366fa4d099e2b35363a0ec40a7603bb29c69f2e63967fd1955505d4a05713066d289750f48b4f00191b0566212c67c272163e27613917d64fcc84818383af709a1d398923ea5ad8ffc6117b6dfb5eea7cc88b97d4f9c028a1ffc1a39ecacf278dcafdc787d12ea0a0debd936116e6644dd3f90916b222f3dda78422f0e262dafc92085bc6c15a7027001d4abc3f40e45e189ed90fdb66a2eb26440cd7ca2e5102854c612765e5c65d3f1c8dad2eae05df4359eab2ad9c7559061afedc6b2a583c71e81c7121d35db9308f3fccde020eb356954ba1fd66d474c33b3f91a241f9fe8b063e9fb14722e725876575a5b64858247ca4904b0485de70a353fea58927b4ebfb1d9c0e682889fa09a5347efa764dafe8bb2df0c4e7c6e33a8d55e8bec73c94a55df658185dad643e3b72603be40c5b6271294388c51944234ce4de3ba9a8c71062f62f058dc5bae9d558b5c432d0e6f57ae29b1fcbd654e102d76eac85d624a1382289f0855346356b6da6f0997d2229de1b571196e30c39679677863c67ca3bb364821f7a5c6fb986215ab79773f6c6711c3a7b1fd8859d6de68dc11b13164a97ccc09716fb8a13b9385177de2a08a7f3f06d08f0ffbfe5a0cf3184b6dd3032b92c0bb592cb9846acb53fee1efe2c58316e683d183f0f7db1943c37d0ea873f926e026f49d009f0609d74fb5e79f5d4f5884409562cbe7df7d213df1f0a82c61c508317827c049ed4646458e9f824754859975d397793a87810a3025ebe9036191bbbb4bc7a1d7ee803ee232a7dfb2bdcc040b1f28d587172e0956e3276f530795d1f87c3b2b210ede153b08bdb1992a38e3895bae1a11cf45fc77f76ffe7c6d80b1fdff6d83defb08d6cab43e80d2b29b7f3099d23a4e1d41ddd1015b1883968bb3a13b49d8e124824aad776644f586e8cf5413251ecdc86f23a3abdb46f796d67ba0d4b01d6c2229a7c76c69032aecdb5382cafd712667e12ce2e3f40203b4bb8c353dfc9c5587e1de5d42b20457f77ecc9050792cc890a6f21b58a8cc15427994136674176d4248160fb72d4e962ad04f7b513329c94af432738d4b90576747d0854420c376575d28a15cadd6c1168a33840a39e782b49a8c4538c29485966a322f92e8e36f14eea66a560159b96b518f5b87fcab7b63ea7a6ee19ac93619036e816568749506b1cfa2d69c2d6645d493a7c4519741718e5eda686108542a1a03686714e2e4f2f8a51c4fc2242a2bcdcd33788ad219c21ce4c20caf2a588db147341e7a2de92f318bc340ec4c0e1220b9f6928d0bfe95ab6bb88683b9b2f93c51b02298afe44da31aa1e4584f1d0397d34ddf26e24220cc804f1ca142200aeab117beb51e05e7417eef7ba01dba6383c02ed7b67eab3c62e9ee8e27a061b687901fb538c4b465562bb739cd20a5882929d84f5164ba449eb587233a76f967d39b14bd5808d953f2d25aa1f995135645a0cab6eb15c68013537c0542c8fce5fc960aa58d855309d8715ffd7e536905915bcbf2a017d012f55564fb468e8e8619ebad23087647a7d8e2e9b6123175ce5ea9e0231f101fe7c481ae8e8f15840a3b7f708012280dcde123306d669aac59fd68fd4e2a4f44648551c165ed9ed3d58d760eea55f12e8ee07dfcb264252ef14231d7aad3221f44994c41fcca332834adaab9be18aebc12e14ec48348b7f1ba4443f4b574c90d70d73a183decba2c27ac3c01579713f4625aa83ba56924f9b97c1722c1586583c7ae28fa34a32e89f124fc5f134cfaf4fda9e8462608f6fc4eb1e1f71c9ff7c55f47a0297dc00494a47881fe8bb6ded07c26f2595b2ade29722c9d5b85066012781f784df62930a81574263dc926a142b6dea78c4b9090422fbf23f73e30382edbe6862ca7f1195bb526ccdfc4133e2d0fba29c46f32f201f636502ab0afcc78d55e9abb7ab0f9a81c6f40a689c4392d61e60dca27942f620d146fc3199c85b172123dfe34579b1f5406a76c5e4968b488ab7c491ccfac084adcc0d59d4aa2e69d20620da96e18a512ba0513c60d51723ce87920a48968f4bd9861f29bb848f577f7de2c4aa5b193e461f22851e82d7396185ab52846ac238caf87c3acef82a6dae2ef80887c0a79c1168395b6fb0cf7bb5d0a4e34ae46b800ba82dfe4a3d481d5b3377e53e72c0f09ab109c20255f0e38d5c8d71443133e0038a074584849f00078182d8816015a348f5f56187855da94eaf2f3a8e01d0df37dde9aad785a997eac834d00ad77c53ad1f53fce78229acebe3bf28f680b980b5a702a653ca72bdda1478047a46df459de8d06730c6d04bea118a8c81f57ae894e8b0eba1e14f9226964d4c8d2341bce15a7d9e2627ba760319996fd5cf7f97009006421fce0d77f842245b1534f429630456942a6f3865fe1674cc754092e5e31ddaca50fa6fc368918318712976634b3aaf032158156c0754638a81a878b8f680068c19a1251eb0bfe002d1412821a8bbf2e895889d130e412c7fbce3d9b2d875cc45c25c09ecaf829e92bfdef186d9a254c126217e1c243e31c8d3af747a027b08b51521d16b0ea530171c780e3de8957d3dd8d2161768f0f7b6aece144ed218b219933f14eea3bce11ef04044f607ba0c1fb5e99b1b4dc46de0d2bd31682db9787854dbe3cfe6934a5de42f2052b8ebe2c1d330f3cc240e8b60d478af3551747e94c76e5aadc7a5666201ea53cac9f6824ce0cd5d1740a9e550367b1352bc7989dbcb234b42c77f3c1613402dfbda761e89d079e5a65cf2a0bd6a50b351723f07a55e46d7414a7fdcafc1b8142d096050aae2370fca95a9d1c85cedcb4668e6904abf12443109d484248e64351d848c419c3ff676c6ab8f7ae66c76a6724fa215e90f25ff572a2497a0f2d22ebf320a72a94b135c6e69f03ead97580fc11b1cadb02e5a286704277035e7f8e425e1f4780ef9ec94812b5c0c2bfe62a30049b048a86c3f70ee57426587d42ca75ffb242b41d085571f30e06c10a3643eed1c73dc6482495e111518c36c92c20314465ac9a5f04299f2e15ebda7c45971fe6c1efb1ccd963d2e412f7b169431031a828e7f58301e554d0789d67b610c3afa497d8f4babf19324f8b1e9add4845844fde35aeb42a51befb565e8a612ef9d7f049d5a3800a89b1c31dc1773a20a9fe50a106c86788ce67ea2b3175179a96251e8fbdbdeddb6dc52ca94920cf607da07ba072670a569d0e09f42ad924da24ec9247c56b7fe9c4da314a0f411d3a8fa76d6a9918b3a65c4ead4286a3b3d9d1ab7964fa7468b736ba7464d88893aa5c162dbea56cb82754af20cc1e8d6ffa1c30e9eb3ba23ffc0eec842f5a787c36fffb6ead4c8a20d09c988669df260609d1ae5cb12e954db1cab63773ac5976fff66b3dd6cad4e499e24bc6efd0d472bd2a91dcda5f1748af4f5b557a769b66b710b61c820e4d3834318234562ac97835a0f068a1aa9222f4f1284dcf04a9e210cb9cca1a945e61a0440dc11c87da2e809a3278e5a4c420baddba04c420b4317068a568bf2c218292259b60be0049f4cc248915b8231c22355e47d62d6a2bc3a58faab09681dd55f028aae7215fd4d03c2b50e4d704c0a87940270e5bf743cf87afb015c005c6975f0dfc955a5b7ff2922052605e897668820711ef7d2cb61ed97c07794dd68f44b8fc7e728fb4b7cfde3b7e2345cd705d9dddd3957be7c4d7e954fe54ff9de5d17babbfbc4de1ec73b73a44ab376041010101010101010101010101010101010101010101010101010101010101010101010908752450b7cd2aad98dc4755e89e42b31496152b182c5a9858b139417308250824e825c04b5083a05b1085a11042346d08a201541a6a0144126412541313ea824e80b22092a0579410f0679415d1017440a02c32052d0166483c21b826c901674434a90169402802000e0e02a66b87dd2aad98dc4755e89e42b31496152b182c5a9858b130772391831be9311ba919939305ea09cb8682173fb4f0fbe70d60d85099d58ac5061a2b0db9f020c51da75e3ac142625dfcded27096f3861569db58ba4e4d5d9edef6e4871c12ccd35eb34d7ed27a500a045c8bafdcd3724160070385d0118f1cd9cbbe5783856dc2d47aaf4775cf8f8c72de7f67338b0b81e8e15d746775cf802b0e2cae7c46ee6c81618573e47da6c89765e0ede0bc531c38f24a342878b31c467add434aeb529f539a73bedae1e0e8f31c4c797ef6871f856923f73f4370bea6b2ed4766a86dc76c271a4ef5a85801315542f5cfc5358d787e0e2bf82c4ed52924708abeb5fc3ff0647da6453afd9db7b69caf32b8d406ed7cdd7be451e24789243fd39c36eca290427b97d5292aa752536d01ac6c914ce767591cfc9d8b5ae922eb4ab6fed07f9a66ce9dedf06f9fcb90ec481898f7b26e190856fb4f7eb149b3a453a29bd1cbadd93486f65cbf6422a81d61a7d7fc7538a2a50eedd7be164ec9eac6c09f28da7bbddd3139cd33c1d9e0366c7d1aeab9d57ea34bb95b850b6e8e5e0007049e149cc0c1792af770889e521c275b78e3d06d8dfea4bef01b38a84fdc0f7d919309f36f0ff454104d68a80c30e984c2e4c042599cf7dede5d03990e0fa5306ccdb010964b208ecfce9019f724aeaee72059c4ce9baa7e1079aba7f6aa6d144a70ff3ca146f1e4ff3c78fca70acb57eb729f30f47c2ff1203c60b9413172d4e2c56a830a53029f9484a5ec79136ab553abd7988bf8deef6b0fb9a8081747710f752fabf09b8919f7e0dbbaf45538b9372d7e21c51e69f28d371ce28fcbb1fc8a52d2d55c61cbc171c33fc804c0e0177add59f824d74fd91d29631bf9429faa3fcd9d279c6f9e8eebe006efef6f5ad54d9afac7d979fe4821c8ef5995f0bb93e7fed00db21be71de1f26b781e34866ab6688987de7047252c437fffb3effaa209530a7a615f1f1cb94744ecd7612d693b033094afb74aa36364d9c9cb0028ace0bf3e7c8b7049d98a6750d7c8993c98511279389523a27ca8b329da6134a9d9356ea4cc04862e86c776744f79c90f020dc9ce2c934e9a4359cf3bbcfc4c4a63951eb9c93de6927ad3673d239e990c9761fedba1c7c45d8eeeeeea642777777a748157f184473d21899734ead4b62958439279d94d2395f60b2dd673211156189192c82b8601e314289cf87d259f9eb9ebbfbaca593d2693a99996cf7cd5abb6e250b4f27a5744e16c83418a1c4a70469e5da92f1fb9ee4b94f019a8025a0f41f972311030948b9e7ef724ffa418a71ff6e5ad5540c8185217858b9b4aedc194e1ef480a4846fd2393e47b2d9f1c7adce85a366390fc7b0fb2e9c5e0e1bb85c386a4fdf865b6ab7cddacd6adf324d9c98e0fa28ede88bc07b3e00e9c712dc3a02e97a4620a410dc292501ea6b407492bb24a58af6365a1bc19d7f82836fedcb9504f3ceedd6aa691aad74c25ca739bbaa892799684f54f04ddb9d4e451861a83b3a09d128e8234a031a00917238174b7cd3886f3e07c69973e90d4b88e52ae1200a6148018b752729c78750842566b03042c24e8af8a6c90adf3867673fb07df6e7ffdd1a3d9f3f579be5e4799aa7254a2b30c237ce2b653236246352e6bd26e5944842101993311713dd18c425bd942df649e1a88d2187be369452c58af556e97e67385da60ad30a42768a5fd793524ae99c63f8a6eda628aa817362d0ad3bdf4519be9177ee94b17925ece6e03bc47e8cbbd8300813b84847cdb7d1736a5ae3742ba85b8d435d4a413ef78901ac5bb65bd3158325b8e019755d679a05b8137527d89d648bfda9d9709457129f930d57f58811e38ea7eb9d3a525f1bf22055fc4d66da9c5c86ebdf7da692150f0f6ad4b873668412df15582ccb167adb489ce685ade348da8fdfb41894de194e2fa2738237b930e23ba1984c2856b6d49fe1c96442d142cb339a407bd64c2794d9752a68e2d484153ecdf914048109eeacd67e950cf3a9e9a40993ed3e5a6b37bb95d92aad4a2c7042adddef737e379b29efa55ffbaa105d77381221737f68fab5d8d67e55133b26bed1da4f7495493389f49a3e189860beaad569801af480619a2e350dd13f6bd3e6027dd428ef23964d1c2253678728a7d26d16e1195eb10cb32addbabbdb656608e344ac8f62b19850c35c35670d9bb386cd59c3e6ac6173f603c6913c47785dffd6e9140be13aeeb3880eeb4c9d1d1d1d1d1d9d228c93e36acbdaecacc6475cd4ae8e8bd8a8c8e572bdd8c5ccec834f1381b8b9fe41f8bb8dfbac31a38dd0629b6935169b1973ea10d9d8d808912ab373ef05e68919cff87373c6e170dc9c519939a333dd96b5d959ed071ebebbbb59d335e9a6f373c7ae993a3eb3d2add2cd099c8e89aebfd6030e0f5da72304070727c786b5df773a8d2f2355fc857cda6cce5c651f27e56ab8fe9565327368a8c3931e6a19cfa15a599d721957b18b3fd7d09dd99ccda27944579de3cf443c6bab4d3a43593039569bd36a53d4e6ecce5ac58d73e8fa8f74c788348fba9fe9d4090feff9ae5a35e3c9961d96a6d1ef81339a46d3681afd94d5aa56a76cfce94da7705ac52e0ca2981ae5a336d4a99165aeff3871aeb73ad553d629bffe9368b5b1f5d61df906a62f3f109367be664fa7609d62229dca9943985dffc9bfbd7ded250a9822bef00c619c6069353ce193c95fd386d0d3218c13acd9cc0de304ab45799d6039d1f2962d4d5d5a8700885d5b094b158b44d197d66369b792ca2ae9d23056dc7ec92ae93296aeff385333bc21049914a587378440240920ef382f4b548b2bbfe4cadfa44abad42fb4608923977fca943f772d6ed74a15faeee5e0cbf38280db70a5ca514b73576e7da3c5de2eb34ffad3dd75c8168e912d32a44a4f40aab4ef67803c0098836cb941aaa8a4c8172a341529331c59dc7e014496d49f369a07d8c5c183bd1c3b525a6ccf87f6a1eff8370703a44aff0c41a9d2413e92337ca324ba6d6bf0c5b4d82fa3c57eb1c51e5e8e94169bbd1c1eea902afd1c7e14be51be7434aadfdaef3bf50e1e3152a5bf99e0034c4012a0bf7548950d0a1ff76ce301273b65358d82dbb32780257cfd1e0ee1720c59bd0bc77939b6a79582da3b3884f40d86b351547697c6efd422d83b9c4c19b7d9adcf49f9c2a1064b8bf56bd83032aa4495a8f6d457e5a9ae5aa4eebc785c35ac9a96b6d266b49546d362906fc26ebd1366f2826e6690fab4d65a8714467b5aac2631f8c6ad751967a753f4c577b3e99424e9748a43ca435d2dd6967f7ad784383c615278c21a6bac316b53cb29b2e13847da849c23d16dc639d2748ee4da6665449b904cd3348db518ac512496cbfaccee0c3be762b96e883ac59d733e9d1a351dee9c6b76ad98d5b2b921620dc63e301913b1a6318c1122a2a21d9d4e51676de7e2a0eea48fba14a7c5fa2f2731d352f024867e8a4f1106f199d47cfde3d6d2ea9fe0d82ee93b1cbf2a74ebd67295940eab462dd6273dab5195a571b686a569a4d748da0c068a96365b9bcd2664cbd98ac8b6d6a6733757e7dc4664dbd9786efdaae174ce5159e7dcec9c733045b45b6b6bf5b6da7ab69b149b1022255b4e896ce16d45fa1212e95380329a06e9b79dcdc5b3bd606d026aa0969302d4705835ad9866345385b423bbb25ae78ec4eb8e1a12b13b5a194ba3fd26d429cd76aeb5663b2762732c0e12ae5bdfee68b673dbb92664350d47d3882041a391421823429a462269304684846423755d1fa8cfabc57a42819212502347d52f79e942c1130a9894a09abc7481c182ef44834f3a6aa4b35b9fca1c552d11be71ba64b79fa5c66a3e109793981441a4085dc681b8d4b44ad2b4e338aade38aa3e3769987fdeb4bc356d5aec3afb0c6e359d92b3a6c56a27ab450bcec6d13492ab5155b5f9f86d2e991c195fb9ea2486fefc1906b1ed38aa3e0d7de5a8fa405c50776b39c772d5c81676d58a6d03ad508b312e3ce16c8cfbf189b94be626ccc5d3e52a76610de67ab1c76d96e3bccd7a9b9ddee68ab17858ae62f15c9116757e385be408a2cf92948634992674c4d0adafcdec4752fab827e1bec4592e8431e233bdf0849b3a27dcdcd199f3081ece07c6884f8b9299e3d8e7a70897ab589cc5dec9d3446e7d16e00c11a375ad006788a0a1e6392f3662ae2a117295c9d7f7a229e339ae922e3bf5dd097980e9627fb98ff41b96abac4bfdd992719574a9f2a26ef523382dd6adcf4333277259a766648b906cf9e1d814d229e9baf5bb61de3fad922e2313c9918fc63e2aea214f47d7b1e780eebcd2ec48beda95689d89a743a64c1a6c5b6d2bd3b6da685edbca648a693873262b9a44d3e8d6df642421ce83e94e85ed3498865362a2f968381acca48485b468b3ad369a8d25731959a26d664868b6c98ee61142eec84547e8dcb1656efd9207336fff26245434333ada6448663a35b2c48486649f25e2e95473af4e917a3aa5c14a8eb0b1359d6adbb236f6460345efa55558b6b9f53bbeb151a162f67abd2ee771a4cd6a9576de5682802440fd7a8571368107b803d4d1229dcf913c3c0101df1ea44a75e2d6e925182330a922efd4845c65ad260481ea62711c55dfc5173e13b1be85e183cfd4856f9cae17ab636eb1846fece79ed47d83f26a355aac4fea9ed4695d6843b6e1fb3b4ed774491590239e20831b34b0bca0832fb8d49f2fa9eaa1852c1c41260d454df871a93f615225bbe0844c05376cb0c10d8cb8d49f31a9aa4448c2093056ce112a70a9ffc355a2a35cb5e23d705a878ae27bb9cace66add9223bd3342f9121b6583b050345ebe5fcd3299eae1f21764dd7e4993d333687266cfeb85e1c499b3e536812ddfad5b23892463d98e648dae4481a0a6f6d63b3a24e8dd675eb8bf698dea753273ceacf58ab6656f02e2aec8f6c71c56e6dc1adaf02640167340dfe3d04a1d917c35c751213c4c7303e22e401f4c23c61f20266ceb5f6f11aebd40ca245992232a57daa863dfcd062f3d022102df68937913b0e2e09c5edef57ab4e603ab7bf7d8a70e9ef9f26aec97d91c1c6dda5dfabab6688902f32b0ae1031b8fd6ce4aa1922607061a058f520b3dead43e7f6efe814026e7f8a263ed27f8eea1d57b14cb6c33217a8197c63ef70517f0bb9aa5b8e92824cf509a458e4255bf8bb24d33352ec1fbbe7f60efb40ff077bbbe767e6d2f36bde0bd7cb2f65a44a7f09ec15281dd53b48f8f8f2b790a8866fec1d96b9fcbd235bea6d996ce9ef8ebde3c30f1fc79e0e074cadb39e0e796dfd260236d8454802f47360116e878dd3627f8a307ca470fc2e6be1c8adcb33a5d00b3f47b58cabb84817a9456e57d7e4229a56eb6c160c142d19ef9ade99230abf768af0c07e845e433e422e8e447b6255078e44e90f8e442747a224bad32a922f42d42e8fe7eb9e527fc34adfffb2e58572bbbfa4f4791deb6fd9ac55ecd24f025670456c54334fbf0e9dea19339d6af5f7b790fe1f9690cdedb1716ef7b3acce8a8cba6f6e3791db957d18c631fee9fa3e76e8e011d3a3ab6aba340f1388dc7e205866d4b071a329a54758d586275586d84c58d5232c8dabb56a5a853972e448eb75f9ed6be78402de97be1496c00bd9e5287ee9d2ef325c13e0049c4c313d8c972f52da42075ff76f4f410f30eb76a0b9ee380bd687014a9707a5cb0419c6f1a0062775f8a1e2b9184debe183995d7af7e9399e72ae78ce596ad8e812fd19e1caec28f5706c57478b9a0df2999eb3dd0e297e23752f355af4192b9d8f1edd0cefc5f33c1cdb5d2995484858be92121393131ea4e86c90afe4395367837cdf73bc42ae30fdf0f143071af650a1a2c68a15263558e860e1bddcd0b163855fca3283a5c6ca29e6e4bdf0287d0b1be46bf15c0b90e439eeab85a308aed6023479b1d1a2ff8dee6a610dce4567837c2c9e3b41e9ac121fc9a3782f36a48a3f27e4e645b7e385f7a283c5c3e86c908f3e57a3458bd7341b2dc21b313a1be44bf1313c1cdbdde1f5d1e7fe41300c5966dce0bdac68da0c96166f98329426b447b3a851fe37cc59348d6eb86174d89c2ee44346d44eb145fa292937a4fccb167615f8fe2994fe0d94ce09364dcfecb82a868a5daab0d36a71e2e2514ec01417a07883f7d2622765cecc150d5352aa6033690853b40353b4d3a2a473d29dcb511c009072438842a14010041f858a01e3050aeac4450b140a7542b1588152614a6152f291943c140a85ea0c503a8999e1f2e2eb1df2220cc2042ea28327311f7b202e30c0931818ffe25f8441b890a3fc613c1017e9bdf07098a394f88a5c3057f18efb0e6c47001e0ed4e59d165d85eb1b3d09233777e49d29d4291661f35f902c04522e2f42e9a87e1b4e024f200b7005a802348129401370acf1f9833ecc1b039c21626418cd05c119225e7c08ce10f122ec7e82a84b432b7e7d8d9a3e4d735a593a35cea2eb3050b45caac3e611ccc7e8c7655ec4f2f7d83649433ef3af4cb44d125d6d93644465b6490ac114eb3087b50ac6fb37d3a9b159d7632f7e62fc8b3fe1f1cf5706fe376b1681204bd3a8ffe08ca6513f06b8d234eabf0065340dd58bf777233f9a32ab8601ca46c946b9ffa96dae8f0cdbc1b1cb32abeb4492a70a34249d9d2ee2357cf5f281c57e849c3e95e177bad329928b875f46d74f706c97b471a1d8e244d98a88584aa7b43689235a43982222f569adb488481191221d6a81b8681a5b70a186545052cac25f5a2981a2a3fc3df01de5e1c862f2826f3c5d717b26d5d315593a94ef3e7c0e4c09515af49701ae9ca0f8e44f16db62937afe80bb5b9bdcddccdcccdccccccccdcccdccccccddccccccddcdccdcddccdcccdcccccccddcdccdcddccdcccdcccccccddcdccdcddccdcccdcccccccdccddcddccccddcdcccdccdccdb3bbbbbb5987bb99bbb9b999b9ebeeee6ee66e9edddddd2c3633e624b2e16ee66e6e6e66e6eeeeeeee018787aec3e16e764ab9296d6e6e66e69edddecdccccdcdddeddb5bb56d664eee621ab95911a233547474343b3198b4475b35ceb83e84375ba5999c162e3c60e1e31afebff430a3d3d9f4a934235e5656cfc2ba4df9e9f9f07d9c297ff87945d66691afe1b38a369f85b70a569380d7f9656d5e7b7d129fafc37f875f0d040099a50fcb243ae39b937bb481827622dca99f774576e6197c304a684231970497cfef27a383ea9e22ee3870f19ad52213483a5c60d1d3c56c4aebf0e2ee3dbeb135d052351fe58b8fe40d76513d7b36e79a07949fb9fe45c7e966915bd89e9c186ebefa35538b8f8eb7074fd77d871719e22b2ebff43a7562c2167729fec0baeffbb805e182856b679ba804a29a5eb6db8cbc5ae8635cc6f6e6e6e6e1ce5d67edfe9e47226448810214c9b6933a7cdbc31b29a3673ae562b9654915768b6ca63ce9d25e21b5b060787c8cab2868ae2d3268eab662c8663349b5d97e994bcaf4e5166592df6533363dd487819a9315263a4c6888d549144e6d0d0c8b2e9c347734876343427d1d1d111a9db66b3d96cc63e8dccc484ae6f3d736675fd67b2c5afca57aca396754af2200105573e2108b3a68687664d6ba8664ea2a1a121225f396b88ce7bbca7679df7386ce63dddec059586af1fc6895987b3cb71d7b2dd4b1dc23861e4377ee3377ee33740f01d2398b8feb448a738081667f2f074374fbf7a9aa7a7a7c7a7775c358be6911df28dddea56df586d5a6dcea156cb5501428ef22bbd9721256fa34fb8f08d5cf246ae6a5965a119b24fa37e8cf8467bb4d1746a2cf170ba0eb3479df21e7f81e11bfdf5a7152e0211f05eea47c07ba12f814e02de4b7d09782fd442e1e39640077a3950ecd9277cf4432f47e8e140edd0223fc7626226c015f0706c7705acc51a7cec13932afea71ea84eadb872484949c8299d7455f37c2e7f39cadfdad333c3706cd76747dfd8b9fc4581cee4e6eb0acc64dd15e84c76f0f597bce94dbe3b2b74ddc66c0d5a880968fa1cd55285bf71a40a733d7a8816e82ce0bd788fc304e0f160d8122f151930ef9908c380101d0bd232eb84af61a555bb4a9bfef2015c95c65392511a456b8df0d11f19c673fd5132dfd819c15cb5d2566b692dad4543ada5b57c989498849d156e49281dc5c4f5e732d0c94ea16c13be91617cfd87a398f9535a74fa2378922af45219577db66564a45495bcec84905d14a4aa24ec8c1ce56ff2e9e07a281d15e4fa5b267c636780ceba3e998ca5cdaacd8a9252417146878ae2d34a2b57790e0c76bd494f37dbf2b963e773fd4fb4871a7932de8c97c2c27bd13cc775649e3363861388d0225991ac4856345342aa3a97a3644bd31f4bc6edf6cc552ef3cee52a978244f9c76230d8eb35fa8b85c55ffe6ad8f6ba2577f45727fd7559dcb173f94baae8f8ebd52abc1ca84b437fb5d86391f8467f9d66509a86bf5dc267faf1749205e0d91d4f56902dbc922a353c11e8b323cb0f318a4f4cc337facb5f9e3ae9162f07ead603781d6edda7806e6498c9e4d336428b848f861d4b80d7b08eb9a85880e9c60b8e1c3a52989430a0015f0e243b4a5ec7916036eb8007f0d02a04e88c800462264001af400f0b5c0003ed23031ad800cb19a592927e2fad609d917746b43b72e75ab9b472a1a5954c5662d15aabd526a59ac9fb97569d1a3ba3965d8f95fca4f81293e9fd4b34b2854b2b15a614253c6b55095f137eb1ab51fedd51a78c7cec467f5defd4d8b9ae37ecfa7b32d5d5298ea753f3d503eb143bc1e68e3dd403f357a7248f136a6856acaea66b754aa2a4f04c17cf24e2a1b5d2229e229ea29e5205526afe020a380985a10b85278a68d728fa122559aa5a48140f93438b22552b27b1cf06f97018f2a1fc2046871fc22042966991b91d3c484f79b5c209823c6aabb5b55a2581cc027985aa810d1e28187ce3caead2ef416d7027dccb18204419b4f3e8caa52f607607ebb3d576744a7bea2b36786c33586adc08f2f5f0f143b6744f5f87ab78e5a8193205441640a68258912dde535ef90391a9d2d3a7246089fcc01328a5389ab48dc4759e57f2da82230f2cd30c9368caa619e6cc9b33771f815ccd6a5e25d1d9b9d6799e0e792f57eaace70079f972db56aab7e32225a074947783bde3bd78c8391c10edb2dc37b759ad724ec90fcfeec4b9f3e64e6e356adec93434d27d7afbdbe814e796331151962293e1b529378f18149f4f9f5bade27e4e16ba11329f89cce79c2934e797bc8e44fac933e773a00465306b3cadacbc66d498f3b98665fe0f40cccc1f3af818baf3f288e994f673b31a1a8c38e54e31653661070d46ee8e062334183d21d39deeec4e4ff8fcbbdf7fd60de17cc4660a5c0e3ff0ed1fd47571e58bae629c46396dc66122741429edd6d1d93162d3a9c8d67a09d9ecf41e3e365b5bd57d11cee53df72af5907ce9fd4d6cf491943c8e7be78ea252a328f6b0c6feb9dc34427888e4ac906a50a1c81de58b6980d8563aecc043059debdf03ddacbce291994a51e8572aace891997eae9bd29e99a1a91159c6b6c7123aad34ca5d3d5cd5323d53633bf4d41e3c74aa6978c4c468683ac70616d3a193fba6896c1e0ea793ed119dacb15add4dd334311917acd542faa7611dab3b158cd9d1289fe3a9078d0e3d04d1231b2a3afa2123a2d388481af1ccf5bad159eb8b85e0dcb0363ae9e41e1ebda3b9d60ac214dddce09c54a41557914259575e2482516cd73426a12074a2e9d5280f4f6acf8b9947537d57b18b0f8a109f984ca635d49a09b9fefc4367b38c8b6cb7e86c8d87ceae743615d9877d84bc56ad1f2119114e4b48519599a1a99379a44fcac8ad9a1522d77facc173fd45eab23bce70b98ceeb5954e9c21d46db26a0f3b085d7f20627cfcd061a3375c4f372676e514ddd4ca5c4198a29b1b1c1e675cc95346d728c7a1075f25f289dde7634b84205941b9a828be51f2300cc60c639f17d9fd8387abb8b9e88608e7faebd861994e9ef96b1e4cb3a5b3b2cc0c651577c343322eea9bc6e1a356915634dc8e1b3da32c8a59f9b8d9a175b84712f9c47ab4a572680947b78e2cd77188300e66ad8afa37fb74b3747a2fcd3c27ada3a8e6c60b2fc15554c3dd5c5453545364332fcbdb20134c11bd1789606696937faeffe7dbed48726c900f87218ff2836f3c8539de87fc945076386fe808bbf06b51860d1e1daa1584d65adbbacfa443f8a81aa060c02dff1e7e0022884ed1d7c21cf25679abf6d644e7d75a5f7ad576465f8a2a7c6de0f899a3b0068f73c80b7652252806c575c13bf8240f10d1d5aee4099a5dd9e2dfefc91669afe409925d131bc1b9f28576ae69c57bd6307465cfd1cc0057f6ac41e6f68eb5df773afd8b62cbacb094f15077e8aeb5f60b8481a2c52efcaecdac5c9b32a9839919e41dce91790fddd19728e2111cad7feae4b83701076772ad7ce4c811318a4fc651fdd67e5f3f7b39b6db386bf8f88e526624b3dd6e1a99163be522091fc36432a2d84cabd3b5e9f672b34345f1cdf7afcf3257318d8e4ea9a68be469c2cded1d3cb81f3a459f23516d4e9a1366d1bc30a1687af5888d33d7ca20cc111b9bcb913a318a1da494da0e3c689aa6699aa6693b049fa5a8f4ab994c7a12a21918000000200083160020280c0a854382e170180782103f14800b7faa585a38990b43410ea4200a82308831c820600800c4004690a9a1991ba474d9de1570df61447f2e8f712cc65563673ac58eead7a0865119d170a7a3dd2eec22d7b01ab0db64b8d33042d21c0fd2a5c43d08e4b52917edc5c4c71fa030e8e0906acab34d6695f1aa835c416267f17a6f75b613e124cb7c9d880dab02fb0f1bc69856be9125e6cf79e4f5928f69e27e8ea24af4f2fbd9922eb7bb7d059befdb4177add9a3e3c487501489cb447fe74de8e5112716e89f331f02af488bcb2735274b398b842b83aeaf9ff02de6432bc2ee6351584f6a81a85e44747ea4111af615ae15ca02474927de38a19b6bf3742d07c77a1a7d0a81976112545bf0738dbbefe512e4e162b239184ac07e115993167c841d84b0433dddb1a7d844d3497c3c4ec1110b2c2c391a720826e90d21f271d12dad2732f39e76180b2aafcb13a3c1e6ef0c045ffc0c46a090f2205e7147770bc2d99051ad28695a0f471e2805150b1f19501cdc4f05610b29d3cb8f366df59bcab1111936de977ecb4df2d59ceb9e86daefa090c87cfa284048882c33fc62efcc35b1e7e50ea832e93251b64dddcbd3c733444d68c48e24b8bd122cd11f85fc18ac37d0df9ff93eeee94fd77fe29e94b417a1c61f6cf05219d6c74ecea22d1ba56ccf7783f3340545f7291c472fec9b61df514220e2a4fdc3f2d39b1eac5f0132067c7d6c784a3f1aebf251a1e7241e0c461875ee0a9985bba0eb3f0d46dc2d21d919124e166cc0a4d90195dd07f7e747485ef002ef7c55c0fd6a4c2bfd95e0f1284b15cd4b6cb2049988e6796f39211d75d7eb100b2e0c203dde4c4206c6a57b7a7dba018c5e3f24bfbee2e57eff721dd54b25d2cefd000615adaaa787c131528bc8e26fcaa6041f8b2d10570bb6c62458be18510a15d2e12a0313cba68fe48e147ff68da8ab1622ecb0da0f9036f07f79aa2a0893a2d4f2ce3f904c6aaf6b8f928b5d79ed06fd2826f1fd43a8253641a59d8dbbc4a1f6666704e4b8cb326a752a015378a4c3ba2cd36d3eccd816bec20fee65666160335265d6f3b428eb6e439d7be303f68105fed085b56df4ca671120b8c23aecb8bb57bb8a09cd1b0305c72d9a8c273e2fc162bb57bfda60f208064e9dd2d8ed21853b0a3be41c844e83711b2a852496519c8b622b1d8d12264546112df9b19636a2cdedde10b2a041df5c5fdc7fa74ea513e73fedb186eee79fc1fefee30e61b06d167a2c9cba1a82debbaa06ce4c4e0fd730e002b107665c4db0c2469b54afdd35c8e900d4c62a19787c45ece87fa0a723e1dd704e99246c3d480d8ec30a99a0f5533b9e7674cda7262b66ef0f0795098dc89f4e224777e11df44ee7c456699ee90115b3f8c9fa132ef258f52ea95d003535a329ae633861cd22aea709ce6e8056d8ee1dbd17ef4a01cb15d8eb10528f8583b907b21f164098c59d555eb5bbd9ea5a074a7c5f0c6bce3656dc5afe433ecabeedadcc4e08e972bf1d3eb659983adb38cd72236abe2841adacc8f9d574ce5eee73e5df33b82fa52cd7cc3a7c05c1ab5830e84fcb303ecd48c9f6f9910906fc0acc0484c7f1392a6d7b900b9699e619244e7dbf0b202dcbf6597c8d89b7098b3bccb10045fa94918aacda27cffb89f81b9906cabd7297c502f306a88fe144cbfecfae1c13c8c7aff40447b1a9ed3134f4b24c0b01ea40d09922d357d0758660d6e70a4c262f24e754ce365932add1025bc66ac5bcfda6fff553e4b3325e62d89810b7b406b7fe424c6d46d21646c1b2e09a2a0962ae56feec1eca3abad3dfc35ed919850e486665d54d05f266af91fde8d01a29212e11aead84fcafa6e92d356510feedea2587829bb1331cd826ec07b530fed7da496ae7133582ca3fd6256aac8ac843eba55f6d046318d31e966e693e0fe70efa2bc36b76e09d078745d23cde7913d30e6c6d68759199cbc18489c9379d6346317dd84d34482558edfa98711fb3e9f692f6e8c2c11abd3365dfa06ba036062973e9f9d653e7063376a2d8bedb1b3d0b4d36a8af8c6de6c93d8c9b40bf6e9d334a9ed675afa6111c6119660371ec697a25fed859101c3311fc2bce4aea7f62db9f4420e687263be3fc47fc25b12cbd7c472b52e09ac60012506227971fd1f2a50dba18f0cdfbb516badfc4b589c1c1a9e19b3a2addbf7ce0f6a79788cbe0770fb3d5d2dcb78c9cd34f49493914e7d5bf21a1072f0e688f38ece56191bfeac5991061539fb526e5098affea0d245862b714054979b24bf88bec178ec039da5a9f543013363ddce2b9ff89c0b1887164b1730f5d3cd0e6cbf5528aedb7baafbe71f497000f883b22ce9a6dd2c8e0612fae3c099ea7280e9d5f88fd20da270f12daaae8e1f2fce8beb65fac9c7f31fe765cbfcf2d802befa3cf1b4f7beb3fbd62ba2e865cab2a68d1fab72eac8d3255207deb10dfd4b924dd3ed166f17a4027770d24597e9b87340beffcf88619240547b49621a25f5ab20acc009a999955ccc700574e753b2d0a99d12d6f0e84a42b063d8c7b4aa91f8d82088571963be4567d0254619a82b95c3b25244ca4bb61c40e58216b6f10bbe57b2c47c2523f60a0f52e1a0416b88e7a6f048500903ccf9f6a3c0d784b0739cdc08de25c8970d8347416effcd59872a27559fb05dd37e4026d563c07eb342839fe5c22900587a8ed2b7a95445dc1937639d631c5d7fcf94096b4b148c195462ee009e3266e801064d7cc5a9659844fab2da04760ec7cb6293713767fae3d2c17e638d0e08f1bbab40f3b075a4ee1120e5f549bbdde805c4d743671bb2a5b56b321a8e05f7c7dd818e1c8393dfa1623ed661e0b13d5e49aed50f9e6c809c004a61dbbee88a4aa48d2ca00351712387bfaddea8c1bf94f26020c4a87f9b887b0f23735c7c02c96d83ea7eeeb9b10579638a43913c0d0e09b4e8bd61defe80b67bd376d6a0b00350a844bf6bab2e8fe14dad532a1bd141e7cc963caefa0430106b8c8ac0fe5ee1fc195e27cbc757a6b927163267fffe1ca309d2c1bd7fdf9c7fa688ff81c1a60b38e57efdd509e41aec58504627aa963316ec8a49f72749d273407c1166692b99924104d316f1e1d51a9257ace9b7ba32fbc9d64e18f0dcfc0c2ed8c19acd1db07ba333af1f7e530aabf8118d987b62a06de94aea292d0f61d9fc7a4df9017b280919fad59f126bd4a03ae295df22539e04e31e912b42ca8a99696d2d9ae4678ed2224c00155d781edea4f4adf68ba86df5f2142b72d42e553e989121861c83adf10b9072d73c76688583ce64162e9a78c0c53185a8f0ac67fab2485d5b097dadcf1ca722ed354e9558d90be1d48d38036c20d08f8099a56e7d3e9fad788eb84431f5841da5f23f6381c31b2ddd7ea31d179d5958efcec6a2581ba35db2f9955770f37a678e763ca0be92098b1df277f0438e06c1479c4c69672aa02669990b58ba2145b88f638385773fee780689ea8c9b6717333ad72d04d360c2149c6387aa250091b256d60ad22d1392d1a00ba44d4af005c78310185953e1f523fb9120b009c37037e3818082d56e615b332329bb4223ad3755dd8cb208a9888611a1851adbaf4e448f6c45073cd81be1b8259a2ffa6064faa2ce1ca7c4037757191b308611c52733930188cc353394f3a18e08d7b4b631ef12b73ed6a7f707a82b8f80dad40f2898f8f8cc4b27390cd8ad49ca91550d6153953438dcbae53995adb51e8a35039c4e6e4844d325296f7753e2bc0ce1472ac7044130075abedc1e071792e3beb702e6f17ef51fbe8ce3bffbb52a8f0c8fc8e8c8cbd63f893e8254896b493f59f51b123c168b4cbc78e464688e6915b493c26f63dbfc724a0d0034c828ecb5b37896befa342d706d89ce567e44f673a3548305d7ffe0d8840abfd909f2cb20de02afff1fe4ff9643ae813966dfc15ef9621a763684ab625af9ca0af9f4e07e4938473d7673a29b2e1a5a13ca46124bc083631f833f8463da37e5f4ff3741085880d23b415fcfce63172b6613cfdd4af6f4d5cb76941be65e08e96f8218868a4bfe9840fcd363dd5c0e458823e6a5ea7c387ee8acbdbcf3c186d7e6c3987993fb6aa0ae890e44a4f3ef5eb7785f1996ead5dfe3a4a148df99bc46bfd9ad22961ae50f1c06cd56cf5666b4ffc417ec63064084934c511dc37f7cded26e694479d30aaacfacf3da970dc6f4e151ee6fdfda060bf53e0783f01bfa3ba26bcc6eb7a8a29a15cbd35b313445020fe570941709c348e23037c85b2c2896846260e44ec320f115f5936a0c2151eaab1035c8aecae92f4f6fa59761641786cc67b0dcb104bf53f0b4180be59194e215e5551dac4ea18d9a36a09be4a84200d38131ab956e80b240240572930e36bc822a22aef42149edd429a63f3758a05e8c1966fa5bfba31e4ffbf0d93fb14f62010101e56ab2d86bc9710983d16add2437b7f05560a5e110ef990424c9b625e65ea9fb11e0064dac8c5c6e4832a6d530d3ab72235603a81a2fae216b351f6aca0bc314ed15b25cf0b77dfb3c4fa253002844a0073940b9214ee8aef6569d5d67f5e0e816a0d90d802433f701ee2f9f67d78a1b1ca1c04c2495bb7e38ed99004f495c50ce0c7a1a2fac0b0030c5c333d426ab1340515a0f6f94485078b701d98abc357c835f925ea707e4335b9d2aae54b552f50aa50d5df101937103f46f445fef840ec1237f834d1ed2f8ce20672f02b901b203c52965f06fc7ca723289a8c31097c808d86d0b22f933369ce50ec7b3dd420fa58a828a0ba8d25e79b15c23eeca0aa0a68bafbc03986114e8252200897b0ef8dbca409f9ee9a129fe34b7f7f2462088300b1f720905aade543c7e066c8232527ea2027aa79aad7f5d06b28404b846d034ccd5ac33f78abaa7549fcc1f12b2c2076308ec03ba77a227a93ff6f514e4134002f93866942110f1c54108a1e6bcbaa16a81ac74019babb204046ded090a2038c9e7c36bd30309f00135bac462e5bb2a130e288116d3a41fccfb9bf896865bb0d84bf0595a3c049ac5d09bbffff08629f595b7d12a7c64557ffccc6d26e7253bb3e280938325d82ebf3f7c82d144a2d58bcf41a7392308198847600f71c44bc40fa6adc39ce866f19650c5d3440ea33fc8f383bff83787638679579b064e56aea8e54a200cd3b1dfe3ef6a532ee8f03504bde045e1103ea9617c6c13a6be76ab8f24c75524bd5e8485aa5f85fae3c951664fc5b2a9f9066acc631070ffcf2b564efd6a6e590ecf04a6510d1e66acdc85c0a0b32cc26ce26249bcfe563480abd27cabe87a35a22f0dd52e3d437f364dd81af24c165e6dd67d06ad94451362ff8a6c8927b2675980f4677b27be22a4b627372d9fe97b166af0cdd6d7af423f21cc149162d2d8f9867c6423754e2eb6fdcec52945c8c019656b9a643d0c7c1f5c238da84d9be7f86901a19982211138592c188b5424393c18c150e9b0ed0ceaaafddbf46fa78876e40dd2faefd4d1802dae09728cb1cda25367fb62c183052d099113357f412b22d414c8acefca8e0badb2ee879b1937e43f0e2b8b273d459d7e0331674303a282d6eac521b4eaa213d0981dbabc074638b0d09887942a5a6b8e8ed20666003ba9ec44a66bcac98f49f9e93c4cc9eb64792c19b77c28e5c2e2317151fe5cbe7c50d99af9d91e6a2b5676fdda05508ecf05a3f028ccaf3d8fc1f6736af83ad123e3da286dff38ab4670d22532fe568764b1cc9cb598b5c62615c6e4493cbec7b89c86d90eef557804ea74029dd236ee4376f4c4c73c1238c4549c9d2d53fb662ee4e01dab88bf9f8206f4ed91d28ef0ff6c9164730dfb2e842deb592a4d74d76e00a9ef4112ca77eb4ef096203cce58f74dda93aab9e5695a51ae8541cca69aabb5bf2d6276b8b6b2a8665854c0d26497497941121888f5a3ec8670b53abaa99c5fa4829c5446413cf1017815ac2e458d8912e64a47b5c79c40d989786d436962fecdecd06cb6b7c9faa956af806b8546f96f6b8369a457b2419463f88365b06e0426d68908dc1e01880306ef6640844e5394c7e201f14edd6e98d0cee04741d2b2a16d63db8a6823a2c6f0d82ec78d00931d8d489d670d12fead347b4e768653ee9bea2cd10b5a323e8cab0d20e5df77ce11cb835b96480269287f59e7e21b9be8d12247b6edb09dfb63d5f43b6ed82252a10cdb546572bcc2fd702334a3458cb4f1d2420e5444307a332a6475ca5bcf0b69d744cc504a53b917bd50e0e8875643b655e99f95e4e17bd3e4978788312aac4259e264fe39a250d310a15a5a660f32c398535d9462bf3e0489491cdeaff30767304b8be111d092b90b92e68e3ba9f708f33526dfc9fd13d48d9344d943034e61c08eb219995ad85d9dc39893e8786815123b45316dad57142de563f7e75a32c766ac7347aa51e8ba7f6013d83d19a0786675bcb85035da7a9e3ab53c349906265ae24d8bf61e9b25f1ea60279c6d5239540427bf6351e30761503581383d3ec3f8f5d351388131ddb7a49b6e3a780a14f64112c82f1caddddd2728e65d41bacd12d96b60867f73486f6fb6e6d2d1795809cc8dbb1d2326e5dad1e007a52a7f1cc1f577bfe72436c543205d3cca014588095b700ed825fbfdaef89fbadefdae6080d9440135ca9c976ace0546520eb44125c11ebc894cf2efb6a1f65f618bf6c35d7d1afb4286902acb48888a5e71d1b6749fb1c9357f4d854502dd2d21d10d79a4b8996ee3b5c9dc3bea7b439ed7984f0748b072d98ef1e5ee990d331139e40cae34c135672dd1c8742912375797537fe5e1cc7325a4d814d5afabad55d833c16d5c70ca6192e760712cb30fafe7603dbea8e44af3b83682bc3409e6f030793a166b9327bda8b10c76cb4029baee51b6c9e98d2272569204863505cc12d5fb6a77438e35ee1269b52305781f72f928c85a55ab061b11097aa3c86340c5f3ab54120916851cec0c6c32ecce00d68cb7869a99ce6b54097bc1aac75c2856bdd38e607caa90c2ee68152febfa30aabeff711eefcacb29004d32dd7f79e37a72b38a725ca9dda2a5843d231eb4bc1b1b1f1750df01b0260e38855f8f37aa6554bcc999f3014fc68e891a2d55de73a58abd9f78e0968eaf26c87e1bd836fbcc85ff97c08c5b2d5337349cb2eacdd47395faaca2332b92eb813e8a8a4622f5a364ed59f41c4109eb69696579da8dd7e476cf88c6cfb8f99a2327ba25dc4e0e9d0714f74087157e3d8ff361301ace9c4404fca902edb68d1a0e8e150576bf27a9bc5da0f39d189c30bcff03958aea9d0ced90bb00165f390316bd7686f383a059c82ddaaa1e2af2687aa215584fedac1a62b0f5e294e01f7e804928296c23d4a8d6175fb47d948d82a5e2f7d8aa137744795c344e0f399ea5e895929d21043acb861c39107203aeb1fd0f52781be5bf073da1bc94a78783460668c627f299412fd804e766be2d47dcf2f74ab68424239ba8e1974ddb3c0e0e8989e513385cb7b2d2ede1eddcb993f67150d805ec757085d69ef7b8acbf60949df01e220cd6821e8a6282e02f66a749bf03bc0b5600f3aab1dcf7c3d86c51228b1d3efa183dbe9597488cedeb69d56bbfc9a99e15c13371fd0302c100fb4f2cbd2ae655bd97645d59a886339657f5c3192ca051c77bffae51b9c8adddd51ca371532c688fb2b31795592fc6779b01385e49a6ad3874d955e9803bf2d9a4bb9988747e6d3b15c3a24e16eb726f8d3ea7c2343a07caa7ccef095baa2f2a898f134ced73a464363f2ac36490080f38b0bcac8431d900efab35fe52a5abd291fcbee9effd5163f97931ee3678d6b31de91eebd1ee53afa88ac4e02b9c1898de330b37f29c86c19d017a57f2a6a3a119dc55d145efcfad68ce0066aa5aa7d0759fc8099307b1ce057864d4e3e0b8a40c7d063aaf9c9b748286c2ce399b353abb02f41be5b803920132b037bc1c31bb50079f3eb0bd5338fb1862561ea087346ff802976b184b3343fb148175a6e126cd594ffe428c50bc3876f0d75fdf1083096a75497673b193d46a2b60ef5577e908632da8c07780b153d548dc51e4b8525d6e96bf0340823be3bb94435b4100db650494fa3e313a97cf2a459def232b858c23bcb9e0424aa5d41c417dac905e1840911ef981bf8e6f1bd99e2bbcbb707c67ebd94a8c9b86a720975e91484b05f1707cc27e39d726365c6f80c537efd90819a1906f56e6b7acd4ef40aedc48f2396ca87ba26ca807b62df071e21d93793bac83d660168d4e5662b36fd872749385a0fa155380c93781850e5b6d1ee6bc18466a1f770c36d1107b15793c4625805bffcf04224d822bbdd152451163081467c780076e8b1efd899ed16814a5430810099764e8d95d1ccb7c51df833c0fffba1f8623c7fec7b4155af770806c4640abf82fa0535ae1a57b1364ece314457dc7145ae458f1860493b37f87c1355611f989f0c0d3c1a45aac3e312c38d4ea1ded12a1aae6288d0b0f0b1c6137de03cd5312eacf0a5c5aea4326a6993ad5cd82e20c5d487950b8b6a82be580fd9bcbfb11b89532ad98726b8dc39c9819672e46bdf687dd4e7bf7b81a2d33c199e3aaba06966efa95d7d0ea39a3595dd506cd5e4a5725c4711318ecc60826155cbf87685471dac3ba09ece7cd45e84ee716613170df318115d579ca66db9a246e1a87c6ff171e7681a50454745614cd119c2d286598b426fdb7ebd7f6777911c1993c51882465add8aa611e9abe9c3b0a147fcde0a7c5172a53497ec4ed94d506bceb35c80c7cb2b4d4ac202929e22b20e382e2183e0ba8afed760b271d426383f92e85983167c383a3a28c2288b928fde684349080c2b05cc2b5a8003700f5f475abda23e8110955171fe984a7be1648932223f9c08dd5051427d4059fd5a12fb959f1a880551614fd3b142f5ca1bf3f4bb7e4e34b39c146036145da3c372e4c2d4cb6716840a3a5d39b61f4493de76d820f7abce007e568cee55f747aad19d62afd2e5a7e8d412b5e23fa673f3f4ef738e183b6003ac9d14c462f87ad56d0a4940af8f98d546a9871f2a383d38c2339afad2ebc353a9dfbef88f2e7253e3136e796b2f49f2e3d2e415310abdaae2c323aa5ea0a7605d6943fe50d3a41e1b0ab0ce13b591e80094eb2aff919275f160dfb38089b06f3cc103d4a1c4b54ca909281ac0732445af9a3eaaffe2798e46e7ac7ffd4825d18e5859130d38d54b1908f919ac28010c3fb5e5d0211fe2fd73480ede408601d00181b1d4539c0344ed4d14d214b861ed987990a6938ba32faec84cb378a5fe36cde9fbb1f0665151a2492f638b3375ad957ec0032f901e36a5baef216d9a576cd8354d5e463dfca514f294752a4e6ebb8691103b766ca6539a209c1acdc8d6e37e460e80d3e500a0525361afbfab2f2a8631f7671808db0f72b38129f084917392588b1c4ff96d059e466d41cf1040fcf18bceb07b1720b7f1f7dddcc2e7e29888ed8114ee58dc6933dfd303b71572a0b785e205a029088e470972c45809e0313cdadec12bc5e8687cf33194b119b989a171c7108aebafad0b3b4539affcaada68be4e134b412fa4efa518e846da07070eb81a0800a3c02a1cf24a482295dbb0b9f3f516b048c0d08e7f3e00633aef18951c7ab010168c11adf17d288cc09afd0afc06e100edc0184720ccd7c1287f268d2f04bad421b93e35bedc4da7933bf1ff9ee3ce486615ad14c614d4c2f9ed92cc9cabd3e2085799429013c51c2cfa28c09a4669d08753e0c3e58533fcb5de13564f9127736d1057fff27a3da83e2bd50542c30152cdb1022fa4c29e2ee7053535e8f3f25d541e68533d9ca98c1849762c0a511e69f4804c919c3206a34d5c0c731eb9fba8fb6c857389a3a2588c20083d1b144f242e035b121504d5a5bf3f1162acfa30c312c3bf7ca2efe531d889d20c99413400bc50038db2a4c235480a56b11cb0b490da72465580b9c8a9c89ef71d971a4db8c5456859d77cb17ac09e74c28c353b394caf32c781872f21420e460367653a9ee2d59aeaa646755fd7d5fb298474422c15b5aebe68300f383184bd8dcc3451cba5745d44b45e6ff8f61edf20dae6c984ab43a4c647c4c86fd58be093d04b4e92514bf07a528b8eb698b65062ee9c45a656c1f9453627e7b6e099b7fd214aebf36b465ebbe1e6045e4b47ea612f12036b1f0a01a4325b0d8991a0c6229612143fdfd7b979f7de90da23b890b8c140a6c9ad0c7d9ba9b7536b5de77e12b504ff598006032f15f1aec66aa56669be43996b606b8fc4cc7b421797ec9e7b71219589a5ae3ffbf6176572afa56d7a76909d4fec80ff882c5028edc4f8a6399ed57d6a788327aa7bf437d30b408d1ebfbe3885dc1ae62875cc349490a21af66f189b472907f052698ad9ac149384c641d397890cf256820df8a26318c22f92345c94e9de12e27c70c8a3fc6204f59440ae0fcc6f349e60d85540fa9d44514d49c334dd30304fa23a45fd1fa2e1f2c1c9b1e1a3e984ba8629b83671ceadaa8993fa5186298c025bca12012ae63453b8f7672cab759641c32eff7115f1cbb953b36d267402188d654823335deb89e45307aeec44f548d416771ffa5bf709c855a6872288e1836f657dc97f343eec61965ed4197610858528d489197e017d02a9c035f7c766ffb5e31a27734ae4b9e5a554aa62574e8c1a8581be640068f5921c1eb3e4690904902743d49451ce92dd8da0765e0857b099011bc1aa095d43b61d83f665d3835c408b44da3a2fc1b2d53514f43c84640e91e3bef92e13ab0b4e0e29d1e1b3b2b3e6044fde40a3d29a8f8959f3ab32a91b397386a821b162216229958c4aabe35f6d5e58c1936abacdb9fa4033e414c59b39af1e68369d3cce31a862e36134b5f90c80de8edef04f761675e543b102fec99ab52e79120619976e7e19cc9a5c949eddcdf9f5401ac814e56d73be1ee806993cce3d5805f36064def456fe805e4dac997ac321addac7af949efae0566f0ee3e430974dda9501f15ac5a647fc406f7b5b3348a39ca2b891c3d4027dab93d35c0655e253ad316cc8e6aad922c2b3c7e3f173a047f11f5bb75e704c5b4855b3d214c7dcc4cb57a3d2ddb5ebf44720021f17cdaae4181bc2d362cff0b5f64694ab85a1ac0190240014b00b106b90a576d607c7b485f4472b4d71cc4dfdfc352e9dc3e9af4d951c1fe589bc690e3d992cbf0f1d6abfb1bdea8163d44a4a9a9503318c211e7cd868d7b95397101c7dd34fbbb49adb0a87c0e1921803af9907a8b32aec70a19c7d7a7fcfaa650d78194fdcda5ffe5566c90c732f1cf78d71394624918e667cd73920eb0a4b6dea6b97c2f18896a288d67c7643ceb7d8ef3a7f1d50ccd27a71eb7b6b7e7e2f46ec50245b130f5ff89f1538408482f145bd7e89ed66b43448e3bc587cc1c167b9bab0d34412b5f0df79448eef0e2d281ba671083d3a3c13d01db626dcf323b4f46078ac406e6247d6a146946cd58f2079b1a73e5ef79538891ca7b63969385b67889d83d25dd95b9f4939fdd3197c1ae70ec63b5d67d067f9262ffb546cacd191cdd0b77cec384b2643df227698f52d735bd40a535b3278d89cc1f55bc0044486c83dd465115bf0b9a7a72bf0bd64b092629cd8a46ee5152fd48ad9e2013c5045e02d8ad05f0dca5f6c9130d32b32e859decd688fb0f0ca1eb3b3db4a96877b7770909a850845ad3d0bdc88ebfe229cd34af97f8087bff710d33c89826354567eab5649cc6c1ecf83eb4e2106dbfad11d0234eff57bc1a3fe745167ff0fbd3db8c22e45c5b91d4eee669909b12f82b404e97fa5b9571bc959e8298a9837ca7e15e1bccabdcbce59550800027c4f033f6ba451d1326342d546652f6ae312096604d2b9a59582b5df0d79522d32c432069fb80735b4b23ea2c91085d1f3f040dff690d307752e68bf2659dc6a81183db42510a8d54691ad078384da00b29fb4ae6b6085f43a883cc5d6edd251f5d7c11d3e98b71d215d1b404ba8c36290a4c140804791ba0e24577781287a501d217a0684026501084c1db28d00c79b0930ddb33f0c0548ca47d283910018612d52985b74a3f1289bc489e3b5c529c76979a883cf43c3519120ad4fc7d35610b23a5145712bd87f2d8380fa5cf6263a0d28c29ad7094bc1610c2bb2fffa21e1499c095c06fffa1e21cd59b259ebdb78c97509936020cc36994fe476f02c2cd4712ed9e6081ca10cefc8fa26da46607b820992302a5e58a681428bd00e4471f178710c5abb3cd603c253503bab581a18e3d16178ec3b7b26decff2247c803c69ffd1f235368555a76e4f8710f6988ed8c37864cb8bfd752fbcbaf3b8df9503c53f0893b3d4152d7d8b103b289d415c095bb9fc01a1c8c137313281d263479004e0135f515c1b809289a21bec1b7d5079a3435e8860d4d88f0cd532c398b80a2e6de853a070afb049cb1a1aa2cae3e1e7059368f949087dbf90d174d437fcfbdcb6a50015978ee774bf4ced6fddb8225fe72ce194ef1629a3b2792cb288fbf90ec6e50cf3b765099653a47856ae85035a625113585106a53747babc26e22ac3b9548dc7afbeae113b3820df6bae5f70626fd3886140fb3807d56d4eaa780c41e34918f4eafa38bbf90ac2a0caad588b286d5921f00f1ca0c938fd40e089b6a5b4b6d0ff28d014655480218dfbc226ee40307ca43b0ae414621498daa1c22a7cc5085861b4740eea47b8b8702093d2a46fd8a8c07824b47a49a2882ce588331d7eda9f2b315719d87db44e840fa1f3248c2605bb53e232b617d9fcb43b54f8742d4acc64fe5bf907b857ce0fcb6c36e4dfa0a9ba0610e2b8977ad0633ade99627a802d4721b7252d42dcca0c5eb360224e0dc98bc7d40222a7cc278782efa043817cc49984f1d784e0e78a154ddd8ece8005d2fd2ebabc71d84437d90efa10de51d47b5aff474a2bc5c6a905988c0c7d7a1a4be9b76b2d245224b41619c8585dd0f712178f42e5285e895edf9b3b18d4097c05f1e211b25243ccb9fa74978988104b1ec4c100c63ad0693ec43bbb83b467b0ff94f6c1043790aba29277a0f945b7e42094ecf874186be08a93d8306697a8f2b1a876876516ad1e6b9afc7e894a1f75725dbd8eb1c8a31a7623d38aa1569b90ef8a0735acc477690fdfaae59434125e9b5eff93e353f30e5fa088f1a31b748ca12c1cd81003a3de397d6843d158c5994093aa2a4d17b11ff54984c7fd4375ea733d1fe5a3b4b44e8afb502fe844df9b3119a89de761377a46e907b8d5a4fcb9c0e82554a3cc07cd123d00ed814f267eef374fe80584de15be54f4077c364383afd21f36b177b356a647cf61b2a8fb9e65e1bd7ac348492fc56e40fa9ca998f30837c187e180825972a46638604c86f50366b41dd7ac842146b675daded3f98fac904ad61ec20fdcf3b01aa5ac7bff2202c1683d50a3696a2b29f8ddc82ca7a46c9988ef406f0013b19bb5324c29eb877b5b2ac8491b93ef08e063cc88a1dc0c8d78ea364bf9fb77428c08fcac94072b1a7efd4d83edaf52bf5cbf69bc3edf93e5e0ed3bbb9bd8de5d5ec2b244f42b2693a2551a22fc255a7d307c710131f37765a4488d3959f5cafb22fea0d889b19fb5f3926e98c52eb1143a7578da95960f1d7a72491d564e04c10798870997b4eb756d3eb9b926382a9e1c77120c675f3bdd56b8db1eba3ba0636afbad9bf51263330cdc99e569d20304d0344a7f15b4e9c5213e0f114bc8d286bba88e8bd86585bd68701676ef85d6c3b02f574ad64709a9e2fe948776e5c44f66b1984e149eb9a7a3080a6996944b8e58249a4f8df9b315c573fbd55c3f6d6b0ff6369ec2cff2506dbe41cce5da29549bb83b84d350a8469acca25a166f5984ccc282b15e6fc8972d14480c832f4314c88af11e4c19216677ef09822741f68565b1c60dd1b7926f9f5cf758e12c05100a7fa2fd4762a58c11127703c32a4a9777027395bdedfb27f1796c74bcdbf9340775e2264ff7d98ef9e2c98e0c37bfad690ef8905a50a72880948da6a0b95a3e9fd7424c7b23eb76cead03720c9a01b677c9af600b7ad46d9f46e16de7e1eb8a367d54d0e75d57641965f2a985de56bf6a3678bbc92d5b92ea8a62f303a2543d9773342b7c9c13fcd0547e4033a8b5e5b0423d0f60244dcc92f81b82a6f1cddc046d5ca061de864da879f27d11ef53618ee7737fe75de535bf6242e70b0348611b920dc7105dbb2ce3fb719e6e5fcaf34e73d05804097eb1c3061d25cdbb83e878c8b261c9cee1e348f73e146cc041676f18c557563e418a9f2eced6523049c1d030ce73415d4bce06f49468c470a6c0a3adbd563d048dd306db839bedd658d26537d4601d85da680f5f7d046a1f6c21d14ffcd64fe22359a7072b692335194f72fcc6445aa7f2207ac5a4ee517c4166a3aa72fdfc8905b42e3f6836a2d9fe6d9d5d524ed53c36966d9fa3b291d5e645fd077102e47bb9e013f8d62929329271a20b80b6b00b43e781ff2697f59a717b99d67dc1082629e50db60da159a0e44b841e8489b5e04e338d7321d382027a41aae0fc355cfc1a78c0ad8ac95bb05aa9ef83ad56b2e012459f7a51da44ee618b46a22fc6ebd099a7274edd1614a75d07fad7900a1cd500c73d8abd02a4b9429f087515af3cb4cbc96da72487ae1378d312fb9cd881c08919383acc159eb43ae0f9b3b907fe5af648a41e19c2e46ce4b884712706e338ff77e2686dc32926e669ecad8ab2248b054e61c21f94bcfa206a90341ad40ed7a673f3d5527cbc44a00127b90522222441f4fda81123a64f894a0d866853516b80fd2f7f54732dde9dd6b3b7a71eeefd3ba5181ad2a73ea47243e059bbbb2b5592d1f080eb3def59feb2f966a9bfdd77bccc1127981f273e10117045854404818404ba59d7aa5aa3b81a203512edaca66361c8cd19ab07c5c422b9f54f7c227722e404fb4a33579023f6266a3d2b72b22cd91f4ee5dd9057f9b22f7eb2960bd1dde3deaae84fa8d2ef00c0b340b6420b2d0e3e717a088b64b91891697afdf48abb789d3d9940c50e7915bddd8759761626b83807a499b74b34052bc8b9e10e6adf98bdb5865f18752a0b81c8ba3fbc35c34235c8b819a51862540b4c3a1a1cbd8258efb286fd2ab41f8fb7651e9391cc66e4cd3d403be65d90e2cae7148466a068031c64df9e8ba2b80445838b572e95418583b44f5f3c14b2317637c07b54987ccc434d85e192e13d1026458b543f1001e722d72a79790aa8b641812e6499bbd7c57163a0d76d8e948168595d7883286bd107670ebc269d33b78abb346e47f7060e9d5aff00c8a02173868ae731134266ce96f8b6090fd17cf1f5e3b005e537c2958db52dd4de142cf0da4516aec8f5a5a6b10486017f17d55a5c107abae419b58a3f7f45fc54a4f581ed9aa3160bba3c40d29be65fe8357aeff9d2e98ff0425854229c8a9c07481f27e1b3dd73e9856f478e880d590abe5713abb87c96e475084559b0d695826b4658b5d004c35ba0bf2cb22a1ace1aace21c22f6d580827f17de74882084d80334b367f0900b84f752d25054ded964f572be384584f1a8213425845b5c57259acd732b082ab4e4f9d58e25a292e7260d54cdbc3264872276d405c765b991602402385e713a4bb7741e861321b4e3e86759589871e526bcd3b1f10230a273b8d033b20e88591e91f440d15b159a115e34275225b2d80873690c098f789bb0025c609e97d09a2cd0469b611eff9cded505a925da6191f54a6b4cbc41e709ebd883ca8ad01661438ae9f5224278df7ef01cd590db17bf381296cb0fe324956cd72da241f4ef2a88533224b98c4163fd6d98e05fd3418dc24a70be96df6720b09c03c0ae02fb2d514477dc0e46fc2830508eea27229ddfb357d0f997c5417b11c5e4d3d26f55cbeca5a6f7cbb420f08ea0b3fc2a7069dc62bc662c7d129a35d097dcfe7fe44020a732707da32f6a5c44badb4c85e293ba5b893c1928ee160ca6f08825d6dcfd538614e22610ef0fae8c872aabb8311fc7c842183294be61165cad509534bc0d5baa9484539b1aacc52ea538cc0ae06be601de7375ebdf8e5d910ca68703a485c3c90166a6d38c3927878c5bdb5715934050e6ea6771e0b82d778664619ff545a89abe98d32a0c966f20069e2c6c900a16e0c671138fba3641b164f9d3fac6654d7a27b89aec8d5fd2f0b79db9232b2f3e299c521cc072b86bdc28b4f61b9f0e5e71072fb94fed132f6c97c6c97bea1342d78e757360bff60a95ce8f43bd54746207a63281f8497d9450220de1269cc599a488c9ce309f88e20014144e71f1f7829c159a57b9aebb1022df3a7602905c47c21787f6db58a64b1c040e13a02230bdba825555bcbcb31011b5ebc4580103ee1da34208be217e4d3f02a93811769225ee035aaa7e69079d5f726688047293af3ab5412b702a09bc793747a4a78f2337362a3ee569493e59f40528a493b60d9b982b4b0f8ea2267083d07d3f42de3b46d1845b0ec741c562f03316cf1ca343f7241f9b3963829c6f6478339627c13c6fd9da0fc69ad74b4a5ba7246cb608047dc2c34b43f63f1fcf1a28b67e36fdf9141855046f5c2b1e6b348dc26df9893968ff2ec0e5b13ed2bd7860e4f910e2beff458c28ad7057736f493824b93d037fb7df65b801dc2d18d71bf783dd9a79f18677f9be45b467e7ae716a27acf4334c3f80049dd2ba45c4205bc1a882c0b71fa658886ae88627c7452d62a63371d491410176052ca4f3962f21eb35e4c95cfbc281739e0ecc248e53edc17530ca46732f8abd7aaba04cf68770da2d83fb4a262d4fc702d96b24aa5fcbd1dcbe24acb8eed4b34c225c5ab0c053bf9ba1d0f39d73174cdfbf0966a559f3780f73d769cfd0b205dd45336b9439898fdf653a163b192fc8f089443adae7b270a46e4414ee4a6cd76fc464dccc9eb094f0544f5ea283673d2e35d488897ae162247594364c8835351fa406e9f8c49334366adfd159f3b9723c153d36be9dec479e72b246804ea94f3a919a5b8de45a2d7e5faa2711ec1c69041b551d0b96c689b2573f53b649063032c2834b07f67d6da41e8690fa1969f8f3302cf58a588ee619567682ef62e8dc8148d16196a6cdb8ec175be2c6f68f9e6028eeadc1bf6e34ab480ded9b6912cc57290d2635c110477474f5b8b6e2eba6a1d675eecc559d352f49935b20bb98c607bf45d2f15a51344af6d0ba9ac70a3e690879e9e5eadf7c1b757a3013df9216f3cb3deeba7761495030087580a6af0cd4d58cfeae97d3a1731523a414d16d146da89a83cbbfb054b3ec7d36019eebc2b2ccf1f0a5ae2e5db601a076c064486c6857142866fdcdb002ef6d6570b2bccdd9f1ddcee28e8667b3c72238d7af625cd00bd99e551f4573efd099a4dc408299d5d5cb3cdb687465e5ab4a8db7625f305566291b561cafe8bf548d276e492dab7dccb676df3c528dc3e01099fb468d26de9d0f260982e1960f50809ea2a6a9e2d49093049044544bcd8f34a317218ed4559d596da76347f9580b62281f6424ef48409f82b8e4d2d187d91898f37e45f3e800fdc3fa261b98cc445687fb8182ac10516977c0616a550508ee4e4f90cf51fb12e4ebb181ee0c176169a5ce1c2fc82bea0eac1aa6ca52eb6456a180422c48c14c6123233cc75f188468d808e3ce37c3a4158728a6b7c91d736260b783ec835f0b2f0b3898674c2bbce07e28b83575656fd08618ec0850d7ab9d590cebf8936aa54954ce61356eac086d6e1b1d3aaa8445611ce1f092fdf1b6b8cc60ec41591180fd41dcf8c1c5003260744ba35c703e015c7fa7328aef2a558e8dddef63f32d163dbd3e36f1f490179d50e1e17a5fb2c164139b0772f0f23d865d33d349e382d2742d8acf5012e2c55273aefe480091947d6412e74bb32699b20fbe55b58a42eac552b4879f392cc5e68368ce69506abfd9535da147149f8fac265ca552c2ce64284741bb34a62865e05e4c76722b9b99f41b6611c3c1eab5e6dc2e91397b2f3a2874297e73095f2c4f2e322c3cdd67e8753e759fdbe6e67ff1e8a4a316f445e3c649f949368e4c4f51df5b40039beca186bd9c26fce9bda69de8360b54d4dccb4bbc5c0d996e84e96d6d901a85d362361dca986f7bbc2b0d3a18eb1075b5d4972dae7442711ce938a9b9821e1fb31d546c05185db7413a15de57c18c6ac763d7c0bd3d5e02aa7d5ee5f5ded52ee1ad39f028fc6bc4e99f578a5b6fc68497be0aace77c73fd88513627b732ddd9306e153ed72f6bd770b48daa2bd9a51d9b86b23c02a13788ade4c181e98bc58b0fed250bfe8c1cfdb8cb89213baf728131e80a010719f496f2cff56eb7d854c863579b82a7876d358e2cb7f5894b1d3d211f2ddb0ee4da6967eee351cf69176622bb6a3ec6ae8b453376f061a83b77bf6e61eb13848a2fbf3997f616f2016b7732862477ab8bb42745a55d46ae8dd2da4891725860b3a351b6264c372cc611a2bd06741b8866cebaa6bab9362091c0965d44f7ac6ee018521d15259cb42d02f40dce3faeaf61e56e7e301ac0e488e5da1eeeaa9113f8ae8625ab078c019757a9b709ba188d358b746ef96946b9dfb5734dfdc937933339b71acb4d0bde5c1852c4f6079a1f5280824ddffb3b68b7c415d7916297c3297fde98be4e794f6026982fbd4f43b3085a7062fbe87b1a709192b16156b79d61680d1dc278580f23fa39f161639a69cf1098ed8e551d7497780a47d0ced4d0ff4a82d6f792960d7d968b730a97f951c2c699e79a3822a8b00396f98348f25dc30394bc0e18df81e4ec03ae308ed10e17427514e81db5282f00d1bad556931ed49fbe5f12ccdc7134a45942924f8f1e591e43073f3a1aa1cf6b519dd6594cef282fee1efe563078f979a7e69e261216fe505aa04ad33632f03104e9a82b0a9c9b1a1f53ae967edf5a50c5bb288f0301dac5057f95b550c9f611253af2884aeeeee3fddd701fe94b22eeb1f5b78fb46c7c1276f9f344066a4332e9b767d3d3cbba596965dd2c432f53d263bdb969b37cc43416f3d267ad39235da6d284259379e99a4e8b55a334e6d246ee35cd1c771437416a25e0dff000149b6f036ce7710a467d45cb909d1285f7da885bf6d8b6ecc99c79338076d4bac17a642fc3346e4ee6618d2ab72f4a5c90b022d952c265492b49d664ef044e772b092c24af94bc926845c2b2d49f94e06645d2aa846bc956122c88cec96db1aaaa29aa2cacb2aa03581b4a46c58cc20c051bc4097e5e90b022f1274f68571510eb7ca43294aacb08a932e4eab32147e9ad174da248b494abe64f17ed536d078843571d639af21f2be19802db236b0fd24a7c5939c704fc6a5d89956829760c00ec540597463458c88e6403fe62dae55a0f89f35e75998950d770329ee9e955d6d84dce3a7b0da49eeb114c2d7aad0d75cb1a1ceee36bbed373665ddcd1bf90f3acfbc89c2e01e53a2cda3ac77dfc641e4cda9b06db5f3637886bf5d897d8684756750e37359e84c94207bedd90f7791ca0de90d5f8508d461490019889003b7d0fe1cf2ac44bb7d7e52dc1cafa41a56ff95dc515ff44f3cf4a5904b8fa14b37b357733e88408ceed0812ce2123e2605401e499e1a94a750bff13878968b0d2f13d892297f27f9095c7b28bea2f0337860b04a004559255200534561902763f2f2262768c54c3cb222dd37f3684f3d1968807454378989becb0aeaf058d6f0d98953ff91101d1d371219afbe7591e37fc3feb3772737f8890a4cf58434f0e9c90cd659c29bc44557becd6bec18a1ada1911dafbf38d84ce88cc73411411dce4a6f241e0b832573bc355c236c981071b537cbb81a759a672d0270e63b5336edf86a0569684f70cd739841cc8a0492f3038d6ab0de0c8fe2803a6a018caf9989eac6367b4410774977f6f03750831145684a4fb4353f5c0111906fdf4e1aa0b5dcf7ea306b82c6abd51cf2c7fa88bb074bf6af6484335ac411aea35a21f8f3627144efa881973d52f487fcd37707fd25e76e0263732e16601635ebac7663d44728c636f643728da752ce2bbd01ce26730add52ef319d0ddd0ac1faf40c58b7d181b1112655d09666eeb298fe7826ddcf6858209f094df576e1cdc65c8e753dab7c1709b8021eb6128fee01192c05b9a614cc58d5e97d4c9a54074bfb13c8b886914df2b8a4726d48b4be4a118a91bcda6fd9b9712d0d296781e33c6dd2feab5e0658e28694c05b29d58806f63e556c747cdf1015585a6659357e2c0c29816dd21310be3224a92a54e8f1a9c1b67331a7e26455df5437d73b5b7572bc3e47c0fc91bb2efb54e28501f6125f355de049f355e54831b38fa38cb7c8a3a7a395b51e2af462f9276d1b40018aff71bbc31bc47805e81740dc2b59e7e2ab41eb332f739e2886f4b67fc77c9051aded8b351922c15d7ecbd591841804b5478ba93d758ee0bd9de963080a72ec5ca257321d2d33408d75b41baf63630fb7082737967a48b774bf806dad95c24bd762cb7bbca587f068e9b1c1252de9cce998e64235099677e2bd3a866326cb447e87f3a04743dd50a38a723e776e98cc279d79378ed83f8421a1b5af28a37fa6732936441121daab432e9e873c512ce8a17bb3ad1b4e609d15278ee29d2503985d2266d75e8e1eb89dfc6c6edda3748004d87bad3515a961a3d00eb66f47aca00d25ca0a3c30a0461ae9a51d641c97b5c75cf1584e2019dc5667d35a0061cac3cce379ee1bd7676f17b2d9240790148fa5e0f0796e71f01a1f216aac2c19649806d5343a79d2724f4e56be1ba21024d7afdd196e15c18477aed807890b7e804e88f4c05042f0db6e12881d0d08cd1a2b31177d53242becefdbbd85a438135fb8e0823b33deb5bed7de6ae5baed5b5aaec78d4caeb36acd5e77f1b4f37078d83a55e947cdd4ee480d1c85d935347850cc2f8bd20a7bbd94a48e0cd4a4982710ba04483a17a5b0a9405ceebf70013fd36bb553a30e0106ce6d25822494860e5f86dc604fe2aeea5c6db5f0f0d29fe76a7c9a849f8eb2fde861c467c416664c153c1d931d0856b482544de2f13e7b7f7b468d7364883b882b927eab8afefb24399b88760fe0811a898659bdb667d0ec9fba1c4faeac64004e206bafc50f63af9de04941f30ee0952b02c7db8645e17289453c15eea83d11c345b5ef979c4b8dbb930a61217f8da563778d92dc7ec481953d4487221c0dc362b3fdfdc8f4751734237b1a2cd4b12453f526cdb23879f25407fd72df472760108ccd7c71865587318eabfb79ac3a26ab9e8a3e82e8349616510c76ab7208240b419e9326fa675c14268eca0f0a6f4c221d22a77a8cbc7d0caaa4b44f7656477d74222ea625c3a082e687ad7928be29c45be867194960d465813b9e6894f0a2424ad2ad42b8399a0795a3132794684975595cca27311a2ca223f8c90d90678a28f15a9adc41d31adec8a7d55cb7222679896670b34dfb728b1c7492c380b957d752be937f0acc0ef34c8ffe10219a3a0254c98a28a4ecd0d51c76c5a8af15023e096b28e9c41040fd5c423c81150dca1a9193462395431d0a53a7555da1929ab989e15e0ad2a00a4044887857e92991caa29603e5bbf373ea639785f7c6ea71a4a964556aea92239f2b190cc8976e7b6ed0da9fee3b7d3e8a1c50c4c9fb000d174a06e2780d3c5c73650eb959782308b1797214049d4adb5f4bb87469e89e904726d57ef38216e1a79ceb590723d5f8a86e35e93a9ff5f5b0880ded48025ae8238d2b3a2c6eb38711fc1ee1239c5172218fe2b22f0e5bcc7fffd6ddd5b29dc884c3ad9cc7dc08564baad90e3ef0b70d09f1131933248b7b5bc6250d88e52fe3a311cb54e4768841bc3b9f718cadb1c8111502dbd48dfc7c172cd81a12150b5f0d95d207a0b45e4bb1a739bef565a6a034888a113be3aa9aec2eeb1d5e0920c21af9faac4958ae8b9ac7f5958662055506cdecbefb5985eb2355aa96037155d939906b2c445c51e75383b631dab2e0ad11ef07162e4509a98e5c66464660b2359589ce8eaf9325b213833bccb62a7ef3f7f38018c81abcc32b79559ccdef1faad36687f061de1c88b2e8220d74550a4823c554a94c1f0e1412caf105ec3f427e8189dc159210df5c4e858108459657268135913681478a7c9e849c46c98d30a54bdb0d9f60cccedcc45842acda73fd1194b6370f60d854fcbbdc246990a6bc8a136b9374eee713531c8924c4e51785d38823850d1a6981e1029f567405bea3a335335c8141963b636520c8bc853a5e920c266b8d244e5ddfd9748bc7db614ce646870c3293daac2397298aaa00d80cd3fb283efd11d575d664a4210b9b039c069e72f2750c7f279bcbebc40dd2cd216fa93096f3eb8c22cbcd7c3df3f353bc1b6f913f079724b5a6a498b5a9fdf2d2005a23405bd5df37d13d60f80230fbb94a20d2f1d14af74c56d6b056b0c67f396330dfaf5bafb9be648d3f26f13e43b256c0401ab2d7803d493b64e1b0c485427fda14b04490bfa1b4e996d0ba315e4c6c6ac582d8132d18314cc003b75e5d4cf439d9540d7ed923aa5b2557bab59e40e8bef6a6da1deeca85be85ac75d1c5e1760d5a13f2d2f5ba783aaf404e8869bbbb0a612935b3ea2697a2b74d71451f3a0c4aa6ee1c8364b772915c1bf60c436ea6ab2b9426cda5d4fa2e3d352e5d39864ac8f242e941b388b403cfb46dada8d676005907fa0156de88ce576df42c30ab2089efba557f144de847ebf5a74514278e100cffb4d1caa9bf55a48b9e1b4efc238faddbf3480b7989ffd784d967c10f546d3772e8d7a03bb28cadba08b3a0e6c1018dd3b8c3130eb4a0235eaa2536b441b5bf8eec6a7c36bac00568899731f88bb44b7a7649ea6d0436100a373784c602148c12d9667e375965d5a72c00b708effaa8d63d128ab61858dd4c79f51947fbc1a5c4b9c833da336f23582df86e2619b05d18fc404e6008da8a5347c033322047395a894435c0666a502b0c0ceb0202ac4f76fda7f542cfc7b6c548616b5c0a245bc5189b6c872ee258bd6fe7df20a82f08a132d5410ac9a5b7978dd768660c036fe0dcda36b059b73006d1182e304487d0d6d3487555fc0615ab33ffc8a12100f9c80bf89f172ee3163f080e8394e09a58ded2714cef3b02ed4016b985f96da3724ad318623d157a50558097283006ec70d70e0a853ae3ec402490696247bb529e313b49225b545ee897e433d31f01d14cd819e231c83dbb7c4f422735803ec4e4ad5e710d14ce224fad5b3ee03f2d42544b88cb5b9da9fd7f59407b6b629e0b1aaa25a2668a9db37ac38aa64582cd1685eb2b8a8de489ef817693bb53000befa1b2e08a18ea2279e7bc892a5eb1f23db940e1d8dc953f70911052104ad7532a64ed5e37b2804b30b41c862baec4701ee08dcdadf0cf56900ddb8e5643cb85ba0dd953813cc5409918aad0bfd55f5b1c71431f54864ed8e8aa393ece31d2ecceea0a22c16d558a1a9ab7ac2b5f04494323c7e46ece543de737db1d642023b3795f2fc25dfb83aca776e9ecf792d1c963f3fe6f779349bd5c6dbe8ee7616efb1e61fd7de62cf48e4714d175610eb6d209b704a7f07d2d59430d3cf10da4ea7f000a49cd90c8bfd7382edf64a438d3b9cfc1e917b8296a434126df3df239eda905cd79fca0ce858d333be9f4c8602820d1dd9c0c9724215b097a2bb8ad8b4c0c89ddd2d208a700e66e1608bd0fb503c99efd5355f1b7f5593dbe57011bcb076a7383da8fd7682e55066789fcea24da3e24ea8b3e269ddaa8f5fae44da51c5f60910dc7e2a2e051a43657aa34e97dd58e127f5414e42b465a57d7335fdb374d33edd6d7052bd66a76b624be75d992ac702eb7dc27ba21988ea799041c009a6781ec39c6c87c46d83fe031172d44a441745428ab6c1365f553d2c8a6b25031be3564c6a38f17e933b7bdd85738b3bb262e05f724a177b65d1ab78a60bd6d64bb2eae3136cadeaec77b1732b999981cda0bc55210ca23cbc99c424a0f0622a69798acdad83e1c115ddda853977db1e6cba7a58f44a61084c3720ebb8ccdb433ef5219f6090cb82640455d8181dda32d1f4bac2281b352234884f81c3874f9fc2043e8957593a4114f19fbe5bfa47741055542abedbf501f5bf5957bd21283aa08774aba7b8a5b889b508c970fd57ceffac29a94dcc5ec8c41dc7accfaa6d55fb908d6fc754ef8404e82938928d28630030b6dbe6e3db882b39e28ba73e7b1130fb4c2b561a66ae9859496215e3a65ff92632595cdc6cef2e0630298826a666319765ceb3bef2dcd44f536f6564f6ada58bccefee0b1cb547c7ee2d0cfd4d88de4831dbc328acfd3b0f0268c6e277d6dfa3b150264a1bb2799d719f8675cb502c117ad1f5bd2b289aba5e98ce84aab22879edc2910a6bbb997b04fb90c5ebdee0f4394c171a494c24a46f5f0d1df2c8b325ea32beacb1843a37490a4249d11e4b4a700a5680a3295a91f48d875b3b34a2286e2a187058dbbd00000c3ccd148dae590affcc5ab1efd0106d591cda3192043874360884b5d99595067f22f8cf9b73f0465b38390b7b613741b92382109b8ab4aee72a8d22be12b0c7b197966fa1ab5f9e4388698133cba7028e08eadebf1b2c7aa125d8e7b4b3d3b531526e638d03888bb6737e7eccea941fa62b865b626950de6fa43c325170dcc0ee13a7cf20de95c88953b2e0b39fd75bafb8c2877c612d85348ddd12117c9d34ddbd4f71a5c7604be6a29f0b4a443af18b83bd38b35a582fe9f92360027e0686ed46b7f31dc5c5a6f78c0c1a341ba5219125d649ca044f29a73ce94e247a7abbe4c39d1a6dac4d82bb287299252a92f1db5c4312bb806aaad4dd7c7b3b52045637e8b8baed113f04f0fb2b395012c4a9f735a40f436e3c914ff4660205b0506406157f39e5f173a65b69fe1a690ec755c58f882376f722761af160d6cb2d26ffe0518f51baeea0f14696f81e688c8247f00cd469df0ec7150c0111b527086cf72ef9b6baee1ace4aac14f79b65b035dbeeb32b9d4caf0667dfbc537b02f9a8018996661ebb0273306675642d00dc541d3b32223a1ec9e59b0e4b6dcf00b60256d2231d9cd36e6dd2b3f6b7ba70900fa724be10d03c23ac5a3dbe88b41b07fd01c35d64c59d07977c22401e92112135ecaa62595cbe9f83966b85ea0d12fec05a0ac28f4d75e79a465dc1ee154a52b998a4ba27ffee8d8f8d9d4203327f90889dfb61d8ba85815174e6c47b1f05c4f3e2284562c8c6e41fa12c5a5146637d049b422610095708881f7836b57007235442d13a60d245d3f3a324f546aeb0e9b4924ae1f771f94f15dbc993a8bc2ac7f87572419fa2802ef9e321577e6ed1a71f1e3653be38cb7f4fdeb45622f6bf23ce5459ae7690dbdec78cc2037abfbe8b58305c554d07991ca4d7dc109b29a81b026717a1af3fe221e0929dfc22d6c097f4406a07a44470b661ae5cb9c7f17a94a64160c09201e74825ca2a1257cc0cf16ae92f5cfbc3bd889f264b0e99586cdb72ef2db79452a62403aa089008ae0858beec6c58397df69c7346165158822cdf6304c2bba9205d7e49a69ac5a2cda2436468a9542be8284904a8756c914d9bbd33376dca1a333736ad661dabc8cdb93b27464e5efe6c7c22ba3c9eba3d3f1de9b264acbc9a0cb9796e3b42490e5d90d0c932880f1f7e386ad3b12347912c67c8a8317363d39828a94c64ea114d9828a1b552264a98286122d44157098eb343e0f85cccfd8525c82ea55fb77173b7c085b3cff216b58f9ab7439e2a12f867acc5b8f4b4ca9851c3512db14c71dbcf7fd931f5124ff5e7f4ac3ccdd73c2c4f31a56199d2be5a65ea868f5d029eae564920a44f242839fe0f124a7e7e90e0f9f9f94922042404d42735e01eb2e58ea9a37bce1f8eea1e2b6f6f8b0b0c8c769ae2565a880f9b8f1ffa6d8c81d3bdaae9f653ff8a0e47f5d790e1a8c8e3fac9fd336e7c4b3f8f7ed00506464c67f5352709bbbb76845e2ee59cf2f582758ee6da8a8b8e6a99499949a9454dd43fc39b0d5c6c0945b1c699939393e577672325e62f77e150bafce200fb8f9ef434a37ca18d81719540f71833d7b7bbbd80512fd0dd7d3c00d95562a4ff56483305490a14383a76d0b9ec4647d927608dc3a12f57ab5b2ddaa238381487b65a60708a437168a5381b85a9e810174483280c4661348806d1a02314466114f69c4cedbaaeebbaaeeb3adabd64aabbebae93e0b04bd201754abaee264996dfed74b476607cf15ff1937f77d3dd7477958373d3dddcdcac562b7b7373d3852659a904aeb2afaeac565729d24a0be827b73b355661fdaaaf9d9d9dacbeeaabbe549cc061bf8482e8cedcc9ec4e0a942bef2d48e04eae0f8e3a61fcb24360773cea01f60f6fcb36015fd20acab5712403b00d3894974b49215d5227a33d0e107415990438e33ff91620905554c175a51cef6f5fddce8b3d128b5c3f1b78f6cbc7c17efd7639af243af1400755349b4c6b564d5de49b39bfed759993b73d51e65ce0e0e0d8d8ccf99cd6ed80c3e5703b593e57699db27acd44feed9d64ce5bbabe80fd0ed9c854bf246793699ccd8c9df4912f1752e0edf5025bb247069a3808fa0b0bf7e5031f1c943e1c949f831670c8d164f93f1c94349e02224f353106fda1527c596d2f99c2a5ed377083d962b6d78acab538202ec602c6c1b816d7a21c8c8371b030e0b0fd24a58b6bd1cab5a490dcdddd2caec7628cd27d2de48e49442372bf0262cac693bf0caec5951163c4dc37fc0ed8641a82c57b87c8addc07576e5881726693ad4e2a95aa025520ae5532e53eee99caa1c0351b18932bd05781b8fb853787154889a75cfce431468df8d28a89f193c310c2b3be9f9ca5c511ec433fdf41a928def413fd7672f78fa328f58b5d886e443732e52ff9bac92f3a99d84d29ec93c0dcbf7e3d811dbfb82f3a197f51da9ed7c96894725db91694273d89f4a44f79d2471a282b1ee551be20a609a098fc2362f428afb242c593304fbee4c9e50890cf8dbefff65d4913fd5665e7a183c8fe5d1b07e57bee5cfa020e259016e6ce8e112b3742d5acb96384b533678cc562ddf0f0f0b864ca5a8c4ba57f10f4c1a1a8d5e211f1885cd9acd9ac97a7d592a9596416693511b15c3df4b5b7af1c8a86d0bb7265bac17e1c65bde6ce2233e7f210a1353535422eb62ea7713528e096b518974a24ad85437f81d7660187fd729e8c051c4ea06cd6fa9a401348a6a41123a2d64bd4f281f108993c3574130911b136cd56232d262a23b552210965a262a262a2a2d73281437fbd3c11ad58bc70388196c8f27d8098ace24bccf30e75ac9b869822e5f00ea1e4f00e0d49203f49d257e7675304ee25e24b0acb93c8c5837b04e03212026fd9cbbdc65328df90ef0ac9f2ed1243581bc0cd86b2a16c287b65af4e664356b552f9c13fb098f6772553a36f3534e41351538ac84444d96cc6185fc39c89294544b98f07752a8fd9cf5ff1453e0c0c6795c0e16d4997747153cb382d63653f95644a5c6dbfe270f7574499929a0531319229e93682c5c4487d1c4c8c5426466afd7030e129e2a8ec5b27c6a8df5ec4c18e91e1a017c9ed4d7c284666afecd5fd886cf000603197ab87071623c0257da354f771b0df5fb97780765a3265d231ea2079ea1f7ddd2249980bd3c9dc9683f26f600206f3e57119e00e817b53358d8e2cdb2430eae664f95cea661b736dab95a56d5996f49110f8fe4806788753eeb7adebcaf695259863672300b955a2c09d4279f9922e791a0981b31fc900e76839bc95b41ad980630e6f2b0721585c7ac1c4dca12c7b5a2d9e76cdae74a50574f00ed99a9a16aa067644912cdb469b366b715963c383bb11b066e2c3c487890f931f4dcaa197c578bbf3c5801a2f2e4c36e0b662af562f07dd93f84f967f051cf6cba5cb41ef94ed1170d8afcc6fdc1b9d8c7d4aefeaae6ad72760edebd795a366efb491b913ab672f26954eaea8e36192aeb5025e0dd55c9b4db3a2d5a6d91650e59ed48057da5babe356a77d62b98d706529ea3891a8e3565ac04e4665fb4cd4ea64f9da67a276c77e3898c4ac8e8a8a113b4c62f47130895126314a3f1c4c802ccbb2b2fc110bf06d2259fe6805d8bfd96ea3225f1296a50bc7b54300657f3b04962ee96a9b953cc95f616c5f1e63b48f83a0845d97ae962e69773cd537f224638cfa53c904ba516557bd2dff6eebb6329454a37cfd42218dbebfd0ca900143ba52acc5f61bfa5e3dc404ec4032255ac9534489a8104126224ad472b00e3551c5182e53a46b736fe4a9e5a80b83488588ba3511947f716c502e2bcbafdf5dc517f98dd345a280af4e4ee3dc9df8227f2ad1e99c9c6dbee66a755777755717c78d5ecd1c046e74d48b1c9fcb717b986da54f9168cb610ab27f4db2bcaa9a268eead5cab3f901d70e816de25b085c1e2442f9073f41017341597eb4429ea2699a2047f96f9f861f848971a71f0f12187018617953e5e841831120cbb755f899b9e182c5349806d3603df4605d38d46cb253946fa178cdf9490e6f4b0257029d8c08091cf64beb39915a8fa35e3e9a0d8eb1718dc7469ee4935e83828cb7628e4ca17cb7f5a1e6ca3287de964cb58fdb25e0190ed67050c616d5588e1a4096af0d718d488a37f29ce479271960692304c8956442438eda3e1b31377114cdaac651b2e4607da056b2699f655956ab0339501018db15474514c941e7462a3ca30305c9d6e60818c2451fbe3ec9ebec0310164f63f162ee98bb039fc0ef3dfd1d4444903eca737d61c020fd8caedca4394935626a47ec89af95200421819444fa9a88832c1c075948cd422235e9636679faadb2b12590084d44251ba964d928fb982b8b87af0f40484fb7154ffa6cf8f861da82f44d0e124b3f8944fad9c990581a6612ddf0339cbbc16b9217b3253d2591fe844412fdbf89e491fe492c9ef7e2df73f19cb7f24d04fd26829fbe95a91df244df5a5809464a21915e4525e567b88aca93482ba42f0442a5a6584b229148241229e555be1088acf224924a4a4acacff04bf2547ec5233d8b97a272b5cc71decadfe099be3bee77c418359b5cc4182fbea7fd996e5b7c3d43b9b28eeea429974ed2ad52c5e4792eb97817a5d20b173894fe2bb17cff4aa9f42d5a985ab4f852a9f433dce4852ac82b1f3b99120b2fe6162c2dbc7e162c1fbb15962f79fd2ca55f5929954acfb2522ab158f94220f2ca974a2b2c58b06029792d7ec52b3d8bc7a285c9d372e7b9e756ddd07dcc256f7b16de8a6f669fe28d3eeca0ccf80b1b0699fe8927f242306f7f3d1bd1b4699f35651f8b97908c62102325c5f8616f19379ac6aea959b58ae65eada6664891563599734e210ed2b6a969725bbbdd5a106efb7b2fb7d909e320dd029eefdf6f5b5b01179d7a607b517fdb73f3672773b2a285e907627238933dd0bf3df5c299bcadf8f8e3a7997d0d8365130b561863fa583215e9b3f8beb07f851782d92587ae127d76fa88c9dd6ddb6249337998124f1f7a602b0cb915829a66c63240d3c3519387399bd0d04c4133450d17573e8471f121f8e2c3ff0f4bde079ef0e0993e27ba4a5aa88584e8909048d41d955017e2e307aa7556bbb5ac6ae089ca5300f8d9a1bbf25ce15093c3e3f0f35d488b0edff2f37fb82b2ad1a1c583918307e3e20848e1f05d04005ef4930a070fc8d01482c1687eba49b354b8590525396c25797edde99cca42e527c74285a39d4c9dc2e6440b02e4e4a40aad9ec286d26e3a85cd143699b3f7b2f809800f6172f810d4e1c3073f2c9d3ec401f866f101f04e1ee8e9e0e5e001c08bf2c4f259b7c374703e8b0ffcef0badf785f8bfb014be0b962f84c92be00ea73c1a0ab1e9861c799abfe20bed6739e1e723119c4ffa18510d73707eca142cfa95c5c98a9fe1dc8af939728e68a99b6971bb7b80df1be435cdbb422f4bbd2eb26b7a138bcfe4cd18212a683257afc92bfd0d5e8b3e12513182a49fdf309f3fadfd51a9f45cbd346b9aa63da5345441ceb429d48f372f04226f742a6902d009e420a54f299e410e528c7f86779d795af75c76b74dcbdcf5b25fe1b1f8ee6e9e2c7e967ebee7c4182d7ebeaf640a879f1f65aae5fb0b31f7a29b61e175e6feb6f5ae67df9515873b69cba51300b7ca1cfa59789e392fe6d2b3f0b8fe16fd256fa7c58b12072fca1390cca9b05051798e63c1a978e10f2a5e57df6902709f79dccf9095f3bce520f7dd598b543ef3b8af2a222d8b3ccfb1bb9e1dc7515a9ebfc2d3b2e833c90be511f2fc9117e28ce261efc453d988364cdc17b3df3c13fdee29cfa9699afd194e637268f3eb851720062c564d0d6799c031c6971e2e2a1509bb0b68c780a5941e639432c618a58c1f8e1f76b0893410707d9be39b7e80a3bf76ed18700903a46ba5c0560a0cc3c1f86e810bb8fc0b3b062c062cfd630e3190fd9b39f1457eefa066b37aaf4864d2d1d1c85e94c3ce44645f1015d0f8fea6cc952e9184ef5b2670fca4779ca210884c330ac220597e734e5a6bcdb2cc5aabc1c8b868608c9b8ce5e8c53af8aec8ced2dbe1a00e07bd98c3c118f3c37c129e21c3c1f8b809ccc9c8559cdd4ec65a292795524ae9cdafd2e5f4b066f202de368ebbb7eb4422516fa21008e959d67113752109b2c39472309f9ada9b45a9b777975ce6c479e518016a1da06ee5f9d3723bb385701b9535666ed45a8914c9d931326bada9eed51ee9b2565e2d64c2c853de642e79c6e419c4870f3f7e00a263478e9c3cbfc70c1935666ecc4c28cb84eefc5a9110121212126a125f629e3d293a3a3a3a3a2ed711495c2e1d97cbf53262c49fdb010646f67b9d55f61013332525e530a693b154e06e87ce86cd273b740e9a169c5a606d7bf1a5df8b2f768ad9d92091b6ce064a04fb51620844f6ce06f60f9ff4b075110835bfb72315cdce868d2ff1253e6723ad99dd44f4fbe9479baafa310fee33b32ccb6636b36cce8b67ac95f4907792f8c36db4815aa8f2701ba545b88d4e1e3d3230ad36df8a7ebe9d3f7fba58078495e09f7f630cd69cf8a4859ca6c6455ee791fc34bf67e55d5431357278e69c733ed8393bf22687305a55c8c9b387b050dbb7cd9cb30aac3c1fc80e1d3c7acccfc0644264da4ec69584091126448814b91145c124c624160026cc6edbb3fdb6f46cc729452d06ce2f886f7bdd5d27e1397b529189879afb2790ad836de0e88c317371739745d93a6abab8b91379cf3833511b6979cde320fde99d642ebb36b6935182765b07cfe7b6eb5dd7cce7a2c87e480a014fa62b59cde8cc649689a4a6a1a084bdd34d88def54e7ca1cf3205066235dc95ee73608509b2b1f383c30e6a75109ddc173e902034ec9d54cad39596141e5ac0e843194d1ca4299fd3e8c08c648018c69fb894505ab0e89bd97e2ed4432c43b8878341648038482310162a301099ea9144461f884c794f4f563f53f6ca326b670644764d500b35f1d58fab84bcc6594397ab4ee3363e24d3af3f5cae521e97abf372f57a2b5e8bdde86bdf41de031b6a42b342790aa3f447f651ac7d91e7e208b0ef369e123d7d67397dbfc139f13c91877b7ce84a101a4a29cfea4748a61fa98a7e8f0cf8e0e38726a124874d24531e376672e8d8415fd6e8feb6916e75123e997ebb566877b90c0d4336d33e538669123bf6c3818621fb35b336c3818621340ca93ca6796989bdb17340677fcebd1eb03cc58f5d047ab0f2d4ef273201884258086e02dfc0a68564ef1b647fbf418c11b3ffe0feb094c3411d4dc09eef192dc1ed96c8cc8931be3c733c79674e8cf122cb804fe040caf239f9f257628c98412260ffee6e24828296f42ce9692f4e941c798e10c9a42335a84784905266d6092027866c8a8fd621b1899ac015719d00422675b73d27762072c2e6a4e6c8e3841058d404853bc388f4444a8a15485150c959d1c40d8b4b2291482c4a59186a62882d3561d3a28915dc80d160122559b930962d471e98122eeecacaca8a8b1730235eacc8910756c4dff6603bc8a4518e3cb0d597061ceed7c97cdf87430b177c92b4744800e0642707ad081d547600f2706106a7164c02507a4200b7bb1c79b6f0ca2497db5b706512a883f72f5cb0ac986e685162b1e27227117ee5395faf97167a3267354a4731e8d6e95a73ea680694360e8d91488c39dfc0046c3f3aca458e2f753acb648e2ce2e0b45e087a12a7ceb79fbcc9624141b1b3166ad241a736e0581220cd669b66b3d94260d18f64805759c73ab6ca41a598773e30ed230f133879fe8adcb1419e2f5bd127cf2924cf1fad00877d23d4715bd7c9742c49938ebd749a3469224473e328cd662977a9c375bd35fdeb855d847a2cbed0cfd4335d6ee36e37d37533a26ee6a49bc1dd0c4a37e3b29a35d3661404ee1c5ac45dfed219a97013f91b07e7aa711c9c7f8a02ceac5d9d9880c38e2008f610761bdf712312c64b9bb98a01a53f94368575fb8c662012f5ab69d3ee9b390e4e1d3fd5ccd55c758a0f700d7d5c198dbfeb3acf03dddbe84ebaeec47970ec2060ade6699d4cd53c204aad794b7bed6367350d88978de832611ca57ddde3e07c244e43132405d72a88a697a0a94b8296042d095a32742353d6ce1c1d4494d46c628c7e956e7e0ce7903c47daf7f78eed52c94d9da963dfeeae5bd47dd28dbb51ba47dd29dd9aad160a1c7a6c2589037d57353b7546f7a4869235d22e9f9bd93844f0cfeffe4484bd95160fec643a3c5b182f2104777549cf929eeef1ccb1b871dea87873e44d9b126c84828297003a81c1746030586ca4baa5db2eff365d95984c8d627ef2934b19a9acb2653cb5015377a77dd22d6aabb98f8f7cf9f8d07c09933f6eb3ea42794a559e2fb259ed529346e47915c931af84b06c566f2743972469ea63a2b02549ba9708ea254996245992648992b9b216e352e91f9ce1e09caba19be7e740040ee50d1129e59208822e8eb231348fc95344c92095af6f3b991a9353def89288924088e0fc93af873ccde72810412044148f69e33974333ab89cb466230fc846cda26f477c99cfe9d0cd80b74fd70370a500ee74b9f4e5d601d48a4f504652d41f8e253ef125e6d12806dd27dfb6b376c572145723aab37ed3469b3f6b34d61c72d3cd151538fbecc3084be9384dd344ddd7aa5ec597f9d66b9a587ce99f2b99f298c7aac7aa27696b27abae64aa695422b5b31ad8b9e6bae991412edb42767050448f4f7e3e88824f96b4dc0a315996107721dbd26aad3896b496b45c47f0ac8928ff42046110513106676b8cb1a94c9c3c553c6ee4f91762f447947e37d32ea7e8d2935b357c334db394be8ea0fc55aeb415ad59699d0d9ab539e9eca1b99a3542e64d91b9d32cf72122736246f2fc6933874c9ca933dfc632595fb405dc6eca4fb932326d464286a8cc9b15af22149457a8904613c753a39f3f731ca53377662b858ee4ca66ca1d980c923733b2e7e593a78ce5f9ef312057e241618c1249e20bc7ccd46392c80d4e911c47c93ca58f4ff7b4ab5fed438582869a4c157d1e3d7894f8c9f37d302277644bf238cab569962ed1d9a889ea6c9d0d9a37255a3a96d64a97e82cd15962e4b48329fd95a3e8a78c324ae6e49d35d34682399cabb9ea66b2db2eb73bb94befadddcdba07382afee729bd932cca5dbe99cb73e5282e536fcbdc76a9a7652e6e9ae544261eb82f2c41a6f3257212e070913b09c14cadc5b8548a2d339bae911f63967596b18a00b500b9645adbda9d43c70e99d913a09a9ed80d1129a514e11d4f75ad7ef9898b9452ca550b89e17e4b02a7470678dce41c3a7670db4ab674cf90d04a6825b412b279d48b08ca075dbe244511453befc58a7fe46c37f3006ededb8968764f687fa0831e41179818a37fbe9dd99c764e6dce6d4e6ece3b6737a7887e333a68e38b897e84892f4e29ad74ce263de4e0e62a3c883146f7808023cf1676b2eb649f39f26c8107d16dc045206b312e953e068140313826db1951461fe3e8f31b79aa8f62ad3c711e5d274618ecc727b605d9bcb71f32e53d9e04480aaeeeb947ddae2fb7d5771defe906d2f12974748cdccc6639587de7ba91ceacc5b854fa07c19c6540f214638fe0fe214f11d55244500c11d540f7391b6354f7ac3c65cfb514f347f6b523987d188772fd9aefd7b0ccc2d86f872c672f330e7b2e72974599cb7ab6b73b3b59e63b99b5d6f600e9f9b9719c94d3a8ff63e8e513e3ec562dccbedfc854ffecec1869a01ae4fa1dd43639e53d30ff89b54eaea83b11755927736dd63f59eca7f6d0589c2db3369b02072707c6d149a5d3a699287bf74e32b7dd2ed9da344ac059f69ca471b066cfc5b9fd0ca7c9f5b928bdcef9018890835545e3296f12797864c88001c3c5a5a5c581b6cf1dc873251ee460f51bbfe19438d8400ed606b23186f655870fb77533dced7bbdbb5274e7c9a5f85694eda3d53c9b79b309cc7df6f57dd80f7dec70500a98fbec3d90bd0f478536033d1c6579642d5a38cc9e6edc6f36c7ce661a73ec70703e228c47e679de11636c7947d6c1235a9b638c41021064555431947f90597bab0a2267df3195807892d1b40f868315c6c1fa36ead881440d84fc865a223c4caabda6f95006249b1801f2e327d6d282841c658736b75efdb7afcd64df09235076a026b9ddc80ace8f1f8054493b992dbba17d38a618c2c3ac57e8d1a618a2bdd53ecb342df3704c316448e6b85bff040b388c3eb9be0e1e366f5db66df6677806f3cc7d56464cb970394c40ae53b8f69978e032cde10872f645975c7f25083128d3be0e715456a4c541edc30e56fb911c641171148ea3b4afc66c236f58f847abda860836abf54f3ac0a10301f9905c059a8e8cda11341d4d87a42941d3d172b4236849b8311cda766f816377b77166a2195fe2a805a4ebad83ce717246d44089294723743064440d5a4dc23e3ff41cdadcda107048ca8d8a5d0427d2820152d860bd51155e8894982ea44c6902f7936c60556e112752724670ff26a52606dc1f5e4f79c16c4e06524a29b3215ca6996bff50ba4f937c2d77999bed80cefd6e7240e79b65eecf8eae80439b8dd8414ffec1734b8eb30099ab5ee6ec45c9dc76db0b01cd3f503aa5746f2e6a3473f5b67773706873f438286013ccdfb2ed74cc6c3b1ddd9eccb6d3115b34e6a7ddf871c737c3c118cefc5d1f37df70307e34f1103f2032377ddc10cceca354332f9c9964b3c835b7452d4aef6ee6fe421248af60e0b28f2d5c7c97f26d3c1f642ac793fc2a043191e537117a33e28b19681c8c991984440d0547199a38187f86b518fb0caf86187018c3f8351c94330eca199731943420f92b41e030b63421e0306636ec9c1cada987f22987d97b20029106b7bd09b847992ad5f69949fb72703a628bc6f695e4c99a78982a55a639763c74cd3ed033f5341c3cbf30be72e61b115e9de9db2dc6d2622c1b63c51c4c51434c4e9a025304113d18820637c6e262ac2dc6d2622c1b63c5a610a33d9dd6fa72be9cddf1a56637251c16d849b2569554b115e096d87f54d597f3e5acf4cbac549dac955fd84c2882881e0cd134e862ac1b637131d616636931968db162358871ade262ac2dc6d2622c1b63c58ca06d2ad513f0c65d6bbd128ed12c03877ea85a655780a1b0699aaec9fedeaa2fb4aafecf4c4a69552a954af58298d2de89e062ac2dc6d2622c1b63c58488bdaa9786c0f94f1b20b2d556ac887cb837431890588bf13b98b2068edb6e67b58de336cd6671e8e56974c4774dc2a224509a0a300b1ff61af21229be70c4e252e91f849de07e9db2acbfd05f5045a6c20c6168244343a5d23f08c2c090688051b67e7d11707ff8282fce09237c9242ca32cf4624b8637cc1279d03e4cf17753b0091699e2a4772d8af1c873a0bb01d5ca092f5abb91081c894fd9a0afb619b218701c966ad86b12d95863e06b87fc5ed44dde536adeb3a9bd5aeeb3ada1da0567c92435da50cfe5cfb8aae27a89e678f315472fcd7368edbb47ea5349193a6c0f460081abc2056612dc6a5d23f08f67042a35f2324e828cfe9d1ae5e3d8f20ad99ca9a6508449e5fc7175a5132f87217f694b7e4c95ff3bca6e5df92feed2b07fd4d51ac4a527e5f05b2fcb27cce337b6318035300f0a46944f42073b7137d18ab1059fb5a954aff20080343b2b1f163d096fb5504778c11633fe24b13b61c5f2342eb02eeb84cea214fde43a67ec41f0efab7678a226cd50fdf0f2fe07e15cceec6bc65f0bd520bff114b1053dbfbfb4aa6b62fec21b60f9b06dc87fd82fb61ace25acb61bc954ada2afbab46c4893ad1e5364d643351158944222a9ad2fb0022cf9c45125809f78a6a90a46e66b57a23c7220526f9c8f307ced4fe4027101a83dc4b953006162664f8f1da5eabd7f612063f264f17eac6d3fc789a30313235239e66724470bec7560e7fdc5ceb37a7925a6bed7b49398cae95e872b93142c32c228a30f8c7974967167fba607a36dda767d3f3d8333a21069083d962cdeec7ac5afb853c5af2f672bbdfe228bb7dafd9afc54179238aaea341305161ad17e3c94a28609b4110c45e81a50b5bccc741f9da5a0fbac078ea69c88f915163e6468e1d5bebb5b93624db6b63fd680938dc76b69dcd48966d312c9886c2900323838783407548ad3ec42da43e0a9aaec137ed333f398a883f585acf1251eab0d6051cc6d60c1835fe613e47252326266623627b2e4a944cb33199a6652b6c8ea8411400f079954ab5e4881b20911d61031e95e6e1a082c6c321454c1e318323593a0f531544ae5f0e07e58c3c495314dbf633a7a8097312214eb7119c1f636c2a0a836410049999cf12931aba8e2ff29b7e1109e9b26944f92e570e39234f2a629848601183b533ee820c59e693052c6190594619a38cd2b3e44c3b7406415711e3cbf429ad8d34ac1769685ea4e1b5bd56afed3434cf7a38d2905a50b12c510511840a0eb22c086e7ad23377cfa4e7cceb08a22073cef9a456e627b87effb0257ba694d2d8eeeefdd13d53494495d59b881125c7f93353d55aebcc8c0928cbc2105d1f63a09fe4ab6aadb5c136e0f05b2248ff5be64790f2a02fbfc78ccbc571aa9a831d949eb5679967ed9f7937ba1d66a2c0e147e9a28e2ff4551eaa20cf971cf58e738a627ca1ffbda3e8373f2c53d12666440cb6305bb29ca67eff20b37bead8429651d86429dfc711594a9e285459aadae712593e8c4cd140892c7f0509ecdf49a2c8a18c2cf362042911f6a9b7e2f62bb58d8402ee6e97b22315c91701e7cfb9177b076b6352d10ea69c505461d249a12862440e671e92853092490d854d56b35993c3e9e508850bb6263de79c990582a2d65aadf644abb54ddb9eb8c9fed2c8cdd9b88df3a73b08ed133597bbf10528fb1342e470e61f588e4fb040724330f7721cc7ddee678921584ab7484851136686046a8750b91e6439903f32c8b22cd3b6219e9fd59451c63e1293f2c80f65820897637ce1bc7762efcc5ec20e0ecb673534c49305a08e0e769b9280fb3d20ba7de0b8c6827430766c24382eb6cb49ab12d211808538638c05f0e1e27777e3905346108cd2833031b13573e6edee9e14461960b8941e8489524a1c7286768d1bf1c5a56585645b36a0a98163e686127feb7f498ec22510494bda781d8863f096e7035050f4380086dc3fe37b1b883c487c724c92afa6695a67a36dc7ddd88da495c3c8c383e4c5dde8213122df66d9032ebea7011c799c70244bc89107894d8e488a90230f1255a639f23cb183dce5c883e426f7dff85de0c84e0e67f623af188fb88e6c75cbb62cd3ea8684ada6a18673af95dbecf7d0aaad590ce2346b27d5ecdb126e420c0cd3143064c8c0dc19c0b444d0767e1d0c7764ea9d0364ce3a98fb85f88b9647a6dfa0cdc2996d900cc341989ac45fca57cdf054abfe86671f39fa241cb3a711049da9ce0d1e3e7c48e71d3dfc7d60b510bc63ad8e1b5608a54ea8740c99ac62eb100900000000c315000028100c068442e1903c8e04c17c14800d7aa4546c4a1a8ab32807521c860c42c8186308010010101899198d0bc82e978dd3ebec69d3101ad964fb9531716932afd5854157dccda4c80679fd12dab4cb7bdb7484860b7d97731ac2a7b8c7dba63d34869b9991d6ee8497ae4beb9a72c019b7e578a04467d6951a8b43b6b599e9c9d713439d502099d6f70f09a943474dacb36afe77ce4e1fafcc2eb1398fc1e28259846db656e018a4d68739c15dd6e5faf889b4b5774c53eaa4b26059a01068a371dadb144cb5a6a6545c80ed7b7b4d4f07c7576eb5b69412c503a80377bc733832da14321c8f2f36e9364d49afa67ecbbe14fc876707a6824536b55974767367604b07231a87e2cf033818fbb62f1548b0254d8b348a9a39615570a640ebf3144e7d2af96230b04f86a80506a0217c13a3e1e2508ba127ce38974381768e75314b79970e59dd8dcce1c386342696b0504c473bf31ba2aa211fc7945cd7473e907fca60e0cb2a784ca8014bb68abf4281beebd3b892dfc100791dc6cc5f57ceb70c0b72829240d839b7302107e634410aabaf5348bbc63264881a6662ff065d0e1296f443b217ba9bf7a0d1e8cb486eb649894e10a51a91fb27ceecc4db66f2c218d6b8793b83601ac2af1fa043c83aaf40ef3d333759b1b329c68c31b7e3c9edc33ad4086ff1006eba911d48fe934803f8be0210a79e39034394240106beb863906756da2467a46908c8c418a0d94d5424e0ed2f693d252143e2d47b6c7bff24356c24fb637cf65078b595d8bdd1c85bc98ee838062103f66182db6b22b1a6eb3b9bfe8c62fabf9f936de35c16b60d145ab9c0e2446629147d3400159ee3577afcb47a22b9174a90e3eb014416aededb6635a5ebe3ff1dead0229db740075700d2876f00d722f03e60381c824aab4eb98ce26c7ffc6ff2744bf7628e069195a2187c0844909af1347bef0721348344b9fe73b961836ecf08b38731e6883ebfee6aa5a8367041144e0da5f1ae961341b5dc72a8342ea6613d04fbc328d9050e59cff571bfa9843da26c6f680d0a0a226c12542a6bef3a889c7088f7d5535b82abf4b686e1d01b382921159d99966b44217cf9c647a34d8870f145c10da5197deed6f9d026c2186e45247cfb939c85ba18d4cf904b73b1025c0d48c8741dde4aa6a74a8f596c93ff82deb906a9bd38e3831d4f52cdcc96343644ab952eb545d7ea29a4e7a2c3c07d7292af8ce9bec2a9e383b1960fe88e717d25c1f46899429f2615313c2b6064308788a28ff4cf02e5f9158024bb10a1ea55fa809bbb24b478a1e959bcd8fcb4ae78acf7b5c9c1419a70f890cb6dc43f97e273e91e9cef8a026d3f71977e03b43a04f8fbdf28347fd7af21c05005f1b0652310275f177823f3e3d028fd29e7715a961c991cd6d87ec37ae6e3921aa932b0df8956dd260b4c602253801dba869dfc8e53e8901cf2b2d433beabd247b31cfd1dac3927cdef1cc04ca96f5fa6923f76b1116c8fd8b8cfd448b601001d2bb76d371a6dbb7d2f1b48d142e03f7b8f046c1b774d1e96c30cfd1a25d3ccb23d9e3d4016224f4256135c7bc58971be85730b104dd6cccd7687701b921c299d8e199ebd303f857fb4bc1adde4d4ffeac79f603e57817fddc84a8f9f6e466b8936aed4bff624982620dfa106f2758fbb3e2da32e73072e06813355e684c32e8ddcfce173f11ad4c090df2379b98068d5fc1f0f248976bb8049434b40034a16d33c995300367c74366a5ebf96139aa53397e6c6bb201a8e3e190bcb112447b9cf5967eb743bb9316ab8f00a7fd48e6efa850b6300a23d065699018cc55eac5a1eb7b12b8723ee5428890d332361fd7fc0b8e51720e3e1d35b21e7700ecb2eea0794e6de6dd7f5e4cf8857c15a4c08e4d631fe4057d1a4652b5c90a57e5cbe691a27491f635b9050028865f9644b3a2e35620b7b86b3ac2f221465b2d5f719bbb89627051f3bb3c881608d1259dd797e8f31db925894688c92269de933ab90be36ad0c361d1b8b7d55fa55c0ff83dc3431b30c51b5505c27e73553650cf29d3e592d534162d1005e0bf0c2697c8fadfb9c9ee7a389040905be2a57c7a2ce0a172de4f7e0a04313f22824e8a3540a2caf22178e2a080f2042cad3c07b68755a1428a78c1ce9271126180fead89982ef31572f872c670a5fb58cc05a8d47b4826fd80adae49d6795b5478a7d5f516268be0de536ec66881d6a22f1ca883dfc5f7dd85cc10a5863714a74e3b34bdc543070468862cdad683232643a7e31ec18ef81664068f4847faa11eeb7301f48f5762fc2edd74a869c46d6e372589cecd82701af222c0e5306e39072e84f16ca924e3979b34a4e226fa14e8a989e08d742ebf02536d8de45610b5af551138b98df9b3df4c418fe7d7d0601375109bf73649e12960eadbada8eea4ce81dae9c6b1c1e17f2eeefe779f6e9c24d8ca8b369a20e6f73b6ae08255a201b754096e0c6cf734a10a42a2f6a382ddd2963502b8a247571082c55b279dc8dfa169abec08f28bdc29b60dbbf6e410cea70a9e771b3c7c98217f6b539608edc95f9f5d873437c33a828062a6f02be8fd28841f213b763dbc3753dbbfbaedc9da9935d890b90dc64796b26c35ee9c1b0562827094eede0713157da12b5e23e344a56f8ab964cb11ad154a55153d7a597cbc480850fe20cb36ed62933531fb6388997bac69286aade0b1121b449658c029ba3456eec3d574360362c11b26842fbc232260e15819d7b2cd87a3a59336986b77518d076d4cdc22645a2ef17bca6332f49169692383004db43b2eef6e902bde34dc28b7d3ca4fb7d7dc6dc5da8de51883c5bf72707f107c8a7d31fc30252a287a4032b5da83fe091ab4bffe6dca77f02cb7894777c12be46cbd812c4d7ff7308b8babd129309b869de4848255ebcf64d4d618f104ec2ec9692d9148b6347392d29ec033293c96bb2019ce868ec59791d8add8f4a90f6aeaf50932dd83a76d83952a4cfa7511cf96b94ac76339112f088120de0fc3946aab82df09ead20b58c91899e202115f503747b128f1f1d8af20d3ca951c2827c696728b8ca5b22a5ba733af48cd43445882c60f27d57ef481ba0db052d974065990007436ad2fc0fa664d10e43df38d8cc37f2e415fc16a59d11bf0f8a036ec857e495df04b77f77b5c1853b0dc7de5ca972c5518b4a96ad7f40715018eafa4d38e3ac524658e1f9bc15c1fce195136fad0804428b0aff152d0e076d130fac2aee4fb1c19b71dbc42811a4154b59752875447e9ccce7313a70563cedf5cc7bbc0509908bb1c192bb32dfe4c85a15b0e9685f314490608ac2f235e5c8a761a750714b98ec67efa23d719b4868ede02c3b4a7edcb61562658b51983f0d9bfa4c8554272a234f9834a48955647a857f234db5b0a5bb53380972abea0810c354141952767a5372709393cbfb44cb71dc4aa8d2d746398cdeaed7835c8d5bd3c59d06cbbb66078d20f5d39bdf6dcaeaba02bb2efbcfec9ca27ff374956e808b28440e0e4fe308f59ba111a084631a5241f224580059a900d040f26b8576213718289751cbbd602971435af45e6c5cc34cef2511dd675d85f60ad83010010622059ac9bb766794b5f62ee12ec44742c017ce4f1bf14fac1a2dcd1c6c8e2715d3793c6935997148fdd0ad96c2dfdd313aefc2ec5c3c5da67a40a85ae66163e6d20c7db416c432aa0223ee7512b3539f279ce1a874d7c378fc8312234a6e560fb63ca03c2ac1e219b99bc124e82801b91781c883637a4ab2fab978193ccee77bf8445496a06a1cd4778d5a26057be5b513bc397cf9d7927b61babdac9af7a2a48a73ed841f92ca3d8ed9a07976aece0d5a0c0febd09ef2650a7a1ee3ddc2cd893c7b9566b77562dc7b3d7d92158e169b08ce55e695e91ffab3fff24bea2af7425758f4bb43a2e164b196e05aa4540880a835bdfc946625f2a8513592d96b21af7e8be466d89d3fdf6125196e3c912e1b3ace0f3b426d961b0f657c9e24b3fdcb0eabafbdbc0e6d27ac576a66c1593081ce43f78028bfb62b42acccc1a5d40521436b9826125b20ed9b2a571351b3b59eb08f21c100e966eca397f76a1ddcff2296cd6b84e96ddaef8df11a2304d7c893362862cc0237cc897be120ad426f0e8e17ca6a653738187c67d10a5266d02d737786d80f0fd949703c799963ca68a113b589e334d71b06de49db17eeb86c1c9c0160e1ce89550ac5ae40201f269e890ca0b0ced40a4fe08e07475578e687ef102fa0f807e379708f2f8d373ae641418417af5a8e04fab3c3631775693cba69a807fe78414da593067113a649632e14ec49813d77e514a95a4bd2bc7fc4553d25cb7910fd8e5ab02c68703d898f7e819e470249a12e801b12bf7646bf34944e88533ba3a36e518b58d7d92e6bf9fd6ab6818acb3773ab5a6e1180844dd04286f82f5d54f14394e7f80e613181f4ac50e89a6e6bd7c926031c6d8373e5ed1cc88e975eff06fa519073da276979c663ed30200969a7422bad888e28696c818f068cad83a664a5168f429622e9cc427535e58ffec193f2cc139c1be013304190082392a97460f15c48fe61c448057f149991b0a8200b1523cca73b45fd7d5985948c74ddf10d4adc070b3a63f840c75270b8d6b5a7956022fe05027eb34f6e323c7ba17cc9316fc10d71a0ee1c7a471c9d5f89316ac4ecdb17bc53a6eb77b2a7b34b90658bddc06b0e06637f44227295bf5e7bbe90e31b8dcc440f3fabeadc6539331fe438a8fd98ec3aee6e6fd4a25ecc7e25694e2f7c9d5d81b334268e92a9082a145a579a4e9d1bc297a16b861bfdc84bf80661bc77d506eb4293cd53e8f5e6f2df006ccbc491eacc6a414d1741e465a70f7050f172282c7fe3996b1126d079c69c83c9b798f330b9d8780569a333559163e353cf0aa5452f8bc4c47e9c8cc84984661da0e8296711c2a58e60470cc954b3180abd0811c0be74b165869d46f37537828439dfaf38cd7b3d92f7f54484aa67a2fb1ad3fd4f75e275a1b8f10d7761a92c4577314f7f0dc41164d88501cc31572260dcc298447693011d6734f5562da72ad55eeda452be47b2bdb8f038223e6e96348be2b1a68a6cac113ee038a2fc12297de3ae90b17e4e51c2163c4dfd18a59c87a07f689efe939b51d15ad9d06575cfc49f3c1706aa4b0fe94dbd064d9ff9c1cb5efa1d1002a5d9f9e4ac48a8ea33a2f0a7023feea51683799d914e9f6903f7e954d0611b599a0331736c4d8d5b826aa887a5db73cf21dbc46a391d0ef1e3c963967088b3b44555046770894b914e35c6f124469786e5e50633837c883be6183b6858c48f8e2d739903cb0ba177b7e32314ccddb63d99cb5c1c966a100daac360ba6250d3be24e1f6a66ed0256c4f6d5213107cbd451b2ee631e577c41e0086c46cf6f60763a80c271245333843489293804f3f4710fe43712d0749cae8feecedab6e2f1ecdf57f06f79ee37d66b62d726d49e865561a8959de5ef6f9683f2bc5e0194d37ac47840fbc655750f0cb0237ddc54c532207590f182035bb7d70cbd901eab9d574ff024741640d136d5d70525d86a50ede904c84dc48742be396764eb4dd6f1e78c6d09465e9ef3c486a31e08008d4872b48697bcfa8dd74591cdd3abeaace15042e42b49b063ebe1c18337872e2f483e10159f5415e71981efd3c056ea1c040a74189584a40190b87c8254067f1ddc8e626456a15f64125c4814ec70fc76118f01a1f63d452c04040cf72f5588b62ccc45d99ddd1cf22ea768faeefc98b2eb3fdbfced6f560d854253ed31afe603ccda82fb9970e8b4a23b5cb2725508819dc047a4afde51014d3bcec82d07c6e2bb1657d0f42d02576d4b9f1586505a7dc13cee536f67b0a1297965e289af59589ee250e86d5c9cf0d41de1918e74bce98f321ce51dd2dc493ef9ba3c2539e8b7a44a111e6ca477229f82a1cbec6cbf9a4a67cdf1bdcf20bd87933f5e72066e5c03f7d7c21e4ae5a00f68625babd74bf83728354010816fc233da262854b78b2f22c3cb6ce9292bf34ab09bf272b9fff04d446eae9fc1d454e82272b9f59708ce4c6dbf1f5bfee9a4f36fa7c87ec37fe3135022ce413dcf835f5cb28f0e7b1ec16808c0b0fce7428964aacbc69f673888060ee91eea5809c7d16eff103e2c2805602c3c96adb4c1abfd1281c3c01abbdcccfd711e7a2c489a73dffe4110c02a4e3c1e962a8b15c6297c8cdf7fd5d3c988a27183e511a7730e878ff5c313ba8d8dffb270beb6a127bc005085a8430db57c539eddd240ae8058015ba3e4dd07dbb046415debce535b5017125c8d4d203146ea012687ff7c5af5e3de0cc3c493330a30d0cc448b09fe6df69ac10531fc3b260d81e13092325d40ea4fe7b8fff8c748f5087ed2fe6b99dbdd7609bd7c5968b80fdf7af6cf45bdd25171fcc66f5829d9fda98988af565e0bf4ab3159bd7a209e8c9bf7ecf8e6cbb25543b8bbb1acb0979f311cbacdd57adc1da714b6246568406a4f6d6781fa180455b30889119b8825f9d9fec4c5e9ac8c9ad1dce88e175e28e6d69eb811de15cd371f283b3325cff1f187e3e8437570d72bd367678babf132fd501d8ac14313e09638fee6124fe7c53c94ff777fefe555cb1ec8c50fe9d08066ecee3c6e232e1d71e458206d7ef9700cad3a862a76b955ffd69e33028bc85127dfb7972729ee43ecc1b1517f4066f20ff2213871f626f5960e7f15e0e30f9a6e89553227c682eabc833d4267b587ed03efc900f04f2cf0be3cf2bc0553f3ab11e3a5f3ca6be04130ad4783eb21901d48d0bde2df4a4c7f54c747ffa44cf327600b3d4e79738a067226d8023dcc5e71036cbb9a12765b05222c254d6d96c5da9dc37ff40499fd5cbda413ed242f039094ec9383b01ddd95a4c95a094e1cf8074e00fd8811fa01c5047d67d1e44b22af13d33183143b3acdf8778eadbe6167481a659b0918ede8773332fa55360a3b3f6e1db1cad5aac7d50e624a1873aeda601cf40ec7dacd7b271eb1d32fe6c3854b38295c51429f989366d4397461b14a0195b97496c35d5ad2420c39642bd4119c0eb2406d064400b5416036fd1a83bf430b310b7fbdd123ff0a5780b262145cead3e8692bd784284466280dc940039f6c6deaa411713b675311024439e252543cb687403f7bb41a24226d8f2e427bac76de14f8ddfd9bd3b47efe98ed49c0c0a3dc08f42fef829da8192b44d36dcc5b7c34cfc988776c554a065cbb1f7e29f06b0f07599b8c99a18b43c9e64a103841766b3839cab0f17657218e2927719730322d06eedcd85792f4909e969354f6c5c8320f2587da00b2b45e23711379d1de3c65ffe0286d91f69624a7ad5ab49620278b3a543e32ae6a7e9f6f16e281d44d1836e5731b3a94af982b729ae1fdc8aaf69d52add6939193a237f0d4dd1849879e533c4c183be3fe2360ba2af6314a92922d073162bce8a7d297ab552ae37855cd60cd2f4b7e088005e9ab7a3c30a0a3a68e369d2702a0f14034aa235fa35f4b73311fa881724d2b213b819dfed916248e8fa1bced1c74768ba9c26109e5f6c3a9198558197b227deb329d411bfbcae1ef799aa23ee62c9990eb74b50319eee0a42a241f22e18fd14c3563780e74f2ed50a63689b36aed88aa611af6618517d605a51e71763d24d9852e16841b9b4c1e6007a462cdaaaa9480c0a5c6d07aa1d26c1444a3eed325105c50735937ea094f9d3360154547481276e8a36516ab5482c94cf0c9115ce58b4e6d83e6a71fb2eaa028f22c6909dbdafc455a102f683c07b91a4491eb2f8b78f6b6c9876ddce23507a03b016cb5a98b4ff085f0ed3118c6c1f9311c86aefc34f4045171d951e2b0bc18b8fef634e1686098aab57b49f00971e84ea4437bb9ef2e6819f665dd8c0058780dcd806903380701ee0320f1d7dad440b119bc03caac06ef4c8baa26997811b4c1870a4978f706051bdd19717b5c53959b8c00188a89017d7d3b110f7fae58aef02172812260914722c60eee50a9e81430f42c4145cd2bac0cd3e4e9e88b080918051fcf0b9526ee1852517f2146c70f6d844ab3540bc7205c0e1f0241e31dab3a1e2fd3a043fc2d5527a4acb4c20c02c2ec1b67b06678da9c96ae591382c8f16a3d1d30a7d4cc4c303b44db5a86cfef2842e09b7316219c809c8dcdc3c06274542756a1e73ee62b5c5eaf38642c0db3f38cabd51427478340d3da10800c062ef02d53ea0e1f5e6781b81dab3bebc59ee2e6dd7cc74afb22a9efe298c9fb8852849c0f2ab20e178e94b1bb841ebffb825c6b2227b8989403d08b4d073f64149ff9edd73975cdfb10df6259af6f4b69bd7f31ae9a30c5b5fe68fd884d013dcf21e87577f330865874c629f6812c6602a7bf4920108693a6562354b61f5abc36cfc517484993745b4fc78ce4740296bfe29b3515c5bf698574d5874655f5b8bc84920ba94c3a989a01cbd8048833f1a626777eae5ee2f84748827818153dc20df453dd1c3258baef02266b7aacd9a51ca26cc3476205d7ccad8db626245df650c3dab4cec670aebdd8d79a777af6639c8b0281e4907e31e7197d8dc1737e7b0ee04370af7bda3f0f2897de9a7e3b02db48b4120d23ee0f8f904b01ed38625e20e2e0394cc12e7954aee1e07dec14e36f18bf01ce90adb48331bb7b1bfc13ddcf48cdf4475c920f607c6243f51bf4a0edd21a83f7bef030db572bee84c1168a6bef3ef5e4b9d034ac05923de59655813fdb4a10d56a248d34a79b4f72fcc171d7ac99e71ed5b175e8c060a75c7840773135dcb134288dd163807247e3f63444860d2b71e1c8a07795ebb1dec162080b35a684af592a7ca4026d97e6c8b948323d507c709de20096073821601d6a58660154cd6ee284362715e7df470e349c11155d45d13abaecca7ecebfd529a6c8564ab3830e4f689a85e27024bf0ad4f67b1efee7343bf2795f8c5d7701c7d1c03a6bc3fd78585419fb40769dbddadb1f0d7724779348c14f02315616619bb335dd90283c1761961ab0b5f5b894e78d58171302732af3f5180c69408ee4803c461138ec600c91e3ee592ea0d17f2c66ee2226ac664e98ec85bccb7b9067adbea997295d9287084b56132c7ca1ccaa92cb04e80501ea48ef49baeaf769e1b09a6c7d3cd87b727d18bf95bf35263d9613e7049ba8df43c558630e85d4abfbed48a95b65b92f1d8215b0481d01accde67d70fd95e3493a13344ce6452f2835ec6206794bde1e1b28c8172b5d3d0014d61164a4b48339e2a6ca35cf727f733c1d4d664b18a4e9ad8d758ddbd286c44cb6a85afb24315e26e8d9d9417294a90fcd88b5c71d65d542d3d7c0446d1652f4578599be692a9d6b96680328863290d9a19431047d259e90f359b335d825209da045943a151921a22de935bf822744ef191423d9585f8f534487757e1801ec71fb5d937fb64e657b1d1abf9c985a239f827f97bf247edb477f8b5449ba51b38c941def2acbafcd6a41056f8da246f9b12ec080d8101a90b8c23b4f0ed0466296a55be5b2099bf38d85df8ba09fc97e3a9470fb520a53c7a1aa4040e4a839420cfd32025b82a0d529a5c4f83946207f0be219a687ff5ee49fb199ab442d323565685ca4b850a68ad5675d3591de84389e67e77419b483527edbbec825d55c57bc6e37255c78a30b41ea40cb4a0512a963e088886eb92b4378617c8aa5ea9ff8d805f6be80c5e1025a927fbdf707dea81c24ba753bdc33fbfd568d94f7cc7d3e2014f666b495c539a8933fecac778b70695213ecaf8a33bcf99ae092c95a2a8919f5902aa4d8a53c14c308ebf11de12b38d3fb8343cfd4fc0a9c30d6679695d63ed45037d8ab0155fa16155c25a0e7f655f510130d6c9be5af70f16f06c0ddd0160d5d76ddc427ee88d1d69bb5bc1c7afe423ee34e7b6897add772a41994757282887e466990fe6c42bf12be16fecab7481e858edf275cbe5e81261e76896dc8940c88e406caecc91b96a1e7d59b22562488a8b54029f791dde0c38ee8989a29c42ec665783576dad0c66148482bd5983a130757f8b897d4919ac778a90b895dfc1e7e58866768881425046042cdd32e94d78c98d2f2a5325bc99f0c9ab971af6d1f7ca4344ece823e753e97ff311712868b579abc848af818a962244d6bb945625c8ea0eba29faa9b1ee73878d1814958fde7c404fecab64c62a362dd173883027a2315d58b454ce083a0a4d864b98310cd411ff088b59ef4c8c57812f8ecc9e9c08a655213d99c9aa1d3a4b1ef217ab588b13713c71c6b686109080c48cff50efc99ca0713e1a57a268271f0b90223e0d10b28f9df6aa495243029c46782fef1236180ef33581cb62a624a9a90eebf89e88168d3af7eda441f9ba459201c3f6006c6ac7497f4aa63dafd17c7a379f6a0c1c8e41745698a4a1cca4af40596050628e07cae5498cf73eabac0bfd3fef43768a38c0d5f287c5e655c33d0f892b0dbd094b832c94fc00092a5050c44b09bfb2932911ddfd1e4025e924b837d09af61d0a538d74c2f285cd7429f16ccc9648340ca340c99503c042d63950595e8b70281badcf7e7f2e8bfe447d0073e6038e3a19e411b90ba01b1c31409bdc12087c681271f98aeb64a050280947db45b1a67b766c144efbb1ba69344e30071e77e5b34c0a9f87ccee6d1b928af47444db1c3f33c97b2de91eafc1110c9bb88c3284d90d93675f0e57d9d14b6b4d726743be344de2be51b86c37d8eb2010c939dc26b9cef51c35e9f7600f6cc081a811a76f6d6d0ddf063540dd09576f93b04debd39b387a672cdd54ac93eaedacca67512af42c15fde466ff734e573b104f8f5f69b19646e2f12bb12781c3594069ae9b78ef51cf743cc82789b3a3517dc8fd0baf5129661b95347633b03c1a08a87640c6000a205139e246f603382f555438fc8e620150ad4d2e0bb0018520178f034639259085c37094c132304a05527b11744aa792c89882ab27829ffc410950e4fc617ebf48c912616e1e416aa1104fa64ab704144c2da89118f4085246b5684664704d37b646d237d061955de83c41953e39572660a4ff8c91a2df27ac45e612914a70dca3e0f2808441dde1dc184511121aa0b1e61bcfcbc17354b3474314863c839ca69e3277d9b77eeaa3d94cb02ce2b249cf6324cb6f0cb151e42bb2e15cdb95514aaf39576e97b43eb07f3d6356c04ed2497cdd89a66448648ddd0f9836401f5a6aa04302e977bd4e0b3990c292461cef08dcb54a9d58c365a0304cea3c6346d147eeb07c597217e4a0a149f284b3525dc92d3f00292eab7a9a69d8ca9e5eab139d19d9b2aec28ef0667508bb103c25e9df8d1c03a41f0824190bfd954be7b724893460a70bb324ce591cf5b1a5a455d825fc48a75e8456a217b491a241e67cd31c8238a1312f9029a9ea90b1a0346d0c002d40a5625f2cc5317685484092fbaaa5fb63c48c9c8bdf0a040e2ac545e50eda5270c41cd3294f98362d924175c6036552d524d0ef868b46f2b6603fb73f54966e2eaf12f385975da30d88b64f3bda1e60375e990b15ef14d6c9f077d365ceff1876d87c2a374832dddc17d4849eca4c76621bdf733f62bb14663a4fadf7f5bdd41eb6741ac9b4e013ad141df52f8eae27f5654d5896105d5dfd8e8649a5bf60dd7a8f3360630abd77fd1ec74ea36c92604b92de5c5e2afadc7137eaa9b5033b9fdbe295469ab6287cd73bfe197b9c076d5827896b05c900e37f3b326a676712c0ebbcfac011819bf48ad0a0c7a66c1f0dc7eb5c3f40c63428a26d87a2beba1b5845cc1074af76a7b495a2c23fa700eb4fe1d51484d2c6fd8d80ee788782e808b837e906853c655e6c9323a529122099c6d4cf65ee506928417352f9bb342c12342241984a66a61f27a104b368b28054c698054537600ca62e81b3e5564d2dc92d9f18fe7a4ee45331afb9b450ea8e84287f622ac3f7e13aee38385c812501259094c2932323b44d60ec2c856f5e4df5902bbdf22245788ae9b3d80f00602e160419343b0dca799b827be3640ae1f59b73b72123b498160e280ece6e23d0be831adc952fff14940cd604d5bb244195d923a861571445c586ebf1e281a139909721226f31e5b59604718de700b7fe81b4da4d60ade4a00a9609f673083bdb9d7a22f105b4da6d1d9a82c463d86fade641fb85930ac39eaa5699f4a984fb4d186b9c4a085091e3acb1d0556a527e8885971725940fe03a5858400ba49ca54376b641b1c4909df93c2276ccc97d468e0e1bd1f85e50a3f73b2347279fcc511a20142c31b832f5c336de22b2aa18b98667a950543ddf1ef6c84824f48ca71df44b31c4d7a2ab0bb9818d3a54c2d02f8c73f02b4024828abd7732356fdefeeb4325348051d6a426f9915e02ab762367a46745034f21b69df61784deb31a332922d12d69352a217ba4e52365e506d6a65f855c6258cfe3c0e992b297e33770b939c23bd7a1520c39ee36d6825634691a26b28aebb99f053ec967e5ae238b37ef6520ff84f1f2de07150527c23a23813e5dbbc1925cb0758418c8c9a771de95c248887645afad21f2f9244528000f3be3f6ef0483420c73ea2720f0fd3db563aa434d85ba602aee43214aea39d961bcb6f8bcdbc29320c866778fde045111b144221a4479762de4d389934924525d80a38b5f68d43a9fa98d36962a667b64a5a0a7d1ecad08d3510435cd6746e66022cd290ec295086b6e3d2f5ed571afce8f59581e79bc9e6fe51bcf1178cdf3c2b2b9eeaa53ff466e8e6b7f15ae0e8adaf0365a93a3b3fed6e9b2c3395494aaab45b544445c08c30b6c197852813654e6b451a8280f1c390796b9915e27af891b0fa89314eb8fbbd97ba20d0b07dcfecc259a7eba1a31eab395b053cfb705ffa0415785340b5acf7abfca780fa131bcf78bce8a34833e547646a2391ecaa7b94e7425978b9e4f80764eb321da6f54e492c2259ebe28109fbff5a842b219c62848f78d1d48ee61f8cb8d13ec87018598a78af58f90a7ed15a96bad78ab9c16c56b4ef985f72f65c4ceacc01864979f23e2037eafe2c68c0ce86d8064ada9b821a95fc006d6190bf093f7ae4ab6f2e0171437a80d80e647ee7481bc486817427cf6063616756121183482102b226e54d7dcb13f39918f413fb24700376beba2a02f80b9116bdbae3ec76d326928dd7096d23d596bbf711cb6225f55a2288cb861f352cbfd50d10a89c537f9b7c8c2005fe2881e12c40d22977293221a167ca85084b8213cc0d1414714cf1626a1b6f5eb68e5e0ee0d4c3c2736b8ec941fa7f76947dca8b14e8c24d7f5a1e8c2b025204913ca47b5048120b098713e2945b8e579f9fa92a159914024f764249165dbeb470ccb8e1a4232b4eccb30c24832ff88398d7a06678271206e8c73264c7d1037e00a095991b102d5aaadc8ba9d094213f75cbf1e77b26b3216f193a5ff1469e9560ccf00c909f68d0294d001f5860691421fdac689781680215d8cbbba8fbf2845dd4ca1689938af1b48267f208f757fc7f8755ed27466c7d40a61d7d3fccef639f617562c65bb91bba2312fff07c508366ecd0d6c925eb8378694815291562c86086ca0193ea5e5d5c4909151de980c629f59350ee8a182f6372a0d1890b7e4e2daddcd58cb058af8657162888d5359b45d87abd9b1043613ae4b9ead31b5b052906a6ee4a04d83994f4401f9ed4ba7bfafbfc3f9ab9357dfeb44bb16e2ee63f92b5191cbee5868f1cd471016c5896a0dbd5d700e801ff6a1c170e42feb15157c0461a4b768de28f1303b2fc42c9fae6a02c24907b63046400cf89f982b8e45df053a698a51fc8e7a01cd69b38c0812cef03414f3054c9626731bed1c4e4e60526ec864586ca9e4e01159ba97f1589bed1c3ca9f84791a020087fc4d377199ef25b04bf659a82331fe1faeedecef34f5348a0208f2678c409a42fd0742f533c30b308da218adeff360beda287346b832d6781008eef1d296d2ef1107f35671407211810044fe840e661c455293174a6c85558ed41428a2d1205e0b160df84ca260855acab7175121e367d79ecccf45d20b86a4a0ba5fc8ffd96fc1f947cd1a851ccad4cce87be612d093a2bec58b38f355f7381ffc4f2f10fac85f6af3665f5813185f8f4b52f8ec4cfac63b6fad1991efab4367bd703ebde08e207d8d9a1fcf5786b7e0278cbba368ecda46d3efa17c59ad563959f013d378119772d1358f79d75603ca334874354c4531258e72bd083aa477aaa2ce7d3373c8ecb266187f2b207892f849615c3d69919dcfbdc7774f1aea14c577f12ea62e34b10986c4ac7bad216674163923ad37016301ae34abb3ed0dc4d989158c113cd8eec709d19a239a4e0571bc45a905005958179711db7eb1dda8fd3cbc39d8fe02511d5e5369122079f6496cb1b49ce699acabb169cbb2b54d9bde528ece61fd84808bbeae75697e0bc607d565c22a81c8f572a6839c6c88b4bba558a7021cbee1b1143628096b5f1c7905ea73f98fd7dad54be6ff168bdfae65743001f724b25285f6ed59fd7565ad5196b9ee216e22d836be790228d34a42d5b58b134cea4914037e01f8476f559020dbb9d23c77fe175b70b1cee32edae574963752b6a813627a0ad74ed71067cec89fd6d0ab4c88ce7da501627426f4ad33447efc427d00bb9374c826202420e2056fd40ef8be65fc09c9829ee5c12bbcca8d065616293735ff8e596b86186f8a60daf77d82dbb608cf3eff0c1f6092bf7799aa8f2b33017373cfa47e111aee21a74c1f0ba1466233fffb18c2d0264356f88734663f8de233366402e30f50324120718941b010659d8bfaebd88d90467f7e811e6bbea564a71655e766b7095dea3fa1bdf326f9309521eba19b09cbcd95b84d82c05588257d23b2d375e70441929059958e6add109b8cc27e475e00041dae09e7b014173d11878d30a33b3058c6608a9f158ec1d797b9d3449579c20ed60ba7af10cb901c2ea602a984d3f817d9512d23477f97fefd099987ade931ed134896758221842045b52c3347bbd42b5be96018216a6ffbe9572d82c6fb22e8956328aa6cb730c74c40213503a08e51a253818a0c831090aa10eb01de0c40b3e1518fd4e60f36273515013e8e4422171426516c669bba4d214b49ab4c23a5903d08f7f02ab9ae07d78579a97212a45239c497d1416e907573505917586455d26812b91e09b38cdd609e11906f32086c3e3e94a510fddf2999a7277135e2d7d834b948bc545cde1d1f64beaf48c0c705f9fb53264223841603be44a8d2d9d8116706032b074cc0b363bf3c5d0e58165ca985d30a13858fa39b504817e9a446192ae645bd5756fdd6ef7b4124a704178d5f5adeb0a74eb70a581250dd89e924a3d29702c5b7e0614cebc585edb34969bd13c1c7af1d3ebf4d7cb7e056f2aa69556c45b98adf0dfb8e526ffe0992705d18aa875a528aebfd703ec9e2463c2f481af34eaa337a4ef2e66489280ace20497e20d4fbcca01914724a9d807bce0069e8cb91a6db497e16635c100f77e9da64cca404f09479b974ee305bdde247c0c16a4e9b7b5e79beff9cbdb96ed3cb08a4310fe5124ea5f5fd3535871582e0447ab3d0563fbe504303317f3f64bda3aa5a9f8c0b68580b181c6ca0c020c7e0a0dd766f19ac58fd1be79d67e69bc458941aef3be06a02e0ae8098e96cc345f1288b636d21e9127a15efe6cbe157d7d1b161f8074d1e68c05146b0c5983cb01605a3d66c13b861935e8d1ec2edbe619eb41274b594adbf305d41adb07f5f21d4beb4553bedc050c6722921be406f4e81602c8e84c7c2ad230f78bc4907aeb0636689cb7f7caf3b402b57f7113dc4a3b934c31e7552afe90d03aae19c21e5c0e791c64c7e96b808b13710e3265a1d1519b128f4af8d88256b6465cce5774fb0ae3029508c275a0e73b9ef121eddfc4f1c7de930681062f23f656502a23698784abc92327dd13a4f21228cf0c02234804b5b57f82ba1ceb6ce27ac42bd1971ec18d3ca7334b3c299a7c00384ddf3875fe1fd0acc5aed2c3f9bfffc5adcd71fc4c986a6ff8e005a0b9f88280cd92e0f995a068e5c1e8c8737d7033a59c57460d1bb78b4c909b66cee09bd22c760fb4ec715d2f83e53ce0d26e4894447f80e5f57f4ab7e88adb7fd34ebfc085df18c438accbced3ddc12281200cff2e09f14957570dafd883fbcfb55a880e99a319568f04650ec8bf7905b12f514c599d5762eac8b1e2b8db74d4147b9e8e04b9c8c8b2e2d43d1ee0608b00061426d5f08ef8bbf9bc3481dbaba4a6f4ba2ab6d6da4da50ddb87ee810a62e082d682425e2b700004a31ffb5af5875eb1a57bc08939e41b09de43b873bc0e0d5446c5eb94ff76f0ba69247178edc5f4d7038423a8a609edb99da8fc17f88d17dac7475872709cbb4eae320872c28ea3e2ebdab54ec41bce069874997b8d7d771d2cb4ef70696eed60623b19b72a8900dc2c0f5c8b20d0b17cd78affda7bc4d6bb16e78b87e643c6e382a3bd398f2fdc3dbf5b90fd7533e30bd4aa5a02532d27ae217976f005147e09b6b1eebac260e84f82aed26524327c6c617dc76951c5660c33d175bc11537d877893aa5506d9aa1b636f23642b1c5666dadd9f0bd9d97d04ec3764d75502b8bba240866614b32aefe399ca15643636c6c855013658981443ce12257dc8dc7d9eb7e6b8f59760c98e767361ae5f9a30b7a0e766338d02f6fc79aedd2ca701d7587f46b64ba36e4dee5b18cd050acc098f4b8f2817ab8e338ed2432fda9e8e19456664bc8d5bfe4c007ac00af784913923cd9f91b720de7c46e6fd9d2b5549c5ad08b9432930e8ec9d7bb228d45e7347af68f071ec7e4f673a2c53d6fb06a94eb6da71167dd6344d505f8791138fb3d01ded94c5a01da89d25c54687e485991a3ab3fd227dc709777b2e4431d88e8c02e7d67f4a679fb125a5520ec4abeae7603d370c4ab7777cb33221be62d876476651aaae35aa1c39526e5f279790abe152f854192104ef224af878547706e0361650c99923f75c9a6c1264022399220ca451367b8dfa26f4d3deb70de5f893959982aed99472f9c46009ca57f1d2349d5fb2a9e8069f887a7c266795280b9ad5c6e2302670c883954330f0f118b2c3e1c44b3de871e6a57ca23816551ee31b15f27a496e061f9603b102b173f25026c023241fd271ea24f5eaa0561ed2ec1f09ee42f67f4a7f4db12062e7afa2e7e10f37ad5a51340f0d89fe0e1ea50ea028806105688db4006a4ce3daa61b8a308f64f89986a3bf6d77a9aedfc34da200fc544c3caa23fadb8f9bc88aa9aac649ada78cf970bd8149d255c33011b9243670900b62cc36760f7370f91e702a1ce4515638ce24f9817e95e16ee5dcac43112120b7d00a13ac04b726f948211e281b9af0b440613210f0650d03f1d78e4de053b9800ed541d3658bc1b78609b78790196e19c5c6b5b5dc9c63a1801b92c439f2d3c36fdedb82986e74e9bec9531c3992a3203b38e8cfa356133c65948bd82cc6a3184838cc83833106aa7cf17e04a8d522fbe0642000d93b6a24f5555de3bef8cc162bf848a75d5df33108aa46609a3b691ed4bfce93020ee5bbc6600878122c3236871020dedb46b04f0b51754d39d89da74858fd0e741787072515a5cdd841d67fa6f33dfaf101868674b4efb40ab740a8480228406632cc70560aa9002aef479c3a2be9cbdd66c96b7569ac8a8d0268e62715c118bafbac4aa5ced5119000af698640cb0965966ee3c304a89b24d8fb9f902bf51ddad208ea140ee0a6d7657446c84884738858610d3230adbb4ebb97093b95fc61b32a4aeaf487f8a1017c1762f36962823f19d6a0011ae2f9fc8673a613982fb30a2d6cdf98fb85b3b6f30ecf15e085144e9b8e33dcf8fef93b100d1862ad6d6c6e8d634aea9b0ae97084bab5c7ead500db3d4462625449d079ad1f14594163c2804a6a4f66383c40e84c82371a3582d0ac0bce896ca7d43b457d1f3fc0a73e321aea5a10d935ae8443ac541f73c24a03d52b7024c04008a5c271fbc2a083d5f9fc8291749ab16f508a3f9c62f5eb23b35673b1b251c2a482673d6c958a05fdf093c43a931045559fc18905f5dbe7d84af378838133ec54c208f3c398b6e02288e64a5d0ff8df9b1e0c302bb6ea024cfa827f2f516508a9ff8b1529a8d21bd9a6bcea73cff14419065d8f04f97c0577d9ab5e7baeeb6e42890a4a213fd25ccca5eefb03b9c2e2ff82586b00815ff3eaa871c0f47564ac9ff11f8666400c4e4a43b188bf0b4d88dc26b3f3924cd983a5dfb2b26132e4cf552ac2414c4a7e910875f805b03d5d50076c5eeb1d607e2ed14b94f47ddb82410d08999264ceb8954183cfa34d68a8d78869fade22ddc8a2396a63851e141271bd11498c5200831d8a7eb314a300b89797ba2c366ddf344c99c41f8660313668f0f9e1f427b0137eb6e3268623c7155057d89c7a7512d900c6e4ab862533398166bec47a5853efe36151710290a24061a33c872829641a94ab900a41fd194b793a6b072a0fcf2f5418eba789cf28a9e4064254f6cfd0294ffc434550ae761de4cebca9f7df40d26596faa49e316575f0af20c6e32553b81accf68b036454a6653f010bf673a1e47531b8f360bc0f2f218033988e4441f3ab2facdff11711ba95d917085d8e3bd7d4ae0de37b0ac53a13313d17db7db3daf906dab40325a18cbf6d7bf2dd9aca2efcb7d11d24ec4c561fe30bb46701cc440071c0ef2f038b37229183da5a7ee8c84d2a0c8bc02da69d75423b9a238b8176a0b7cb923442d054abfbee6393c33308348178ab4678c524e76377391d0c4a83f39e14befb4d64374c01ab0011ff2da3ab171ecb6c0f2b8fdd14984171ffb92cc65751fa6058959a114bbcfb66a21e91201158a9197bba4dc36b3d83315cb29f49d123081fbee01327050b6c0f9a99d48c0b993103fdc5ff7cd00baa740bcc9cd795d40c8dac6b0b55b3ba7b2b49cd389f5ccaa6da0a6d6d9932d8d46ff89a96a0c102efe04af53b587bf489b4f48dd81de269d6a9caac1eb029631dee8e43839a241dadfe6871f26e446152498b28340850c674abe4a1aa5fa51b696db6e8cd8a1723fb0b6db9e3c212048a7356f512c5a48aa265d06b4937d16b94418f656eae20335e63b1b87535588d16de6ab8928c7b2c1dfba1f2766d6d03e15a96c12b094b8365dba322816032edd92018f8cf26f64c77bd87bd922bd2232ddb3fc68686cc63656ed476b3c251b1788f81ccf17f91806f3b206edb67d55add4513d41c4e1348c4208e39752dd8e6cb12c24bbd8843aa9366a6db39d97ac365eead15c9a1fb61dbfa6807525845ec4daf091c50b03374ccfd841f663f6e282f8b46114ca5e7689a441ace9c94937dad9f98a8678a0ad1617ab27755ae6cec6865c0d48235b9e235084b2c9c43c0fbe39a30c7b78e2bbd14389e91b6875f43ca189e43195a031dfd135046ec39564394383c9b700f0784713f85e1d7470de42644b251a4198d915ba8e37a5ef888f402742eb302be60309d8c2b99755a74cdb97f5fba021436caf39c08b1f846c50e41740ed6593118edc198bcaed263c2125d5e307203c93c9a32018422d86da5e4220b6da976e2aaa3a71b79146f6a2eacc0848c958b9a52e3d3fffc3dcfc0f9f3e64eb60292ee2ccb779049e50857c4a18c9f3b2cd46cfc9b77660f7069c5da1cd736e739aefdbe61c0e4a917065610c6e195e6c8aa9aa100b21497d4e2190bb48fa42246186c1bfadacf1d0173acf1fe034959daadc825d7e728202b0e923f7d2f9f0b8b9a95b98bf8eb4228fcd65e336447788a8e425f50db7dc0346103db520810f4cdbeba5de9e2c491f6e9482b5dd3e430784a3836d370ab48f364494bdcce920117cd87edfe798528b2bff2b114ce535008e23bf40dc10f8ebb34c6cb75106f0936e8359122248f0647e3826d55bae692cbf99ec0f0c8b6b86201444fa427ea6075c0b5615b6943dbb8cb46ea05840654d70143839d9f295d1e6b7538c706d4164c0d0c8c0d91386a7330c8416d4d4d0f440993fff016ab7847c74068582014dab4183e3b6521b2af531e7da9f60ac1bb529a8ada1c179981a61033476f03dd109375fc6e782824ffca44bfd246ac545b2d34c6cd36db0d84e4755a07c3d2639b76097ef52fbc82c09aaa6c03f8aa9b08c78d535bc0a92039d6cd1adea03a1cefc16cabb6307a96e7a76e8c8c233469c560fc4d340fd219e831b62091115367414d38adfc07c2e5d05c9a84319efea3814e8f9ddfc810986638dd1bba10739e002ee76a776522569d627a3eaf4d8f2d575b00428864bebc256663879ba56e7dcc6af054a2e71512053db97350087bac5322548421c143467500622301cd8a639eac48511d636ab65c6fe24aaf5255a9567296e1266b1873a39049063d96b861c4c2d19ae7cf147a575972a749929812d0b02a88cdee29c6d66555126d0ebedc6375b0b72d34c408bc587f36d254e1c754111d6a0b73da480c435356507482b76a15d45c9c3f2369aa32f76d53ed34c6499e3c4d23ca6c204a935dc73ec72fa50867a825bdccdf4673bf1a6ec351759fd2c356c2b8314c7ca849968f2c86d0c063a5c47fcc0f244db7a12ac93f3312bf01cf8f65dfef8b556494708714eed336a02b7de34773f2776cd20eda8a6de1c3356946a84280e0e3cda3e60d3714fc3cc06e8fd1f1bf522817041d4ee84c66a22bbad8b59c1a9fe08bd09d942fe76f6e294ad83d1009f51beae359bd7706a4c8f48308a0e07977dc6551d2788885a5fef8a1cb997ecff3498f2895773b5402bd3abec7e59cbc5ba6dfdb90510aa5b62800de3474733cbc155efdc6e7fe96c9bd6142c4a56c6bcd6c574a24a466d94bbbc434084c79a3fe092e879e4028f9efd1dc58b4df502c6daed5bb62b56d7792f054138fe784ce10966355a0d2771459fd1a18d84cb5ab1f196df173f7428a23eaa9c6be9e3845282724d5591ab0952356caba839cb71c634469969df50b8f8081c8e804767126fa954efd59f028eda67675ed6f9e6111e05676fac41765db4c76ad52b5b97c9531ecf16f5087d2540d9009445d2271d6a5ff5c3fc97293e05c9c848e24275c3fea1ee3cbeb0899cb040874ca2c94916f62a0ee76ca5c3fd19e610d9640ef5e679692d8d8e708a8ee605e71f2a214f1a16f3461ff49dba31bc911255ae903e49f7b9a63554dbb2ee9a73d32c1f724f44f01c9ec9bc5b11f44730c65d12ac22190c54a42e0f396889206b36585739c9b0b33344f824f2bece4425ad5f930eab12237084f1305118019ffa52e9221ce9570b91f12a56be2f03914ee4349914bb669a5b8dcc8a64d2dd9bce7a6dec926fca8ab482a8cbeb1bdf868e4a903247d5f3c0c88572e855f5b508fab3d8686c318aa8705781c262995b0404cd7a8e5b3707db6f98075bb36d0bb3f11c889be68deaee379e8c9688582a58116944751bd22a710690807dff7949680dddb6101bed5543a8899250c783f828d46bb182b8b42643e74bc1580a50cd70806cb52d6a6c2947149fd8021a65e3c5491778401185acc1d5ffc3e8268104ca421f3250a76cbfdbc1d73c74fc9d4a197d7c47a081cb7d14cbc8513728c3098526baf662c7d913665bf4074fb884b36e169695790196ca4fdc7d0a9dd42efb4a2a4c6363e605b888395e7f06dd107c66792d6971fae07defce8f7fee1deeb6a79f3936ca6c7d4452fbe98c9d38842bfac78e9c0c805fb20ab5b270801daf00dfc915841b08bf7c30cd1ed9376acaeeb5ce48c4d8d39828c85f68494ff183acbfd3a3b50297936adb562126a673b509ae37f4829cbb72ae1d089d18201548fc80ab486db49028cea709dd03e4b832abe29bc428d87d242897c23903ab8cd4f40829d518f172f6e29dc3cd7495bb2893e58b3121a18a1129ff601d778471c86cbf4471a09271e00b63611d6cacdb4c8a99b2f489eef5f6e13788d73c839ef66643f018735a7054e0421f4479dfa57246939582950dcec1c82428aaf0fb15efae6e52c274b217810cbc8ddee561917ec79af92e70e20cb7c23c361c5bfb33370ea179a70f0a1091b5a1a171ec63d4bb85d9cbbde6b775a3f4a1056ebc5881fc7ec58f68533359dd79600233fd1d006c36dd9abbedee48d3f418e109bd6526aa31e828318498fd039fe1389ab0b838c544c685f5095b729831284b57d6a022077673ee57ceb82d6c0d059b46807184844bdb9e2f335d5aee7164183bad5b3cc95b2020a749c195defa3620c4a963e038ffd0fa2f9c50759aa549d762c79540636d022638d984205ae3b54acc703650a72d8d5de806f6c43e370c5b73b01d2789d21909c0f958e88d637e2ce45014d9052d63eee1662e895a11aeef389d7a9a46437334501a15c34b0c9e0786ca9d7a76e8d17f671696eb53ec0c595d266fea97f93dbf8233be57ed9cfc667b9f95bb1a6065086f354cc583cd69a9e74101cf0065a9f3ca65085e958a1614e473e763dfb6c18419ae52f086262d0e3f9e725334e9df4ae1512b703dd0f33674f159b05ac7bc075f433fa6118e8138e992359fa548f20413fc6589c03cb3a192e2deb72ccd15139d3cfa00d45ba90af0a5e50dd320537dacf833a7c06d41e2427c4ef38f1cbabe01b42373054c7402ec92fb7d8b73cada33b3bb557bdc6f581ba0e7576f989a862601d023dfe91f48a07a63b8837c888366a8b9bd44f4b85f9bf87415e079bec57dc2dd4bb07a3c91878496b75d520e9e7111682f9b8a58fc7dad30057b77579a49ff02260fd7b2f02af115b4c3343efa01a0021ac01a4b5242032e955289f9386ba46f3068942319d3d3416819566182faf99978c0b0d9f2c0ccf1ad9176fac161f349177fe8e0c0fdbc1e26852cb178b65cb36e2f11d10c0e2772bf2459a14d353a7038744874d515387b1de335f840e4880646e8f18408bf4f6d8e65b1d2160d07f77e097c77832a6ae2baa4502a6276ff9d44b028c885bc52b58077987452572d51220815576c3cb8b6c144720bb0290229bb713f3e25cccf3f1e51a5e753d98b3049d30ce4ee447df112ab1f3a2df0820390e5090c10e30ee0f2c5498831cea9687a3448f72847a04dc594c66f36b61af454a98ffb76db7defe05d7bdb034a653fad10cb1b8db44288059db66087eab6696027c09c486981b7791308e09ac2410965a2d601aa0c59db444ed150037c2f8e3a7ecb50c9f5e64cd232d877d90c06ce22914c0e088ba53de113962a9fa276beca595cc5963bc1409b003d38e1047eaf3c7712641743c19de974c9d36353bb5856bd0dd2846652ed090a4c504aa92f1d89917240d9922141abc5e1100bdca5edfe5afb989bf43f089c2ced7399174270968b2f8719ffced5f72d6a3ea437d6f1b4a3746ca175b942bc8017db56162ed2a764c3cc9863faa76aaf083d4379b50c40b7d042b2b3e0b17ce9c197d466a3c0d74b3da99c12bc83346e98b7262e8fc901c32330b560137c2ff765012590c4d72e21d800ca320dae14b2c7d6f03d4ba56d218b8960ed19b90a05158acece35b15702b1197235780e29f79770b08c99d506827b31e857ddd2723cbf2eb385e0277144c0b11d1df10a789b634949d27b252bf1e444f13b1bce9d219bd5b378a5251cf39cd95a131495bd6688c3dab44ec6d8fe25f75d57f8eb7538b065e851b40eb2d583a88047ac78f918cc18f6b201405d0883b51a919dbe8dc83f5112c0b3ef98e7401b0ec95e9a3007a8b684a01b637a18c2961125a4189e08986911afa90b753bb66d25b5b6d939b4209e0122eab05e055fd521e4d87f25a8e3e671befe7bca31f415e4e49b03e054796fdd40b2f8eec695c76d4f102bcdf4617f0ff052d1dbe00bdb84281f391f7ee493fcc0c32526f58ba8c4bd9c817e0eff4763dab9140a9b2bc87a34417650a2b59e0b45b4054adf3451bfa281b2fd49272cb498c45e4e1fd62b5c2c52a62af33e0a5bb43b147482734058772184b37cfcdc3d7d374cd58a6c1aa980bb5e8ac04f103aef67e04465ff81cf908cc671c37c899bf27e33e93cc4508ca9744d2249c84a51ad7c880b146638089cd53a0ae810a797ca5d21a0f26add5664954261a11fd5b2c4b913a04374bc6c2a5c97520b965c88b0075b391942c6459d525e8f46641caed691d6d2e68b8a60ff2a005d01e74a967e16836397ec8416be52be05ce2a1485dbc1f3d87c91d1750824ab388430dfcee280546162a531dcd090e5e1f66177f92a017930c38508204cac0b210c12398e38a7254eec56e12e7c81d55e8c68e97381aabd755ec23c58e26776f9e11c2d69ca61f74580a4e5a35816a498f7770b1f12059db29c1bdb58b1ba02235e270bd80b522a5111ca8676ddfc3c2b83e39de8c329676e607f20080f4a4f951acc4374f4094fff67ac0d7090a03d9e1d4ec22b97249c31d5505119ae6de2fc32c50ddf9655e1ca33cc58603d55d22d9a1e140eac20ce8a688d70357a90bb4bed3fb2cb95380022eb5a788c2d2d58238c506c968aa5bf71542c9c37904a5612ad484ad97fa8bc274abf399cb662b896ac32e87923790a95e83c68643d7895186e6bf5af5945b5c56d08b939bfc84eb26c1f72fc484321a60b768867e5b5bbd07d5ba700578ed8d76925a6e261379f1186e5975b23fec22c426c46d9f7af90098274669f764aac24c589a61fba19385ee23314fea74695be54b03a0ea1f543146e332ca84b7e597f26472918c0b1339b4cecee30b8d029049cd7f350fb689b90855835f38e4d5a1fe2b301d2c313437e0bf6671c017b4882d85b28c6c8bbb119744b7f97f4299a5fdf6c65a5667af67509a7c7685657479cb8821dd47cff2780ae1af0633801e9a6d4f685ea73c65f67ea159bfc5760f8916f0c4585f11108968184a5440b7bdb22a68d6911c87e8814bdfbc492357e89a1e812f36d2b0af32285d9e50d098f85c3347bd521df75520fc244a3b9c530f23924a5ff49af4a120fc810f398267ed4da8c02ec72a6a4dc86581fb14b2108b8983e5532bd6690165a690d7d9c0b6bfd7a424523de6318ca660b865f185061d18101996d03005e6c03ef3defc61c9dedb17eb3ee04b9efe652038d24c76af80f5ec137e6d079cd94112410bf12a068a4d679abd50547369bd3114304d9659aee282081833d86416a0d605eb4e758b94e218a33e0ec171d03f25846cc264dc3482c6abe99d110d886344aec2b8aaeaeb9bb93170c7d17dd396d2ad9edae59652fb02be73d614b3c52edf348e7dfeb6507b217a6165d816308cab53598b1cd59f079fcd2bee937921f87a847e004243e16ae06c4a8ff1486288a2bb6fe3e78e7c26a0840673cf0aec36f76f0a7dc51154f1d964c22a9704f7471a46c895610e5c7f7d6b463e897bf7689574e31c43c401befa9a77f9b4ada1536ecc9d21ae1fe222cce40d935cab64b5d675f8da85c239ed44a20028b19453b0e3c6cc215a69901c55d4a7502c72250322e28b87c39bd8990595ec88bd692009a15ccd61d7450787033deeec2257693886eaa7aba578592342c22971cb0dc54d14e7b8131fa1951b00891ed4f303e57089d504fbe6d441d5cb45221c4e71ccc8c00aa87437aaf47c99e77b1dedc0558951edecef33e956ca5b34b8946616890cf0d1b830ff1d22e945459134b2a5a917f19ee32af13c51440e73e4303c381885159b061d46048d52a081ad35caa11dcd43408f5a375978ce11849b213779321b82aa21c1198f54d9e53f930f8f150c69e0b574dd7879513910a839f779034db19d3b0694c02a5a415225ecc51f4eb6a80c37015d83a9775d09ca624eaec0302294865b328fa7c248bee01aa8e3cd031ad6e239de07bb7a4f0353e159dd6fb057b7d3e418140cc1c87222df901d5f406e95e83fe427464e767f65b37d5c2faa15ce2904496e56cd1e045ca04475cb9e0abedb71370edd8d3a8599399d0352f60ea7732ae3da7eb3bb04613103d1f06c08947c0b221e70f18241f9a55df80a0648635daf2b2070af01ca9639551cfacd1193597b0b2c18833bd917cde21e506926f7f210630b81c8039b46b553ef995926142cf8d29c7052d4849559acd7bca2382e300e529b4d11323e6b121951c82dc75c01fa5d203cd59d020eefa39dfff71cf460112c67e6b12bf322ee59c4bd1bfa61d0a5e27e5229caa1a1adf802bb04984220ae63ef45da05995c13ddee6dc8cc3ec0e71a8d741a1edba7e5c2aeeffab5b59c3745feac87f9265605b2e3ada21ca4509e3b7ec2111018abc8e3502cf04a76475195504b03581b06223c96dfb13e7f63e1fcf5967dec001e23ffedc191dc056471dd4bbb42d3da14d5dc0860cf3e2ba2ff15ad0082c6fd0b5418f670c102cb5eff31e20b4b3f4572380e1087d78983aee0ee1e6f9c87444eadf4ef35bb59d1952bd4983b891c4932dd67088c62e681fdc791434e4244687472d9394db0a2d6b8e1d22380b42032a182b62a7cea9f900ef3141f45cc4cf1779550df13cf1e10ba44d282add31e68ac137580a7256c9a35c1bc15c64d8e1b8e748f04bfc2b92862437c8ae344af6e632bd402353aa5ae32da386f4bba7e3e714ee720b50a56b7dd7212258db43143d892d06748c57b85a5cb1acacb724a3ade1282aa50e2702c21b8e584b70755572017310c8309f8d56e63b88458704678cb8a20112357d856d3780d7e5d7e6c35ea2824d4a59c0f5171be5c10a60b9a07bbccc0f77500455b3b79d7524a740e7fc4be1188222674bb0ee06f270a156b098f91b152f65fd0841971a4db95470c06bb9fee368f6014638427059444ff66e5f9ec8dcb0bf4c240bdf6cd684f6d7e10b2ff99afe83fd88fdd3f2ec9778f0d141ea7333bd55097da34ad28e843d9304c8a39f20ed91afaf26339789ccdffe98356cf99c944ddf86152a7f53b5ea89cc438248431b05071e8b58815a3b4f554fa49bdf53e5405d9f9eaf8150422cf19ed598e8506b28f99f1301a5ed2370643ac1788d98b5fb1cd98253200370eb19e44498cf67aa44708de7b3d5093df6d0b09b0fc8ee487634b03b8081a0d409872fda4ecee170add929f14701af8c3075b9d98f62f563d49fd92a5f06baa04008e38659f7789632a18eac35b75c6811dea9f60583924accd42c3e7cd684965007fddd688697e020bc703c13f04641d2aba59d090a670456238646ef02cfa0cf2c53ad08a66d82c0c27c84c44ff8818e7d86b8366a0529267efa456eb3bf8fc08411d6a0ad101e4f0f13ea15439028203b8cf757846ec26ffa56b19dbb13fd62300dfe7bb3a079c52c2ccbc9094d5f806bdc66a4533446c48acf7fe43a5570929653ea63236216a72166146f1fb51a48915918adbbdade8139910b02c1224a30047969e6701a3255efba3bb78bf5789eb80daeb4c4369cfb38df3e23246afe4ed457956b29411af99ed79fee298017375cc2bcf52f5e73ca066c045d658e1e9400abea347ff15939d3b76405c0b47b1fd24e8abf5c377fa0d8bcac5309ac06bc69e63bb5670725db1f45dc3789452cec06fcbed6ac018b41f670b2a547201957903f476c4ab2bd75fddf5666dcb13d09332d9b5f0fa936e5b952c5d78c35aa48b63d22c1e2e07dfb3663d9b18484bb3c445bc01030397346ce4de56c16020d40ebd58e1e985300106d8529dc2975db51d3704c954db5be42c885e223aae04c2c095e9f9ac0b279f236cab3bc111b20d4cf576d16018fca17be356180306f40978bcd0e8111e1beef6c797a9ad62cd5168442bd5d7623c4bb4e51e02596d1073c223cdde3b7b08678d51b2722d68829b48c69edb8267a3aae0b1603e09d34def18c8299891629dfd406fac4864260397249288756ba98c718356392b8ecd8d1d79c78d55df497c3f92f502b13e56a98c47638a5708a0a6e8ced60a529999b10b3ad711fb31c8b1de3e86a0f1634634c088f22e708fa842af72a2fd71159f49bae0b08f2f9e1e11a1f1b8d048e465ebf16782825d566101853ab7a8b078bf99e324bebdbeceb3ab4bdbe03e0ccbc2a470981b844e3c6d7b19e96b95a1b0715aeff4acb4cbcb5d3c52788d56b216e71434b620902886921ed36a1f607d7341a704a423df39067910767e6ac11e4d30ab16e822da88674408eb57d2e18754af2cde68732012da85297d32cfd6ef6bbf6e88020b2dcebed28146d7835f036d0d1a573942de01be8642f5523535599bf49bd609a2da46623ba192452347f3fd8b1d4f6a7a7cf4bb64a0b4fa949436e98dfb2a523f258ac48e5b3d3d21c1f0860fba8658e54313dd5e36a6d1b823f99ec78df2fbe88f910287b215867b5d6b54f5d89c2e9d29f10f24bd1b0aacdc6d312f4404480a8582138dc818bde686c9533011fd89137143a95acc69d4aa325a50672f2370a8d2ce8275291966a85da7c06be223fcde18fea9dd1639904a55c27cb152ba0efd06376ae25b5b9925158c1ac11cec959f9c90af27a62d839adfcc9e9e2642108dd1ffbe4d9cd9b76463bffbce3a5867ee31c1e7bb422b7dd8ceb57df505d006bcd16074a335b00314dc7e1ac4a9b1fa487c1da41dfeae14fa3a15bd59c4c07cd1f7eed87432f1c16ccb453732a060a94c08fce7b28e915b06997dd5fefea2e78271fcde79b8395431c2dd5cd0dd3248c9336d54acaf0a4dd9a6a519d956033f91d25481bc69c9be5595bea58722811678900e01942c1d1366f3c00891bb727db9c92ba3128426e73a0a8ba0302dc84e12de107026629f0a24c840d950347768b1baea05188d7ed148d76e828d7c7559e2c45adc403e880c0a917667adb436489eb1fb1e39041eadb2296b2acf936c88e1f2bdace75aaa7d1b4cab8f49a7ff1abcc7627b9ae82b139b4e9048df5b8b7738814cdab7abc6c01cf4b524fe55860d95599462b85489ac2a0739da92bc898f440fe15394a6e8a008ef08fde3576a008221529067deaafb01ae791a514ceedd6cdb40d0db338137fda2afad735128ded8eac52d524072db41178e340d90464bbaf2a66fe92da60a9b869702c92c6a82d9955d00e96da2cd1d5133ad673f50aaa0557eab0333c08494126e38911a626cabf400add61f374936650f1a89448da71e9c054f4d2a4339800cdccc0a0a456718f826ab523fbfec7253c17135dd9802829440650e4db72469b51987bf4121f498b3c980843aadf888cfc024e324667fddf7c51d1282dafc737394bd77b58af68e609f8fb56b7ea324851fc3d27731c7d5a0b70c973bbabc4a0078c97e906d8852966b22929f3b814333d72e1f6b7285da91c014ae2fe066b253a6f72d384e18c86c399deb4aba02ad156e33defc68aa99e701da22d81fdec86079d981d8a00854844c47c20ae7c59473af8f0a4f3777186a442f9ce1bf7d75a9808cedc575dd8d26f93f1891e2a72321b8642e51db9bc0793aa35873dcb2b5773c32d3885af67f184670b5035bf71f49f99b3ea965f0a7a2b350c52ab5b824a6d069a64882618499d35373c05d85c96b4d07fb38ca697c2ee3787ee3d19e869e721719732538f3c297431e966c1d386da1e923b671b4719d545a71d40fac0f13db7e0491b590440bd50f76611ef65e0c305b35bdf2226f54ee95777c192adb11948eb2fc3f7dc58baa67fa7fa81bde54f854f4e3e7bb4276231392cb64ab3dec3fde8fe6f34958db71c3d44f8f9913da884f26d364f0d35289636173ef8d94086149ab9ef3b11a13763f4ea72b32a8e2d2992203029545ac0b64c055cb50af7b4cdee48799df44a56554af8b26c506a383eb686159a8ecea088bc835fe370ef2a76fed27d4709d9cb6265e3cbd6650b83376c6ef8f520b82eb9df1d4bc98d21119f2f5619e410036a04d1c94de359fa32c3804a6d38d258394985e9f890e3f19e80b7166a6b0c6958415fd464bb3e3974e75e00e6d749a908e8511d4b0a6c8681e73a5c1f480ed68c21449a7907ba8678af3a98a13481ff1c2c5cde98d817bb4c689f835a82353dfe7de0785b4c37b07d639f49557dd51400b51fe27017fc604ea897785a3a252dbab7aade5b2bcf872620d5c101c15426724b71da8b88bdf0d112d61f286d3c6ad291a782074a10d96d15b660fdafb856261c75902353d03e0782523c33c7de7437a80282e762711769b1a0949e67d1ce087346dd1442d23d935ca761c4529794b309cb08ee56285c9155f440fcdba800b2e6ad3a09a843a7631a71c233dbdef7581d5535546c9936fc9c7705edd2900920ceea8c205c58d62d9c159a19df84ebe9f361668e6153e221b8eede114e8e34a7c4fcaf5dbdab662a9591c723d27d170d7f7d6a1521cff960f02ef90dbc8353e28c8dc2fddbfda4802683af095f1a33175634ead07a4e5b308e5dd1cb54077c1c0f34b6b85c2f4235649c63cf1dac0fb42febda1357d5809504ada08b30db1cbb092d5e13f2163646090370531b1c87873dad2e0a36f375f9316e3ace139349a069c3d4d2ae4a0b19b9504b095555059ebb453cea7d7e72c9740ebb2f05a6b7a218a782bc2076e9c8fc4796aa247f6bd56c8a6b5451e994a1d1c45dba2aa8bf0c777e972bd423db0ae242914d2d2f728c787484514cde9f5bcc08b5861352cffa9764d4ba6908357f40ef0fced1d2110f24777d2d61404ef34d6ceac7950e40c1d47159d6e36f03c885f6e0c0a6a33e70605be11ba434a909e19433c7eab81b4ec224c6befce5772baa1a1d07459038d141e3d73950c4ccbad3e41c055628886ba87db21c8b0c445e3d620156517c27cd2fa520d4bba3b431b65b2104364fc299c2687eb9eaa259b5d10d2835c9068289210cdfcde07ae52d722e2b2425644171674aa64b95c6971b74b99c26eabbd5a9d57eb20ec6203aaf05e9d5c304e42930eae30516b4cb7298a9af56bd2ac10a5382f5eb3de15dfbc59e07d60e50db85bae3321586df798a4421bcfd23915018002a8822ae856ba231595ccb99e6d3880b336df07f6bf08110410c00a3e24227befbdf7de52ca2465d009d809ef09edf5a87d3a886faf4bb5d6d3549dc000f9fef66a556b372ae0941409e342d593b2bde66a2d03563a54b9411543133c94dd56df54a872d51ab64276c826d59a83f90e5e8a552a6dd4114a7c716295fee630e082386b0d6701544615215c19c3ca66c39940a53150752d957586ad0c6702b316d822359476161382a3caadb4325ba486f26b5a0d921108ced66b95255884ca1613e5e3a99cfed11d42254f9472faa4327c3a96cd52acb2e6a1440fb9b2877414912ba911233ec69456b663450b12328829ad6ce66c94a21dab8c16252126578a77ac32eae4e996548add5865b52c4cb752f4c62a6bc14f1494a7d205364852096b02501727f8ca347e965556c3c1085729cac8f8c0f97bd008a732fa27d50e460895a20f3e8170280e9d955e5639651308cb8c3bb0a3e02b0dda774a9c3b2618417777df6b8296dd708db5cf749857c72fd423e2dcc960920643c410f5e3071b45e0caa98413243278f1618a274aca06e2a7c71c37b5681873cb71849124aa94530b16265f705ce5708041d93ffcf4106acd044f78d8b24505ae258294d349a8351a49508e4c1165a946d754397df8e91ba8b520199c8851a2a38b2b4739bd03b576822d2851403142a4840ee5acfae92a984e85e60b3f9ddaa6d3a3e934693a5daab51903234a7e88411358b725e574da546b345e3084094582d03045938f72760f3fbd58c56088122c3c926ee57c1589d10104d4981d5bca89e5a7d32d1a060a6ecfb4eba77f4ddf53087ebe846ab35460a8f6b94dede3d43eb4ce3edc4b4638f4d6a9d59c2e006c9f7e6f7b74b2fefba74f923d657c4274ba7c42748c10c0671d2c9d2a9d2b1d261d271d289d289d239da3239da4239d25a6cf3ab6d7671da32e9da1efdc22b7c8ca2db6b8a8aabe738bdce209c7e716b7a916482d94bebd8550fbcc2d887ae8730b5b8b234f891c319e123963649f73ba72b472c2e4e044cf3955395639b99c2c2f4c4e94172687ea13ca697a12cab939f99cb3347ece61ca31ca41face385c394748cce71ca21c1b96d677c6c938b88c6395c371c281fa769ca3f6997192bac6679c259c266f081c21de103828f8671c239c219c2216572cb05868b108c3a2cbcbb1a0f2722caa74f8cce2464348484847995944b16062e1c4028ac5120b1b8b2316495d2c86bef34dbec9ca375b5c37543755df7ed3d43ef3cd13d1e79bdbcd140d374b68b8812183cf374c3748374a3745374ff54628df10752c4cac6b2a969b8a654dc5b6709f6356dfb1ab28aaef1c5bcab1a61c7bbac58c6248dfbe62aa7de61538a7cf2bacbe57e43ecf2bb0be57785ea1f59d57e498508e11c56cde8f1552de8f1dbe5744ad705a0155571cfd8aa4154b2b9a3ca315459ed10a232f49059797b442c84b5a410424cb67155b2ac25c617d671559c514e5faac0267a582c9e93babc82a8eb28a24154b365d2a86bedd26d73eb34d56b6d9b2e1ea6c86e86cb4c43edb5cd950d954d928d930d938d940d94471d9d8b86c8eb86c92b03edb10d914c1c2c0ba6c86605b302b580e96058b8251c1aa6037d812ac09f604338221c19460b66fafe18209c188ba2b354d74576a9e58f1b9264c0d568d56cdd4b7d750d5e06aac6a725d55d740554575354b4d5dcd1310309f6b926a946a8a6a8c6a906a8868b668b86a849068ae5478a6c1faa6d1a2b1a2b9d14cd1e06898689c68a06896be9dc6467344938484860792a79e697c7cd314cd74d10ccddb8cd64c98a5dc52d64cd5ccd5d3cced6966ea690697c4e799a76fcf3350334a334cdf7926cfd8f2ccd14c5298aeef8cadbe7d669ccb386b0b4761aa6fc74bed33e3a68c9ff0ade38191743c9e7ac649be3d63256c849164b464c27ccb7461214c846d9d4dc6aab3c9e43a9b4c1610273ecbe0beb34c1554d47796c9324b559f659a9e648a648cbef32bbfb8b28c900cd1ebea85f5edaf5bfbccafa9223ebf702fab265e3734f17a02fbfc827a31bd9c868acafc327a21bd94b8c82d2e928beb2574c267322bfc4c6a91559ec92bcf241689239fbe3d93b76f720aa995be992a12e9442691444fdac8a3318ce7b1eb9b1c1ab7be7db41a73df79cc1a62f46108207eac1aa346aa1ef3a8f4cd34368d4fe32d3712e5465b6e3cfaf17914faf63c0e6985f9ce6216ada63e8bb92ca8a8ef2c6671a9ca67b1e9492c128dbe3ddc6a9f39e4caa2904874738448dc1c497ca875f51d6285b7702ac48556b72874ba4521d43d0a93ee51b8748fc226219fc3239acf2152a50a876c66f92b82cd2a5518e66ad6374d8e0bac02afbe33f8f4ed3383b7253e835320ee32814a33b3fc317d5f26d0094cfabeb759fe6cdfb323eb036882f5e1846f70887e612a6d9ff9c3d2b2aa565639aca6c2aadaaa4f5bdf6deb9b4af2f96bfafe9c3ea46f57face5ffe88f267fb8eb4bebd30dfd9cb9e95cf5e2eebbb41d1762f297b4bd96bf29eaa18cf481573e43d242be4150979465765ee7265eeb2baad8ecb13f2886c15f76debac60f8dc4d7de78e0ac909090a29aa0957798ebea48a5bb24ddd906dea8a6c5367d409d5a69af5fd6dd5262e1b551575f5edf7a97de67b1bf2f94e5d1c149706289afc75eaba4ab48be9fb1275beb67b7493bac87675d11dea239b75b47564b9a03edbdcb7678bc544c554c57435756bba3ddd6e7d6591faca2af51553b647df5b9547e86b6bab455ab5e8a9575cfbccd58a1ef1b9e66a960cfa63c6a09e2b95ef1f56be2b9511d41b45e59af4ddb6ccb5e9fb49a979682bb54feab90a113d6d92fadc5c329fbb6b0a6b4aeb3bf7546e5c6eab9c55f3d0ce9d947ba9a9e86956f350cf3d443ea55da60c72c6a0def8aa7d2e35d6049aed948a3ed59ad3174e4c5d7bd14bdff4a8d648681eeab8a1efee5b2a48d5b76fa07968efd446d03ced1d681f41fb501edfae829ef69fdee1db43e86e8fbd70387f4749522fda1745511445712aa9c9e6ad8f2219244281b317e6a9d770611a279cbda7bf7598de5a82736fd9a7fe654d20fa71b5cfdc2185304ffdeba20e0a4d20eaa0ad7de60e448a78eaa0117510a97d6e5b419646069cbda9a7fe9a82b367f4d45f5270f6929e3acc049c3baea747bce7350006488ee8c8218416164acf71add1f83166080c3eb248b142aef4faca7b9ee7ed20260ae845e1dc553df59a283877b9a75e0304ceddd253aff9813af9ceed87635dd77553388cb478eb5e470675443877b6a72efe80f3e57aaac35fb71f6edcdfab83094e346ffdde1670beb9a7fe2202e75bf4d45f43e07c8f9ebac804676bf5d44527eae20c38dbada7b8e6a1d6715b2b458a2f6f5d015d134bfb1f3081eda9cfd9571504ce1fee29d6ff87fbacbefa571d578fd5315fa77804c1c15b32a8d670c15e5304f51a1b3853a5a75e53038b36700d9c5f3c755108e79fd7c97a30a90242805530ae5d7b66cd8069df9eb36e31066c63312adc7df6b16997764b40278c09a3dda6040f4c5d0cf2031f1571c20fa820453bb0d490204358ea28cc97133661322d6a60a9144cf8e900d0a9034498541d61ae559704873a501d8123d2a45bc23442ae93fbc44de2821320f08b298747277749874387c20e7709db1f160bfe92744a5829a30ab62b35584dd1a66e3f3d2d9565f5f359e16e5f65d235db56078a35ab3ef02a109ef65c16b6725fb78207c31111c63ddf27e509ca466dd4466d9d2482516da3425b6d9fe607b669c254c0dc3e37d357afdf6cf7a6f66d0d12b5566badadb5d61d39df9eb10d3979dadaee5a6b6bad15cfdabc1263d1b4366bef50a07aefed3cbf5e77efb5d6da596b74f25007bf99a6dedf476e6676982c687e7a4febd676adb57677f7137efbd52d8db169dfd8f56d44c58813fc3e70d2dfe0db270f0f7a02662de613f46f027d0ffa0f95e139e840f400d4e73ae8e504b20e8618cfa88ceab4bf4cfe37fbc609d454c6f74d9294850ba04fe9b4af7096c9beb2198d413d566b240b9ba7de736f885394713cb3cca53b6f3fa7ed73d659e7dc691a9d30dab3cc4ff726d0f5e9e49431d218edd3c51debed35b306eb9ca79e71beafd70763c0f9450368bc77ebd75166facc39760c38ff7c7b1f9b30667e6cc185f38c6ac004a23326d0cf84d1b4f660facc497bd13381ae7fb35cbfce99259f7af46bead9f58c630c8fb0299ee77db87dfa4a1436cac7141ec85c81e131e63d232c67e7b643cc67fbbd809b667d0c22419d42a885119ef9a3325ae8bba5f82af302a69f0fd9fb3a708a9f31cec9a187aa9ff999430fb79f2403584c7ddff7755bf8f560f0e8c446e150070b0516715260300b15ecfd940292c44502cffc9402e2c4e501db1f30e02f4915266110c22094ed8265be73c2dd679c43bd672ca4e03c6f21cc90fa81e5c59f5244827ca6dd2a5bfe79ec672af77d3177dcbcfd177bfb57e9e6bec99fd7663d5fa37bc4b5f4f9227dbd495d6f51150a5385baaebe7ab616eb84163edb325badaf389c55cd76ca2ed9a61d377cb64e3765b64f51cda3a4658bbe5aa3af35ccd7ae1c429fad901d5aeaaca52d272ba7dbd71a55a78854f85ca964ca5c715d9dba427555eaca54abad1a7139e2733d12cb5c91b2aa505625e2dae2e2b292f1dc5739acaacf8d3553e6ce9aa2aa0d6515a5f4b5979aa94bcbe76e5251e6765aeaa3251b0dd3a596b985be1261555d55a7523f4ef89e54d03c4b62a5361e559fa9d151085dd52a4cd574fa3c73b1ce3f9d53303c78658575ae7a4f68bd05e1ad834006d9147aaebad65a452dd449cc25a8cc50044c129225ac94585050fa62841427a2782993725ab35c85a82c521249c41348b185b40417244ea0c070392285832b858ec107864d7c09b3429a7bc1def39973de7ba2e8bdaad2731ce63d8f3941f59ecb20833c1c651edfc27cc0729441985154f30a4bf6796a453501c42a09a7b71e4bc2c9968fad2d6ffd670b8ab73e4309286f3d842584bc75129640e2ad5f8089db5bdf409814de7a07c228f1d681c820bbd534e800840e2d5a80c0a00c81721cb91184101b5498326c243e4c42860fbd00645098039cf23927bf78f003304faa185479d02de8df0f0f7a0c8c9607dd953ce834ad291ef49e1e1e8c4222f760540c9a3ce83f6410f87d9fc73e2cff798d0cfa70381c5b4fe3f334ee4306d150e11042c5b30049d118a5169ec071a3449244c506176e984044839635400e8e199298408523c75b27001964fd0332087415da2f535fa7bf57172a07181ba18e24afe26dd0783535d309fad580e910f33087f900c820d8270032e8abd171c3d7b8076450cdd7430679309e268aa7f11864100d088220088220087a00c820304ce143874106859f756c3d666d0e216ffd05196459209ae13bfc5ab325aed62ce5b2e552fbcc6bbb4457882bcc161256ed33e94f27bedd36b5db25a2aaefd90fe6699c8065c4564e09150b52ca1c24a28a505832c2e2a3ec9927bae1a97de697a76184c9d093922c4250594a1a4662a41cf18406a61c4094fd39fe8cf8cf3fff3e070019f4b9e79fe7d89b5cde23aaf29ebb20833c10044110044110741e3208ac3fd671559314d192b74e6ba2297ec66dfbf8cf780764d00c90139420057002949e079d033208fcac7ff404a9139cbcf50db2ac3c76ec3b64102e5244141b0a04dcb21e17a81ada63892050366040526346554982a5e805545ea8d147d4f86aed33ec3f2e43fe6ba1fc1c6bf9cf352083be98e79fe7d8a340de2bc2ea3d9f91415e0e3e98cabcf381b5fee5a8bc75ece3adbb0a6fbd67ca9629ec0fe81fe818cc7a3027e5417732089c9ac2b2e1655cc63320836480603111d9e811eb35c05ac35ca278d0ad0e19045a6badf51664905d5aea2afa97db97e79041af2429b836c0b1c483280b1392286c1426705d35c61001441b51c11b202e08d8b75fe9e1480621cf282988784ba4b48eb9a6de7a6c05289678ebb428a4defa4f14586f7d861434bc751c32c84ee59d077d8220e833cfa907710f824ec3a88b13548e9058b1e5284124425a00b245a4288712f4f2a03bd4833e830b8707b9983ce82cc820304744f447e478f2ea49bf21834816bc48213302ae314947536c4a924820e724d534a607ac19ba0bf526688d507a35965098a3e6e6b8a1862a37688101056304c91352a8a67081cb4746f12df3ed96abd65ac080098c90199848592afbc684a4a51f466c4f5b65fb256a9f2ee2dbafed1adda35ac3b7fd2a758d96b72c24fff2e15f1e23835eb9e7ad4f5f61051964a795279d3ce92ac820f2fbfe8bea91032acb0628a84c293fa717be506982030a546e94df96ff8c90e13fb721833e161f1aa123047d66d8839e731e2c52828ebb8c79d063541e8cf2927bd06bc820107ccf3fcfb1e7312faa8b13ef390d19e479076c3bfce876f4193268e421c5479f7c3ea6d44caa215d52b05449898a810782787a82698a2d3bbc740ef479b3d6c6646676a170c4082927a0e8b0e27304d150120c8a4481d3e2d59ecf301b8ff73cc743bce7980cf26250ac92d23aee71e36d8f281f2b045910047d82fe2283400dfee361f59f9364d017028fa217a75ef4910c1253d831c603c2d2c5f582d09118ac50428e8a4913285a378810813d7a84a447acd7f8ba6814d1a27a2265080c463a94edb6a9d66830e121e90956aee88955d96e9fdaa787f8767b8bb253555b4c1fba0d5d248342d0cb1429784122f2f1a3047b18c249ccd31152941495e0d583515cc23ce8211904facd7f524538fe73900cfabe0f0c390148d5922e31c89576fa30e4e488a5a7312ad4286d962c24bbf3de8e2cefd15a4a99c7bfb0216e96cbe3de0bc475716b08fdfd6a50f9ebb1217fdd6d30f1d76955fe4addc8f2576a6886bf5226e8f8eb1d1974812cf16a9a2424246423c48db632c3de56f528f38bb7fe010851315ec8fa87e3ade31edeba8be1f2d6694ddefacf14526f7d8691b751639ab0380ae8d024872670436031299730c2829621a220f988a2cc3d7f85f8fcf3f7af7f78007fc5d8e86ae2afdb4bd53ced608c7c00be2b827315faf62a44c4b7d7a1f621bfe71451409a60a2ea0542a294edd5566b3b433752c092a4a48820ca3f6ec639815684369c1c1b9b1970c645bf15e67151fb64bc03ef984039e374b1c518fac0d4c31e198c210f1fc3224c3ddc81a9873a3418c31c986e3026494a0a6d21d1061c8cf384cfb2304be741ae09e343e2609c62076378028d67b4201122cf18e27031862600600c875e8ce10d18636803072046c7e819674fcf8b94f62d9b3b33ccaab56f76a79aa75f7c4f657dfb9c9229c2d4f39dfa76d183714ea09e9f2e7ad0132300305e00c0054fcfcbd635cc0771eef8bcf7fa4fd37ad6df8761df730608322ffc88b081a7fe038200f075d9f8d3357c6b1038fff8e40f4050c007e04fa9ac29305cedb29685227e4e9f49a342644b8628334891a40051dcb001a4052521b094943ed10ae22483101b443f64b922c208090e4db05052b7e1c2fe193ff5188d18a85cb952b4439214a050010a297421e2e3851a924a1a420d4acd339fd6a0b302c65f10b61f9662e1ca52950b42744895d6c284f0fcebe3dac6396756bd24f4f4f4e01e0cca5460021c2d4cdb49a833aff3eed5c057b7b2ead77bbeafe7eb714f04757cb5afe6c4b36ceb0875040dbcb52554d98a65e15c4f2761d666e0dbcf079bb3678e33947580035e30f519ddd519ef798dc53acfab53b1730a634651d0ddac04a04c83ef7cc7920f904d5ae71b60e09222dcbf7ec907d8075dac93da0ace7ccabe730dbc0dba3ebbd96e6776cb2bd2ddfe019924987e00f61d98d5f75e175db4d85aab5c16096d67ed59b08a33dcb4be5ec29dc1c08fbad7930513ad887f67be785abb7b86234275ebb6fa0760dfcdbe17eb5bfa52de6d17b77f01193bf829c574c4020e2cde07ebee7ed9a0230b9019b040812b9bebdb635ebe7cfb4e09aeaeac1cc184124668f1f9ba4ba58ee0f2333fa55668c25aa9de186bebf5386320f4fc303da12f7e4a317d79fc536a852e566a2002be7a9ed9a11663f2d4d1aa9220f3244c18d54327707b8c0a835946660255c7b3e6c1e4a97e734343f3f9f535fc1c23fcfca23306480493306b1b3812328492125eaa5a28ab5f60d65260c41020b82e35d8bacaea1b98b50f4a485496313a6081a1acde8159738015196ee0ac743179a2649a34c89c39a6e3e9c4ce371dcf23bf838514153a44c00887a8252f902282abec27befb45952b5252305da94043d94d6c96bbe3da70020a861e2b0cd9a108d30d296e4d3c21d982c4891e702b3e9c03c3b1eeeeab44c910362c4aa64218f60afe6ef60b1669b851985cba47f08b88144a7c4c29c161c32c4171b4f46509889a9d1b8645b182c7265d1393bd61d891bd5ae2a163a9c6e58245272c180c22918506a72a090f4fb0dd6128c90db8d666749219c2150a98b719215c2559ea54c0323fa592a490a48884752f2411ba529864a2eb01632e9609bc826aca931b5a6032a185a4312d08b13db440845b0a899809147ecf647ec0b35812a6011305d7fc9442626571c0373fa5903cb15ef0f7530a49131d18f6530ac90ba20c194292830babf829a5c484146cf3534a89081f4a575ed06230ac8006cc084129876f6f8f39fd29a594f4323fa5946650d2e1330d87917477475e3721c591fe7660789c1f80d0f3e3cde90400ba8e9abc701be245a9ec2a3d8244e9110417658ab29ba07a224545e4bf9f5254403eef5ca12bc6181e61ddf341fbd78edb46a1b2aaf13e680f946285efeef9798f14d50ddd525f827c11fa6e87cc373588ede4c49cc594febcbb67971c90b4a4b6bcc0845349e3842328cc60849726627a74982d27babb819051c0bccd880dc04bf864ec211eba2bdf5dd9fdc95c89b5c79cbe3da7eac97777b7cc15af2727f6edf4ed8705289d140b3b7cc79c86f3538a8516befb29c542d2e71eb04a04b5f0f4e969e1299524f439b6f5117d25e89e6c08c74f093ad15786a125e243024fa92339a923565247705ff5cee9b43c5e804356f912224ba89cfedd70c1564eff74300284550b3e202a24952e9839fce06549d9ee1151ba00f642d9b3b109403d4a4a0b51958d20a6aa4cc1c50a890ab71ea7b2872eaaa5322747391dd32a8528a542932be58a970acd6644f0d567befabd2cb85fed78e9e401a908e08f2e7a054631f40b86b2a1d071e5e959796cf53c3ba3ced2d6af92230ce03f179b7c813314f4fd3adb82fb755dbd5fe7de8cd2af3b9ef18bed3995954004fbe0a4b1e082b3126e095200be15e12b47d92c65739ccda608fee699b7f55e1c08903e7a05c8d9e815982e9860124caecf67d6d3286994eda0281b127d940d8deec209936072bd09ea5b9fe59c1ee4eb5d974372016319ca6cb6230ce03d17c5ebddebd90ebb5b245fdf25afd7ea006f017894cd52ecc0f6a010a58c88b16145d156a07e6ebd9e65fab553b7a595799efd6674f2802900bfedfd6edaddf3beea0c05fd9ed74b5040bdfb79306693be3d419cb5b6842ee99cca4a208215c1fe472bc17a4f6036278f0346484195655084cfcea7f1e0f89117205a42829f2fc104f53da73d8d12144940c77ce7a06c08741c4b48f0f326a81e74b34c69d8f2fbc8116aac3882b22528e8b74e657646823a66cc37d8dde17d79b3912417609d2e199acd26d34e5bedd99cb4f67a45b05e07825d0d3f19fd115af0362445687700b9001adf63e75d951045a0dd5be97513c4fedbd276da5917af95a1a06d77aba5f6d296417cfd20809a2964364d380d5506f37a063a73fa5c6bafb576060b6233c620f3da228a388435445434618235c40d6ac6e0e962fbd319073434b980174f8203fc943d0d153c655736af80a1d88d94ce50063c6513ca8b1fd6d3973da5b8d65a679f7db1369d75d66e8a38f0f4f1353e09e8981cc2dba70963baec5d406974572d7102ddf772984821e9275048c122ea6237d209f4fddc21e5cb964b2ea093e2c717267e4e1ab5a40b1c309d402e70c07402d5084c2664b042c2cb8e187228a29c93874ea0768103a61fe902074c5d04ab052748c10f04bbf3c02bf3ca2df8f624647074c0f467546eea67d3a0f0ac015b2738cf2b26387f4d70f6279ca7166803269af9731bb090e92495da03dbfc5514b09db54b0465c03fa3e01cfb8ecda004e779f54b38fbc780f3d47a1a304d112d50ff84800126a9b8cc4fa9231cbe5b2cc17596298f973a7afa1e6767d520a9a153f3366474823877b27854848351b07aea0d9875663a4e25617a036a19343df5175d635152ffc0c753ffe92525f519619eba02da67e6e910acda136ffd746a7b52fb9a399b84d805eb38c156a706ecb5367d82478c98c2b483852998d802e207488d1a4cd830a225c7d8430b38ad15868a886881a388208c607de9b25152af798239d0b4e93550b8660a165f42766021180493a736f153cc4f137e7a8efd07b05716609200f2e506d2d2133cf434c58a2886aa2c25f4a852840922baae9e642009304b462061e2c2a5a46e65332acff42a9b9547c709d30f97c0d4bd3681e89281a80819c414c1a3a44e9b350ea098a4346132e58b1125f59e59a3e1036987282b1d4caa0c95d47f660d01434c7de1b1040814ab927acb3ad0b449bf7f4a0d41e1c13138531e3f6d0fb7717ab0d1f37e4a055bb650a1160c891d500e7a831dabc19d7532cfbf0c409db0859833e2902c5e373231bce2fb46f0be048b2bcff41a18acc68646c5cc0a1c93b979b12071c61cb145a80366f0b927eb665703bb533ba0bd3e5cbbe6aa7dce392d1dadb5d6f605422330754bfbe9d45aebb65a1be6acb5d656ef7bafb5d6ce6cadb5b6b3de6f67e0907853251e515b8c4b34617c01a81546adb5d617b5d65a01705d743c1e0f4fb4cfca7dcab5d65ac3e9f333e20307e3960d76e61c712ce9730dba2cbbe34d9b3306cbcab5d65a63be8469a34ca6ffb361ea4d9bb14df35ac35cadb5d6995a6badb4c719cc8c37559f4e0d4d0b584d4ead3016e01173aeb0b1d88a3959dca8c06161532bce38e79cb38686db4fc729c2f4a98b2d469c223c2330cbfb345a1979a869318e3dd064a033e3191c619f302c9699c9c6afd65a2f3e7acd8864630ff8264d18df8fb5ee8c9f915a6bade19c1c801dcc39e7ac609637cee9d5e9c4b403c0096ee1edab87444c7711abb5d69b6465d92f00c6cf08a65e6d13c69cd65a6b5f8c375598c6c023320eb5600a1e516bad3500303a4600be9e719273965780fbd9f4d547284cbdc62083a683593e6b9db347c68dadadee6259cff3d2d55bbf9eaff33c5c978436cd69cf123d5e6b3ddd536714c64fef993d2db8f4547b63f8eb7a2a0f0854b1285c673f58b43b76ded7d3536bdfd7f3755ec7c2d451c19e1a809f3e36fd9e5a9b31e3565f7606e3e713f8cedbdf598c07afc4eec5cf8f1ca1849cf666549e065c7dcf2adadb59085d30edbd8e1cbff671f10a88a180e6699f61ddce78fe6b9f18dd4b84dca7038bbd5d47b584c01284f7816167cb519dd2ca42709635880b1e5a912a3ee8d76129f26db9f7dec963bd27583f6befb5d6d66e66a3bacf5eb0f3ec178e24498a22498ae23892a4f81a479274ea16a460531144413f5882d768294827edfec882079d82e32cd31f45ea2138b39fedd2795d6ead94de5a29bd94dece47e781df7dea62638925a86cd9b2253605b7bd5dd96db95baadcba5b47d7cebbf68a37b77473cb9d5bc6cce02b2e685da1c27274c585225d7745c7962d5b8a3c81505dbdd6da6e6bbbefb5d6a3f7528f5edb82af75d22eaddd4e7bd6755d772d54adf676b95bec964ab3ac77bbc6e21501a9b488e88a685dda4c4619fc22c5908645e8840182d4751efc66d4f116992d2ff26adc7283001144d7751dad4eebf5aefdc187aa1eb010117505e7799e67c5fb3eef03c1cfb312e57994f2f03c4a7954f1bc2d54a83c8fd2293c4c9132b5830e525145a2509a250728b72738781ea53c3c8f521e9452cfa394c7bd0dc3fdd0736de7f511d40f363451410bba46b3b52912353479eabccf9fc2a76f30e7612bd3c4050fcd5a22f65a5ba9fa0906c76babb5d84cfa79a35688c4dabf0fe358cc9df62154e56cdf5968bbeb7d30fcb5b78383b5d353aafd4211bc9d18ca6295079cf7c9ebba8b2543d7b512b52a61aff5ee8ed1a2454bcc0ab69df7959f57e2f6b9f75a5ba99c546bbdafab58bed6e2b596aeb554186670e8345ea463f714a9e3f6099dfa778f9ad63a42a7a4033e0ffd9241e10c1b352d540a95c2a5ae5da36bebbc9bd98a6ce775d6cad4517f9ed76111a2f3ef731a2ff6586bb51e11cff36e84cebf199e7d582ad4b5ded7d54f8ba7a5ab5aba12c9027df4aee809f6dacefb3e2c2d5dd5d2743692ed6ef7592b77f612baa055150e9cea1a58d53ced388b94e4794ee3c55aedd75d711c91ee7e235cf732fd6ea769b09adf70857d9ddda4e6ba372f14c1cf7f6c96a825d4f2a380101a40c28cf0a707d442bb810999d82bc4ed79c1e341016e8c30f52878139b303a07a63438cd10ca308e610ce44bcb4b066b103661ea210c34355dd77530a881759ed779dfe77554603639d8a8e83a4a7ba858d17594f658b1e48645d769610a5f68a1d375945ad17141c95292164224987aa8c441072fe8804684c6820b00503b7b85a423a10a987a68a4c783aea3b4870702e83a4a7b088052da7594f618401d80006059afdc259a409f4f3074d16ef15cc4f829fed8f24d5fb65ca7f3325a8ae12c5f39af6b0a2e786835b80a762093046e7d7f5626e9b33770cb52b138a3d8cce4e95a3805c441cebc4ff53655af3ced93804933f299d1b470aad642a8b3a9eff0eaaa7d729825cc32a7ee94e5a2eece6381b774f25cf05300fe37792e4e40d77eef254590d55a7b8aaf9c1470f624ad3922255f61483b3a6961d89586e1485fb47b91a47761d0e8221d49190afa5fe48b2449ef42ef427246278d24bbf0e5e2d70376edee6b36270c921c6799866c0c0a3f7b3fd968b7c476c12d8157abb55de7d57ad6f348fa119181a1b59a40dd04ba417c087ef33e5da7dbb4d5547bd2d1526bed6cd69f3b8d85c54fff6ca541623dd5eb2c2746737240c879193226d0a44d63c04fb576bba95af36e93cf3ca547734ec9982d6dbdb9bf392a983abef9b84913a8d274de75dbf44b3f6f530845b4cd108a68ab4333d8cad0ceebf1edb9e6a7c3623353c62da2311aa87efe7d2153d3fa0b979ad64e43a56b53aab5502954a24a4d6b0f95be1de93b322aa232ee8f203d6cb62a73fbb6ffd1e976d66496eaec2e54e2f90cb9b01252d21d52e4a7df14a68c6b24b3546bf788ce96beef910c940c54fb64191b646c90a2e428e4f266e0d4d155ba4917e91a511937855be40eb9471346e3ee91cccdc2504306d9ef79229a409ddb269fe90b679f907efa35ba47200e417efafd3165805034465752891ac8adceebbcee0e7d06a99ec0a9a645415119209428200edff7eb0c1c71b71a211f4d5353531348e78fdc93323581ac37bba6559e699bfa06d0eb6ed75a29ed5a29ed5a69d74ae9a596de6ef201b65a4f9c5da9f5ba6d77eff5ee07745dbb59d7a6d1d12e8a482fd0a475b7872ad18d9b409dd3db402b10d70d468151ed93c1286094578dee6ef612c2815453601488ab35300a8c8a02a35e382eb7bab5b709446d13e85a28509eceec0dc3e8280dcf4e0ec0e08cbf6d932db25c9b0d615b1a1eedbdf45ae0ab6dd2f15dba4d4ef7a97deecba7dfa40934fa740faefc748ba342a295cfe0966fcf1e7c3b8b05e8bc3803731346fb8734cae884117ae8b94ce76799c68c6d9a97649232b2db5479a0eeed46dda9ca13e3ebec3e35ad3d1ca2e93d87c3a67b24ce9d8c6ddf2e16c1b406c00711118d150cefc54f59d680ecad0301e0a7536fd20e53c0d4c3234c7d862860eae1104cc70d2610772accba533b2f0d4221a111f69f3df8ab6190991e9a0c64df3219489b35109cdda99cc167d94fcf9d0ab3ee54d6c535eda60a4d8d8e0f22a20c6ea6d4c05ac06c727042202b7a6237626c0c039e55a8e4ead74e939a54a86600000000006316002020140c888442b1284be344ec3d1480106c904c625830154ba5410c8320c8286308018000000030c610221a9a6d0082e14f1c3dc6685fd8feaa6a432ba98cb12fbd7d19525207406ffef796b50050885743158cd09a1e02f6be2b97ddf9a3febf35ff6af957e74fbaffeafc51f347ba3f35fca9f327bd7f35fef747428529b6270c90212bb62c31055b3749af55ea4508fe0ca64170c74aeec8de9f22f73e9c6f00eefb77ffc79b7f6efd51f727ad7f35fca9f327dd1f75fea9f993cebf7afed4fa93ee8f3aff474d8aff6af163bf6e803122614d29780217890a1491e8987ee9cc392e4e3601db66d4022ce7a934c25d17a06d43bd3249461381161810e7f655d45e346db7547f0e763e55413a337405094041638de54bb426216541eb7c5316f0938ff7669ca59d5a36aca2443d96375717790ea904fbe4dabb61ead9e72212b2f84ec53a92b15ad14aaf538edfc2e71440de6235ad400b7a88f0fde8747a9dd88fceb6bce7b6495f72ea508e97449394fd5cfbe2f6185541f3250c5d0be64c489332079d268f75b3aa0767211b4b2dcc40d8358d300b5053fe3e0362c1ac4ffde97b125143fdc2e5deeda613de06ec177422bce176090ed0f71c2308d81e7d2228f4f0dcc43a1aae583d647575ae340ee4e236b8ec7c82eb0d9294202d421e85c4820dce999cd3ceb7cccfdc15b00b503b26ae3334a81bf3a2f5de8c35561adc86272e52443a2e6d4f5007f85cc2caac615b6fcd7a426d84fa25a4273f87e289cb5456de1b1813f65834d356f690335811d9f440f5a90a6e3dbe9407d196b91a3c6b2882781bfad2b71172317f70220e5b1a66530d37b10944716d4bb8aeae775fc62500d1ce513e37deb11aec126af1a501d7984270a4a46565ce6256330d7030ccb9410bb3d17d1e728386b10219cc3ba6bf7007e136d80989b1e233c2b3e09114a89f652bf17487665275b433a1750cf666fcfb84b692eabd4d44ae238b76be231f5a8c417f132c9f0e826ba802ba4c4e97de32ff07d4d8e0106746b4c93a676de8c73e8783ac0dd8500d28b4bfa77f2cb3305318607b3c0ebbb3ba599105ed5b57cc9a05f2aa13ba4149160ea1a0335c5c43cfda5f96cffd4a6c4af757dfc0c85ab9681f6921595ef8f011c8f454da438bb7d49d629627f77dbacd8ad8c8708f40e11042793428bcd7e8f26805a5c7f92de1f17a61239b8f94f1e65a7108efa477a725dc0fc13da5f9eab9296dc5c19a0e739cb7b72c8523f35c8eed2d624bcdfca44e741f0eade1489a9656b5dec1edb41bd6de12679c4d53ef2dd84a6d478751860a00995157bf3fa354f1958430d0f2b493ec7f335e72f43a34ef1ce0231e229b8e3537d14e821c613b51e86d458c779c561b9b97036aa0938e8da78ceed4aa96c16d66722177ed65f127469d2ca431f1b74c83a69c540593f4ca71249d485b3f10b9e25f04014282c90fd1b2c4f485a63aef7103e7b4b099c6e2758d450f529f50bf40604f2742473dcb59c29e56d95f03e9f032808b1e45e960097725e1d985c5f99c23956616f3762c9f899d3ae64ebf1bbd68fb1627959a1c1596523727d308df5b40898d2b9cced0a6245268319a50c449522e481228c38896c11e63bceefceb87bf94abd311e2aa4927413740e352732d37b0ea8176c9819d3084ef90954218c4d6b4b79775439f0f3b2b103a18356aaea89a5e6812c0f641e9781f6d983e88243ed66690460ba8daa3c32015aab759d5c94953082e95d4e895207bb2bf66b5efe074a6f63df66686750d430e9e1d71cb721074f553bf40e545521e4988fb76962c708237c627b31398c40fa52c53cc0ee45ce649f3a6078d274cc383d09bfccfcc4bc44f3573b35bc95bed5ed41acecb5b740319be08b40b5e5f68a00e661673e2a4c42c4a9df30667f4a960f3ebbbb6a5cffabdf7ef4c5f7da509a0843504b4922d624d79a637be73159c111be859f1908eb2be53868d2815e44b5dad80f9cf195f3bb2d648dd99d8c039b909cf59f57192075d92a85e565dbe66d83cb8a4a9bd1576df227673a83bcbea498e6702cd5510ff4d651ef61ca330bb63407e45d80038e334d47bd61da6cf0050df60fcf9ec305a78df1782f5bd31ddd385466f7b7e3745ccd810ae9646e24ee225c84f4c9c3d58f3c59ef225b25907fa52ea9826bd9757743f292ef575d13e4d55f98c439036163ef794de50f42f4585a8cf9d38e8a56aabb480c9735834a158f4075446a738c7dde0c36d6160b9cc78ca743c1011c4d59082721c5adaf8024173bb9cb2b494e5e8c633d2f793f1a3d5ce5c39efa7dcafee55d1e06c1f40ad16e07244b39053205786a30ee095fbf9f63d8f3c0804db8b9536d978e9e52cdf16bd10e05f4a6f83906c3e8caca49bda3eb396f824b73401249b15faea8e7391306c9ce70eb225689c1ddacf5cee092769c8809f8e6b9dbe0e3718c05c59d1a7f4340c823becd01f8afdc98f25282bc6c7f4999c36a15868604a1bc68a31b75a8381b61cec30e202656a300e2b4fcedaabda7b9bd8205d6f479056fcd500617c185f3e7eb1394d2086704e31e583f9d01589691a1d879aa71934c3173bebd12cb9dac7bfd8e2dd715ef98440dd0a69941a66c147ec88f9b04c7ba0d725bf38ed0b951cb7067a6b98bd97146e72da49f303031c2858f0e8b27bc7ce8d38fea06be86f7f836af88f7c35000c85428eca9cdfadc79549c4d63a97bac385c367e16bb6ba6a1675cd0725b681b01fa4c3229d981758ceefcccdd958b45247b2f67f4082c59b3db5ffec5ac41a240975e07c7e5fae60419dbb8c3068423474cd8929a0e8b2071f921136e0e31437a55ee6bcd98722b3b8fffafd7abc4989c8a914ec62d28ff453d2b6bcfd705065e5485e83ab8f4b38241b83ed50deaff477cf699342b593a07fe637fc50f3fa28d34a8f4185b9efcf4f32cf7b33d8f010fde6768f0808c1c1cb628d3f03724e3531d6510b685b7d08d0b89bb0c6cd0e5ce987dcff92fe071725d40c86b023b57f4a567a1da8d23b3ff33d2b8216998bd69c7ea79dd474a6b1252cbe8c4679e4af49604a5b36fd276068703a20908d7bd9c93dc0e529301f32bfff81684282e6b7a6c79c8b31001aba59a68ccff90affd0eafa7157c363f8195ccc7074fc4a4aafa59f2b32e40601424148328eb833561ca64c011b53e6ba95ca2fe8bd073d56ff7a1ae8f39a39db8fdebf22a3e79a0ad78be2fe40ef1e4b46d2fd6665d7d5946ef72bcd8e0fffb35704a695259134d30c74db819430797b3f24adb66585dd7c11cb28450e9141c5ab7ff8b37396a3a5c07700a4422e41593a41d13697da3d8faeb1743ece4648863e48ce00abf13ca402d6c6d88e210388276f1e35d9ab1fd547283429c472eba872d01dd4f1e70978c2f853a461199bd8f2acd3501bb793a2ad494a257090490feb12d7ee1ea144709a270c3bca9c7941d480a55cb1c88155556704f44fc96284545f4a92f898cfb8eb4d0f6907b34c0374db8d63bcf5e4fff2247f5449be51efa34339ac0e366a219f45248aa0dfdb07d70687bf74307c63c978d086f1451cf294c01c32409694a823d6d7d07c08e76f6542bdf49c3b7535d7f45086819fa044281753affc2dc34ca63908c4241c24f48f888f689239460a513e6c0441eb57e47d0777760644be3723addd20f6139a7cc61d86637189295a802b3e6603b36ec61fd20f8d389cced11a165156e22e60173c774a73fe83e9fff934ea448437c707db46a451423f54e632f2ffd94fbd121583b9340a4da74facb62ff96830c3f02340034a691d7467923c5c54ef0ca0c1c070730d01189cfc6606a4905009ee3122ddc1b9062af41c74ae1303f60000a6438a31241e97ab3f03d83270b828447d2b9d04f57cef4cffd00e0a5c888756be83c6b23b567418ddf8c7199ffa04712468c9a235d758b8743d48c75faced5db6d2e5559b7b9bd81a472f4f5b1c7cbbc61a297dabbd1b1a531503216d9805d0928ef41b1aeb11df9e44bed17298f2f90daef84135f76d6341ba5e26ed9d0e90a047927f9a674c89605b019df8bee0731cf4395bf01f88156789e7107d37dc2d8406f4e6ef3d21f7cf29e775dada50a83ffca16de59e7a7b80de5f224043b0ba12842c0e6623c5132895b734fe6182e4e816dfff2194f91683e6f25faa3dfd8e016bffa924eec45d80724cae055f62f511c4a9ef061d4e387e18e69700e5d654030c07eab72bfaab5e52a42fe137a8fbff5feaef01fd8c0aeb3277b4fac41fbd2a127497cd209fa17648bb312d0f1e193e99793044410efc70b707cc33ccaa533efcf7bdab6d232f0cc690c9bdcf2eb2c60e38dabc5069df303df498142bfe81de6153860f7739db361946cb2797b054c17ce19a450239b97a5c7ff9f720d2ec98d1eea3d41eff685b51385cdb1ce1e3729c8deb38068fb04b9eacf1745e4a99a1abf38d993edc55577db37e2ae77d4a6f8dfcbfd97ab14d31f88292049fef08fa6ae0f2a7f1e6b0e90ee56492f36920440403ceb6222fd18d0a354f56487f2e3863536c3e7670fe979a6ab4f701a5b6e87dbf7b2a66e363e88fad55c42656be7495f364d5eaf25c49870c82dcff95005287888d0c97b0decb455590820c7549a47f59f4f01593683d82d2325ba21d715b3f8f7160986e42800f7196ee932cf1af7f25b93611d5d7b9f1fea2977bdc78b711baa3a9feb90799e3907842752f49aaeff36352767d21a67ec7063207933bbf22373837c5405f63ccc8e052c3356e369f6b8c5c55df9d507787b25b0dd414d3bc6d57720e72d360c4014050189566561f1f074c794fb16491447cf1e71f98169b550d3ea71ab5b6a09b533b7e3c8dbb75c34dc601dcdf204a482c3a1ffb122c46e789e55e0800576beb112b44ce7bc6807c47059cc0c7693b34d38ed3bcf288058bcf7ad726595e9ed7af536c8b28cef624a0ba9ae341bd774b6a477f83f9b49040cde157eb58c7c9a75e3acbd3065009786cd08dfe23c2ba1ce4120db47e293586d24856abd1fb19a6592632c67735a3d158ff9ea9a90b2bfa1d1d95d899b8be4ab5ad9d8136704970be4841476d413e8789147d04361811ec4e9e6f9c0443003d909b2b101e5c252c4f5d3d1b714a183139959982da856922efd6f939e6b9b383a5e51f23be6d8625f427037986c3022934112c9158ecae28b775bf71e29252b611f5ce42cc8ecdcad3e3b003a60cf2f5f3c5e169b6fed316730dbb8d2d78f799b6d2bf781d7b5a7e6a9e077bf8dc000f63b7989ce9ccf06b6dc5e1be057b26f32e790091ae1e3746ed061f31e8cdfd8cb193fd86322a8e93e6b85e2fc4f898f9a9192d4e640128aac245462f93c67fabcf4681be448640a2346c3db55f3457b3720c7d0e557784deab6285d9abd5b3279eb73ea5e640042dc6d8e0edbddc26403cb0b6cef4346f69005e6a6068e39a7243a8dc9dfca4129399bde506ff7b49e04204a42d37bb0eb9a57ae37570495d55d98bf2720f75b3afe06f054e878554861871c9039195273fce4e8d5b8137086b5b30ab8ff9989bf144d9fc20c8ad592841798d68f23a25d37b1a1cb07e4186dbc489465aaac7c7339c2ba09495e5c545926bf906805266f6b07aa66b6371ec1429fe82954df0b9084f2a939c2a8d16849816d0814ada813cf92a1720e00c03843380e5b310500bc3c7915d272af98ce4f6c3693bac8d5800984fe88138f7e5a88c106f0c1ceed80a222020b330c0b94aab73f2d55c934a021abb56bd2597e4e21dd71b0444adb728f9624f3bd7592e1b126630201c03783e8b0c7578bceff1a25547827a1b0f3827b769819025a6d4973e8320702b04ac10a38f1e38c57bb6d5a5d41d38be8a53e7892a6ae7ec73413dee0089e252f2cbbc62d6c58f1524e0d99a34b9cea4811016d53c71b6e0013c3bcfc982bcd867ec6bebfd2223e9c4efbd601e24f72f56971522a5632274e4ed0a5f0796b9cd712f712f09dc2b34e34d41f3959dadf767893a995b8713aa695797d25448c0067bff2dd81fcedd21f1690a6dba8f6d13a98fa5163e666aac6f13a1104629f71fed95bfeabb9c8f6d5dc3259b8e105c95f1ed30b20cca07f094ce371879134e265c96ec215c35ef340ed40cfbae55a8252e6a9aab8ad8014bcdb13c9b9e60b809be787e24bcbcc78df2c749163220cf5f968f5c33d22e2c6bfbb3ec8a4deecb54d8fb9a5cd38ff16944c6814c325e85f423e35dd284e8b217bbe5b3275daf8f41afd983e8f75700dc85f19ca1488c633c10d23cc276410bd3b01e094c3e7eab988e5cabd211a4bb8e12500c959ca14bc5981f34e86c67131f592ece30b3cbc7d3d2a9c615c82b4a7d1b031add4aad75b9dad9487ba24412c446e8b6028690691663f1b15d4a1199355ce3defc84aaf75cafea8a75c4a867a158f719ff5fea91f45d1b5fdd90aaf19c41a1fa0805f8e7b58c80e184c50a760a6de0619f8ac2fa55088133e0dc0748dc2b42a8fbda62f961b7af0d7952c0db5a0f2759250b8b56771a5bb2b39f5c6ca18d1dcab6b2a4bc68e8de0dc066823b5bc180e2d1882bd2b591ef544379b82dc75651ba21f489d5555e7291152b84b904d5bfc576c6467ab5921c291bc2d64e449afc3c42601fedab61554fbc8117840d2897cdad36a2a2acd133cf7ac0b6dac064565262e4cbb8ddcb65e53dd5d0da51547f540e9fbd87f8a660cde31e7fb9a52f8e48fa0b3650fec3d69dbe9b35846e62536129ed27122828403e2df89dbcaa0004be535e79a4c756d5884d2adcc02d7a3cfe2b974c8e4e68847ff8a6734522c3156ec9184bc8af4a32b7d9fb3c13f47a1c7f029537b7cbeacd3173630cead6894f48b4cf9bc52269b1602837701d4d187ddb9b4a64ec36ae5f25acea4c775dcc2161d4ddd07110a0ebcb30d09d9614b92b6dcb50a982483245e67c0cc412503be55c32d409337e619332fb970b2d3230661a1ef9ac44c95ea2bf1a9b209ed231560b5e2ea38f170e49dc428e7c1d4f30d75152c4f12c45fd932ac436dae05c1f7cf48c43ec62748453f2af56c6ca42096bd9955b6d9f94f540916def8e8491ceba519b57adb21548000b14e44c9f17c16db1bff5717f9991ef8ae13d35e64088e76734544ba0112efcf842085a2a619a199153ddcb9f388d97d19cc579431fc6e8282136238b6de0cc196b0f887fd3dd0cb1e41ae29333bd2f42ff4ca31e0bd03f1f0025666124eb6b16551e286a361b286c8ab2b0095aa31684ddd99b11f021e40718b7d76794ad36c8cf746839e9a2d603527ec8d14d87cf29d65771633a8df24018d465d50430d08268d49e945d525726391e5eef3e3d83e4612bd8e8af9adf3a3786061e778485d9f9963b8414956a887668020e19901e5dc10ad3cf94522d4f2570a8c77bf0051665ec2dbe1f97c56144f114c2c628eacb14e049ecdd5ad4ce8949227cc05ad7a8909824390cf343729d1cf096c472b024ea916564226164ac92a1cbe59a7c337b4b26e260ef633684792909682117ad61a58a962cd6e57d5c03cbb1616dd924008cd7a76ce947a595c8572ae19680c46fae0173d7b305bb9da85202ad11ca9e0b71988c804984e5e0f5429d931cf4edb0a79775af5e91961a1d1867a1d6a6a3ca1e7b2c753122f4b4548866b4ad0272c59d22edc3fd1ffb07385acc30f87dc8094a9c860955cf2315604cd46cbb8d4e564bf6e13c7d765d861264214235773c26391e452ae3a647df11d80d669b0b682929ed5cbc73ad86f94f01a4d8a6f227ce4760e0def164a32a80cba8df68226514aa795621d1a9f689be0c096738883f8e2926d165842449d1b08841240405df8231ad94eff7b8c4992cb8b0441b019924ac05ee88bc2c99cb060c9096d28c05c977c16bec69cf6bb132e32b979aa8a5d04827e1d158991dfbf949ed319b99beffe613f5bb471df9905f6b270391959999abbafc5d554851074c9a1e9cb4465042bab3d18ae7309dc2842586e3a80914c99289f10ae7a0786ddd613ed085bda3286dc8519ce5c0039059d9dd3426a90d46f20651e69fc322223f5b375f09dd0fdc4457fb15ab80fefd3f39fcbd4adfc0f402b2627164e012245a02e7c33109eca167bf4d9a6e135e052f551907f899636c1f0604faa1914f2492690a0f7c884bfa4719e514ef6304de91d091d5847fa45ce7c15de2e8de70a805aafc1a4f5aeea4c73e3cd7a62eb97a1a305db51115399e91e81b66ab026697f659b749510f0eea59a0aeac1f95c5b5ef64a47db91ede4dde607243898ed0bd1c133623819506e3582f97ba9f20892a84dbced601895bd1572473ea6b19092a6fd961c652ae426b41ab1beb43d79b16f48c2ae89575a0ce0198f459316115fc384ef1ab7031a0cacf049c3964ccf18e70576a02c155f1b0e9e48e68f06a123757337435b0399308bd183d3d9d5ab44c986de95385008a5c014f1f268154427c3f472ec0394b9c0dbee3b7b8866d1bf31cb1712c4a67e829327b6b2466208b6d28ebf5d0db6a7f185b6a76c9a1c0a090b02f64f3d8d3d8e0fe4e316982ea87d6f3e82099eef11fadf82d82693b0559771c7e5b48d92e08b265f446963d4c542f18b044481fb7097407e51455c949e1692266eb559a6184708361cd195cb876018f19e2402cf032b4483fa2f3a9410404ab916c44a76c4fd700550b908509963a76e101c62c6f843d4db55ce9bd27de4def721fa5b57c5d6636ae5645318c51824178b5226b67ec5cad765f9ee10b6c9bf4a320d0d8c9eb623cc879b55c0206df6b8927fd13a44e68fa896b46d962818d1b37d8f0235ecbc2cb3a9a7cc249db1a8030c71ce0224273e7f1e44300c80f2e79cc90767f428ca84460158ed1ff49a316b0a5cce6e377bfb5e0092c79b5584b79bc0696f6b4384d0d09d1b9823b726900e8260cfbe18a45a0e100ae8ca9e77f7b494fb9ec0103e8b46976d801512aebe8b48156e31a2c43a8947ff3365ffa169671ae25af653bec370a3b22b4991f10f276320f1d12bcd12da0b85cdb9d68f4412b7f7ddb5a63781e7307baf9a8732d5902d57cf0744d91e77aad988622e5090d315b0f4c6849f264c66021b03216a2721b163f816c87d4fd38b030d383ae82752ca38a0c93a245b646841079ff726ee6f71d501c33dbf1a594974215fc2abd328bd9075f5881f6864b7829c346e8c1e66ff3c39a90f8e18c0b47b3d9a9f518134e8520ad8f7dc21c62aa15866ec6d0d0bb6644173d02ad14c6d6a3e3535bcdac5d8915591df05e25c98b74209684a60938ff360589cc6d99aa3811b7166e5306a2fbca7a7098a763041b12bfbdbae3ca57c307a6c7a332fa2beff4c2e580454abca8d79b0467295643960944904423491ff930262b039ac260c42d14900837be4d502b15b951c32f313ab9c03b7a8138450da372ff8c67a3c4e3445e982d4497badb8f93cb1b0d60064bfddf4e89d4bd45ba5f55af1e5d278326f89169dbb6c850f6f21c59bf2ad7409f20e6747042b2130c77a2e84ac91eafc53f13dc18bbaec19c80d5d57c8802afeede89ba65d4fdfd9b70322ef8049dcf4530ce4e905312a70f631e33f0223500b03bbaf958c03f14405c82ff7de7c4643b1df64703f08a0af00c15571ebc181ed7c0891835296d33d14bac15cf54349db014bdc351a625fa0769a061d4e6a25bf5642937372927fe396b0671d2bb45aec75a010324a7f8619e6a0221b2826fb9e87608e9f989057c412380c9e69f5d6daef1443657095355bd61a997b386528e50cdf24882c231e76eace1c2be742e3ebf2a0f16596a52387537e98298c59081cd1206699fc4f06ba835cf7dde45e3d891892c2b34b0b1185a3c85a83854ce8ce3a03ed20a5ce92e6e3275b3aad4d8270b36c2f480ecf76e508431831a92305cf4a957c6725aabeae0cea44ab64e9a27da53505c37abe5719d680f6370a9c25c423341c0b01848dd3b14bec007c78f6662b8c2917d13292aa7d3be2ecc3ab02056af9dc0cc8ba8081aad524a3f6abf8b09d4403bc10100836a36d31d12ec102246e517383f04fa13683d00e62054368b398523df432db798a77bc454054e94595d1630c98a1f512df12f515799089c43eeae705583812b7392a20971f7e75e962a4248c9edc80141675d54b117a9612ac6e80c8e460945d254eda346702906cadc1d07973799e0c26ec0d5c95a6c5ed76880543fe8b9a3498620b90615f971455286dff789abdffb18e591c1e8f09575084ce2843e487ccc9aaeecd6b0eba5c260237426866dc949763c6c0ee6019a95c2caa0540f7808c26f1a7648a104001a88f53ae6b20926f9a1de6e1282951b61f22881aca93cdcb5a05e920a28ff3a191025238ad645467bd42914f132527c0803306484837a1d3e27861c9c07ffc505c5cf0fa3cd0ece5d4454ebfb66c8eefca62072d6a14c81b747e5671820647b8a23e84fa9991591801d3b4fbd904f280712252df2562c6b885fd8001496d34c856ab9ccd83f6ac3b87dad7102832a6220639dcc9df479a54be2440d21cbe2c8788238d34cae05d0d5251af0d7b1b71369f10c5fd1d2b3772d64f067ac9027451e5d35772dda6c01225bad309459d1b58dde77f72f58408f1258882c510b945d7b45d1c6187153bfb6882261ac24b5b811d8ded5fc6d877b94d19d8ff9def19e4a1206d0611731b71d9d665ac4191491af858a1ed5a139e245bc9a96a8677149e994f8a4623be3ad26dcb196a2a85304aba819543949fee8795d5727effe6d028798668a4696080b1ffb7a0d9eaa9546acad9e46a46d891c4b4c2c6efe4ee0290e707402d2068afaf102b19681e9e1ff81df036bf699b8c40b9ad4d92b724008b894ff03252aa5f1871acda5b5dc3adbe63950ce0fcf5636c27ac231c0615c3b87095008bdc499a5b926d1388737e9e305d112746038b7099585411a574a97284b9112ee39f7cd9eff491ee4df2ad2c773fa3bbd842e444c50920869dca9d77d7cc6468e2d96d78d2a97b3fff787a7a8dfe9d48e79ab5119efe5d45c50869115e9f11e5c4d45422d9f796b3925bf1c8ffc110dc435074e5894d04bfa50e5beaa8e8fbf95aa6a07d98b5baf12eb1a6fb6877e307871dc33486fe1c3513bd56b56b420bb672fc048c05441249fe5bb440a8c8b7a64f8897bf22c6c31ace910d430480e1b2b67bb70835c063199a40159de847d086312150aa78fba0fdec086e2ac0dff459895286e26be897e23a02b6162ce5ffbd760408cc11957690c522d27679d5c890825b631477f5c833a4fd502a5958d63acc71901df165d5a58a69bcac027211908ce0fa261ca633b341695088147c0446cfcbe8162a55c8831591874417ca2f7aa3c95f3274cef87ea52dab31387f9047b278f95b0d722509baef3a426a54b07e79a0ae480887bd85278bd8a38f0e76fb3c07706dcfcafdf88d39926d09fd6816cec91db37e14b71c2d2bec5e4e51771bad56baae37cde54848fe0ed263610cfcc2cc0c486b7bf52ebe34571e3deb41a44dd4c1023d67c31b78d0c3194a36800de083eeda23978ae2d6a6cb7a4ffb6ea2ae2aeba6d9a57bc00087aaeb124627666be9886861e38757c08ea3bbbe9078b52599c00f51ded74e5144cd4f09fa9e23be7c5ef2d89d96cbe60991b03a54d4581c3220984fb6a63ba37df7d81cbfa043410562a4c158e458814252bc7e6d17c3eae8f54c9fa0754e4a81cecdf619df3a40e85922a66c354eeafb49525d5de3a4dfab4eedf3d1a2f105734d9ee156e2b680857ec7d7da0f4eb10727dc0cb75b2d1ef8d761705f41892b3cc136204820074968066222715df159b3de95cb62d0cda36c2edf9e765f8d4a13896b356ab65247e9fe6aa4c431203cb4b71929cd8b2fd0d76ab2a69668be46c0bdc4755a29fb8227342bb7e0c15f09fba813a7c5beda9f47440598555f2d217cce37472f415bcca887c47dfc271c6155ce9451c2ac611d4edcd877c6a9892d78f3a0a9bb3100dc1f3d044e41dd794c65d732ce62409f546586c258fc19ccae12b9e02fbbf5d079562131b54701db3d382ab5e6f567961dc242ef0c058172b2dfa048a1d70682c43b4298675ef815f6c6beaff584096de15961c26af824749345819c2dfae78794e191ccb5aee28ed9aec497a413b3131b7a77865d1ee6b529468bddd6f1b4a5469217303dfd0f07ea2abb041f5df9254b0e66b9d46ca3031df0a8d1ee78afb19ce805fe332c4905e566f4fd5c8a134ad4313930b122f3803c290c6d61b9d9bfe065e4fa31e0b9873ad43a0880dba52d81614ede93a882bea3e3e90f1480002230c2403c49d7ca45ee13891a08e9acb472330b24a7c5cb2bbaa8bb035c9ab46b8e05d5899df1acbc02a63d4e40021f17dadea5a7ef22980b06b0497cb3daa0940681f7cfc7e216a2eea5f3cc30866579a7deec11c968a85c5615cab781b0ceb17ac04e1800ae6a0d3c38c01277c26c770745aae18855b9bc6c56145971dd6b699ca03f8839dd026a0b8a765a43735cfceeb6e4c6c0a8ca894a309a1efc480ee1ac7a6ccb9469366e842462427a309c13beef9a1726ee7871b4d90b6dca060288f89fe7326e25404698255fe0c0ab0f41067bc04f97bf4edd96ddd6088c48f7579c768ef42164d5c2ef96e29f9d8e5f31451600daf52b234ba9621d0973273b345a993ef38bb2a7c8b827ffe9abfd7b60e3c431f1f336b36d8fa793632ccd092b01a42d3a9c67d0604f31fcb0fae68daf253a3fb82a3ed2961fcf23a7595ef33c3aee84042e7f16c7887ff388377a3bff1cd613e0a983b403003024cdbb050749eb9f674bca6d90b2a48e213a0cb8f3f9f9deaf67f306539b1668ed566529e43568d8827a70c6a66a9deafda840d51afae0be492fd63d8b85e1acbcbc36531ba093a1cbaaffa26835bd63f99a388085081733dfe94bf0239b664b48792827c801cdd93767d750e76ef1c7b16a37d1a579fc1fe4f492363d1a13c3028a1030e9af9840d9e0cb91de2f16f2cce695fb3f1421eab519ae04e0674f73d3a08545e3efb9ccc5df6b820d7a701949033b12c5369953e25b6b8a67d3903ec1f38a01d09629c4a7b603b6a9615470a64f21c7e1f337b28c81794146e7c95f8ea865a97db7b0ca14947b83e068162d9276ba7e2ed79654b7ad725bacfa9ef52e0d219dcd9351ac49b0670202221908dc1de98c88eedea10b32e005ad21a22791b7faab84f185e9d41851c26f99c53e9db579cde7e8c0b37905e18031a5bee85f8b1316efed90ffe98dd863116c962696611775f562df4a3a8494b35fe6d58450b2612045b415e4588da0376ebc6e7e7057f088f8b665fd6378b396ca1a5ad18a905e82aca38c57867ea0698d6767c34496c7cfae03018ee6edfa2c8cc82d13e9a5a468b69d1b4980d5ae14fa56233079bf633e19489e6ac0518c2bbbd7efc3ce6ed0bba2550f47d5a8253a7bacd295063857ccae6c6d0c6e7e80d60843f50af2883f5206bb42f42c2a2e3ec569b39a8ba0413c283e1362bbeaecee3ca503a79fe22d51193ecf202098423ddb13e91434f51364361224ba06bc2052f35150d7f8828db76889b71f511ba410d7cb13678c16a2b754502d3146886f436bc8e924f769b552b8e1ade689ac0c681badf9ff0cc1533cff769913fd4890dedc0eafe6855b7c021c2e0bceebc45e0600b465064d7e160e0ca584042b25b2020d51aaace74147b427f9e6417a8bf2a6d6a1ade9edda0d80742e7ded1b09475b4d089ff8dbc776526147dea330ac872baee2aacbb3833bc791dacf34a7e14a979b6b1a80dcfdc971b4c61a986c3876b1076cf5571527058d21623c82a63291f6738726cb6496cebd8f3aed88fd8d48403aabd74714e5d85d47361f99337cb4ca17e107e940d2b6d150b69cdf3f7c5755c985cd4199989aa854c25497a034f66213a571ab3a0be8daa0f6f5feab4a361bf9cc54419c751769b1fe3b7366606a3632f43d7118d361d47544859bf748646692ac7af44e90b742c35ca0b268ac91ee869498d2c9960830669d38587c220edb5b61f05c8c49060dbd0237b23993cb2ece19a6bcad14302de63760f6aced77ac7d1dd871ae9d9f07646205acc545e63a8a39896f3596db312b2081790acefccd8f0d7a521cf5ee6df3b2cec2fc040895c377edae9fbe49d0d9bd3cedd75572c2b3a230b93a4761058443962e162e0c433612ed8a1d4770ba2d7d38c8c33624bb4ff95de29530918258b666f4642b16f64c538d9bc85328c6bf28c3a9489c0504bbf5ae5662bf820263a5da0daa5e1fec169bc92692226e5d4c2305b8f1116bcc144103f36eb7e99b728400e2749bbce89186959f04acac0c6ef556cb198ccadaeb3070a4a14598fda017521ecba09c283c9e510cf04042169f0ec1ce2470d83924ff0204e13905598e001f57c8e79023ef08189e96f5d17311d9e916d343ff05352822eca808509e2176501980519193e141741ed7dc75b08954bfe5d92ee44badecdd27f4f74e5403162456d51b7d2b98db18083d42416f6317592cc4acbe0206832daca3467ab7fa648ad9808b64a12244ac37dad6867f3b9045b25e978a5bfaacc8c15b7cbaffa9b4d5580d50593d9ee6205dee458def86c90b4ebfc9e98440628266195ca5ce1a52ad7512d8ca492c19aa99beafbe03fda67beb3bff717fa799b4505b00db8c20ffc1c4ec8fa44c5b20d07e22fc51704b9fa1a580721e59686cdf8418793fd8b4d8faaa67c77da6cc6389a320ea21624326ece204d6bb206cfddf6aa6d7c42caba14981d1bbb567667a9fb2de6c0d5ed222365643e3073d25b941132c909a830b5897b99c5491eca4d48a8a3b9afccce4028439a81c221f28fd86ed82b2b10a30359e458d95f458eb7d3bad105379c1c95250a77982a8d862a68d8aa74f3abe35794bb24a162f728fec9420dabb02159be564c437b3b0974527cb647f60162d680e450784d23632858c3e39db5d53b05cbb43551c20d3463965c010d9d9f3f170f9307c0c4c71bb5aa8d9b2b1e8dbf6cfd3f317f194fd43273d9d0a74d3c661a06463465b551b17230c72382f986a164a27e6c180c89e7036c8194c9ef83da7c4583241d20c15e957b49e6e75aa85e7dce02850307884585acf52aea314c7669d4df92d1ddfe692161306405dc05acff63301619b9488a4bfa5592759650862cb6ac3f7d9488610b7c25ab2277011327f952287599c896e1277f5be06f99e4682bb40c38f23fcdd0c38f04d53bb2c98a94e334d05898449b76e0dc0cf661d73f20b2db5e074fbbad364ed28f431fdc6e436e2d4a2472c8a715ba3b53b3c9257c263be596e0322b74215188268ee578303c12b8200300bedf80e565c61a3a59c27e8a0555e87fbf5425bb542ad4ca080e65c88e413a89eaeb0624919cb9a5fbcf8d7c92bbe742332bdbe58f5f308951ec789e24fb300dbab8f18c165e15bcb41817761c38b4a1bcb29d79a80843ff713c4d690bac22e5f5fe71de095272336f0c2e5024f676ff75f3906ee835f74442a31537da726f185dd2fb82a7018b418892505b7001d4e20aea29d68f9d2999d6ffff7b381111fe324c7b0ff742c0797a0bb0f0b668ca28c0c2e5b835a2027931fef51546500dd06be076bfd1af4ea821f6c6052ac18673f771a83abc9781f0671cee9ad355aacb3de0b46bdde033ed89da4802b7ad37db6d71bd03fa69aae2b3605d8455de57da29a0414c494ef89c32937810c51b16ef3ab68793e87c9346ae55db2b0aa2f0573c40c3a0153c166246c7ac429624856b10c2fa87baeb097d3e0d1ebcc232b0237cadc1602283b5fc1973c2530d5e9299b45be48cdddc48263c9177893c95bd66b2f131b1106f15e3b50b649c041f54604c74e845fd953f0ea7ddc3686c5771174ed8464d633607236e5c90e6470ff6ae1f40ea405ea9545de41ec1a0a14033cfa139f1634bb0a5e560952077341c1a02b9a500beadab4898036798f10b7003a350bf279446e58ba8f5aa7d2d800c5ffdbf289104698e2eaa8b4f15d02858684dda77f802888eb23e5c4a44614a6873d0bdbbd520dc5f5260df106cb00164acc783eaddec3f7379f43cb8f990e4ae41f5fef51acaf9360db35a206825266fbf5b9c4e19c997228707574b05ab4b918d6864ad1ee1e5340285a9d84ad02e30d2fe186aea9f50f7194c7e287a15638e3cb5fde8084a2d9bcaee19c9bf0cd1f263ecc1b762ab70ab984cc2b5b03bebe7ad9c9b1df163b7efbf0de00ea022b04980ce5d1e357a8204f7bf36cff0fd08261aef4ec9352c2ca8dbb3708ccede0b32036ac9561dc9f485f5758276ffeccd8f1b5d7502488618e615d60b67a3b4095b8800323cc4dbb65e6eb350b3b90e0b6026cf082c581d063872757ec2363eddaedb6a968d1c8c1e1ab2d76a1ed22d39e660daf621215e049270a8215f6ff50b8af90f5bd23978206abbad685514c2bc0b4712ad417e80f050c08a67eafd50584b12f2be852664c0c9d76146f1dcd6431c7a1d9d9e83dd6184c64500c23b8c363a1e8d802f7b9be16442e16826ad8d32593883fee576fef1d6a382f21938f7edec2d5b34705af4afdcc9d046f2ecda76410b003a7be586f59570b9373365e38be0a457c5243c23038be85af100aebf1ab8afbfdd1577e0e2e9fe1a9cb2ad9ba1103cc4e000bb14a796e0de25605397a514e0ca1fd8fc3eac9d473fb6f095212d8b5b496460aa15e623d9ea3af738aaaeaf77ec91bdeb426b84823a8eeaf72e52ffcc5ba30cd1152cb7fa491e80084cb232d03cf9c31e006f89a2aea27994e9c2987612ff04afea93fb1e8a6cade0093b4ed121623211b106ca50cb9f6cf5a31d957c82dc09b06adb5c2021ab3435e24c74392f9d49b65a42bb1b0c9314c8106df6ce68b3db390ca5a723afc512f891f34ef06ca99f9237816fef1b1a055523a45ff2929f867b26b15763fc0c794e8a9f6161c5d5dfad52cc75fc517de8402340f9ca148143c7e23669149d584a817d22a06ca1614672c05b8aa1cbf7f880a08981ddd08396500b816ed308a224caf557d2dd4431628c64db1513876366133b54fdd297bdc6c8dee1e55e48c18d5e25a5650bfcf649386cb42aa238da0687d0a437844a374aabe72f12a3a120ff1c0e918839dfa8567fb161cea7afc68c78f86674a43efa402b349b3916af0371eb8fdd318439497131d4651833e4eb6290b5194c8ee2e8d85af0fd8bb449afb2fd0ad2ceb186531e3947fe4df9d4a948f76df112b695aca35e8c86fb48e8ff08ec6b86145ce493a70beeb87547d3a07b7ee2049b263112fc3b5358f1f2bb9730b692f6b56fa24558c11edd2f6edb7b3a097dd42ce6a63fc0102c951693775968348d33ca7046d673d1de5c054a5095912a00de18b6fc764d307be8023c955ac30b45786288d5d6c060976ab2f87cd625c3066e252774c94c1650459ad76b849402fbce0b26757086a71d53254ee89a01743008715ea0529bd273a54283993e3daa9e6d066ad38721b8a225804309ce60073ff7f1cfd914497eef4a8b6904f7c48bd5b5044283c3ec2b0ba0f959701dba383e47a62d14dae68b3540039fff6d74ea7b93a32a70131088ca3309ac3e313178bcf29f081f80f5d441c56768b187bea11c68ecd7fc9cab33072e4dd4b33226f428f2a39dd6dabb52e1d9b49a03d196d5552ed143faeb85d0d9b642f0a75ae890b31f2ba9cc17bd0ec47b1d3801a00b08832834bc871ed39e398ea4fc022e8e42f4f43a22c6230c9b0624785c8bbca546b67385d6e173b77ebd3b78164eee80a698030e2706ab4b295ba075e10562d071871d04a9882555a5bab5029a4d79a1eb4c9b3ff1a525802997d101bac39a3880f4567a25b898fd82664ecee9aec4240c29e883079e548f5d2f99e2f893ddf39170561b0eae18c8e5950f1c7fd0f2d266a19a990810bdb6779cd3cd95479033e331a5b8a5cf72633ffc0caf2dd3e0af0fb109a9e2139a654297fb0270198a05700c9d92e068d77644e7cb9f7c5ef9666f3889017a901e7200e83ff762e1e5782854331d4258528e8b92ef99a5a3db48f2350fb28763b258e370b5a461c042612030cd490f74ac05d74713a2579ff3cc868fdc4b90921718a9db2d9f92e02ea2c42e0fb9c10503ff35874e137e66efd502a7aee11a8a94e62f009164551bdd4987e675cb6985515b50fff33c96b713da97c80113ed6d01dd8cf1a463f4d4373604be60b574f19daf2a1f02346de2f04d0aa13729710da6d422e10f1aecbf808039612332d663ca242130a03297474ce2480a83c438e36234ce2950744ce4a0316a2b2f905c431d90cff1da48f8f6889debad7942354930103c9d0273574e9cf82e3da28162520dc877cbf6305697a2ce6dca420c1084bba2708b65b2e2e9a51703b9917ed6eab53d68ddca7d53d03bfea05c43daf3ff0603aedeacd5c03e4bab3ae6f0fd21101a9f106b81fa66a5f19b2431f7ff7ac436caec967b4be7ddf61ce7ce0c870f88a61e7cbae008dc3d1cd278612d562e76ea8918702475dba130edd7bdd1a893b6d21bc60ce27c8359fe3d647ac934453fdb33c97fad83d0716704df197c4418e85f4a48fafb6286e18b673a95045103b38e93d69f4d14a721f76b6ee5a0a41a1e87899bed465a89a7fdec0d58ed8755a4fb9e96dd13981b2895636d62245f971996ea16a4c13f73879d7d3f9e86050f81082b4f05308699cd236d50633e4f201862acf93dc9fbbcfbd3496284ea9e4560583802ab24b3cf04faa6e1f21dd06f8bceddce0359deec095ab8a33d27f8158fe809abcce2db3b0cf827ec03e27fb2c58523b6253fa27142b4d86cb7af9083000440010a50000500a20550d0bba9851e74e0f7172e760c252c8c6addfa855d61aa44f47a088300593eb2f05db4481336099ae56919c321e832292ab03bc27b32af09c1c2608cab6c020622c5d9c6878bc29fb449e2c8cd0ec55c922c0e4d84f4dc093c0e7ceb578425b091896337bc351395fdcfd54bdf69e34f18e4249732b3b4a303af459926b325dfc9411671e8706770b4ca1829026673ec906261f03fcba6409cfed0e33488adb0b0d752a8423b556afc8d056a437f2295019a22d121898ee96dc5239cc0cac8cafd90135f5bc5e18480fc1402160494bedf33ad93c497352bb8f70f70ade8e7e48c8b741ceef685ecae4ddd9c6582d4366c1078d84bb73e482ecf30bf0d1eb72363f8af67cc7028f49405bca1054d4a5d9b6167a05ca634c16315f24aa6bd14924e969f85030f820ba037f114ae28465ab3722cc8f715416e731deebcb358e0f44f86820cf391fefecfa4c0a05cf2fc0387a3f126305dc5d371836c20b0c3431631f35a4d9564f029d025cc121fe18925c8662d802e46c5e0bd98f6645e0c2e04af871cdce4a93724e0246a7c2f3f923ae3810f5171846c8071443d7bef7ac239d7cbd6e2b2ac4af1a55e5fdfdef1ddb71786963f0bb48fb20dcd3c551419b68701d192f7070884681bc3b649d97d3a5560bfa1907e966c8b06fc5bf001d31422f6856d0a39ed059c420f33732e21d4aa31a6eec76aa349267a8932e62909f4c3c93944065903af4e20d6c2368fdd64faeb56df35b8b3d08c8f974bb0d4d9702708094c7a58b1d9066902b109d0d0697e359861ee068be6849605fc154b7d08d036481826c6b70f34436b235bb8d5a7b3562697b75a2629eb0f90648ca91b861c3f397da87b8f754c21cd346fa3cd4de0945a05dc5fb828e0b0ada1eb6c7f5021878f5c5f5855d492dd5241aa98f599e20da0250c416709aca992ad11d8e75904aa22c152cbe25ea2f6ce93818cc2edc5c515039c9824d24afcc0ce97551595eb27f4fc42e5460370fb65bf1563d3ed7ad3ce341a71e400095489829dc5345ef9f13124c794860656209bccfdc9428ac4b76ed4d97a3a563db5d48c961ae4d8644b4343a80a914fc3a607da9735f07c84433800e0fb9aa6e24fab674ad35c1ba7baa2417591270f7e5b345dda844871729626c05edbdcda8bd3c1f711f4ff8a103d2a76a4b78f653e01688697e189c309ba340234bf0c51679710dfc93be009bbfd6e4f06aea76ebd54e3721d4732d164e97404bf970f1f44be0920e7b666c49cb7f8bef2c6edea06944e60271907a0ec4ee9ad3dce4a214d7fb4595b5c27758b77a3e2493ccb4dcd506bbc8835c7f37280187051b4ea20f39185109a00207d947e85e3891f1ed53d402c6c5dd7909c0e3fd63507dc0f53c198e0c0da7b7dc3da4bf752181d56470275f821f885a82295bbd1ec17f32b4a75d100fbb5827d3f6da59464081c9787817f906137aaeb50b06211cc7257c3d6a020edd4a07f6c530b8d17f7407664b1a8871cb73c909d1b64d2b603b68f448d19336ec7c83164c93d6063f0dca971b7656f0b4a9b785fb3ed41359a752d9bff73fb496dc67186c5e735bc124060a920fc28bea2268149ef7913c4cb2c71725d70c33245814519ab75d494009613782dba4ba6ab173b68f331e03f5802c5c596310e40bdcfad6dfa7bde95db693b727baa19a5f28ed0d8daf086fe0c300d12b1f7622cb197034eb1afdcd94e7654cbc5418085a37a36c6237a90c661e1748d0da4d969e8ef0fccbec1d5beee2863c24607e9b04b29332d628cd35bdc3336d798215b5da8aaac4d603fa8827d80297dfacc4ffb4c9b97b62e401ffc0e19c58900cf18486a63bda1f9dce61006b006c37b322fb01e576ca0264170c274886440eea92622e18f7bea86c8d85ea917f0f491795dac254980bcfc8f33384488032d079ddcec09d57559b28c2f31b1ba7d74484ab3fc147f9da7ce563f1632c9da51b180835e67b1bd1f02799dbcd5c0d87e66829abf556898182164f412dffc84f489f0463055387df448f8319222141a7e16306c85d78b92c8ef882cadd018186bb02136becaf3df040a21604e1d578b277f18ebb170a54959a99cf263a615ae85ba0fff3cfbf63a1301da18cba2cdfc594e23444369b07fb4fc8e0f3794346762a864694de037ecd3f6c7f8f2a69ac35f8143c6d0d3765ed08ab049ee841a329a8ec2d7e0baf8e6bf1e9cdfb53ea2436a00b061a0c7ea2058670d67be58e97a0f594bc88eabfc8afe54583756f28cf4ec315462ee22c15ae43a18c9e4ec25380eef76d7f568eb88fa221b782fdc6fa75519db34993da6b9b9a1a69b942e796b3db7bfea0ab0f5bc6838a181654dae2ea0c49bba8ea67efe2e6291fa513628f4cd49c4e809244716561e61a9428f1eb135c9e5d7f2371c7b8675a5da77cd7d08059f4adacecaa490ab2343a0985a709c6a1b97731842901f06baf287eeafd88091cbb8c6ab3d99227ab0674566036c79298ae3ef83693abe611c623808b71ac12698038645fcc9cb28f1ddfb951ff50c2dfed053e103289781952ae19b1a3630d3babee7abf558b8bc18a1dfe34609eff41d3e9c2dd86fd5d2f92b340b19e0e7aed497486d8a161dd1b31595187958f83f9db41cc32501714f7c343aa6dcdd94adfc08268ad136ef7caf6d44f8c86fc07bfb7cdc1bb2f6d793054729f6e301b18468825e023fa7d79248e2d60fd86f58b7dbe567a6bda928eded2d357811abc6cf56217eaa972dfc9c95e13734083d28fd9f73dba9db4c88ad91f7cfbf3c1938b5ebf154f39105126e23eaaa10d5ad5f80d301efeb11ffd306125bf52a460294f2a32edc4ce32c2aaf3c75a87e4cca610473ed9e9419ae25f7a726c972fbf80a70063d3f9b8e914d6a64d6c65c2c4c18b4aec1a556c814b61ba0fb57093c1b1923ec689123fe9d259129824e33348b309f000a2b36f7638e39b9ce7e175141320af9f7ae736fe43b68e278e31e5fec06f5f860e132436fee69f1c60ab62e0ac1dbd8b8c05957295515cb8ec3e9b146c31d86d7b111fc87572cfe3668e87ed8a49fc0f2055967f1433f1444997c271222f505e3ed0cc29206768a59c8bb0f0ec9ef8e8cf5371497078e5b27abac19ccc3982c733288c9d63d97bb210ce47e4e5aaed5062cb7cce3ce1ea1698aa15f1008ed2e7d33d2db70094e28522cd6757b41de6b639aec95dd60dd0e25189e0c8c040dbdf91cfc8f0c2fb88fe98f5f74447fc4f11abcf1182947569ffe4c4b3abd632253febe1df0bedca0410cf0d100bd2303801f81039cda17a073df550f4b5a00c1ccece921581d1e6ee4a8cfdea502bb70e134375bb882a0d8c258d0b10142b855c55ad7161f666be3621441f9c68d88ec448acfbc435ab3f55861bfc0e910c1c0fa590dcdab1f1ba544832be0f0b800c44a78fb79a201b2a5764be23c037dd107503975e620fb07b235058a2798d33715fddde8ee09c35674c174494e4816ac8629d84d16f5c831c7aec3b32257461615399c0031529fe15a9019ac50cf38a3501786158512dac140b6bdc380549fe8ee052845e4fa3738381631a1f7fc4c4316f72ba6a1c24c87958019ceee0cc135e7853802a391437308738db302400178c470668383453208daa665ba1abc8a16a42001ece487c0090358accdbf9719a82dfea45b374c10ef9f438b6fd4d9709f0d28c15f41694887d9495ea27907c256faff05824ac7c66902f741ccf39f122434bd90d4be705480573e4ba4da7fb0a3b5f9c0221950fe9ed5a91d04281b0685727f9fbc7b76f749411972c29b753c79595efe5b36b9f1bd65cf60cbfe234fe4d3dc9d3d2f4e9d152233ea2cf86e8d6a98fe759749e8dd9045282de0a3485c1468b6bd405dd94f542d54e048788858c4d510adbabe8cb1da83334ee250b5d7537c4b557c8dc02ec9c3e3d8f75849d239f19324eb63abad0da1aab1764d694540528a0c59c4aa7cc262b7ac796e28707d2fd7d6148ce85e6541896430af8031600aa6974f1c07826d3385411557061b9e2c163efec1687f7e8f6f99f8c04a5ca99533de5095999359cd3f41113d88a572392cb5a38ec442dc251e16edba7cb084a412f8c3e729326377f6bbaf91ab2b191dd8cdbea2c1c4b26e48350eedb59ad4cdd114bb00096feb74d668a8f12f0a24eaf6101b65f07a8fa7c16799397aa90cae8cbd9bbefa3aa5e2bb273f13107fde1d180fb7364d084faf9821b3ede287d9e93e3bcc9ebd352e8d6bb06c1f80846b32b2c292fe2365217fe535b02ecf91a22540619c0febe5b3431e0fdef4176963fcc662698519b752fa3c4e4e7000107af14341f5732e90732841a79ca20e59fdc9e2435cc5e2b0bc5aa410f8ee346e55f1ac6a026b43bb1fae5071d0f872a914ab50e0a1b26e8dc2de3c1cd86127fc8044dd591939d504edeb5b926030e7a34f01c4f537d0702ca31d7ddebec50c2176c2f057d713a816f8f613201418552da69c0852eaac2d05fd5f737b03a04fe5a9f423f06a4cbae97321ede04fd36d13de347de47defa91a6b897a4772b9dbbf498cbf72b719ff102449a6ee4cc4f461ab5fd857c287fe4d0ef4986e0811d3af0d593bb4b5a1481e9db8451858064febfdd3a05011013bc47975a9b4120e9014484aafd32374c8505ecf642bf23792113086cc07961fbc08c49100765524fca8458211a8feed279d23e81b23185c9831be0c088b02f42c4be9ac28b60ea529f345de1d42ed917c5d240ffe2876b15799877e3a7f860e84a0d11efd5cf62563236cff7f16c5c5a2f6bc86ce200a9affd61729edce933d79eb04f1d248308df145bacc14604516651f73fe833baa00d0b0ea6000b0ea90c2013980eebfeee17232ac2151c7d221f7f6da45ac793beb15634294b3f397db7b44b47350168bc9996aebdbcc2372146c85ac77cafe368d6fcb4d67ccca5e330c82f4360b83d66566fcc79310d4bdd7d9011df8b9cfff38b0af0729d9ed219168fe520e308c46acdff70b9ae82f9be0c04c0d34d61f1c13cd0ba281b4ced9233cda1c57dfd1704909e1eb5a204052422a04a648538404a882393116ed3ae6ae9c4d03dfcad944084d8525346756308f51b5b6eb48d0b42734dd00fec4f5a458be26d074f7296d3731241870c4e501ee85fb29aef3fbc0506756d0f8ff14349c53737003283a0b3d8505c7448dfb51b83b1c11d4ed526ceb8c7bb4242ebce4a787056f521021297da2456a2e08b162d54be1f6f2e24588bb093a04f4e39d1cd10edfb44fe795c7888a6c1b8d8507c30c9d3b993e4f9f49448e982c2176252061d89f1bf7a4097d4b37e74b563335536c6dba423a41283cbe98203eedd366fc94fc73eb1c4187040c0b210107346e771653edad21258034013d0c8d9ad696cd251cf5d7b62be4b14db4a63a494838097af121f8921a38c24612dc0c3edbc4a3093a6125221d86bf885ad21b538f309f49a373404067b750821fcdd854ee864a8534c9e412b8ee6ee0f6a996915857d8e56ae8a431df1696fe0a3e909257ddd2917317457c647a2a1d8806db267b5be2a584b613dddba1de42dc079cf26e561c15fef955e66f2de6509373c109a85253bcc4f0bc3a4895a2de2ade9266e6bc561838842435b4302c453f2069e183a514b308c68080380fe71b390108fb16c5f555dae9a04d4674f18b7b059db7f9ab71c2bf6ba3535d664800e7333c38c70bf97948ba8b1fa083d562fb18c04a9f984bfb904e76b6e96e918b552fda4ddc4382c0672cefe65bfc3f4114c8d178b6bb60ff3cc50ec211ebd94313eaac83a5156f7f5425771c303c51cf1e452f45dbb747d081d4534a8b09e2881d1a49c16c56183c0ad2cef504da25541df9989dd75f261923d4212096a3df578511951a917828a92a9a735c9c6b66241a3d1eebd534d64fcf87206db0561eb79cba2df36370485cd2017033c76caa2e82107b40fb9d9a7859438803559c780b577fe68b070921e72ef15710794038d9d116876d220b767e5216d9b545f2118dbad4046f604497ae13eb13110851093521371deb271e6dfa401d34c31f843f4fd4f6afe0101a2f00260d222c01c55f4cb6e5d6087a189d390d02144c040a965fb85b3092b24a075964007caf31037cd1c604c0615522267f941ba826da243bd58860b4574506c09431564077228062e4468b8001de11822556d7b4fbab61a9f9180f3046afec38400d957d380686887c7f2b09d95bee2df796322599021b0c0b0b7d0b37b8cd61871d76d2ed1525e2f6cb201879028a0f3e2823080cd0461a465924d18405559c01f60c4f1c4a73dc733873b9991bd25c6e09252ed704e4de06e1723b40e2725266b8dcb7bd91bbd1956eeceacabb75dc8a00b73eed726aa7524be87037ef6eaf75391b4a28e5a4a1449142a559559aa54414577bedb32e476b1dc0074bf880b50ca62290feec72a8ed4151968cdabc8f24c09d4547dc49d9a68b7c7c563e3e3e3130f9f8805207675c12acc48fc1625be9b6528704b554dec8288411d48331aa84b183181c06105e00c58c2c456ad8e0e533c545897d74d1eddf91c20e51a2307185952d3970161ba478c1107c08428c21198238b18e4f073db8fd335d4eaba183947f779c33c763bf0e7690e5f6e7b04fc8c4187ce088541cb194d3bcc344860c5768c982c393202643d0a1882652ca9081a3000542bca4b8b0e1ca882889db2ffb6fb49021447ada94026e06500004832c54d04312234049831134d220820836a001760cb7bbdb86f9325dce8c42ccd020017215372ca108e8ca97f2a915577e0a89225cf97f83299208e2b2511256a2d0e0cad799e1cab711d336fdc426e72bbac7743953e2c0e8f6c3589165373211ce4cb90f28424bcbcab74aaefc140dbab8f2572fb8b2065c5cf92f37bd6a2d56309a2676e5e4f68ea4680331aebcf26d97239d88010b4cfaabe39ca82ec7e5124624e574e8d8945f2373f8e9a4c4b425de72e3c7e79137d62e073332a286eb515cff4e7a9600f9350a704203b1e8e3add5fcb8af7372df2a6e5f48b52fb4f40b53ab6ed9ecb52e278b463680c175c7d678d9e9dd331ae1808ceb3d4451400184228ac8c11243bcaa902109a52d43c8c206a0f471c2870b8f9121820d5737d22352f0ddfe29a148638b8d9295831ddc7e974d6cb1e104ca6f81affcc221f68bf1d1e518000a16b73fc608197259e8568a4bbd151e9a18b1c1162d782901e5d778ab851dbec04225066bfc5c01e5dfb44daf71e59bd0ad0c94cf2e3fcbdde8333ff9811249ca15106f31c844b9888f182964a51bfde3bd507ef071bb094045651358362ae2ca65a322846e58b5b8f1795c362a82a82add78551fa7786ba231763bda9a98159f37beb95c2344a506f52a3e155ae263430dac711d05d7b5f48a6ffc1922ac2ae3f835fb5816cd2893a807fb44f1f9c85bf40766c58f015211373e12377eb829dd78e42d069192eea614c43d68113e20031a5ca9810f4fcc900215a230634a930bcaf84902a4347ead492a95b609a3943549b762174a356889507d52a3c4af45554adbb0d193a21bbf1eb54d77e357a46e71607ce91df4dad23f4ffac8bcf123110e45391cb1e0c6df9068524771e36f4a72cb8d71691e71e36fd9951b9f16e146ea831a373ef5c1dab7af6934e828bbd236d952c6942d654bd952b69431654dd4275ba24031280645a1f82412cd236a706a8a3021ecc92422925bda269c40d367fefc002599425130d9fe23001296c1edd9af800cd20f59e9c7f6f453b92877aeed53f512d84c3158843c246d93c45b54a88352f65334e83b89b7180c127a42147611d57e886efc49348be651fc89c402222f30e5c03e40d0452974910a061e6ec2817d8060085f492132a9a2cfebca6d8faf5c9f233f477e7030b9c24893275a8085108f2125081d6cb0e508192df8014202145531495442836e7c1a4485e893f894a82a8d008415f61ca65ad8d79f2af691b3da59ab9a9b7e1cd45d718f7e981a6c489b7b686758ea3954a69141c3f2aff8a9aa9f3986265fd0d0c50c67c48003907fc5ad17a1356a500ac306a41b80fcb54321defe38a6218346aa5fc88cab6de2a7e27ad564917ed8c7cd7dc910d70087fc5fd96bdf4f0105f8ec00f116f7f5b7ef87817dc8ef2fc10acc0004b8d847fca65fbdeddd939f790cd6c831a1cfde7b34cd62167fe6c9c0e3e303d2d73c19bc083e20fdcaee123fc66e750c4c4058b8e1f60711d23fa45b8cc44632f0b9fd30b40d8a8d6610e507731b95bd31bd622a5b6e3f0e09b78970fb2923b9fa81f407e917d224b40d7f8a7bf04723147c4600b70f183dbe6b64cf3d22fd78747ac0cd53ed50d7f780d5bdb07de4f7dc237e1883c5868ca4514fb5435f7b8daa76b6d77efb543bdbd7af1f8fdd3ed63eeaa976543b1a4fca59f4b3cef2f72f7cc1ef0a4da5548e1ddba5111bd2fb16c5a1bafab2a1bd3197b9196c7dd9907639a91ad830f5b2fcd545ba8b8b8b4b47b691baa8db5f18e4c68f1d730f7e96a7d004377ed44a8dd22b274ac3621fe46acf9818f4f6531bda48e6d1e9c33991da269b1b93b390bc49651ef5ca513f8fa8b40deafd2712a35047dd365d95f4cabf26e995c71a74fdc38ae4fad7a0b609e7d15d5285dac649dbc4e7c891c95bf38859fe94725f68531b8ebf30efefd08115da98aeffc6d4363d977e2a7a39da443dca2c6a9b7874825f27409686ed8f377659f7bc318b3ad8196fb27f349166d254aa4f6a13dd8f73748ecef1a98b04c440d22bbe3d37debfa16516d7606972e317da242ed0a062ca19663c4901232921a2c90c198a90c1112033b317355002082f988810031f905f875b16b86265891f228cd41005e4671af6256ae5f835a6a85855c72ec705ec51e0db1f017260735c3e3a22cb8def82fcecdd9bd9bc95e6f188e11efe981781be3727e4b829193e80dfb819cfcfa296587fbe3a2a48a6979436ec2007237840458b2aaa80e20228b6c0610c1bc6804113e8ff2d10dd60048819a051a5052f2802881ba000420ac4c801e835fdf2bbd977fc60e3a386583565020ca22ed6c012850e3d866002052f687a411a5a401e61cdc751200d9141840d469c40418f1325a640220653182551013acc65a32119dc1730acd1900b6e8d0c1a5e7383c32d1b5324513183146110b106e8afc3ad1c35b0010c92509282245800fa4f1d487feed574c7d1b1d6daa8133d135c27460e47c73fceadb061cded9ab6b929e1882fb5e04a2d68240a7c318ed3ca3793669529ecb4c2b10d3ba04adb4876c0cf83d9c79f4936f3234ec79f088444a7b5ef225be483869d5ebc976513fbb8712b588acb4d609f0bf1c759405e44e2c1a0d36fadb7184cfdaae6e6368eb31ad478b0f2934a5811488e20dd782bf758366ab2e563132db1cb51b9f8ee54d775ecabf7a8f82e3cd8e8baf3e376c4bdb8630a1bc6d78ddfad20ce7a794b88b3e2bbbbdc5bddc79718d6e393458e88b7184c695f8ce57252437a159ffa73e7aa44521e25c28385e1855ec5dfb458fe1c1b19969ff9398fbdd40d53d33b14fa4f29b0f3f98978ab7e7c18bcc57dfc14b77a66b8010b124c3c397a628903c8c0890c7830c50c7a90848f82d440a30841f800c8073e430801041a40c181d115d09f6bb01f527327e761547b1e9ea11deeebc7539ffb78b81330f504b9703cf344c0735bca5ed5dcfe22d0868522bd76f0c2064e4e3e986e9811438619dc8073a52939808430e208073f3cd86ee4d0c54b0e4b90aa0c523c199238c142c380c8006304328a9a5cc934a6309860c280324410a4019e8881850d1ac4608c8c6332a38a278610a0e460868b2cfa810a8e1f829cd0b0c5100526069f2170900310c2c7835714237810c3103cf0696ac14b2a088829377a58411326b499c80062c710b0243183eec5670c2152412022228c2bc2701245d351c4045195013061832968f0041339283142dde82eed6a9b4fa98722c4ec1045a887251b3d28d944e94d636e8cf159c61d4734bf73d49d97d0c8cc4532cbb0d827e931cb9c5bc82da2b0b73a5a6f610cc63ebd12cfcc1ca3099936a7c6918bd8863987c238ae436dd8893b9d4e9b36938a49cdc4e8508a69ba0447a351d7f183319ea66d229649ba4d8dc382e3388ee3384e6ada4c2a263513a3034b8c7b8cd33dc619e39c7f84b6b4d4d29f685b36e7dd0270696ed5b61a4c4fe66a1b86bdcb77fa5566f3189cf393dc03fb4931893d712ca39a84b255ae3ba1accb0b4c8c8c8d1ba96e842896de9052c7914335a363c708ef7d00a009800006b09ab7b20850001eab1e2b9af9093df3848979cb6c8617bddaca0570ab0cc0ad93931ecddd40181a00e0d6ecbb957ab76a7feb36c2ad75c7ad9c8e5bbb995b4faa5b51396eb5386e7549ddfa72e356181bb7c670d293b9d49381c8645637021999985b6dc0dc7ae3e5d694cbad38ecad3954a75b67ba5b7570b7eea8b78eb0ddfadaad1ebdf5cb6e0500762b4d0004c0697deb005803589de427b9c77c1360447440892d3cfc88a183153f48a366688a27538888904288a39e1d98a0d164461447b09481461644d89092a48b32c200620557506c965073c3c5ccf865a3254f6e4873a3742206f3e573f1a29288c48b305e3c59ee0ba9d5bef0b30ffbbe909b5c5a66e1a83b9dcc2f0ac5254ea84d5c128522129d244a5cda536b53a9ff06425dec4b92f296cb59fd36f5ab558c96f8a4868a4df7f0c65dbabb7fa194382edd2736673cfa386f641b19d9fde429c43bb168b9e18abb41182e57b898c1b9c7e89e4aa55231de94807383c395e04b9c1a61a5320d177977bcdd7991f49f1ffb76d96849c90dedbb0eaa1237915c18361f7bec5d40da06cb688a59fdd817669907640769be6b07088665980b08f6c5582e420a6f6e1f096f6ebc15bf9f8a9b523252141333938a8999f9ff9f49c5c4a466626664d4b04b3e37ac7919548ed13d468ee338771c137470743ae4c103cb151705589ac8010b5ae30fedb1ff91658f7d45b0efcf3e55e4e119daf954916f70749adbb4c77e477bece3b793fdf6f114e9c7788a0cc52fe29ff58032fb1dffcc19cc30a4f961de8dd774cb2054a6617ff681ba4edf433bc3fcec67d861c4865fa4e317999f4a3eb6f256363ffecefc8f67487e3cf1fb53c96790a77f7eaa1ed05f93e1fd57fe35fe374fda89f496466b74ae7cb9d42fa5d77037de52653bdb7f3c43d9b7539ffb789e67eb01e56bde4d4df46e3c127a2571b07ca7a6edd0e791f1d6dce0e8c8964e54a60927d2cd3cfb1a9b6f04923d0efba8377bd9e49f6519533ff63b8c581c6fa9e4733ade52c95aff87f6dc57c45ffb2a5bbaa7e3b1b3b00fa757d9f5e9c57e0cf8a7a28d0115fd1dffed775a7bd5fc528fa3e30232b953d16f05ffed31e0bf7d18e8ac662275cf1f2ad3b44ab5427dec77ea639f2a3ef63de0cef6aaf8f10cc5e7f976fab1c73e1efafd3d20f73382dcc5e73c0c8c30bcd02bf915199a5fc41ffb8a705e064e903fcacebac132fffc76fab3c7e9f8aac17161379cb49252828cfdce33b8d3af9a9f05a59452662fff06ea9e3554a691ef2fbd95d3f40ae3bf18106f31dfbc4c621f3e2e9662b24b4ab4e808a9e260a30df66671b28fad3e0faa7e3ba7e7b17ffa54f4bb6770f362d370b3bb797d516f57383dea774e8ffa7886b6e7917ffaeddb41bdfd78fc513fa1f40a53d15f81274f453fd73b0b7bec19ecbc1b6761cf791a0f7646c17ed362b1979deb253b11c43568b835a3e172dbf48f26db86d28065f494ddca022b9fcea2d954574e6722d5fce9ffe8d7be225f91a122f43d7555d9fcf7962afb542b70dffd8f5abf038bd4e73e55f631c8e09c9f9afff357f36be6f6ad3ad73bab4b015977bd66a0ef3f83d6a95cfe3de0a7929f943d15eb9f4cd270552ea7efbf43df3f9ea1ec79fce967df8ef6fdf1f46b8fd3aa8c7aaaece933a879efae94b3e6538f9da5f160793e187a35b5441fef2983ca3421063479be110ababc7e6e40973742c14e81bd027b7bfe18a32427977f70d9c807549c9bdc7a19b90161fe878233ef801fda35e06705c294f07303c27c0873f9654caa9fefc76426a26189f8cb07f213887df8b82d9dca0da755f8747a25a76742afe4ef98c2aad0365687722b0a394bfe0412e4adb9e42cf9f2e513f196ea258e971f81bc954272f997cff19fbae1f5f8cc255f62ba1288b3e4cd478204d22b1984855ed1d75cc8892e20ced23afadcb9625e8b11d2666066bef38626c0e0cbbffdadd73da0cb4fa00774f9787a409ea19ddee1090249961840978f87270824597cc07661020cce7caf9757bd4a6350078f1d0410c0955f6b134b6788f95cf992d2be324855c4cb511624577e04a213f3918ffd780a96bc65025dba2648b972068870e6cabe00f7902e1e06224b7e7b2f230f725440c85f5cf91c17103217576e9f8adb54dc77425fae3e906ec51894e40b914f44fe0bf2892557ba5c69e40328577efc911f819aabdbf6dcb9bac7e90a24d626367a4cfca32784c90ffdb160bf8bf7325203f6c7cefab1a07f409c85ba41367ee7c2fc6dfef6a1c037fb1c1b19367e54699fcbbd7f043dd373bd5e39fee55f385ef5b5867da0aecbb7673f0585f51bd6a856e87ee681743ff3a9e6cf3c83404e2e3ff33f5c7ec67e117f97ff617fe667be22fdf6197cf118849900832f997ff1603e8727f338bc98bfe1a13ee5d97895f7f20cf32f2a97cb4f804197ffe15682c0823540fb2aeaf17df11854790ce6f018c4e13198baaed76b06d4dbf8d74be65f30eff2af987fbdbcfda55e492f081167c98ff1a28fb3e4dbf02290b3e4bbc078321e3b0b1593d8f01894f1188cf11884f1187cf11874f118ecf101329966164e929fa31661e39fbcceab37d87819b33d5acb04c17f7b0655fea956c8befe0fecb7afc8576468677bec8bd4cf7ef354aefaaa1f5b74cf851472767a255760f0953de61fdd13c109f102e995fc0930185dafac39fae9a9a7f257cd4fb5427ded8bd01fdaa1bf7d11ed6b0bbdc23cebef2cf99b57e32caf5ef518a40c621e5367c921427a257fd372859982eabb57e319e5deff47f7fd15d1be7b8dc1bac3885d79ab46b5c2f6a7ff51bfbf22fdf54ff553c567b048fded53450619cc5e93c1fecd7e957d4df637d9e3bcea977d7f37ceca9e3b578db3ba1590f5afc850117feebb8f1fae308061a0bffb54d8e7dabe3e83d8772aec53adc0bdff4ef7fddcbb8c0cc8542bf873bfd3dfa5b81b2cf633f36a9c95fde6ad9c95bdc6838d47e24fafb24d8b8d40dd728c1a3797184b29dddda5e699bb9496c35ee02ec6ec857408777fba67422eab5490cae1679799536f77777fa6cee2c8ccfc91ddddddc39e6b8110a317d89c139b53d36e94588c5e7811e3528c63c4e8458c5e488f31c6e88584819cbfc598485343738566bb75e3b2186316bf4deb52d08077e589d29cba1cf9923e5bdad9735762501623c372945a979901c15697974853437385e60526d2d4ccc74e5d04e8f540bcddda6b1fcd0891a686e60a8d0c77eaba28dc9269bb92d3b41b67c468ccb70d4b9f71ca8c625ba836d5d036b971658af35dbe6b97ab9259dcca3efb6250311c13a3559a617509cdd863f5594425064bd1c0c1942c2ab01b17df141fd4964cd3106a160331e5e80a6b2f1b4d21e2a2294faa3793a29299da29476e98c26aa02989a2356e30f4c670d4d69086daf7c7293efd85da3784e676371d6d63b8646620695554d8123de2a696ac060994e428b565fc60d4c621609c849a9a1b339e348b2ffd496b6adc891a743447a92543898c25329864aa668c9be356a17d37674439438ad7788bf66d4209c51f1781c0a624dde5246d1ecd5b8e529b9a978d9a20bae1db15c88cddb513b5ca49a67d64c32134977ebf0d4d5fb1a9cb464a8870e36732d8c7422e615d9c68b22e48e04006b30ccb5d365202c40d94bc60b24089cf44c3d2cb463b3461517600e36246ec7085739a78e22cbe9a37239bb031ca1ba68ac2ff94d11a5aee0bdedd95bb9de8b7b277b7f46e2a3ceec13f3129f9363fc63fbf883133b32e337bcce27fff984a592be5e7de3e657777773333dbf0c2461d6c76da86b09763ec2fc648535f36a4ccfca06f6143d47525998b04c4b0d22bbefe9e621f1c81b8c695b69bc82e437163bd31c6b8aa312282841b6f6e8c5582f0951b9b7c7e807a8848060006103e39a0010e9484008631890090809830b5008a0f9600c31854c30da3109458e4831b398c610c6392d28d313211841b677c8ecf61fb6071c3fe6924498eb6b8b1975cd104032878301483a02760d842a71c2861c30f3d4061f4829f1b36518c1f724c305d4a8f8e2e7ddb4897661dcd9820e281924dc0c57820440e766454051580d1932bdd8aebdf14005288b8dd34225d1a35175c762a1f7a95dbb1bfd7ede7b8f443e8496ebf0b4d3074299da599b4dc76d7165c652b42153d3c4185900d53802127ddf6e287090ea81c0125090c99861bf212331d01436e7addfe18a4445c12c64824258c11a93f32e170a568054a646091820518c6a698283c2c61051a601c31030cdb48d8408da49380612fb9fd5d1472d350282ffe30893e1bd349a46322c459fd73ce8a719dd44a2d468649ba7552670182e492744bcaa259b15193b2684b4a5a168e6e9d4ccaa22d2969594e28ec8bcd7558104ad8076e9dbc75faa293c8e4f6e724d6aeeb8a8a62aaa8120fd02ce9a61555c1a460556451150ce908ab228baa201d55396d7fea50e85b899ee4a06e1a2614251ea0272614257362a3c34e2dd451b226ea27594741d99a6d28140aa893f410de40bd04e7e6e6a68190709d76a20dd44b3aa893f41294dd501caacbf147752edc4db76293b3fa9b7a73790bf59cd405b8ac44a385470d8b19d04756eca353d2b2fa6fa698c735b58de5554cdc22e3814b653a2ab193374deefb27d5dc87dd1a40d1afb6fb98db36ad2a69b96243d64d490bd3921617972e87be8ba5418a595caeb9a8e76d3203a2159477f2bc98acf16abee6757cd4362ebc92a16223bc1e01192e06f5aabfa8242ab97de42d1766cdc880a489452b11f0bce3e479ca30ed65db626d26ae0770c494a22a77e6f29422236e45a994ac77775bceb9d9a73a8f124ce024c60012d01b9f04a91bb1dbfd3878865bbcf2f8e9806263f36c2207cb5a38117b90851337bea466b0195a00857ca589131f8cb84208498ab200435eeab9610918392039820a0d5580213301b96b7103cb5d86163f5c1c777e8aca6e5299851957ca942a0b2c746431349140fdc901dcf39fbc99e6be17fd7e2316e4deb937624114106fa9fa619e41ef4f8501eeebff70f997ff511fe62bc27dfd17ee5365f5537533e84941841071e5788f813c294e7777eeddbdde789c764a3f9d5e79bbbbbb8e7b1077fbee48ee2d1cff291c32edefeeee98bbb7b3a045ba90e302e22c4c76981785648e0bf2bbaefbda79f55f3c0ef52fdee9a7e7caf1dca9b2cafdab3ef708c0498003aefbb8deb3fd7d4e77777777079aedeefeeeeeeede35dcc3b1576517e01ecef9a3dce9a6b9bbbbbbfbc79f4da3391ea7b51c1e107fadd647a19e437975f7eec69e3b55f6b9aa8beb85e3edd7e94da0b68cd77dca8b791c1ecc5bfb37a2cb5b170a652783af19ba8ff9d70be65f2e8ffaa59317c459fed67b89f1502e0cbabb7395b68c0ea4c4628c1be3538f6f3d5a6f459f48c1f82e2b65dcc855388995628c32fea0e8c61a638c31d6c42a5904b125e015639431fe608ab4b4d3285f2ab36c076096a971b3cfba9c2c85c923e69c0db8d34a0eeeb41243ceb449408e949ee45578105e0212e86fad3872dd0a2eae5f7183eb58005dc7a20649adee222d6a46682a1631a1d89d1fe34d931a980042142da0286a0128d140d283123b08e26809326ec0b294d1605daac8f23263bc79410a92a8c2053bcc200695216230a184d0952557f460030ee60d95815e6dc9c6ceab90e2f6ed47b9afbc27688c16aca1460caa7c714412520401032c4a20a1a4c90a9696a029d50e52335b9783c91b9c2cfdf6892c9e3022a75352c6a7c0501a52d4a0a8064440a3e70822847e20c20f4af8000d501ec132c604254bb9b931412707f320dc7086f2d0832fc6b8fd5a97d36f13c4c7c7c7a7084cddc6fa53fddfbfeaafe9bfe9ee57a1a94d7d4d474e010d8f908412580a152330e42a5698b5f0b8216fd171e31124d1c995f2398c51a07062898f228a2bff06c78564e5ca0f72e4ca27e2822b1f862882f091e2879f1e00212505499103a11caefc384518571a5171842c5203c8cee9b60a0db7bfd98c3582e82c538e5600559ab81d7180d1effd4574bc4f1d332f755c5ca77ff91fa8b7ffe3e55dbe22a77f797b7ab19f6a4a1d179020f128888ecf698969c791cc660937f5b59f1e092500917093c475925cdeb2a81c2fbca44a3d8e985496e4ad08e44953e94a59bf15cdebb85450a225cd243aced2bacb99bfe3ad777a7939fdcbef78f18acc3cea63bc22433b338ffa223aa8e71ac185f9a9b4ffd18f7ad53682f7daf1a7af918b4d27130c0108f2994cb329a949a0243409fdbf9d6a93135057e7a8eb3aee755c3b4082b43084c80b1d57bbabcf9d6b84c7696e842a9feb50405d9d2ea73e4ed7ed73edd8711281eba5fa1cafd21e88bfcb03e9477d3d79aaf9237c0fb8c37bf9d3efbcfce9e79f3e9ea1f9edd81fe1e39979fb3bbe07acec2e53c5d336e14caab5a56272ed0009d2c210224d0c49b9b26e7326edf078867666fef43b542bbcfc08bff3f223ecf87874b8fccb6fdf8efdd3c733a37d2bf8bbfc4e3feafb516f5d3c06636cbcccd7c8b97831aff26ca43c98c7e1dd7894778af9d3cb0b8a415406c2fc8d7fbd6cfc2be65ffe25f3af532ac92573c38b3e2f31369a6e780cbec0e8bc50f6b973bdbca7e3f2829c94ac1c2531d81e633e2f3d5ba7a254a0079c8f060e1fa71b7ddcf8de8dac1b3f46c08d5fd4ab2899939c642167790d8eb7b68fef12cafee6463e9a42310a798bc1f924358fa4a43aef35997f880b39de5d8b542fde3c8be6518c54728f1f304fe5cab257fdc0522ab44d38855a9c0529fe2a7e4debe397101fc7042437beab9da790b75c0537b677edaffadb4b7b4a0106e714f2bec6c371567cce7379919d3553ce8a4fa693a8f5c8ee9054a691edffc34c5e462898bd110b9e9e9f93f37b6fbd7e6ac0ee9ddb61c486aca49a9f7a06bb3ffd0ffba8ff71fa14ea2b32b4d37daaed553ba74f35eda79a9a0c3688102230b8663e2a79475f09862049503f402aa549424df6930709423c6a9e7410216fe1087118a44ce80374659589c1f156147a7284138485ec035245480b5a329c0c4a106759cd3ed176bcea3b0ff57c3aa1fef4aa13ea719ae6f0ece3f052d473cd3c773bbe578ec7f1aaed27c0a00af52fd5a3be470906e87cd8684858256df2e0b16d800807c4002388bcc1cae77b714cd0b971e5bba25cf94282b4402467e671bace78412af598d89f1e91cc7ed7fda95bb25f90204a9af628f09d2ea8b6cfa542b95eaf1b9fe35f361ec79d9eebe4811d144ed00f877bc84f4161e9afa0fa9907827a1d4fbdbeb173ed7894a7f28aa81ef594c70e2f888e198d074b428d92ca1bd21ee515f13f7d9d1cbda8cff13ba8cff1f10cd1a7df4eea717c3cfd2995777a94d77d0e0ff538bcd49f7ec7fff4dae7b7a23c06e7cbbbbcfd1bdecbdbf05c3ec6e35ec683f994877aee56407de6a95ca753f7a98cc1148336f8ca780c3eccbf5e2fffb27ffa97cbbf50df793d3ed213e22cf9280f86f813939cec0b17c4e0c963b0f318e482e898208bfc27d365009c00a687b68c17cc3da420b9ba834ca1324dc8cc02108951156a9e66f52b1e53051d1336bf36e517f3f8dad4fc5e7e1cfda8e4d3fe3d60f6fe30780bfb54f273bd8838cbbf07f4af7f33c433b4833d7d9ea19dedb3e719dad1be7e3c2b67f96ba98f67fb40ed439a3d835a6a5361207bed778666d60332483f95cb59f11b72c58b413e84295280844cd901d1ed6f81c88c3b41bc3555aa1ff3e9c7ce0af23fb00c9e9a70c1901a452cf1f10129cf906a85f9f477e65376d6fc76e6671f0ff595532506e787747e2ac9a04a7e374c4355dc2009201f1f4d06eb5ffc6a922adece050bfd9225343ae1b6b8a46d24db08b50d3b607be62823fd18638c31c6c8d8eb742b4e11e4e3e313c30e90202d74cb0e21f242d38d0f4386b12781202875fc45243149f4f16cca7e8c31b20ef52fa6d3288a1289184a8cd236164a2ff948d44b3a0605f1d66abe93bb4a5a25ad925649ab98a41feb25cdea0fe24e925649aba455fc4981b41bc84c9a5770ec1f22648635576a5c6d93fd0d093524f4aa378f05ea37e599dff71683d8b7023bb9e67292c949ad9c9113fe72466c38815aa585fd9929be572ce50c4dafb83dd91ecdcff48a312c62a9d076ec13ed15b353c1c44639b12ff54264c3989917246cfc1d221719f387cccccccccccccc93999927c7f8708c390d71d607cb1fd2fb27a83d58fe7e16bc4606a02e6731cb5a8acf0c90641da58c4f621173897ddc5cfff8847dc8d84454e24624ae7fc5b8f8445f8412774c61db5e7f8e6d30208c149fd9267bbe2173213eb6a9b6ef84be9bf6f149b762911251db44f18f45fef1c83f52e996f58f551c75fda3952a978be2526f5a963dfdc2140b415ae8955b1eecfc362d585166c3e9e4829aa7f959d4c905e6ab1635deea61c1500f6a082208295a8400ce1cee9ca73b312ce5534a170263ba1886310b415a681b7bb1d761ec530cb81867c1a4082d84388229cb1a20f635ded25952c119538696ec100688e170312c4d5c0c93282b2cd39694b2c7f031a5e41e184f6676f628bfb6c4157bc93df8993ffbb08f3ffa71517e1c726392e521313786f20a216e979f3b8d2a18b38c38b0e02039219a79e08374b907a53615a2503b6c606b60c2b0e19098eb9fcaffa657f13f957f5f8dd603eac60803a89143a12cb3e2a32e0494701284328640e28c1e70164c5039438d355aa06404c6222b807084085bcc70050dc0a88595293dd881164d820087e0b013b63df673c366bcdc179ee072dcad9cc7727bc7b6f70d7bc7380c7b8ea3dce374ec72388c3e9dd53ff7c7bc900beb0b1cb66dde10ee6a5f4c93f9740ac923474c20b524c8474c1c5de92383fae74a164e6a42fa237b83d48a488e711fa6acf5a366c9ff789a253f4c6da19979810da3932ad813284452da263ba2f2f26792d899211b6992e8e755e24fdb60f4312fde782402f18d9f53396a6a297dd4c4d769639eb5c21e0111028323b1d0362d0c21d23698ac9f5351d1a79f57f9dc8a16f6c1465f30dd055c36fa22899b44c94d28915c7eff78cbb7344b3e922ba53c7265ffc897a4368561277ce847497ee42d076a96f45633354b3e52cdd1aae85d285563579416d9a3b099e4e758c3ba94dff29b8a95b6a11f0adc5984da86a86de851aca265daf7044adb642fe9179744416a2589c50f258e0cb24d95460a1a7e1acf3e3a61e517502e77761b0ab34dce1747f0757f9c669a6903922f96b49217757ca9a152f96deb9f4d8bd63e5b7a155f4b1f0161c3ee2a61d452c54a5caab55a2b6c3f8e770af3fa17835e8ed8b09594986e5f59cad236de4dbda56dfc8b491a4954d2d14c6163f69bc7a3575a426c659bb0a18cdf7c2549da2626099291995737fe357c5d052fd2625023c364bbc76a6aa66d686d2e26354335ede96fdb566badf5e57ff2b3d7dcb5d973a7d38d9e328f0047d829a7f4aef3ce5affc2f93eb9cb72764f9fd991524a6f29a594f64af7397d4ecf30fe971cec0c8fcf65639e464aef30bc888272ef64c8b0ccffc5ebb291174ebca0722b7739b2a7bbfba594b25b625dced6af79f2a99783f18d41345358be1da5d4f94c2333468c9a102ad3841d74e757dbabc9513a3f95fa30c54cec57ebbd86baaf2ee4705f9f318c6bae29b536127de7f1856162bd1b6fac1d37bf9bdd447a75fbd5433a21bd9a5de7c5ee8e2f39d8ae7b759fab57d1a78f749f0ad568c95bb1a959f327b76127ac5bcd1fe1065d97ba5df75a97d335d5dae5f4771edf48d4fdec66f731b11fa6bcdb915f548a4abd9a567a357fa9697ed4b2d436fe2dcb3e3cdbacf99585a5b6c9b9e11ef359989f4307567e1891dcc9bd47244994042d8942f189176fe51ef3b8af2e9cf5629666cd7fe94e78ee2f2ee558c286b1e94e9f61fb709bb67cfd5abd785fba5a5fb8daf254bfbfebd8b3b7cacee3eb5d6b5bc671bf6d5bedb915d5dc449d3aee55f4eefb765f7882dbd5fad8737f531fe32ad675ddd63d4e775d4e47b9a73dda637ffa2c2a552f7ce1d63fc9efb955c29c40c897c4a751b21d24a383da3f7da48f5c7e172dfdd3af5ebd2efb949eed18cf5e615573febcdab7f6715fc85df955e7a2b7e644b6ec1be3944e837a25a5fc29a57b747e53251e0a7c651053281ed1e5b757d83042b9fc114aaf54fef3f6fb97c2bcfd6d11bba2320d0d1516f5f4dd562e35d3365b6d4eca8f49cd6cb507c3befbb0d7be8692dee9525dbeb971331b679f79976191baa326caebb9f4e5657e61f633cb64f6dd5729b3efb239e7ac53d37edbb6e9d59a657372cf5d9671da4fedabf638be693407cbcdf0689b2cfb5cbdb2c1b0294fbd555131a65242cfdc441bbe344a58ee5333ff341ca559966559f6fed9fb9fbebfe7c618e9474a6312a6c1fc08938c10ecf637a8af1bc7fde6f55c0c85e2be90fb8d63ecfb621fb66d5bf7dad671db771bb66d8f6118b6758d6118f6a94eef01eced3d616f2ff6a76d3b615bddb059466d29fdea237da485cabd727473d9a88ba323eec12a6cbf7d5cafbc5072d9a80ba21ba26e7c56e2b2d195a4cb5d8e8b7bf8174ecd49afa6f664fb4ddbb6a79e26140fe057a341eeef7c6170b265199daf79fed4cb947828c420a6b651a5f0c527441fb5442ddc833fce20fa9e78eca7682fa039cfdeda9c9d3146194f7f827be23ceed6934725f7d97773729fcdf4a9fb4fa7eef4383dbb9c537f29ccdb3d2a86d26d7bf9db7bd77d966d19d773390cf58dfa0ef5384ebd0df571afb6c8619a932eec41bd074e9ffde9b3473dfd9edb5ec885acc479e10b977bd4aaf3ea73c779cfe5bc21dcad9fd6241e802eb9d86f5ed813a4795189ec95c5d29019c3ca9f58cf9d5e949e49e766252ad3d05061e7f3571d94902e951e7c2e6fb94c4509110c7c6ee5b802cb547460ba9572d3635e512fdee9f5dc94b778ba4f751eed05605f88bdac1a67db862da5cee2d971afb8e7179ee0ce5ba74769aeb098fc13b0af7ce747776ea8923f7f088f9bf3a530aff6d5c9fdeca9a7cdaf737e2aee3d30df5eaee74a2fe4c2a874b51764f5b6e70eebd9b2975e08b3243c41d0cd1ec7b9eb24eec13c43aa7be32caee9154f0499f24b0e9647dde9157f6a4e4c856c88ca34d5da66f9db5ef9f7cabb9c19eee1afc3073bd31f93c586f6da54d4e142df706646870f36a43645fd77b081437b4e18b10f77dcb671fb74fb31669becfbe3426b001fc03ff31ec02bffffc254ec4ae01efe2e45b061090d70de41093367d890bbfe31336dc3d72a0d31abe8a2861bd6d028d1850c3f506316c1d2326230069686d5be4081c594a41296266144b014890bab3121afb09812492c96c42402ca5c62a913306c2604f6847d6101a603173560c1450cf3075b9cf1c31835d802cb164a5b24e980a2356ef45443c21631d06afba0d46de10298ad87246e8b9f932654b7703448680b0395f1a0a47151b11faa34e5c6848092a64c29079d84800f44d88b432088692a1101d34952c90532b1e606023f3c6916d3a8f473b210e801a8594c957c586b1833bcf841098d0baceab2d10f3160665817260c0d6b95260e16258513cb19cd1044832945d0122741404d6825d4dcc4b7728d80988244a56dc39c7af796896998177769dba8548ad22fec3e755ced4d6b9abdbb8eb318e96b6c4abff18edc9acf5f37d694a65229e76e1e5754667eb9f1c668bd5dca9eabe2efb9fc61d14619335793d0ab6ee95f02c97ce5c7772573e76d8fec9333daadf96c1a18733695ecfc1fe773278111ae07e6b3bf302fbf63317efe71e52c7e9162bec4a46b60d8c83518ab172934ab316d95a2dc7a748b8a57501675c5a7bb3b36b599a16c36b56a5157a65693b26c566c5c9994655b52dab27c5a9d4959b625a52dcb976d1385722fa36a946a75b3f37d6a752baa9249c9aacca22a19d2515665ceba69455532a4a3accaa4d99c5be59cab5be6f1713a6b21eac4868d2ea3146539baa150281cae43d939e589d68db32839b393455d9113fb0cc33aac73ed5dfb1ab5994926acc3b00ed3004afb6a4f1f77df56374e49cb151b72e3aa9216a6a559358ab254ab9b455d99d86f18966119f62edf67dd34262cc3b04cbe4ffac589c5ae07805499c67d6294eb4e5a0cd1a7864dfbe9e2e784d2b64d3b4176f351289b6d9a8b9182ac4b1756b460ed8b4158828a82bcb02a04e9b8e4f8bbbc4cc7b4e7b026c2a41082d05e7b3d50f4d2e5f8bffc510a88db592681ece9673fea0c030313e345413735354112a6cbc11e86cab48b9362510945526294a00f4a1161598c423f88e853195614a5a8a89fa3414441912988c8cacb6caf51e62c1605c50350ad0b1be3955e29f52a2e756440fc011fa0bfc9669f16dca3e7955fb4c23dfa5f906cb5e173e389fead3da744a8144c984a9b1432543312000000d315003020100c0684429158124792acfa14000f7eaa466a489bcad3280862944206196308210000004404666668120426d09f43f86782d7b44d60daeab13b80ca18915127540afcf56e82482041c68aa3e6e6872efb60d2b61739d90212cef6adc711817a6424644c0cebec134e90919980713adb40e1d19fb2545a973211625c1f9dd9d0bb7ca6e0b828a93320068884af376125d667a293eb751fefe71ce6d85f2036107d4689cafebe6a5c935361f898425b6c39b7228499220f26c9fa35416af3297252fb31707af324dd9b8c8c4361de54b89f9035033b0416b1a47a795a29bd6c3ae8d68274c4582c03de848845cfc982326109ef2e3fa85bcc2fbc67684d0dea272de20817f8286a409ab3d0557b93578c7839360086facc4c419eb0c870bddbd7e238600e1c5320797babb86a7ee763882b7335e545d4e2325c2278379d6cd769832010ce78dddbc6894c2a4aba1cef960f5a84bfb5e6089e1d4025aa32cc8728584374f33c7cb865f12a2f019df502f1d7c1aed3fe5a4aa12fc74b8c2264ce37d3393f5951317ee79376c8ec7c2d38e77aaf1149a7df8070ac3494e7b5d20615ec2d6025af92944102f2457751efcc91a3bbc902d8a813037143d7cb311a4b74901b58bf59c1712901ae4b90a0c1c13fbfe7cffb687ef1d22473b845963dfddc968676f075381174d0f79ef5baca08fec90870c4ce43424642b3f53a8fb5ea67740afc19af7a8a999eb14afff52b546fe1c59137b2350ba3aeb304d6c0b1bc38b026a5317d6b36bed6ee033edf6a6233e0460fa35bbebeaf31efeb5b08e774f3146ad9a5038a6227b209c02de10208f6e411501371adcfa8f1ae3955330aa5e4cd4b9b0dc4c64b3b779ac7070a1f19d202b73234d39b41521fe43e0d5b91ac19eefd6d69515aac747aec7e948bc1e0eeab72ad799aa70a31613cad3cb645419707c3e3cfbe7293a45bcfa47bcfbd621eabc9fa672cffbbf0a25f98f6953362cbc533db3309a5d5072a0afb8dd79164eae1fa5b415f3f86111cf79d5fd692c463bab6eb79e2b6705a974b3d65d77a373548bd75b316bd838cbd214679e58030b826b7c5bb75aace08b95a9c4d151bf35bd066717efb6f21e8177193549fe5f5f25ed13632a5585ba4dc64e12e0a3156cdb2032f2feb0e4867abfc817b1737d60db3f1c4d7390658a472dddd02cd5f18e66e36819aef8f510a5f8a15c335cf7b9ec0ad8ed043c2ae3657308d2133f9f44c74fe5db51c0758feeb04ee4d81d38656a24affd6343d191b4c3178d891e2b6b2eb246176a65cdf86b3c4954ac9de1d703f329ca2e6abf862edcda92aa478728d5f459bacd363dc1fe0042a9080b34ca62c5aa1b1a184341b2dcb35718e1a1b36b614a7c51e9b5aa82b876a0fd451e6b57525c61e05d71e719d6d766b0d0c72536ab60fb1cc65f5eb781658a2614d559789ebefc401198c0dadfba038ad1e397979f2c32fdae7a076f85bcc4fbb61a76f1a4e819576360b50780408daa4d49465b91aa0bd8a9e1b17353e4eb5d607c4c1c0bd3554cb21760d6f481affd752872e0250232b3e5960ada23534ea91944090412b07b448c9282606d24c9e768f5a5d9f712b038ffa911eea29c965fba8f59797e20380eae01c96bb9ac2fd720d287e888780007ffb8e0f9394587a2f530685bd3cabe8b2012f04872ecc79c1cf088fd1448bcefdb8ac8571f3c9eeb57971982e1ea7e0401e160819408f649d5caed43e1510527f11ea31978c966893b3c0ef1879636a9c87360879f31d997cc6583b011d20547cce6448ec85d0be6a0778090241cf74813f912a6d1b69d5d664328edf16b87c8d9db8e67942585647dfe29f80212515c54c7e0e0b747a4fc968f95e49f40729f79adbf1b5ecdbe6577e7fc6c5550ba33cdec37bfb66ab50bb7ea174e96f2ad0e1bb575b2ce44e0a6393b975ff2465d34729bf1a1f77cf71461feb3c328a0a12d567d65b842fc467efa607ae2d67ec219860d4829c0019da39a78bf594d9d087451bc4949df513e1bfd9594ffaa8cffc07b2f7ad0e8155cc46fca6d23897b29e6f6a6731a24f400b7f2dd73860be2cad675e56eaa5a68e8151b9a6b15c2c5bf6f4ab6dcb7408d1c337934ff25ba26c51132cb109a9b43027e262110cdad3ac37dce988bce3b3841a8b8b51186dbc7f57bbaecd333a556fe6a5ca3e88e6f228b44333d0104124fbf1b7c36422fb569968bec3266c43ddf9cc7557ac385af3f852e05e122740d7742227930c0cfad505d3290f9f6180a58aedd320aebe6e944197c5550d289920699dc4c3d1328400c616ba831febb66effa8d153b49589948e93e48aa9c46a5a4c57bf8ba63387288801cd806f17363aaf0f640760bd4e7ae89c43120194779249d38c437a44c22deb658667f91d85458c44062df9bff34444f53a9c8372db7e48ea869c4e087068e369f61572f6375957ca46f71ebc6b271e012891b49114f924c2f8a735621c8c99627931d274878a234047e33d6f9018983d7585cf8b2e68fdcab3d741a42f8ec7cb9ae792849899c85078b38e4eb8041ae6db61334c97a19d94fb6f63799c6c2bf0876d368c823cfb8bac652495d1ddc7397ed7f1ad90a9d897cc5e24cfd0139c59f12b8b8ce4178ae88cfd22a705f8342a47c5ae2167e577f2c5ad8b7663f968d9c66f9072fb7272247c4a8c9c5ff32c4fc2f730a65649705d19fc0e2bf697037d819ee8a8b7723e9c1cb8df22a7e0e20f14fb182746adf1b5feea1fabc845adf0be6a847a44b412b4c18c0d7c528e0225398b5580f1d858c735072193b790e87906f27763547a40a18dfc813c0b9c50a444ef7376f78587adf0d63fae78dc3466bee137b7c1b75fd244c51ede5cc558ef1f810a74ccc788521dc1257a0f628ea88695f57ad353d8a2f7a26656e1fb52a2f6dbc1006d09f65212a473cfc26a3d92d0951c2acf820597a611cb53f3f3f99e1b3a0419123012de1e463c45108d91a920647eb71921f146b85a7190eb00184a48c64d5327b6b179cbc0817a954bcb12e5a577a3ca0981dc42ec336851e0825c084b7b9862456f51f2c79bf27395c83f2c99314548bc261c16502ba73296327db34b0a58deb6d14ea37479d62c62c9c0b2b5c5e938b065bca5215044e4a24cdcad80af1364bad59497a928d7328f7a05d3b80784d57d9026f98e7282b461a464501dac660977f4fa970544d064f3409af7bb1cbba5c7d96028b0ba2082507f4c8fd19641b96e1a6e71681f80374a6144b1e38ef64024f96084f9a880e3bc7607568307ce6501a9db265768e49f9e3a62ace54659dcef894807fd19abab61f3e2a5db606af1c8e990d342eb0c3ffd66569720e2cb233a95ccf75332084d5e474669bd5182a56419def1a5fec1cfcdd2a79c5ec7d98ea8175430a8eb762f0e46f4b40aa07f3fc092fa11fd970395d0dea7dca0024ae6941d916383a60f76707ac373e1287b70649c1103e369814844075f15023322b01b292463edf040c33dcf38faa824c4536e75600a212b70d26e35330d97ff1db29694d635b60c706cc9ebc0f2586704f245de667d923b80c19fe4fb8a55c1505668c5d388d29a01653aaf876b9398fd94461cbaaf4b0b050c6c764c6ada7ae4ce524df38dc818e4e9582ff70e3d3d86a067c4586f61a9de5247b6cc55f6b18c9f0b3a5e540aafe83ab9b778462387a7702e201b8c6277a8f31f0814757420dc40ba39a8f78a250e6464e4d69ca70911f61cec9723214e2b49a4116269acf3bc9c94053874305907500566cb3901a24185244b65778ba1da7e7e4dc3ee0e2076c2b081285e37bbe4b1b6d87762bd7318ed05b63244e1b3003ef615797a6651b74772f3b43986683fc36ea16900e62b0247d0afbebc75525ed3314b2db1bec72618bb6e393b7481d0f973d13f55eacc7e283b4a5b863156117db4615fccaca67d09ffddc227acfb6e6cac9712593a05e7611aa209cb7bbb2685703ae7ca6311f4ef8c674db8c33c892fdb2200024d9004dad39d355e91411adb240678322ba9867490ea3525d1a7543e385bbcbe7cc95f69852b09166d2c8987b0e722764ad4296f6e0cb548c5e246def87d66dde3b5b5088f9c5c1d96d561f69e82a1693ec75398bf7275d8ac51b11944f9c8fa33bd1b250b8f8225f19a9cbbe4fb45f879a05edd75481fcd502b3e3b7df7e5eee560340fe0f88c869a04366f1fad7cf486d64ed90053f1013a9a3d40ceab94bc0115c61db8a6418ca09be6f177cde8e63dc946393433b4cc7fe997008a42bd29bcd4ad01f0c14416b6289a9c8b951b9b8545c002b55ab379843b296a799545d87fea458c8cc2b02cf6514b76820528a0765dd888d840799ed79dce443afb7ace6ba4df0eb902ec8cdce3775b6da4ab1466cb30d46b7f2a213ed6a9b76da50b7fd87a6e92e170328dab951da89e99ae51d961161082ff4aa87431fea98baa6b7c922d553141838125e1f069bacb71aebbcd684ba569c67690ea9bc3c719b32c8be5f2b313cfdd7a4be3ad3d843669e875b4a0135585209844fa0a645d62e0df4bc79e3c0e64a88c7ab1003b164b9138c67a4ff7f1a79d99745d3234ff3c352ee60b6f45f7f7932a6716343a0e4f25fd091eca0dee5058cdc10d464cb369add21318e441029baae99e76c353855fc44b70c9521a43622a25999a28593be69eeafae642a70700f085d04aa8f454a48ca17286ac16a131dd05dd265697b929a3ac65cf3755926596eb563e64e387fc0990f31258f601d1d049ae83325057e578a98713111d1561636ce87d261a2b09678c845e42bdb201da9cc122526eefe854a931005a1a052ae7b1e1527dbe67283b7f8b1c0ecfeea15633aca7b4cfd60f8bc65946e65c118a933da21f845aba8dbca7d850a13cfefac1a100ad3b150f3e29e3e664a64bdd53712fa02b7d7a0d191671dbb4a590ed6960b6933fc3308fbb772e8faaac3b28099a9910a2570cf3084b3a8deac8afbd0df6b9590d4f275f346be263540902a9b44b5c8c4a63ed0195ab72177c7ae261c081a77721185d69247d0417761e4326f262788b442071169c0d481619b176f02558792f7fa85c3f287d8826c1d226b6fef98e2e4cb08cfbd752b9b7269d40ab73f4ade0f0cb2a76509d6074437140fbcdf0c5e3da1ec231f67072cd2f22aa44e16e1f182b02a404e4d0f2216a7b08ad81bd225ec62e866d3d7736250c49815459d2878ae717a1c8b3686a3deffb646a5c31ce1de3aeae9affe781226d89d1cc042583ece3f4116d171907dc75b3686726bcaec53bb08d099b58708b80531a46322453bcf92051d22d25194bd34712de339151e72a551b23859ff0e2c698120e66ef13c1f3b5f90ff7e96e5fc1c2145722304b6479602ff7d6646e2991311193f0009e1b91fb0e802d2de0115bd7e06a2436d13d872977f61023dc900a559b0c5751bf6ba0959af06637e3ecad55f184e85cc7b3d66c5cf7b780204d3450e490017a11a721345a2e34d8df4967df7262d008f183c75aa6f58c633966f889bd1cc721d556dd0c9d3119e5f0f17013ae1bc87a4b46db7188f1adfbf7151033a3599925409bec8731ab181ddccb459800f3fa96c4fffa56a52b8efb548e0f8d4ed0b6ca6d72e09d4cb0cf04fdfa25294ecadf5e07e728973808b6692c843827beae78c842879741ce6e4a7df8a74c9c6f6787b18693305fa873b25edb1830b2c7bd0dba8cc064ae97d5222541fc0e3c89ce3446e40bfe7ec3914669caa9d5de53d8a215d06bec783dfe8341c7a77045ffd11cd729956ab4fa7c63ef2045abc11543bbc3504dfc220a440ae09a5adbb3f30b9afb0518bed63a95a2588893b2eca8c1e93d24d08caec2218d8a1cf9e784d2952c1fbfb72198c2487452ef396d13545469eed180854be85f29ca9b61b85da16be815b2ab378b336e17f06d45ccc29803409434c4caf13c2408407b5166e25bf7661758bd846512f57c66206d9a8e75865d5f743dc6f2c94059b7445f1086bbcf7949d3b6ef5d03b979435d753c87edaf03b5d56d0b22129115cc2050e95af93e6f9029a7681ae4fb313bf28fda3d5ad1e03f44927746722b575c06c05a67696abf14df0980167fe2c0054731e1ad95eeae75cf2dd179472243faf9022903f4f26e2075d3c0d2beac132d05999b02fbec1971ceb95018309cd467c0d28c54e372033b6fa0243107281d6e34a831e18306e302525a18fb71340cef46c2675e842b91012ea18824933a0ce14c796afe1fbcceab4896e94ee75e27ff5fc92c7fd2550d746356ed42e46d81f955f545998a0b786535053b35da52747e4006775b1b659581f540fcc72a68049381de319a35055e155225c72912fbbe39b76d7d20236a6612f73ec2d07714714f14a19589498ff2199402eb5d24c31050cec5b4399cae1a52cc497cb98b56620f2014578cae3e9749ae3e3e2099ba8067eaabb1d63b8fb580c0ad9e5e000c6886468e159e7f9ba16355a23bff86589a92214f182b78952369b3fac07812af6084c362c9f9bf4bf33fb916c99986fb6f5992c6df41d69d144c832897ac553763cc06f3cbbf60080221f468168fcd253b64883062c6b20c6c4bef616fa4872ae02447b5927e397ad8a5c0acaea2e4af4a5bdaede76ce9dd38e3512ff4209258a8e27e0e2313d38b0384a0badaddeb81a2df4c69ef9db1743d705222fa2b51e3d147f92bb172e2b657a8e7d7382d4f68d47aef51ca9e1b48c260cd762ded16558d2ed0c207d37c961616b9a22c3f3730d1073b63a9166110b56e812a7b11171a5199ac0dc4aa9134d39c57dfc0d1079e89046f0304b888d291d478ff044a2003e20afefa94a0dc4c6b4fd14c4e23f31fbec8494918a0115047293df8a1ab0aea798651122a07cf36ce6f93af22a215912757d7aae4ddd8b64b1fa504f106098a29d820ff7e8c5b7dfe8ab7a885cc3b574c3509ad098faa97807dbaa33e1746171541abe23bd961a924f4eb68e5100e0bade80949f9a347f2cf7ac50ca9a0ecf4fa8b5eb743c232c4b6e24811742a6025d440a372e453544b8c77113a44e2412cb6fdccf907a61c8ffcf7f2dfeb5ff50a275081aaa7f7fff4828742026aa7973fe955cf3825e4029c3f48bd266d01ec1702ac9b32eb2a0ee7e48de86f52ea81d8df5e3ebd75f4fe5fafd97eaa1374a317b8c1fd96c666fbf71bf65cee531a9a420acda3cd02022297d618cbd1812ffd358e90c0c97a74a77a7a3bc57707084525999c2bbda4e21c7522c5e27e554f295a42a76c26ee419169c1c4fd44c56578864cea29b69c30a55b155c91001dff21915abd78762de1e0bc0e3ee10ef7fe36f8602602e60d7222799d6ac9064394b308ac50cefdc39dc24ebef9a96b727c49fcb7290fe0b964fb9afeb3850970b888d84a4fcee0d2e1ed3a11aec59a3f30b090e3ca681674b03af94c5b14433d22194f2046dfaf6f661d4aac42179f76d9f42b593b34491a3c4d889d91597fd9b101f73a315cba5e8cc86d318d5e7f8988ba18fb78dc84a7fc225debd1b2d9869ff72a67ee70547c1608da53463c78345bd3597cacb9a1d636c23563468948c6396590ad2b69de7959e8045b3f9a3a773c0eb0ca825ff6e8b3a6994cc5f23cc4caa4fa01273897d48b284e32bc74589e19b9756c82f33f39449271a885f37a8cbd7c2195a94c0e55329bc9a0e9844c0ff82854b8d1f9ea4dd9920637ad3931aee40aef74a3372fe5fe87cfc6a0d36ab12e08d0b35ca14f786f04f4c0547cc907a1025f127ab7f5638d1dd8df348a38429d1cc59ac2883193267d7a8555690332bc8310668f14028bcd6594e0d26cae0fdfc88a177ce89dc96ffe52ea2af8e71bc462f3639ca641175adcc56d3e5686e0aaf8655339d50a446cdbffa590736727c6f9b0c3ca338c5d40cb5339686cb72a34865307d5b170e5f3f3c13e4743f9b695498706e0bac67f701174cb16ad5d6d10f3a73ebf1e3c4799bdf038b4788fd422b7858a382140df318768746f439c98fb77bd737e8919587464fe9ffc37572f79cf26351f8bdeefd08a5164b8cd32a70ac98fcba38093bac65f709325f2980886be98258a8b36c47ae45463fdaf56cd6118cba222026b4d3817a75763c92332649c7daaf6c7dc675c3d1dc2d2c959ea06230d96f7e279b59df98c2a7de87031a69a6f5e9a16e78eaead4802229cb9b40022f24958a77a7dc7965565d3c1b7f6f9545dc8ab7cab3f6afbb992dfad5783345c41181754f919d0e69c0cd913af771e707cecf252f9913c01d2ec37f182d9b3d78c87e5c978723739a39b73d96a11d52389be953b11ea2d55817e68e6d9d16adc7c10ab26416633f720a7e3d9aac17c4b624ffa8c499e62ced8627338b9ef38f65ce7f1d1ef942ee78bde651dc8cf49639c93876d53692982046d55a35d83fc74e097e3477372a9e310315114b956f578ff38c8b97c2905f2d3fe9b2e493f184fcd5b2b9264c8f7abac7dd54c4549a788e1c5e7c7a9a58d9ddee74137757afbb3b1b258eec320d074a2e27a27d125a4935a186fd975d7e8b248ee642700d70f33a0e862c63275ea0182a8e9469edd10c66326d44d5b81cff60c05ff121e1f5332774efda26dc51d50a959bcca0562657019b830102a7e04b967865993d7e63652c26196651e224fbd0c2a83563a862394919d3d99f410f2caf311f6f48401dccc1064f2b676a18f6234f3548ba9b31be4f31cdb2e8a2b3449ac8f3eea00f4618c2195cc725dac0096c8b7869a4e13bae8f28aaba90d222a15a448cd6fbfe11fa0598b8baf9b48d2b66f0a2f7ab9cea008da483b2e4fefa08ecf3599ead55ed7066fa4402d4de3de3aa5c4d734be1c2d6378eca35405e91da7a37e730c5ecf71277170439d8bcdd7b49e323c2d7555d17bd3b1e00986774b5bd848bf045fde6bba52ca1894830982eca663d07e8e8f0c1cbfab952961c93d5f1a4cf2fb9f003ee52f0996c52b769d0e993d286fe5ff832ed45c5dd404583902658072689fb26acf7656c902c16745ea9ba7679ed8486563260de40f1992da4bdcecf27e1d7606c896df40d6e440803cdb47aeb999778b98563303d894a0aeb977807b4ff5df0161f75648facfaa0ebd374d23aeeb719f7ef53299449a0125b17b3be763635efb356319464cdc6e88af266c6b9acf7fc6f03aac2c789141ad968e0567138ce38acec84ad50d95908fcbb474d7f839817edba83e26a910b2ee282a20a34a43f9964af805cdbb6fed08e6733ddb0593e8ab986afe728d257ab5f56fefa1abc2f48d3ad8c783307768235f89b924d9747d5775cba567166c1633bbe5ffd8acc2be8b7927dd887ec56cf6b757955e3bf0cabea940c955dcb24787dd702fbf45828f08d1b8dafd27cb89266b983333a3dcdebced61e217e06d3e1478efa17901884d96c9c235a73cb49cdfa9e9004d97c2548b59269914d98e748835900c7964f90f135c677dacf4eced24bd9f226f7fafc0226d1b60a4255ad39064dc67bacc5951814826dbe34ee3e86a6c6535e56a9d73fef30ea0ac2c3e8d0b15168a553003b82dc7cf5843044c5d5c678d1a85d5c67584443ff478d6f726fc64ac323be84fb7dcb21bc6d76dc28bded6446696c978e38d626ba8ca241920818c2d5d59c8ab4b56411c8575a7935161651eb5525bba0d59826c79cef1dd1985afa944abb212dfaaf4788e3de077c4e132e062dd2025dedf9435ecbfdc0725bca12f8daf340cfc9fe30d9d81fd5b52b3f33e8ca2dc2222dc9e0e1232d1bb5371adc8a896e4f0bb0d9455cb4922febdb61389cfcfbc52b4472e2616216c0734025e6e7baea8fec75a516cddf49328c506ba788e4c44abc2301865c78c281dd89bd813847d23fc70a094f0c9c579b6cb122b6fea1e89723ed60e8cebc339b90ca53f63eebdf87ac7ac228862a27aa75d69ac3ee53e7218431fa743d6122b6110f2de4f67d8b8f54c61deb1007f94e1d28e5e0e3306fd00d5afaa4a6f5a9e9c52a77698bd6d0d7eb6fc38af789e8ea38773949f68ca469788414a499d3fd2668b53d2261bf73195964a04d8442b3415e628e8a441d34e2441d03d9a84e816d9856a4c4600f3830e891d06649c7e3386f7d2ac6c9469c7cd56492d77180a9d310e856609ace6c2e600463fd11b60ba843919521639b0930815a6d9748f1165005af5c32e0af4800ceaf0b571d2a664016ac839def7eb3c82960c10afa0b9e2a2ed8379fec9a026e01bc64336c7b8128aaf334a8931c8c4d93d2c8356a8520f4f201659c369ff141333641c86215ecc21b4bc678950055d7b025833f050422c583e4f33ef3d1872cda5be3780204fdc6e74a8efc801e761a8c98645696b60e5da8c79d524bb71ecc6476c360b37986b6b8fdc450d3c9b685e961101d68f40654940e5391a43209a4d8ecfebc0e25d96fcfa611e8543da7bb2d63b1346b2a577ffd6d5b1696380069adc87d7740f7d098a4939e7962a0a71e31e60ed4ad4a9391e8218725938e3cda47a81d1568c8d01dfc37cd368a7a0665c6fdbab08bb979d8eab678a0f475910b5e6fd69ed5b31320d1ad66cd9feb444147cafcb9f64ce5d806fca3891b1246a32c80a7d26e690bf9a4cc9be36ba81059092b3b0350c90e739c0f96d43f22f68994d30500052d678ffc9f6055d1ffaeefd4d9f4001e376e111570c65357ff75ef2d4a987a2875ce1ac5e7e00966b7f18ea7e74716e9da94e268ead611e948280b1ca6197c062c76e23d03ee6cd9072e49bde6b1e284007590724bb2084639da2b5032fadf1ad0064a268c41ea3f6ad38a35961bf4af86fae7e08eae4775f2fb9a6048ff658a7dfb65a8498b799ecebdb6686682feca15ae1ab00844f4ff1dc71d06952ac15a8918265aa8f2b561d3d1464aa5cd45d331ad048acdd539273729bb8b435d6b52da02407c248fb5e8da428cb22c3b5bff66e4708a8557318760fdb93a8db9954209a3ec483bb744df35cba9bd7956e946e8725eedc2e06e9e45870f7ba7b7535801fa18441b9e071bbeb1e1c1a29a9a12030c4d87e026e07658a433c448da037db6e497d3fa1acf3ef42418859e40219f7f0d6987a540784c22d77e0d53c1b9a7a99c7a3f9cc259d86a38d14bf6bd0296bce1f10a12d048013ec36b5e3b97ceff34776335fc36bbd9e4cdcebde7a1cc131eed15d74b2d1a3ecf972062f46e6d1723b24c73d6a812e7db7b50133e2d1a8839e853148da5e993c166056fa46328673c39c760859dc86ed0661387cf506108ca71c3a84e95bf8642230ffd790ecaa580e18ec1577d7c91ad800467e91a16cd24e3120237e5141bd02c2e96c037a166d0144587fbb897ac16af733528df397976004e65c32237dbb4f615a75e608beae68e91ad0bfdd52f3cda3456f9bef0f11765b9e7d995504b7b0436907ccf69973f6d621e200e2620236d36f376924a51b8d7c69d2429cdfcddaf55590de6e83b42493e35a97748ba623ad3d83e56084188d4ee71d70953d49d313abadc40474d917d7e29c9cf7e3b3ff97e0e218e3f2883dbb22d359caf25bb17ca4df03b2c33aac2ba40a53e40c92710daed948fd32f9c0cb49ef467068740688b78fdf54637b5af5f6354809420204a102af1994f3167cdb94729a2593d3bacfc31f1b5f259aae5ebd30bf32e0bccf2de57afd0cffd388dd91495f74d514800bfd4cce79f7a72b03e0ce2805c1b0920d5c97ef93377b9576c1793fd09218e56dc05124041058eb82c33d24cedb541c5b90f61f7034f78dc0819df7066f31cdc2bd24047a5ea0298161c072b5057747388b838679da9794279e866c08f33a0730377a4950d8e03afd5c6afb69de31c191789552efabf64cb579deff8496800e58c73bfc590bd396c3ff59c91046f064a72a942d0998e89e298cbf3d0d6d525b5e2919408c59b6052292c7c491c55e5e4c3d94d971d3d1547f23003cfd9847920ad0a4f50ef877bb3df45b0ddca749078175139616702b5bbfc5dd031c7e66680e460cdb3a221aaad78e2438885540b39f8605dfae681bd18ead441b2ba6fa730b8b7f664fa5229a30579c6c327525a911cba0cba5bdb253085dd6852e22784fb39a9db3f38d19372af61deec61e53df6c0e5beb1208430fb65b949e96726ca38cdfadd5730e63597c02d1239f81b1ba5b010cc827382972a4e6411c1cb6244bfd89e53a6a608b9f38fb131eaba8f7c0a909610f952275e7f2c638e1c9bfbfe495030f76b7f1e49889b4fe9d8233efccc165d5b82187c644794650777e0e7025724e3cc51cfceb98826be03f10c8d886553babe3eea7b1168a529a609eceb94e7bca62adea5737276b1eb67e25b5df0b880b39597614a7881e4ee8776393ac388e56197ad10dfa819de51b06057f3386b3049db4242adf23a2b82b1dcd55ee96c5ab48c8c2d2094251307516c74092cf90d711813627b88043ddbb4a97d1566442556b15ccb46afee1550fcd0f2c470a38779b0dcf10d06f6ba76aa8c887376d8b4e17a2e662c1ef8be2014e1840e79badec2b453850c2c428c740dd4bc79ebab78f17b78e1e61c7b90e7a7dd4ff0b3978f65bad314cb35ba01bd30c87f4b35e9e350963d691ea208cde3a0851b7356b0b1e4ec3d1a6d5ca56e1cb53e7fccafdd4c118a761b4af53a388095f95b4994121b97ad38b0db7a7996f67e47c5e423bc6d78ca57b2d48b16a0c10d9c7e9346a2b2a77622dc7900ba199d67c7707002fd4339a3748cc1c8cb1d9911f1c33f8c66804e62a8d80e0340256f323087a3f82df1f7947e301ec43d321a68198d1fd089cbd782b06f93e4d34fc80f2455f984c17a507ff45def46520608c93f287b44947eb1193f088e60459c76ff76c63e95b97e559f07e28876cd13d4a709905151aaa6bddcfe088b8690a8ad2969899a8039a0b2aa93215c8a499d22c123d06ec81b4cee0d04bc2bf366ad81e6c8373085c4b427e064a2abce759c12ccf31921f4127b975382ff1b39dae2c046c0232c5728945d2b3aa2208f593b6fcbad56ed84cc37c3c25b7c8f7f377826067cff80b59e52556a995fa68ddd43b2cdfd94f978cfe8e02e4c58596328761ec5234cdc351b93b1f1b5417928b3220121e24ad6b9d3a6bbae56e42702027e9e3430401bdfe5c7a2e34c0c23aa6b093eb39b40a84b8d243ef85220e7ad6270241a57edad1f7906e900a9906515cfcd45e0bf0436c3125b9ab8f30948c3c61ac42b5f2990f270682c79f08ca1ccc6982096204ed641515ec75340e17644617d42fcf37538cbb9208369b6373ed1b4c42b7ca915f190ada5fb120c91c46189c8a89de414294833724ff6ae4bdc8e7d8fcdaafb4f8ffd7411b51b4308711839f68a561e8d381c516b5aeb081de61c4dd6b6bf9b3024d90ccabe1892d45b00322bc2cf4eeea6d3db67028119cae463c99fd1414c230342ea217318b828e20fca609c87054b0370c5f726b96026fff6bc17c3b68fab2a244f10a0c36a1e7ca2f14f29aa29d0f0c68db18219a2cf630ed36d64fa75d83caad296418837cd8a340abbe5d5c887ed50eccdb58db2f18fe14e9b336bed6e6f60f199c78eec83d7aa0cf8e212ad766ad364ec1039aa31aefb88dd7461350b9ffb54902e6a7e3e6ead8564184c8476436944d9ff316ce1f8c4ba08c1b1e077e70688895e577ce2f8fca95128091f168c9b4bcc8626def635e3060b4a9e3f51dfac990e0092d8cfe077a9cb77c02f3364743d2458740bd9df749bfb13d0d8a04a7f2e8e8cb834f01a27ad559b530842eee800f29c13c228dc61187931e2a846ec0feae68ac6b8c4002c184f72f339b72530a3dba02a7d602627947131db2bba0feef4b2e68c41568cdfa67d600912cfcc5406b530d97ad53662d8a6fc5943369e2411e686d4843946549d6dee2f040a1e1ae360ca3b8daebd6366ba32108abf861b704923aa0c0130ff8be04a4143ee24bd1cf6cbee9a2414d7c510b1e2021a125096d7e560c34a541de1da81dc10fc3657272d780f11f48e272d78b48f8e0318c68d9ac3034a0768542de4f6ed6970185c348ad238592f68dbb56557020417646d0a9b3a9611c838cb3a0d4fd91c28c65b8f874505f860bb351a37c6f53f7c20c5b263af5e4ea529d41cfb4f21c1afb98a2e3d0635fdf762b8c4e97cb2079c7938e37c664970da12a29c822c08fdc93781c0123e63de0f20bd78e65eaf1939700cf23333d9a0ede4114fa9965d78b2b86fb5dc96cc53a70aad28950146e43498ce13e38e1ae9bb215be2ce393e2d2122a5e7cf480f735770f13e94d5bc5dbb2bcd1ad8bf946727483e22292b6dcb7e2f4f1c34e38f76fe4bbd9ec1504e0b069f86fb4545ba12e3278dfbca6a9085ac8e5a719f175572e1b26374a536f6c175bb44702c624c16cc3b585bc653e5270cc47dcda5278b836f243d34cd63a70b373ca0384f11199d7051ce623e1008f88b58ce7b5a61422cec0b276aa0b8fd33d2af72d47664a19a8b7eb35e2ee5b7f0da07a837701c3b3db93a860711abae50cd0e6a2688517ec48f6381e0ac4afd8bbaee072e04f62b22b9685c07423ff2a2e8c3c9dd20c46e23be83a389410db52e0369cd6420ac8778379d185eab6c0046c423ef7306b7081d85008bf32438c5aae1ed40a61c2fcc6605cc7843a395bed6826b9a158097cd421fbbb682158ee5a721005cd9855f55e3622c3246ed4c50ea9527369960b5d2a835045f7eea16b3602606f5c6c0fe7383eca7b8e7a791e75997cb5594a817f2c839194d768184720a17a5038fb8cf8dafc69890553001bc21c816c6f750a701c436989637bbbbef8659f0432a4a553b53f246f208983209f31d9e38387c56fe600e9faf5370770411b4f12227820e4fe13528d579e8db8f2e73a9e288f17d704e21a770ca3131e044bb0f4e3e48aedc10a049e05d925705120c6118451e9ea7691056778c2929b874d3c603fce0ae4fd19b3069080864331d23625662192bcab00b016be0db9a341d8cbf72f475c14005330a356c8c94cd919aa541f6914836c3cf6d673f1fcb938e574d844ec0382e382989069989ca207a5bd65a693909d675352c3b0163f5b2f2a62ecf9a5e7851fab6c9cade703a11e31babfbc97090b339103967c2721c24d11934960ff5cef12ee41d00507f3b3d973e00e5e1c6b32757766ab76c302f0425f5d87dff2f94e6f018821ac9c49b8a7ad99093ee7915d39b25c9904d5ad9dad79ae44996e1d4458196ce8e23ec6588cf2084d111113f6d72b846b1fed2cb6040dd8ec25623ece54ee662687f13d9e6c161d2f217c71a77a3db8eb864bc3a34f96bb2838b34b167ab687cc4194d8f96784713ac9d179b997b1a80b82a243017e464c66ca74c73902ab07d96f3d23cb5367202371e4d59792dc4ea16550c89732f023b1041c094994a1ade25c4c9c2d45c03029f13933f89e7023b2008dbc5235f025be4de8c1425ac11349231b5080da81637e0548b8cca31e33ffdcf73ba3899919880427407ddb3842dcb335364af5d55031ea4d35983298c5c367af4014343b3fb88f10880942b4a9f2b5e5f45637d206c44f4eb1bec3ef0d6cc6a79cd9f46d8089705b2d7ebf83b3290219105df4f0c00beb217aeac5bf987ad8f43401070e5e701b0c03b20f7cc5ff35815c71f8860f175d7b23d3ce8cd1a49bdb5ff35787471e65e2a257fa8233052f256eb16d3f4958529f73bfa2ec0ec1678d4e0a160c95cf66663e34dd87a351ef8dbda3230f7b40791368916fe7737656d2dfec7ee9b0a108f5c06baf73b5d7061e9eaa556ab94fce0b2319c28d8bcfc3822ab024daf7377ef3aef9208ca988b54022edc8b609cc6bb69429296f6f2aaf88ec8bef7aad64a94b597b562b7473caec240d6de09b78e6ca02a86454094a14f24c6e00a655478cf83e86016579dd6050f8efaca2f43f0b8cf664a2725e831a26333a9ef47a4efe49a89a31a692a375157642cb3ab5feba23e875cfeebab38cc927a841b6e6151946912738619b83453aa93c1843b5fb8d337435a3acb2a3bc2f02b75258fd07fdfabba732e02aec3e85d7b8ba15b878429460d8ee5ac0ae66ae239e247e8d27afe377f143bb6558701800ee23178708c2adaef8afbb24fdf9aeb1503403b2c19232a8c65dee463561165fc810844affbef8c4fa94c1d046c8785842fb6e57791c71e44c29be29b768847aa76408d51855691c9fbfac6d5c9f50ba1ba30b00961bf3ea855081bba43b5a1c5d85ef8cb7ed927ea82bd8c464e696766c7a149a91a4093aad0b3a6761d0b11ba1633ca163a1d0b1f573d01aa8413bea492fce422b2e1bd1b10b7c99a1ccb595b750731e16c6b0acb4e469af3376ae62da10292d5a64e9cd82a6dd7159c4ac28322bc86f08b8a5b2fd9b871e948ab09c8d2cb197d79e90728685e6d6f28cc81abbaa5217dd8f86446351331ac92d9c43ac0e4cb00d0e2c468ef5dc368a12bac26a25b80f647cb6333ef9f6a78a559416144424ef910a2b548e15ae8f8591fb78be0d75b4e11ba8876c80111ace1f64a39c2acc8f562265b5bea914230b7cb9fcb4fd53a97330b6fe568a9219221d69d356103d7ba2eb762e864ad7b0d455aa5952095ac1d55a29c9aae380d079fe5c7e88b23431b16403807b4305d8e1a3194315ef0c1e6b518c23a56e8fb0ca7e818d07c66c87f193847049a9a757d4aaa1540ea1c33b52d96d48615452d34a5ee111e146744ffa2e8fe0ef6837c5897dbc77c641f11605487d864293b3c2e2cd88c21bb8a1d87433a7cf2258d0b88ee39c8884d5d1b152a810827d2636d3368735cdcc1120b3aa4256f8e37d17c97cd81c442aac16bc7e781ea56913fa10389624f48857bc577dd98dbc3ae852f0b3f31efbc91a94046ae29aa3e8066fef38ef416f40b46e0680d6f5987521db3a5791adeb8dd5b6a5ff924f02d1b8ee08122f825c0731d917cabf8413c382c629434b226dc062e16fd05278ca8c61af547c8234c9097d70ec189eb687256f1d97a24c7a97de100c49dc79d5efc90e63d92ac6db80805b17829ec4025c7b8158483056e3de64237fa01ad4e5acf01b4864fe89f821e9b7d495d58571d0b8963b0f0030cf138305dd0351d636ad2029a84384c9d236f72095a2e443ee960dba80e54926fecf30df22c21223d4de0746977363e845c8dc08007d08803c85ad1914629f40b0990813d1b1d240ec3d300af80324a600386a3f00b8032c1ef32f16a4cb5262700317802f10be361e57f4b732e6375b2dbda5599659908ab87b64d324f8303ad315b1ed2f15ddbd8e484d14d918a2713f0b9820d9928b3f6888d8494b85c7d335132d3ee23bc960afe7a166eda663442fb8e55697f3ba173b680f36214283468141ee3837d2a44bbaef15b9ab9ca98de7444ed28def5dcdb53723b5f5a6baacb6d4fdf08b31cd0906447f3393e21070a794d3e8a4c4e84f5d44ba08fb304da6cad487d77ea671ae0a331ba6a70b6f4b9d5762469df2ac504e7e20435a2cc5a6f5a401f234aa767c28ca9379a81e063ca3c75807062936248f77b89a90b34e021468641c74287cc1e160f84a87234eca8a30abe00068891bd3230302779fecb6545d9503aff958b571d49588c36ab3c22d0612f9d5065e8a2f2aff97266ed1aaae7e26b8314328210c08f152bd2ed46dddd81d36a3268d514a65511196a96bd087ab495c69d0a5636e606b5187f0f34a261795a5b68b4f07e89aded9674e3dfea02a56f74d303bf832d0b5f419342cb38b663896ea5605225483eb4a5d6b2a4ea1de9b13ce92e290efa8484ed3671923b8baab80324aba27822cc51450401e7224a4debb6734d2560138612fad93206dea5524ce65e366cb05b804a3b2954ebc9f0e7a2a5e3a188cca906343816e2ae3208650747063d67cc7b6cca1ecbffa89b64e949bb2469c254b19fd8322a397330cc3884c72527bad3c7ff5074f8fabb8b3b13e040957f2a3e8b91e906618b355740c04f7388d81192dc3f64c83388b5cab0445ea1be2ce9eeea7c9a660a74ad2f0a03b67bd563f7c42a55dff4d56719ab52853372eb39acb8c8ef4f7381fd81193cb1612e906d98c06d49f6c3c6ec50bd265d1fae305239bc1e4b14d9d0b396c9d362de989ddb8d5664f25e485a161eaddfc354bfede02c4a5326448f29fe558ae3318a1e73e921177b30329e7a05d12e894776cb2dbd3f0fa493fba2d6cdf0e220d9cc347ebb1040a5c2d76122a212f23c767e87458353ef11226c7c00cf3e708cbc8982459a82ec3fceefdfdfbd5c94bf8def0698ac2f0a8c5f6d043679ef49b0e8b9461b4c7af014bd97bc5588206a4f7aa02b092a927644018b8ccadfc6684a61c043d8eb49c84832f3239c29eb0f76c91bb7b78a51a05b503cec9b83c2368b3c936d630b46ce02581839d8cc644dcdc588ffee847ba83ecb188d3082156a9e7fee41f1d4a06ebe00482f4d9917c85a23788a9fb4ed043cb90f6e407c0a7b340e5d2542d7d47b2653b4cd514c51937a26ec5e1a143ec7e9fe277a872dc30d1ff2ca7c02dbe4029d8ace0eb090003f66f63fee42d6a46f073fbd309c8bf2621d013b9b4e023ef4740d8d724c2ebc6345ff25c07d341ae460e00d0d4241f4e292019cad1b54dc3a80c12882463bce7f35e8187e70e7ebc2601633e00489979af0e2875bec6159d276309cf3ca6ce3f0ca9511b09cfb009397939570b7e383a357a6a6e502ea46e27f14189e4ac94a64daa33cece81ac179d7790e87ad417d01e6b9e9ee725630b447daa4d2999e2a596d35348e3f112925604caa1648410d2f42fb01e8892fd688a5cd9e0399163079cfb7ffcfd6bf2fe085158542d51b937fe5d6406aaea3ef924ca4b88d53ac356a090fca184569bbfd7c460b084626a79c059a750e710f8c2c7ffe7cb196913200c595c25931f6b2ea4fce2800e05401b16ed76dafc6759e2f92ef68da0c9a94d900bba0a416453c2e1dac57d970b9ee5a2b60edb7e213bf2593f11e674c1c88328208c552283ca1c49fa37b6789075570a672c641e9fe1ee613c160253ce5c3a24820a331604e090cc5c2e297987c0e857279df9fbf93668c035ae0fb2e93e0843b170eaee80cd99c6e1f38a6793295b54ed7da5dc1b19076fb944388011e4c0dd7d3546b05efabd670854eef197446ce1aca893a0c288732c1b23e1aad5eb2bda0c92269759434f572838eb64bae1604c776714dd22eb0c109d72984d4a5196436f71a22845307a449567187db3c81cc7287e413c1734339e9c599dee7afa789acc1e72cc30d99a61bc27ead77c88e88535a484e92cef3b914c908831be4e147e6acadb05059eeb16d322c8ad6093a21b784a02f9222cd3efca2a41adb7e5cc0a21056dd22233c4300998dde146d89889d428e7ecaebd38da3e525def79d652af5a6539591f6a492f9f5e3c662eda36f8000c1ab0433b1fe112adeba8930255910b9a2f2bb808bdf54c159d6290956968cd3d798ebe60e437255d8d71d11b79e0411f8e91ee3b291ec12f569c271d2818b72e44607d63444b126bedf878325e27b029bd7281370fe5b163fd6e660e7569ffdbb4af61073d3d5db427ab568793097b174ecfb227f0850265a04937ed594e800e8d25fcc9c326d8e138cdda8bb3ba7e89acb62b622bd5af5204c315815177ebb36733f3b756be39ce4eca14c4c8b8c7d4984198f89fa38b95854fd40d8c93845b3205dde6ae405aab0571e3cf844bdacbfb92c7ccfda5d0cb1c87542eb0a3b380fcc545ac7bbd8e9b5d314cb656d7674ae09387d15906b42887394c71012fe1520313d6606d76f3e0ae46bc5b0d3bad91773121bf3c6636d869c93519e25f77bf9a420b3d411f9b58e78ac5371b1ee250ea0b670bc425f5af3e7cc58608c462f349f39df22714b4a61fe5447143fdf5f95392c95765022d2842eeb436dd302a93ec08352b4410ac85e6cfaaee872d85fa7c50374b9decb2cb8c54f396e993fca9caff5206944048fd67cb5de3125841a1c3a4579f8a066d43a38d4cf6b2606c23a64e47db242cd370b83a0837985b0e4a605329140d43583332c576bc69da61f98ed035e3408bb2fa8474b9b88638b0758671067c663361e42970b8559e792a0aed5362951633c9418345b99463a0ef34dc98051a7a53dcf5b5c8c763f9cfd03e0d09c0374485fd6135d750e8f2f380b2bb98f8143da3f2c0b697d75a75d1b138b00a85302377f598a7561a7c6c4c3b2cb7a17d3bfae8e9d6500d6f02168593e17568ec1e83e8264f3a2d5d6d20e6b4f63e8caf1e2e8a6acd479e2faf1a08ee429c5e1640f410a39abd3c9100e98bd93d8fc58087eb9e2c2bf717e815e97437b3113329675b364cbad4c681e65197678222d3b5822890da8b59ee7079cf603d8f268a90c54265154b736f3afc5e9962d5c97dab5c5398b1007171fa08fa4d686320ffbbc73d209a7db1ef06b9ba983881e13193fa410043fd7639b00bab74ef2b43f176dc0dd498e11e39c98c5ed80c9e8c98e040594d042a7dbd71c0eeae048bd594997fc9cee2f73de78655a6f63eb20a9d1ae5b0a6f83e8a8b0e7df036210052999c439b03f3c8aabbb9957f9f57d3299becd80a8a2eb6038fdbfd294e3a8b0c38ead6db6e9fb6003f9c1ab6d45411c8e6bf44e702380894e3945f7d114328dbb59c527b2ab6ce4bf0d33830645a0b29b81bfd4ab70aed8d803af0a8cd2124cea3975c60c658e4af54ea9aa68387e45e65e02fbdbb0373c4b1d3613079b8ae50980400259b833837374ab52dc33f3600d52df31cea90573953dfb8e2bcc06097f99ca7cd1d0ea92dd56149b447ca0e4d896e2204619538da9a32fb33e437a2eaddabd577ec75199913029ca09a30820469232e9587304f3fdefa0858e039fcad93f8be6d9e28b6f71cbdf7526683e4c0b930f0f3cb8007531a6302cd50805d1359169ae02165ff6d6d73fd55b63992f671b2eb339b87888360cf66d5d2825571180e17c9eae56cd0f0efb0c19958006332136c385bd6a4cbc02dd222407b8be44581808be4056a4c4de667f60c3818ba488f0c0704f12e1b05114ab4553dbb657bf93c3357dc514083b76396358990feb18e6c83d4e73d1f3351bb0df900297c586c9dfbdcb657b05c28c37bed27cdf986227a29145edfebcfd40a2459f1e0574783b22bd1beb8d7eac4aaab5e75a382c0069534e0f8f6574e09cf0b015ce87e6f1e2884586d0115f75954ae1dfdfd2e887507069534e97b524641fa4a05ba12f0211b69188ec9bcbc698e9efe09c2fff3d2c8e3793b87c1791211485cfae9c5cf89bc2dfdd3dfe216f67962b53b3e7c7f43fb4bedbd381874ffe6d787a628fbe11d68a0ecd25bd50982e1671672b029ae3821ff082d6189c887b0c372916970f1054f1b5cd6b9634c0708ba602b6eefa5f3d32c35ac0d7ca1d69e955667a75504f8f0b28a63bba2c8f5499a8837931d979428b97ecbcc12f034d404e540ac9366b0c46ab0c4aa06e4cfab1b4927428883b1e2743334c27b1d46e8151c6859ca8c06c3a19094a0884f5128b36146da06fd2971b30ba10def47adc59e0e15d6986c1ad3d9ebe4e878df5e638dc77dec7144c52b6ab50a25c0d7a479840bcd8163a6f1de965424eb04fc232bd51c63bfc844d516d40520c8e9a4f04e872f0eb84578b7c275992570218abda3e3f49471a0ba1ba6a7609bd923548c6e1b5d888e740360647b85524791e4e792b451a652a422ba80b959946c4d2006d36584653ad058d0266d46033962110fc3c0e1d110a7b8f5a3d609777640c5030c73e8ecce88a1dc1faf59a7e4c3329e904c0ed9321780d9b9b4a1d0d8e7ca8c454436780e336c8dd85393aebbbded118c17f416b45b6c4e4aed823bfd595f5c5cbf4bb420352d9153114e13e3a07a07d6e76591ab2670e09edc3b7d447b0f73c5004c7093a0418e79b2b5c3eb239faf6eef05395dcd509f4866f20b8ab109e3f5c9960f375a3d4d422431fb4c2818580a021393be12cdf226b5cd5fadd57c5d0c865b0ff5f0f305fd4867a914747533317bf31ac134fa1f893ae5833df804163a7011a964c2de62ea3757e4c9cad37211b9f2f50f1560be7ddfe051ccd0b9f07e190704e6dc6f7ca47061a5342ab55be52a7f1fe0793deaacd9ff5733df3b004e8069bf66514ca8b629857ab2b913a0fd509eb01f1e55af241f8cc608f9a1a9688580757b84047c9a640660b5b79245b99725682efeadcc129467db4fed86dbec17586230dc6be2e980b23e67535c200dc17e5057039bf2f086831ca2379aa2a03f63223e2fb552070968c5f466a1f13fc5d7364ccc10b761f7381175f1f8db44253a64ef4f01d8be59e334a1f6dd128d6e5d546ffe12c7096c9c0263d92087c76e43168c856924c69362803c2e650590834aeb11f445cf409f18af9334d0062c0d8b9b67d4cc798915dcbbd26a538b3f751c55d4ea2e0792bbef68a7062a85dc95dcb9426cff5d73b051dda331398e7261f990db382dfb573ef0141283ce98f8839b8e60a72d0e72edbaec04864c03395701eeaa6bfed779329c599fe6e1f36581dbbf206ea702d73e099061f74230b6b85ef886a9c3492c90a5854e35940c904dd7ba9bae836b4a9ec3d2d5801f55b8cf0ee26e9bc41ebdc0e36fd2d64059615ed9dbfebedc96c171db966443df170336842572244022e17544bd43cdc5849304f0e4a7c494055f2fe0244b9ffeaa1cdd1f87839b9b278bf046ff714300608efccc8ab70ccd6b00128a37672a9d3a02ef79cc5678732ccfc092cce8346a82ac053386ebf9052a4da7f4bd85f947b57bfe75a6cc92f5ec3dd4a015e4c203a3e2384b1233cac375b58dcb4580adfb373f8b7d5bdb4a902966a9cc2db5e5e08f378d3c16f3e6888af80d4ad30c800674d4800fd6b77c16fb6860181cd0aff61777afcf22eddacde64839a13f44e3b8f616f99f487ee0b97dd4a9c3ae606691c336a6776a26eefb446ec39cd6f94990282022e6020854b93a860215467f180e00526618b08b646ad3ae86f5eef6c245b25d18f796d954ca6cc2148041fe67c3976be69b4c97c69a86050c13d81e31620964f15866fda75f47540debf2e98e9e88d53b708f78e61855dab0d98c13407080e9eb0238c1a0188b0198af608030d1d783e102c39fd78fc3cd3d57418080d31bca7f0750da91b6fde4e1513fa78dedb9671ba46e27a1619f082b5192d9f53470b3c9d316067440ceddfe6245df8b1ba3c5eabcf273efea604a08fca7ca3cdf86562960270abb6a4f8863097145ca3a974091e2ad89ff9f0627805d1516650df6a86bbfc49400587becc12e4bb033d369360dca7b74c418c2d97a59c86d18f690b3e3c74b7283fa74609c723cd830118303a9e128db93db4ede96459fcc7f5af0cedefcb56a2a6835b7a8caf681dc0ed6a8a8253343b788038a8c6caf9fb540dbc360187324023f066ddb5d768fc8256d74911ccd7ef36b2c326295dc850a83ad3e7cb5a9d9fef42a5f335afc95d1c0d5344296075847a486d597f94c48ead2a373fbc959e4c0327b6a7233ef111385f6ba88f13d57e134f8d55609cacf88237f07037d494a5dab46480334cb95b8b46e2d93e5121e5c63700b7f10fd6e2ba11f84a0e3a504a94fb11a0b47fb989459fd3091da6d6270f545435bd588a020c41df45f37900727eacc1a1c12929d16f6f47eb2310a780aa5bdefa29f3c43d6446c9747ec93c39890b2a516a4c6deafc93a5135c5a7284988824d5924ae4e232a2c896e3ca83b22400dc36096cf93f28124c459fd155bbbd198cfcb1475242880e11c7ce510220ac8921a2fe8cae574d81f7d737c6460461d23b60606a603c02100d31262885602465713d30c5d2b6e2229df3c49d68137eec554febb964daaa2bb8d9b5395865ccab3a5157d2b2924eb917ed6c891aba03b9bc4ca0eaf8cb335e90171a0e83a4c250420326e14e677b9b7d2b265d10b06c9c5af4a29a6d4ca21ddaa1cec2ada31d455108eb31a67103c704a9cb5647932237cce6812657077e46baa0131ce84a15c0921854ece3703aabaee8674fd0d406822d4be103fc2c11f8443de7a0907bf0ed3fed64b6efc3dc00265c22e8c33ed0f94881b4fecc99989db6a8befb120fc4a65127e29a3938f19e0d249208927b54102b8e90e1c7635c6c5a0335768d33cc5b6f67c59416f675a85c5233d088b740d06b4a7c818db0dfeb00ddfa3dd440444d2074c62392d1a137d4923c576debf2d305806c4be3ddc96e3e4f9f29d3f56b04f26091373a63c4f814991bf72ab22837b800d086cc3fa208878cc228d74b92f9799cccc83ccbabe2384b5ab37991a6eb6624fc559bf1e09b5c10623a37a2907cc584a9c058c848b4408949a9b2e35c153943aab806941732ccf30b73b9c7031209dc90d9765454502345b042fbd796cb894df5d310009408e340612bd0f9dd353d32e7b5f841a49adf9be23210fd465020fec87609aafb4a3610e13f56075120ea17671cdd387ca2198f46798edfd9f240f97dc5b38185a3afbe3ac6530b354a217561f946b28c5e90fba532471e1b21b0f01b6cdbaf311867fe2b7c311028e31731209a1f029b3a7d96e9cc03009dba12f910cbb50447e5ebf24a58e42a3bfb429e828b0e88f50b365bb8f99ac42a397c0091a3ffe205461dbdd54770f58f611958b9f164b82ed81ee3290347541ada54daa8160603d0222c4a190c06f1ceff1f46e988a391e06064153b8d309877c80aced464401f3dd54cf98eedfb1e042aa18550d89817b7a6dfac29ae1d5b41342491cafaa5e4cee8375975da78e0ff3eda2942dbea7a595817cc080bdc160ead9fb9e017d978055f3df0fa0cd408276eaa9a25602d871a960cfec0278e0b1762d153b0cd903fa83775e0ec9f6594b9bd0b791efb8aef341d1cd14d61e17541e1bc41c06ec495c0b0df6e78b612fd6831da8b98acb0186398e67fce42e467ff9b4dd6d95ff75d0d26872f511e6ef4d280f3340ba19f9872673ba3e0f38f376a96dd5c343492d880d1fc42984e25de7db962fa0f9f50818868c6c1bcec7d9fc0c818796983333001f68aabde2ec53803219516766f6bbc70c4631f81640d59b9b373689cb857a7154bf35df011ea2cb826db70af3b9cf0adac9efa330d2fd7416dbbd8ac9529316bbb6f527aef45b2577b3699580d2b94c26568943f9245722fdccabb49976dfd0edb54089f68b306e38086b340232d1d2b365ffd2c3636b29142345d295e16ec71b386f182ca9a7e246a4df9259aec66e8f7a8c81b980e6eb6c951a791558c2c44ef44b567799723806638d32704bafeb5cd22bfee3c43c8ae8c8ee006d3a7acb82a92128d3bffa6e1c09d4c4a7bae69be23e7bbd9ec3696052f08c50134ae9085a9cef02a3260e35ff75b11395463938eb214907f7bdee08ac634d9f69c59f4c4e9c040db9035c26494e1fcfd9e17d6e838c2dcd71f3d9f1cb4fa05c09991830d8872c89e8eeda5fd4fe467cce586576c0f16d39f3b68cd1041121000bfe5d1636427096831bfe5e912136c8f1fca12c95a2a6bfe0e9d31f7901b3ce3fac9719b0576b23fbad62e57e1a4de00b4433c75e18cf8b4b3bbf2a852e5fa1b4b26c85e55074419957dc4bb910e4e79391ea4f564aecea844eac102b73e7a018e785ac46b86ddef2fd9fb7b5ec2f6adc3ab9aa69d661120731ed2322685f066abb68b01726783fa6f0a48b5c7596f0a1e97a2bdd08649557989c8dc62ef7a96fbcfb0fbfeb164b107b02195c61f1a40c4e8c90970e3e49c0731ea3d7436be925ca52cc2cee61993a8de47802ccdc94ddb8c0362c53e4811bf4353993cfba837adb087eaea01791e3168bdc8a83ef9da10fbe54cd79c8bcef8fdaab100b6505fe0e3ff5ea2995844d71126916dc9874d7432ac43e1d4c02dd701b34b2439c7705bbc2c8eda4d411eec608371290e9332a1a62bd1dac8ebe316786d55e2ef7d367afc570a8a72663fb953c91efeecbc71e7f6b27066aec764ce07dcc4ed69f8ad0b77c0ce8092ce2061b6b0d3d102555b63195f2a165b5e6d54fd63de5b8bb983ddf229cda74191c5b7ebc3a1b238301503549da8a85fb42f83015128fcd93308c51b25b182213417fa7d9457540b64d6582947372b888f3c10d85e7c82ece99816baa7c9835964b0488969c2d1f835e9bb30fea2a632ed7cd4f47b7cc99e57c71db45863b2be64dca04415ad001316d6890ed7d481d97dd4166050ac10e437df08293599333129e2100023879cf8711f0f281a006a123de2cd19f74e7f259ede8a195a0d98df05426bf8d241576e08c3eb50436b7bfe064106ed0b8ae0220161db4e48f007063cf2d5a64185cef0f7834ba534dd58e37ee3a9a0875b4b129d0587049fbfbe5af461003dfa8aea37296e84bd9504e6d2b79898e29e1e66245dee06fc8d5e088bec587a90f15de81e57263a1e8261fd47b08e14e97540a8ad22a2b96cda382f30fd64a93884c4a9a82264812c5a63cf4bee51c3dcac0d4c55fd1cd07b77c9d439f8b3a2436bd897c6211557f0db7f3fc53d220c03b58ed63fa20485c7cfa2f9e74b179f26eba650044a1b1c791487dcce60374179ad2481a756232972f9e45933358795eccb22dc8cc00a3a5594f80457f758f25519c129a662417c1467c5e01e0578b948ee0bf544266ee310f11c06ba0e55d22c05cbee693076482f70c7f32d2f600413e273c38ccd868f9277ce0202749ffc62a2e8350c6650ca6d18641494a595de52e0b56223d2616dcd37248f79c768bb2f9f8963d9bfd22cd026301806bd4063f2e21ec568d192f035e5ad258f58174a7b0f5136918f1789651bb918ef56b99b2b472990cf3c051da6fcca1b37cbc403d7d5170f4913168a706dd749c96e855270307ef93bf38c8e00557953de7f5c9082f0e657529559da4cc1853f8a9ab00e1b8d049394c877d284f310b39d1bdc417195deb510e57db77dcc61f4206f8d4915e1b2692039fba5b0676f9b1732ec9ab0e2559a73f288da948738c0e0386b5025f89a11326fa69dc45c012fc63094b40084c2dde9d3681fb85776e6b2405c1bb9dc4d86cdd46493ae874a01d478dd80b2a809047ed5bd95654fd0a7b42dfa281a1f3b4c2bbde33c65b36e32c7032ab24992f8749053669221fa34503c1f60cd73a39d3eea847e3a9e298a0bd9fd8b4eb843d948d5cb5a570d615dbde84136e01940623add1daad2c6d6ce6da8ae1228277a978533cc832e2ddd1a1e693032f414af302c8992a939acc77cc48b05ece47ec64312241bb1f92f5f5c4ac5e7a554b56803aea02c3dad6aaa472506d94b3ce4e7537394b4f8a8664f23533d380576c7ae55349d182a1c7fb65acb5ae0d32109abc5f838e5f985fedf89a481af046e5d13fcb7ec7c1588abd2dfe421a03eae024f0662fa0f387bd7050ecb526cee5ded0d03efaceb00951abefc8500275fbab1d33a664b68bc4d4cdfab6c509fb15f1d9dd9747588c25dea8b7782b06bb81035dfc2afe957b6d0d7ca6c9828bc49198f8f6cfbbd387baa54a1c4a715b638e8a2ac291ec6e51a3523b555ab1f22c119ae5daf959aa1c22f0cd96374ace96f2c68c4025b6cee2e73e2a485f2ecba036c177c4fd85026b37f0581c3fc5530eacb85044bc68a101dda543ea8a217f6aa394e6261ab4ae6c6ddae8b0984e3bfaeb3752e25f61a6639b8aa7ea03a91387aac300235228a406955d9a150f999957311c035f104d4d8d70308d123849a535e9823cbb915c2182662c85912d369003c55bfb92bf2285b869192969cf5c94427898f5374d4dc08109d01d340a38bbcd5cdd5237346558713651cd675d0a146005b9b6c2b9a9a1facb82d57bf0c7b26dede412397e04afb7d016350bda4884b190ae52cd031c2ca2812a26ea92fa00faaa140843d241a3ad2da28a97d9d63ba4a311a1d9507f1abf815632156647059e36b961a74dd061b6939705a209a9ba6944f09fec41d0f0ea45c09f344064e4f9e690400bbf8f01a8ec22b82600e3ea259c7b115c662e32e2a4387b9065b5cbc07291122e80333b17f40d882d7d84544bb657c5d5d6719fc6ed52d18ec999dfde093fa556b40586694ad26a0e35538afc597318b59108052a18a85fe9e9d527bc79eacd6a5a9320afae0d5f845f9fee2622e1fe2de442c53000c44db6958667407597b6a58aa8e9c0042e7192a0ae2e8529eddc91d222031b0f085403c81c200979a68522b272072240733819acc37c384a55096424b99c2377e3880e34edefa4f522a0b48887a2b6d25da808cf1a6222f5c72422f122864cae550118db920457554a9bf79410275762d391f50af291a58a5b76075cba9cca00cad66b7fbc0ac54bf2e641201884036e6bd9095f3077b5826d6a5fbfcd748e6b404f620814c04695a194964cb001b58cd4aee4c03208ef27d3da2c8e035db6a14cca2a3b6d9d02bd62eb20437456ce235f17514c756c830ba9d3efea6bec839749910e8c75672507d5d239a833db851733cad95c05c3b96a82ce55ce73d504f891d6ae7532025a24abddc26e7912b2fd99e13a6af0ceb4758044ac76b6292b34bf6be184126e7e30b342dee687f0c4ae6602dd4ac02587a835224b16efc0b3425266e7d030060c20f5ef61dda3838953d9956279338a3ce125bdfee0520f267f2e55650860e3274bbd667274af4e3ddc53c8b70c4516cb41b925ebf4d1954b928d3d28b1b08999f998ac3bb3fd3bbb132fe5763e5483a5d24ed190a0ac0a3a0374ee1cf43a19df28d235902a93c3bd879b2e051ffbe14e1a7c337375cf2b62039f2e1bd1a20e381c3409b7bbd42a1a503d2a9eadde26131ebaed1ce236fbbc7a36ed5c1cbff9ef8e422411d10e4ec7319aebb40f8c79c1938e3daa0457bddb0cb556214df5faa4edde1f220a78480fdd091e194e0037cdcd15b6c0ae82c7f804ceeac758cb52d218e205d62e03da87ae35331c9277e6f62bf002decdea345b2d4505b3dfb36b705dfd77e9e0bd6a637015ac23c3d8504ee8e0e08ba05ce1781617f4e43a90ffeee402092454bba5569d4d418e40e60fe82a1775588cc73396ab97d9819fb5b3fe991da7fabee74f385d22e0522917f79bba7a93237674928bc07788cdcc2507225c4d8c595776a8c76835b95e3d7d91d330c78fc0ad2cff04936ba69feefa06926b749288bd514313b82a621008608afc4a90d6d92802171383eb611ca01c427d4f05d23040b410a6d14484d9a0b180bcdcdf9ab33db0f9ad53dd4a399c2244b09e9b9d7e9604da35c026b051b835684ae5783d1a2e366c3b8408d52e34df02f9f54cc7338b8baf228fb15f9f7673bf0feb5437b86a624a0236df1988808bee0c2f9a92c6e10b251c40c0fe983e50f3ad951b858a8bfcbb080b165ba976c54c7321f6d23ade1ea346d5995bf61b8f67fbed16a66765c991c3ca63d8bf4648933b972bd19d7251dbf0909a2e2b6eea639f95fb7c94929a19409a37bd06214e5e98f5db3ddebd53e2317012d82408eaf0f3e37d336b0927de5b76650c3f8ca1bbea1ca672986f8389061ea6b589224588777023f60b94a047674036e092511865256660a6d264feeb13543d9b1c56c03703e1384896dac04e6e9ca567bab34e5d8bcfe7304ffd5bb9107622c1447ab61fbd636cd22a25f1285cfc1940414531358073f074b5425bbb0331f58a5b358fcb1e9be3a3b071163c88c1392d32e74acc2e48cf99e4b23ef95f240aae30853dad342bb994ac4f26b5a353c2d53b4e6cb937ad99bbe8a2a3af49280b552ae802233f909e9dc21b60bc4a0d4d5d95c698744deb16a057d4570ede719e4118341c6955ebbdd7671e3880ee8ec2a3f6df13f4f9c2e76c625b2a73e2c7c0a42b79a9bc1490e49cf3ad4a57460c2979435ac7f324837fd3d22fed4dc516198c82964066cb7892e91fb48a47663cb5a8838a804ea1a69c06d8e21e85a66ad417e161654cd4284df51799402d93ce85eed93876a7ef6a1de7363e5a2131d166075a17781ed37fdaa39c574be9546c527811a7529cae07028629dc15f1fc6808c2a874db3b30ea4903624488dcb322e9be488f0b7af611c49bbd5d092c95e90d3887d220630cdbc9a8b876124a09408d393dad975105eef1424c08efe6a0fdf6f96b949fb57622bd6d89a0dae9ddb628720af60602ad4c51ab765239dec220cdd68aadbe453b29870ea24700093649d94ea948c902e4ef94403570f82454ff920cae8183fe6f41ebefbcbd0f9ff59ab84d0b7efb4fb7fc750e9d0ea50b755112f54689ae7d4efdf0d73d7662683d27668648cbcf69abb1ae9bba613ba0788615a5a1dbdd815b283a2ff0b5021dc8c69c83b83a3c88f4ef82bbf1fd8b913e02501f5d1231285bfb52a469ba797ee2541cbc3fec49749a7ef8cfa012b1faf31b2298022684bbcc520e4fbff22a9f334e78d3f417088f54afd5a11071750c17768d6bbf06f48a39c8c8d7f0bcca7511fc4368cba0edbef986e815488a47ee48a34a218820a0eb49754a2a8c847201027ade8cca09e6ac833977e12fb365da71c9aee5b6b7ef8774d3a26962920ee9f10e7407d6809ef2b4f23d94b219e522d5f101dabdb828f44bc091c709df7b4fca31fd2a6204ccd0395043a6c26b6c8c23b387768750e58556098b0c8348209193c89289dc4ae9c2d7092984c043887c178c1463726ff5e498331b24d5d798d8a7d743d761ff779ab62488101b06c8435df40f49bbecff8b5ed18b32707c96b2244ac04aabbb3aa07b1d403148db25d8452b460b9eced86309da6aef4a3e1b0cc6309ca409e6a8566734c79c62b9b374f68d00f8d9e0392c09de2bf7b9eea5cd837e1504b55d9adfbb5b660e8c5c8be33a15ce4044d74c0047646960594fa0324a9d0a6d6d84d504096e6a7e2546eed411b26b7ee6a166a2333424c1bb19188fec3a5495167c5e8552825013cad3f63e9dbc316f7446b3da2d05e1dd4ed76f40012839b099ce6ec2a2e44bad616f847ea7b4a2ae1b91a7cc8c17abf9036133590f92312a1b65841226f2269d829275dea272ba7068e6ba4873787fa24c29ad62a262669ff38c278a225702d85b4333f77fb241348b223f50b674ee3369df76097c849f0875360949667bf896f24874fca3c7d8e6582462db1d7f8623f0c30b667996de808f7689e38fa3e16c9702e7d6673369b8697513c5db4b633d80b400abf3b7c779ac483dfa3d00663d70dbd0eb14d2b110ca0d3d76918407db4c2ad42226e518fdd75b11978a9ff119e247de2d29a4ff92fc8b02be5f7deef50d6481e7afbee590320385036a7ec6d1cec2f65a21ee55c0d00bd5bf9553577084040eced2220ca9cdbb654f45b99182ae192f9d2a629144933d1ee35384681e2196b93974ec4649b179b66375d4bd3ef59cc746e680ac902dc7abc736cbe74a57ed1456c9055785f8286f8ec4c63c2c2ef3609548669a4a6c591d3a5e583344326d4a62194ce860e3fbe3705d1a6625576890aec7828f1391539389580c72e379fb6b75ca4d24cf81ed1156cab379d4ca42dde94c0750499693d06a8e30a2372c4008c437cfb7402d9303b8caba4f1a9ef78ea8c2efe8f189e1f4485de5a9c4c5e72841c4cc41956243925ea0c53844b52e2783cff0319f8a72be5e9975a75f943a9e39e32042fd8e18964ed043e42b222e72c835c378fbc1857e82ee95996199dc113282daa0b85575444cc298074f4b4b7944be29532ffe0edaa1cd99732d2a132607d8603759ac0fb2da306d3472b9b8d6481411037bcfd1b857703df21357316235caa0e34937de6fed4ed6cabc58968dda949616750b7a9bd9b37fb60ee894444433a1de4ce761c75214c509e2e2ea044923b81bbbe2ce8a77959d071e9437753deeec0fd3baa54ecaf9d8fc18af97f60771a0d773b806d9585020ea7799110ab66be78247828f6f4b2309f57cf91ba25d5ee0e3b9db113b870a63a4b72cf3dc7f8a4b490b4fe2a648828bb934792fcab60bbb7fc40ea4c6eb319075898e011f274a408dd9ed13c404be47e5b1dc7bdc82a8806023dee1e32d8e2bd2048d4031caba0fc3084052056c0de83391566537290288776b2bbba2dfdbe7ce28a36bc95cbf8872339495210017d8462a7dc8ce2582f91b82ff2b7ce93e1c33984b8c93994a3bfe4103b35365f94e40a2f1c0d217e14f8b10b095264266c8cac31f229664b1bc3a0c67207b6d9157bb51f4cb05ba1deb669fda2bcf45c4dbb8344cc45c6d94d0a044d73ff7d5e2e9f69828ac8c750d61102d540a0ca07216e80050f8e9e07a5708c515938427008beded274f6d112a887a96ceda03a5d802c48c83cffa95a3589f39a4e468a63319b045cf52b4c2029e8e808805be9dd87ef62b6e6cf003d7916d9b075395c022be4092e3e2c57dba34c75f45dcc681b74b2f30e1093b1fe757f717346b0719cd6b525fe3c971d8214fc78be795d53e89a8614b3f0661d89b6717d72bb4ea9c0d8999a83c954c562f7a81755234f645d89f57c48a75238fc35f3ef756f2159d539dc8509befb62479469a291cd1c06b77e6f762e95ee246b5248fffb2333b290eab76cb32f5007ebff7b5ccb397c2ab74dcd778895828c2558c6cd7451939cf5d6698b3b6968cbd823a3f6ae53d821ed79a005a54878d288958457190ad4535a0ce977c4b77b130698a54a9804dca3956a508b08c1656563aa42759ab2b061a7b0e8d663e1eae7783689149e3667149dd6eaabb740e973bdc6400f14b62c17862310b62e130ce7eb378badb74ab0ef3b1c6d63b1a4060f6983c60a77b7dd5f0ff4cd90f39a7969324c9df7de26142b59d90928a22f7f47994a6cb0c797fee86acc8ef396e5cd648539c0ebd055d3e2be60a50a108274e8947ad4b69efdcda6d39dc2bdb88787a5736658e4998be3743719aaf1c8d4b62e75b412e840908d17bdcfa572ef2b55f327596176ba04828d593daab24aa81e58560540ab72facb2f2d71fc56b0d270e4150d99211f135972602b16d20fdbb7ea2c1d6664c3ce965ed60c637d13e493c4e50a30c4f023f1efe6465c7c264c5ade50b7be9917af99057f465a5d0325d0b408b5ecb7fc7227111716e913c6741e9619ead6356f6ef10041cc0025a0d8a6d5a20ceba9a0e09e4d9322243537309b1fd26c8a4ce481868833f384e1c9fb563eaac903650bdfadb530ef72eb34955e6bd0970904c4d504e760cc89175ce397447c543cbcf2025ffd8ebbaf330fdfbf86a4b76b72db79452269902100720079506f3e9cfd6aac309bbdbb012840c270727a78d263a24726ee4dcb04aac946e7c37be6a04cd0e9fcea763a10451030e1d1c6d38a927c0e1c2e14282db306a0369b586cbf33ccf73edb021c52263c7934a5872782c168b9543474b0a1d3c3a78d8e8e2b1a1e5ee80b10183414295e0799e67c30a181ce8cdcdcdcd83b485047a3838383860d82509bd1b376edc08775c1d1d1d9d1d3d637a3c97cbe5eab98072673cc982010030b849e2f2f0f0f060f0ba748a97cfcb878d25da844f2b036fc78e1d3b32d0803e51bae0820b2ed080077d82e427003f016083081b4c98a66a6cc49ec402d023003d2c9626ab306e981e403d80700b803600da20cb8ddc4d4058d940001b0860aacad416b2292a4f088003017040cd602420a070d001071ddc292816cb51075e8f1e3d7a743080a9267700de061b6cb0c10082a690dc204f766cc07cc07c58a8a9213665f820800f02383153e6068a00420410f2d0f0cc7c719d9881d27942684868a88d28335b7c66843073642629fb7b7e9518fa31f4c34c8c076ae65b3ffcf0e08707b4ae869bca60a11e7ce0c1079e04dbfba78caccc9397512ac3d4a48613ad2dd42838f28137343434f40108396140f03cf0c0030f4020c2f982c8036115020820702010e51ad40a8b1282f02aad5068a84829ab4de3288d24ebcd39a7dd9c4431a5d6badd5a1ab1b96ddbe56a4db5282f73b52d7287230442ae04dd52ee9771c0d179a5f2e8791cbd67c218bdec525a72374929e51167a92d72374921e5f1cb2da50229a62c5b82a902668bdcfa06b344e83c74f3e70aa77fbc41ecaf295b352935273b9f87d342ad166af9ebfdc81a42a4c24c9c19cdc922a64cadd56e62624d62d2d8a4c6a0d24939c4a593899252fa6c220335c95c91c339d5822633771537f810aba2062e4d2d88d2d405893943a7024626d22f12eb17715f24765f24aebe68862e20d3de2f12555f247a3c60f9e3fde95306e8558217910f241f268cb0c38d101ce81f2ef0d813b499ea8aa4bb1aad26b8e9cfa7ae24d8e51efef491cf0da161828d40ca23108c8ad2c988a32ca5fb99271b33b8dfb39ca9d274660b59c64d0d6bd5715f431790ba7dcdf922f1fb2211c717893b5f24eaf822f1bf480cbf48ecf922f1822f127bfac86fd9601a4ff5367401b1f7ed1789385f24def82251e78b44d7178939be48e4f92211fc2271c717894562ff05e05582e5f780df0e104f8143f0357de483e02bc1200f0894850e7084e9233f074894048311708126a800e30051d0014f50028f1fe8c2f4917f035c41867340698403cad84d157894b1d64deb466584ca747a488262446503ca2389e495008ff248264d1f259a1af028936856342b99ddef13cff24bba910228464220c94fae07caa50e944c4eb2e97303051ea52c3f49282e78944f59aa511bb2c2089958c1165f4ec921d984c9a9f6917fef88e50c472ca7b204237bc89753bee441a3cbe494c3422762d412e53b134772189027b9130fcac9b29b7e90ca7266f92ec5613e6144a2c87709460729352518fa12ccc0092cc3c8443ce6af2934a48c320c391506196011751c233e0f29a550686ef0c226e129de0d618e62b118139eeccf9aa98cd025344f9d0e35f75f65e17ea0c562311d3c2e5b0edd92329868e1aa2f1b932f9c168bc59470354ccc788ac5623a5c232a195c168bc56cd86ca024a8c562311a2c0e363d2cc562b11dea93cb440d948815154a26150e0af6fe2db773ce7054e103b33fc5212afb09629edb3baaf0e51a561929098ce419fa53afe5413bd971ecd0d140f1a6b46a26bba7ca65d9b8d0327afbe22a23d486b6c141de272b229aa3e263e76e9f7e1b5e198e9f8738b955c002fc2da329e3acf9d2b374f7176186583ae9b0bb1f41b9acd4f5d4b22f5072bf0a75d2f6463243227fe5cef1f439a4242b3752eee93fbdfa572a2d7daf9baddb5675d4301a4a36681c4db17fb315379db1c5d7840691dcff93963195fbf59a39e42e43cccd17dc797bf97686e3eb6b4d073b104404c782bfbeed3c6b48041e7fe496dffd9d33469273e4fe6fe7ade4ee2633aae49e0bb04a98c95bf881adffd1d5e778c05b688090c85f389eb00d47ef6be8b2f5a7b573e7a8df0023bcc426632ab71162973144ee576e32b8e4cefd2db8add39b24c8fd5fff055bfe6acc25e1cfe38fdc0a68d83643140e525596982245ec07d2b09f020ca82153bc50d222f60f19936013185a2db7fc05264b6ef92b870d68d6f80a94fe70d84d2de710fd81786192fd23b084e87f8215d9b3377941e25302659f3864ff0ea8857ac885e47edf99e151e671cbfe9ebb29aa8cbcb590cc46fe72ef1ff994062a29f5a2268882215bcaef5ff681be40e20ac94aa392526bad009aa882a8fd42b69493052f72e8e1c81945845421da254a78b1258a105fc480102d936cff05248d44c5aedc22ab965a6aadb5d4fe27a5c8006d0aa431928821cba2012fcea0418a12262db31bc4783451f0034d588bb365324caf30afd65a6fadb55e408c21b3a62192b0200ab17eb6267be4845bcc5ff73fa7a8099b5fdae7fec509c78b6fc2289b30aa33a2475369fadcbf5c385e5c138ef8c3e1f84da557a6b209ab52ed73ff5e8cbf2a759f729143f448f6a063b4105dd23f9dd184591dba63ba0fc56b892fe20b22df37b23de4fb464ff9d2f2fd8e696e51f98e1d131699852dcbd0d6fc7597b4901dd33ff7efd3233964c57c5eb0bd5f55b45c8f3c68f41a132d15c983c6f925d71ae64ef34b0b714ed49d68cc492adf9f5a68dfe4e241fef767148f2c5fb53495f27d15d384b91ef0bdceeb8430e8fcf7b4fbaaa5096b99d313540bc9dc2a5aab68ada285e38ce5fb812fe07818847c8f23743d40e7713c8eb0c517bec711e295095fd82b135a078ce2baf7abd47d1bbb6f8deedb23554c155319a98e5448aa249512f7f404458baa4971495c12a7c42d714c9c8c73ea41c2e3ade5fb552adfb776dbb6edd6aed4841589f73fb048e462b7c5fbdcd18451f13e87d44232df9fb18649a8e973ff1ae15e23dfbf2edec772a815cbd7265f7739e59452deef3c1f2e54ffdc102e12cc82cb2efae77eaf2494d75a488c07cdbfef4e13b62dd13ef7a1b82ac01b16df0695ef8f3cf772e1c6d43fb7072c7fb4b57c5f42cd7bf177bf7b6e98c2aa861d4887ae7b3af9d8850418ac1d3a25ab85a8985da1cdaed4ecefc821df691fb9d3b49da6edecd8c81ef29b06342496e52b00c8092d146c261536def33bdeb010d0bc608265a9ca0e5a44f9aa861535b98209256a5eb0300a42c6aa905ae9660509284db0e04c132a10330b90859631522cd8a2c4183eb967474d324dee590c64ccdc6bedf4c83d3b6a8a01aaa6da91b52b4ca6b2d0a4409972623ae529573c71a508318e80d18638a7c0400ac8f870454996981ae2d84fb33c36141b504f8cd1258a14349210c78e6a220349135998acc00a19a8104759258f91a4cc184633940005e22891a4702a2306d3121396a825e22897a2a8a2f6c509145618a5401ca5131419c9530e3f405529428c384a994bc19244c54809b4748186b8c1104dac5144175196483d915d4c91256240c388224824a110860b2ea260fa418d22445f22862b92d0020b2c6a62910f668489010c1ea6808a42f47761c22450c40d51f0a0c4e8c908518327b29035d1c31155c44417410f5bb2a480e908a327c4119e48b2f403134776a041f4975013d6627ef08246149a13a427b1566372490b7b1f1d819e7a6ca7b19f5c8b3c36548f3d843f964264ff274222fb9fe0442c097ee59ed95024dbe49e1d8979c276392380eb8ffd3fb9431e5fd9a71c29b1b2040a254acc9410a1c4892da22407329826f74cc9117b03eeda8821891af54892282449ba4832bbe9a96918314b02449d9a25e16196c4c81414d8abd52eb813835ec19c162a6648a6cc902451c1c0aadc33245066487c50dd3002490e4bcc9018cd6830b36a512eb0f7650b5e85a105c6598cc13798b042c310341a78a84e602ff78c861a86cc6820720499d90c66cc66f06236c314b3198698018824702bf76c8625d48c1964282355c9981d09838bd9112bea0eb32332228905c5dfb372cf8e0091bbdcb32348e4f1f5847db01ba5d31a4931c3d2a54b0c27f04274275ed353c0f2450b962846b12e3160914337f72c862bf2d8236341f772487ec084159e0e623ff61e58a2e85a24a5fcd2995e4a5d0524473318cd209120e168470591822e707b35cc39e79c38c8b6209e0750072a782e916422df549e7cce852cff5239aa9e3ed1ec1050945daa4f413ef7aa508637f4bc960c8f20ecdc80c77f2fc25de0913a9d49f08877bed942bd4ac09d923e7569d010c58c128c8a144862d7c351186210cf8aeb4888701283fc2801096c9045c4203d133d8cf261199a6862901e6b7c0165b4059218044806a4a3fb7917246830c9280d59c47ebc8194263191d513f6822452849218640e113d1f807cb1a35367a8e4f1e6334879acb1a633489c51caad30b5afbcdfbdf7cee6f1f2a0cf3edd8aa63248f9078f35b464fb3db5d6daef3fcaa7f4e5b7ecf741875868e16650089c020def076e96df020bf25d9d6dc023cd56c84582ff7d16388b5bc8fb14b8f70e04c9fef6a957d342abb760f78fec8054813c34d99f86267f568120e8c8fe3a74e4ce6567625905ca273207ba48f820fb7ff0417689e042f62c737dea5afaa4d57644a47c333b274460fe045060ddeb23ef702eae6127b88f77e63a4afd55eba4e0067a2e1b26a0e6fa35a45ecdc5dc8d7220965e76f7af947e5777e654818737c172650b2923ef26e3041921366fceb99adb6d22b3e44c1a5dc6641993c518a731518c9132268831388c1132a6158503757373b3bab93937bc1b1e956f08354ca0c6914b0d223b2e97cb95430797860e1e3152c4a000044130dc21e66847983061b884c9126616664a981ebedc4d618e80990223e6c592eafa7b8aa90e7df8cb831f3da4e051f0e59a84c3e823531f3f5aa8dfdf83160243040f3a7a3bde541ebf9d34ccc8634f1e29509672aec14692992ad227ad76bb9caaf39a8635718d4d8d8dccd3d25a49296d6eb2904138b55673d2dfe3ba48009aa10756f057bf132de982d4e67f4c749d338894c7248d9ace2ddf73cef956808872c44375ddd35150914e40b570a8c0e38ecb35c290675276956a54655f4d30320bb2bde4a7fe39024b2bbeb404041e2713261e344a27241806dc47fe4c92c14754f6dfd9e4cc482dbb72cf8c92e455ee9951ec4ac77281b9a3c81d0b4b4c4a0774ddee3a57708e18d4dddddbbddac029e19e2ff2fc895c7a4e69fff4fb38df9da31fa784ddc31674dddddde3af9ef27e920b02cb6e5aa9a0b56eaabb0aa1fef5ba596bc527dd54b4d6acb5d5de6ddb6ecda173d66aad956e97f33a8fd6d1e4dd562b006155f6395f95399adbe538aaf2b103b402054cc924450172485ac9ee47f48fff789754aa6ebbe2979696b8a5234c78c92ab2149289d7d3d3d3ebe9e539c1f43d0f6f0d86095eb7a22858cd79373fe290dcebaae2b6a7368cec3fce7c82ec21a9dd4638e15ab99cdc024af5249f4638e1f6d36d5ae771d7de2d77e7c0c9402b98ac40d8e3a9a5160dc380fbf8ff78b964c9fe1303af49e98a8470baa4f5a92a779ca7f2f08475dd5bbe15fac75fd5e1a8714eea2a8449ebaa2f796664c0fd33f7cc8816930915cc8c3ca192aa34234828c14b221851eafa622343f2f87571e3b12447e9ecc152aabb82bd78f21cd6b21a372b82451e7b6899efc663ddf03eecb5a6873f0f03cd59912954f40a1628f769dac582bf2e7893d928f0967b5664893c7ab5224e72354191a43c7a5e4b842248af9e9bdcb318a8649a7b260316426ca053544c09ca70439763065e2156c20afe2ef8cb58c3301430b1294dd49a6821fa4ba386b51930464ce161ca52942ea2cbf6f187ca9716f595d03fcec31537b084b084b084b084ccf67c6009610969b186b084acd6f07cf09cd85adebee3fa5e8cbfefff0504d4db9936623744b83e4a038a44d713b987388cc06f34615276e3b79dc7f15bf743fdc3e8c59c1fe597bcbd2ca33fc8f9ed7b422561a42fc7cbcdf13a5e87bf8e6f3147e80292e3059c43e7737c8e100617cee7f816754217109de7799ef005d7b4df13ecb6ff40e7e5dff0fbeec5387482e7b4dfdf75dd86d43f9bc772473d89d442acbcfd5c35c02dc83581c79611cded861bc6a30b6c5cb38032983af009cfe780469fc8f352b63d0e90c890edcc99229fe8fa0f2462f4125d5fe41389147951e7bfef6e805294348b7479b66b34fad14ec80a47cc03caa8e9b3bdaf244d09eeecb9f38dc7f1371e870bd3677b29b5b9b803ee1c113bd066498aca392027e61cc0f33705be61a1859b5bc0013bdb273bdfedbc049f186171e77580435c88c562e2ce751ccf3ac016bb20f078f38682a47171d8ce6f397eabe56ddb1e0794b95724b49e7b1666d5b070706ac00ec88cf3bd0a01e789bce67b15828dadf1acb5d36bbe033b1024db6f0fec80ccf6439af2d7f616f49abfb66f816ec55fdbdf6c6f037a949be162fcb53d0b74297f6db1b93d4d380225e50d296fef85e3d7dd6994b71ca0334d9fed77405f7228feda9edbde05ca32da677b1ca0949a3edbeb8064f2f61fd83d805186c9dbdf00b747616edb6fef1495f46d1b035ccc09256dfa6ccf420b389391b79751b2c7f65e12b6dfd9dfd658de82eaba150ca962ee79ee1fe1c1b085eaf713f58fd0c441ded130a0892726a1875a903d7cec2ab46cd15b80b4cf0c4d68410e31c0bfa36139f2c80c001b003aa07f80b48f7b69e2a2a59ba609fef25ab300349c0089611a3e7101d936f085fb3008d9fe86ae07707fb9308a4bbc215e99b08577fac8106ee713c680f6f1efe2db92dd4bf6f71f7fc1057fb598d815d2350cb8ab2023b5aae526febaefe4caa3597b7e5789347bcede98c827df13b9ad4dd817da9adb9abb5cfa9eba88a5feb92f9726cc8b9a3ef7fbfb267890fcfb2e785077a553be5296af6492adbc74d085a25f32a727281a14b2bc3a5a2dd1417749bfeedf3cde25aba37c1f020da33220728872591dad9056492ba5d5d28aa9852499dc5fba501918798cbee231dd8bf1f7fd4d5f17def169f01cbc8694a4c8d746c9d7639a308b841735615b140b3c262e3c262b3ca6268fa9098fc98b92caf7ab177563f9fef4a2ae51beef5ed41979b46666cfd92bf972a8853c268764e8425ac8c30aa54aa94b35a92a552736ca92d13ff736c0c52fb464b4908d924356caa5c48ccd8c9a180fa251aacfe6c7afb24aabf4e872b8e6c3cfa2f9aee64139b409abb73a4d583d52396dded184754cb58d88fbdcbf77c4aaa37cff4638decca3a76767278f1d2ddf59655a994e9369ca26154aa1f8ebbe94257fdda749a1bbbb7bfde9aee4afebc45ff7ebd192feb9df69c991e1b193754c75490b5930fd7324870262542ae794d4ceb7b44a51a9dd62699562ce392bb5f325b5b44a616b6aadb34e2a29a54ae4486b7f525aed967371f253500afdf22014fce520918fefc3d829836c1fe0ed6355d97e40c5b58ffd11529828dc9cdc7144b86f32d7032d6b3233535693327f65e1c0229f18a5032020ef016c2edb553435dca8eaa6cbf1746a725f0eb4f573b87c394234de0972a8411a69564b85d6ddaaa5393e268cfb97cf839f0eae71599b34aaf004d44873b4ad5a3a6538a7e64926ad3061b2c6a4f4fa5eceda84c97b3196d5a8ac89e06529d37192d1acea77d89b35d2da1a9b95cdaa041b6f4a59b10d2b09c729aa93d6274b5229a594934a59a5f53929adb56ef75e8ee34298abe9d53a82eab9154176126c092bcf6b9d9db762615b5363bfabb196c5aaac337c52da0110d00f1f3e689d3dff3c5eaf2af1ce67e536af70515448277f260cd95faab8bbd94ae75d75f3bd7b509a69cd2550ef17a56e4ba079ce1ffa41542c966c4a5d4e3668b59bad74aaeeaad20450d5caab7292604e5baba5dbeceee6f085cda97ad9ee9704ddc6a9ba7b278c856b6c6e5ebfbdbd6f53b3f96c3ff18a842d0120e0ee8d949245d3751dcdaa898e8ba294524af38ca29452a79452dad55ac35a6b9593524a69edda737ead9552da52d2369eb63b09208fe8acabafb5863e75f57ad98ddba07f7cb5f369f92e1404b048f268cb21d73ffe9e3bb90ae1eac81669c2544eede37faf5b242426785439ad0185cac922d1487981657b4dfff8e3fe719aef1197ada2d3afaa6b8b84649b384c65c5554e2aa76e73e96128b75d4ab72cb2566bad75d5b45625b72cb4729c9c77dbb68d02f67601439e2f29ad964a69c615d375cfc85aaa9252261d9aeb779df4ba3ac11696d652ba6ddb46bda3d36e9df47c415b7bd091aaa9beaa5473521db94a55a92bb58302164b1e69baa83d64df5622e8c85c480305857dc290bbebb2374a53c32021d43a55a5544a1356a5a494b27f4b65c1b2594bdc1049b74eb5a93a313a55b3584e1c3739ce26d70de479bd7deaabc60f54bf2a95181bb7596bc1194aca43a5f5b71548ee5e8e0b77ab53a539d170bc17639afc2c9d24762153122e95f45e2e3ebf0f831600336d522f135b953105d52368505df2976f37cb950944b85015ca16d42bba7dea920755277f1dd141b58aeec89c61ca2cd66522dbc7ffe2409bab7b031e2bad3a7950a5f9cbaf0d585ed130ae62c9fedcb67af2b5c0d2ccf0bed7b5741a7aeff63a72b8594941acb281e65ba83aa2d9ea97d64dfaee7f3dc83d6c972f7d723eaf2c31492927cde2288939e7a43569864b741e90b99fae40e9cf511ba1c13a513e1033b2fc089860766486209d2c1f05a32db27c155af0f9fa80bc2ab274b0b33400111111857d52ae44f8a66cb0732779348f761d81e9b79ed46f90851afe9022ccfcc34b028f5f96d800fe9244e08f25f0f8e30791f43941166af65005cf5f58a2720bcec35395627f903217de69e1cfddef14531eb13d9244dd823df7cc48cad6c278d6e1ef8dd0b2977b66a494c7d7531555add2c830e7534ae70455f0a145f71551bd26daad0203cbf7eeeede32e79cf46b6cbacf273df0dfff3b61fff7fff5afe5a7e5d1c18bcbd78a81528e33644cec5151ae3c378ccd5ece5a4e0b966f2ba7f226b859aba2327c1fd7b7de3555a2ca46c3f2b726b7d6edcad075cf4879bcbe4e3a32f52019f617587e8d65ba54441e71117986efb27fb66fbbfd66255dca13fc6c4345757777ea2edbdddd63fde33d675df2cf5331a5f4de6d924129a5747ea5b5d6ee6e7b4627afc0f2bb2a3585ea9f2e9ad2adc05c50d1d5eb12f542f565ce39bb7f4e0d6a06d667f3e151221fdec5dddddef7fcf5be16fdefe5dde6a9f12efdbabb1b87ce2a84fece717917e8f53e807874d43c0f6585a07779f4649c69c2ef1feae565b3855a0d32f85a74ce39674fffec645a35e8eefebc4bffc3def5ae7733956fe98ba71e5976777f9f9452f2e81f0d6a06d667037abd6cb6747713f54f7763a0e2887ee480c1f2c79b01903306cbbfa0ebc95f0bdb59c3af0281aadadf272cbf75dcd8e4685d8a03ebdc68e14b2f557dc1f2bb5a70fc5a76432304cb8f4747cdf374773f2b045f218d10ecc5660bf57ababb011035ce39e7c4c0a3b5e79c73bad082bffa392f32a6419152434dc9a05bd8660b96fdd395524aa9ada00a3e5af809a809cbff1fef71a9d704963fdf660b96ff401454c147ee9a25a8444cda4a6ad4a19911010000007316000020100a07042291204af24cd87514800e588e42684e2c184c2381400cc32086a11808c218880180210a310311536cca0400e0b4affa1b9e0b8cd6ec1b1b0846374e5fe934d1c37994e2261ba160cb11e32b2a6f8fe0243153915c5d7b8d894e7c407a978538aeae271dd75b581468e8c4851a96f8d2c150800c496a817632447c84e29a26c416f80cce650959df1cd15c4f8c2c455c1a142026f1dc7eb5276429bc453900b45c6586c83ae599e7289066810279a0beae5587c6708a7cde100ddb1db221ecdb82e91f466cd9717de937e96f9bdc51c1ff0b9ade91a17e4ca507e8db4669edac8d78b9bbd3b331658a8f22ae371b5ed8d52ea890b903549fdd109492df8d2575f5daad368e7b8f21ae93950399536c51f78aa27aa5b04a359658d5d3886ec8331d56836ecbab82e14dae613bb45031d21e422bd9c2a974522153425c817fad881023b45f68a5959334cf0b412d6608e8d2994d12fefcfcd338674ae517719b4cf1194b1a3dc9e41f0490cc20c35aa99d61c3b15d14ec3d54a8855b8be60e3c69ea6a83df88608e8cc7dc6ee51b429002d06554b38117e5f49fda9d999bb08da3e8540473d213b1aa482aff85305e5b6299e8384900ca171b7518cfbae500256b0fd2ab6be3ea3fbf0ee1bc39142e132eb60acdb569ba1a692614606ff213076514a61f10fdf03988c5f2537e14caf2d29d04d8ed6af7caccbc509830810b8d2bfb366d48facdc86c5bc7aeac4329d784dacfd8b2635c92981e4e57f41536d014cb720e141e743db83db882278ca1e14c2df2a1345b3c5ff881218bb0e63d56b2d4371195717e541acd69178a9c6005c3edaa92400d70751d94ca7a23ec5f40503cae16374086becbd0020457a9b1be1708d238c262ef612381916d369a4a8e08953916a5528d1c6023529b714aeb63a07dde36bfa11de6cf321ca4d053bef0a0f1e7b0b92eca465661acc846f16aa1c00a7562e7e4e4d24458d4c03db7c67738cba454a3efb39b4644500a74b99e8dd8a99e9b42a1e18fba70b31defc6d517322ae06336a894b1d0e0c39a1844d03108428d8e527d1e94bcedd8ceb3d191c9871d408a5b405688c7585a173a4d9efe804ac323a40dde33b8b7bff95bfe6d03aecfdbc386e6003f505129d38b53166a2dc56072be2cdf2d4b4122706c0e0379ca9eb2398c68c5d46d181041b4ece1c2ecf28ee0a467542d751b486f2686eea87703e62b84f1029b4e15b777af2d56508228ef601d3f7378d24c25c6f1e27cef81d39d09c468be33e98978c233c5fae01a5de773036c4d3436ff78670aa0d38d3493b73e0db0b3e5d05568b3a96c594a610ddb32e44713c8460bfc2e36ad4a34102cae09040875cff364697dc02072010c37754f15248a4378cff1dce59ccf02b04e1410dd0e87a18550ba8ac2c6d94b54c2a5c6adb764b4d862ffd9091b7d37a8781fddd1bcdab01cb9fc41fdf2821ab8b959308b5c7811f15c846bd8f2c975d41234b04950285b486c32511b9a122108020dda33e703cb48c1418210bb34ea51558eaa5b656ea152a6402b4fd8109dd01b42e0d110f644bf85ab101870c78f544f2f4303ba75bd9a3e903a90a1bf4c00b05d2545e6eb9742054d6be967a4bb29025401ff8bfb962dfad869e4e894d9092195fe5046d098efac06a462590de4437b8a83e3d089a2e794d57c88922b540836d547a609b0c92e2824d4a60207665f3ec1e265fdb613b514d0822f90734c1c864ec5f92fc693586bcd40b65ad3fe872200f45221b95e094e27f8bfb6258ec16cf65f20b80ffd99adb67ebd4acf7f7d412acd6e34736ac1e198c65329f69f4f10d7816cabc91aeb160e2babd5ff40435fe6d091121741674bbef7a02c4c49df5729e9415c3a3a0b4d66c127e81508d99c0da4bba1ef4fa574c29b277e39849459ce70b36c8dd2e9b8c8df5f233bdefc08056523805bda8a26d6b311944a87b6f56a6ae406e3bc3c76aec40071e21a04b279624fa57afea73606e4cf18fcff620400caf32a986a2285615b9f450fd21067d8872bf98291c4cfdc57778698743f06ed8e1a17901f7a5b4b8c4e8624c8a7088d908a8c8120fed8790bc7e9d6d8cc749ff79f71a0a78710e0b08d86bed01ccac0b3ed1ffe564bab4ea80110eab068a92cb40cf0fd9104eb034f57a29c8b55947d35015a704783bce0a3360195020c0d5876d4bf793ce3ba68334c7131e8d08ea56810bd59b566c73900f61b4908ff2b1f615ee2779ea790092a1abd88f72570fd5881e87f472fa579b803bb0014d83d42a19582b140b44f20c574397c0360652c9ab93cc7e6ff5a48616b2116918385197cb6a00611636444bbaa0dd9460efe0a394476fc267f717e968d300a80c44268e0569dc4de75f3120980b77f1a9093ac4ad083bd625b3b9b2015a9f2269c40e082cc753200252c35cd8ef25368b1e79752640051cffe028807600a6c4b98673e7786a447fbdfb25bb611d63b866009026ee5046486dbf2300acc4452ae79820675a6752816043e98f05d3dc3bda6ef6feab5113b3a351eecc7a3b52c24b67216fcde645523b0af3dc8c774e0947ad9a00ad308b6cc457d6e8528ff48c47f86d5b6bfca5804869c709e163d66ae89c9006f6450df077a1ee1170479200aac72250653042ecb829ed3cd05fa01ae28ede249b00e3faa90262058cf86996aa283bda1489570984492ce80058d1c52e7226936bf659891896d0644b7cfc070971046a51f0c000f8f3d8a479b1e0993bf3602b5a68ffbf0de3c4d1238544a78d0417b65f00f80007b1786cb3e03af5a18993b039c951f67918a5d8a455df99d9df40fc6819c0c7ab318d96a29b798fa44736a8518e641a43ae9da3ec02f72f4c1745ab70a460af115c29dd3cc4ff16e7ceac75f9f89af6d16fb9ffd001d415d65f9d953d86148666acf38b3743111debd5985d38fa088b241606b35c83610a6c5fb5d62bca1c65f6e47f1425eb91672aec85919ad1e648525019567f5daa610e1cdc8d9c84f6ec80518a1f6099861b30e643219576f8942c1ebccc33236e90a1190f8b3f9d119ff2e9e671fc29be973df160ea4b999ff64f37f1666516ba573f40438e1bdc77b2d3ce4068854d3c67218430c574e0cd23a6952691d1be9c793ce1b3ff60df013709ad881f1a7ae0f3beea5f38a160ebe62d893fb6da1cee2912dd4d74f8bfb40c2e168484fff340391460610cbca0467af823da1b3cd4acb408276ef056a1287c09e6b107b15c54fec46998593f6b23abf39798887d83d95340eb50b865ff45abc91b2d1d0940302d1646280323bf83dc896425472249a8188411f80336d5b6020398c02fc5bccdb1c3a852872f89d418981726888ae03608437ff2cf3ac3b4d109163d2b1d4171d643f12e8958d395b3fa6bbe9ee9eb7fb317312df0e01ee15e289c6916fe5139a6346e6c91a658a405689df75ee1e30ee48050378500022eaa78060f6000b2867843b063ecfdd7810ef77a1e9ccf4a61921a1da31cc3bc1cbdfe4cd45a087e873058227e8201303f2e59cb5e05cab7ed691e329006548c167a3e15b578ea057c3bc7b130552a268172530dbc1f1122fd9ead27f531b001e9005c2e2eae32634c7a811fce64c70401b4c10bb34dfac0637af43ba6f8d749986ccb0c3f23a39db4ead6e7ac48a4c616195f86dbf223d8bbc2571c1c52a450427509cf359d8440bd280f9ca1d56153a9cc56fb657b9880d37a1b89004e511848208cfebde5b07c2f3911fb272479f010bc27723a122628e117e0440cb019d4f82d730e41425d0e5e425a9457764efd6e81165c89033f1558ca48dc9c70df8fef95b286391473561fb6a9a21cb1a47757c15b2205586e2849b2c75eac28510eab1ef50d9e19f178beb2178fe0b5a55c3df20fac3295105f44742f0038686f933a18c0022b887ca6822b2b5afd7a0f333639a41f42375ff187c0344a2c861943940c6aa05b1f47f491d4d24b84dff4e0b885c6061a4dfef9a25a9b016523f6a2110a79694678a694936b0fb0e726531a13a7c767649f4821d50ae585613fc81121671df9044b13699a92bfb3ce46beddda4aee53444a7f95d6f945ce9306b792dc5630cf25eaebff9d190b7c9b26535158bc83613db5fe4842fa06fa2606713bc794fcf2b9b8504e70d089de6fd45e694e1e7b57547919b41e5ff5c955f9c8b77c1558609fc58919f3b2da95788739625a49f8f809c6a36d707ef014af719a927c87d14444c2951b157e0a405676dce1a6d37f00fbd7c402c3eee01efd05febe438d4d0bf5220a4abf622ca78ffd26c128d00e60b25916b5122b2fe9be3971ec5cba60bcd1eca6e4e2412de11a18e920089d0bb5b7944b9b457738ef385eb2e8c09560dd711355fbd3de40e967debbe34d8a4cc1a89e1db2076ed3d1a4de118b5e261cd70a9cca299f677055d11a0b4e606e54f88af0c33a3000058a6a4c51f70e88ec26387d71f6b8f776d7fb7d904fea8ace4f0f0c51e20767a62b1119e5bb95181456a048f8046c9da67e3a1e7e34f9f2130b74602e616b337a53db213ab68e7a7c9e068c188411dc76381453f4d498c9e04af9eb28c33a614137a783a192a0365583dc316f1c597e4b1b454448c2807634608f95cf40405ea742034bfa503f6f1c723bd2aeb4802d333b79fc8bbaad78d1854ae31d2f077963c744cc516a9c328357c10d37d0d29a24a86a5ce0733c92570491294609e27725f2ace6c0a1e5d7b317fddf1599f3cd810125a5d2244daa58b36ce58163687a58801da4ca77857193b5a5087528c4be135fe8d2f9e8d580f678426846aa9666e135b5e3ee6fbb32edb97fd5395141466eaf44831627ea6418ad50e62e26286dd3c81bd197df94d91a47b9a8f1ac49714ff1284001f7cfef3d9a6494fb28b598935e903af064121837e682368c621b40ddb66b456d493bf8e27660a1d2ce739d80b145544a0f4c85b9641bdb8642672e52f79c9d1b40408060d5cc17e8c767adfbd7e68703f6eac83bd75c71f62c45b3f082138e9a4c063224f5826fd0bfcf1f46e76fa51581ddc6af0bcac0b9acbb75a18e0ab806443c281956baeee5bd07c9a106865b2ac9e2285b11355562987187edfc39d02f2611ea9076ac270098f5d17c821d05738f58ece2722ff62ac52aa1aa21f2900056f3bf22c76805f0cf87d4ecc113afc7463b7e811f96f2a14e3c21c394cafed4279d337f2271d51837deee30751d3a7c0331870954fa34eaa4b0c85b537d8b35504d13e3d43fe02f3fd4803195d56abe700ca7526f97d6c0302449e2f566b847fa9d8c7e56640e851e2aa5526d8b63bdac3c7bd7ec97c5c44f93634172bcf1ac023531a9fa894b4f9552654d923de6c2b920f87fb2adeabde19429ef39bc4a7c69b3a701a52406a88a7bc4bc504803121949f03bb7d68c08558869f63619451f5cc2dce05dd184eee4087b7a0820c8194119eed224c0c6208638ca97f4f9d7d77c050914e33e60d189f5504f882528525e2f00cb9e21a0b2f676ef500b378d3232580421c9f1f3c4c2cda87667c98fa8b5aa50a19777e1682a8acafd343686ce1fe5dfa3e87ef4e687f03a51459ca25f4e6e48212df3712599396028610f9373ab396a1899c982102d5cc60bdc482abb2a89c4fa10f3cf7486b686d3a03ca1cbb160e7ccf1866fa2d1abbb5db0f0c32d8fca56d8f6433a2e622ea153892344cc95dc7fe043311375bc8ecf54eb0721aeeafe66d56c33f6ad3f38738bf7f7c8a0e59b4b27d06d4463831f86d113b48232ecb6e25592e224cc04787248eb1366b72ffb92ba2d33cf778900407238f23abfa8b47bc11877cccbe93d1da44b90b8e13acfed3671ad26df9e6d9cab7a9decdee6d1712dda068d8643b48f1f8af35e2197b7566c57b2aedb21c1b41c5fe80350693a5388175772de9be485fbdeaf923a80526fede2f41e9f1748369712b69342a5b87a3e9312b61b5c0ccd1535e371beb68e51fcfa6e40e1461bba34355bcbb06d02c8c6fa16eadcc1a6d30c0fa714d1f4612f4459880856cfc1f6783fedca6c530f0c395fdd8c0d07cfb6e6679cf2e6c26dd233dc590a1556c2a3a462bba35bd152a228a36605871016f251356a016dfd6758e55a3c63d1e458aa1d1110fbd338a607e331ccaad834b78d8b4da26f379b7d5dfac810cbb85c2e872153fe77ec2040e488dc62d873c3dc1aa21eb4e4ad9db7047633e65eea60944506c5414209eb6fd58c0f12c40c51713c34f2b0de9ec25c030444a912dd154fa0ecbf1ac529fbba8ed5f1b87965adc368abb5753092a8444ed49a8a4489b1fd4b31b9d42747c5aa4561d03fa26d18921163a25d1ad8ec06379af29c590885f2c4847c8994c05c51c388a1dc7350189bb714eef34eca5ae5556d2bca55836a64974791155fa4116c1ab0276aa600cb0f8161f2d0d2778182a9ff15d58579f602fc1c6fb8b48881b0fbe22124a5211087e9a90008bc8f984afeab6c5df52c5eb442b8061ab444807a1854bdb153bdf7bd1cc8e0056f5c4692a5e9e4a4c7d34247e18748d7f0d479131c5042506a1f948d2b2d4a9cf3fe0f63074ada28ad140d00366ca60f950615e97313007c310a7d97bba29da74fd5f0f72b83448c1554e3793f7ca843349ee866e44ce6db4641979fe5556a3c04d2bfaa39dd3261e3af3298b0ed06c48dbd575b835d0a0f3068d3fcef1c375e41b973adc2e361b386a81ac535378043621d01229ad6027ef5f72521eee7a180601276dea207c22d80e158265e4ea708a09a46509484b86b732525ffb052c09de2011c51e93509cb146243ade571a5d1b0abd37884fbc6ff24ad5d6f7936c6d1d46f0e8d702b54a8fc60ab73a25f9fed857176604d848a1c0db628fcde406dff73316aef530b9371c517105bf2f5b644d70f698b880a19209c37143411cce1563ccc97e04f50e61ff6905dd522ef074e17f336dff6c22945b97c1ee8929831b227a5d610dffafb8fb5fd7c0426816a4cbb0076368317bacc8503bde07b8f056e30698ef93a5b286f1c89f5c9dcfaf2a72f4417b3db4cdb38589aebe00c0115c98fba559a3a8ca668728c885cd1e50abd4496a08a2c4abfe405154c3caa2c7a5a7709ad61c08867c3b1c6f328f1f32c324bd65aeb080b796e7634f1a4565202f2d255758153628e75e55f2d0117ac89085e3ee8188c31082c7fc8c50580081c7c56676fff903bf57182278a78756a6663e5de3f1b3697b972291e5ec416fb3db098fc7a2b8f57293bbebde3edbfa6b65fe09df6ff25de9eef3f6addc24aae7ef31d555623dd7787310d6210a9a4fea13038888daf18c865889d2ba4ae646c2f6abc4bce98898cd841f3cf9ced800d23c3bc50318da60fea96621b6c4d895bfd81c6394f3801cc1260cfad9001896f4669f83a8e32f37b14f36e4df9e8216b6b091bbf50a01f9d003373064138c50142e088e0aede44f4973c2267a07b2ea3339931a1473400aa01e495edd962b50284f57845c17dd2fbc133e799263e380edf0a3e7aa17c1c67d4a69e67360a76a81d4839ee71796708cc62ae4ae02118e12cb0a02ea04c0101ecc91aa7fdf25238e607c1c26cb4aace3089bc40dc7ddafe4c232731947b794421c1baa23ac148adefb274de54a0cb9f13f1f1c036a486ebc1b5384f138bbc21b1ec965eb91ea0228bd48a0216e2634d364bb92e8e7da93b0d0e0ff787dd9fdea9712d44fe440ae36252a51573e8744a5fb75e9a01489c556ec3d18adeebcff7d8f904871562cabc539e17d36950c13d569eb4a4a60506ace67c350bafc98a325c823000a29dc440586dce17eec8aae1894c052ac4dbca42a62886d941d0f56e91046f3b2ece0e0ea459c669728ff082d2c861c60cc57e0679d21a4c3f206a4dfa0e8a440aa55b283a5a899b696331e461a22b123902428ad60375c58c075899930f1c95446a8e43544b06dac8b785bafa365a38937ae837f04537e5b453e10c8a5f007681902373493c0562b97c6b0bbab5456334d61eb0c0b4a63a2a9e1e116429ebfe1d1f741cc98323e7bb97035cf84033e0025a3b40f0171079565161550facd14f02f136e1f272412603d512df4ff17b2fc4d24d3a567d0007b0101790c1e493319ffe58025e227f47022320924575f288ca714dc86ed15674a461522d3fa623d3dc04b7438a960f6fb198b52908ed06aa218da844ae6109f4f4cb1b7232454a21664502d080c015c48cd4fb033ad8d3580d0e5cf7e8c33db0ef917f2ff65833f1f063f87f2ad6dd78e385c491d8613eb691b39fbe40095d4a6275ee39e01af9ba3728f94db8b7d89f6e62d2ebba2de90b1f407ade4ec82bdcb16b050bdb14b837796bd292948eee13814b7b0b97b02a20452c62f4d20b54e674e45f627833239cd3914a0b372c8282abccf94842694f5cafbf2306bb1b1931c1c5a1e2bead6423fa0fa906ef3a25a0a762e20ce50fc3fd2dfb44f56f4a5bc360bc888dc8f1694824c3860b5a7c94c3415d032a60a61ae16bd6a3d59329759f4614264fc00763521eb24388a1ed226596ff49da4b5ba2481383dbc745ddb9b23ad12b3db25d7e9ac2732a655b4224a14299314598d273790e13a4adb94928023ff69d31a58e3f3b651c81ef302a84e95458ba76ca51d2b2074ac02b85b9701e0d5d608608da65ea9780599c3affd47da20fe1e584a7a5930bb9b930f1ccc8636afab46b3a86eb8ea9ac732a41c9b4b6aa4d4ee26e7fa24c502379a0ac31597e32eed889c81a38cbf38b64f9e2fab77be2c69491ecb874f55f13448076733ff1be99d081432fcbdb923f8e8dffe8ea2341680699d3070a80a9ace4b5f75640f739f0cf35243e4637b0f468150331f1e7a981c1624a409b9ecc215683a6337c93b59919a4d313ed532dc6f969c5c512f10764c2897ebd48adbe51d2c472a0b8c2cce564055f42cef4bd0ca7c31f7b8cc89bff48cfc45428eea4db6c8aa6682dea93aaf7dfaafb83344a3613946e02348c0e52b94a67f15b56429426064971a57abb054122b8e22c4e1aa0c575571b57aeaa4c7f2496ac2d5aacb4fd117d16ee6dc0e9caf5c18ce46cf4d981df3e2d28cd00872cba388e38db0563414c0873a6b4acf12d14adc961b655ca4d5386d9fab2fc8b072886244d25dbc48d4289dc0b461af1838990f277e495a390adcd44fc9d368e9f9e74742822a16aa8a9ef0ffd799475fdb24657a0fa20206008bfcca3712142db83346f0f4d414818b451b5a0db6b684cfec91cdf9d14b902b8253fdd5017e7378819ac82e76fc8077ad8f4b83c38aea13634ba39a394dd794f616a98c41b3574dc1b7008283ad05cd6534df76cba43be3ef6bb9de5a40397fb2afd1969ae9989426d6e096ca9eb6e806f5177a7a7e9a1d3b89bf8733c5e6651b6ad208fe0dc795a56f566cd9efde076d2771a0830a422e4a8f08981393b1fafd221b1a9dcc8b8c6c77f7d4c3cd13a43935b9b2b41cc40855f705f27a7edcec763017efbd99f0319334d59a4ce7e1d0f65a1f67a2e358f62ba2c78b2e082b2c39c34c2cdf0a3cbb84c5de6fe7ca34b9f6f4fc547afa8f1a64c6e0ebf46a1457fb78e454be5d72125d2c951c69e552dd7e6df0ddd1fbfcda0860065c5bd9081e826922ce268a5bf03fa90fe107fb02d2babb183295626faca2b95801ce5a3a1118befacc436058dcf1736d492c665f0430bb1a3c5a0369ecc3d8e1482858431f8d917840cb66b131375e9a9e05293eef68d992854be5662d0bc76ca84593827cd27cd137d8e749f3b112d7b6b14a225c083b6af646645d15f79dc38298ad92260415bffc63a8289f1175783120b9ab33731ce8f149f16c31c58c440575fcf2298e80e085f400106d518df17a20b0e742f3ebcc7b010f499bf33c13137feabb92e701a9e658e29c1da44ae7f9deedbedd8b669c7b5efba8068555dc647cb8467ae6f403219c1a6c32d86fd90c10536f5967463c454da2ee48d51298b9bfcfea993b2c153d8c7b5c48d75a9a356f85c7d646f243a5d65cb13733c05e2382803fccd3ba4b53971740a60c53b25adfeb4042a6a9cb0db4f7dabc626cf77e13e7f07d60fb6bcbaa075d06df3bc486dedd4b024b63d5a30605d3500ff9335bb558da9b4edfd868c0de37f6f41dd3394068b543f03b92c956acc6997d0ab77bce6555b86ecd3f03ca2e10ab72d3a46cdb332e22747c163d07e474c1f4815f1254e1ebdf2346aa30f8d42f3b52e025532d217b2cf73331a62354842c9eed401d2bf8aa9dec8af26fd25249168ad330cf76a6887ab0aa9a45c2764b90751470f885e4baef9ee29cd3e08cfcdc5f4d598ad9688be43a0dbd24a5b1549661ce052ee401b382a8427eefafa72cd56cb44772af2bc764297c2ac1cd46db2717bdcc4c6b1769972f6baddac3e36257091624df6c1ec2c16c4e94459539d3113b60407fc7c695189120218a027a4a0d86aef1b26a0f220b44183e639810cc0d80ce7dd2be6fdf90a732e8039d107aa0ede552443061796b713290374047ab9aefb675908673e8ae7d74817cd4bf4b8a9cdba750348b41141b03b4ae0b77b1a6433974f1a6ba8ab625d43a1425b40cbb5d25da10ceaf8e236d5b8fc30caaa7bee4bd0374ea0c62fb17229043a14beaeb4dab90ff9cbf6a423e0d100e573d3a45baafc79dbac12592decd2c0b59396ccfd0eb87c31e6f1104e29a02f225cf50f1e9b887abbbbaf8f430b225480b5feed6a252f72adae03e7d758c29255f9b2f81f0c5636c17b300c3e8ebe53a780849893f07afe4ec8f31ede2c61dddec162571f97558450f3ea463f8a474840c62708868c6725a10224589c640ddd5d781c88b291e3bd642aa31fc4d1c3184b116e495c8eafffeba2ef6ed70b411996bc2f262dfeaae8f537713c66fec8ec8c0221ac26299f9f5632770c51599fe58a52afb4a98ffc64167e3e0efa836788aa8865b14a5e3e4054dc7d34b6dc5823091b2f792f3384011a6319aeb16897875f011ab69d234c32419c87036ed2f4d6195d0e22e6c238f43b9770af290abe1ec511182148aca2d5232abe59b06e71c16e856970ac2ded1a32ceb458c8f05e2ac2af518460dd7c4efd0c1efa57a2bb3779912bf40ef849e1821685b5549cda97667e7572fe8d33a1ad2f74a61398cb11ae118c972cbeed4504454dbe8070c1d7473f8e1aeed22e6108004f2cc00b8780269dfab42a5327d22e6e6fd5d4b28613eb8888127145ba05967c76187c986156454d7df1e2807525e7c60164aec76415948000d82483954ac12383b0de84ccca9ecf2e0eda2295d4feecd6f486dac7a0254ec30b09e1357b4b70d34c4833c4f903243f9e78621dcac6040a63c21b5320e80f54282a8c5a27392c5fad31f400bef11920846e1bbfbae6c611303ceb82970857c2d61587b2ad5ef0c4ce59713207f71cda500262ec790b915ed710ae3cb1af3639a33b713091fcd34154519c4461ef7d0945d3ebdaf465eb860d303a278843df7c956765a83955aef2a7be8236341b6a47a053782f3f5bb2d6d01edd148d0b3d2ff6db08394291fefa602664c9387c8f7355ad06d494ace2e30a4f101fe5f9a7a33d46c5a858316f96267c580267af09230f3f2464a4dd8cbf5b2889f36bb6a36073a46c3172c868606302d172a866cf344808bcf1396dcd4ad44d324227ac792b0a8e22648aae28c2c0f0f5a7e90302bb077ef05b4f89fcbd9bec664cd3ab69fcb1c84912bd4a514e8bb54f91f41f129cadbfdd5781d18d1e78b1e07019671dfc79be4207841d05aab6bd164cddbd23eae8a11a9755d9130718d452acf28b4347610abb97b6cd8655b16428dcf9bcf66395a36f3432c552fa457bd622d2247d3c0dc02de93b0539076d302ce8df3aa69b85ed0cb919fa74d59e6c303738435a1805ecf68082bca96eda155134cb08a50eb08cccbdae22102210c5dbd52e9359e8933fe15db9fc0a96b7fbd89e0c13419200a31a8315f160f6f2caf188593138d51a97a707a768827ff1436d043b18b9ac8112c334c61ae15268e0ccd2e5bb1726eb51adb1993e649ebb2f893d0dacebd03e4fb1dd3e3138719e872578fa24f3793bfa5ae7412a818058e7d89e56987af4ddf114778c5a603f828ffa2ed1f217c2c3335b4c82be4a9e18380923291552c7cbbcf62833a52c0da21c55dab3ac257e755000fe9ac82f83e9946a73433abd2b3770490804ea16debac5fc103ea8887e23b4a3e134057014d875bfbca1b7f1ad7ff9b52c1292678e8fa7b447c519c37e73a637de679804ab0249c1c51f752fd83b1f501321780c9305def4f5113c0258494f29a400c01f1c515d4b94385b6a7528ceafd7bd16d1f35f35e8310fab7884a0864df6ab3645344165e77eb711bb2171135bfc8e61c745a032dc22fa0f064b8f98312454677ffbc693dd10b8b5cf90f8b5eee8d305e72c789dc15bb701b08494e75d52236804fbf746ee071d6c3ff5775d43db994e1b915ee76ad5b257e35faa962fbdbe214a5b5461b846c932bc891efbfe01cc3e41c838912c7ada3852e787f758f3cbc8e9788375c3a8ed42f207a6d64359036c9f45d6dde291fed17136cb50197a533a9c2edbc3901898cd197f7cf59c8f729560eb2e53b4295601a00114b7297c0e4825f69c500859130d9d43164f9cb334497ee5d870e8c47d7a76405c71c384277d08a47c37737bf676c08dc76ef23966b24b7fa1fe3354db651b905bdac1957299c51a99344ab1324d33761d00e337e70c382d0d2319f5705d5c15f8aef9557ea2172799363b123bb780466aaa35eb1dcb2d832ee052fd411313dc413fdb4d2f2b205ed8489e29d0333f27854a1a4f654314a6c68eeaed51a35a9bbd6730c2b51938a202dea3c3d1c3237f0dfcdf65411afb0ebc9ecb295bf54f1752f9e5c215a2ae550e2b070f93ad87be9a0c92faf8455f4fe949873a6e039f5df62db535e699b2f4104e28638db3b61eb133deed8519ca368e8146414c586c28d85dae0e18e7e8437a61ae978d81d28ecbd21f0b2e28f6a5877d3b8b8f7ef25a08b86cb0e3402664aa79321f070ba39acca403ea36ee28cf696298308f9648f2f0786272c9e52445f2ab069c9f18aaf8df249393a446ae88fee71a636875d167ffcc13ff58eacb8bf8b648bdc25f446d467f0e147116fe0d63c5538230cff037785d11400068e862598fd3c0a1048ba674c92e8a2a9c8da6ef0593a76f53c05c087113b4735a9e5de5864ab930c77dd51adc18718ec8f400fb65bb11c176431122449fa1f5f8adb0864c4bcd0ce9f0b5f91c41ec3b2cd250b795501568be40269e38bc479b94e2a8efb5ab72ad61f7efdbccdd55ee4b463a573e72b1d1f9af3ab77f3054c95b472e4ebffd7c92555b528b35f302540107eb2384d26908d9f5063e101cdf7d5da6fcb5470961e41003287335f381644373cce10bd5d28d35fb938c98fd70c29e6d3100f3363ff66427f75b27f6959f55dc0dd401ad02e9553daac0048c4a3d1fd752c83b9a028a2f058a0192da2225e80c6a0020cb48f777b6cfd9577aeaa9e2d898c9358e06d2929ad28b1546cd0ca12074da1830684afd877f1a08a587a418456cd1fa1a9808406b284f612135acf7f42e3b01510251da919089d0a6d849c5d06ca51eaefd941bf509b48387f87761c955acba5cd302000631f9dda2f02cfdc108f4cf56f7a3688eda63923564db572bede5f6a5dedd016929b386b6caa3f6104ea3021532570b1a4c2bac78c54c51b3b6856a0c29abad375812c21bc5384e7687f26f57fa5527c04dfa04974d29c51d4b1d6005ba92d587338542dadbb74b1ccc0e752699fe2463d96408a1eb6848da734038a5496400293f031de1c7344e10e123199d0c41f3783a8ba5e152ae2d822553ff1cf54c77a35c3697af3b8c5264918511c2a77a75f9e7f72f3d17e274a1b53e1013aa5ce69ab40122a9ee0de9853bf91324d362f552f29014386c4cb385228eb86b238b98f1be9f5d3240817b64e817f2c3fcd6819b17d59b106775091124a4a20228a14190a8e2933d9c9238d01fe1a8e269422c2bf06a5ec928462c97cdd0a24eb49e1f40f0d803c29568871be7f27a5a1c504b9a233934b84972125be1952389bb7cf412c6bec7576fb49d2fd9af815df29c33da92204528c79f3db6644f24b2079f4deec57982a764acf1e1d4f23273dc5b6fcf567596141c4625388c164f4d3750aa0f0574c2f40dd37bbcba27c9d4c2eb0cf021eff7659f7d1091fc741c8e04f6b9975d416a2fee733466eb44ac531833fec085f165396208f40b569eecdc5d10b046eac7c79cda19629af1a008476782a4947cdcd19fc952988de1f4945bfc4d8e4a19d28b15265f09fcb652e0c25c6a2edb74a06c498ff238001552c9bdf2d69c3ffd6a90258549a9f8e940bdf5b836f22d73f54acdddeaf31a71da9e183888e98cc32f8e7363a0c41478fb86404b87fa4907b937803f99b1993d20cfe03b76b6bc677b6e5502e59b911ae078573f7213f866f65fdfc054343c21c6c319957fd7d085cd032f933cdb8f1a8bb826c8eb1093072fabbf226f43d28263f223551d43ab6f7f8e87e31ff876f4b0ce60f006c34f855fadd92efdda611a9fd8bec14de637b529f7279163827e59d9bf6dea24d78a37e7d9c74c5a2c26d4dc0d64e6109947112061d2920193255b449f9849a18d7b65bbacaa465865e707152fdc0b8a4a04c27cfd3313ecc01f2ff36586ef05070cc12f61fb3a31b1c7c6fa336e9bc18888fb0a8b6e263076cb3b806898f3962ce11ec51ff8f5c8c3af99cdc84548534cd7a1057ec5f42f2ebf63f3cc16bccde923a0814816cf0fa5045fb3428fd2abe2f49759ecd9ca828552cee1b38b8b7f0e2f9dd3813b1b6c0942bd6b454c3a22a2d116005a0ff46f63cc0c5ca52b9df872fa5dbf289d2dc5bf031b8ffa800c5d7945b0f64ccf798b062f7b2bfb669a13928fc29120dc0736184d28b5055199aa1284935cf818ed3cb1b559a73d857c644997ec0debc4fed523161be341ec8bedb43fcc0dc3fe7e2e304d35ec5e35b1d3c0676cd55b25984446fa30cc769798104848d68c6f67274c9cd659836e8c78a3be8b3858bdce09a47916c63c61bb0324e30cc8d0384f7a58b4c9fd4180a9b1cbf3466239a39f3bb475790d51a3bc818506ed6a5b1f0a4779b1608d4ebcce1c43ab25754f7a946a6bb28770e95f693d720ac9d222a8b7e17c1432d6ef8ca5c8f0d8e0e4847e9b6ed79e916f12147cb4e53791b4aab1af7668b7616c6610f88f4c98709c316007ce8770c4ddce5eee867cba5ccb98857e0fa4d8ce0feefc14cfb4407e1396e5cfa9b5907472f4092844b0eddf58d5449c274e46e0a902ee5de8311f79f30b2598e9bc943805509e4cfbb3b1098fb7e3c3db89d45d3efec47b2f952b5f50c84a30e2dbd56b605ad6afea38009f5b56bc3e435e70a09e6c56f050a3ebbdca0be85497a6945566235f6c481c5795728256a8d1adc8ec43f943e9c3fc169088c48c47251132aa3a64c091e16cc4e3728d382086a5cbdadca8c01646500209efaa17a7f9fc1a00fbbcd23946c978505e72be7a9b0b4a09422c12393c196a4110d3463aa893ada51060cf6c82625ee07915abe1c280b41e1cd01128b4d34665b6ff3f7f03d93db956818f14cbacc919f6f653907e8763a966c46eb1a124b9c248beb03c5af9b736e8bc5430d07324932b23980307415445c634394cfbe896cdf5f1128761f926c852e999122d18cace0072dc82d97d14ea5b9476bd3c273ad63a99f0a25d3fb1ca5335a8970104ada964bc40206ada5388d15c5a73f92b41952a45824079708fe0823932746b4041d2b34a8255441e3df897d1fae722c0bf0483f73d5185730df9fa388c650c04b4f110cd52d21648e32ea4a204351bc2ecd196e5d5e8ef682b79b6b3032f5579d4048be749d089fc5772e0de49a768148b156469bfe02255e05797728208cc79aacd4e8a982008a1b156f80645f26e0e04ced996667cd4659aee76caa317378812edc87ba6bb5342fc10a68ddf102f6fc562b964895f3e971be7ef8fbf42bd65a2316b10c9bb90a8b3dd011f6d9ebb1d91d3988d6981195bbc6b9afc88728ad2c01d6b9a1a0fa119ec417de788cafefe5ea945367b2a239dc4de2c855353e96dc191b5728e1c695638cf00a580029707c063a5ed79940a022e15631d78efb0077b41818980b02292d6472ad3c05fe636356f01ac337777ebe7e87256823047b2b63b1e1cb32172e03d6a87faba955188f7a38ed47be44346e1a3d30ad5f491965947e7d72f0bba059dbba1f93eb920220bdc2cfb6a3e93198e6a2c355345e0ff3330b192aff9e3e853811acb67ea3a8d096ea35e88076a5564a6ad9f5d6772307f4695f528214501d234484918a4e5b222ba15e57b29754f49c2e722c9a7a432a3e9fc985e9fe8b406249466e649feb93681a13de4edd70ab0492bb4e12d6e410cbc6566fbd5d2b2bdd23e883c21237fab4bac27fab8b9b568f31ff8b15c1de1592790c6d6643d3581fe6c81354d6d6cb497a9cdd90cb8eadb0b65abc5d701d0a811426c218db2afc411fec4a0b000436fb02b13b9529bf3adb97f6981b30fa6f38abe9ae4bb46d84acc476b2e83b46b91718ebf7d52cfbf30000e3fcfc73d246f0989bcbbd5b7d3256c167ca0a2b6f72663cdba58e39cb00fcf8899ac20bfd7f8445e229fb3add7eb381ce596c347c87aa4b97d1ed66fd5c96238f864d7019f4c20aeb7411ea5263b282645d58319815b96ad97863bf345213add37c2f9623a8097cde129c8ff4ab71d9fe1b8a0d06358bba266aa28d5a56c54d81a3549a0ef79a288021b8c0a287547b762b2ec72c2aae733643575cb96992c04e28f5c6e9800fde46c719b655832d6bedafb82da5602f5a834f65367c0f8279cca8fe897951c9308756129528f9a944114df7ab6be4bfc0b69865239c8fa332af53fb906a1fe268f404005c08e90122485c37835fd8afa7ae8772f26db17be81c3098818306197cc0e041e778bbf4d3fed8a2058468df07f22d640e688071003206468ae40641283d670f9ff349e8e489be351759435008cf2d9d7759b7e7f6972f113c9fb06a40e62981822fcf70935e059282a2cd5a469f31da1a08e110f43c48fceaffa91ac8beb37b27d82de80d0e27cc832c34170dd4c25cc82e436c0a2bc88f7015dba9164ec24900dc7fb28770e0912440de7dda8bf4708844005c7cde19249fb97356f366bf12b48718e0c01853dd99516af4655cdbc36c45c88ede4573948c3a8c20709f2192484395794b96470e63338fba2b1b14e1fccdd4ecd5c004fec8b404e109acc770ff7fc6a04283951f77d364538d087aa5c5b4e9031c7ba0243122fc528319671753e926ca0b1e254e6d0728d61ee26782c8fddfca19cd3b7d4b181b071f1e6214f0e2758fe3529e899af85b67595714590e217b12798833cc8df24e3f6b8760d080c0287f2f66327b6cc487cbe277014f410ee12eca2d67493f59599f28a35793de9b527ede0c3275c4e3600182e821dca594da2dc2ccc80bfe76ba27b2f32d256195c0a043c0af66905742ef42b132f8d1667ea1647f3392b6bf7d5d1b32648e38be652fd0fc25e7200714c817c40cc31848886375a79afbde3a79b572f8267c90dbd8594e27329d1f240eeaffd588b380a3de29a56657657bb011fc55b97801ea919de502b2e05898cb1b121bb85c04d89876bdac473c74f0590d3956a6f319ca82775ad88d69763d3ca87be68a916ead8a624eb5033f3301903ae4118795089d40840501e8fa29c01ccf4b3218055c3632000da8516e39f823c824f9935881a1240ad9ba4b6fce7509a0231fb26b032509d04342ed9fe73de813ebb2730e9f2182b3261300873dfd43412f584f0143963f8a73f407320d73299b5ceed9998965bfa3c8399a951878eed8f1880d7b3ad89a1c34f51858c00ee8c4f6a1f7308f575726e3d823c8ad1cd1e168028ac419b89497c34f280790f7923f3e0fbc5184b790f88b3fb646b87289206be980f5b8bb63d35142b6982c7fa20f7ab4e34195e011a0113453b919e17a6e180694798c4804e0fe93fc2c7d20161da05e0c11d41c882000d71f9c144f99197ab2906c7c9f1d264aa9aa924eb419b465ce8c73c5ecab043051ecea9dc147c7b2677b86f9f7f9623d86623a4cb36ea9f40556b6c62983a548eafe8d72d88942faaa90d469680e6480a53c3fc40a160e00936c7e214378b7b2c4c2e22e7410fe5296b09d05e0dde7c83186e85d86cf86561c23443dcf393bb7a74235561a856819df134ab8fa2b53943ab9f7db88bea0c5752ea1cfed69f11970a02d37fe40cef0eeca80ef4ca6f0906964bbbc841d9762c977851f7461fad7d9dbe12b6646244ace47e39f1a40f592db4b6f1ffc0cf656567c4bae2383600c754f0aea506d158c6629d616f37f267baf7680649a9c54a5663a26370c5257ed75d5032f9ca3a2c0b3c9d90cfca4bc74c2108de6d10c5d55da6e52f2b52d6e9f375c23b23567a87a03a951f58f9d696475922f1ec5329e07ad31a9d7a6ce6aaad7ca423036e74b13c517cff3608494401c0a48378a4fe7132eedf7ea6c838cf2dec7638c200d7f8bc3be4082c49819f47708b3b4072c7aa17522a30e1d5ac6f6a3605ddc1f09dd09f8806006a8d3cf8fbc50b73eb91d632eee4ebc2a28bd2e1265d58076530c805d0368ac07aa756db5a57212d5a54bf03a9d9a60cd644d6a984c9e89e7f8ed588288dc4717d05ab78f2f8fcb01cd8b55206447654a5e3ec1b72fd4dc8699d14375fe0da098a9622c0160efb78200ff98020bf38a3229b607fa4a97da6af58420cb0d102ec755f2be131bf6ea0ae2323cf654a0926a648802f5110ceb619f42c967b3ec53026f28b6c16f69282d4a8208a9aac607bc9cc008d479ff094716d1b1b824f4f83521dc588bda7e0affc3823fae1249fc747ee984b75d84872df7788600462f6f1710a746dfd48f103d7bcccea8e637dd253e0b651decedbf793403dcb7f445a9af7ee0873cc3f4a5f39d7c89e2fec0ee9ae0d2fee162abb22ac634fdd456826a1757e189022ac906d8d5a3de48b784f7330cb1a902a8436c471768b18ffea909ebffd92838552f7ebd505c9c4111786c43b5fe1f2dc8c1aa9dbd7d1adbb5c2419e3f10ce479d05d040791a16bae4915ff0e0cf7e68e28b51d7815189064efd0fe0a8a1e5f4295a50f2ca7d303288c6094931327423ec519d9a57dbf07b83540ad7a60141481110b460728d938ee308d9f6cd361ab83d7245e5efd20ab3eafe8be76c63ecff0722c2af78c6d404f97f74554da1bf21e661eaf74a567596a7f6edc3e2d79cb14672805864e4ae3513e9673d867ea673aae0306979e13b7cf91c3f27301fde3629441b9c62fa03e672976b52f12c47f6b83ca8a4006b8f9f18b991ed6590dfa0ff38a44adaed6c6394ce00d19c1b7badc8276d052c4328e318e1b8a2ba7016e16823ecb082f0d93ff8e5f37488605824403e5410d8e3ec5297de4658c2fa189752eabe7ba650e4bb9b46f1ba8c07091b11752ece618b870d70c3b33c26a142398380189e170e25c666502311c2fc5696a7ab27c09d10287eed50042f30f8f24752e3790233a2fb1af5663c717e616f97527f8840cffc4d32625fcedfaad2aecda0b8a032ce1bba2e27696d9998a18ea26c29ef2a3d5f6938164bc3090e683b34014f4f3fb53d0ec904a84726816b9900d00fccabb90968ab08d37044259b7f5b1a7cbfee85717183c059aadb4e1270184980d06a5bd5d1852dc74a16d642bb0bade57b01392a7959297edbc627797723755783db7f85966b2f838758bc2205f570072aa72429a955eabcada518d86e64a748698da11ae713f86ced659e69879e21b96ac1528e1ff59be4464d4d13a8ac42e4cb981595b8e54cf8783961603e6cc60e52f1557d46b78752b5bef8628c43a476ef0cf87930d63e33702e43e50d0c8eb55e9a79b033a03affc4a1e4059a6ab371770d87c533763d4b98bbdb0cf8d0046dbedd0fb4b31b06a11106ab752b4405e373604e54c1f87ad81054643ccee25d64f7fc75d08acf681dd77cb41ee79cbe8cdf988e43cf41ca17573f58ab4d1fdecbdb8ab7c8e212cec136f492efd1acb2bede8e263a450f16b30cc1ed69b9becb960443c963ea5343109fa0e069e24048f5751eaaae7437b6a31a7d1a4bfcd9a7756ffd23f58a11ccff4b8e603453593a925ff53c5a6a493537a16e24f050f662919ffbac08a0a7b8d5bf9821b4d89e420aa376f0ba1606c98bcb2a44c2229705782fc6c5bc8bf922de75ba9844c0108a057817e3a2bc1401d45a5c90a43002314c9bc5ecc98d6a7920ac92782fe645860f9f77e29368e1487f1c51728358d8c01e56c5b72998bd76f6f15679c99058faebfc8d87a7a7778481ca0162ca9a031a84e01b5ba4de5cbea0d922a9c5c39996d3032e78e2d062cc8887d4ecc673d79a5a3f949b07765248f45144cf1875a8038bf5d8453456bc4a4a627e98be5bded0309901de61df47d299a9e275b3cd86905973fee20edceaa5d3e0c3216d86a1e4c1c5cfd2dc184f01c27f08c7d30dce6a437ee3dc067cb7bda01ef4d01193694d440bc193da495875e10accb601baef6fa150bd96798441d90aecbd2fa32748c7561d471dd07afecd2e12136698d28bb7346222a9bc791f03f263ebf791c787e4660639b666f20aee95c6a2fcb1081eeb3bf41a77749a23ac99e387a694a2ea5492a8e5cebdfe8bd5a16d6b9e5dd04767558aeb03a8eb7a17f4a35df2f1413736dfcdb3f31f46144319699baf344541b55cc5e3d8d3b2a7cc37a486136b2aba208a0ef3e5763f6252d454c99d1746119a858b6e536cf13986fc08010c91116bf9ca6faec40430c7aa49ceabff90face9a7eaaf6447755bf1627dc653e3e82492c645bde8ff2b7373101f40883e83ec645eca85ae8e3c83fec20e480e1088cf0555454b5ea99973d3fab233966b7d7daf161004cd72941f3b632d6dac9d05a4e3c4aa6b99f3114451e3e67940076ab7b8688141b68702d2939b6ea77b0e7c6346d59dded5eb7e6bc9702212807f5022df654441ac271c583182d19aae92236adf236cb3b24c2483edf29af5369fab16afba542a948b4f9be86cded76a214d17cf8bfdbad06138d11050afdf48f1599ff86b236dfb5bc6ba12593161a9d2031a21a91b750e22aca860b4da756d9eca29c54b08e095cb69abd9b25f3183532bf4d7d6fc46cdd3630a2c5359d08ebbe67815a701f6904403addc41c8c790b4548c6ab32668ab40767bb1e300f6b8424484e2494ca9f209fc68227655e9584ccf429c1bcfd6d013c21bbabb812ce88eca0c064b7161bbe9c254050a7e56321f538d9f994864feeeac64de83fc8e78dd94b96fffc15f890539a980dc77048c9871162c39048b42f656b9fc642198f755226f0087f899a5ad91b1bbf6311b7bbfc88dc728f39a9e333c7c67bd4f5591d1c01675ef9ab482b967cc2121f049b4eaf04e7bafdf7f045576e420c02fc85d38aae04e5678bc3b7528f7054a8b66662e4ec436e21b9256d5b72b00c18b56afbdad805367864ae41b1ec74314b772b8956ca4bd840998ba7ee0ffd237063f04db9c958a2eeac486dd29f1c65fc021838a6052211ca37e482e41cc9b2bf8e2411cd54b87c51dd90d6cce9d1635181128645ef7aa3a99710bb577d59cb140a61bdcd92fefdc3b0ca24318083ba10184579a74856a6a0b9a5f98a773abb1f0822852d234a82efe02d346b7dd234d5c992dc1237af209e1cdb96cd3838f3cd69f17e43658c84a367bf809cb4bc1185cb0253b2553154e38f22eba617f576301ec41e5c6cad2d78717c6c4973eed6198362e2a58343b270a10b4dfa5e28012c5df7c6e067d6b2710823cf8e5e01146a02ea8851525c3050ba9a0e1ad4955564e70a756e9e8a0cb36ebd91c6b27508bf7811da268d8c8154e34d2dec3eb33862444c4207a350a10a2818c2dac136d42ebccbc4df994e2aae0c8bcf73dd614ded7badbec163c8c4629bdcddb6945b4a99920c0a0507051e05b91f0bb970707af480e1c81f5cd840421a48a981b00d0de4c335a78f1ed46314d137361ba5818e9628f31c2fba60d9f6346bb2a90406131f013246ea20f9ac64be22b01401f9a44297144a20c1d689a2288a08400273116171e850decbd7116fd96cb6caddfc0a9bcdc6ea06c1984e942ea5eb92309b48641e4962b3b55213e6ab6d42b6166aa1a7a4ea8496948718dd8bc8977da2842b6dc885d2fb47a87f847eb01713807e6c361bd0cf131add2126e380b232c7e4a505b7ff1b3cc4bf29eefd4609f3c585b0190e9359482c309084040a833dec61b01bb541120a540601441e2e7ab74b86a28b1ed3893374e8d32bc4b1172147d86c36182501a5b5ceb861a5998e5a4af944a65cd566c232a7ede4cef7320926da2a67f36c2919ca36a960c235e49e3697898b2a264c039e018f0043c1417dc444d6d078911201cb8045903b26312a1bdf32954c8d18586456346666e018f01319343c9858c9318fe251300c3804f8050c8441809d601744b003c590c96cf9cba31acc75b4e5e8680cc23fd7eb69c32d4c813f809b60d997306efc864c7830f192398ff10210a39c2fd4673b7b29ed464a4156f69ee7ba17e1c3142219cc757a0edd3b45998f1ce5a37c948ff2513eca3e1349da280a5ca20f91a2100e0912b91493524080d296cd62b158ac2142c4a724d8a1bbbbbb7d685dc7f120e7ab7beff59d609c2ef088b7fc3def99df83ea6ebd15898b478a5016acbb173e388c95cb5269970506a11946397f8acf62e52c9952faf3753f3febffe7c77e9485d1df2398afa64d25f3d54bdef2ff775abe2d2d3588bb64e7debf8de6cb1e79cbbf455b93dca193ce39fb36bf8012347b4e19fc5e8ee36afff831bf7f68f1ff4f8f3c79a37ffa490371c95404631eab78ab85788c7e637417a292632d040c1a064d2ba22d9bde42b216a250a040a79cd7a59cab6ce48b6164cd903dc3f1380377c9fdb119a34f218fe53a1de6e2455c5ae9128fd0174243d89c13ca0b3e1e0846daedb36fdf392957b949e9bd1c57b94929fd89c2fd2577ac1cb529d4915ab7955a0759b0cd5d69395fd46789b74ceeccef81742e99e682942f168b4a8ef160edd8217724f76d43d9abeffb58dfe704a5d57259fe8cf22de7abd5af564afc2077aef9f25a60b17226325bce1119631d18b9f0d63a29077663e72c3e91f9bad766bb19951091fb5d5b7210754833d582872eb97f105d54e5605aa9554699412ae3f7838fcbb7e50323f77f3ef07a10479a519435345f7c52523cece0fbe2f32266368aa8189509eecd99c5722da19229c1281f15c30899950eab992833397c5d8020e7cbde30cab7202b7f3db041060964cca861460639269f72e4a0e1e322f7cbef96fbbf2d3e1e7c5a7c597c5800e089147700001c1f0ca36a0a1c21f8aec8fd3eb41610959fd367c557c547c5d7946d6e3e30cabf91ff69f1f1fa13a5f869c1f97c68315b3edb8703afbbe283c2fba0f03a2729a55cf2754e366b6766603018ac1b225ff7fd863eb3c398b462944cf20672092cb79efd0de59cdd684060dd1f020b225f5d17fa1431f389225f10962ee8e339fe2c798b31b3a261c5a0098166265cc9d81064dcaebb342d77e44a66068def06aea4961fd19c06a3c9a8b00130b05a39cd8dbce6479ee43788c934806262481fea985ce6401ee4331fa234c3d4b07154838121f60d64c59031d2255ff5534f7f02912dff21948eb031e134e2a06f120c6fb421ec096fc49f8def7b94d75d8be238fcd9565b74578ea359acb5b67ecfef6aebf7acd5da098496762f49c1d14a29a534a431015570f5de7b6fed0671e6199d7745d63fa5d41b27b802516377bf4e5408fb21c58ff15aeac91fe30be63f9af8c171dd5dc26b77ebeda8e8ae8481b19672967294724eaff85fa9a51cadb304b54e4129e598ea1494d25a99b83a27d30c449f969b9c6db29c1259cb94382951c289ff360b035ecde7e8773d4a5797524a69ad934e6fe4402a41d478c1a1ee2d3804f43cdd574a695db90ec77d0e72cb4abbd1b9efc55aad56abd50a575a130d48125da7abdda854c20224b5a196ab01a9c41720753050c27cb50d526682962d53325f2d6b9912d76959cb3420c1105fcbd370ceaf959b715000e1e33e3e3eb72e7c984244ebd3eddecc7a51748d39cba7bc24e75aaee55aaee55ace4e3e341f9a0feda7e06ed3ec81898949c8f319266cbaa638ff36115113dd9ae8d644b726ba35d1ad896e49b639f485fa926da989d27943019420796b0c9452ca51a539bf5417eefda8a44d279db2e9f419ae59831726ed584b0d43e5eca5bfc39c93668e4e1be69c94b37406fd20d749ab2b294992a424ac98ab84a49f4360d02a45e5ac1445db81ff3ef8c28b2e7a80535da46032173795cc0f32ab24563348ccd0d08000c2163142e0782063060d241a35906a602d7016180bbc037c0576c256e0c89143a47449ac42c70e494593161d6471e1e0f4c036f4e86e6c830f1f3d54417d3327ad735290b272962c7e969f73063a5aa2cc290c2ed72585124810455114118004e68a0157a9f0a62c59b2b0b420e24818b549972c59b2c829748871dae5092dbaa8800aedc2444b2965df2e573a3bb3a094fa4727d5a288ab0511dcb51dcaebb2d8982e0f26748a1c2c4911c5173850b2d6da25ecbdf676ddb54b58db6d84b5dd5aacedd672036bb3d8c0da6e2ad6765381e209279a602269097bafbd1d0ad55d3b674fa144b714492021816b808fb0b65b8bb5dd5abadbda6e2d317ebc0f5f5487ba947663f3e02cfe7b2e3fc2872944f945263a2b05048e1461b1582cd61021e2e760bb6a6fed3c8a3bf5614c5314a37eb0372e369bad2593139cbd4f5b78b1d96c3f8012e4250a19fc5e262f4958a15ea290dddd5236f5a2c30b487093d2ee4929ed9e94f6a4b49bab5ce5eae56abd94a3d34eebb35a6b91a89c05e3369392647202e57db6bb5e3016a64e4acbd159b1119806f80a2e0213e1051e02d76650028b172f7806f7dab00ca64cc131c0426018e01760176023dc829e3d6d388839a7c02cc0457805580538055ebccc39bdc478b9990b73be281ee4273a5ae2a475caa64f9fab951e35162d7bc5e0a849929280e6ab7e47e91c3a07b9234719ed9964beeecdf95f26bdaca1b9e59e4e45d05db75c6d3abd06f6724c28a68ec95aae22a14db306262626168d76777727d3610ce612a564325b3c4085e1962db52d394c4abf502626264aa7123194fb69a7924a39e5942cd61fa0fa0df17fccd78dfffbf70b189452ca51a539610f7bd897da44acb28a544a2a69d34997a60bf9e2834999982a125f1a22ca9c93668ece9c6fdcf81b63cc860db9935972c7d2d939af96aebd73c55aba71637c89376eac72fe6997e7f99a2f9ff55c68824d66423331e5d964b6cfa3d56a15ae9ac8f3fb63ab0ec26940d07a5552d8af1be2081c9c9628e21fb0155c0553c13ee0148e12ac81941a48d6d0749182c92200e129b91f13a9646a38421a6865e36d8c319cdabd404a5c50124a3190c043cf626114d0a0a15163664629c7bc4a03e11eb014cc03de019f00cfb009727c220e0e0e4e07932f6bbb7b6fce4040b312e470899e6a09eba063c752149c03c601df10fdddd82660219b4a48e2f853171c31fac71c612119ad1a76c78e6f821d1881129478b46773c395c3f69820070ec93c0c387f5c94b928f333e0cccc0401112ec8205eeb1ba7011f14654de7288daef3e9e84c900387669e06f49bb7fcb3d3ccd0acc01f2b9e85e7846e9b2da7222a53e44db7e4af1f2b30ef34ef33efef49f3a57abf488ed0cc105559793a36373ba0d08a684588bc5c60e83210e8c75faaf777990af42c9c82826642aa506a0439e6351e171c5abd5f095d05fe7892e7783b0d65ee4ed918f39a0da72cc7941ccb370536900f08eee11e083642c7209e858beec4021a5c0c2aca13e44027729a5f8140cfa3f2998b34a13bcd9614296f6c18ab378cdc7555387fdcbb70cae828c5674e2477ba1fa9d0e5a2ab0605b95015e2c01ac5c5205f10dc93fa20d8081d1d098f5e8e294f46ffd9c20c631319a44291373f9edcb9cf43ecbf34d83c593d0c78e386bf025977b652609d09f92ac80170062b00984b7ebd522e8c30e010cda79e824340220f4dd85e33418e4d99cf7c9685bf7eac74dcbb5d0da9bec1cfb3171122b8c7b3432a21df043b8be3498ee5b1fdcad8de081fe6f2a5a0e4fe1ed897c16e9c51cda85e22e558251a5513f4c0f9049c41730acd28433243aa70ca5af0713c809b910b2710527ecd843f56ec7ba63994208e27f965c04b03b26612a75f26c91aafc8388022de91cf083652d3f92ed88d39d8488d910ba951100d7556004f00553ab0ca181943d2909d6cf57b48a8d4a89c9e6e4fb0a727202d6140800081096164a44a6a526a7a3535c15abe6030d8ab0a119111520d490709e9d552e7f57ae944111252cd54453115d1a852bdaa4855a4d332474747a7429162bf97f3c98055685683640dd84f863410ca48d6f40f71f2d1c387a7b8f78109d08db115468e7608604d81cce98f01562832a79f139aad190c58a510559ac4e9a701ab110bc6fe99da82b17dc46a0d368cfd9e02a48cde00be095e90bac89af1d7459a0a3f12be0976e055859f530b76e30d365263b42135bab9b1e9b6b1a9e92ec173c0c7814334af02efcac5a60959b3951ff04db0bb37363702decd8d8dcd951eaf49c9352b1baa4681af7a35df6a65f3ad5ae82f3c33ef35efbd9a4dcb9b9b1b2ed549b13f86c7991528e7b549f224c75c63a5c99aae42805505561b5b055eb0c037c10b7c1d179ef0c580924735a47a4ff515e4c02725cb64cdb4b9e82f03ce266ff987306516cc3972e0b84400b871af082284610882b8c76bd6e6a8e1c845f72b3dee246bfc733c91635356737aff0914cb40a5c9513e030a000201688ecc18c9f9e1a3078eebc626962306c5028c3fc04855030b6a5035092225bd4042073318ac0471c3163cfd28f95a418a1f7460c30e9a9a76a8a3bb0bf9d855829e482364081a29a5f2c5420abecc60a80b172c3575e1e9ffe44b62e1e4092c2ae0220457d480870b0a9463548ea917b001acd3fc9ad2ca8554869b69399627c771a08ed4fefc3cc318ed3aaf9c7395ebea7def62d49bdf4a88bc1a6b5bee2bc8c228710f177260faea2d15d68b72c73fd7c9ae5385bb933b19b19e9457ba833a6490eb77183341e2fefa44e6b3e6ed4c18b92575a45f572ec831b9c47867f490fdfb939a20ca4b7c9c4fdfbb1e3fbafba8907e879776ffd486d4e887798585169ee0504313268078e282122ce810030e5a9cd02008275260914a51b470022b62c812d4c34c82073658019630840842688a1017ec5bcd98a334e2051eb05c5933bfceae524a2927a5140bcebaec9546e4daa1ba8bf2c467596a59e2532a44ea7dd2f5c3e5ba1fb65c779380f8fb2ffeebe0a9b5015e7fd786f2893ce93d0993c70b555881c76b9e6bb434c6e86cf2320c106240be1c6d13446103fda54341142df4179eef1b939037b0826c9c731ecdfc637dc41111b69a155d9bad2317bd97c62796908d95f25c70c8fb0f74a0896544a1679304388f17764d287b8ccada185361dc40d778e43b4f8731d6476d60c85859a8dfd557e84ba90a63add4a630d69ff5e7bd934e5be7d70ad4a4f567a573ce6f871cbba30a2e6619c403f2c6539035feff5705179d090d724c26dd9bb3bba7a35fb20bd03a618b2db9435f12b1d32fd93d46ef9b188eb13f86638c89a377a3e7d8c81cc523724c6e19dd039289dca1124abf307a3719bd679e947fe567f92cf9928827e55ff92cf9520047e6abbd837cf6551c7ef9448ab4539f714141f676a8d08bbc0503fa0d288806a199149c82c9315545557565699de98a68b4309c9e9c9e9e40f0a4cc66552435253535c5c034040529618464848424630a8d6e2910183a348aebd899dca940aa220e355fa92c7952a9aea351a3e8b3e050cc5730742ffcc74a857999ef6e0b0adc7b36aa4f19d06c4d21176794195115149a0b79e3bf84357a17b2c66946b7bfac6ca5aac087015f85e2c2bf2bcfa961f632cd15870cb1860e1d3a3e18f0ca7efc75651db4a36b6edce4e88ff50d0c158c5fc8204eeb1bcfe08b12a731979c9393f361d093966c4d4a4a4554a6582418da7c19cd5756a1b8542692355568393637afd681cdd097bce54a2f55f863a576aeb41afa0d867b6a06bde6394e5b52f29bd248f3c26fb4d06f370ac33ddcc3bcd9ce92920fe157812ec55b46de72fc30459ee39fc11faf798ecf3ca76be52cc5a17b71eb501d8ad6998b3bb887fe4efd14e84a5ee4464278cb6f34efc2b9a09cdca9a877f23c5909952be57676be0cbafff8f3d45605b154c8eb15800f06ecdb0fd0f3a43af5ce7991d3fce6b71c7e9ebd860cc13db4d6211f4cea7d287fcd9fc2e9cfa01711699e0f73198182dd28bd229f0af49b0c9c40bee5c78aa554d664d66c8d40a467048f00df116cc469b0575036c0077adf489c767aba3d01797ada69096467670788104646494d4a4d4d4d5588888c906a48484851842a91aed3f972ee3a0e95ca5d2a739608ad447ef8f8e1a9fa3f9c26930279d35f85ea8c68b668602d82590105e9cfd6023c87d61b9b06c0b066cbc6a686aba9e1c18307ee71da8e1553a7522950f2c0c0bcd3aca5f1d8d192070f1e5caa529894ddf1c1a42a6a07eeee0e097cd44ee0a321a8c455fa6f83e36ad4a04183c68c1042c03d4eabd58dbc2806ee7125bfd16e5bfca92a551a23c9093d2714891921f3c0a4f0099f873aa1bb27d813b8cacda0339ca1e748b2781ee6d2840bdddd4ef413a3cbef2652a79990a2ff94b192fa2577a41ff3dedf9c9c967491a227d1e92d2a9815cde719fcf28fc89d4f7613ff40f552b399b5b7868e04a8289e0e1f0de6532b2417fddea0231cf40d31fa7f35b9c3dda0990eec2b5dab79349d0f45345a184e4f4e4f4f2c6f8a94d9ac8aa4a6a4a6a61030110d41414a18211921612e1966e8c74affd0ac56e19ccd560ebb02499c560531fa3b5765d2e48e9d2fd56cb65445aad9e83f8346e79191b161a348c581433f56b877727964583c3221eb9db87840f81832f327eb742a30b12bf067f54e2ecf8f1317cfcc87f0decbf4d081f367054e1a70d2e634022706e7950f9c47b2269c75b6206b6ee6ac0bd2e84748d1df8639b6c8b10bf290594bc210f770332750715d0694709fe71089304198cfdf043b4e05fe5839c145b7a08c01a0c6fe98faa8040d1cf92cab110100800293160000180808060442c1501ec689e0f50114000e527c44785a3618484581240752184641108518030821c618630c324a3164560064031924adc3969ce090b2e1203210cef9bc2c5968a162f32c33a9bfc8420c62e62cb747afe605a9b258439d705ad6df22171d08ee6b691205110e972222967626d37077e75f7360c0556501c694ba4a2026a92346091fdb69ebf2ee9b86f3db6ec8f7139c2bfdc603a0ed19799c1d6740b1c81b36fd64f8f251290c4552c3f0f4c077c47749107015e006a0aa62d40f5e89785b293f78be0f386063fc4c9e407350eb4a4fdb6e05575d8835427930edd44c1bd5fbc3245906cd209399dd46b0e052e5503666460716c73a674114ea8fe7150c792678214e084a27b80d08b7df8875439886c239d3f61921f6941563bf2fd9cfe23f66d7c00913412c75e985b34e8d3fd7191511697938bd8832d40f29492512b4f55da26db82a86c12bd52c9d77a8a50e693b4b91560baa08bcc50d922cd7ddcc4a4724622d93851a90c345734e46e0bd856e1707b6586ad653450a2f45835d475ae509120ff38248a779d1a557c5fb198ae9c8121cd5fb52039807db12cb5a07853aeb14b3a7f1e82fa87472e13a9d9f9ca66313bad3a8e99a18cf6736cd0785a9c948b8720c56585580f394a5a123c9f3180def02b0fe44344b36c7078ccb701817a5004e08ad8aa8200b5a03a9f781c32a00575548a419a181697c8b8eb3e9102f49cf492982ccbb036e9578f3cfdf7a87207bb6f6d7f85d81b0ba7e666e580320ff89f4b771bfd2cf849b303387e2f8108f89b4f7d3a1f50fa6fbe780989cfc5407df88f67e5272d6db89cce84a09e9c2c3f3874bd21fef251aec1bd2cf1f7cd22d88e612432768a1bb39b7f6de9346f18d70c6847a93777f66d9082f7cfb899f2871d5d9968a64ccc49ec417bfc192b6ad61af4d90ee8492a514e445ffc67f4fff469ea5fe4d5e9144cc464dcc305278dbbef0f129dc4eab99d46ef49c61c1c2a05bc7e7df5e2ca05d1fbcd972cfccb3f6ded1a655cb016276dcbd63e8e377c643d6e7a1f66b0951734b208400340a91026dd15c6586a27d83ae83b34d9b0086a2b0fe96274600c29992c35717f575b0f5b547e697afbd69d59a872bdf71d8d959499be080a99291a7c5cc2fcd5606d988a156bf7fe41c62d418c55c48be36af5fc41c63d41ad55ca8bc36bf4fc41ce0d418d51c28989ed6a74fac16956d50db081299ead84e5cdc6b0c48ee49969b9527a69ba91441972f3243dc1c59a6d24eaac0b7f21d65a1e99df10ce1cd5452d41ecff24cd010cd58d44e1a9fa293be121f742387521a95b53da745747e4e5780302cc5eee9d1982761354436efcea999936ba523d2392d292eed2431769a9303ee8a6ea7da385e2d4ec4c13fefcf237471ea116a5d225456dd621d8336a8f3595de76caaed78fa9699bae3095ed9ab2a7da80e1144ee86f79891c31ebcd4712bac89c3f113574898108bd8f416b6b98604cf6b358883c63788cda89058b31e113e40a468197127f528dad815a70a5ab3d8adc88a39765443355a8df77d7496733ef5edf459765ad7b973c434b5ac25260b44f7b701a23c19164d9a0fb5ab741ce226e3726fcb5a7eb4e4644c6ba74e2e33928d8af897a3acca5b60d337ac422f7f55392217612119046a864c4835b82c758591bfa72ec9e001196ecad4127c19919c90fd04a0e54d4c0ffcaa4890e2870cc6cbb6c9fb760e11ae11f7bd39fe6ce47393deb1cb8b45cf9180825d86ca1992131f3707f0da6b9ff82b91d62445428936fb2dc4ad841d61fdf1373a6065e2ffeb013d3ab463042f6f638992cb690d35bdb2e934d4d6c5bfbafada9ee9e435c0922bf28731aa323a1445d8dc9970bbf8302c1bbf636380983256d1fe4e555485abf675f2ddd8a7a202e0ff373fa8f1d2952c196067d4a0163e85d50990d1cff7de838c0d65540b95a16b1c74d0e36ef8654ac67eb4f5037932b6d6ccafc736d6959f160366b499a3ce02e21d7a2b3dc342a1b1179ac28670b2f811350ab0c75ed7beb6d663075067c9a4f339e6ff48b50abbed48d6ff2ee844324bb74c2e4b9b6193d45214e706f68c73ec9c7a55e7412b43a22f92b1678a50dbf4cac8bb884e9ea39f0150481289f5ecb6922f6252efbdc68e44a26fba6af5f4b94e86f6d223b4bf440aa06b123382079eac0b8f5677e09e0c8cef1e452fabaed603279af6e31e4e16e13b99cfa1819e71c746f25e5066445a47302c0b99d95b4d54f4544c4c8c030cff8a4290d4ccd98f356f0f92e33b3bd716e8b89c0b2fea295409d3f31b8d5a02b1acf2f9cc996a4f9b6fe1745d6a23c4a655976a3aedf34248fdbb7264f34424e1a1652d1b69e553644df03bd19ec3923e249bf49769836f96f635d98e359dca99d13f22f8c9882c38d6c7e231f292fe0b44a6bb41f5940dde7a015fce1d47d686230b4372b38ad988b5c2c85e62e6cd296fa891bb5cf4bb9eb20b2931538199d07cadfd004044da9d09aa130d1f003ba8d02432b020f1a1ad05984304428c419d506899aff0b61e3584cbb387f0736c83e617ac63558b58a84e6fc620fdc62e14e3ddd87f1d1a119f0de2e8fdf3b9db9e720f5cb1d60ec17d8b9818268e3e13c0ee807d2aca7f690c294b6804090b04a53f48972bdce97649cdca6fa1602161f19a8da4ff72417bbb27d5302df93eed3219ae8aac6c5405809cc3e037e01019095357c1ebbf82ced944dd8e66d5e626095c0c3f2458e54aff97b4cd0bf4aec91a1b2c323cd4500dd0ed8da2934174d66d76b423b999367c8588a5506894062650af5d6c2ed44b4aad7866fb1ea3b53a2e39b200a77cb815c14bd0aab43a9162e1c8a5413ea9e1f29a513a248d2be11262fa3eaac10b44df9cf90a64afda466ff970051f58ac3627e27a2c08a16f6fdc307af9b488dc2551e2e9599050ee307a22ab4db7719d85068cbacde4566e6da683b67980a4ab6ce1fa1b02fb44edfe8d8ce1f65c21bad4e2a0672b71c12d396234d40bcef288d1c57b41de73cc3e4296af37482cd88bda739d6acb15451c6cd2cee7893b86b979248304264062c013efb97cd07d74b2f167721bd13d179ee74cec16bf0ec9744ccca0fb5459e303bdb4a8a6cb0caa587efb92cc6a8d8b2eada8a28f2b69335e2f25448ce50f5f47a1d206bb15d8288b3b7e490b4b06806a396fb1bac33b17d27ed8bd3552e1a8f2704a55b4849191cea37b36873950271e6e617e340cc28dc689d20cd23ce0da55dc1acf11daed36630782da73ce498548d8646d725f5b880d672ba1b53b99811ce3811cd96fa3403e04abfe490df22ee10f32a8b74860d248d38934d154bb897ed758368d3af613c13173344919610685a000491c6d0e598140cf27245906e1b26e23eaa4959c277761ea7bdc10344b031d343287508ca3c5698d9051ab20c7e89dc7985a8e372fa5085b20fe8f49dc179191cd3b6e25c72acb1a94c1b8c8551c5a2cea0d7bce2e628e183546650e2c3f4f21e2d86b2ad9b16a31aa1cb83c7e05c0f56fd4b2cd544b5490b27ec501a493bcd684a1dcc504b637a165afa2e11e258f40cc50128f07f28f38d2190790cbd75d64bd70dc95cd57f5033e19b25cbf43ad3159af510a3df45bc8eadbebb9bbe7686685ec3cc84f4302bb52bead9fef17f1773b3aca13dfa6b761c6f2a7ee9300fb9e5e58a6801976d1f471adf8f7c7e6a383b90f6150411c591433fd7c927ea20caff447a0d5852fef53585b68af71a184755ce683ca4e0c03118d802dab9bc9f251ead4dcd2a26c88ef1d9e3f53e3420c9502693448ce4d215e3456f8c9dbdd443a2cd762b32969a19ee0c59e93202ca8c90f23acca838c9701901ac552cd00c400071fa4ef5e47a120280396363ebb28a4d286ba0dee580fb7d57138581588a7d1046c42da35adf26ca726049a82a13c8e14239cf1172018d47c1ce1a39de93f7cbb1fd5c889b0ef7f57289f7bcf93d7df362e206c96f6a65c442872449a566641206c2ff51e5e1d7b3cd42ee95a846bf632bbe388fc62da45f38cbf58a8faa97e97955fc59bfcc875ff5982cfdd1366f20191976b365ca4e88f5be1e9d5d76b5fb90fe095173700e42a023366af53ff5905eccd972b5cb72da5af359db720c0f1db32cebb53b449345583d8cab12612da5732ed9b50fec1476d843872d09c438cbe45605d551a76ba21b358b87bc5174e48950e524b9090c64683b9c720559e3d49fe34845f92e77e2bd6c9471c5f201ecbc06b34dbc671403acf0cc82c5cc0a354605940b3b2234bbb82172a7e8e8a376f9929380a812ad63a856dcf06e01d25d0cc28dcc5129f5ef95621989f397f785ee7e2ebf6fd2d5a61a31a7207c04baa1cc9d5ccab0468da242f1aed2e1c31f9237ca5ce309c9e0b3fbe3b88efdc89325c79afefd7b21c452c0ceed8b0e3bd30c0006a66ae06cb49c62bc98224af5812ec6522c207e5ac869046481dbcc6dda092a1be516d2af0df6f8dad549830bdc5967d5643117352ace0197e8c962000e861aaaec3c68bd2547b650a54387b9d0db520007f5948134b07a75427e01cf9255d5ecf087d5336b9c18bdab3bbde81feadea2e10ce48eca379910104f9befaa6a7c1357541d63062767ff067289c61a415f6a120812a7b1c13869ed470c443580a105264be62f3b403c9ac338aaac1e3d6822ff8e5be07d25c83f2c3849f40839e8ab272d157043be5ca86288309723679cd415ea47c5d6605ff24713a153e326014d73174b72fa1ac6e85c46da5cc4173bb327a55eb90039d66cddfac72e7d04d41e172bba2000dc2253240ac898ad04d87c104d8b2ba94977cff1a23824f539999b1e01219091ceb9ea0f4d23b892ee2038e553f400bbb072ef142f313c7b5a741401efccb415edb878e937d01a4c79c0c1ad50e7fdc777047e31cfa47494c1b0d6cf062b26c93892bcad147c75bfa1f7d9f6fbd0fbd2f3e038a7636aff215722ef13912e801a7d0814222ddccd22c26c406388f7b1b461c4a161837fd366b69bfd54b2fa2a0194f3723be1cabac0f6eb5efb76620e9855a8c8a06db042c7e288d82216e7e10e3851cfa0a430e03b54fd2c0b8319cde1fbadbb8facc98b5a0fe23dd59bbb6176c480cf27dffba4968a2cd01b4e5fa08d2dcd24fcb4478c39c2e0253b0e97b28164c19b8188b0d38bf87bd1f670a8863e289fd2a7d447b9e84b04207271de99b4a8fa35df57a3f7ee1b4a5abf63a6892b267b81abf65beaec6b925c9ec937150f0d8c30f83aefc81e2df617cd262dfa833ca0dc4e9c37769c99994e398e7a9ca18a2123851945e9d36dd4f99418de40eaff1ad06e7b47e3e47c64150c77dc1ae47f24251dba708bc2819c98f8849f33722387514dab61f93f2e8518fd9fbe44b8c24e05b705364471f60ed99d17fe9aaad803c9b30a271149985c89f0985df406a05fce387198d23ca1202c5d302b46e6505e8eb8e591a43c83e427cb06f73452c88a40411a854dc9bf050fc66e9f8344f10afb4a95c3c8bede6de4e26785b6fabb53f49c89fc1ff35267ac95921c9c578e66d5e1e3728290c6f851da0e3f1871f1fea8a09adb529ea69076c6aee6fc37d15e60f97524b1a4f4149931ee592236613b6776e25ce6c7d57b8933285d06005aae71f68e3f8c00a561af4d1d526fb12bed377a8896f5371f4d5da013dba4b9e1ecc8f5002ac91ee7dfc784ed32a10bbbc4e927a0cf39363e194417044729cfd1ccb58fc28c23dca36d58ffb8a6f54be94c12ffd3f817d84cfa933f5d4f0cffa7b04f86bcf132c796278bca2334d2d10fb516a39e03c5b4eca019602aa093e13491aa9a002882d0f8e63013ebadf539102e8b0fdf655b6121f046790ccd08b174af7ebf8ff781f347c50c180105f29ed26176a71d1d0a6d2deb809ff0baf9291a19da530c2b4ebac56511edd201871f41365a5acde9a0a7ab2af3cd23779bfdfae8a48ef184406f92816ad423ab13f7e911a2c6eb223156589f34bb4652d10a7d3ebd5c4e8b2e7cf4a230d8925d42e533c08f959bc73ededba86f5ca6c5edfe85a0f14cca337d5dea1a7f877d9b99447356ae6c410fcf634deb7fe8c5e27493d863f5b89294e91132bb00ee074e513ad35e8232506184898b0325b0a03e1a8cd6a2cae876e0445b3cb3a883a3d9e985d829464a2ce5e898e2a70b6d07d7ba19e18c5852c4e94ce6ced62e9f45782c275efebccf6a928e08543e8f12e8c15ae869bf20238a5547966da47707eac61132547de96ad95080dcc27c5a4954619de8a445e0a84b99c5642e7f64d4030913c8bbf8b1fd043885e1b0b1f9866b12c88cdf2aa38f464cd4af03ad411773f5f5a421387f2972d06094c477f977014c0eb7e245e6fefe875200228e6b3f4b75efd5d491dfd0ce02563f059c6bc6d73016bab53e220212219789154ba9ff19600de0ab34c599a4f6213e1812b15683772bd1340cc5cc49963000a8de56e8725304a2dd297820762d1788f4e1d43cae4d6e57239a4c1e225b6a68e192db7607d371412076f03ef044b138403486a6e2175ad972c2cb75885e644d3a881e8108847c557a048739b73824bdc7a25d9f694e38c939636b0c50f95857cfc194a21ad1a3f61476a0aeca6fce7e3300c4981d5a4ef9e9d141e3044a78af0b53fb70c1ecf8aef56d4b8b1cb776098a4dddbae3c8aae5b64c49a6e94c50895798686695a988a5f8dca641d8caae1f4987545f273917a2e2b81a2934255d9a9e10aff4b21bca36638b032d7535646b0e3f0ad5c0a82f84e55589acda80308ef70e1e0390cd99a8db4dd90ba1056c1e24627cf6282835397af9ce290bc64a8a459e690367660f43b3874745277fd105ac098800f83c74697e02709aa38626ec118de47a05335b563473cadf2a4b17527624ccf51646f131ac448691fd019f9564ef53f13919b3a12e0a2a4616db2d75c4096e9eb24d57ea8f86e6eb62d3b1a4a5b826b82dc4ea6d8dddc77c44d7a5c9f78d29a0a80fb38d1e49303f317f67d9ea36ec926cef8fe33ba903d51aa3c16371d4e399200fc6e6224e1821193e5a1af7724c27f4522ca4e9974c0134d44b62f5104ee9a42c56e4cce164a3495e0b17d79f5865503ceec2d091c752570c24be022fd72406b033cfa01ba4eb6220fca7b00c1c694b23a24537553d7df562269fe70f143c94749d0d16355d75828fe06b4c1107db151200677529063aa3237bd8ca0770c4e6bd3dda37c0d65a86083465dd9c70deeadd22b88ddb4dcf752f656fa8578f5472372f19c89e85dcff0f7a01e1f44c9945746c57ab1281029b10b9b2c0e47226eb405841a2ccc778a6fc474786a13c0594595dc6e012b1691cd339f29798a0c6e84b789836c724f2b58dbd15deda17ce143553c14e7fa18c0309e9ada1328b8e7a516ad5e543a5cdc5061cf46969063049aa36ea3cdd60e7919a473852a74d67ac6965544f920b8707acec2301a813c270cf61cdd51c42af2f3506ee8edcc3f3223b91eaec08455643f23c79c36c760154b2887e1d4e6b20848b0dcf96208a2cc38a67bc33849961af856be0c8566c631a308846f2f1b629ee69080d214aeb7e79c29af069e2dafe8c0dd15c19cc3a5353a960faf40ceb825602010e19c71d72832e3ae72289cad5b0de67a4da351647ccb59faa8126d35ad47bbb2bbb6eee30967fc41a5ba15990baa82f0468fc52839b1a552133276298cb19d58f771653078cb23b9e2ade1538c84448c0355b90b6f296cd049868b8dfc1709ad794000364e2b2c45e4e1b511b8d4af0e22a3e7be5aab74ec01d7aafc09d1214056259fa2a800b7019a7b5d4290f32e94904894dc8faaa706db974147eda33365a0138c5227b0ac85bb588b15cb3ae57515988cba5e5fc58ae9ee6c737fa973127ee271b33b53275114f3a3ab31ed12a4d5b7ed5b8362c3066f41de6c8bab37cbe63a1c2b7c4b1fd522703c356df061dd489037b19cbb7e150a1c70aa8172c9ee4828e02130a86c71e185e3b6d167954f52057c0b91fe6b1f61feb15c47e7280bf5df2710d72ff5f5df261b7ac653d9d5661e5d7d89f8680975c19a550a7e550b76fb2f03418f4cf20cb3c2942ac1598ba33686185ff265e78dafea22830b6b51bea6088b76aeea2f2982ace2cb56a60502d905c5e6d67ed1fecec1e49dd7b228e85a495e82a78ef909682862fea017c0342d86e2489c11b2febe65973333386867e727211352228c283444a420678a9d0b2078b24199e88002e6c1b1cf6ed7c6ab8fece50cd95df2a4417dec0350b4cacfb07b816e588862e8cbee9db1d3ee1d56b77bc7292385dcf001bfafd4a17e77dcbd48407e64c5d0f48ee1bfd3e82a4f3403e5ddef895fd4b01b51a9feff79e134e88e38b4aa88aed05ef0e896cad4bd0290fead2d7916ea54325d468ab7aa0a5cd42162c5d3998739b6f7cef68c4e6370b32346a9003a7b19722a5ccc225a6174c5dc92f14c558369502c3b2711c2b08a5fe0364924a1547cdc5bacd2ba84149356ce4608f50c56534e4bfee99b74ea07e882b03620147a45b7f66a3f21d03272ad132fa42c99ed2f21a453b39bc01c516d5e4dddef95c8dd75d9d4faf8e99acedfa764a05723461ba6d708dcb0b5f7434c739556a408acd7836b442bbcb50e6c65ab2d1f39a2274320542e049a5cfca4d4553834f70ad1dc2b46738f4b6b8e3c99edd0f5422a48095a87e80892d2253b97d70a6ca1a19b5b6dd35830462182ffc25fae19c09772f1e1907d6c06bf18e76af978735524b3e88a8829eff89f9c4d6f40e59c0401a9cc37b7ccd8d6d7f6a063d371ae8d93273f98dfdc357739e05c635506e7d62d7d470405c82cdd49f1728b7b174ba8162ae1ffe096f6594b0ec2130239dc75d9b8c5f24c6232dbc09ed20dc296e09f3ff4eff864114327dc37c14e1a410cfc7ad3f3ff805906edff82154accc65d8b3610dc770110825cf52da76afec69dfc0270796e4108149922e77d55773ab159867b93c26b17e1b9720955440915c838150d026a11dd4bac9152d1e5126867f813c41f0eb02caa33d6adb23e06c8d0574f5fd0fd0c101bffcbe226d2dece95fb6a153241e84057cdfc2704d1c88a170f2854c94607a89c834e549fd691b4d5f0c33c070e85c48d81caa011d7058a8e5ff9e9e6ee26960dd6a562f565af7441b24c2c382c1481c6803553df5bc39bcb25a0a259272c3dad89605dca9c68f5cdeac5607145cba94ad83151c1e95193a0892a7baa9999c6c0f8942be5d6ba6b3a00ef970bbf250ae6a0de3402e688b511b06b6c5546715eb2077db8e67e342b18055b747904923044984e162d184696175bb66bb792c1d4a22420075bc7265618bf935eda36758df43ae8db6d6c3206bdf279a645d4c4b881fb1663d8a805330a845acba533d537fba28fa367bcb68888ee9b76dbdddda4226eed18339911ebd98b004286ac86bc0854052c694b748184139e5cd461cef10ed33f19a1b89ef8005cf47e2b7de931f1bfc0709133fa4f5a105b2828f10ba8c545b3f81d34ee76b9f1ed485f3c8ac64278c3d10d68c58811e16ed2f520c532275b73777cf86c918b024dd64cc0261fae9f5e3bda5451e315ea070c345217bd2bd08bed8c9e49dd11bbde51c8f4a44122796710bdb08d027c67461a0fccecffc6be044e9c4b64674955c90cca2e16accacb00b361a584310d7b99e1d61ed3aa26dc45a69ccc1845dcd843aa8a80572d222c6926c08eaee68b64c153a685940f451859a49b5348e0d65cecb3cfb4d488ec202ce25a4676bca81b82f90c62aabc9a2d21f7760ab0a1927475049cd0efebc90746644b73aa1647a11598703cea5010303ff47f48a05f0033617fe1fc9910446e0c910633a723d1efe17a3f36e16f7eb78448fff84fa48959a1ee8d8f24f449c3fe0aa599bd9b9deb583f646db511c34971a486e998f54177a274f4b38499c2551eedfd6bb5cc1d73977ab3505a29634650b2fe5048f0d34f7e76c9b16f30e9a42faf5eee4b562cb0c61673c73f362f6d6cdefbe4023f888c926586b07212d6f45bcfcad5eabd1775b102acc8721645fe081ac4d502643ce4c9b2b1bf2d5cf33c59f63e5d96c72fac272b7f98326a088a269e0292ad30c34cbb0b2b60ccfe618d7c595332248c4b90098c864f02483c16c351e88befa53e9ea4ffbab29b603b1c439948fbef7124137c1e0de6fb87a6a93a270815a9a99a8d91db4006dc7182945ac4f989cdc6669694942d874dc5b96e73f3ad49e0a33d1ded4e98a8b85199c41832c8d3d7b8f97f01a13b46c1696e20a9c065621946f7cb5668edeb0a8c03b8cdc9680d0f0020a67ffba5f7c0de7b6392972d093ad306e87a78a37b313199740e2a7cbc995620ae44e684a3a2620cb7e1e20e07bd572e5865b9605c576cf340a0d03ba17d00a43547d9950e7fe50943265bdf394bc903dfb4d51e7b07eeea69085b85bdfa73db58f30bdc828ae4e2818d69a51eb5ae4465b4e5dca5fa4be2d2b6966722d8283ead7aa08fbabb10f7dcc49566547aa99c46fc5fad10527e7f0367c0eadbacc5c43fea56bce4082fe6d44c6d2788cf97209512c6b60caab7925b8e1126feb7d72a6e4c9d8781668c39b49c08a1655836d1f645054593c6ef9e74b9ce79a395ca2bfb2c489539fa31f7b55f7b5b8a8b46a34a1f6110f45a48fa785e94946dd6db81b82260ed200344e6a820c09d56107db69314f2ccd476cbf45115e31aa54ed9b88a3a111abd2a90b3c70509a28b325cf0aa7a336888b993448458a28f906f60070c1c90e48da340858fd21332ebc418921218f8ece1398d9a2236acfdbe4fd838f3ef7dc2ae73968ee91117e3b8aecde041a8aabeb6ad3a92710790a76374110ba2c0dcc9b1a98f2d141418ecd8b0363e045888b2a97249b52231eecdc0cd0234c970867f8917e37826b0611147599528f8e40da67195096ec933e702a18b25c758873a1d75e7b79b210d21050944183cd15554ccd31f6b5005f3c266d07c08ea2a1e09def847d154b70e8e9ecbc1259929f43a9363c26fbd5d2f82c9222cb0d36071aa016b9f3051fe417d9fdd7a9e3788d6e556a7553879c4dc02450e77bc5b5bbb7c597744b0afef2663f35fba103aabaebbaed9bed56d2d2b53e0ebe5438888ffdf44ccd56fb76da41639f4198c6c8cfe22c1e430c1ee4105ff3a1180cc8758325857b6c9aa1048e061ee937eb499caa04ab67c55c14e341578d00406d102fedcc7c968f062ac423d04e49c0a6ecf0dfee81aadf5cb65b303f8aa8cf1c052a0c5b6d0f7fb41e3caceeca5e3b779e9b9aaaafb7df5b69abbf0fd00f7b56b1de83aad0a2dd6e3ce402dc33f5257d3fdcf1f8fcd1b090458bfaaaca76b59ab1180e4332aa5aaaa1b51e296f08bb7e8c454b8a29ed960c0194055080860588d6acbd957e52825ca2dd06984d0ba84a17e6faa48ecc422d9ced7dd63a2439d37d994dc7a3b166630125ba53398764b956bbf69556a1db3e18653d7687d868f7b8c54fdc5f33909b53fed74aea4f4988f84080ff4fb4905e79f83a74696dd516c16cae897a2301691291586c10f74608122fc27bf638d2b406a3b3630c625729592a678a01d55e865d3664175bf059bb356679e81271e90542d65b0a7c3dd6847ce9a7d04962be0cdecbd224612b9ba0263e9435160fcb597d755af53a6e57ee46aed325a3712b233aec681cfe34d9da7706f8696cb0a3dd348872b57a69550c28c8ede746e8c8a184e583109d9f397399372e69d87ab45d21a2b726d298e5501e60a910dae4ccfe6bd9ea69e833f6101fa3367d0c035d7672e04d9b3f5fa01306a8ac974d6d47182dfa7c51fc8372eb67c4ff9fbce0a359225e5ae1b964f6813997cc0870e379dbc601e58ba201ee74ae54a72154be8720532d9f153e2abda1bea84850c28e08e8c3703eeeff514f981725cf7e1cca03db0b0b42e7247b318c1503285ed086edd4f12f24ee5da657c747e79cdb13fe82d1d2a775a4d12046354def644bf8655f011c3028571797c741d230af83c88c78bf3d1e918ff0deb58c4e1e6aac221e6961727041672ed23942ee6a3ca9d6aa8ae3edd6d7aba3d85e7cfc7be8dfb23adb5d6b66dfcf844acb33cef27d3306954430fabe058abcc4ee66533c19abbca8e1336cacdcc4c562ec144b55aea3a3bf721c942cb8200bfeaeca6a9e32542df887d8fe5026c640f57131463df29931c2a43605e3f8cd1ac60168b4dd4084673157840ff9207aa3ddeb21ce85e2580739f0a9a74e6cee604325000f32924e44456148f35a2bb7f4e8c906b60b1de5670d43984ba43f5edeac1f74233233eecb6de9c8c8b739fd98ce6b818ead9b94772912a3c40d64577426f0a68ced06d05e560dea67d9105a183b49567cbf33fa9178a773f376459f49ace2ab385b32cc6875ec31e0f0fe687904211e98c7bb6854e2f8f7487f611cdec53b8e0105b0c71b9924f4f3b2eb03ab2b59cf6c80a00e30e547f85607d0bc99e77760e2055c5cbb401b942efd67226e619076f4707c65e9eff1adc75c92dcf4f98f28d0ded7455590da93984a42ee19c843cbbedad76227e0ca60e10309e0a0cd3cd3f9a950048eda731082dfda105103105f4c33644a6a03bcefc914d08bd59fad796615b4efb118e34ef38415056cff0bbea9c36c6ea5da8cefc9221447dac1e29f16ce8b03d5d51623cc8595a661c3747a4acf897135fe6bf70a1e3ed60e4b4024ad55b44773d52f7ad35d50c7209ba8746f6f29dc1a590f8f22b32676f4061b2b674c37939f8665b7e1f87258b85bf9e9f40cdcf088149ee5f6280abe71f79a83a8b11a97ce01b300209c9f3ebfd843f71bed31ece980f636d6acb3dfc88fc57e601eb864664921cdb83f79577ba504f30e2904f12a2852b9f456838310d6fbc99769e44bab5427a031011f961824291327f2f08576f5519faca9e206968471d5cb978a3f361827a9b50b6fcbe3a2d4a59580cd7d80254319b1c886e781ee8b82a00ece27d1d630e044da861f1a73d56d359013217e7c88a359afea66baeda586c209431cd76e4b82346c5ebe14983f81f092fcc7b405c16141db1b7044cf21f92bbda590c49991f1ec4cc786c235f5ae90f839adc98b6e9fe7549132dff422d5ebe474b3189837ae8e3f55a706d0c1ca608a00e636b91e1eeca6e18cf682c546fbb8e159a52187bf088ca02e844c6b8fc872416c36c41984a41ffe4565b24f1836c71ea42b00bd51eec46f142458ff6483128f91d14a8032148b811a6d5a7e662a51734e5d49c2f5e6f4417f19c37d55ef14a758b100a0432267591e58ae3d980fa3a3f982f7df69292d9f375465c391c975f8e170020ad2f95c7a5d0fee359241ea4e1122dffba351c8852b48d5d73e25a15a8d25e1472d538f8e3a5f25a4a562ee097726dfb6c4941d7301822ff2c21391660ecbfe9540cc678bcf4cf0befd279fb87d219c333374541d7f67859de75ce02643b847ac39f72757e407f4eae8fcf2f9d687ed5d389923ba25ca631e9a857504705dfdb3e4e12a636ea0c85dd8074f889b05221ed96d77618c91a495f9e2b8ffb6a74ef2e276c74bff478ff81b98f1517df3c5132fbcd1e54d1b1a991baf5662feba113723e8f570cd0eac01e7f9768a2111d3c858c719eadc15b6dfb6fc693305130567370915b552304a403d08024ed335ca0a8837a95c9027996a1bafcaa69602ca74410a187bd600a481786b41011fa473f381019234a8b2da849e6b289ff4a5954665c19165767328bed473c098ee4f3f2225689024892be0261ae83770c2206475f58d330649f25f8e205c2fb3eabd469d0d278dfda319e457ff915682a0adf01555529d39b7dec151db03c722798e10c98cdcaf0a585f83f17304388b5132c6362a442eacfb62367e1b78c641b11c36d8a6b00b9f3b191529b08892f2ccc7db0b7ab0a94007b8b1c1ea85cb775bbf6a5e3fe64b07499858ff893e1f95b3d2f76229eca8ebdd419bb8ffe21586465304f46c53fa10a590f4d0cc008adb0496ca6015e379c4c517e3883796f81b2325892623ee09a72e14dfe1a3d8937dac10c0cbe7aab8b78868a620fce48186538514860ea49c41b501733545302048c61dacc376e66b902e059d72f33749360c9cc5c44c466f7076b9fedf5d2a6c965d5c9e0397f630aa4e336149329ab22d4a673cc44001078ce7733bb5cd8eeb90dc95cf3dc7c41f0a5ef6be9308b374d71df403f5c6543ef039b4d3b72ed5b6d96a13496a93fa8da93178194feb0731ec71f942e234a965795ffa07219e93d5daba3714f62118f929994f6486da69ec21c475039c0c902f9d8d9e351a256fee6b081b98cae254ea72e4d4d6e4b6e7a9b0d9abaa332a1734df15bbee49b3876d9e480db4da87987a0bf7491543b95bf308a4257d7dd56f18883e13161ceb107fc6ff1f9b9cc70c82872629fda4555c2ff9a1b83c7a84a4a89b69c57fcf91a9b888ce774fc2f8b05b8033a803e95975e1546255b3beab031fe82f94010cdf471d0320043ef91b8b226646049437403edd73c02e36a0b3e7a3500d1655e31dd5a95853e9467ac77b3cdc4287b77bd53808399f524b3d95688ce371a4c4ca40fc62a3d71ba0fcd8bb1b1ece429fcf5ed859bb1f6d56a8f62508361212a044dbfe2f183b572618f555945799ca80d2c1844368f2c3c417945a0c4b506bf4e19bfd6625ad2ea934d94d855daba2192878e3ac2074fea71c6d358427d495ba9a3b58d762b9c658e4828782c5305ee0698db06c5ae73f1ba6395b136ca7556409c46cc35750188507ca5413bc3f181ec7e12f9de0e62d3d852ad97534c7809a36c0fa00427b6336edcb4c7737a0900797b86b576c73749af2ff0b658fda9a211fa6d08a0cc2f7d2731bda7e146e60db7359bdfb70cdfd83ae0c3d1a5470d7a5c7cdb14134defba1144703ca8ae43eb131cb44f87cbb135f5bbe5b306586df1c01c541fc3b5c18614af351667d8a69342dc1cd0031dda3c40604bf11141232ed9cc07538238cba8707215b9e17bf7ad1a62e39225043fabe742c9f6781ecede931d29d9022c6a0a1e11afbd86ae64936bee0fe2c0e10968e8a147bc97e216d2d5c070bb4db17326c23736f1c9dcf559031a0cc77a4b78e2250a14d7bc4ccf03fad329de3776fa2d4e4721721d5198637033769ad3a2a59bc3727fdcb5e31cf4d257a24e202c7083945a9f64438887bfd86b2cb11cce670e1f5af12adb26add60f5b956b453cb70193ef64da3bd0060c8aade1d70be75ee2df8200081556893bfa13d468fb3a23068d0d2c245da12011c42706d00845cc0b99e8b4146b3a96f49a82fae243f82b289ca245a360ed27ce4c1751bff6cd3b57ce9d07fa409a822ff4826a7b8a588f72b7ca253ee0a2da35a3cde61665198f73e0263a490e1ce00cc837eb4b52a365192bb0cdf2b8707a48d94f5195e6a86a01cbcd41ec37a1f8e284e20d39a4eae8ee01adc01738179977cdced8efc801220417c08fb50d5e7d8c6c190d558704ba0a40cdf30d2cf899d0bba02b6cd9234a73ca7de4610e36a12c029a575de7390a43080f184aa5755cac52b3d6eabf7b633664848cb401e3dca414ee7f69389a39dd1c58b691706fc9eeac990fdcf18a340878db2815fa06573d6aa4b2d87e6e0e60c11a07069f3affb71904168a6a2778c6694838b902fab08ed466b805a3ee4950891c8630c22a03fa278c91ddd32c859b0dd65cf88623f09ae30e552601a9cc1e2f5e07dcc212859c1b19e273dbb1de786fc80c1c8aa71885e7ef0b74f3e183968b77c2b935d05a647ec07669d9d5c53e03b4f0ac6c37e20d7ab7f3041ff4a0ab5300d01c4f8f08ee44bf1f3647e82a64f2f3868f5d1a4c2f0e5706c75bc93da7bdcde14a31f98aabec9f0e5778d14fc53da031c2620fd0a10b9f4503a284ec59dee18ac8007e63dce7e107a64e1774c98cada2087623cdc57e7595c50f17b5ac21e18a7d86b640a81dad4d81406e25d704af3b187eeb457975209df41b4f7ec050be866c6ef3a845db4e474031033c8e945bcd05a59238365e531b358ef055539037179c8e2d501e46bfff25564936be89afea752767c268320a81371265366d3818c6a086b5f136bca15038e814fedea0f8aa1a750ff2126bd501df4c0b30eb7ab36479acc73e52a85cab76142664c717284e7e4210561140acba566c501a8621420c79b36b15042b21da0fdb505df6d0a3b46ff9f2d612333d112e07572816d05d2bf303540e188d7464c89d145d7f1f24e1a57de19b05395c9831ed90fc791a6b3a29cace8a2da8cdfc7e0be3d42b98e7b83c285fe2faf6a0e063225110e9cad081eeecb110a20765d01bb98869fdbb43abceef88ee0f6fe1bfc2aad2bae3af2513c9643d3954e686f2b8198cf6d0277c6d409865f46eb3297731ed979d68ce932aca9fb53138891463dee2190692fc6fcd1b878018a2e1379fc4f5070445ae455333899681785e21c53a13d179768c74f7060a9ad726cc9fc27a70f3af4fff021a78701bc1a666d5cf8442f055c791f1800e40932e82f2a20acd301e3f63e1a7696be80370085a814854259c7c5282c7187188b4f304b6e94ce26ebed0c596cdbe4cec895ae16eb937ff802a82582bd13d7aebaae20bb5d26fe2ec3103a4a207a86b89e98fba5f05929331f075b1d0c489f197215130ab9c45645e23896d048a93db46cca606a5c21736ac4e24e5f7391015db847182099834a9b5ebc72a348ceba8e340cd2931157c5a9880544f064242927418c1c040bd888298a04eea30dbe2e5918a8f91199d3dd585d4b62a229e2f1a8233a9433e4504169016342a2042934e95d55c475d31055f835cc3d43776c51b07746b2ceedfffd522baf215cba43aaabbecb1228f5091328f28c70079a37cdd72e50bafe05052c378a936b8f635cfb38fc421ad39452aef3e66e68f3c7aa0897410f1de666764c2545f94a6eaa4aae18541bd2c162c389aef50659f480e344da90c01e10b61d03553a32d29cc2f6ee173e40224c3c8100f42b4fb190d54e9782354230b6b06d56ae9319903e730652e2a4b2a920d06c7d5795dc11a53c8a722251c94e537a5a81ea0574e35ca1e66684ecefe4f0ea799964b4b2795abd8ed4e7384dc39597f069016a0486159b32b83cc32d12b64cfa168ec4b042efea6a1ffa0858a1802496a9c11729d39ebeae9645ade322bc5193433cda8711fece1163069f07c48f2936c632cb4e48de1a5508be59981ae052883f89f105a39997b423fee09b6ed05b83e8909559487e96a8fb3263a85083894d5404c0fa49cee0f2fd8183847c0382f7f110410917ab2a1f49dbbff4588566effb4b84577ec1f29abdf9592261f702e27512996416d2af521e1e67744722c7527e4680b89f9f3482346ebe191e29ac75f2ec5a579b79e3b8e99791be0a4036f5f3f597143484dc7c81c9926b20c5ea9a1a5e03fb84aa40f1a945ecbe6ef41c85cce1858980189058ce4d85bb47a270c78ff34a6ea6ef9a8ad03b8391ad2f2f2971ae31b0eee3546b4bb96eae750f72c6bfe4eb0e8a12c403e62e3af6cfaa739e1086700adf8b58709ed6986d6804de997814520c800ee982e89d1599908ea078c04cca1563a0fa62289f833758d4ff33e0817fa3f068e3d27f516eb1e6920493aaa358fe469ce1844243297062c823cd316bad9a7704a991898d38a0ae05662b38622f08c00c2803408348997d5d57f84d5624e8d1d50348ff8803b10249d076a9ed470e6ab1687450a87b29789f65fd4d2e9ff825601a3911ebd449ac9688e9f4919d1d5eb6f238ae4cd398c32d0c0a689250de4fb15beb3635e20af704231f59584300afa6c94fcdba479ced39541f9c5250329a777a1d0cca195ec339f15b57a59ac6a1f2eeeffcad73f153db0cf2aca6197b8c14b62032582c0570e8d9ff78ec26fe25683d544210d0c87ce591c8613bf0520fc0999f767e2de8b97e2f20eb8782b7dfd8b9f16827e04a04b35b374b44f9c4d66fbd0868610689ae50aa38fa7a466fff0acee5dac3e666462b2d0f71ce2d1cad1a589f6d8c0164370f32623c5af3c6364e3e1440d4943a5fd4b33e939683bce71a62ec89d377acaf83e005898c6e1b337c61c80e3fb282e5c881365830493af10e29018a38c62f21c8a545adfb51d2ffca0405a7ba0233dce9544ca62230891bd915550c8450816416f4367188aa4ae173820855d33d64b310911bace2885168af02273c5a701f390e408cebdb6e9c5de3388c0b163614bdbb9cd0aa8347b05e5bdd161d3e8326478997fe539c144b06222eedb993d5cc89ee1e44448bdc256801928fdad988d24c7217a44e4f494a727eef75462060a69dd7b5ad4f4c9750d6d77cf49c020183461c33a483529103fc29412e72834e9943d38cc9b09fdd664ccb53d243c22259c1159d2b185b5f0e30105c6579176b8370eb2c9f4fe2802f97c2fa3eccd88407a904acd08b0fa1ea63dfa2cea9aabfcb27b7cef08b914ed81b5485a6876221a56bae2af965cc20b9907472c87ab8ba2a9355011084876d847856f6fdf1f5261f31d8913c91deb503307c230dc37230e2a0ec0fc9ffc898e677dd5f15edf8468254cb32c434ae3055408137c77b8e1a252a053e0f24502714dd36c1c02dd82e22e4aa111055c302080f0df1fe650d485e9d290b3f41708461f300f1b1745e77df5a1aadcb169fee1210fa7a9f7e5d3ef0270b4b15767a2245996c89f01a01841bbf2a3778ec8c782ac39bca3389f1e67c37f5a6b6e34cf373ac4ed49317f8434dabbc6d50cd847fabf2272f7d6526c03e4bd377a724bc4767664ac65d7d9d75e2d99a0ea77a248e484935bd383f579584a74629f3995db46dae68f73644ecf743c30f8d11c392bf8192b0a8783cae7745b5ded81e43d26624e10471386b14be5d55980eef4c7dba58320d9c0a2a6d221dcac02bbe7154c5cad33d0a2135a82f5db4757a1e7f1270d530a3f6187725e792faed02d53eff2658b2b08aa9f050b4315e5994fa83a828eff0a8f655c1bb3134a43e6cb9119f5929b50368ef97b860cbd9c99506af139641fd91565a4d73720c6a16db9189765379f4b2c3a91b98cbfb16bad50cb86a341623b045204c89d4a3b1e604571418c992c7ed77a965d1866907a6ab42e1894081c62815737904612806c591648f5765b401e112e3c47e7a205489caf43473791ffd8030de4e666a772833522b9a83a55bcdb5781e16f50480e63a3a8db86809fd9be0e350ef0f59acf469b433ae332d81510c29d94bcca623789521d2e5aa836b652ee2c2e7b29e2f42b73f766792dcea477202078c84299dd5b683d2813bf3d8461b1cb3c922c0db0ab75c49371407e1d0c433ea46465da20c74c6046b1649ff96b32b633e3a70651e6b5b614811084282d667bf099754271e43a6c4b49c85d8063ef8d21f072cd74cb84484597e8ee6aca73e60d68fa88c8d4b249a1582fa6825439a2faea8d88180c6d4e5182cc8af2484e1d9f42f2a9833d78bdc6a69e89811b00d8d3db08c951fc1d0163cead7288cb4b8c47a5f217a6eabfeadeb2593820be160fb849968c2d7c8f417f78d611e4e7b78f8b40a2e23e5a77e39ba378e2fa11a66f671286ba5e0ad096d25d9259f143c126185da761be05b0e2ff49d46176e330ea939caa5f5c80d8e6ba947edf94461c958a77952e93dffd0aab13465ba62e28cbe88b64df04115b2dbb83c02bb95f5c485618906eb4630ba5c8c965a8cc01e6a8aad8a01e26a21d53c7ddf2fd51e6f9aba3fcc115e3b740b79831cf6f797c1ac3a6283544de0ea8d3dbd474f3c7141605873201910ca6aa497458d3ae48828f9bb71196355135bdcb3bb3cc5a11a4b68b07b13002c7caf275d1329b889780f12a7ee4283521672c10b2732e086774db643ca1d0c111feaa991a60aa30cd4d08549c1371aaa48c26e13391429ee6b9946fda9908656af22a94a61977d7e3983c2e6aade340d2125af08a78e54eeef4679fb1a62aeb2e2b4ad1c55b309fc6a405691d1f09651af0e6a84f797a5f155c6426393250825d9cb59204704305a914a6a23dca8e21b1704b2ca9c32a9cbb43557e96913b8e86b74a6b6a433db23819fc161accd935ce8bdc6c859a6efc2341e841462284c28e1523c28b30e5d8095c12895143a25f84afec806c439413b62e4a1382e3504b7611d7095775415ece737bc27bfae0d984c2d5d54d01ef952d865a2e38fd37f720467132692a5fcd3eced94c30d20955a537f50a75c2b687363f56a12e5b425d73aca08c0060c2d5f7abbb1f8f807662e825c4fd0de89b763637c69819362cefc2c58c48f54426e560412632440b9f8f0c34339ba187229b356084086463f4d6d5242c668676c17dcb2b6f11571659a9d6b2ee6ff585f0fe9ff68f67db9bad7f7943bffb4bd4e18621e65f9e6db748927684a169784538180f048563d1f47785132a0e57587318460225180a6c9713645d03ea6a2a7f5a208eca4a07f70cf54e1d9ee90bf0f9da339bff3f4a1b8478391693e1655f4459bbe4fa61a99a5c9dfec6d8a310c419030a3a8e38c1c93c5c8988b704a5f0b76367c4556dcee36ecb3e51059ccf1c7de893bdc9967b4b99524a019807e0073808e2c640dbafbf889a73d6443bfb526b221afd8ca69fd3c4a9fd6cd543346a3fb3e9a1a53d25a95ce7c61cb5d5a6ea569bdaa8b66796c2b196dab38e6da2902fe0c84f1fb09671fcb8070bf92af734fc00570965abd564e52727da99941edaad9602f85bad9fa56a4bf401275698291367c99a2896c80147c02071820b104b2d90bdff8aecd4959bf4d026c27c489eedc4009cdfe4443c6b9ffede640c43da931259cbac63cd33a91e9af590166e3e0ab34e5188147dabea3ac695c7b3850c53959f5597268354fd8055f987bce518c0a5c920a5f673ff16e58141e633bbf41fad1f22983ee5fe4871641fedc2f6600fc17ccca4faf1f80b8e32472947996351cafd48b9a760c73d9d950bc2897468736cf55f707dc07e6c514a554a99b457b67172e3b8cd7f0703fc41f007e26fe44f827f06e4f67aff9d3b5dec9f75a4fde308a4cf2aa59cd5bbf627d52b299ef7ea21988f1eb76f11e6c3df95545d4d93c1698241b66f1184fde89f3fe5ee50ff104195cc2c8e72834811e6a35f7c35975f7fe153fa9c5e80190b2e1a0c52fc4093618986823653d2190d05561288dc3699032ae6a6c264db85062db5bf880795dacf802e2d5cd57ea36d4adac50451fd60171aaef416a554b9e5638b2252fdf95b2288bb31de1f980ff9fe3cafaa2b3c99034eba3ef497eaf4d3215e2be4da3527e48d793fb9dd735ad463b1deebc2ae5f98b331f93b1ba8cdeac4d6fec89c2a51d8d63be7db1fe9755b1da8fd1bfb8e6a6f6c486d6e5156e16ddb76529e973759ba3ab63db76d2f1d8acf15a7c559b0902b682da5d27704157d2e1e565d5a92235ea892c69b919c719ed78b4752122ed04289274b3c39b38400b88001e2cc0f36bc7141992a4fc02a408464668702efaa58752fa87e90b374518ee0c1812c8ab861218c962e8290b50d2816b0c06429891db09031220902740ca4a494883753f2785569c1934454912329cd52854897858836922661229338797d8c968485242ad43188c5c40aa268a202171a90e8e10563b20c41850782238aac79e28296850c03a84b430205923045f8482f13b3c98290239a945439d9826506afa509624343c493275ca032a5c314ba4d176010c5a5e985186867aa687a4a224396294cc8c4d08eb420809a53977682334c24b0aadb33bb3e19a45fbe729880fc3cbc5bba461094f413628467c55198d545212b77a5747717cbcd0757595b28d4b16db228f3d3a94117063190b8a17cf9940a5121e66eb2dce436e54f1e7a3f0abaab08c84dbf2abf879cfb51722c6e3f723fe5c2e9f95e8eb01f5c29ec0757f936aa58a508802af52c593ebb7c4e9e39e7fb377ffcb6d7f4cde5e2e85eb92ebae8024edd64e53e10041fa314695dc2de04d82e660147504a6e35603d1800119051700c5429caade2f99c9bc55c2ce4f45de36cfcc90d1b55a660ca393d0bcf5d4a20240f64a494ee2c7852ba5f711daa6c1b69849b551bf793e3388e931c0cdc74b93f1c47822537508991db945f65e86a500adc1bd57777f66ecbda9f2d677d366f81361b6f6c5419756bd5edc709c3f62b7d32e9fdd9b6dfdd7626f1e6941cdfa8337435a8052cc166a3ce2ae77cfa9c46b82672729b646d31e79c37589d8d8eba9cdb3669f0e4cbf5e601709b96fbd4f747e99bc3cbe62d57fa0b553b13db868a7bd0f6cd03e01a83dd64faebd6e76feb87ab4ed1dd8388f8b6511d56d354a2c1f2d9abfcddea9852865f3d7435380598d3ea1bd5775bd923a9b7b75137dc8dedefcffaec770bb4aeba5e5d71a4cf81ad1f3f2e50e97b28e987a5c96c9a49a6d3fe2c592026b57f76b3a6da3fe9733fe47b9d3f5b3de84bee69cb07ed1edb833db422abe9c967833f14530ff1efc6e692cde13fad68ac0e961106eaef0983e7e18dcab59c53722e27962550c669c56aea581296d3fef4b38cb00ed692056a1613396335d59e53a7498db73d635755fd3d5add6a024b6482fb789be044bd647dfcfd49f50fd00f8a2b6dab8b028edd14062a8a75c81af7fe52cae6d06189249524b33fde50d28aa4c2b2615147cffb4716ebd8c4a9c4406436078b3385fdf125eae8d52daa3f174e2bf7f19f294cab3aaf466e8bed0a95137160d6c7df4cf5a782a2fa4f518db3915c54e796aaffb85d99296c0eaec8feb00e6ee945eecbfef88fd36aa995ecd0912d53992c5105d6a709c90ecdf71baa4baa44c2fe7c50c0bacbee395b9b30d57f0b81844624748c5bc6519561462ee37ebbaa91c520dcd35984eda28bcaeffcb9da4764a10fea90e735baaa24617f76893adfebcf5f1de3699e8e357f77c57744c0d125c30a1c5db577c6803c4b753660ab8b301ffeb32be79c73b1771f961d3479e4de3fa97a93d57d085894fed6ee67d815084bdc6ab4335927bedcc70747a98f9d55ca8d301f5d8ff6877f95a88ba56e22cc3fa9ba38ba57a063ec40f6279422ccc865dd738d525b903f7e0ca4423bb450a2a2a2fd49034e2a7afe797890148b7894402aae268bd5bd91cb3a11a904b2ee3d2860c7b1e695eb04dbc37ec8e767710c42c520b58a6b28d0ef70a841b9268b45a75351415141353143edca655451731f2a2a2762346cacd254d57ab09294716194ee59613bd54e50fbb74ee4da0cfd5b6b39ae0b5bb69cfb156764fc0ec7cfe65896ccccdb6f217dd128d03d27960036c28d2e64ddb38ce99375cf24654bdc17a132de03c8b86524659d5844051d19f72d0b9582a99476a53534edb369281bb04dfb6c0d4efb74c7d6a382247f699f9d5a401934cb5a0abe3825bc29577eefecdddd95e2b3764b84ae3b941df5a6fd5ccbeeb0abb7cde6d607491b3b0f62409dbab42174e87951b94d94524ec9261b4c156d08230008e3142707cc2c0b104c1395aa3056edb3b40795a7147c8575692a68a92c1cdcebf6204365189599e55ca241695472d81a4f71aecaf1d40cead853b95f1a2ae7838c0bc24be584b8a17242bca9dc6f2d4e2a2979b07d759ea1c251e7976942361f4ca1ce7ff970a6ceff1f7ca8f3635045ea8482a24e2080a8b3071dea64a5336386c8c89c87a8ea5362a83ea56a872a6a8cb3aadd575c1aa6b209a903d4feae378c0bb8c86122946b3870a9fe5ccb030789c6ab19d50f505b38c93a022a68b346053ef430ab42d6fe446d375f811232d0fd42ba3f4ac14d494949e949269fee10adf25ba2ba0baa5327707812abfdb20947102d872540b5df79db80ac5c918498365fae5e20732354e0706406831337c8dc89ea48aa7b13ae5954e47a31cba93a7e3c238e275a6a33a9366871c31250984089a2769009426875275a2e31c04069e9062a4ffc20f326aa372172c9dc5b48c14d992a6aa0a0c12273f1db6102737c004717cfa271a2a70aed888244ed7e9088142874a85297a6039cda5fb48319b005c5dbd5b1f5d6bc31908efe71cef90c34df93deedcfb67948f76793d265d2e496c2ebffc06bbf74e9923d6e5eef9622eb98cf5fa3f24e6529a2d02c141888eb6743b393fab444439c9d6c0effa62826de9573caddfe0d8f1eb03ffc439c0802ecc35fe3b5c35b7bd32290fde11bc0e5a7e0120971b2a5ea6a0b245b5c21e37f2d9101ce58b91287b6c6c9ca2cce992baa251c413d7f86b24681414e44838a845efb00581d1fa02f9ecd315f75fe7863ce0af690fc42b70bee3d4e92c0eb99794f5050110f1024961d26ff0d122adac1a3aaf603e9588ee844aaf687f07d3b01bf1fc1dd704cf2fafb28a56147bb053bc659d5279a6ab062e6adfbb558de6ab913b5af81891726ffeb7c1b423811e8355ee4008edf0b6fb1bcd56ae20aa4e14aed1fd243de62bd8b394b805c977b57c758a8ebba1ff45bff01fdd6275556384aa48eb1bcdf967b78d4d5133560205937977c5afda3e43ce84439a210057b686d48f221852c36306dc102c9110a28820989993149285981509091fe12fabfaf3fa69e97e66aba9bd043b01fdcd3f92e181ca5f24581c81a05b6d50884f9cf95e5fc5854e7f3bc4e087bafd33cbd789d10f6dbf27242af6b5731c83cc5a1f114c5325e7edc0aa163f361a18ec77d03e0bdefd68fd6c3e827d51a2f80f4bb0f42bf7b07d0efbe09eb9e9bd010d6893d641de2545b218c7e50edbe7b01244134ea98e7041cd7ca04215d48427746c0aff35969e24023441148cad89085982502e10a9c2a9e4841458911998e88043b4206cb1191f80915cc90012564b0dfd608258022839a24bc300a00fea887b827a16373db8c3a3637201d9b517458217d00843a556050a330bd4c47446a9f69a5fbf93ca7767efe64751485593b5148053a36a3e88843da67bef1fa11018e6b658210188f8814a3882ee312432421cb81853aeff1d76aae66fcfcae7af24e0cc186182a687ca813227d57643aefd16ffd05ace39ea7d0ae738aa6a5f9ccd4443464f3b9693e3799cf4f0c65b66c7ec7d5f8f92438d1ebe7d32f1c7d6efde04ac539827c1e11e9bbe20f139138c0a26526cbf96d8dd0812c329ddfd60832248c48429623c2789e87f3843a0f0b3be9390b42277e1d0b47596bd0485373fed5a449cdf919546aceb6a933c652cd79d70c5a6acec308e799baf3e44acdf9174c966ace834dbed41cfe7e0613c363230a1c570ab6402627396e00332e60548050c3c91954e44c2cb932b45b6c5c01b27e8ab01f624fc7c420dc5329e4a872cf9bacfc4b83f0d3a71e65061a4950f99781c611a0b0fb337695f59ee7e2178e60fddef340e0a6c84996df83e0de095e38525943d71260f7b2fb0eeae998f79c387e95db9d61a97addad9cdf1e621d3acffd463747ebb9ef36c7f200ceff01be7c9707600d3020ffd401c30f70e57fa1f36151c75650fb38349650a9fd439a68672ec15650d5096155077ca103823a5735f085ce8cefe9211d1d9dff7ae8c5ebbca703061c79787a72441172fec5f7f0103ab6f32ca794583c9d7024e7ebfd7742d8ce833bfffdc2f871a562b81e09468c1830d8003122a1840c08d9a934256f5829860b4628c29aaa1a4fe3fb8508dbe1d13b45a1d0cececf9607dfbff82f84f97fbf752784f9ef3c570ffc150a61fe753080a01842c744982d0e70173aa15058d4a3fb9e8e75084b40fede190431848ef14f07b87cf6f59ca09d38e01c83e8fe70ff85ddfe70cfa164208ef532c21a5355c6d350725565fc2baacaf819e1520f4b73baaa32442fe4f9adac10c63db73c983f3ae8a28b2e8e64ed1ee6839f3e3f15619c5824ab0c53900201080925b8fa05e555cd2a4f55168fba7ae0558fbeb63ffdfb26066d5832b0196266dbb68d84f105cfde6c8e0e48581f71a9664a1d6b21903ad648433ad6b19e0226c93c188b49b28722e75bdf8391f7396fe47dceba8f68e47d4b445af1dda7ff1379b632ef3d9132153f5c91c30c2a9ca0a4241bbf870fb02a3b05556efda03f722f849bfce0a4a859731804aa639df303c852226be9588d30a0fcd9416d0e66fdc842d47e066275b0c4284c3199b73d7befcd8fc3e6a278e256a3e29ebf37aa6d8a7b66274e52eba1a98d2a86fde9671d23f316e4b9d008480d33e098d4fdcd4d6a4b06da5e06ef3d5577f7f93c5a7253951f14a68ef952c7dc95f6c71bca7ddc01b24ab1a1744c4a3d60caa628a5dcc795800d65ab72d3728187668a6e6396c62461a2c6c44cd6dd9fda18a831b35d29a52b868c1832b47c39785064cc9031637b6a2999f19af19a4c5e345e343614d0a841a3067da1a8883ecf27adbc6efc8d8fc18001e3c3363468d008400f0c06eb110078e3c60d02c4a6be000420000530800004200003e03053cbe02783570c280828e8bfff34d80368e0d260030d36f80d843610fa8080808410e0b1212a222adaac90ec38519480a204d02d9b150d1f72e8c8616606ead8a163c7ebfb8a62af43870e0574c001071c74c0a3830e3ae0511b00b5f362e70b8206c397284983a10a0c3fc88821c34c0e1e14193364ccd89e5a4a66bc66bc2693178d178d0d05609c1a346a9879c10cd30b6e6ac06ac0aca6ded0a6ac682fd082b861e3c6d4996fea0be599545ec8a1ba6efc8dbf52c7ef05267066cc982106200dcdc8150d1a3402d0a38666a40b6d2a079a91226edcb8418058cc27e633f534c5880d30f8c9e02783a9a5a924b3ab2925aa450640190035694df19a022808280824137480a0033c51464b99a932335946830d34d840f6802e30d9406803a1579929b43256920b5a0b6c90d05a708256a6051a9938343259cc6864aca66864d238d1c81841e6094ace1672d0b143c78e3546785a38b283831d1c449121421b0303d9c20a0ae04001696863dac8d7982f63d0806f6af09897695f54e0e67ec182f6762f588cb2ae972bbc4c515dfe49975fcbcb13a3f4c20410559f4b97525661749053ce3046c2e0504709064e1d415a982634305734306c6860bed0c054410383464ac9d1d9b66ddb04e6893b56c76a539b06666977a4b42f69769f685fbcd0becc3ccffb5ab42f280071083393b5a88eedd3ea55d88a4b0b339581c7dcdc639ec2a0690fe3673c6606a5e07cecccbe2bdd97fdbd7ac7a4131e83ccbbcc454a165e8c08c8ff7131524749ab149c3ac6b834612edad0a496a8685bac685b6a4fb42d504cb42d4eb42d4b344880db676b57ce35cc3cb7292e8dbbfbc6d5ae7c99734e8e7269531bc2c1b8509b2283b7bf0cc2a0bd2bf7d4c829e7546d28d6c570599a1825aed0c45059ef5b313bd4a589a9619726c689d5f280c562b538c05dacfb17289a981842ae3578ee1ae872559766e6aa8e5cf7ab4beb92a6f24b66e676ff9c695b665d9ca82ee9cefb63a6aa36971bea2869b53675a495cb939e66685dc85457c758ac79fbdb338241407e29703bb64cc48900cd8b93972f75695e98685eb4d4a5757953c7575d2f56ea8bba342f522aff47068c144dea089a94159a14159ad499af8e124a1d5f34a924b42d33da09b248974959657825e5834014cae41bc92932f925f4d067a087c22adf845a957f24b7e9620a641334322965de14e0b68c6413344dd0c8b665d46686904149d6f21b0a2fc87d967b97a00eb749ff1b569800dc9f2d0077ead2965822d6a52dad802e41a4f2ab32f3bf9a480c593f38c5411d7b7614d2a90efa0d1ea38e9ba68ecbe600be70aabb11662848b850fda53f95a2fabf906053fd3f891854ffa059adfaf378aafe20fc50fd812451fd4b98a23a0d0b17d5ff0423aaff10364c4a5c3565d1e2c4a4ba962daa3f652955d66eb994dabfcef3056f35d8a9fb463c9c69c24d1114d4804436b252120d32d094d4ac7044098bcca138537364235fe1a9ea3f325575463384ca1cfcf9bf9c4b1c2cd5ddbf0499bf1017a904bdcb36bbe77a8bbae7d1dbe4421810ead1aea34bbced19b7a6d6d7af0b299d55555172c47935ab3a04c17d7a56295b55ea9ed30a5fdc44ff0b61db27c95c2a846d9e245bf7899253f44082fbd07fa41e8c7a30eaef1ea90723effbe9b3c496880482fb882c11a9c87de88b484831f7a1ef8951b1b1a87f652cdafdca58226c134de818fd13dc87528fb2a2ef52f4bd46e977c8b3040833ea7ef42897ea21180456b644190603c21a210a4be427f7a1ef68d624f9c23ab8d2974eef513ecb40ca882294642b6320c6fcb0021b292387b3e63ef45d5680a34755ba3571531580ec0f7b4ef4a1d0836b14ee476819f785b0ee6ed1e87b2a22f1701fffef91fa599f24f3beb00b83dce7e927c9901cf680eeb937ea9efb6d138db8a722d2bb8f3f972473a32ee70970fe075b57b632d67b22eb59dd30238fbed771df7d92ec0b57b6ad115ac6fa2e04c398cffef87c9e52268737329e024607293220dd6f992db2a22332af633dd7282c11a904b26e064a71e4a8ba25c9966a8a98bbefbaaeebbaaef33ccf7be266d6795bb440c28585721942cb4b094eaa052ad02c9b941b17d47e49c1fe57ffc7fa83e615e012d26d2802d236a8fdee361cd9d2ed9ee77918ba3f51037e4c832296e7799e90a2ca123f1d2c20572a72e226c531e6a4ba482342f58af2b6679c4f554bc7e47fe928716ebb3bbf777bd6573a2675f86f3bb1395a9457ec8f641d2e4a2b06924ee028a3e44c4ac99aa492554ee5545ee5567ee56fa4924b71290ee5513e7329aff59b96e23ef22905c1d7eb3b56fda51371a2b47237d244124efbc897944a1c3dd26aec3755ca372f29d0adde58b59437add44cddd44efd24c5642555a3aab2ba6227764a828a9aad9509270c612566e2a6a2221e200021c1a97cc9d4249de4939422a128e8444932f9af97cd278f052f25cb9150f294ac280ee0579776441bd71153edb368d21cc1e6f52b4c2a29a5fc6d5eae79fcc3276fdb9597d580dd4d29e5669322a28a90d2a308a8069d80303e9d6e2964c58c0e770c28a51f5bf9d22df8e4b27285eb61450bd8b3091187ea8b93e605dde6129322b68c791d9992e319e1454a931153a814b158dec0d046054068a1c595a351d30539af408a3aba7888c052bb4a1044e0203f3056254bc62102863043b88053025f75694348416b94ceb2b644ac52ba241493aafd38760603227f7feb527842c79a29f6c1576e9242299b24b5dfe5eba24ce008d667e7da23860cead8d795c20a0d539a581402038e41afa02e72f0e5a15050c76065168ba0a00ed6a0a93d95eb576666d115be567c815f7bb8c7bddf5d0b0fb6a80055a4f497cbc131a8f672b377909094b34d8dc7b21a59ac799267ba564769653563e81a9bdadf53dc6f0f0e771762779ebfdc33d531aa4640ff0b0a70046b5c81a3b46a1dd6a71f882b2bb5b12855b0d652f667c2a6c09156b06e5fa03cf51552260f8e1df27b623be4e2f720018e3cfcbce2b8d5f1c8ab7c9a0438f6d3130f149e34a092ebe5215411def6f4ebab52483b559fab03c8cff5893aae1b2993553e476d0e4f27053c3cb4ee4c81b51c3cd0ddddb73772d9e61f240404c8e24b048f00471c754708b18e05f5d0b2d8c33f03280416e180536368118711e0f7d0ef042ad052fbe5cbb69773ce79140e6904f47770c01a6c40569a22aa9d3631f24a133195ad46a57dfa5f54e0d84e3554004a282792cfa663fd56fbd3f28d5c2645a412c8e4cb76729f869250eea3e47babb1b18252fbc1222feb0101fdcc5fe51f5e0dc25f4f0f5899f9ea0b1a6274772d9686581155fb911e0557aa317e15e92505d8ff856b40ff71df387f148ff2923f138b0f037064255733931b3142b342a976b7b8c8e6c2982e58d47e5791da1fb2014756ea9000bdc97d48c78e3ad6efbebbefebdbf2806b588147437e04db809ca68a26b597794e1a0be6744e53a53804e9280669febb32d231f0529adacf4c2cdb9e1a568074c94505d8df4a75a4b0cfc1933efd3a52c0a5a56952d540312159a8fd21f098024ded6700c81bc21e207fbe8ffed1158611d0918030c480e3ce281d695d9fd8570e0ca9ec525f1d71bcbe29a4d47e70be14208c9f4a916be7047c185f982645541d254d0aa5da4bacd4833f5106573267a61efa5e8890051bafd1f4d07aa162a96e15614d550fad9729d8d4adee610fae2270b1d4433011b858aa6dbce9ecd3d999a6b34f67679ace3e9d9d694ef72b399d36b9c9229bdc64914d6eb2c8b6a5e1b66a6ae10de26e20b5f6c992c24ce0528aaef6699db953faee7697b313090c1b29c0b091821b376edc2421a50c195bc890b1c50d390cd981db7b860fec8f4e01ee8fd4a7c0e1ea86eeddc6026e527ab38880614309860d25293edf7fa4535c296efb80dbb18f08efd9150c1b4460d82002a6dfebf00973f3076a90cc95a7ff14c3b07656f8ab8bdd370397c5ea49de7699f3757a20a5d84638f160dd8d1b196e96af9c9fb626a30db8fbfce369066f8bc0c95475f8b79503653255e60fa2281385d4762f096463916de412252eb4a9626d154b7d7ee44a50c7f27a492e50486049022a0b337159a45801330989492e5454592612f327d3246bb6ad14595860cdaccc26de62c5d26c6a279f82614c3b6d96cd07b831cd8fa4ecc24d471ce5b0b8bfa15a6c38b26fb2f451bb5c3b5e3069b961475010777b2864a9bcc4622f212f2c41317630c38390202cf248ce97acdf2a51b403850b9222f188eb0749b579dbc06db278e4ae0d967cb1c2e572b2901ec965d6e9e39594948e26d38efaa85f0530b303132f219f991dccf020e48bd1f852059598e4c2a5f65521c40cb5a5f2c614e1b454b30ae1a62aa5571f1c0c4dac08e1262f9b1fc93adfe5e423d9a59bc2acf43501c7f7f595fe744addef76601d50e85377532a871f78d4435b802aa090addafafe276c0e20fbc321107153fbf85321708716a82f5153b3ea2f0532018eac04fb21ba96abd47892a3c9808eb96c025190635191534fd343db557e8475b027a91cca8ef9cb895646fde53dbfff83c9e18854f77b373cea4a453fc24032cd584295cfc3fddde7915d9dfdaf633bf6703580fd5cc5f1ab2cd74bb000ebe0da21e521d04cd1d0689129782f9a80e357298f5fed6ee6313c385e31d0f53d5f3ad63dbfbb876f3f9d03db532f7c00c75eaafb3b4bad0447f29b8eedef98c05d86fce44d263b794c85a9d4fd2a0f8e1ef2292c05747d4f10931723d2c5062a51508942cb1a2fb6022fd3aa94aa9aa26013455514549d85a7e342c7dadbd6b9e257bcaddb3306492a0691cfaa411685a024b92b6e80c5914a2aaadc1f4067b08c4895694ec091e206364cd38854a738f28669aaacca4146bc2f0bc8dfddddcd5e42936fe714da9fa08ef146c1ad39395fbafc6dc311ebaf633ddb7ceec76dfbb6717b6e7bfa23c76dcf05e1bee6dedf73cacdaf72f4e926d24fe28890e2d7b19ee28db4cef0937586b47ae8753f706873b1693a51d64351f6cbed1857213d6ef9c178c9d1eefbfd9ddfefe9a11c9ddf17399a5f74b70f6fbdb72d0824318122b2850cb41ecc4421dbf5f1bec396387ea31c8fea0b1f912a8cf79df045c8d53f581620c75aff8d473aa1ecd6e73d7f4eeb6b7d9e277694d5d1954a404efcd48041781fad482c886f8b4f1802c089a9d029415dbb7f448c0e2be3d37eb0ffd54eecb6000227b52ea262b0b2010659111a02a526b22891d2e5055460d20e57c8bc59c1042b5489234b52e2848a1d8850522b4346b62bec8c075d3642c4770ca7ed19c287272e224e534fc7be99146ea06458c8f641f9821949a41742b06192490f0bd9fc24d9f8aa1cc6f6a77f064e8762c666006b9e68529a540cc0cd0da545db6b466a4da0bcc6cb92853a52314a6fdcbd0badcb9521685d7aa075b16109ad4b13c771ecbebbbb4bebb2c4e58a55868b14afe1e537525234120f9ea8a864cd56953d0a11cd0c000000006315000018100a888362a15024cb12519e3b14000a7294446a482c978903498cc2208861180682300622c600820c32522954450747b5ac8456dfd38d0c38a2dd2a253c191c77f9232d1d20a39f59bd1bf026f4223aad6f4d0c8fbac4d0ce4f5183c08158d5afee1c55a50e9f3f4deadbbd102f797e39d1f3d3aee8e05fc19f7761055f8551b55229d9d393547b7bfae35c40dd7d6347edd8364ca17e6425db54666f6fd5439bebe974effe09dd3d895f27715f46dc07bb8ddf9e01e3ce5e6ff3dc8d4a55bb2cdfe766eb45c1a1e54de35cc324c98cf15f2a32ce0b0c8e4a8fad36d55c860db56172fcc8d38e13b3043afbb32ae007a1db494fab32fa89d331d90f006c1d87460d70079dc53e22aab8d054f7f1f34e81b58d91fdf6c1914904759cf224a95e7312ca3d9069b5df79c39e4a829657f85cee1884b64d2174d00c5bbe0556426d98f570b7c371a0017462857d90b903458439959c3578af295337a9f1e120bb0bd166dc09219251f262ed894ce8903b0ee8ae4e395fd589fb7357e742aa6e797da352f93622be275791c62bc43ab8b7887a42f09037f813c5ecd2504b7e24b7c63e1ed1207e2af4a75d83e0cff641566e20110710dde09145c0745038c3c21ef274804b595a6b9a34ae6f087d7b92a45c761d158a1f4a8550e9fe74b0c847d8b030769e1ff84deecdba212dbc1923923a012977a97f8fd539b8c20b4afb01371808c4ec50cd1f95b28d23fa8ace42141044b5c8f6cd12358bbf72d0f3b944efdb88e322a572e847d1176a29740324ec252a109841d0520dedae312e12e6b922d393cf9568e2a1464769a88b1524aaa333039e42e754ccf4fcb0696aac68b29a8a47cc66564000e132c262ad7dc2f1ec3f52ce7f298c2ed61c491c78c355b415f8d489633c33586c692a38502471e09105c5c13e84b3831b90a587fa35528e8eb3b568f902eff43612333868babf0cd583847858c18d5bf61f6ed9a00c2a509f2a65eb938260de00979013d58fafe8f547e58b103438b9e1aa72e71f2d34fddfc4365f6853a7cb7e4d9755e6089adfe2614e6035587f2ebb869ce7e3ce40e81cfe281f69f448a8bf9134cc9c0889cc5c40fbbab887387e7b154a9e7e02c4b171daf614461a9f4cce381d03b8d9c1e019478cd7b99ffc331c737be8d9ddb3b9712ac07880ed11e0b7887e5ffe8246651456882740d15f028d848919ee5b5d8797006a733210e8e554b361ecd93fc92373441924d70ab602d1d93f4154b168fbe5a12e9d4c4b5efd375adda6218c8c99e099c603bd85346822b2c043f4b61c6c60d9d96b420fc810c7b3efa1c2f436fdf149feb7c194de55d550720071d69c34b593a09124d4f8410c16fd44475a302f06c196cce2ce30075a640dce2dbd19a1293e09946bf8dca8daee5c51e971acaac9f991994da606c58880f4afed4a28e333a0122f8838dda24a8845250862454d085759ed6cab664713a60e8b6c0c4fe76795516363197a37bdfef2ed7e45bb21a3dc139d4352046034e565ad43955b2a45a878c1971813b332c03ddc4392d1293a1acba6f51ce84476ffe030c376d368c6e52cd9753060b7b1764e54d583fe4caa0ef970cf5137fbd94a08ce85b681fba9f4dcfc6411a223fca2e9efd9cde5672cab4247e9a11c9983b7a45ca1fa24bc5254ad1cd596832e0c6a71e19c04b7e5b74cfdc38a85f205a8731b6cdec33387e64fa82f1da7c9ed1ce787a824739fcd4acd01b18facab865e31adcfa8ab514445cc20070bac19156f279cd3218f72801df636aece5f44737b6c99f48d286267727f67b816282e09041a7178c47b01818667020c6dcf1801b253a63e0b88369b70cc1d12e844b8f4654953bb4dc3d416878b78a7189d105c950d6fdc6843834eaeef31018cb8a6210ccadd656c6000897b0e29d9526a28fbebec0045d613f48d13d99e4f3b986c96ea451b0532b0a3d86d763ecec211dfbc8cfe0e3c1826645a3a9236b0bbfe8a3de768f9a7f39b384946907f49e351b53520a20c3f7279c2603c04c72bb9d544c41af4f6f48063dc3ab54877971e662c619e6f3861db6f12c14acc3203637d718984dbe3d4798f3033b46505c196036f9cd187cb4954fdec4ea9f1be0f28120236b17e656bab08a02743a0167c0e9a296b2e5e0f5c8a1d667934729c4cab39fbdad928c3378a20a2b4bf5484a6c46d81f584ae1fb46ca6e58332e641d62e2add8e3daea9e4832a8b1710e8626a6ed7ac8e19a4530bca48d5c45d74e2aaa08e739a8dd4ac75e3c4f24d93f6ed5a00dd23e14c36edacfad3e2d5584e1599e33920aba08646464670b8854db84712c0f2012a1849b9aaf9f27d3a1cae9e27613fbb333bcd5d7b1759e70605ef2ef7784f7bba6a95fabab7b3dc45f0245891b095bd8b07d9a6755542d3b51043770c245b1a7b1d80b0b6aa3357d5c8da4534dfd07e5381a0b7ea4303d93b2f47ac28c52a7ed90e025542fa11b32029d3df58dc8391430e915f65455a83ca3da30ed90839d0251a6923755bb0dfb1d9cb5f7982fdc604d0e661a86b6d73056ab4d36cc494ab03210fb4650c72d000bcf0b27108582a3dbdac9bf6100e300b5efa780c658151e0477f8005603abe29ec0ffc8630419b3d2147dabd29b470979749dbc52ab7eebc005dd51c279bfcee3dae1dc98389f55f912c9a3ed1167c4b163ffbba725e39496e5d4213188e7c7561ed77c4b19f07995e58fff5c9b6b8d80c703705437d49586fb3ce021cf0748460f0c55e1bed5d32bc1dd0df402d7478adc14698d900ca0af29e57c90b9116140706c0ac6932e1293d660fecee78bc568d6169e8d9862ce236ab5c4b529bbd76b77d4bbafaa064c6b48d5fb40952d707e7411d45d21ac4817a18dea034f1feff2efa2e38ffb40be9745cef0eaa5c6b86d46422ca6af877572bee232d9004a9de33ea0fdd2cf77d36b486bde227b47f7bfed3b673e2732e5f7770e0246d4182c8910f63ffbc8dd6129e8d7d2389f7fbd5e9aa68d2ca50dda185e7d6a1df1fc6b7c4cfc67d24491ab84b55f006b95c2df4b3723a7d0ce64a9b267a6431e792d9ef3c4d76e8b3937f1f38605ef3af64eba97c49987927d8af65eb1ef0dfe4efba66f569b0cc0c1bdf42c33ea293293f1d0299a8b4495ef5673da7e822532f2b03c52b5a1db31d5078b1d79c035592958ab4a7226ab2d49e1ee3ad17481a109c5b024a9ac736a391cf29b5a60596f65943143e9249c232a291ad6d24dfb67f9296eb2e009e6c98baeca1cd2e1103b8c1fb273cfe2ad9585081d0408243ee1332ad4e982c2fafdf80143bac1eb977faa6e6c6ba7bb1c50ed2d683285e81de902999d6537a852026ad78b52f553a1314e929e84596e6a08d4c1a9829290269f8155d995425eea586fafbf9ea5607d23718b6ceecfc19fbeae37dea6adf7878a398b3ad27eb0940d0d03ba068e0e04b4bc90192cd386c239302662a8a602df0a7d4c926bbfcfe4ccd0f67ca1f40d9d393cc9be31302f8514064045c87da8d91dad35e0afb6f5e0a846b8d7d55eaacbca4d974de79c515e267905fe29e01479926a8f69508f32bae44c4a8fc16fc914a49e90659975292330f37a6539f735fb5c27b8107d36693160b2dd6e672842425be29b4d604cb3bb98c653f31483a74a8209f39723b3f4d21ed83280ee3139015bae4a24d023ca943b12f66c25d4d31311416f4707287474e3186a9e38036326960a6aa08e62def57d08958c2809155692f6fa95153f70a2a2338f35bb59da1b529579f45602395299f163c9bc838bd32902b34e39ff00d4f29eb63ce120c88b632d53ec968227b2afbe173e7fd998620c14852826b86842f8bcc1498dd9384304f743c758a2224ffafe589c587c2ded98bf64852863cfac436abc50b37eeb95256428a153a573edae05abc167581ed88f8ecc74a12b1a26c4bf2658b9372e7a8f246d22dc71ff9c14af385fc5d84c57e7ab5f40cacc014cd19af68cb2032aaf47853a24870e3083e915262b978d27bd33973c8155b96cf2c8aee53955cb53be70a08bd1241062daf842e655c2fc24220039c89cd5252c47a3a7d0248f919b03cd341fd02b2e809d19fe2009891ab3a7d603f42c189597a47768c0c1e3a127e69e90db0594bf463bd50833a8bf18793aced85d246b059e7890964798e3c13db1f3bfc81c6d10d5dcefeffd8832d8005e5a458de41cda2b0f98680e8e12c13f00e6703928cd064f5a13c5ad7eb1ec485caad7eff28012ef044fd4f820e021bfe67cf2718d11a3874cdf53b8ce2d4f4e59461ce3032c336459cafdb5b3d3398d2ad644ae5300650da42d725c04c17e4d4217545f75e0f2d94e33d91a446480b02c99d4acc4ad8e6afc0d25f06d226a7a11f171825532c10176343df4852c109c3103a67cab0d500d10616a582e5f4c59034b0808fadd6beccd716226346803b478c611f4014f650ae0426d7898b96e600f8606640e8bbfd827d9537bfa1e3008ee3d080605d6cccb8b1644dd32d0426d76e7e98b0b968f8eb5ee4ac3fd0f7f7d97009ca2a92c5d07471a95d33808688ab8989fd59305f2ecacf97e26b09166fd679ef7a5a107472b98a1b3897683aeae95e72c7a3d8cf11354b955257508ee7a04251f2ba5aca9e8abd359217b7a3a4504785de05f911ce6856db454be3bcc0732a42630b02bf36ca262f1c314a8d1d3f2dea943c42c39ac52711fa4f4ebb04b8f5e8d67ffd095bc2de468d3b08f21a78249b592f80b2685c4404a4db4358b651eaee51b4cb3c20e13493bc9803474e3b23b7f613f45ef6f210197b97286b27d6bcc734a9155f6ce3c0ea9702f9238226c6d5f01519456295df3e53cdfc68708e686230f9c1509acddef6258663e372d1bd8899f3f1d8a4b57118a358bc75b2373a91d1a108f4c89bd1f82ed748e48b1095a0aa87336d18d5fbc36bfebfa57a1799b9a36b9e09e0b8b7498db13c56fe17da0246b54d76fa2ea7ba9858568f7ba43b0f6bc13f604c5be6ac29955ca78c5b5cfa2d8255e98ed168a63eb7390ff890cb7c637ab6321e67a3cabef18b3e24f95e14f57d10ed2e21b96d9a1a8d70c95434193336dae69df2850060b559b80ad544b40c47402d151d71b335333f1f414c56436bf731075b27e2a681dc22cf6d68ef879de0894c05189325f3d70ba0844611b5956308c8e344a56e6f60ded946f8215a752c139140793851ede4b8612a3846a9b57084a006edfd5ae44de7f020e8ebeb354bc28f03b0d2964e1ef18653c6b0b00949930c6f861c8f2d0c8b3a4751ef7349cb7686e7105435d8194364b225e49c71633cf647f60a2c7e52e8ec97f722473be79f1af547202c708a08706758664ddf70ab9b7edb7bf39ab3dcfa91b915c1be8aad553b8ab1ee14670853c2fbc1412d2337cc09e34b87dd0edc2aaffb07c7ab4b9841ea0731bdfdec046d4394517e844484e5d3c6a9efde4d982a1c3627bedd13c0aa11632981a918ce54eda685f9b2d41f57743b293b310c187b1d5de71eab7db41531d7b9508e7c0a2cab0be9fc1e83d0cfba948d3f97bbc33a27884e5f4926c2726ef36826f1fcf98362ec2433a54f7664dc7fbf4b3cb4f91dcdec65e0aaaae51e439befdfb66da437c2b3d5dab6ce808a2e0a31b82435ce6b5b05b05d8acdcf22d74221eedd2ce74a1f912440ce0781d67838fc35c0278c2ac4a9026cc80e94cf189d1aa29ca9ffd6aa986c9c7e78bd2150e6fc72c61aa04e80d3c45a8ad27ac57dd7f9c63493f3dd7fd69a5445d84480ea28f613359bf6158e44b57edfab0c2a4a6cdd82ea5004505c9e8bd248c5e01738a766123bb9e824a533cc920371ed8d5b834ceade242762001d79f466632b9a3bb2d3dd45559c4199de2ee913fc26017f338f790868f6fda278c249fcf2fc270c163d4f56d19f3934f99aa8780fe600c6e4cdb3add03f5da4fcafb9524ee20e8edd6e1a0ed1ed7228f2a41905bd44d743c320291d241da993e75dd682a99596246b176c0168f43fa8a53a9d9d533ed1e754639eef5b07c0257a95b6ad4de5c9e0974059f0a23bc0ebdd6164ee867500295b711a59052f0cd976df6339a6653cd402ebe7e77bbfb79b25e93a70b4404b1c946fad152ed8233dbd9b111f0a2a5d0754f7729ef34bed5a1299425d5c17c819a29eb6387b6d41d3565043c4972d6c992a884e825922f1062bc23c0ad13f661295357d38bcca6d32d3253e6d700f0a5c44eca46afd3f99448a2f58df9f6400edbdc392772ceffda00dcf2b8fff45e833970bb6ea2e547ff44b12fa87c377b05e806691937347f60c8c8bb5c804da128a7fed86fbbf67c6a0ed882b19e35d6761407e72fb6400389292c312b44d60e84051ed07b42034205420e6e5c4f8bfdcb2140d37aaf59c6317ee5abb454d80fd858b1d7c52086c100ec00ba8dcb24eeca770b7341d0f82dadb22f9fea35294a34f9765470019dd04f63a00087510ba218953c96e79be13b0a6d3cff7014b6defa625f14c17297824bc47898c43048d31518c6f9a39a9ea8d2c383f960e29d4dbd1e404384fd909153ac9889e5accc0b1e844427a0e41def24eb78a696772190104c035633c7229894eb81c75236db0a4e9b09589c13356f2c85bcf683b2820346ad10698eca4755e56f7b69494b0607b186f943e6cc9650234b542a1dde4e3874c4bfa81339e1575c46533ab83ce202cf706686a5647d97ddaa72fcd3c5b714bb4ff301bce99900e8ab187c44e0f9d2719970952d27a07bd2ee0ee7ef06c34f0f611e60760f421804fb0a3d8beeaf58ce433b2ec96e2cb1251f3cb55572aef514aee14b7ab4d858fcb03ea637a9857af05f4102544dad86e6ad8610eefc91c8100cb8d99907ed365c8eb9eb94c51986cc8a255f985754a22ef548df1f58c8d6f86e9cb2e3827eddd39a9d2d317a5c325ffbd4e3df975914b325aa5048e8e1d1b28829a4a7fca5c71edd489de04e44010e4a30df30ff4a7900e6a7ad2e5f09f44cdd167926e5ccea99d718efabdb5d79462a44d0cf18f26fcf172e0687bb88415644ba1611716663cc9ab8d53707af29e11e4075a3e290167b72f490533fb475d17d0618bb933e73b7f81184f3e80be551f0e4e273fa91b168ace74f1d4fbd6cd1f71f1e6b3b5717815d988c88a90563efe0285bacf006d5e62bfab13460edcd65bad597282a220e6e86e5f550485afdaeba3c4624cb0788d208dbfb07ab4b31d31d738e86bb04bc71f641580a2d9ff455516e170e88196a454b13d12bf5f8fcc99c1c2359e9320c2d0ae517bedf70cae78a1bc1d48f5dab1a56c1caa7a9dd10ea13021ea2d4455c80ac792f1f747e108c5acab25c9db665b650315a7d6c3852c701d06e4f528771a9690221d839b28ab6e56c7d15bfe033095a7ebe31988e77dd27e76c5e070083443658ea1a47cd9ea586ff68803c91b8dc5cc2a2161a1b79f82adcb113139d371b7e2a869b3db9c7864d75aa4c70715db8af1116f3675bc9060f27227a5f5e136af3f9d454c8c8ca19421aeac1398d92c0dcc41d1171588ed3a3b74bc13ff22c7aba43f64ce72c424d6974231ef60fa23df60741b423e269f1e3ac796a9ac7ad101f4767c3326af0563b30746339e47d48ec30321ac1bbce4904602cdb2c98ad0f42387df7256133104cfbc933b38dd5ff6f9946a7534f7003ea9a5cb0bdde91558a84179183f0f1b4250d15265a2c38847a6fc9d7aadc075a42fa6207f6dc5093126589c9855998805a80dac4f2ea013484a4648c6dc1b902516771295dc34a19e8d32615a136019987705abfb9a40695150912b13a0ee587c8bbfa9778a541f88635d485cce55bb3f39d32cd4cd2dd482505b37817122a0d53c0c5949624336541442e5be9ef18ba153b77f372a0afd17dc53d1cab8047abf53b07b24b24397920011c7d55151729822a8680376d8be4352d1c0d66067013ef6c20d2fdf6dd3544cd40bdbb7f11c165349078828ebf2de2e4a24fe9baddb0389645e8b6652bc09d108d033059d31261702a7a612b518d95cb66dec820d3f477a95f72df01671acddf2160d887b299858a99e9ec99705d3e565ee50ff765894d599c669a95563cb7ec6ce62c44251ee04bd73f0eec244517a9070cbc77a698bd8cdf10d9059a98fa469c672452a0ffaa94ccfeaaf003452008ba6b5b4227d2c1cb6aaf672ca2bd8124a3f2649e2df73a8d90a8342752a13d2d50a42e01e0ed1b9f80698136f8893f4559e281fc370a30f70ab18bde61bab882214f3bd94949628b3097e02d9721d78697e4965d56d6abc038814fb3633252a005a6aa30aeccb37fd97a14901e4cd56f0aae335ec61b5cfce76924e6705580a809a1cc265e03b7d9b8602a026fd7f85fb100765e871f0f1e4576d75871bd5a3f502a28a8205189c731ba51d8993d40d622077935588c48b142da898bed0eff468d78378f8d8c32341690b38538c9c15c3afd71eaeeb98f4c666afa8002e736e4a2a8e53f5d8c09d996a963c89c7b1146660f85ce24677582a61c9d7d25a0dfc74f3501a0ad41fcaf83933f09faa32411ceb23c9668354d2d62e0e74f9ce6df33b4e51099b4471a1bfea5d76b56df44767c89b4ec97b6ec4f7ec019168bc3e5986b730d7ff41a620140ea69e25215519de02ffa3b0fabd7fa6646c297e361d52177bc25f65a2c12912f5f463555b5d8038efb1ed875b04e6fee027cf2dd1ecbfb67e1a9900fb926c4c6c61a364a0c1ad6fe7125f082dde82db5ee90ed51c185917cbd4c9ec0a0ae770232d4dc79d63b08f387dcd1a3789f720e7e608e2a01e54f441027c4e1c9251c75b16a887731b970621f411013d3f6a4369210a7c02784f47d8a2881d1d408a27a0fbd61ed962ac0b299ddb148df8ce55229604756656f5426c4d4780207b3ba58a3ad965c73e92eafc034e5640376941b55727e2d91926b39ed53a20294e82be6f8fa1fb450e2cef64d709984c9c8aba61bd1c7c9620712cb0730be9586347bff76c7606e25ebb571043f7c0cbb4c30b4e9cb9b8adef10e8706dd56aae275a653b8205d9db09be9d705d28f6900dc761de85a4dc4794933bd385b3d513ab3b84f289335947ec8b937ede33ace21d517fe2983c720108060da8d077c181bdf3574f02770fa8de83236845ab7d3eb6bf53ce091e400a27a1fd86b947477d049ff66db7c22390c0aca612f1e40135359a3044bdf73fc51d2a84971a5e99f22853e1400512209e726af180a43c01da37793f8a8fd2a2e3194469823579bb9bdb6fa98d3c1b3bc5f1d7c0023884a251d86d7487a174589407f6cf51dacba36b541c791d2fa28ce1d65c225096acfcc388f0d510e3261c06dc245796e613e1c65e8bd57932d90e827181598877048d301a11d7027dc9fea2c879e09335f1f37cf7f8d9037b649d053eb18a1ed87a4b31f88379df157812aca76ea5871b72359fd81b4ed4234090ad9db97a02e28a53712fbca7937cded3acba9ea777c5996c741d185ed1dd4b75634f3b5f71642eb204025534425117326e40da93dd4f31523be43540a9dee305bbe87732b57deb64d04aaa6980b6691148245c14354bd7a2f13e0f5d75b16249c384207df161879bbe6ab00ba12552a896f26e44479993b5690e2cff08809e1b69bbb523853c02b0e7466c7a65578bc0dc1e00d073116c8bd94363a9861ba4cb5b0c74535c5f6cb061860367f8adb5e00979780c0f0bad688774258cb816aece31b2293316a625c8e0b2c1d3a8341af6de1f6b51d833e7050bf19f5e2a4de2157562260b1f970642916f3a810abb2822839d3523e901c852cd60244f6352f612b4b9467150dd83275d0363e8f98b1ce3edcdccfe1b0f0d80740f0ce8b9076d8fca29a9ea63bc08099cf2c2774f2da897caef968490542c44b672505c8525f6dd54d825f177aa9da56172147d4e7a141b1576f99917fbc9493e0002c271001743667668b1caf0513c3313d257bc1df4d779ff241d7cb6c22ea05bc55e1cfdf4751530fa46487f25fe070d0525b2b60f2ac1717fe49b3d61743e04bf541d9c89acaddda3dabf952acadb1a35f509e81261609fa813cb0d752d083544782282c22ed7bd35e06f61a25b06d3591922de4166e113e52fa8aeec4c8a2dc5a17cd4c4e88fb25c27c8a433c9e24a31de1fef6174783cb55cca011f421bb1d16cc12828e14b0cbf35ec2273f3c41e0104c8a35825e07c551dd407e1ac161d1917018dd8c82bf14b8921afb9732008959b85601e2142e85236c65b6e0ab40f9c66a9b42689f1552f59cf23123ae4442b9f7403f8867546edb1d6cc0efa504c3d0da62bfa8593552e7611b68c99fa34220a75dd7c4b1272832a39ee64a10094c50e34c11c15f2ab55a876e4fdb08bddf8afad73b3d987969e442554442c403a2485513eddaa320deb751c9ebfeb001db792eed96668dbfcbc98eb812518fd39632419ae560303064a3901e91e1cd74965460c4a1b4917c263708f19daa224a0e084405060f002db43bedd6398a4541edee04777a6f508a1d429ac384a35cb052862119c8228845d2ca0c5059902d139d9c5d5aba684ff0f9130b56c927bec670f0129657048b9dfcc1821fe4ceecc78aff0840f067a25d3626d3f097e5091bc9a809b9e84841b7c7c8d1950396dd10797722f2a6dc7d5dd0da042eff35469af96da1813cc62d4778ae1081c4cb91e1d55e85bcc6f3d3900fafb47bdb78ede7b5b7659d3a30bb1791955b2c1ca0cfda0c7a09523085a253270bf507126f6642032d01e1088f5b83ad2d4c851157b0451fe40af89c0712ec960b97d718744871b81224fab279add928ce55cd6964d43778f16a6a56d08787abdb3a8113f9a84c7f923be3041d3f571df03a0298d966446169363505e685e6cf93b4cee3769d0464b3297d171c02b3f4605b573824adc18708c9664b064bdde23d061aea6a07f57b22473bcfe5696fde5e51e00cd1801303e11c357a9480b722dabd8a03130d102330c4504827117b150d641cf77d8de7ad7c8bc8080031f653ae47fe3e77d78e0763f8aa6b57589cd5664577be85ba2c642b7a0e2fb782a9febe94029a48330a2334acd0249ea228677186a560bfd635927fd998ec04631a766d4c1470fa05fed6090fd67d69f8190f7300abc8a61fc260b0a690f5088ceafa612db6cf68aaca9c05eb749365b29eef8e6f5acc2d7dcaa8a1c60003a9a80090e5e01fef4a1824ebf08d2fbe6df02d55dc7e0464c7904f5e17013c6e6141fe75e0309301a452d355001a671e4a52d27c61bfbcfdd5e024e63574a8ba815bcab50c0832901cef3a068954b9b80c6f9e17a354d50bf09a801d752bb2c59762b7ffe111ab4a0c60a002dc9aa1904245ec55eb3758c91aa97356bcd2c2ca53b254917a486013b2b284ebd58ee2124852418306580c9b52ec3c004d68031abf1370f4f4f389d8342186afb24cd12a12cd0470a5c567c4e5c39c7943f09fc7fffe340b6f591f85f4b4675550cddee91e16a5e4772e8e48f50d0cd7a7121cf0e6f4e4eb4f0e0ec2b345ec9886e67ae287946541b7e2903f30637b09e6b33838b7d3d7ddc501e8560837342a4c2fe17e18a7b800d838d2f969180821902579dba2d544739648a5907837954410bdc3a4a0e1251f07361dcfc353968a4d485c0539c2ff646e841849aac8d3ff0a4ebb0d394bb8ae6e5c5e82705ee26de61d2ca7e1023b7db16add608943bbcdd243988e098c7980a67d5f43e61ecafa770c0392eab78636e90578e324dfc2798231f40c5548057395f6b8418f08433c82431f76f7d0310bec01375e7a56542837eda9982c3f3922b94a9aab37e6bf595a8f58293302790d6efdb97e545464e140348b6054ad617f3065cd615c52822e6b53262402f8fa52067246fdcb472acd8d808f3b4f14012344fcffca5b960675b4ddb1465638eb9e1a0f9214d6e1e9c84f87f404456a0f20d4e8a0c1305e86cc8c0645345980e6bcc5ab2167451cb63521a121117723e48e1f3e77e934c55432214728d1ec04633b9f9a6ef6ced941fb45822711c1a4bb39e746464019fe4a25f112e53adaa1818194c7b90109364d0d07e84d5ca4d895532edde23307de1046637acef259454b96448588c6ca3c9352c3a6eb4a4570df2d7f4afaa3cf5d38d6c54638533660a2035811ec96382f590c991e7a42acd362b15e150aa024103c4d5af22485032ec7f5a414f8903300b18052ef943910af4743ae193a5b816234f67b033342199e5c5c52904ed0d69c201269b7021c99e0b35ca8b21b0d8696d2893ad84f7a777c3e9a6939deaf8136c0fb3eb84ddb98a618ca374056014b8bfff223cb5617db5cc15421ccdd282450f5fec732a8976ccc78bd2cc4e977ae6210645a6805871439a4afd41931c164491e38ee673f5282e8e5ba4b53bd6ff3e71af61d7cdaa0de4a0b024f6bce109183284ca8ae81c024c6b7e22422c1f5527d00a345a1f05098783ec62a7c1ed57fad2964e9d1c3326a29d4142102be564cb094615dc0cad78702e9156661e9c509a26316b9a8ac26498ee32a2a2754677988cfefc6381fed421f7cd8ae936999f94efe920c867c2c1f666bf46f42449df51870526b08ef1f5631a3a34161ff48403314ff21cefd3a1f4de3bf97fedf0d076a57f34bdf593a4e9e04fe7e41867a3c93a06bcdfd0c4e0e1ca4b17196ac23f8f66c23c80629918d641c0aacbe43c788c502dd2822944d96483245dd381a5a754c43b646e2ccdc4939b5fd7184eae1ea46b455b2a8e7b33ca9ffcbc066195700b0bef66e096cb7fbba5763d3d12a9f987b929949cf5425daa3cb58e753df36a904883548b7bac7955c4028adcf936f4fd80aa70d0648d2c2cbcce019987b5464f72eade470ccbef9b4262e999e6574aff09daf58d86581ca012d7ecd25c7d1781047940b81b67dcfde3b7c86cd7ae454c293dc74aee805a0b1ba756c0dca620ac79373aeee9c0ee34a75da640759999d0e37c0d3471d0a876ccecdd11518d2649b3ffd44c3336b81fb4c8e248e18911d3ed83e75d1aed128ad80b341f3e9a5bfc785a3d45a86ba140250953a25c2151b96e386e3843775f8cfa50dfc47d535279a5ee391dc00f2ae39285e641ad4877503fe2a04ad15d76e58de0d4d974ee71e56bd3759a4e71847fa12475b228dca88cf69174889e128a5917c001c8badff3123ecd391ba733f3aa7f2bc418a802b1343ba4a604641a5ac4b5f3ca5b8df2bf0ceedd791ce23eba6e516a7edfd7730167bb2479dcc2e8686160cf8622c5abb028cefd51fdea2529778f1ffa33094c02e407df189c05962b67357ea53dc72b1e1f4c5608234393eb7be3fcbf1e46711a738ebf993825100714e0019b60c0cad9b7cc4081f688242dbc7365ae20a1825995e2c2fd84cb978a2a9c465ccee1bed5138738ce59e2607994fa579169506d20c93d5b7638d31c68fd3adeace8f73fbf9f6eae08ebff246b6c93de2b84835d8b7f3bf92a7028408ff0da6589540a3891afb90c4018f1232ce458084b69aa677462dbf2720ad4852c227de4a8912309825c61f1ed1ec084c9283444dd8f14d8dcde8606b60951108e522e14d404d606756031221936115afbdab30bd957f07954958f0c4918f6810bf6ac4fe9728a36527986aaf6915fdeab07dfdd13ebd31be33b459d952cfb642297e537fd886e45ef6b2c7a8c47d583e0633554740b91c688ca3a6ba7d0ce10fdb9c6ceef5d4b3b692a367be31cb964c09dce2771987fc05b914f7954b84bb55d8aa6a2a3420151259e6098bfe3c39a3c6981c7f2158f16a875451c9eb4708aabb80ab9f4c421b4f042de92882a75bb3bbb030a32967e27af91360a9fbece613209bdaa655c1d8793472de478f5e0c04dfdf1118b1e760f7dc7618fc53790438a7f82c5609105e44a7e0c68bb263222ac3133335f4ee7e2192be33b66fb0d22cbe89500e4ec1dad674d530ee7314f8d726c3c1e5f171d87c1e305c51137bce3a958e3c90249f5807e58acba16135f2ab88376594ce00c343599d0f9183f482d8cfe08857491570cc09362e5a69fe6998b3332fc470d69332bd7b60803470664de3f6225a9984cda105237d4dece5c3f41465fc4623692d650f0e81617d721cedd397361e624b0410efd5f047e1fe4a35176ebf45e1edddb66627b9cc6f585784efa08b39b1665edb02cdb67db6adb68c4bb9cf438f317f588e957c8fe677af6524293fd90e01886c2fefdf7b0278f26375e80dc41c775e958a679627719ab59b5b5e4276562ba02e512fd87d5790fbfb6d62b7824e4b7d780863789084c3e848aa127001ab18421038868ca9d146605f49914e9876c5973c94d4c304e3b32537d6854576ae550e24ac66826015f19283c1263dcc2dc0d70dbfe0c72bbe4ae293f74d2b4609cd9beb3a6533396908c82d200916c3958573f15aa89d03e182bc5fe93596b10381d330bb513383c1c775ee2cb199c62a96ca4c212992dd77291607f2aac35bd5f522e2463e5b42c543af47cbacb9819d1e96a408b97eb542284d29465c728818f8055be1699693e4c6039aa14a971a541a8fe5e5338e200902d0f573324d29c855572d84d061ad1002f2509b40e4276f69d09401e0f21620285037ac9159c99d835477b802db212d606940d30b628119e94573ec1f2262cb6063a3cfa1682e7465dba8025f4973e524f6c25b904680154423e79f52ed70849f0b9d237af08471af397b1f58a8d22c15325b8980ac8c30b0f135c57c232f177ae1da58773364b84b3716746cab1427b14757e8ecdb4620a7c48cc3ee1b38d5a36a100fc40994ce67c306e57768feb8f4e37b1f563e72b7b0dcde5f82880eddb237b13d05c4ac144452633932fe202eb6feded80c20a747b420faecd8fec81a3dc664b5f4901b6ba5d167d0e083b5b831fe85d9b16567dd7428d95034edeaf293a8edf223360c069126d05fc4fc0b83a5a83827c924568518376b01b3bb3e6c16cc71bbf63acecb4e9d923e46be6f93eff0c075cc93d656b65fbe8ece4fc16cb3a84fee889cd6530e953b14c223448d0caf12184c5332203ec27d762ea547961133f8a0f4af42ab368806e014712ab6e501bdfe33f3ca6cf465d8dff07ce43f5b93e06a3d81992725a3c6374495d98fa451cf6338047f0de1957b2b2fcf351d485e9e3c626be8848b5dff09b88fb9738478796891384189267f7c4fa3e86734a8d649e98cedb5a86ef2a37b611f5f3c4124f136be7110c466c2870951ee252e657d85f19bb82866196d619a7b5639c6158077b5bf8487a40ff909b46c34713f496ea163d8fd249415192bc28b3ae5ddbc9f31ad355eba55a29c23a1970deff22d113c903fce7eeefb6440e0c0ebbb4c1c866f4a73f35af681df5fb1947701133eee2db4a3522d948ce5049f9b1963e7da11f22062be981d0730960741060ca9ef728249ee04853c73e3a93857405a707b8df6649d4145953f3e1f152a4b0e81b69ff4ba15518364cc01404e1fc7da2cc193ebfa8442e107ce87cb2ed084d1f48ac48332ad15c4717bf72fbf9d52c5f97c94a6eb863bf49cc64e2ea55e031da68b4ee670a07fb9376269d1808fb7c1a53486086b8bccf85935e70243dfb1e994ebafb2bccd980b8ecc77603fea51b57f4ada2ed7a07fc4e93e6dcb95123465013db3f790746fcd203f3b97213ecd9f6ed0b13855fa1f7137305580919108f7d43f853d42fc43088b7e214664e25848d4e93bd9538db0d42a0e7568e521360355893c697188da9c27d8f11953a8a3edb123e93724ea7e9dd2443b4f617848f3cf7dc717b784975ba9ccc35835c4af900739f28bf8545453764dea50afd16f863ae6fbc7a3996690e140a159be6e07d5dac3d86b5dbd4c865e2391e5cad1f5c65555bbfdae227b10a4883a9bf4b0847d2d4aa9146db4ac64b0620fceccb8c3e1bc6100be12ed17ac2847e2c07172298823c8ebed4f69a916c4266d0269c952391243d4da2eed9bbcb5f29aa55a498e5c3b4bad6fa86adf58546692c09ffe96b212e555bfe4b0b9d522f05049787340963483e9db59fa9a789502e0b8d9aa34eeb1ced3b456fa2eb039685a0d20a5d57f1f5d21fb498e6041b4beb3549b603ccf70c5896c6835aba72de2bcb542aa59e8320b3ef65a54fb1c6dcae1c8ca33e56cbfd9cdd8a79549d74b8e007480d72e82ae0ab1fe642b6a568241e07c4eada761196e76a444f51de88f4b1aa844901be826efdf89e0ef8f4795b24a7fe4f57760718ca7fdbedbd76bb36cb9761d89980b3ca96e44238c101970bfbdba07d290ebbd156b5be5a830e87dd5b888d5505bd3c7527a87bd3676a7489ac5df9eb00b8764586f90c25af73b6d505c00ad1d95d042184c25f773fa5f608ddad475f438f6711af4e28954fd642ec39fa0d716529b90fc0f37cca1a62150ed22a1dca5737eeb61f33876d579d6c28fa2bfbabbacaaae1abf11fb981c78e71988b024e3fa151b5157510b82d75ae5aadf98679743780fd661cd8ccf7a391068726f8f6838c5f176092c1d43f24b1503857b94f89024e14fc88a5b75f8a0e1b360ab1a5d4f55fe0d615bf0ba611290b8bb1570de9fdb403c34b6742ec04d934bdcabca661f2e8f3317b3ffc9bc8e1899c285e705bb24197a79e61b8e02126c076c40d78c63ce740f8f5e30856115f4239b73c8aca74a51166fdaf7bd702d2612abba555934c05cd859510aba1282a0208f9ce889f52ae765f5cd5220615539244a6376c9d3fb6cd89798018f7f3b6f0f83c297920b48718fa3e2766ad27d4968c79ae7378be8c28da6cdf6f566af982f309901450f64c8e2d4a93626a6f92aec310ac6f73a2904dba1318e2fb5ea0495d41328aea72aa3159a284bdf5ee7e92a817dc72d6b3f5b212ac76f317ccbead8f8af4ccdc3f1e5f39923fe928ed426e6b37dbb86e7b0d2a94367efc84c4901412d5017572e5d922e13a0d77a227f42c9838d6781776662d803a9a4d8036cabf879b41377427e8d849614c8fd5df89224536ef8a64510914efce0d17a0c3587807d50219ee324e9f39c66542f0b50cd6ea3a7778e2ac0c9024823309a381ec800624e6c6a73d88537caf0ab5e872bb873356bc17af416fbd6915e4c290aa86ca200a01f92912b76dd03c45e6ce21a395af3b7aa11be5f70ce88d450bcb6aafdd17d6f880b47a68e2cc299737a7e47aeb0dd3aa4dff5980c583195bedc475fba3da03a17970d9349bb52cadc9f4f586eb121e5b3a8898ad0c3ae4e0ac64c058f0f2b9acc9662633ed1221d3aace1e3c49e7fe4b1944c564bd32a71e44c4c72d44ba57905517aa553b851e8faf389e782c97662f279ff5ae74ec4fa573607675e905f9cd185aa9a1b1eb3e6f0bfa9c13aa230266e2f2ef0de49e814ca5dd2bb96505a3af8e860671b87420473cdbafad020cb55268049b48878688fbb70e8496c7a30011841466a5628fc147aaf84ac6e6e9e47da18705105a5fdd11316cd8940d05710546869b3a5fc0a69b505cabe9f5acce613b2e1cbdf8124daa453c77c8972d6eaec5c9feaf89ea67908c542c83fc89c90a6602d31ef6dd2f9a08bd991fea1bb5a9232bfcd79081a58f0ed1979365b721e2d5e6499ddea47310b90a840c894e70531db9a141fb41798e02f3d0d8372bb3e038861f19e8d856e5a1b739e18e8289a432fa6fcf9cf925665cebb539eb63b4f0530b83457b63f6287cd5d81cdce9813630c682b9a9145be93fff280161fad2d8464433e36fe2de40ba16b3af515cfdef90005f48c55e3953451bd7486de010f60d8fa65a59389dd9969209082050841060c5e994eb4ab32723aec293abb065a48c805b9f64199a6f65a946b34ec641290037bdfcec3f9ea8e83a59f5da31ae6f72ac1f741fcd9c950dad812445d428d84d1b37ea8c31fbfbc966d3027ffbe05f551f88af96a2c23811bd81d0ab6199e514078b805118ff61ed7034abca2083a9246b21a560a145126dacec26c0ec7ff5d3e932373cdc3f982a0e7453c6c98c0d6f2aca6b735a657e2b46aae76b981a54550e33b98959d9747c01ccf03199ec39b5300f30269f2fd929d2f1bde784b470389102d527b020889be76ecb5bd8e03b08ee0b11294b7e284f6369828f744ae3d174b00e34b907e46c33986d32476df7c980447b7075fa7067530ae141f7d609118abd90fdfcbc878add9bf98f29b05aafd2fd8e80696a28b3ed4175ca3704d3bcc914d83ef7540580fb838276e31d60a8e8bbb1556cc7d79a0082817f02f5cc634d2040c6680a0e4ea33b922cf3b9f2e7e5d14032b8efffee58931214120ca761fa4dcc4cab77218e996fee5edc24856a2290f57c2b8ee3851d5d91a5bcfb8a4753b04e7f52f37df57ff0252d6087cab293e8d70ffb8f75f0894d938d0d4814c03e364772d97091e5a98f60cc965ceb0752a28ec26b811feae5af6f3a4fc7b627cc98eb1c3495aeabbecfee0ce24445248a533ad40534284baab0c79a4ab89e29913ce790b947d4abf98baf89afcb76af9ec8e242e41a3bc5c6433426a09b8bf5429823d77eeac9bc82cfa0abf5c1aa3c5a60f39200339b5415548b644c12549a932391846df4e7b214a1c69a525a16b2947d4640cce0fe0cb540207ec45d7d4c30269e614a074b877d26ae0d35800a7cd56bd113c7207ef79102a0f37a2d142ea2ac3d4de3ba6867ebfce1cc85720ce5380b919e787c4462b404badb908ea090f283a22c6b1735825830108484dd761dcf36bc7dab6c19562af2c8726174f9bc934206c306bb1dbbeb73a2d21f85495851c1e7b9e98e9dfacd4514f16c2c5a1e91df141bbd4c4deb5a494a6830139a674c92b859d5d4e3e8694f758f34bf333482b4e55835b09be71b6829a2682d1476d31b7d67fa07d55bc0cdca982457b2b284ab5f7506faf6bab73409a77d0c1781e18b5f83a329cd0d2d9356ed47ad40b5bb0b4d85a7b770f6a39a128050fabb2c3e707773455408978eaee21c3e67c29e338d145d641945f890e67ab9473499ebf4a1aab0ef3ac08e6e60514307fb1abc03490dd0c32a9f679cb98a2417ec1739c2ac79d8d6f382571c9718aef9755e04423c76951e47cd099bdeb499aa4acb54cc6beb5ac50460dc25850700d7b707b9107044c2beff8932be89c8e0f06da1793786bc62df36596e0717e583c501f8ae9e57fcf66ac3d713599ec7083390024a0f773e6aed936500c41ede1a0e2e1607ca7541271fc474bd24349ea876c2de3941790b570dbe58f7c993a0826afad26e6dfa22a312c2e70c5aa6c445a430f20be7a78e87b18e4eacdb66d575c2ea5a85d57475fa04c84e09b07d918db351a2e17ace728b0d59c36fb744554a66c464e03a9964281ba9e05ad82bc1ae8a8452f5f8c2e2d4191c37939aac8418c4ea46dff0c1ca677890958c98fb11dc0115adacb4ccb291779719feaf760cae930c3469ae1e06083c497407b1b87314273422caca0b3efdc1d027452c0f5c328e4b3a1cf501238da34a1998120b75d9c34f0db764419b74b5a8ed86188495abc05fa9cf3ffbaadbbd0e86a65804a88d8ca67e60eabaff70fd3af48c5ba756bb0770e81d2f28ac74e321632fa2777d3fc8935959d24b79658c1a1bba64739c698c53469230c1bf024f50efc9d4bd2ecc55fe80acd837a7d209314f6df4a23cb71abf221d7191e473ffb7a02ef8e12e2922c548023170f7bb302c892363ac86e78425aeddce62c37930aba297f81cecd65f8a2f5c7a90b41b9a2cf4021c7b918e7c19735f98dc04223ac2986174fd0a3183ce569e2de6d1757ed3dd7e2929ac97ba050206fa3bb614f054160b34729e4092bd89bc43b5451612bf521d542dbc5022921f11349254529b7ec1fbd0c291699fcf4bea2d13a6be035a7e91d931e1a0ab608c0e53f1fa8440ff41049db85f2f70086d2223232ce7f0333f244dd8764310f55f14f046b954daa1e1b13fe5471072fae097e7692dc50ffb00902b77df83f40a8871ff47c315452d53fb0b227704d2726d8314a2887b02ba667319db3d5838cb4a38f769f5052f44a2cbcb9fafba282ac797c03bd582890541a3db695ad917d76254cf00e9166b21576a557a8208c3dfb905ce2b16a510747168db1a21d82a10a65152c648f34aab23a642abe64af4c3f76bd0804cb3fa7ff2394cb6d6da99a6122830d9b5771e05161ec9632665f27e55e7c80911918c4280f6fd43677a0bfc2884b55ed4c541b04246f3eaa1d43f8c1887628bc998c2e8d7e38cbd0ad482f6c69067e1eade60401957db0abee95031ef47daf06846633b8786cb2bb68807be5019fb3b6857c86f99ae7f28f04b2c2aa7455744411887f87ccc99875c462c54153d8f58611725f328053d23d37c66a1119b8d4219ca449c83b7dfc17d2d97ccad8297b491dda9f90e6fdc04565ecb721ff240e138ae9420ee9fc03d9891e5c984217af121f43c61bf2a6a40ab2362e240bd05735c9f9593ad9c426ecc35c46ca82de0e8f27b9c7ce423323545f2adcc27cf4d7f4f66faab3eea14d98fdca4cd0d1f6e75f4a655b70a1c21c370f31033a20278747d386fec1e3f6c9faf81464e813f9b61907e592c11a1212d364692d7a781139cdf200c659de4b86ecd1ec07f1a57de320e1ee015e294f203853694d2115f753b1773a31bb2808012bc8d583c50d1a8f982f4f96801153ebc90c8038bce51768f52d1b0423dd2370ce742fea90e0867c6cca7a5153d5629c49252f8881ffcaf292cfe43bec7338afb43da4325dd336dff3240c9a3c98a8ae405ec747a61a3941952b2ea12e2b90c94884abbd6a6d51928be30d7c49829bd877c77f85220cf4808d1650b12fa22e8f316ab5afe0a6a5786d836f41120b15855158b73a95117ede7b950898202636aed469d818d95a7f3b730f5c52d53ef1fb1b2140a1544e8b9a98094219166dbd332cc84310959ac282d1349889ba6334dcde6cda1cc6a603c21db8471bc4e747c1f5eb27dc3d021a2287b9cfb44d3f03a34f1d18305a781ccd2ccda9cf52a9fd9d610f6c1f951dd5e22c09e6cf591a5ea2b10ddd6d75eef831c9689e75d293a49349850e14c449edaa5479ee9e92a649531dda1da1e7502fd588f5aa1f060be25a291ab20bd29261ac02d5c2adb536ef2122f42b2e592c5fe267e18fccf6c3a7e420800a81bf5148891d1da461c8acd1ef542c23674a501b20ba50a371dbb51af75495b2f1c17c598b255045c330c2c31a9d0e8389ce2258d294c489558944f6e09ebae8c08e9b185c23b4cb1bf80ecd623bc30e63cc60896efb61b45b9ec13e14eb21aba7fb8d0140581b52b2463351e2f29726ea17d965de642ad9500e5fbbb7d7d6cdbc1f593e081e48ea9670516a66039864956f952a52af4cf2fcc7115112351ca6b939417643642c73c43dccf751539fb49a8fb05f55f67246ec6987a72623b00a86b55e38b606f6e2192790fc1ad56f00fa02aaf1a3a36403bcd01b9fd28d685a5f516b2ea063c7c6013fc2567287538d5f1bd000d8c66c9d384fe7ae1b0d9a6cd1dc54e8436156db077e868b85f41305737034368da91f135945ac82faf5dd0340ec3137f5cd38bec9e2d5924411a49de2b16ab9777f38a3f0fbda4727fd9e67a0e4d31d2c75141ea413422f88afaa303038610d2b88ac7457d8347e0b8b54c96b5999721040ebba64323e1e68622e95e999cddf08822b253d3e615509e833644972ee8e7b635186d1a5bc610cc1f1148e393615df28380d012db8b6f82261e93400731386b7defc4567fdc5575e0651ef31b414483e507b196456dc7a277cf400cc48fb12428aec0e087081b4177552d04faf0ae8a1533c44ae496bc27abaab0fac9090ab1831c31e9002456d99f312e079a55d90c23e4accccae714375edf02e1768e7ecd1f99ed145c8863cefecaa492edf4fb3f4417844248ff7a971684da388400f432c4b8098fd5fc5514304f5ad3d6b2063e41170a57323ed148cacd9dd37bb63e1205a980c78bca1c8b0b324e097986b24b525028078b40e646dbe431bd93b00e230b019075d2e992ef4db4ffa2034a26f9418a7709e536b6cbd0d11b6d25f7b36549b2952f318b68d263482b6139b29f7573e5ee2f503bbe270abd898752c1511a4b9add380518a395d30c6bd0688f56433e0de6e992459db85e16d1f23cedfead6d4de709db37e9e4b286f892b494d60a32f76058e7e9fbbabacc1bd44df54df481c29f7129e02713c8e46167e2a04b00562b1e7dbd9c1e0a3716295262644873333f6b4c35ea8b05c3a87df7730c124109b4ab3eed7b193c4e793c8a2e4814422e8d82949f6744b137ee61b77f338a224eba7a474c894b249cac178771b9bdc6034ab6b9d8bf4655eeb3fb1bc687b067ff355ff86cc3f86aeccb07b07ea0322f8c7533382f38299673cbdf424cd3f06de96f776bbd71d84385247f8da755a8b2f4dfee953f5c78cbd143755b1d7b58100bb48f50ac928ef27da7711e6a9909b7c5d931ca3171e54ca4a6e4720f69589522fd9814901efe248c6423b938070249a44fe88020a46e6a63d0727e5d9ca0a638d7435dde23a1fa74953f0408c8b044b08b2b79a49f84cd3755c6d812ecfced2f22d6e380b316d50621e1d06eab8b95ef0d0f2da2bcece2e2a3ee84e6fa47eacc4f3bbdbba4941be41c8c75f20db796502d5c1e3d05b159041f092f7e82d58da9887dc3ebfc0b179ce1b3b350ab476ee438c3c796d47947ca229f472addc1e37cca199e0e0c4eeeb06c0c3b6c58e1b8f782761a2d00abe71d14581dd85a63fe91254139ff15edc658ed0eeed159b5f0c969ccea0cffdd87780e7bc0ca5405d4860090c9e87250ab6bc7884ea408ad46e3f91cea979525d926ba24248ac24d137fd537a7452c5cf20d15a2826a1535bbc2027345e7ef95b731eaa2febbb9411725b3d1b212b877b17b50036204ae3719276b0c9c97c5c5e8fb694a7657e51ca6291f8f2ea1b34b1a87b8eb51c1090d74d1fbd64606b39c0f6a52117262a9c7a1423f0ac7a51ef7d5705cb2b007b22ae13e9f4fd06377454520a2e06ac1d3b28ede0859fda997bd30ce515ffef6c7a25659235c18877dbcec500bd92c4fa11f19ba02315f2759ad824e027fdde8d16d1e271b0c3a21f8eb8a801bb76bacd71942ae5bd0e4205d6341b355f6fcdda75aceb6e7576abe1be724637dbcadeb77bc9b8dda612aa1dc5b4b622a1e606b0f6d393172400899d255e1231579143df0689a75209169c34c892cc372b87b918bc974580a986ede33cdbab3097fd7b772824c1d04d5d23096e9bb0d5a6045a72ec4878f0efde6e022190f72940ff4beb0335dcdfa3f01ffae945906d5b286052735bebddc29495a80629e97469d771a3470a2e06d609a2004f0c23e1ab2519c503c8a423f773deeec154da29359d18338454cf1f513fd607c66fee3a83acfc606bc39d8f85c60c6a6c44ed07c3654216495021828bc9990792d8742120aa49808c7c0016f1d0c3946d34c8094beeae3e44dc44060603f9be0fd646bb8bbe60f2b52aaea668d4e58c3db615a29b38994aa374f75fbd4f6b4d46b4f8bfe997dcb46acf041c1210c4a906f9f00f501f8931711a01b004874b9a8f3a302e03376a392ecd98f7c8964e98f97b501b645f8a10ab7d311bc3f18460f01a8b3209ea195af4099260efe5d34fa85e64c43744418ecb0f297f3f76808401df2ee0f8670644bf26a0de70448dabb01f7fe461d880c68b9a12ca0a050852e5a8efcf5ae1fe406057af306677aae02a5312210e46ee1a13f71c14f250efbbdf4045c25cb17f8497713aed88f744f2d1edd6215940d35a2ad6c801482a15416d8f80042386e85e714d28f0519e390a1d2b16ce10df74889d30786b7e3f6cec1371b44a85c197aa2c9b39ccec92d1f78b2f7938af4569d184d85b21c2491c017b01a5cd813b03e1356be551d426313b27cb42557369c697688cc8802c8312c026f291c5f0d49e51c2166eaa3f0745c033cc1b5cc7ef0501a9041c08dea8531043c91dfed994d4ab0af94963a8d31631e25b56908e7ff9dce8963e2154d11d95148dc45106d0519b0da656066535a9a25d852c67d890e2da29c8e3ead5f15ed5550241cd158a64acafe33846f606ee27b8c85a5966ea33c85c2c3763c8d1e5ed7190dc8688d15f92bf51d5a4d3af5a348e7eec7951964e040255d2c70bbfa85c72d42c1dddc88ed2e396ca58685975e44204f3f6bfc5a4e6a2968bbb88e71e507a0f0224050120b5105ac2588b2de09f0cd4a134f38708c71eddc1dde88c476f9cfa95db75992efff17f21ed1063d1dd7708bb97a99e91a3b49aac3d09dda0715487102f441818811b64addc895e10c55350f8e76488f8579f5125e92938ae2b36ef63d5336cf8cd34b0faf3f6dd92056066ea23b06163dd2e410ea0caf6ee0fb74ac2d417839914e4b32509eaa44fecf71c8dbad9ac55e261e16656cf848784288a30d493319d6666e3ee7b01279dc00565142a3d165b1ed2f08353bd29d0dd0c8f01dccc5947630e3a11a34e8825585d69da2bbaaaaf6a6107d8e92858eaa36070bddbf78f77075adc9dc0adde4bb3c21655819f9854aec7ee590e0992170ab1e3891c8ec66f71bf4ad6c5bc9df4adc4a605ec172bd412d0179623f5d21c353e1fb6f0ac1980602bf5ba83ae4db3090031f2927628120c733d97680e0e7b891e3b9c4c11557e9676ba4e5571df9414b98acf195d20e492a87cb1d5f800269887ac5ba34cd28c79996d83c789bf50af8205bf722c01fee91dc3123e9fd81b0b9415f6b2cc4e2b8569aa88d6207a400e6ffa88c714cf5d1c0cba87f7062bfe76ec3da4138be35200f52e0d4fc12bc9dc1a083c8f895e04c472077fe433eed3199cf75f408a61deb0d551115b15ff73418272e3ddf22536e31d066afa0a5219b410761d2e2d72d1d837bbc1e59a500f0e5048b0803a052dcf6b9f4600b4ed34dae9f9b9841b054102d48c32c276e59e364e9e2a4bf00548b9c6ceb4f0fcfffa61659aa0d5920220de00003cd30fc2be9c2b554de15707609643b34fab4c5c055983a49e14cd245a7c4f1d35a0b06022732230af72eb4c1dcc0b2b090063c7a901f784679efa62eb14f76c05d6648e8de9cc45c0de4a1f0bfc4f2e9289a98517a8b2732362372a273a3bd44e665be28e780fb06805ab1263274732d87c1140276ca4772b2a29c26973acf3443cc335675f3f78e56ea078ffe6455a64ff96e50a45c8cb535d36f412b12c181a9a83b71f209ae4169384b63dea2a4fb620f0c327525705bdfe46ddefe3e449979a94ece178b2cb74904e930c4e2d144dd8083b1392fcd7039c545a2a9bd494a51e2a7ed3e208b020f680d0f230c75f2fa0cf397d6245d549b3b70d0bb58da58c1f5626ee17e26d14885e3821ff705330e9ab2dd384736a4b4408c4091276224f5e6ca7260e13c05c1c0df54408cffd1f77e8aa0d37f96643737e08aa3aa8c9b14e00eddbd9c4e797502903e15e6b6fc00f7c7fa4ff3c3eb8908a1e34715027df991ccf80e1f1888953c775faeef301b2d7d88ad4a22aa44d937823a38ab7485c85342d030f250dc0f1aff1e7679b88f2b65107aa1d18f62c9d31b4c7bb58a972e04e6ae5b1403a72654f1fbac80c15b6c4537259339cb2f113629a966db7ff7a160e12ce23ae2f82f876d6befc30def4857facc0677a0ec983ae478c0f14e9fbc30776665236b9bad1bb03a56f92181f312ed87efed875d593c9dbc334972a64d1f12566e984f4305365ef9c3d2bc12ee45a4a2a179011f8511ea4c4b5fe778e55632cd63309336006100fc1fed98dc83d8ead95ee8bddba2b7be25dce060faac3bf82e25ddee1cbcf33899313089c2b91938224e4d64458434d205993a43741f1758cc5a9b9389a2d6eed866c5d47946c3e442072cf772ed19bc3251a1ce5013703e782cad97333f955aa0824fc3a20becbad8e07639d313e85ef0ecaa6c5eb294357469729a997aa62d9322a3e22973b6170ee29893a443f35d6483f91058ffd6d86c90c99691f972e5fb743925713d78ba8ac197bb5cd3385b027865688452b80817ec1e811c989cd830dacad5c799bc62b2651b22fba3e25670ed7c1b73b10973e0d94027bdef878c201df8fd722de1d84a081cfe47da2391bedfa5ffb04ba4143611b2a5bebf9944ca219a9d8ffba2ded44aee0ba054b1550c499802ca5d1188739a305de0a21e65df558550fbd6a2650432593e77391da176a639b902fe89a4aaf604409a1f663e2e84ea90e2015386864ab0bda1991ac41c681b8f3bc6dd0d90868ddd357e71e7d85bce9074c0babf7c31bd4932667594101add1299181d0f1e0af91e9f5f30e7878718811f273b037d1322b36752364a93b005a2ba9db9c538632d27d2c81bbf4a912a2e88b6c84ad58e8f2e25d942586a0872a5bebeca64b8f4d2b8e2104f7b2404b18cd35e41424de3068cc2fad5c4c484197c34e820f3ae3a915cd52f8beb26a12601c32b1b731fd6d0173212babbe50917c841a318bd4a74af23534d18261ecdc793663e30e1e1a1545dd31ed380630536b5f471627302e118e49a8913dc509339f2e99b19aec79d468280d5bc3688ea2347e1f421d0a50798494e82a05f3702425028b25ed0f18528bdf6db24c6b721f9861cfb06dc9a75e9404fb0928eda2a14e4353d7e49420c5291764885b84cc6cc77ac5be142f42b474bed5b642c888aad6e7af3f26783b32b5cbb66a7dbce00523fc85c64f4ba62acb51e3dc32791c726b37d1f3b5dc691b9a7374d33dd32d8a227d66e2fe952762e5177400244240d091f0249ee9e4cf831aae60a7b445289b4aa11144dc3d92e0bbb544be68f0dba1e850c7f50085f4c0a19028e18012895072bae281f08bfc1c4a46f1bc43ec4cc0eeec2dd432e6237ae557bac06ec31a7413db35c04d817bfc2fb7aeece8b6b786a4464734cc48b89404c91d233e177cc82ee97cdbbd0f5032682137fda440bf02c349f49d0ccd84180b4e55eed91c21d422d300c2da276a8abfd83e67a83bee8c0dc71acd0dc873f758ce0dbad17d037b780a4a221fb8e7caeeab1190e55d642a8d8a96c0dd366c90bd60028fb108bdd10bc9d90785185e9a2223f9950e317ca743ec040cfb3e1972cec3008504cdde54dce3781bd17723621857cc1c42a1aed4f3ceb61df176d5afd0f67d026e3bad36a529bdece1856581add07ad66575cda6e26f62d61deb064363dcc3d09874d867065e0c3311c38aa89b4e43e0dab04a886ea39b25d67823787dd77a84fa4ecc1e546356305c4a92505779caa4cafa047ba1bb57177f206d667120ffe27d6d2aa9781be2ead063f71927d020e320c8c2c218cfedb65211aebf508929413825b3586786c72140c1cd0eb96af3fcb2488a4c1c1cb06d3700733a874916dd9cb3708fb98bb407ed93bcdfa936b0120d25ad99b196c1876e93a24da93ed6c0326c06689eb9b18bb4ddf10a5910f26b4e203aa12fb9c59305a954d07460ab51c74a257ec61d1da6adafd1524a5e2ee9154fba0ce73236d5e971d7924821cb75af33fb4b53ac956000220e10004f0cc198e75dd8ffbab6951b2c6e1d5b5712fde8fa7c150bfc96e7b77db724b29659232fb09b609900937959f48a9d76dd52a0ef03a577134a781299a78c3eb354573f1eb6f608a16ee10ff8633d88af314f2389f87ea41be0263903799c21ed35bfc0b8a6f6b0d28c5d45b54bc579ac4a1bb4d9c6ce734bfe148f3d7f1edee0ec7eb1a58dbc58437133876a9215ac7f2c53ef31dbb2b7dfcbd9a635945155550a1f253eb4013366862e7ab8fae0ae9483979a840e308f2ed1dca22aa366fa69df65a7bc169236710f34608eee8b9d965213e367ce138ff1b4f8edaaad5fc8e34fd9a778ea57b93e2f67260e79cbbd7f1c52a2e5bac9eefbd36fa6e27308f759cde81b5a3565a6b2d6735897d731c6adc6f21c65bd71bc4a411c494f1164813287f7643ef2f89b87a9eebee70675df3adab9ab743734d232204d24b2242f43c005e121102e871af3c1d5a15555431061b3d7d74d8aba7e3fef46e5890065c18828e061cb45737815daddbaf9e949dc8a3dfc8455973aa99300e41ceb1f4148cabc5e10efb5b58a3c68865702a4dc475f3a8974484507242457a3764573d1cb9289b7c736a02532d6e3360afe13ec69714ead76a32432107e9a7ce4305c620f2556c285b1c6976d8a76e9fe61cc405f78699ae952037018adc04a1242421379104f1e2e94d9f7d92027a6de3ed456cbb57bb4ae89e542585c3fb6e09533661ed13d7675beb84aef6c5111b042e37a16b429d5e5b9f7dd283d9d5be2862690f275df5bb1b74489dac83a23bf2c15262bc6ddb86b76d5bb153ddaee8c12b847c977aa57856eca46c90ea49f5c8a06d542e9c13f44ddbf4095cae76b5a022bdbc88a23b4c3d52932a529dd5191432654958100c9603aecadaac6d551b941d5a59036ebe64771d8e3455fa7cc3fa67be1a08400d7479c03140c6f49df9e8eed29149a3ef2c0c93463be757695ea5da4d315cad567a553728d2c411225e10451475064792bea085226c688044162a558de2f7cb1782be7084ff02d08f2d31f864d038586bbbab3a3f64aff4e5471fdd0f50123e32899f116c75ab8e350c8e34f408090a190e501cd91de070748404850c0728e6d1d136ef0a7490c2018aa3a314975f455d7cbcc9a4f2b32565b16dfca38fb7ee23954a4205491002900f7ae6b4d3ceacca1f10ec144146802da3dda43834e7471ae95e41ea92d121280ec53992d9a02b8f1b5a5a4687a038361ab7b4a86c26e75aba70cce13be73a193f3dd768e9dca9dd543813a8fad15b5a5240a042d7e11c0327c20749c81fda536afda8f166aa5bfd315ff30d771448a3e038af83d367d2b05e6712f3c7b525e4c0445365123f3f264dc3aa2d9bc2718737f9a6ca2a8b370c6622e4b1eb747ceabda91655f9cb3dc8a34afe54bce5832521eb523db946aacb38c8ae6399ba2d1c697e73bc6d5e353781b56d902d22914795aa576302bebd4a4c446e696a6407f945fd871bfde27206f5176a3d1cfdd665d8820acad5314a89dcde45e0720ef20c2d4e3f3901e4844f0d03511886547948148c7ec6cb2151e879276a7ed45a5ad1ebdbb51acde58413461f051d959743a2d09a1ec8724817662f873831f4a3a98a6f7ff1724817749efb54dc870e34d553d9c70e5d2fad4e253001203437b4d82d827228a574eae8d0212a44837024508bed37362822b2a44ee58d7511c5846c50abc5a523d50899440244ee9cc4953d4ea9c53e42f2431e6b0be81593fda4380b4771959fb889c3662fa42223f2e5cb2ff194af70124779f6cf4f6ef21f9b9c988be7251f3be79b6bfe33457cba74f94c6c33d311d3ecc896d4371f12b90489bcc416747e53ead5b81d2df5ca74636a6d47bd9243a290f4ed2929c8230dd25a374c1969e3cad9e179693f1a0ce7f44af66a6b6d41bd129231431b51918cd9a13d4764cc167e7a555ff0f253c0992dfc6c0188c59bd902504fafaa0b5e3e0b7066d6f30abd99d96be6d3ab6ac4cb470167663eb31fd09b99fd0cf5aaeabc7c1538636428e6de8c919851af6a0b5efe093863c448d6c29b3122f3e95565c1cb3701678cf8fcc8e0cd18f901ea555dc1cb2f01678c00c156bc1923b0a45ed59c979f02678c24cd54bc1923b39e5ed522de488f91578a3763e465e4a85755052f9f049c317264048985376304c90851af2a112f1f05ce1821325284e2cd18293212d4ab8af3f233386324c88890ca9b3122a4d3ab9a8297ff81334574764ebc99223b41bdaa43bcfc1338532448c8c49b292264d4ab8a82976f02678a18c94abc9922329f5e55d717f1f9497933457e6a7a558578f91c3853a4c666853753c406a757f5042f7f03678ae0e49078334572867a554df0f23570a6c8500ce5cd1489ddf4aadebc7c0bce14b971656fa6884b48af6a095efe05678a08e1f9bc99223c3dbdaa41bcfc0ace14e9799dbc99222fa25e5512bcfc09ce14212af2bc9922457286fc992240bdaa365f04a845f9334560df793345603d55a959415ab3a4ea03f45d572059d4260ac9a29386664929391963f22e82c7e608150a3209e44eede17b8813383fd6214d15aa78ab80b25d2e0dea95d0d09c2ab97937aaa441aab459a7e615fc1182b7be511b4a593cbeaf77765c784bb66841c1205ddac7f035208d11c919ed3c288b3621b514482ea8ac40a7a0b52006da0d88487ec85a4bce50a22cad07e9d2be826f1490e0db4d52906f384e1f9de6b166e5a2a647bd92a36a4691fd3a75ad4559d36602b528e78f9cd3b18a6315efc67409d6318890af216dd580e34be7c85811256a719c39ac3bf64ddff846c5a6346d5a1437a5af69b1a52db5d8da4c69f695c68880c8238dd1ae63697753a22e2a210da69b61f7cdd8392e9c1667ce8e4e8bb76553736f7e53a22c1ac37453a29bd2e7b4eed4699776ada5b5a4bdf4e885c49cfaf5764cb76ea7b74eafaabb74549f3d64d83a8dd362ebb65aa1d6d25a08218f7dd3ba699c9c0a6a60be74aec5a20fb0051a3a1e23682f8890d394314d1ad465ed3627e451c6d0979f9c1c106901124d3842030ecdca504c99f9475e85b082274f1f83f004f9fa361c7908b2f33d8200a97a83f78634405a6c5ac4e5da059c6fc7b9c35d17a05d25969b7a8d5121d2457ef51a4817f9a385f17efef4aaa7e3c80ea48b5ce938f2237f724f670d0e688bd398f5bc0a12f28871d7a2632aabb7d4aa8397169591a56f57a9d989ac3284a395e2843cd6c754703244981aae42533d959d4ba9807ca00bd0be620b79cc2b82c8a33c626488a861d7cea316a7ecda89346553369126929db27924ad8d3e4921cf9d23e48c1c9be9a22ead71ca64b8c59a3418d161ca708b66ca5a53268f0091b561d7049c6c864e481ebbd578ee30fa0525904291ae5bbfe1fa077040c31bac6fe107788436a84b8730272928317f7834cdb5f003f345e70e9d54b03177140043f4dd833cc23c8c8c9932d4c0d4c0fc11e65b18d790d1be2f6f5115d9f976155cab107010c28704b880f4ed13e86abda6a85a4c49e171fd0192861b4aea0281eb34b8ed34530626011da81be5f9a3ea95b050830d094c80c70db5673e175e637e5a5d8623f519b64f8b3e6dfcbc92f2d8affe99fdd3ab1a2af1a80a622323789aa72e01e8557b8516f288ff1485dc3b2de2af06079e5b7ce7e2c9770e03ec7c82dd0bd865e77296deddcc47aa4285a3e83877a1ec951537f9ca11d951e0887ae19e9d7317ee3dc44c9c0b172e5cd8172fdc05c7c2e26127595872e66cc8ce853ccc6709c7e99df7e8b2c91e4d2bd384f4aac5ab6b3ccd82e1d553dcce3a99c96180fd6a979f1760c35aace0046d80f3898c80e83bcf3f9cea08a4585d870a447d412de2ab10725645505b93466d512f87bf0b17ce794d4b8bdbe8daaaad5a536dea96bbce7f7cdf6587b1b3ce73c8c30cbb708616abdba5dbba35fd6ab1ba0bb07d5ab4d1ab91731b3fe0e8215b6c719af9b526861179acadda62f196b065e5aa73812176a0fd82145d422b06e952bd86427c15432060d82abc2067541fab8c9d4d1a76567de8bc2686117984e961a3c5ea34f367f0a103952d7227d94ebd72de02760fcfabf8d8425df55c57569c259c3d17c658419edef9585bb2d5abec55e6b24b4e406f018ef35be4d0ce5aac3295d7301cf1ab568ec8d341cef3cdedacba55ea15e8d5ed52af6656bd0c2e5b0610ac352dda98ba6c6a31a32e37669485b187b5d562f5937fced92866bdea69c3cc1eb14a32463ef58942b9fc16635e01c75a6bbe7a0a38762cc05185026a40fb5182633db1b3af6e028ea992dafaea30942543460d9485f25438e2ec2bc231cb7054792697f15203535498c3daf90c1d6c91538bb17402966851a1a717d632aa749275793065491aaad08dca435913a95dda79469cbba5b2b93cb21fb1500f3f626cc3be69d710b254820737bf921f5473a84a956928d2185734a78fd36702cddac3335d1f9840dfc003a6c529bf20325e4a97cfcd96b2c55c67d3d9347ff56ecc19da23b44dd6dc46fb50b5cd0e304a0759d6295faa90a716a29845218ba209a1288230a3e8b9de8d7baf86a3a81912054ecf6d8aa9e405396948a747b839e55422524a523afa949212638855e983055c0db9099224e496b37b7acf6e393186410c1f64eaa38f976faf01c87288527b96d2fa14f4f1f2d5698f6e1ed0d8538cbba3595b74ea80c5d841af863550fbf4cb278f530739530753c8cca1ac7983b3336d90c8ed953a8d51d80c5c0fbe0c9241d214929efba6d716650b39a3b1d6833cb688914f67ad735ed7264d532986cebb10e3e4118f2db60bc7aeebbaaef31c3a97bdeabace71f05a842933bfeb6ad0b58eed8aeb4cd5730e63ec857214a5df1ac4f9754dd328ed1c83a30c206ff213285becbaeb34f4b7b01661d2b8dabcd7ce5e72f2f852545443354f30a9953e35a72eb25d2e283b1c9298a927cc9b5035441e553644158c83e8150d4f20d7fd74de506bbd0baddf6ec48f31688382b5765d8fc04226d3b9d7371b60215bcf66032ca406dc0458904779f4435938b44b7b177518a6ccfce9b35ef54442cae049a365ccf4c1d8e19043b3a4f78cb5e8833c633f1d4f991949637a2fe17b7a114eb68c9cbc210d3e97f886aa1ff2a8b2416b755186a3cadf09f4f7be80aae7667dacec753a974105b44beddc0675b1be7905b2be6d9ba669d883fcbda606c2a4317d2c58db25669bc055af95eb2eac41b4e41af7de1dd337cf313dc86b38c8579aa6b286ad51bbb9808435d742954dc6b806ec33a854396bbd6df337efcda97763430235705461999c55a5cbf44e8ad34d61962ed3e4353813f883d38003ec946edbe639e46fe186c1718e3e6a4eadd39a8546eaa42bf4515fa1911a09c441caae36a8cb3a722f7dd808e48043047cf8d8e1ea9dd3045912f9c106081bae7aaa7b49e5d05e592f361ba86ad14bb5986fc0a1c51cd6476994830426305f28ebc49d42295db04b17ec38efd0620e2db65442661963013943073007094cc0478b2d8d6838438b537ba20077a6d57067578905d36d340638e943423c426a5a54468d807e7270fa48cf62433d3c3635f368ca60403a3940686ee0f1121beae1a936b5e6480603a23a3447e90851ecd5d3376d837414049b3b53e70340ba5145e40e6bb4d8df216a08d41042a8225045d820c8a31665e800aeab4129ad35c46fc371b329051a072767bfc8d0e1f237e0213215ebec53450d915beeec1375e939b428fd94a2431261c8177a00f012f643ece7e34d85e24fab6d9c65839aebdb35d44d36eaac7dd99785d91765cd24fb72dcbe7939ec4fa41637d77ca4412cbb4383ec2ba95976c7cb611f87f6b54359d6c7be2ccf16d2a1900ad957c5db4ca10ea4d56ad5bc7dbdeccbbe6c45752dda20fb831ffbb23ef64756cda57581439d206f46b800841b5410d3064164e9dc9c94d6538a0e8b2a22cb69f3c9e4901c9a8101edd232484545454585c7109156eca3424e4e4e4e0e4ce8f57abd5e2f9fa120209f9f54ce26219356a48806484fecc8ccf96cc4cfc63f1b9b6a732344470867e8c63534858a72502ac85f518b292de4f12ba24217d100f94051d1d24e10d2cd8b48490776b4d9f4c48e14dd169022595151d16d013972e408073eb0011a54108e14f14092157d545091112981d2cb22bc2452029fe75041e4e978569f04799c48f86738916cf2a8d57c17fdf41a1ac65841aeaed958ad46e89146ad66ccf6b18cbaf4db7022b5d8b2c648dfbe7244ae610de53891f0ec0bddadb98d9e95cee9c38a1172bd511da7549fde55bc42843caa6e5457e91c77b59f520a4e5cfbdbb33949bfa2441e55634501e5b1fe0d2dae20218f35a5fa0873a37ad7b0468bd3fd615a9cdeff633e0d5f5c65ce4a6d9d49a1c8c889ce67e7b3f36994bbc579bdce645dde94934fdf26e74ebd70c4f8861cf6e9f7f6ccf4ae3b45c7f42e4ce1c21de273ce85e2733ccce742192d762879d48e21832c5b6e129c9c22c872892bb466cf395dc20a32cd39294cd9f988f2316090af4f4715a5784e1d7b8a17ee109fb67b178e1873e198b30c29284e8c526911bbc9b518ce35175398b243946ebd19ea1c6712a773618a29dc218a26e761be2994c1e3069772067519e4ebb2454b01911e6073070cb01ae3693102941d6d080a0ca6386a2182a6b2e9c78239e7a49588103794d26a899ce00811216a6c12d39bd52ec99666734e7b9784968a28a5575b7a2dc1acc54b394b3c73ce39e7926b0908144c39a5784901e342cf9e22698aa52988a6386aa2c3f9b16251482805d7c287251ca174f2d44a516230a79c520ce9198772c4143b2f874c51f3637e2987485133054e532990a450922226c50da4a891a20652c8a458c2098ed2222991e05ad49028610851ad5286d54a80093834f6a9b59516816bd183943f8415232156f35dad4570284232899cadd4a30444a9528e128f7c42d7219d5576778ac652aaea728aa685026de0356d17a43818947f376bab95d94a71ecb6d94a43ec160ad24a119b209bbe8f4ca0c6b906529cb6417f6826ab49df4c16949386751368addba0a06fbe49e976b3db264da0a5600ef956fa68f1c21444b1564a5d7cd6f65bb5ee82b3b14faf01f2f105af4bca75f16db8437ceb36142909284e4951bca3e6d5b92828b2620078396474333c1c0198b24f244e9febb9221ce79384309b4b3903fb9882499c857783244cc1d8f10a1f491c6312f1534e82532b409afa9b73d3fb315fc694f89802ad93a4345f41e2e29b782a8524dc21be4909f863ce3e0543e2d353294fc1aef90a97bd4a8563904ff99882bdc6e79fbe0294de8d542877882f2d4c8bdfe623f6cfdbf1fde61c065b9ea3d1cdf06ce8fffab90074343c1ddfd3f0769c7e3af732e55639f93280d932d7df402c80ee034f3ecaf8daf25ee7db9448dfe726de8daf1b421e3be867733a2463b2d35e827a8937f3f9b6852939a73c1d9fe730650b7788bff9b63550af680ffcc391783be613469f67b26f284f07f6edf3eff3a44bf59ae3822b4057a30005e856ac0e06c600312854f60d7fdfe7a82f7b366cfe6dfe7df9cb1beae4c17836d01f436000cf86ef3fe70cd0c5783b4eff3927d3cde0e9e8ffc07e6e869e7dcafe81f5000890b3d6cf206eb17227ef46cae73028fffc0351608c14640d4cc9ae6517ff73542873b843fccfafdb6de65096a40177dede8ff93184648dc457780f12c729a7eec998cda9731bf8e37b5fe12427cf06ec9b2f51059bc7bec927d9817d934fc2c37c9230b5021cb1a3bcf37664df9cf374e0b06bf1fb6c642faff09208104acfd9e864af5274d0a73fbf767286f7d5bbce9e1a6f7d220947106050f51b58afadf43486e0718b9f7334f794d49e26528b9f677026b5f885f8048efeded8a91e1c7dbcfca681178ca5a60b97c4386795ca47f13f26b2f469ab6c178a71ceb56f685bf66cb162941132751fb184fd70b2659c33b458bd5a1bcaaec5dc8d2f0f531d05cb5dae9eb34ae5a2f882f1078d300a68b1861de4d9c2e12ce5b2a4d42bee28378d8e7aa5218d9308cf2ecc2828ac87973eb3208988ac53a35ec98edafaebe7c8db912ea9cce9392fbd1ba969478a24feb57ee5b5d12f6ab1bd29d8dba9f54b85281752225ad4a2752a4459d4a85dac5b7af476e588ac39bd2c62f880d62e49e5881abdadf282d42fc84dad8258cea87408a4b1162d259a2ed655aa9c31cf5bebd4ad8f94e8a9d0db16279a32d4ad5328c8d5e6d6a91424ebe4b6bf931f29518c4689defa09d4a48be61b48a5a83905bd0b561c7ee9a5f4baf4b0765b6cd8c3c63a1db460cfe225919690e7668763fcdae03f55281f72ad28b3796c5e4ea2cf59b5167e1a4e598b4737b9e2f4add4ce19ce341cfdc729ab61a55e7dec70729ae5cd3a9c0e4736763857cac9f266d4a5abcc9b51d63c9a324f89865836c1894f2d4d0843a810faebdd3faf0a511e5d868fd31bcc5dff9cdf63aabc99f937f4599ede2155a972be6177ab843d0ffd3fe6d294311aa0b9df7f2baafab4e10f5905123819537bcaf4df192e1c4d1a92a3272ba3c9e9a5dfa9b9f43a656c087e0c42030c40b181a5bf3c04a17bce397b00a73108cf16e4e190434bf8ebd3c32187ccfe4a21222d9cc7de8f437a210b38e41580fe3a155ef0c41457088234ea914e48267e2097588274620a4f48311304084f18800a0608423b8d3a8e70845c82091a4c885ef8eb21142d66207681850f4ca08a6821831b3fa412ad1e9ea67dbef576a4bec3310344dad5ae76b597d3b80311502a583d91834bfafb846bafe9346e20a65501c725e26f0ecb82bf3f7f5d7a0bb8387f17809bc84185fca58fb18631d6b0a661ac691ac6589b4c9cc65f00f5c1023ed85fc79e8e98bf5e4529675049c3aa50ef67386a5e7b843266e6e3f0c56779861af0d73c00ff43564185f6d2cb3186af698087fbd869f0ff48c0638e7673014174b48c1629a52e835a23f81a2d52246439848a1966a0fea2e243a8287acf75e3674b4b8ba23847fc52d213cd217b4e2488884046a48723447a90a186d09ce35cb670618fd9ba49535654ee1d2a63e06412e77057eba4dd9482ed1a98837a33c006a573f60ffeaa2eed56b75a275cdbb7cd627cf2e99cc9a4c95447d9e8eff514c8033c9d3a8ee34ca6eb178ff9044ea016afbf5cbf451d5e1ade9828242e2bc571dcf5eee9deb9de427a35ff3ad7d13087162f490e447c38a23206dcea55c90b97cebd004bfc55e2d3738967e7722e29711b9d4b40f9b9c427580296dcaeba8ca1827c83d33a537fed8a729ee0d4c9b9eb664eafba1d48562b5197ea2c3ed61fc8aac4d7ea73a75daa6796faa32a01a70e0a89ecf9966a91ab2f3560648cfc295d585c0205358b8f75a8f3e955f72ac14399e8eb588dbe9678cbe6252c289f604665cf5da762e95858bc64eba657ab8f4fe6dc54595a6cf47c20bd6aa7431b51af3c96fc604a54d848252040a60ccaab77c00372c5f202060b0ac68b1d144755a277a1c9745da8bbc38123e72ccee29c863716182d2dbeb5b4b04c96961d7c68a88b33af4b23d28aee8e101e17ca59c04682017652bb54e7e60e2c53e7f5f2f9c99939d52bd05717ce7a516135a80ad5a11aab44b5c8c5ebe5f3a3438b3bf860913b9f91c57768b16e2e7c585e2f17a14f8bd5abcbfa42b5b20c7ef2eff67cf5dcf97cf9c818f903d42bd80dba42bd92c151126987166bd803e534f3a74e0e2adcb94258dc05d846b276517acd9ec953774c266fa93b2ce1cc79cd9cd775a1eece75b9eeceddb93b97e7f6c81597920564c5e7077577b8bb435940507747e9ee28bdee8ed2ebd680a72f0fc77152964d33c7e7eedc1da5bb73775e63ad0e84b2a492d20460be938f2f2f2da20c895b61bbc0a4c64249072a63266a883cd69d20b5d52ed5b389af788bc9c78cd3ab12ef36183eaa58dc148eb9572aefc22a84a77613e513ec1cd575dee59cdd06d5a157d9a4063975a828f365757157f113137701a77499a89f0e0364716ecead3301573c83f26545e516b9daea41ae56bc7af5415612c86a02595120572c5ebd122157255e61843538c8127739a552f11693e9c35d4ea954bce5cb9eaddc58bc64c559421ee66fe1d8e1c8618fce51b526d199575ff1ecdcbdb57639a552f116f1745ad1e5944ac55bc415b86a3e5aba394b5670748ef271d6ab91cebc3a8f5e75e198c3eea27c4085302dc2a83696920cf2903748d410d9f372cc20249343d5b450afb24908537d059c418ad35b401b529cae0215206b90e274169001529c5e02f290e27418e00dd265768eea483c0604c91a111f8e9e880f3cf5a6599306223e187d750e77b39bdd0c61e4b49576f6fa391c43f02de038bf25b3786dd539e21395ea559e59c21cc2b458e5c4a8d025111f8ed456f516ea950c221da84b75155799c84230d522e72a70c43480e1289066fe4ac88019285f078a55c211e79370cc26483042190dd3ab69f2b69cabd377e70ae995573f3f79bdaeaf453f640c48cf13c44f15849efad434ef863b275b2cfadac91aa8d7ec5d07faf46eb8d730654c5e7d0132667af51d72c579c58f921fc547d5a3748ee21c48a5d8d1e80250f8a18e068c6e0600e48e70b3162d176b17eb2ea52ae4858fb547f4b1fac4f800e09f8bef33cd66b3d907fac7dd7c1f8b51b34cb1d8e7b2635fe89f2929e9fb4cafd7ebf5adf8671a1afa5af867727ddf27837fa69e9eefdb66b3d9ec43f1cf7463baf952fcdb62b158ec63e1df9694f47ddbebf57a7d25fe6d4343df897f9bebfbbecfc4bfad67ebf93e3c9bcd669f4fa3ede6fb56f88763b1a0d850ecf3f9933469d8a4cfe41f7ebd5eafcfe787873e3c44d32cec727daeefc33d9f5f40cae09ee992319e7fbe43afb27fee43c6e0d96cf669b30fdf7cf82616fb3e2df671fe51ff34ffb4a4eff5bdbed7a70d7dde5ad2f769afeff32965b4a149c3fae7f2f40120d45c2e6208e345a8b908c3960f9c7137e06c36e36ede3ac8720a6349602c164b7aebeede8ae935b4627a995ea6d7d05b6fd1a293c1e432f5ac985c2697c965ea79eb32a8702833d30dca6c36b3be926262114b4289c562496f5d45b595bc865eafd70936d95c5b4fc9e63a6dae6f73596791d25078b6dda0f0ec8467b3ede6ada3acb838869350612c866338e9adab4c42d97ae0e7f335f47abd9878ebaaeffbc2d193c93cb09fc320f68c5db807bbb00bbb700edefa09c6980883549be11b6da6cdb4193ec15b37a1d4352da62569312da6c5b4226fbd04c607724adc91228e888bc9199c1197c41da15c79ccbd32f90cb715f6f53997ae41ed4d5e85344b739d5cd5e714fac9ab115246ebe9a92ff893571d19d3e227afad665d998dacba643567a9572c514819ada5b5a6f813e82718ec0896043b52d42b199a9032d7c8080a4432c67f9a757776603b423b319f5eb5d88194b940403df8d38a9f9ce234eb629d3fb5a810daf327a7ae5eb1f09353134899ab4385f893d31b19a3326b9695c99464b425a33649bd4ac98294b14b4b5bf8138a9f2c0c4604333a991c41ca58232322413246c5d32cbbb3f3daf9399df8c9fb082963818062f0a7123f79d734cbe29b3fb51ae7e4ddea15c918a48cd5b13a552cc99894ac5955264392cd4e2b9c90327569490a4532a6036a5685c18260433fbdfafce4130929538d4e3e7d640cca4f756767eeecf09cb203a5e04f3e5120652a104db36a8bba58c74b55c7c89ffc0252a6ea4cd70e3e640c453a9d9c47b3a88cbad82525fcc9754819ba3469589741b0196afcc965348bc218d02bec27af41c6687e72187ff23f9d5cf68902fdc93d90be0b29438d5a644cf593e3b67e3af994321468d2b07ef213ec6db48d0e3597d6136a2ecda5b9b49eb79e6af958645a2b94c964d657802719604632c06030a3b74ee25e8bbb738164b8406f1db5d2b1b8adabc3e2b66eebeabcf5acc2a5c89658c864b2a5b7fea1984c60463018cce8ad9f54db89ddb1402676c7eeec00bd75af0493d896d5b1ad936d59ef52da0ad91289cc939d42d9d25be7bafbc18c603098d15b37a1ec8f5a4daddc4c9577dafa46243b575b55a7b66aabbae0ad6f937a446f89a80652c7b225994c46c55bc7d4136ad182d53798110c067be2ad6bb5571e500c4f87cb73eef9c4e8dafbe96438e7a2acec01b58bf576a01aca412faf077286f542ef851a923b8a13c338e722efe5f9b4687fdac5ba177beb35ec90861487e6509d1cfdda4b1bd25eda4b7b694cbc75147c3a3a21356b8607e4f3034e1ae03c6a175bc1760c7a402d5abf2047d4a2e58a9a024d198e099286753b53a9b275829c617da44378eb262e266758470dc9d5c7cf95a7cd9feb73bdade9d5e8bdb49b5ebd6643c199f46acfa75932dcba07d42becd63d981704ceef402a35296acebd288b932e42921382e623f7f396137aeb9b97c3ff4eaf2fc3ebe68cae9bd706ad8a2aaa00c35f0f00e811b58b750ef46231caf2625e0c14e49106327cf3cdbda25ecde849e50c7ba26eb23e039c45ed625d46920c50d53e3d18b5eebd4457b547c3dce2c7580147c4c57aa571b1b729cd75b3167a2ecaf27c72bcd75befbe1d185d0123aed3ebf43a75ea957a3206fbdc5cdeaafdd86186fa4db1ae8376adad76e9dab9fc4b8b9d63a9c5e9373184e4b1717a58af3246f3f62642c650fff1bdd56ca84eabcf60a30606b44bbb0d15d062f7aa36adb39ec6200fd36227b5487d22b5487dc423cccbf06c94c714ebd5c31de2d75043c23a37b18cb22e0c93643fdd909b322d63349752fe89ab4abc5399f8498a08847eaa5fecdfe7dde79d9b785752e226d8e7741409845e1279498404ade7c24e03e7d75e7121089a780b196448693939297116de8d12141f555e52a22a01534a4a5c7c16ae2a618172a272149597b04071150b1b4a4abc73ae553b4a5c8552a2aabd3a39393951b1f06c38711507806eca9839a3abbde23c056299c282c50dac3a3991154f7993f270f4ab4afc3a4c49e75de7d94d7c8577c3244ce9c21de277de491963e25c079adc043c31651330c5c4733671f151fc24c524dc213e4a68a2a95f025312a6f050dfc47f7c6fe25de72627de352d6abec2b3c1c44f421ee697f80b0f47496872612857a97246f988b5b1f6ce10f258895ed3bceb14fc3a8aa857b4a81ac918f99aa352cec3fc5438a6bcc4eb50afc6ce39d77a74feb9e65588fbbc1d13fbf79d3c1d9d7ff7039d87e2bcc2b917c9a3f1bce69fc71092f378e2aa1e276ee29ab78e8c4171cddb0819d3b9e65cd7b9ca4f3a4701e77f9e0d26de9978e75c8d6e82387ac430825b7de705f070f44de7e528719af9275a49389a984c5690dcaeebbc7a3b4cbc73941ff34f5c05b6ab45cd4dc0c6e161fe49d88d25de37ae79397a744e8b9ad3ccef9276d5b87a359af430f1921c921247793a4c4ac2acd5ef9c7a396615318ec863d7bca679ebf44aeb81f2e95aca51de0eac0315ce3077c75dc737c4dd75f177e56fc95ff7a48cf9421f2fcf71328cb177bde21cbb0d9a02e5af00471509e82870ac3ebc8657d4b55ec9d7fc036b8d9ca1551fa48be64610f19a9faa095ed342af75c2d669d1f64e8bf6c26441bd12fd5a7feb55465992069c65a61a2dce51d3348c438cb10d62325986e8dbb7fb36be2eb596f2caab007c53a690513c790cf22f6fb9718e34f37bf448395769e31629c63730d668acd75bc5df90336e4bce7672ceeadda09e6b0df353b0f7a031869053b6500775eb5e6b68510172c675941132d6fc5ecf59a57217c57b15d0e2f56b02e2fb38fa997206fbbf664d1aa463e9fddf47d27d1f48bf7fb399bd5ce2251120728c601fbe5cc20a3a40207dfecd37ffb27f4164df5c49bf3936dafc5bc20a371ff37289a3d8e73cf62dfbb6c4134b1c0156563a1964e85a780782218ba9a3b5d65a9d8587c3fe17ca1671af6a7461078661c7d2d2b970f102068f1f7dbc7c8d01833ccecf357faef9f6b9f8d8730d533e8cca3eebe7d373f694ea9a6f39fb985237f046ae5f08130306b907cdfc1e9a7362f70246278a2e31441700d0906145458564058ba283a8e42cf586412e15a2190c0000002315000028140c084442a1602c8e1351113f14000c879a4c6848994863518ce428088210320821030002034440646848a4004c4641dd5c54186e42cbae10028eb0265a5a71e09a442c3bf645554b50cd2b36f1c02325e6ea087f8d1ad2ac8f616544d803bd5f2cd74d5ed5982de3d950c790c120bf50fcfe2709fc0fe9febce951fd30989031c068fb979190d95bb5b77751fc432911422650ffcd64f8840c29fde91ef1a7bafe8311530a1955ea9fcd650c9996b5ebb5f276922191092054770714fc3a854c53fdcf2531432628fd1511f18f64e58fb5e418329d1d7af9dd9021da7fb483fe57fdff6c74f221a3daff33697e42a6e1fc3301f9bffaffb38049858c6aefcfe252977d8f6394795f6122d0bf4bc38ef659c854e250bafa43a66defd96fdb1b35d67f9c2173766dbc621d8835824ba6a990992df9ac47f577b6b04d6da571a687af33e0ac72841732648dddee417bab749b060f19520d6846221f1732c4a38d4d98876d9e41c8d03e9b7fa9d85b29f581cb103238994f6df02da0ab6db807b0a75cf9f2e21ec1a436ad88f8bc4ec8ec2f81badf8d9e3bf81461305d4366a768c83164827e810906de678cdb670ba4c2443b4e51a91e324d46f39d775c133f7774654c183249c6796bdec5986fc24a2f8919658dd4ac25b03a58eacd6ce6673862cbc7bdccdf9aab6d391ed1c4a58ae2bfa8745005710773254948fb3a190bd3340f621d94d9a95041cbb774c504dd9ca94ce7b8438bb2a77cbc3f3c7ac29cea6e84748a10ed3e9b8d34c7b29b5283024786f64fc5d98ba50a5222e0f5209e544743043d9dd2f85e05c73679f01407559fc1660234594c29edb49373838345a97a06c05006413ec2d277078f2243782590e53616d8083139ad8ccf014dd1c25290dcc1d93662bd02b97e6149ebd29ce40f8c6c8e981e765223cbc65135d414f848e8d730fe922709deda660576943230e2b13629c4ad0ca3b1f8911f5daeb68a5de7e410af723995e0c917814701d54fee5218ae29adc5abb26cd9dd2cd2bbf4e45b75b01906835599044a5123f1dfb846819e976a80df2aeee5927b5c8a8d3f5f4f77e4abc76ee62f4391f861d691a701736a87a9b8e973a174d3dc5b88ebd86118da46265262d66b60cc9ea45f0c8e4d6e4deaf0bcb23903f85707ab7525234498afd07f68695bfee8d28e0136c925a788c02f6189637717ec6308296dbd18c97b7058414a2ba24366762af7f27bf73bc482a92ccba8a8675281d958598b81baf2deff0ee560b2068685c1f961d6efc8d8bf192e2ba85bbc3fb4f42be6e82affb8a4f04afacc076a172f92593ad7beb5d1ff855f002ab3a6b0bd35d27a0e3d16e36066579d67ff053c306668f85c4ad5cf43325d6410dcd42ad1eade3b5f0dc6839ac95ef313208c1b7616bf61cdee6a17e114dba6547a47e06b71ea94cb67871a2cf21d8a53cb06430b877e535b5122babbf5020387b50d7b402e154679faef4a94f88518eece6e670d402febf8507a4d5842fa3e9b1da2da1c4153da6133e5034426cf3d204a575176613726a2ad7185497ed00a2ecd0415130a15492e0bd3ff072f44e0b8eb0e518862c54e8f586cc406eec0ccfb064050f4765f66dadb8645f678ba8ea12607a8fd8f84fa76149e6fb374c5eebb631984b9163029ab5e4de6508deb983f3534b92e1c12d8838d6150ae83a9dc4ff4f11f60351ed1c4a58ae2bfa8dcb2864eeca0950e2efc43349100f66d2292de8288ce892f87f07d513cb98c0628a964f8f880d1cd662f650badd74d9833eda31f64aa704ff6a010ffd1bf7c081e2d82279f61e8578901a102a3e26fc5fe451ac6470fa00d008841fa6d416baf503ec09421c2d70d1ad6bcf0b2a1c5eb0ccaba71847dc8d020dc30bb74157fc08735e0044416957877c92d7cf5bb86b43c1e69372c5f5340b98ec2fdd07b0ef48a143841262734b687e006f99c6e18b4210c5b1c29763a3254cd74812a2c12d64dd080ab24204c10ba4c6bdf887cd6f19d5ff2e7fde8b7585bc83cbaa97a70b4fbc988b4d225a2b3924957eef231c66d8bf95e7a0a5f8efb54e55fcf649df89b527b5981530068f59e1146452e1bde63119e75129b97f954ae708b1530cf113212ea1826439a926eee6e43869e1826b20776402ded9b9519b4dee8038522e27db180e4604a4f063b34ca110f2e4e2f277258e75928321e057da2a1472e8a8a406a007e954be41d9d9e67ca59d006b5dd266230ee8ac089174efba76941432eb69e43cd2af4322833a1dec8b540ddeb809b30fb7700f2b49cb270ea8dc3ffb2ae86756ce2ff1192a13a9d9f3c3c578962c8018d2a5b053063d16cdbc521edb840c13bd755b40f07c41c8640a50497b5cfbff08fcb840fd8b3f86edbf6bb695938dabd0880cebe15b119a454c2d682a89f282e6df3b5dfaa4f03e75fc8df59350e5a1a472138b9452b493e582b805cd189e4965e589d59f44a058e011ee6f17a38844e7e284bcfc5a2bd190f450ee1cf6261fee047ff1992eab2a2a6122960b505d4f285cccb6d36455f8e6576bb63bd919d3261d96cd082004b4e34065c7e3b108ecefae514e1b606e6e6ff5921ffcc840d1b5d2daef30954ec6a0f8619c64433110a01fa99474b6f155875df6aba01a3f35237d2406a7ab035e494d01e87ec0a518d7295427c9c53a7c56e9ec19f967b69b232f274caa6c73cbc0d730b151956095d443efd2f4d5eb160ca11f360084e8feac027980ed990120b8ffe5e6a77e2b480134893632a8b9cd5375e76d5b59998e51947659853fd2fde131d99959777dea717b1bd953dc2200cf4a4aaaa8684d6b361a244aa52ecf3d66efd4419b9ed758b909f9ae24513c514dba5f1eddde7bb2433b6c7d31319a7590841f4beef0788fab1fe378b05b74c3eb23253cb8561a19cea44a99ec79b25e0d812b44ea948188669b2faa80d0213a2eb092ba89019d8fbf92f0122577bdb0860148e0afc9f13db96f62d4a2087d1285d44c38506fdd5b57ed38aafa34507a3ff7dea6636af15e2a8773fa2e76d69b7b417f4bcf661f776a53e6c0d58175e440d6c0baa27a3a0d7746da40fa316d5f00bb39fac33878f550a11c1bd0460d0a362ce23f04108b0799e3e7d5243cbcde8464ffe0d47b832d788d0ab8794d831f011790981667bf03563c63f1fcf97869032b7abe6e56a40458a34fdedeb6ef499dd0982f9567970481a27e52c7c5066507a321dbdc69cf93a118d91ec384a94dbb3b6a96d3bed19b0b3c37028d8cf5b622b19998950cfe50b0cc1ffdd795b70e7580996bc8849f49b1841accda7dc70f7ccb546699b824be2d75d4d29add58ae53cb6561754913ba0c21cfd19c216e3a2b53d2d63e55a192547c72a10f2a589ff27af532781afe04a318b1699575cceba8146391304e70f902bbf287eb4bb6ce6083eb0ac026a3d42004b7b1fd81bf0825256eb50fc319944e384deca9947c763c3f12811b739effcfcc5db0c8e70240688f63655359e1a74670fd779ffd1daa0314ee18af0c47a69ea2d10b79c71a1a051887629c5a18a08c21a68703e81062a34fb9716b302f14000b3bafb1f85c2a20dc74e4bbcffd2788660e779cce3274cdfd4705fc254518b50024fc55d3c41d67d6cab4e4f0da7b3ff1a7d5a8049c162c751822134f9f9bfe4720a04156cb9095979eac7f1f51ea44288104c64b5f30ca2c9e1a7724c10117a5a6f1ce403d4b987b740dd92d7527f286de0d29d613ec7e38f0f21f6563f613100f31787700148b0b12ce673a804406f7fb107ef03da87a02342ac1ae42add17a6767d2d50c2f6f9afe16595a49f60d93f531000643638ecf1da6c2a800234e6ff5e5605800775a0ffbf4542279f5682060c05e2f57f12c4bc6e8f2ee2739cdd49db965080cb9698f3371532a2754af722d18399e5ac82d0638f4d1ec927565374d4a8f82a2018c2b8e6d19da0423a721c9091991f6dd0172722fdc4c7483d7741138dcf5ee148357d74ac87384c9d362ba2fde8e1fc935bc45511daa83ae64db3dd15f0b0cb8c82624b5072a746e1e9508213bdf870ac0de5b6e4f5b5ac61eda6458af7b2b35911a0206825b1d6ac15b87a7872ae8bec6f0a755169d114595c2af58d118eefd7da2fed2615d365b13d6dfe4551132d1abe57e5165786d2f65e6cdac8d31e0a1d463325e0fa9a418da8bf4e3911f4d38207f6054543867a78230e219e3b7f64a02c34f57720df49ed5d1c24957b45659faed623c1c2e6b88e56cf96b093cb917dad927879ac236fc300ac8f33a00a8c366dbe215f37df47380808f7fd3dd8a3066a3b6dd9c060d1f2832b933c6e5abca1126257050623859df8547f949c4179c529c1aab11bab9c1d42706954fbc047a6bd10827f085f118e189800697439d011736266941b20cdf018644bcd10321c0d9ccea5da1b255248a4237610dd924d6d4dcca170ce2aab87c358a2065c89dda14d5019443037eca3bb58382d0ac8a3141610c25841565a671216c325f31ccb808e6489a050e6cd74cc421df4803a630bc9fb25ca1f501929d71b2358631cf2b469f61b3be932c685464165f4237baf815413ec3bbb6fa5a9bfd8237fe88227c6f8dcf1883db55eb62d0b95dd1006b8cd6436a3c4f76e6df537b0b886dcee368e6f1cb3304333b18642fb7dfce339dcff39912c707f3f772c170dfc3113bf0e84a6ff6111f6bfc70ba6cb7f4f91b61b33d589fc6dfb50bf042fc02e41e375dceaade90a2cb7d6a2577a3816b01ef590f89af0965beca11c420af2edcf34e92b0a016db77e871247a41ab84f8c405862d9ed07f387a319d584a12f14f7e7f93e2420dc1f5f188ea950eddccb19291a100821839983adaeb67b1e42a1e5c5b1e4f1d245d9014d91f995a3c02c3321371298ca8550aab7654f421deba75d7dc256731c15188362b11b353628fede4a31f4fdf9fa76b3a0f77d1d65290c1e2ecb2ac28d60751c9e6cd0b937466768e0d887bd3830982f11ab9005b35ccf3de56e9f545b8fd5bf7d45527b903edc27a9ab37b7d1004955f5a65f3027d4d4fb32be773c02ca9dd36512c363cc69d73bd996c8841e4e505a3b704b6fa6b0121fbd1dcebf0a0d9719d77325ae9bdaf8f55858d9fd68c6e28532959283b44a405828b27d7aae2c29a3dbe816bfba6e0ff3d9c382760c1692b0d0aab7a3eebbe59f0331c326bb57282be7e15df14de0f42135c3377d30e95ea291cb87c1107d77a927606a88de4fff3c5d71a398441858d4131d3064cbf1637deeec46ae8d1a3daa71a8067c4e91400eca6a2d2ce63eff275d092fd6ae16fad527394be3fbe24dd9b3dbfc80eb98cb26b9d6361130e44da18a7b81fb8cb9d44e15586000e54a324b2591c966e882ae4eb57757cdebe1542ca8cf0f0c6e1a599f6c40bb157be50ff1d148d96a370ca050a5b10174721044de97cb3efd04c653b68482b70fcdb3a951073efaa7532be33782f2cb5845b52cb53ecd794afad5c58b29348e6623e444adb4c3ec4f00080780200c1e643221f8725a27176cdee10cfafb0960d3c3e099950598d9e882076a7902a317dd0856260eb4d282aaf40e83a937f11645b4c2db070729f59a0f5627f78f580ea4971b45578c86b5f640acc12841af468d1225b90bde8c498c91df77a948603359ad8c78309eed35abdb17cfa6c59a11ec2e2f030b6664d09e434de998414ade2dc3f05d849e61abeba8b2ba83c72f0c4bc50ab03c5a277b809a77d6e15e122f8ad78b413b1495aa8509e90fc606b228a1043cae7f4913a65788caf325143ab69578350f81da2716ea0f9b05848285b9a2ecbc40b278c5f2800b31a9cb9eced3c81e7bcf94812c2fb5e834b8ba3ab8d7546f059803d18c09f195659d20ca0c0e2ccaada9eedb0844a4d9d11293bd6e774aef26baf1ec4464b223ddd9f2d92f6e961b580ba376cbad478016647eb8ca496050e03835ae6ee2d5a22ecfb57c794d05b29b7a8c4b539c802e7de46a0bd140917333253fc65d16288b626fb261ca16abfcf702004500f738adbe1ed549dc3f42de0cb348a3eb12a52d785c510c01b173d06a02569d9559a93f5cad4defe8f69dc4bbb1dd1f3676d8884ebaf1d2845fdd16f4b5c023c3008c2bc9d502dbd66d09b6dd430dc64d3eb03844696e90091d81f448075ce6a11bbbad31279120af1f2e7ad2b979cfcc02ba273c61636f9be600615645eabda113cfbf84e3cc7ca26ae34bcc25865b509859721993b9116476ea5efac50691dd7ef45d154a8c56a26e3c64b6f730fc5786993dd64cff63c78bb96a5a942291442c5c4e0d4e64542d7e18846e3fa23edb2503bf4df17e8291bb8c7121cbe3d0c8e84bd4bba30f3a5ccd76e5c719557ac8c1a28db5708cee7e68d76f38485709f0e620db068b4b12b2fb025ee2dfa59b1ec182af9ea663160c84299d1e07a7bdefac916e38743b20f143c0371be974ea6eb622b74dd3fe071e840092abbfb9c8a990a1659bd8d8bcfc65cbe7a68128072d2c2ecdfe7b89645439950683a7e13810343916c8382278e30d78afc4513b679878813a2f19491420958b944d4d3a5433e2c423984744272a678767e211d75d6a43bd46e36288e5735e8134cc5293f5a274b71f96e92f43eee19f18fd0403b1bfe4ff54c56d01c5f23cdd6f09e24a40459f062d33a7f95e03accfb30fdb60550f0f193b753f279c264a98eacc992f8ed1571bd1b3b3ec79c637dcc01c9e2f0cfa17c1581ff68364365d97e251caa4c3e4a4970b1ca1c58991ccfb62b2d8c1dadcf4fbab077a6f4b9199785c248f7ed08a0c38a79cc5f51d0e9145ab9879cd4b06251f9c5a595c2a5bcbc862591028329a229a71619f97eab0d5e13a24be7819edca3f10fe1b5a20349276a87ce80d4dde21d9850b65da81028f7c12231b836b13fd4a70d8b8e5707bd5b50a64dd48cd422e0185a57aac80c73f2f4b876a1c3860753b447ef13bc49fc1e165f937e40cc46064d943352f9a5759d85328e5e13d35c30ba4f66689b625fcc825f10919b29647df3395a2d92326b7293498c05a050d51125410301863924e28f32e52a49c7da6aa4e2757f38425cfacd4950fd2e4952e74808a9b99eef08ca1d600f618c71ecaf0568b958613374321bde7c2937726e0f748a25ecef71a9af29dc6c678ed1f4794a2003ebe8b05016ada5475fa6211d1fe0d0f81ceb7e7f08a9ffde03c44a16389d3973b62148f9de3860622d199c79e1749e0da77f751a3bfad96935649831a6d89d7e02c6f69d4c84b0e258c4b89c3e068e9e3c43c1ac3ac0b720d4fc8b94de6b66cd1cdea14d80f94728379b62bc8ab0888600f1e8a69c67e374058431a3814582c4e398e6ff1c631219521ce157f709272d95a44701841ec67da98b6b9c5d300a2cbea8c7ee37f6380fecc56a220635ba5e4ba216be39fd048248d147a2d15da53d1ebed47ad664573f2fca32bfb86f6713a39eee5ea1051cf362b31e91a22b1574bc2d5c89461ce88115ba68bfad24b7ca4d81d12c42040c64e9297e209f6c2289e25e97bc10601cd2cd7930221678e9c3389043695a7cd8cbf07b44a7b59b5974ee89d5d18eda8f93980ba65b312489629b922c8be013d175f813a254f16cc571786ab4956d20353931784e7ad5331383d1f6a560b0b9845ac72517b8cc9392da760083b9c116db5822b8052f1b321d4b7a04e52852ca7594653ef0f90c7aa46fccc5492c8bbe192dd179f3c533941a22f20c85855319e7fa8a2039f8adb74faacbae28307bf9a766da667149212d2ab852fe7ccd449ca5bd577512cf4641de058beaafb4baaaee1a5a41fc9d8d90a46af605d057d70108ed955db2f37f0ff29c01fa1d572b014687473f3cf9b190411f418681d1d80be46dcf11f423936b6590371f5a972cefa7e5867a7d9279c5e1cbe534d83716a5378f46386c9e6f280a8ac1c0e0c7c2e7ba1403e09e9db1b589c0ea898588be0fa0540216305db58f04bbe67695597b2fe3498d5f4728d893ad98e7cbaf86ccbd44a9fce7de5944d8d77027acdde7fe27ae26ab7d42ef24c83c0b6cdded7252065aa2a02ecf9e8d7f81881ef7ca2a2a4143bf6646cfacd9911208d8067b90df2703672a7deac2674e5cf4f35565a6cf0eb06fb5b2f7bd5c005b7ff49363cbbd3b2ceda3e972d3ba0040496a264ba5c7094e3b1eb6301b1d70887c8ae07029e199f1ca369a7b07c981cffc7f368c3007545cc8970fee47e9c2596b4eedb894fe721b11aedeff71a1f25a39601dd1f887ecc0f24cf276e6952823648639ee6a4b2b0d98b11ff727d8c4939e7a9e34e26febbf3cdde56d09b1fae0b3b9290f5fd2a1c0f012ee43afd2a8f4b206c3a6aa895d43b8270930321c67a6c51dd52b4906109b8c336f0406e6de6667cebb781b5e3ab58619ac71718bcf15ce9aaf949f2da9c40edd6e68db8fe42466b17f34ddfa9ec944a7263fdc606d4e68a7fcd93b463b5f3d2eb5ce3ab9f6baf7b5e8fb7340728df1953660f08dc3b1669ddeafb22a8ca24422ef327ebc87962704f248d5a4d603f92811f2f02ce808794ff633243f15bcf1bea1e4373ee78c01782bab45dc37f6ab8f0ffb27072ebc5c56251afbb30dcf091cb31c2fd4ea6797e51f6ee68a511b5e548992dbe08793b570971d8955cd32f98fb843756a1949986b614ca8d919688748b4b3b9e33d3f2509c80371c0c01de81a55703e4f54e03d24d5b56131c04258d73f93c016254e296c3df5518d598b347e30092be3ce34d86bb4566fcd0a79aeba4fc509ce942af72f3f6d74e5f8dcdb0d3ce2950ed39b84bc0a8fef578d93bc5105eb44ca6e409f89090e129fcc70d60dc5e77ef8e956d0d415690a0422af614eda6cd321eeaf8a1db7f14f82c0cf0a413bca2c38a5b3398397dc6a661094413f7c5e57734ed1babafc0cc8afed2912a3f940dc6e02239748297b38ac773bb72fc6b46e03ec9aba7842919d3ea91b7700b8866172e33dd463ebcedda356ec14b7d550c016e58cf866cebe24d326d570469f97d8cb9d0125e8e4255143b8d29e6fad8a5a820129348d12bb057d62704880f9a6064ae4f62137e09afd3f6aea9c785bceef9ae22bbf084012a4bef7ab8b28cb1ed57d9567c36a180415a3ef8e006232fb7121a9642135e96625b86973df29e2487b0a70f21f88d7b2274c31247e7ed1ea6997067c2cb8509b49d920941e0514e5359e57c3226fd9ef2768bc015865e0286c5b610613f73b32b2e578e5e2d1e0d8f54d0e41ebcaea088d2b7bef0c508bb62c54cd3fb4fd6054fe88397332ce74b9cf63f7e6f37dd0365fa77976e86ec757c6962f76d9e3e78d176201745cddcdc2123179fe7a3cdae73101150055c729173cadd71a56f4c2475aafd55da42d07eebe820220f5e1adda87bb708239e390b10771a5a3e78693d1a7b188b496728c44c116a8400284b471dd08ffb35a803954e080d8187dd03864b00ebbd7b7f8bf895ca123d2910716e0b956b764c45ec9fd0d9d3bd851433eae9a347e0ec42a9424a23101db38ec93c882da97c6f10ac0814e7b8e79c49feb19adf8a8a2ac23dedd27d24646fdd57f521390f178c1fd4604fa47ea1feba07c0072832f2b87c5e2da2f2522cefda9bca240c5b317d946b4a8a46b0cca540bf4e3c154d73c6ca9026d32487c6a41c34ccc220ef472e977768715f1d1923cac0b4f9b5d9b787f5cb1e7ad1c100f22b4038db61b1213f8c54510b5d50c744273dbe6f07aa421b1cc860e47417859e8803518154976498f13987309c346632377ed4fb19eb56336cc7ab71585320950b2e660264ec3cc3bf75ba9abd8976682fb4f6a7c4aa3b7e4bfe5ec6c2419c08e0dfea3d34bc22d41174f517216e25fd750af1c50099be49f5d8ac7276b81f62b9fbd22bb867d15ec44a29feb7ca1098792179d68d2b4ae4dcf8f42df748e049fd592e34222fce735570ab583302bef10d27e949c39f3963a62fcc483e7b970b5ddb006340f24cbbbee07dd11341fb466684b109c9b69aa4b17733073a213ed85b1c88a7cd4a3792f11ce277fa3e0879643fb2775839bf511fa23ef37bd2f377d9cce96f09da5c76bf8a09b5ec4ae24494015effa6c79b1824cdb1871af6a1d8000d5aaf22a8fa47e740c671c8d70776e775df0e2407f0c107c62ca1fdbb00e4736e380f4ae45a7a7df60cd13b35b1ece8504a962010450855251eea5f011c0ee48b79f16ec7e6dda4ab61266253c1a4b7805db636fcbfa16c3d08ff385a735f9720fc3a2daf2286a39c7cd9435bbe646dc1dc7467e059900453d6a93927e437d10bb91e9f9cbe3e83bd479c0480a1b32b43fb7c1127ee7c7019c26158e72dfee91dd5bae7dabf2f8846f6f3b2ef0a2e8cd7ff3353c19ce5b1fb59d01e6cadd6e88678574ec98be6e8c44181c07c879ffc030a98c0333b71973f5b6e53e0231ac537a82c97e6f50057645592b1022d76465068b83254b51a95396072fa1475c84b35861bcd0113c301383de87e2fff06400d5860b14aa74298e8738c0ca23dd5842011b0691b064185b384fbb601930a156370650df6b1ffae2bd2cc827f62fad560cd37665a3e9696e0d5a3b5cf49a67ddbd6708580459f3e9701d36a806306b9bb05fdce087ddda880d239672a0bbc98de8a2a4ac06676b6bda7eaba3109e27ae6e534c536ea096ea03aa10acef623e5b89e377e89d1c70164386a431242d92807b1c39e33a8adde022244f1e39cc84f64971f56a49c7fe9d27f039293817c660369bf5aaa713113a568a8f56abeeaf102519b7d4d94109de6186aac8ffdcaf2ed42097eaf195b3782b12b4b313211cd60de889b57f2513c7dc419326a38aac29bc5c9e6337066c4944c447a126c9598926372a76b63f9d877853a409f243cbb482956f8e5ebae0939e6c51835373f548dbb56ad7615b126813c0a150001b86430f30493aacd18d3aae54ec347addaa008efc3f6343e23fbd12213a05b9ff5b6465d625067671a756d0e4e8a993d11c475f5067b4ade4efca29e2cab6560ed5ec3d2f6e3dc18ee1c4696da3b5de53f6b8eb1c85e19d96d5d8d25321c8c00b556a7d7a2cdcf33b176ae82faf8ad4ab7ce5269abfe0375740bb7b2ac6c3d6613d3a3dfc461a2905a8db2a1aabd376cfb2399dc04178962b380fa10dfbe1b211055d4a8397ed00fe2a3e8aff2c4471924e189520f9b880913e99cad0ca21b16e32495265e2debaadb249c5243b3a0661996796d09e5ed1114649990f54afee5b56e2c1ac1e474c4422dce43c03d10bbaeab6e1181e39a967a291fdcad11a937673c3d68c8b9ad2a4f445d6bbd08d063b42221f274b682f6bb2eea41c939bf14447814e1ba1b6ec7db9f1060112338d652e21cc26bbbde725cf9ad6ae3eb884e29c0c73d4e49a4d1447872632aa9561b067290df6de2eac0ae86ff981314e27249a90b7b1d2fd7e8ca7cc8d4a4b386a23f445ec71feb32a9e063c2771b8ebde9ce4fd1cacd8a241a2286a9ad2ab665f856004a9367749cc1d7d1f9e3df3018075e8653ed4bc6c18a6825b11196f35c85dda3a338de1f3b090b3a588f03a1d2c1561ebe1d12f69eb0488f16e63f1c0c05a2da82d49e6bf264e6e5891a5801570553363ff7d00a1edd74a75cc31c9c799c6f804364737651296181283a6ea081f591445b10a80710a6b50f20f3c32e8857e2eb6687ef1967cf5822e6bb55b94ae6e2f1845dbe2b9c904c6ee8a12c157657c65f788ff37a96dd464ff0f44f6de90aaa7fbb4d6b05558de940fdcf4722400e0e07a372a59c301a5afe94b25c416a68f519260e64bb35484c6ed524c0142557654911545103a2321d392be068043bb4e678c8705f6c9d50cd3d4e51af4cc685813ed9e05d3bed34f3fc22b00f2c9398dd7b8284eab00cb0585fa0744c4769288f4bb44616dd5188bbef6a518545ea99851a00d8cdab2fe1e1b26a126088788354a76c34eaee0941393c25886a75eeecec65e4f9bff32cbe7c84344e68ac4614caaae35d2b35d4c10e2169d657be50ec9e2cb46b71c0d42a013eb09840b81f9b92185845cffa998aaad88dae6b30c37273f3711eba9a7fddaf747d5caeef0ea8cdeb2d257e1dbf9153ac87200fd591979ae5d60940219413dc2d90663402508eb85b6015a46f07277d662ad98a82812135591f4566921b7f796b89f2cc1f374916afa5d38d509335fa4f1189ba996665cf9c9ca20f1ff10bab3c346cd2f45139c19d5ed996ca42fe959cf2c87af3926d5d9e8a3398172f38380cfdcbc3dfb4f52d480d05e13a2858b8b9c2d6cc0a72e91e163020e83a606b9f54b908880066bf67626cfef4a463d8eef197c8cb6a21d9d6e10a7f62b7e1db5a7ccd5ab0cfa2c67810c722e736ea67282616c3c766d1ab6667247840c2fee81fce95737f5976874b5e25f772c9a7777f953472108eb5ad25dfa92fe34192005a24411b151c05c44fb23fca93b0c11a97cd9af99f75f58bd655a2adf7786f4248a3fba294cc30e1d68458fedfdb354c942451a9e4f53a876de4278e41453007839847dd0d2c1009d6994a75bb5c1ba241e1170b833e21a8940c80a9e218dae97d7be2d3161883f53ac819c979270a13803d70a289d200c10de1d4279f038a1a4994d428d68ef9dc2cdf47c77bac88697287cc11ebb68eec23d9275c6786e03a4ae6c99328466f83e5d6e1aec90e1630906f57789ca69f775a268a5a4838912cd9bdd8c266f5f5d548b5ce06abed7ffc885b6f1c28d6a8693fdadf81cf63e2d245a654c00e4eba27d36b03993e61accd70e801dbf0ab690a6223becded9e15cdbd91427e84a34932735ccb60708731f98d64260f45df47803bdc0d8818fb192efaeea2aecf7022e7a7ac90ea67a0aaf847507de340f10bd75d5f310d3c65199e6751021b859574a09af9222505b0ad7235b6add269523b42355961203d04c732269ab03a82570b3c0584f2c9277d6e7cbe84cb201e37b0569c52d890b4b990f661ef250a5db1bd28da4736898841ce3a81346f471c10998e4669057eb2a120a7ec68bc6288a1495b8a3363894b89e951b68c47e411ea042abdd77ce6ac1a52d5599cf144d7233bcf60d5751a2f131e178e839afc538c5f08601e297b05c25c3b21583f23aa670925c52ada5e5101efaab0db283619c0d4472a543ebd80b5a7d090dd43f477f099886a528b38c18d912620c4f9948f7741038f1c2b412bb33d8ef582ed01c342f9538b85ba7fddbdcb25587f830b72706e0ae5ccbea04936858ae9e1127e84faa8e75e092b1350042fb19e9a9a8817ab7050ee99d1f06d7f75ccbc5982942c0e7277a12e5dc94f24a5ba13ad2cc63c7e7ef2dfc47cf98879d71b00dc309f31338027a00dcc1795c3466381efc0aa3954c522e6ec1821441a5825720419493703a154f4d9385f4ea4ff1a5f5fda37e7a86148c07c1fb2f874c7eb3a80c6e69589dea2cf3ec3f49d01c6ccd6cb1bb2bc8071dc687c136280c4cc861c2a0cc97795c271aa8a48d4ddbabba0ba39364f7b9249e60f5dafd1b510a05c66f218532885c4b3233059d5709f9196debebdf8679f7aea9cf23e703b57a193728dc9f1c645fa8435d7bd4e72b7b10253d5bc72373e18dbb111cfba9e7b4608737ae7e17675ed46aeea50a035b59f1d760f597b8b96415d420c00eaf512609845d701d4a3b2916cef5b9682adb547481781d71b411502474e3880b8cc411b78743870fc5277b14d82034c12f8bd2578e5beea8e6443f4a4ac0a062026cabd2cd3aed068925ab47b42fda83c063d01bcbf438572b0f72f945c20b86829849f01c543425f4261956bfda871a30845eeb1ba1524e3047778620a0d635abe92a47a9b8ddc57b3e01a2747e60f361ea4cb2bdcdf68005ea77c0c2026a7b896ae597f6a7e2bf93073762a0afe71dfb8abe5d638315c3ebd69c38ce736925bf91c802fd10e385408ee306b76754e14dd65be3906a61629af540574c50c2baaf65520f60d600e1a8f55bfbb2db1eecd7bbe97b76a834dc238852bc43a28a18c53783f6072b027fb5b2272812558031bdbc6e837f0a60ea975763cc1736338ed705e35791db1b442f6444d9fc84e0978bf62c2d7757010d951407ff52af5d3a85404bbae23f334c5bcdf20dc524d18fe6eaf0854a8a1433c277c927055aa7530510acd5eedb4007b7699d4772748deb36df014d2bca48e1cdfa4ef90f729f86ddce9d69cab30ba9e34e8514b11a50fb8d121ce202259f580e4c116813f3732897341e15fca19ee6fd12bb5d3fbd02349a80cb36d6180a7f07c841a41370edefc79989bd6c97f4bef8f2bbff9a7d24ef344a91b1a6c1052ecbfd1aafb2c6248ae92ed9e872fb8db3ea94979e90591444d5ec25645f0da2410f3c32412b96e964a61b91c5b25ce2aa5bd9d2413701d143210cc4895f697adf06d142bd4239a976b52324d40d978f1eeb61eb346ee24b6a13f6da85bd59cdf3c09fcccd68973e51231cd125f0b43f2a3b8eabbd36d5561ef45545c646887fbbe0b3f2d7c58955dba84826dfa963fe27a5b70eaad42a7a450cb2f3be11bc6277765258c2c30b7526f2573b6c95ba6a7dfbc01cf304d77397506a0369a1bb808224eed122137381d82a9c16d39b24340b45ef47232a05a2ed068f04758cb27e152de63fa79816f808b6c792c30818b84d58fa617ac7acbe1f0b7f51eb0573eaa660da8dc799603b2ef25d0530f900d684d0371af98a6043d4dc7caa2581c40911a80b5229f2d0a0b60be3672e7d0623050974bbad8ce922ee708c5324d103f7842b4df9b910a3a217226c39899bdbf3910c87926d679d4ed1f657cc24f4fadbdd25b02b38fe37fdf2d116e8361e2aae4a8101209036012580a6215d0afc78f759fdd2e4a5c515901526dfdf497a0a1454e712c2fdec2d8540483f22e46f84dbd02ab02099d167a739f66f2602f3c7fba18339e74ad51b0d6f273a0902b8b813df6f73b4d8decb4890a0a9f7d9a63f9118e7d9ad54c386bf5ed3dcd0a8ee5b491fe9280c0fce31dc83889e77187efad3522ff2f92ad41e0d40fe3432466a3cb6b0b09c95e50c506fb66e8d4921e6940438d4b17f436a9095c99dbc25a6375b539a97b1e55d7f9008bb351a9fbcbf7df56035657eec14ddacbfbaa40adcee6ee5cfe086142ea7daa5807408c55d0192dc0335343f9351914c808ebf060561d90e6f0fbd39fd81d6862be1f465d93c19acc6b302b2ca3d78252e2485b00f638e4ed4f6faa6c451d833b0976922bc1102815ed7480da916fe0b29568ea1788e2322ba36a4f5afa8251a6b838e73df9bdff1eedb8b6ee6ce7c3103ffb9a94759e99c91f775015a7aa963d6629965058f2d8de5d3767503e62e326e2f2759aa38f4599a3999e7b95e000e5d9d98c18c54f46c9b690f872e21322728688ce789633b0928c0723c0abfd1e0d7a3614c608e59b60b2e08c83f9e11a97a227cef3600e9703b42c9a6b09bc657e1ecc013a3faf14596e803c9891f3e709e8376e0770f48a3e8db389a0a1921f19d6e15418f06f6820ea73357b4237c0ec2cd8a8341da92e3589900cc8483cad195495bfad2d148d9cca41209d7876288027af66059d909ae88cf4c7c4dc754296ba103d557e48357f4c3c11fa675ecb444b921ca8b00f34dfc1e4c75e6cfbf95c549b483662d2f4f421d075a8ee2a2396be7838d42f7c281b04d3f0ac0ad9d8205848c76b64fdc6abb902c2aae26581ad7b10b3328ad0521d4ebdeaddecd0b3a840dbeb2acee70177f50ed217e40d1f7b6ef5ee6fbc29d4d8ee908339e84ae10ed72b19ce26796afa779e986e0187fa9e8d61f32c47dc57a6c562c7d2fb67635c8c57d131ad09c1c0ac501c97c3d3710a921f6d0b431ae524c77f78ae06c8d1ad2b24c1ef4ebebb8ceaa1c633d9427be38901785c7d52f7bfac8e7e3f4089a67dc8bfb65bba3e9815b2991c1de3dabf363d69405682b4fcb74c894493e0344e16ffc36c277aeee2052c1081465326e3fdbaaedd7015fa7e4603ba61518d49b371539b9c153eb5508e8a14c4d300099b4992820c92108ba7f330325485489c0bdeb6d5bcbe719e45a3c86fbd08c88194e5330c69ad9cbcbc902cb76ff484a987e4570ee237244c6dad4be9a64b91edecb25371711b8d90d19f125fd85cfa7474331219edb122eba1639ecafd1192fba7d040b85cea2bde5ba9bb4f3955c61c0004eac1019ffe6cbf695dd7d5b6f53abfa016c1aaa266646f7558c292b1ccab3253c8b0224bf830feccf85655670ed2e1f13465270d2d13025e2141bc894196e5bcea012e76d95ec52986b71c3ee68fced34ba9e993873e355d038f6f1a737a5eae47b589aa10a5b9dd754ee177b2ac86ac564bb198fe663fe1b8b40284037514be481065c63c7647617c731f5b0c6159c2beaeab638e78b10c8b1404a605a65e61d60786b9069883aca7a81cfea0bcb72c5b1e9942442f630c5f4b1695cfa5b727073389e0eb2fe4a0d5acfdd30e96feafcc9b0ce29715d4c9715679bc6f25731a85b72ef23b31898e0097461de95de909e6e79644f4f3411fd048dcb94012df8b4c3bff47ef249429cdf612abcb7a2c38510069ec0441612c7184a75d064555f157c91a5342791e2cc630253ef5a8435eef6b5ceebb183a0645aefc6bf94bdbf53f953f164235b4e5c1f6cdaf5ca4b05b9cccde5d9c68064ce4a2de918966fcf369f0aec6ab892bb2d13afd9aca897309ed3862ac2ecccef1e5771b7423b85a91f66de7c1b9cb38875c60c94ebcbb14357a34287f80637ea99b66c48c55d546a76a87a2096058d4b942b7b9c1016a7b00bacc3e216de1b9ab5cc5bd7e33c5222130eaae2d43dfc12f7177a5d26fde95d49d55cefaeb6f5dbcb68689f13ade6761733384f80459c919f1df955bcd47f20bc2547a1d2712edc6e340c902be9bbcfe18e2bbc6eaf5794f0de58cebeed292ea0a0599237b66dd527fa06210e4492562632f7ef58f20fe35d643799f5c4af3d2a4a20764175c58063513372c565ae709102d5c336bbea6e289090990c3ec31119e954f7f450994d70d58a26b018d75193b01bd0602e5e00a329b690efab64d08b76ab667813d8fc57759e44148f27bfed95388bb9c3966cd692020e31cb95111fdc1a1a49e0178f462f17e8483187339f9d35ba8b4ec2811f6eb441fb47d6bd4c8467db5913a6ddb4f189d95cd8ca2dfba1d1c391d34284340b9cfc535a5418e08d591a22124a9d95b98f27da3f004ca5acbdd456b04a1b4336554409919554702163c8d2f8fce34a78b2687af2796c69f6daa76b632bad8df888e30243eb7918f44ac8e2f572211626d33e0a00396698325deae6574e6cfa161974c8b9d8f53f43ea1d8f55af57b4b05c4b3266fa8953aa2e12ea908bb7c605a7957068054f8c8e85a0c29a08180c3790a6bb23a31ca0a7e43eb08a3faa3b8b42604883252c3caec4999449726cda6e85552ad8bf89678993ae50e2957135051a18d3f8829ec4c8f397459c533c06503901debae45c4290a93a8022544baee84eea2308d2a9040a66b9de8aa51e166daf2f19be5f67193ce4f4f557dc756606233d757aa805760f269414a24bf5027ff8597838d8e292e199fa898a304b422e25ae73a898a096a402512aeebac1b8b7e02530ed22281f82f79f1ede3f4ee199ba8feaf33415165b3dc85bd1e6a7997c87e74fe2031a5fe0326d0da42732d16580b016929909604d09a005b12686b81684da0d609b0954052eb4b8096056ec85c013ed99640c4fb9ab1c9936d547cd0bb75e55ebb6e8bf4833c5d8e644df05a37c07a751665d127cb20071d07f1341b3b5d4cdad56cca60c70f00fc9ec74243fdc053844779e7bc342bcb23417d36c4a66ca915e1d77c3626014e892717f4981c2cddde35ecd31482a652f0a34fbfb4b4c4a179f2f1e8c9d895bc8421069e6fe0e222ffd3b68789ac468e472059683a06944f30d02c938bfd6283987ecca609f504242697b6fa57acd8cdd370841e8e7c43409f49c45cdc7e09f38b5fd769920b19816fdc8153b785f7ebeb7b8356e449dc3c93e6faf0695e9f4e18a88a51ecee2c967a1692bec9909e9697f45491a4356578ca3f72eccec9d5892df12c86f1b82cf62a57f124004aee153fc46768096029119529a65ecc2926f2045d602756db3eeada988e3f6a81c8aaceb48b71155dfd388d6daf32bc4f96dae80eeb5b8659d31c993283010d65509d67afdf592b5cb87a021e6b167710912222846a4f95e339e100d9876fac6d5e0bbbe24a7fc701968302e002307c3068051d3f9d61fac3107cfd013b283a6e37d1d95b72eadf9bce26cad711daa3943e874892cc547764b7041579e7efff9e58d8f5b4db0e52b4ae13ec9e54ee60d9be528e2fea963413c8d5b890b09a79ddcbb91b4f543200b0a187a47d0c60955044c7262a0c7997dc48630d3fce9fb2d81f6fd3fe6772b80ee45911aa0e7305631158b5193a212d858d3f1699195d66a4bdd42d0ba209b8339607da309408c56135d8c6e1ca639ea4e596857053e1701509d0b06474ab0b477d8aa2cd290c58a2e9bf08fdf8e9c563ede5e26e7a09ebafe5a3d2f9706a35bf1455f1ab4afb07ec48f0402bbe19bd9b5c64fbf483dd35b9ac449ad4111b5d2ec09297130e6131866b38f06b5cf06c593299d5a0d924b32f6c75939dad6e78c756775bc147424b864e9c0954e80069fe46c43bebb633eb34411e7b586287b5028f51669360d0c94476259d9e8e4c95c63cd606dedba85744bd46625f79fc53ab394d4cc47856a917d502932031ddf3f73301dbcc813daf1356c24b929655d2b91cb32793ddc15fa0b0892d3c6d7bf553a254de6b6d44789f7ffd4a82207b3e0651bb66a1fca89a11e37e2c277254bafc0e493b3f153efaedbcabd7bd61f4c37254ea6d0e51912d0e5f3364ab22f68a91b4e727663ece89cf42410860a418bf9f43b04ccb3d99306e9a8cf2c2d539659f9632f185cfa19ca9d3d5f7081bf49e5496c2586ab37e5be31ec596492f242b57945b147f656ec0fbc62af944eec53f165ebb3494a986d91da0872ce13a1338160d8444ff668092d21755c033d1759b2df3260adab1b6d96870a623a4540ba37243d89c8c12e539a23397e5d34e68ba0f315ef291709943332cd64f74aac076bcc8e5c3c8fcc1acfbf946f679438beccbee71f063438b59107dbb30cdf6fc2439d9811ccc056eb2f9c67311abf22c1d8b6e712d3f37ac1f9cdfaca7a17b9215f99026f230ce919b7f91785a352ffb13d4a2453fe7299447b02b36059a6e3d205e36d1cf36963ae5db04e4e1553fa6495d37876796fbc20bbe779f32f96384a6978a0e6bff21e4fd81ecac4f6ceb65f31c6c9f65fe7e41a55f5da6285e9330182599692d93971619fed8b604032d808ef3cae83155aca43e03f4d47e4dc4c9ff4989646575cfda58d23de83967fe84fab7052d04a98392c4c027a37968a30bd9a916f53486f55a9ad82d3990f45a4e471edd4ef3735f02f251f6846092f1f4e10699fc75704e209a677e91e297c39aea4b0b6e9056b959a28fbd2777b108a1b8adb99ee11dcd988411cce35c623b4243a7fb6e14c8fc64d87067978e813991c3f897bc55bf2fb10f737d34a9ffc822279f8cdd01e286332269d67af8f527ccdd582d626d47c3680b3efc5305ecba461503e3ae468c0bcbf775bd3f701536e7fd27741c5778a8c92839de17d166e04005147064c83968fae3a4970cfef5723a8cbf83e1cfd90f9f410f45254825c8f4145cdf6923d5d93910760c8c767a2f94ded9a152cb9cd5971ff25cc3825df074c41f115fe9e6ab28f3933f01a2719a3dbd6f49d6295e1d671a36a17c0ee2dece1ec5ff85c6f9b489b63ec17bfbf8a6fa1b8bc20ca0891b89466246e24e484afc5552d63a8eb7d7d7173eda68430206a70aa992047b50087f73dc3d7d1758cf58b036d3a7a25758b902244453307e1a7a0289e2d853171158c4d48983a52de4685fc2dec166f1b259506a7586e133f670b7ab35b4a2ff394cf4f977c521348d141c385dc5ed315a65bbe18ab79e6a62a8ca22e74639cea7d29633536a3b526e38be5d3f312d5356edb35e5980fba17d23dcab10d69b28c1fdfe265128b3244a0150f05d45bd77b0bad28daa9794b1e31bef58072906c1088888a583d023bb3bc5428c9cdd1daabcf6bb48e4fd7449e3246394ab988743e29f173e046b68dfe62f1839b0539fa781e349d672ce270de71c7ab3df3069a0d41df321f7c01c65439043681be4194c92c8490add7447f5a77b1187ac2e6207d6667d331a71aa775d4fa2b4f1deb10661a0921873bbfc178e4474d96295c0061973aff3178c4c3497ff84ab82379a319fb323a0ae1d62fb0a8f69035a694b5e639afd361df6153f8908d4480599037b9c2ca0104b63eebccb91cf290e1a590c54630e9b2c0a0744173d71fdcf30f9e3c437999958eda286ef2a5743667a0a8909d5c6245823c257307a6ea9ddef2ec7ebbc9cb8a5af79ffac082b83c23d9de55c010a572208b79c30343f322b3a8819a9c9b880016390aeb6504b2c07b1bbfa1351c49a52fa5350ab59030bc49001536881fb1ef0f2555c1e32bee0aa118e5c78796dc4d3a3ae8c4698472286177c8a0059a9183033778a6652f950a90cbe519aa849a23b7613830f8599fdcb82c5cd659144c758b77e96c61a8b96242330280df1a17a41e8a60ebcfbd89544cf4825f7c517f0e4c20aea35e4fea853353feba89780a68495bc369fb001d63aba8496bde37f4280bb06782a9811f689dec586caa91481c694709ed1c5e3dfb0331e0a25668090ff7391205c1abea186f895aa144efb59383f87ebe3af3677c33875b70e4ffbf9b4b7ef87e376629cd6c580a6596bcf425e9a3567529b823e6aae2e945f2d869814b33a5c6e8b9e4707fe00a2c6ab785589dbd21a4c33bf526c01d33d65bbcb5872b5016734d8b55ce507fa687fd0f1d5a66a648e1f7e5fb94545c74b94d5c945010e3412aff69d4357b06e41db5cdfad56778b1ee0431dacbe42fe890b688c861f86828faa591cfd80c00b68ef0774a84a1729851abb6661149da3dfe3113a24700ee4136464ff7cb300129b9c1bed0aa10bb284c2dcda8937e866b8bf9f56f4c03199f9720821007668eaf708818602cec95401b020241b9be8de4931364ab70d84944edbbbf6c118446123d4f7e02a0321631ea87b0ea22e10b275afab0d141ab418739240e8ad7bc7742f4d2bd26672fac1d50084826d6d0e246e4f1f05ff83ac4bba3f8e36a3d5428abf079b8f146e54cc6bc977ad4f2310b03c052d0e9c06c14b564dd8fd91810757d53274fb0183ae194ca402901cac952bb9bd66be7b9d87a7c6699ccb131b6d3d03b2b33817e9a5537b341d489512a83ead49502b00661698fdf1bdf5724ae93a10f8d59cd589e3d4d8388b6ea2bfba18f84cd8746a42eab8b22339f9eee208074e0fa701c6d124f2d08cc9ab6894a6505f709740a98a0f98f5e32d5292c3e4f43a619ee846c960f64707b47b3a5eade275122d6bb8eb7a269368f9f93a99303f81bcfc0c33dede9a2cc956b1becbd293636495aa97472876b72e170557753b48909ab86c75b3e1ae4d3aa2ab96fe26620d79593f164aeee85cad3a2a0d53f9adafa92e3077bf6b9c1343794df96980a15cf8396b0a686cbb0d8c70f30400690aff0d41712c7a2743555edf6ceaeae02585163cde52251482b0ac9d1a40e8f9e5f399a52dc9eff4615a2b23c56771a843458cb19f1bc98ee755e3aa9ccec1318ff7a91f8f636e49373378bd44589374fe4a2562ff5eb80b06be4488487d053c8d67e2d35e7d5b1f3573383423149fbc8c0faa318219ddb45d0834861b5814d23c01618fc2c45f4789af8c42a4e3104820191edf7ff93e0094f0d49b1ba8d89282e35a181c0a96160ff26e44b40338ded3eba00316006a567463807f34beea77580f237b11fef6ad588bad6933e6dc810748339f9084df569d6310ff25f102f9f922b0346460c7c0ebdb2e206985440c9cde801ed399fdfe8b814b16e79901f5e63b12876d570263914c626c64c079870c1046aae9a27f95fa5544a3dfbedd51f2b70b946f0938261accc12598e07692cbc708b7195c5132822a4de33817c513c861e7b1741f4e04bff2cb4f3f8222bc23c3dae3d5311f0afcdf9e8c9c92c7da172b72f602be9353935b30c223a8d2898dcf8cac161093ce2ff7e9b44dbb82c743f3e903c0a8fe2c704b03bff98804dd716352feb628936a308fc45c9ef1abdc53ead4b737b4933c291e460fc1d64b9ae4befdd0362fd98db430c6325741cc4471db8bf13f97f5aea21d720578ffc776251525c24bc5a97b01c1ea747dd5873243a65c33dba25ef9e90ce69ad8392619941b3f6eae6c6c11fb6f7fd8c7ff806304dbe51d7a24fd284b5ad5639782d0085752449a89806ccc1d2c937bfa6a7aa7273bd2e17dd72c4a9a211b38ae43ca44d7cad8b90ccb535c7fe5824a820e0617937721205273526fc0ef6502a7c5bfabf6aa67d4d83b94a8116122c820d56b0168df8b1927eb3504a5028c8bf7ef456453258d300029d1051c5a0434de4618ca2f137612119e901932a7c90d519a741f0725dce3bd903a2dbea519295c6d398d05d412ac2fdf4efcd4e97d7563ae6824bd9b5adc3aff7cd2bf3f761fa7bfd091be100eb6038fa52964c09654a26f068264615262727f8e1aea9e3426e09df7b6a9d1bd8d615b5813bff6b3fddd35f1d33edd22be79a92abe7f3742364974a05e2534040c6dc2257cc012069e44eda63c470d2b7a3d9064d5c9fb877d7c4cd5017b7ed452356adc892e90a033d85e3d4b797d8aa20d33be0570d07cde8874897d6eb359ac04f03a0cc0057f3bb492971b50ba560add9be01363e13812dfba48046939c0443b86731ad931dc311a93eb0e6274557072e66c1554c124c7713198ee81e7bf07dd117939e60a30ea1a6212b098b3851dfc1197347ceea58d76e1742dd0d56ef33f301928bbbe47f2e9bc2ee98660a983ac1680ee20d090a128106d0c2c9aa81e1df73936d06f15fc4a295a9740b36da7093f99f27db01335579f96cf6c28359b67c5c3bd606f49eb29420006a782dbd90602c80c3e396f7d5addeadf41fd7451a321b8336863196e140a5ab1bbb9f2755bdd47fe25612b3b63172e01464b974712a35f2ca502b55da82a4d7fcaec60337509fd4444525edd0558bda7681fe60a060c811da9e215183f5816ad3ea893152bf645d08b8c098324ddfd87586b2f607cee6a0f278e808d69a3da9c56ab8aa2c6f9a01916a1bbe6ede2440929b87d30ae87054db58259a36edcbbe0c34d3a470943d29a76c9d47f899ebdf41c4913324925ff621912b8806435dbca18c91d33e96200ed17fda88ae89a5872483b9d636db630d4d76df8951866bb5535e08a0316e35e461383ae2c414749028209c0ae8a82a5978607136f7becc206d5db5255bd077687875a908b7a46958f508b8c0976f30d983c82fc7e71eae94487d906317c320c353ee353d1569850d5828c29a60cce9112f0889d42460f1e2a405fa9ae196702e6c394228d01ba08e573c822fe7eedd74d7f3c753ea29569482a8ed8bc7103b32f1abd956ba7834ea5f3588156cded07a68a10e2d96ff03e7b96c85c8fcab64744c406511ca5886fa8e76b99ba20bb66925cf5208a95a5644c308eae5588bda1d407fc608e9b4d5c95edb9d3ff4afd54d6f18710f435d4ab20eba347771b22bd0e286a6067eef5ed034eb53e0cb1fc44ad6c6902c561f8229007a0678bd91de5df55f4bc2e5014c4d62e4fa40f62df01b87340b14f08394f906c63c3afbdc15ca078f10b209c921baab56187a39a122378a45ce091b0800f0805c83691875e1b6d61bab5118e51404e6a1ea169663121cdd63ec554a393c02c8f473124c7f3ac74bfc7782e3a10a9295d76200773dbfe9d0cc054103236af42a169c0aed88207c93eb5d594341977055e6d26263c56525774efda233770624dacadf236aea2546d4642d0b8338318769d93a132da3ccb08bd711ba9c827fde2c338961de7e30ca0a56534705e07b3220636a0916a8b38c9b8b22ca379831b63f0f977675fd6904a73163f3a5a465ccc64d43cbfc214b4be8c525b780f85d10afefca354d2ca8e784fa4bc47d0d83d0c4730792544ae2c05780997427501ac235c51325e803dc1419e9827580d25b9658fa220e10339447fffeea4550922d1f438a1673a8db84b58c93d7afe5745ab7ba925f6b7a94e543ed8f121d0991fceb93fffefba71cbeafe6302423095fa124cd55bce32318b09c5e6fe5b28a5c46ed59cb46cb6ee2988108bfad76904a42acd014e3535626fcebfbf6e7816dcb5224fe4a809473638ceb3e3848858623260fd7f4e3100d8295d62dfa095ad649d7c456ecde3a58b2527f1ca1d8f9f97ddcdff975d8595c01d571ae4c278cd988bfa1bb54dbe0feef3da425e6ee8aab2f8a1e4ddaa39c93261955a000214fa66464c17d8c8ccf6d2b7b1952e6bd4bc5671e3dc5572ebb3c85d17334d055ec44e14f33fb079f9948551cbae5ea66bb9e8b09176b3c4fea0292b52a7871c62fe67dd5d8bdf96b1a629c4a4dbe58ce56581fd92884075c9a22159ebc8a7d3eb5ce01e44d28204a5e4f18b65b5dd5882114322be0737c191d569fc4a67d30ff3116731be91e41742f23e2c2731cecd09b69b484c2cba616d162ae3223a29a7af631e24d69b79443aaee89cbbac19184f41b3242aa31199f35dbe06ebf5a587e15225ed50cdc9960d2b900211accecf69052b452ef1919b276f9d9bfebfae8b0ffcecca30ca45bb8b7289ec923aa6d17c4ba014ab2a1786a6b11ea6af1d53ac5e87c612f815ec490ef244af835bc7a24b3f8bf66606c1822403ddfa7733b66c1c80bde29534a7103c57d3a4ca79fb2cad356ee0dfcf44976e97ebd96a5c53defab98a4597cdab1b419e271719fc2a7d83e04ba76d29f61f7c02c418e493e58b77d183dd457a3e2c18043d89a1f2834dabf06aa19261b370c35cdba6111e5f2b8c85b6284a6b122000f8a3997921ca377d16256eb90ffe313e4d42d476e48f36d6e7f6182bd37ea6d639f8a385cb16e9c601b359d057edea6e4fc29182d5102f7d47f1a0bfbc2f798ea549a02b89793096465ac9399ccdec9dd3b3a3c59923ae23ba03648785b077bcd6bb3604862d7eb9d0fdfda8739a09883ff476086c15dc84274d2e56968db5f1124df91f3459086eac4f2c46642137142d73bc0c060585a06b3ebae5ab54d9ef8d519cd1bb9f330fbbd47eee87461f741724b9501c4c6a326c4a4e44b0e071805b4d8c6019c534c656d38baf2eef68e201a0dd47b0f20615707411ff33bc654997825b63be0518b1b121357f30fad01b16fe6c1dc6449d0ca6e5440c00627cb898298332ded417dccfa62322ee4a806b99b06da456d22b96e5db394a87de48ed7423f1071a6ea22666606a86aac59213f8b434c10bbabceb25894573df14c4b3261da994de42ca66ff1be6d28e6adf398b22366a303e7fc795c22d1e1c606467123ba3c2af5cf86ab82e3512864a2ae50dcacecfc74deafde231f575851eb9f376a3435dc0c434b5405e6932700558af1341e606347abad7407a808bfd4f08d632d2ecc84f52776860170ea33f9cc1f9e33340e976d936364b5d50fb0fe535d3da81bbb1c11fb0b4a1d34ed5166c603d418a9d420e4b25fa7482700cac7525dcb32b93f2bcd6b3ad1b0e244bfa0c09894583e113bb9068a311d5054af4a19d7fa313b6703d3543c912266593042070e03d0ea6e38261f1df55dbfe95801155d2df9fc6ae9becbaebbf518d567687087b39dabd2df230576c578a652b2b040cfa6dbced15e0e355eb951de4146a5dc603789faefb9a671439647470055de2842b3b8a27da3cd988bef3d65737394896ec5ee6a4e4438014e4ab396e433ee1456ed6baaf499686f943a2cc9a787a910b69fe96f7013371d0e9fa92f7475d1c2e6f086d5b71193f26c5bd3a8c6201051b977e6316361f9cba5427ac69b48df992999b75d0012b0c55d8e879889551ef0c1772bf782d44acb458c1fde22479adb65c3d179d9a71f6c07d6c803312386c2a01a00979c77d72b8e33b7f75cad3bcb0bf01d8ac5934ca261aa13bba67f7f98ebf8f0f29f31d5bd999a7052b31dc9c22b7c9ce252de7214c6c2171dce72a6dc249ca3356510fcbb583cc72f19be8fca0ef86d407cd7e431fb068c628522a691a3f5735125cb2b7486d0b56a2d75ff307d05e081b3ea8c1322a5b7a012dd75d620db9052bbb5d37a41f0834aadcd69bcd114d5eb467928fbab08f1cbfe812b467a8a0a7ededfcd8d5bc329c48e0532cdd287213c6574b6e6512b8f211203a2294c4a58aee1a2608553f54aa68a8d639fe3646b36372a5038240b10a95e5433bc534a494d489b91118da704ebadc1b0783a576944c66b5e60d1f57cdba697be7c6b431e3891128da5575a096d34ed6e2a1cc149e5c74c3911dc128aef20f41d90815f83ce6457582b9f5b8e497d51aa38f91682ad3cb495599fa53329c99384c17cecec52d529ee7793dcdd7de6e67cf9e3313a9a6c65e57296a2f508fb6a69839b2dacdbc1bf69e1709d0d454aaeae1ea8a815765d647738dc3ccdeea5c33556fda15201d96a864bdb87418002494de8c8cb0041dc6e8410119a7768fd05724d9b28b22073a9c1588c6067b9ae6e15c0b5e9b19d4f7f3a83ff80b42780b10c45da1e597a1f16bc40b28399c48a6ce234a315db8988b225894b24f0936570152565d72d6bcf661ed8414cdb35d324cb35462e0475a74ded25b2c8ed4e4ce9fc535c5f55cdfa49a662dc218fec8c39a5a502a5b2b0b684bf6e106c1fdd536df27fa1721865624cb9d7dc47951f547ca479db64b5e2b7854e46d1f3ae1238c20016e2535fb957115cacbb2b861053f6db3c50cdce3d556cd8fd7a322dd6bb0204150082488b9742e5678ceeca297c2f83da694f9322e99609fab60a05104c0529c7601c32cf5d77e824fe00b683077aa0a016542a1b6c547289332118ce0719f5883cca5428d8cd124b09bab77a3d8849af14aaec684b5d6c3319bf226b0280fcb9dcf6cb5d4a654c669aaf09b45acd7153ff770014fed7e37a8bfd8bb31358aafb377fda5fdbd42639da62da087a9aeac4220570a66184a3f9d83ebea6724b5330b48830d008d28045ffd83e80f01b41828c856b9a0f509fb5e8f0cb1b1350fe414c147315c0d53f5e571a999f618d1bc0a35051860b3860e1630359cd060d4f786cc9b9ecfd2006bf9801f76705957fc698eda0e78614f8a1dacb9560ae96645b260e2c6874569c11576385a925fe34a7c292314e6ac2c10a7f1c8c33bb775248083e9732de0b5b85a3a9c5290bb87f4917a8ace4aaa5e90cb32634a2343b4adc555c9e1c09527806cc55662b8a8f2e2e1789212be2a8f106b5321f61852d8db1c3d94d07fd24fe57ab4bf491243b846a667ae1e6c33a07ba590c8adfca712796d611541a369938df9ea629543efc6bbd69e62e6821f685889442ab312fb93a666e75798273c8fa06f182f608479be63c633621b46f2bc3897cfe6593a94996ad593601fb44299b5c647c3b809ad50aa039de358b68652299b94bcb048d9f31694b2b7d829245ac4cb96ccbbb8b7e671e5d239ce9db41f5fcc564aafaca3d4bc9d512b7fa361d5a9c4f82ad65272628f1256ad2953ce02a5759652f6641ba5cdb88508e35ff7982c85a17a84e1837dc2ce96b98ec2ea5ce9cf313262283f2fc2bc7971d50192e715f00243e00462e2b257370c0d0efea5d09bc5501dcb63ca2c47fe7be00fede0b2c292c39d843aef7efb1a2706e095200132967768140f93f8461c5605291557a36e4d4c16c4b833eeb6deb5a213a123277983ab445a1539c0c7e367e56359fd5e1d619dc3763d190af62f993ffeba9d05eecbc8c2924fc784c2a22db8c6316e9ec067d5545e01ef26c2bb930754e38194457740d196a181c4d33ed36c92fc752e12a5ed5c92c50af0159c7f24a5999035dcb2528143a44469c6c471e35a2dcd54db403eb5846c3a9cc1f34dde5a2ff6b084e23369190fee0a6bdd35bd93fb768548e0ec0b0632a1e81f6e76b9f268ad3fdc40370bb133af9a7614cafdf7364e837441e9e8cc85df6ad5c2e09d8ca92bab9b5224fefee4342d2f43151739299a14a57eb1bba1933bd04af980a3fdf24bc8c80010be85c9f84659aaf19d59101a10d4f6b516232f52d9c211d8873783199897fe2ba6db022b0e2eb5388e6c13a30c1f327968a23a1be1dc1c682bbd41242b73bd544d021beb7966365adbd3c8451256cb901e57902ae87dde742104180872123ee06f443d946c20018314fff3f409142790287cba93aa5647dabfcc44d71a34347472fbd34ee82725ddd4ca767e887b239d83ad48dbfd980a8a8b782720d2cda273950f6640788d3392bed3a136fc922f33ee6bd6fdb0385dbe5a231207f246e54f1618ab975184704d4922c603a0aae44ac93f3df1c68d8408012b2caccd3a4c213838842a42d3d54aeb497644dd0753f2dce8483d9de024b77ab2f814338a8a7941ecffd9a5ca4402361312863345c5f7023c9b5ee1f4bd3d1bb1756d652cf633cc20a4b9f6a0ee6e51d685169220f12a1fd317fd4f4d3394adb6788a0909ed18e24217db67da9ffc31946fe348aed018a56eea212759e25314d3c20e33364a72e324983c34b5273fc4411c61d823d97bd333449445bce17a4948b5504e876431f1647f0248ab041ae9f571e5d42efa3b311ab00056a2747e52c13cf0b77d17ed383f9e02dcfc592f3d94447f3c18d3c9a70b788119e662b3c0678900c1a4d7bf5c52040e0a46b94c537b916a4fbea1be12164dfec130e2ad141834130a808dce745e6fa72cf802fa2dc9f30f2ae8a08374e4703e334d8c9183cb3d7af68916ac7aa56aa18e7fa25f6d3ae7cc92fad37f930a64ff3845ab1147b02e908efd190bab8b2879830d911f67b34ad75ab3d8cae733e15a2c421f856795347f6d8e1510d91ca79750d14d4e3971ed0254da686f7317ff51191e50a8c260791f059e8a02ad7a7045d6294e511129bbd106335513220b6df5c695f95bc7794c2357c6230d258b001c302182c36e784bb87203bd20783935b0229b8f553800455d3cda982498a890ec703706a340460408cba352c0e801c9f0ed0644831bcdcfa0765e470b56d6705eddb26e2a07b6be3faa9970d6b3238b4da350f4b934c07743e8af930362d7e7e3cb99a9667ba0adb8d323fe47e65a8110dcba9163320224d1b2208f65dd0008325ead1be8dc6c88e30f02c5f12e258ef13117512a2622defb4fb4d0e388ae3b69b91df295130572eb78ff519e65fa1d68dc7f223cb50ec0ae40b575f3432a86db6f8ee9eb6ece6c15aefffd3209a25486b48438e4694b402ed0e96c619626383346912399e6981ecf45be99d9728f198971d79f062fead81d53d5009811ae8d18bb82a3e4002f859be405c56c201a05bbe408b7a300521d07d49f0c09d3cfcbff76bb22d9f7deb58e822a1644acc0cfa1bef5d11309c5047120e5328ba0b5a61321178f6588f8ff49d8491861063717a2d8e8d52264c4a857690fbeacdcc31c939ed7beb538b3b4abb0ce59733a854a6eb6f7586db1af041f304b990f948685555291aacafa26ea395e62b8d5cbe4b556442263b274b1b5f0b9cd68830dfc04e84a510f9a98d9886631ef6eeb4e038606e8a3bdaf6a31ab502c3615057f66c31f23bfcaefc41cf184558605a95373754f61853fd7d018c18df3816093cafe6ba471fe0061134906adf15e9c70a29938bf7afe8fe3a945ecbd648262a2566e3691a22999e4f38074cd3a65104c4a7ad642ca84bed300854246b24918b3dcd147e9f84b606c92ced4849600a204a2a9fefd8230f4a67daeff0416ff381d02f5c7b063e068e6a3f37dc034243da54d7df049df311ec04e0918a15271322beb58fd5d9317663fca95d71f81c68b158487a666828d8ba7170318ce7df469b0ea8865bd721ff64745bb0960d7adea896ec4bbd28d8910fea2aee574b8f8dc82c650c73132bdad9ca4311b5259097f74613a81285ffbd4e121da245ad610c0e44cf9744e70b8958a96ce8f0916889895815ac639ca468a6339b44acd1cc38e5f51b506419414869278cc787bc0828044a75a8c6797965a6204afda7fffa781b517f43371f1d50cc494ac634cf823a38d0bbb366e017aa3dc85526b8102cb358a6ff94c23b411648026ce26951fb49c53b4d11f4e7917c7cb48c0949e49cc6c737dc5311c606284f2eaba1396fe867c16247ea62b2ffbb2ca32985da0c648889fecbbc6471cea85e3b5d9fb8c74de0db6d290e6d7ff313363af7f78741d6e9f315543ed2c6d0f989d310ef2e49fe59af14c185ea9abe889f21dd861ad69f810a17cb21a878d8f94810ca5b4f20bd4d7971bc72b5c54b0861016cd7a409b1a51ffe2485a9b479bb012d4ef138aa229e61011e93261f1f0af64f9b8089ce2aad98d5b0198a55df850699bdbd95f421cb36305e29a0bd440f269c0521e0056225d95227eaac68b7244b0849a51c7fcd79c9f131be3d57df91e996fa3a7aebe8f5495585aaa8836e0fd0f7ab3d7a22038c0619fd2fcb13efe1e050de1f988ed95e43d9ca328924c8baced9f291c5368ec28bdee8c17cea43c0e0c04542c1f2485cde812ea1d1758575a6826266b721043740659ae34f9ecdba898bb4793cb3fed1e883c21e05a2eb4bd024a875e7d5a405c51493a486f916b0178fbad9198de20b2558d160c0b4e630132be37f96c85d5295a8d4881a249bbbfde5de9869d2549e3843c8e42fb7413f7ae60abd537defe1cd8eb986564338d5a1d9d70fd1b69841042f6de64ef2df70efe078b0764073388648c68c3f91c5b24472c3e4698922a0a51e8b71f12383441d0d14f86bc4aaf44940c0504015730530853f13286878787878787878787878787878787878787878787878787878787878787878707ebc9f224232051e4a98becc178b8c41499c11144a5802e9165ef85f82f1a91e533686d494a150d89f28098224729202ac20744960653e2e1115ce4c85541a3760439f90ef5ca775feecb086ee42f91fc1592bf427a25de6d3e860bba6f0ab1d6daebde17d65a8bad3db1d6da5bebc25a6b4dacf56aadf564ada9d65a4b6a6d516bada55a57d45a2b49ad2caeebba466a55715dd745ba2e91ebbaaeeeba525cd7756dd74529a5d7c56919a594524a29a5776429a594c2802c5a29a52fae172faeebc5afebc575c1f88bc378715dd7ce85e78e9054cd9e468991359120745b808a7e86805c2360c0f0660ba6aeebbaaeebbaae9a939a96544d1f4191356930758026cecffcd1336b00c57579b3074cc5e717b873e6e23afee5e2c2c5d648d57cb13a91353d4023636f78581b1058265a3c09e44920fb225532c8f6882c490539c4d248222b2385581ed20a560919c4c39d5de0ce9909c7845385a44ae65897c8923f903fb6a5a716b580608db881a928d342a6b6a44aced4a0c8922af851817cd49f1eb58705158a18988a9d4d4db4a2896a8d547551d589ac0e038f9af3526f5aaa4d475199e821988a3d2c7aea8b54b54fed11599d841652693aa8ca7490ca430955891d988a9d57f8f071094955db5497c8ea255ab525e72abab9866e508da87979b95a52d53057506475087a5c403331578f082e285c4462500cd289ac7885969ca29ba1cb263e7131118172722e9d2b0841729432590ee1520207a63aa7c09db36c269ba1425215692e97c88a2db8b95a6c68510d1d7ac165848c76efcdc028032da9da40506461208602c1d01f17da03cb40a120220ba2065123550dd089ac05c8ee265026148051ab82a9bc48d58c1e91958229f13b3459ea90a34ca14ac880a9ce15779e8d8a534868fa109a3f64e3377571c9b2bdf76c75f7df33680665d93d6bba659bbca74eb778cf97eef992659fdeb3c7ec9165b714ea965df29e2ed325cb66f196adee2ebd6550b7ace996355936c95beac856f1962f6d7323fbfd4e02b2640fd923cbee16ea6ed25bba48972c5be4ddad6e65d9ddbb833a28cbeeaee92ec9dfb44eeb64d9dcbb5fbafb25cbd6de23cbee1e3145a8bb5db27c0520ab5d46efbe107085097540ad1f556cf57140560c8a29f2359dacb90157d9fbb321f7df955efa8a3a277075bd638f9822dff7b0e761ec1d630fe3937b3ff130c63d1e8e19e3d9d3a8387da84b64cd244c217406cd19644e25d093136feebc38c63ffe31c6c7c7ff8be3bfc0ff7dbcc0311f4f1f2da99a364bb480727e6e7a6e0005c6deac79813b7bb873d6c2e43731f94dfeff263731c1fff14d4c4ce64b0b1cb3099e2f8d82d189ac19029812df236726473945c0c4ef4d17988a271393d3c9e4271313939b9c4c4ea7dfe437399d4e412e70cc272c831a15a5508fc89257982d344553e609254c4c3c09e402776e813b672c4a4ea79292d34b4ea7d34f25a79212939f6e722a2929c989b904cb1c21a9923ad325b2641082cc169fa29da1214c234e274fe2c054a9a4a4542a79a9a4a4e425a59252e9f4929f4a4aa5929c31e1984b58ce342ad20445966cc10d90cd4f4d8f7c011425259e9481a9d899850a92528984a4749252a9f412498984a4e4a597944848488a54e09849620fd189ac1e838cc99130f246ba489b3230512a794d04532324242323241f21212139c908c9c848e9242f918c8c8cf8acc0318fe0f6912f52d53f3d22ab970053e287240d90e4d104a9040989d73d2b70671522a491111269e4a49191918f90464824928f9c64844422b58d088e99147f4897c86a1cecc8169d229ca11c48234646bcf60153b12391ba8ef48e44229dd491ba6ee4a48f90baae6b981438e60e374c4baa9a475064f51135404df3d3323d4d02284824af5f600a7716c19d336eebba6debbe755df76eebb68df4eea46edb36a198371c856aa42a0ad189ac988576c919724364a30526bace8b413015b56dd3b4eddab66ddfb44dd3ba6fef364dd3744651a7d523b2221180729432465022cbc79c51e70cbb9a76aff6ab6957bb77bbf64dbbf7469a8b238d904b644518c094789c6ef951e423c7b7119ae6c599ccde6bedbdbdf7feda6badf67bed5a6b37906da05131b604459606a20c50e4f1135f7ace00c5bd5e06602a3e63b873764d6be7b49fd64e3ba79d7336e06a408d543d4027b218409423e426c886892060aad239299da773ce4f3a299db793523aa3ce78e9115937c094f82805a00454d134ad555748a2c80b8d9c321452b136e7be34a5b1e91331771351519eed79fad9f17621cb9392d0f5ed7d69db4252a5f57d892c0a53e24f5ae45096750459a24aa8fc92e57d9128f23077b534ba9c12bc4257284b7b851a45b26001202a1ec046160d98027b54902510b9caf11f96258ded069c11c305dd77853880bd881105257002093b3b3b3b3b3b3b3b3b3b3b4e38e1841340800001020408102040800001e284134e38e184134247fce46ec25f8329091b252fa194b6bb08dddd9f7d7d7e25d8224f7bea98ad143a69029f2cb54ffb6a8e24243a9b214c115173dbae513a4c383c2db73752ea9593464d0c5fc0d48c41f2a4de61ea089d3c6d892d73c63925445df014e775fd6ada3b744fa0278c4a3b59a60a4037e26e536a65b893d948149ab5620addb21d9ab9edb3999cf4aaf66299b671a38e2492628444c58a52090bd3c9a4858b634fbb60d8beead6e1ac28c88f0f4cd1379013604ab693e564a14cb33b7627bbaeebda9569d9657772bd25baae2cbbf4befb4d9eabb7ad5e8139bbc45a056f5b25c60bba7ba38f88031f2f29b9f771a38f7bb5958cf3b422eebb46d5df0fb46eb2dbeb367b6fef24604126a7d0ec7c86e93b4c7bbb2daab7b6eb848a8ab0793e4b21228261a3ebf1da6baf1d6d2cf2369b107ff71490aaebd328f5d6765da91e4a1f0d0a65e7fed973ffae8fe6f964dba115d97cd0a1896c3e689e8ecd8b3f38f6dc21a23933c232865468f4e0404241246a549d31b9be614ca32a849e1c9a386816d71cb11c9244b268f2983153a651554611069822874c1868ad9c018af981c9f52e924855891aa55eca7c192a3d435e5a69a5955266d7a5072b51aeb733bda26d2cd7359215ba4fca5c8f97dcb7673289be4cafc89873646f9a6d63c9bcd91ed5014cd901a9079d1c82a97a6a135930d7d31f37d487fe642a6d069491e15703468635600d5823d71a50870c51d9560babad975b9c12539b2a8c90405584237095fd25252452c30c4398eaec3b4639fbbdcdb08d5b8eacbd6e6868b6cc97c9a3fe6ad846dbfcb879b645f4f27a1ffd91d48b3e33fe789ac5578ed4e607bd6954c5a141647c78ecbce8b4e49a65574c9649d44c97fd8b3d364172ffbb62723d8cd993eba74f51afec88993b5149481794ebad9f435a456dac4b07538ac995ce7cb447dffed36ebf791ffd893d1b4b5ff38a8899f3beecf6d90bed621ad4a87a8a2910a6411a55cf61fa436f70680e0542aea73f8029f58d8bb0d97ec34574990a519d46a9af3c685e7eb5948a6cd150ae45275186eee4fa1ab61e3a5b4c7f50410ec155a59144702573b555934331a58899b1ef1865ecf659a957be2805ed59d7a19f2ad4536a751f8d56a3d4b7143a29997424b90f9190eb2954492440947a3fe1ea1a69547ba5fe1bc55d3be79db2d6811d3bd647c2fa7cff4921b96eeffef5bb7fb5952bdc3429876aa9f3ee908eeca3931b42f0f8f11387d0e8b4108c8d8f2ceadcd55f5b8eedd683b1f59079f1c72322e6cd8b3e8db2387b7bf4074c519b2abcdedab8f9a0c37af1c78b3e3d3dd1a706a4d7c79f268088c513a63c54a334c953a9a93d3668356c3db4a783f3da25f3bae51a8e431a557f710c18745f1cf2cddc26fbcd1b62ca19a9d328fb7a99d82335dbb57695912a9546e9d3f86c4ed72af53c9ba169f318b5b2d65f9ac8223dcdeb1a556f2bd6f2a1a0094d3081091f6002c42128cb777737125d3674bb24430a34ca7cb497929cd0c91c88329f93a37ddacf58b197a48b9cf1ed358f467d9d13443fb700573be2507f33518d4a3ab36b308d82aa06822dd475a506d211cfe22247af6320fde48e1462a2d33537ba8af44919faddf922cf03aebaed17e790f2caeece165924876049d3a3518a6884d07d924645c2b225771aa581fab273cbcba25ed93cee4a172f95c02162fee2cbbdcf71f341922e5d5e485e03013590494ea763bbcbfc664c9e3162d07d33e67dba51a116b27d42ef93304388b40eda70a0640943424137c48c3bdf8914d2a8295b30356fd2eabee992e775f4898879fb271b26cf4b1a1d3ee08ac301e5a4a492eb9846c9cc999eee933231df7431c57f73f34c317a4264bf00a2cccfc0de20cff70e7203a18380244c9e73be85344ccc68b3116396c75fc4c9f35246cef4cafc7ccf68835b46aae2112d135fbef8d255bee006ea2021a96aa15209a6877c4fafe890b20557f7f312097015778c68cc76cbd18ffd18a5b45846c4a89c756c189bbec0d454c912a657ac2743de6341e5f666a3e69c39460f856a1d261c92fe24a318e5a206dd77caa746c5fedcba451319cc462d3de83282275dbee0ec125c75beae43afc05fdfe1877bea03fd8494ee206ff40a2c8118a2dc90afc339e79cf3f1e2f959e8d989bf1936a037a346d915d6fb99efafab099897fcc156be5ebb1f4af2ba8468d47d295ff633f776add4ae9db0e712c30b4d1635924ff74120ed11902a2b32a2a274284b9ed91416ff6098fe9964a832230559108029524563c5e42e6e72782d76e01c9e40a3247788e939ec033c85dfb6cbed870e64f8d8a8ed3bc808f4ca76ee06ccd0f3a00b1cc3079dc9b7b7780671c85a6093732d4c4cbe43732d4c3c13f458361a2793f7c16b67e16999e2afcb14cbcd86d474c8147f33b2f6a93da38738c60cba8f5e1392513993324ad8127a1f07f2e6c12c21aa513d6306a4a7747b6f36b67f9b8d9182eeb3599ae04df0d82fdc3ce8b1a0507972988899e18bb079f4de6c504f46a32280c24adef715de7715de5722f1bed28897c213f148de67b7d373e73cd3e6a180e466038eb0f6cc447fbdfb089ba8073f4467e8ede0404b1ea2f30e0eb4e411fd080f11b767dc21e6ba256fd7b5ef2037fc71a02513613f7a7b883fea097192b18925c3df0372cd5365768473461e265103a29f285986562fbd8f4696d0bbb71845dfa80c789fb55d572afd2854760c778d12e2bb98029f1094520534caa7ab41f4e565344aa21a4696265992647995199bfc148246eeff0bdd773a792752526b3b03bd12ef4dfd0f257fe3bdedb1a014d0286a1b40bdaf5fe10fee728d8d75c8f574b331e392b12f785df2334b152d434903e69c5020505946a36aadb5d67aaa59d248132480c9f61bc9548846413929d028d897a7b7b15133c1073c4029a594d29839e79c734a29a59c734e39e79c27b97473ce39e79c281892033273b85578245805cfc0341ed0b2e503b4f3a3932223057530cbd39f30544588527f7bf0b033d4430807e847a6f751ffa1645affdd9069fd5723d30ca6eaf52c7fb1a6075c5d31d6441b920a6dcbf0d2ecf0590f5dbffaec71eb6106e5e9651577f9c2a3390353d947f9c25c11972fbcf96cf9c29a8d962f9c49982c5f1813c2f285afce856dd3d87ce13aa4e60b5f3ff407cd179e91074c65320b7103a6b2d77c0253d9b5b3e140993353a75153a651f45db2d9b058c342b7423a7df4116de0aa3b7dd4015c6da79f4a802a592481c0955664cd9898424f796821cf172b647a08a190694ca66f656ada58fab57ebe7ece5a27099b668747d8629f9f3358a751df9ca13c745e7068cb8fa24ce750a635b1e7f6f6df14624165d206c43cd66d38e631af086a324d4fb61a157da2cf679aa6e9f968d467330cf39c69d5024ea78e02fe4d9c1cffe68f04fc9b3e5440d00578b106a214f0e7289512d0754078f45986634d471f55e8be5863cab2dbcfeca8cc65dc10d877582067f3daa797552d236266ec5326d66093fd87ba8ed9d8936b76c41c337b163168f05d3999d25f39974eafb4a001e833ed235b8e144064d3db11b3b5194bf65adf1b10d92b86778c869817e84096ad13116d7420d7f9e94d688b62228b964e9d5d41a62a2d4a694febea21f35426ce6c85d133e1c8f5ba3d373a82200a9e23cc18a540696ca13402750b0d4da10cf39333a9919270c597e8d22bb4bb4b5eb71e80297015410053f3b1e5eb22207bca39634be4d1f3b1a557248d34d605c74ce9a4f49a238d0aa25171a31ea4a452a384e8161aed1580809e88187747d9a6dd48297d8fe2e8629a63b669948e6e0efa9ef4c3b63efdb7712779c3a8dcb8b30c035c6137bb40d9bdf7de7b712cdb3bcbae7681306efbcd46a3edde7befbddbb6dd7befbdf7de7befbddbddee766dbbf772f6de7befe5fe69e746dc27dd726c1f6512e3f0cc1bb6b99fe96874bb8ec330dfd1b66ddb369a9804ed2fdc8db24d236131f6d0b0d0e8c59f468d348dbb58660ca374e3b8519659b882abd1a8d3b891bce67ddab9d32d87fcc82b22621827efbda37bbbcd66dd66b32dbb34ec17c34e3a0d6b0cc32446b4b15cd876e5eb8663bb1c65d2c33a6ea3925e01c3b0edde77f75eec1ebb86611877b7f7f6a6dc68f41b333cbf617a52878d44acf6cc6a1c877dc3360cdb308cc3b27fdcb3eddce862d7d331dab02e85482adbb4c865a57b2fc65dfec2d58df7e47ebbdbe5582e6f7a77748db4e5d8de2cdbb66923ce04ae324f0770b582dd7befbd54d22b6cdb866ddc766ebb431887cd48b348a78976f195336cbbdbb6dd6ddb7e81b06cc3300c1bdd7fdcefe8725e0b0d8bd9a6dd8f2ebe4097cb360ee3380ee338ee198771188661d868c46d18b6ddde7befbdf7de7befbdf75eed8e7ebff5b079f1878898475ef4b978dbae806d77dbb02b92e2f55c964224dbb073d81dddcbcd6b983ee60c131133cb33fdc53019af0e23c418e36c494aa29b3f44394bdd30f6e4a82794c1934fd2e4faec7479bab4ef793a7c569234524a29a594524a29a594524a29a59452ce39012135dae41adbc4a2e4a5158f3ff58a0a928fa41079bc4aaf901e3fa357bac7d3d0a157b895edf13cf48af6f81b3fc407d12bf4f10d68153e36e594f326534a390486ef2936b9b5cff0750d67dfb0760e6f6fdca2c3a3937077114c7a0a2cf2119ce22478e42a30c95760152fe1152f3161163fede040cb8e9886dcf14646b6f8c905072637e206c02725111f7feae9f59d045cb9a8b5de44a0c6311d76e3982eb1cdb01bc7d438a6c36e1cd3ffa1b2c9fb2291e95fe3dcfb5019c3cf4e386617a6d32dc7e9f2f3f413cbe9f23b3486bd85099631328d523fe38d544d00a2345c592195a8c22162369d309b4fd886bc09b70e0bdcad9212ee15b87d7e8200050d8d701375116ecda5e58b849118cb182c671aa773a8e0d338b9be7fdce890ebe1dbdadf6b4fd6daf7a78685ee8b3e9fa5828d30877d479f1804ae208bf56c491ea592254d8a22b4e4fa8c46ab6c9144a92f6af1c8f5cae47a69e0c9772aa2e32e23512abd226b83ab28638c9d65ddfdccc6ecfa6054a8334231872975b5ebfb46a2fe52c9f6cd4daef6ab33b9beeaf4332c9bf55a56c4ccdb778cf2f6ceb24cc65e43f61ab2345a750d354abd6d1bdb47d83ec2f611b68fb0a750d54740947afb1936b399c59e1dcbacb55ac3d88ee91eb57f68ffb06baf329b966d39b26fdddddddddddddddd9da38f6916d3ac519b1f323c3a9a46d5c7ed72e647aea7365bf6c69f466d7e1073ccb7fc0d5c4196f9a64d33197ba02c8219c7c1b4728c0c9b8099c9d9489bd970a0e411ad660dc3ac9d9b52721aa63b80a98ac5cb6be2ee145fe7f07c06658ed16619777bfab6fae6f4bde95bd3b7a6bb27dd2ea5bc2677c43cc2def5b727dbde9e6591c7bc22621e791ff6ec75cb21bf7945c41fd44686c70b4e8c99ee5aefbbcf3c694fb65f9a94346d69689aa6691aa59e4201854b4811aaa84d7f86fcc857ca529ec374075005a11128905c4f9b90eb14723d3cb921048f1f3f71088d4e0bc1d8f8ccc054e64273b1e140c9738624d3f567de70c7947ec4136358ab29fddb753c7decb9369628e77d56caaeeb22121214c4064f40f1c428c6592a8d74256dca208327dfedc948745308a6285c411bc41a98420f55e83f1a39a856777dd79c1c21b4fe27d37a094056058a404fe4fa08c0d57c3d0468fd03e08ac6eb1d0059550b5a9842ae6f000320ab72810b50c8f541c0957dfd0220ab7ac10b4dc8f50a80abfb3a34a4845cff035c61afc70159358a2880e4fa1b70c5bdde0664553290a1895ccf035ca9787d86ac6a0691d70f00b2ea902105c8f53ac0558ad7d780ac9a869a0656aea7015723afcf01b2ec073e55ae9f01572b5e7f0364d9107c02c8f52a70c5e22525d7a720cbbe900000b91e05b22c093e1b723d0cc8b226802914897a0c5916091970657a3dea04ae4c5e7fb86a610259368605f525906559f0ad605917c0144a822c0b83135cb9c0a5d7777045b24196a59941fd852c3b03ab44552242965d02a6d0d76b7075bdd6783d84ab1f5eeb6bd7ae5d6b8d35f223c97f9d498770db0ee1687408bbee10fe8770b53a842cd621d44187433880011c420210e01016a0008770871d0e61ce87d0000638c0213c000f8790071b87d0c621eca1071f6ee04000cb21f4e110de3884380e21020e21cb331ab758fbc5f618bee7b88b60152bf0c859e015a69be0d35b6093bbc02d8eb18b1f708db3e0cf2e91e91180e3860f3dd8e0e10006c83b14800003d081b57a37da485d732a504e4e0e50077531cbc71a5a24517a766ca6514a297d0c1f578e89a3d4c49df32851a32876e55c39d7855d17765dd7458b68cb2b2ab5684b8281b628050255d7108d82762153fa049d026d42bc2ddaa2ad1513467b7ae512d22814952a2965449192e87cb4aad248941bc0157c5c02b22a4e4ca1446380abed25a751e707e99546aa6e4bc589ac4bd342d342692ace3524adf75d7ad7501d7ace9573e5781f95d17ec036b3e0cf2a91e911803f3b834c8f037f9626d3dfc09f8541a6f7017fd60599be07fc5916647a1bf8b331999e07fc592432fd01b001f0674990e933feec4ba6df017f360499be00f8b31fc8f404c05f4d43a61f00feea904caf03feaa19323d0b7f950c997e85bf1a45a63ffeea50a6eff057bdf0552e64fa0d7f550b5f0dca15c8079bfb1e1e22e66f0a7dd30b99fe047f330c991e06fee618327d0df89b66c8f436e06fa221d3a3f0473d90e951f0474390e963e08f1e91e901803f5a824c2f037f14894c9fc21f5541a60f00fe680b327d0afe280c32bd0afe681299fe06fc511a647a1cf04797c8f402c01fc541a69f813faa834c9f03fe280f32fd0afee80f32bd0a7f34081f2542a6aff8a347c854f5995535fe4d7a04c8b23e7c34a10357930890656d6c8c90035734820059f6c78f21e0c095f603c8b2373740c8d4b22c0e8e0ff2d50164d99c9c1d648ae100b2ac8e4e0e7cc015b70464d956eb063570a58206906577766ad003ae4492802cdbd333031ab84a0103c8b23e3e32c8f47406ae464e4f5b0059f627a6d0d35319b85aa102c8b24190802c0b5402c8b241474096158a812b16a7a73ce0ca747a4a4f5fe0cae441580094821c74022112647a1a02c8b242a80720cb0ea101b22c514c310364d9a298e202572d4e5d942e44044314044469c8456780ab1aa79f5d180364d921318512c115098bb6620af502645d3931857e6e01b2624d4ca19f65c8b415450603fe4b189f04977e61921a38fbc425a781a7e7bd7871720203460d35d860030a858212230600002043462a158000a4a4a8a8dc80830066e490c3df02bbb889c94d2b308b8fe0154f81472e82535c05163987551cc3dc2fc66ef1ad81fb34b0f6db225513a8b725533a84fed278dea5f1f1e2850f9b93139b1f3060fcb8a9a1861b1c1b6cc0c941a172745050745a3162b4760000809d1e19327a7c52299f9f0004e027484a0a90ca558032bd0dba4108072102189a717a4b94c36f08c24148009f3194c38932bd2d5a519d9eb668d4387dac5929529dc65b359ed335f047733d0dacc22b3807fc91e4fa195800f833c9f538e00fe7fa1bf00723d7abe00f25d7a7e02f95eb0380bf1b727d0a7f39e47a191800f81b40ae8f813f48845c8f823f78845c8fc21f4442aeb7017f3009b9be06fcc125e47a18f8834ec8f527f88351c8f52ff007a990eb3dfcc12be45a2709d660aabe843b98aac77804a6ea8f4b3055ef029f60aabe053e4cd59be01398aa3f61144cd59bb00c98aa67815560aa7e059e0153f52398064cd5a7c03ac054bd08de01a6ea55601e6ec0543d867f80a9201a0053f5133f00a6ea4b700460aa4212125dbf371c327320db67508b5106a8e3524947ea22a9d3283a04d471a9a4234d1988420fe70ba0aacf0051e85fbc309952a4f8ba287f534635851a859e4697536afdca6914bda97e8879d557ef1212c347f7d9a25c0457dca4899944ad9224236f51986e51976b9e5475f53edb75a5d27fe5984027a3326cc2043339830da1f62924e90fca83fb9746a1e791e957789fa557e1f92c0151e851a8bf54fad181fd4147841212327d910f98a23406c181cbd0901395847441f43248c28f500a42c7834ca9e6c51a5216ba915f1b0e942c72ba4389686be29129d435937e0a994370859d7e86a173d037805d135976483751d443681f646ae96f4a3024a764d844095ef2bc766302981208c9271936518216f6897db62a4c5186197b36d269734e20b44f4dca39e568db11b38a576cfac5a6f97bda33277d0d80d8691510de0628d18c9098a04c616810fa15ffae6f20b6dc6fe01ebb47e5d2af5bf2620f3864051e82bb181c22e9efc59db66a3db7b1cc395f7598585059e4224ff122a9b26d874456ac32d70fc9f41a9e2abc4f8813119843b9110da24a253432c4542e0e8f72e430c7e18dc31a87330e631cbe1cb61cae1cbe7e288727a7c2bb119966a8844ada2aab9a640acd800000000800f316000030100884c321a12c8aa3604df50114800e86a24a52421bcb83799043210501318800000000001100001080992a0028721f0ddad340dd8b034da1b44fc7a2f025ead0514fd0367790f870e29fa4ccc25a844ffea03653af16381adea9dbf2e2fc5d15111db1f1d58bcd7acb75f5c209f14f8aba590c726a7176265cf5ce8d2d3be26f8f93eb38dcb33b68ec6c9bdf634f06b2a3ba7ee10f80c6e94d59977438afb8a7084c477effa080cb68ba65212ea843bd822e839b6fa51cce71e27cea030fe701c5413386f364725c1227bf0a47d3e328218ebe114edfe2901d4ef51c9c03180fc7f588c32ac57b6300a0d52e5ab98116a568b8062d1cd1f2135ac8436b37f02529ffc19fccb0605faa3029edb64c2f85d5c311fe43d4b3c4e869c32a2d4125d5e6dd26b3c92ad3b406e9c1a27c913c3ae4f0186f9084bd71d44d0a952c74e3086f20017b43712520f1c2926c485f1d0e0decc3d43383353ec749e25b18e3b0094eb0a0ee2254ffc3c56d6491a726cfe5ecffa4b46fdad84497761e005f70fc6028f305b2cec4bf25f7e69bf8e0e0ce17d919907a0e350be7be06566487889a7c41209c0100c1e3aa300a0bc8da8a0fafc29b1197f317ad033b4ae6899cf65c0a2064771fe2f1c4cc1674538bbea595a18e42361b963f2296131c51b029589b044f9a6fe6ced4b2e1c1b4dca086dd40bd3779155227450c7719b50f471dc77c8e92a4c935a1be98bb7935bb41866b91c0b9db411b8599b6f669b9071a997139d64db8b30b1436ce702c2def75819029c74fc1b68089425f8d294273b07604ca70d484ad18fa2c125152f340211bfc44baa015141a108170d76770bccf07e8c3037805fc9f39f6702bf82c6d65e9a8da9767c61779ec855ee7251af1b27e97cdf9aca7ef94a0898520006b25860000e4df699f3b214ecd075610cc0a0f366289072b66653439847ca2b5c2f999acc0f0d3bae5245b15715570328f31c87a87365b4fb9825d98e1d674a432310c500b5ad58dc65ac0b7ea26fa9e852d513dfd1f9e1963881c105070226254ef22c6a3011e74c4f2921cecbe8a4d5019045d35b0a0a1561dd6e329b0bccf13100e9b078c01c79905b054884c28de359bbf0e0f386462f301f66d1e2604f9a1077a1089496b40b0fff244e7d302179c10e0bbca02cafe4e5900af5dc00bf0a0907015298d33250d31b27868a981d280466e7ac1f78dba108c56b8157435b8d6e3a94c7e3c55992ad7508471c141e3296a49265511862a9fca8c218dd5a6ee7ccadf9efb657b081cd4a1079c87930275923134d09c8090e3ff195405781fc467bf5be883ebde4a0e6dc5505124b23c0439ba18d171f43a4345b439f0b9bcc1641aea2e49d1268696a6e4b62edbe00ec4728f86cf515f5681cb2e368973c04ed7025d29ed072cbd0b1bd302e5422a34844ff509f7db4ef6b370d835814c28e5b24f8582c119a52ee126d7e6577a73c9ade53538d361a76913fa5fa3e4daf9e2423dfe60ec7d6f14f67d3520c0343989fb12bb48eceb154a6fed0f1850ada3fea15378f469122ad399d72c970e81ccb8484e19fc042aa0b9aca72ea1c9c7d1efb260fbef5c4e493483c3094bab1203fab645811b0192c42f9c93e8cdca289f0276f98c7516d5b63bdcd88e66f8977e15facdd023bc3f8db6cd37fa44a05b90949bd66b349c3ecfbf881187a62cc35f5326b1d69704ef07938c08369c3cb0ff0159c0f5ef2400f6cfe62c637722d0d411f5c6586e350e601cc1a14e9d43d14af8dff92421bfb05a9a3dc28564f78b4f128922b1fcd1df52724082d3a58404732065d2fb25adc1e6038363024cb8b731580d8ed0931e25e2fd589d0582fd97634f1992eabfc9ec6dda4a71373cc4581c5f91294b4708d8c43c0dd1b7e091117b89ec6c251a0a49bf8b5fc59a48fc246aa1d0b733145360873c84104a140ab4802de7ce9fdddf62d644c0e183c207e502e501e706e183c007e780e701e707e382e106e7800337037ed4deab0d02681041f473ffdd63d3b1ac41f26043e1816dbcb06c339b6cee22211bc3e5d6ac33dfd6f6316bf14add8673abea65d3fa3c870fb7200d58f336f414a68a0299cced20abfe208b7226b676f1b67600eb76224bfd205b75268b7661bb7631fb72215b52457e574bbcc46018edb0adf4726b8ff4ec2f64ed18b3f6502bf38fcc267068229294e254a989fd08096b8f4b81e1d3400233e44152057df0f3e343dcdca3e7b583de3cb34302f28e7415e4b3665b8b3eb40bd4573367456103c5dccc0351f59dc657603684af60d0b5a689629fcfe74e2af5b1c2a327b780726035fd9f6da45a2997657e7f87da263c6cfece0d33e050e57683ad45edb0b18b59a00cd68eafe6088a1288ef45567e8f9aa561cefa2af5735a154f076ae98392b0978b8f98b98dc0d84509871b361a9c033d75be5244b480b0f9a0fa41d106698bc628faa1d3e1ea028e77822b59b1209f13bdbb7ebca25f0719249a863c93efd8377bba1782689d3102eb489820f0a2d702251d2a5019b044065dfdbd0aa217997dc0ed26c51c36bdf1b9d67932974ca4d19d0d2173e7b6de8541b106c62e71c0f60f830913e22509f5e9e73ae236d009ca2dee47fe64bcad3868b915a375bdd895b4038355aa9616218cd25807f3ed137a510367abebf8ae3d37e17f869ed90c8e501c9bd7e14a32e4d740f7a1693946e71a453dba239d213f75e2cd1245f33631e1528d6c662d1c20e312564c8e2b62e79791b6d7611b7419f339b89a6f1451160028e07b3a08c0e5562035947d0e6e9091830ae844083f65c65ff4abc6db30a1aa7efe037d278916b36af9052c98763b1d9316cc954a97225999d7853646dc217fe96102055a85281b08fcbf06b14d409132e42bf51ac5632df4b0dab9892bc24b2b862baae164977212f20fc96dee00558aafc40a6100439838cf0a6430edd0e2a061bb68f93c52c42e360477b45989bc91bc5bc220b904786ac90bcf105478de4616497a00fd46c220fbe46be868cecea38a3476eb34078f840217869a9dfe69b8b4de59f503d097f7d5397a8276f0a9a3fc02bf1b727679c271656e9c919d35f7f92420ba49f3ed154f3c37f35a90881d8e44646e00eb95d516d805335fcdfaa78ce3da65acea96e4f5e9c009dd10c641e8f8474bd69c2baf37f4cd8012b990d9f6fc27bc9e2e6d63b250a8ee8575f86d652a50c9d80e5c4d1b3869bdcd01552cab282d908a165e933f61acbc7aa4cf264ec245636e675d2b480a789d8226722474cb03169764c2555673ad1a134878be3d61de0b32c985c94595b1ced6653af2014f078b4dc40874e3ca42fed4e31f71c7d2d445dd8d496dd7868fd952ac7a7d50644b0ee5045a9f12399f7c92020dcef4cc3754d7bebd437ac5a51455a8694fef411fcb47c68af048015f37e2a0f11af702d5c0c3d16aba10b1abc87d63c4c2b4031063b7e09cf33ba382a3049420c64f2e03a22874cd98bd67d06bcdcc05d0327078cf0efeb729c4aaa2a6982f724a02c2891853389bf81d2d6fbba60eccea32c7cf98348ec8a4fd0d326bbe8d7bb17e0d9ca03e0ddd4a7fc6fed8be8cff3d3287b4508f30bcb74d2bee178774f19a12b69b2c0bef4641ce1af0ce28b865e194046823efb75c1d92d39ad2cc7ea74144bce6a75863e06f2cb088b61699bdf42b7e07b0f91a988ced0438ea70632cac468314b7f9f39d0e04ef7dd6b904e0db22d5cfed7832bfab527c4569af36877280110594a9d8a5b310847fe98a053bfa45ba4e73e3374739f497dd20d3cbaf67ad615ee64adec0e0cf9eefdbcb5c3bb015e99e3ccce638d13eb0b8dba61ca2782935465ec8800a2e113ce55b511e0127c5296dfd29b1420fae002af96c1d048c4ab815078b2ed1293a693898f40895e3c74b47b7a29b3aefec739972536e75622ab924a2b50a5b5345b85adcd3c400cbb9ca8505c47e5f4efc0ce3079fb713ff85d9ddaa7f9fc361255f732f55a2797e0880dbcb94573e08bdea5c45dd435ae1ad053e285e90ef85224945b89c592488a329f09b2e1d969565b8de218cea410c16e67c396eeabb74e48557183cbeb48df3550d8e2130b152f3f8e1dcc28c17ad74811cc59e0a16dc1f811754c72636b03c1b18fcc7cfd73fd90ca346fbcda7e630890b2959a650467c2cabd1949229e9b205a8773c1f2b88bacddca7630dc264c1ed842a68f175c3aba7c172495ca2c1ab021fc5c7c023b071f036b9f446a855b3add73c4d8830f72998ad49030a46b7efe2f1d67b38bc6e13c8fda0b9a36e2f93ff24b985e69878dcc8a9999b8a41cbc0953c6875754deb196810731b973e033a5ac86c8183ac4e52036e0591bac4cc5376823e294a58db71fc6c1f5ad3e2e29ababb61bd6094f73f7237ec7bf9cb883495e89185a253419c5a309955fdabb2b8f3d568c252916ce0a545e0bf0d8ed6f62ef46ad6add96b73e17ac9517d7e25fa7d3a4da15d48e79d5bc53614ecb03bd8ca71980cb84ddda42a6c875ecbf9248f0a1c40c790d2b7918af45ffe31e3a8c9aefa38358bdf83452c536416b782ad0d37667f6b406b58c9d20c695f446a25b7a8d33d614c69f732fde4c040c42730ae31e542732c1073445e8f9727ee7fe67c5e29a86746a056740d7b090c19755abfdb3869842ccf44bc1205a427b37bcb24a64ec16da6d6c454c0e2b5538a62bb4eb3cc2d7e17ed3022ef8fd88c02e3cc33928c5a074611cf5943bfeb1a2644d86821f829ce3b4f36e99c3ea1657c00a84208fb346d781f16fcd8baf5c80cc3d1fd3cff34551e9875b33ef7bef18dc73621361cce757cd13904325d6e35dfc92d61a82d45f8bf969c11d0293d3a5d73d2f01007e5e387e2d61d4078c1d3d1f25f03cd27372baa554afea878ab69d61e539ffba609caa5b63d0e34ea99ea4309a790865d1368162e055206e65f0575a376f80e095bebe41783526897664094fdb82fd796e34ae7705644853e4aece53623b475c7b7d097df034fc483714fe8ac6d8896a98169b766fb0b4ab2be59fea40afee9c5928e3e7f9a79c55050a0e4eadfd1542a3b31e9b8466f6f492a2b6191aa929cd8264d91fe9fab7aee041f1ae24b16f6e38b9bc42110b8fa8a84720fe14ee7ca4a81dc75bd5f9d8bddbb6f0e5b617c64b1829682785fa14e351a09c77e99a42de24feeb494672be26c60afe174f7e8583897926db95aef23512b6d43dd6f941942ab078dfde89fd004d4c6c804371a9cfcd5f59423c71934eeb73fda2abb8b6654709f9cece3e00613775b9053247407045cff96516511e35f9892a1c1c059901ec2be900d352101c8b9740de76e321262b15237c4c1584f5e66c70031272e1b1508c77bc411a083a5b216224cede88c36e30c1488379d2bba9ed211dad06be0eb29aea2c2a945b64dfecb1c9cb21a382ffa5454fb3dd40c85d73d5c3d2ce873f7ef4235a1375c58f3f680adfe17397b6ee2203925cb69fbb79b75111c22eb789503623b2780f35ee5843d703844e446e3680bd833604b7da6cec13f3ecbcebfe3de9d44ac5a2c39e56189c2c90f22846042a2c7e29da409435869db8f21bdfcfe8c43f4b711ef170286b0f33d4de17a654e5e48c01e88a041810ca29f819ef85385da8701b7c0e24a6d3b068364f18c8e65d447e9e3501314c9e87b6e4c563ace26bc5f7d36d1a21cadf5cbab0ddecdd7b2ee288921d755f75dd6a97d30219a41c9da23dcbaa699700d941d35f233ac29c00bcd7478da551c3604e78990daffdb66e5097bb2fc3deb91eae6dd6e8c36342c15b54f42eb6be82f27ae09c766c9c251f957ee052009506bbc3dab880d845b358e5fce4a7b5c97a336c6350403e963c20eae0ba2c6e6d85d2d1098d78e2e98b02227e1b8ac4a7800382aa86118640425004445f7587f84614973aca6e9ffb63112909157b917ff3d9349660b73caddc85bf1a598da1654fa053a76d6f8fff0d7bd5b2cac0f622a1971ffa55a27d52a61a5633f75bb7d0e761b3cf0bf5ec8b57750406d0d6260c1b2a81050faff19b952309e38f1bfa20cbc7972c85c2d05b820cdc3c641c571a00f5cbe640f17f7f7fcefabc6cee014f0cd83b74d8aa069c6a7e2c09f017229dad348f91e7d60e18b017a2a123950d6b2e517ef633b5033f67ffc71bf7bb0742846c4f84fc009f44d5d1697595e46afa124a02ebed17042217e0e21cb3c92003d172d5154f23eaf4e99c212894aae70693bfded17a641e37b6f6010602f4712107dee533349e084c6242edeed19d46111045e2107c85a105011b93e6a04879ed2d8f2bdb3dca5f1ba72ae4110b8fc384fe406caafc17bb129a3e4f2b225a632360854a712bde9e4f879c7d72fd2100d01a2d0ed48e56f43cf2b194e02e542c6aabd803104ea33a016ef2443201e9a791f9d02536646a3b44cb0d60ec05969aad608632bb1b42028e02d741a21d561e9757f8352fb039a181a826491505750e0bff8141a5aca37006d01bc0d9768a2019aaea423953665ad0ce40627d7ad54c314ca287a3546577c37c06afc1420275a1dde4d345b36230e010d4c8af44e4590016d1b6c9716fa4af5a0c3220c24443220f77123820d78ac96309b52f24f3e54abc22e396cd6131075b4329eecf22a82c898153f8ca98026cc403801085e577f9f27ae1ca400b34d9b9aa82f884f4038610b5ddf87246e0f6462f81ca9334c65450ac90f1fc43e0bd60d3169482dd8b2d03c57f912502686f211c0e3ae0a44c196ff0526c5f395692b46097409c08f92cbb08a54932660efcae33b9069921bb3434dafb1a940a5b1413c5bc843763609905beba3823ffff06e7fc60e062b638e83356106b062b2254009b96c328d9e4eb5b3b75b812a9dad8c8884f7ff64c8ece3d183ac1406ea7da4521758da40812e6fcf683ade864a23120b2e0143799861c4c83a64abbaf35b58d4df6f489207d52d580ff0f50e4500c1e3ee20991cc772d60cdc05b39547ca05aa85b1398dc65783fda6ea3571883ab11860f86535d26bfd548f5e1b7965cfb2b84bef31b33b7d27c1baf315a499f0bb141827102fd3af03853f69158024a173976bfe972a6e32c0ee3dd68a0b3c6c5762ac8aea1cdd295cb65825343b30dd5554caeac19d1b828da4062dc0b9db588049c78085a3ec72f111d953d8012a9e49eadd5e26c7a264f538acefc092d1eac22c32c29b3accb94670504aec8d746033a70c58585a365d899804257c5b4c73b38c3644e09b3540b3bddd599fef678a42e451314bae4f9c15bb22cfd35f7292c320efc9ebc1df34e18361203090fe8392e5839bc960ced9cdf6f9089c90cd338862e51e2caaa1612fcfb57e7ddcc24e4562f48355cecd3fbef81d454b4b9932853cf45947427fd968e4b38547bd6497c73fa3518c7c90c9d72427ca97ae7549cb9c48e4c1901f4ca21a278558abc1d21392e57dfd091311c6be345cf4dd3012b58cbdd9060188f515275e2ed5a279dd5ece12c987d9f7f3376ae2e685e100da49852761ee7b8297513df11b0f8dbc7bf210f3ff4b9f54b9658e652400dfbcd852554106de520aa64628d913d6a401f52d8828f60db8982bc7926f74449f77fa2fae6fa30322d710704ec92f16b21718ca17b6f4647eaca38683ab4c17d4aec18c4cd43b1b6f93938e055bdcd59ee74d259fb0c87d83c913c9ad35f195ab396a4f48e1b2a1a46a15e727ac3f4490bd9747449ee10be6aa9bca5bb0fa96791347bbc82c4cc3c59e008cf47ad4f133aec197dcea48e13a413804cf5d63908d5d1e4b6e93470e049a64e007f9cf53bcc9484afe1f9ce53cacfba88ae41413bb3201944ac2ba5684531996a9c68f284faa8a702a7386c60272a00b5b2894137723e79046023e3b63df299ec6de655a309ccb0966d7f59c2841e6d635959f2b299c3b6f102e14837c58fe1870baa01c353b9b087b6ee7f61c37d2257096c0ca327d8e0a8b0a629763c5cba9424789e3b12cbb65d2a6bac2e4f48067163b052a3296d54ed17c76a7797382df5d3e7854b1058c537e51b159a9cd7198f31ff036ae872d77f60749d70bb9ce2f5d2af818a4b7805c0d2a165bab9c500ee61201796daf84a504e97c00a57e43d290a3b3331548fb833eab6d44f5c8176e84d57fcb5a55740ad18590759386ab0e043194e515e479fb2bdfe60cf3984e5d337776c049d513e52f7399d745663c140a1e2053631d92500e329ebe3bfa1d0fbde5a16a3c40661b444310beb2636d444c59416852319bd22dfd1c3d8ee7f844f5815421a3ab0f5af0023abab31d0478520f60595f82312292286e3c9ccab8c20d37e859eabd08f484fc29d08537ada22828967d89f158b84848963dab0e7e9d1b2bcbdea58521b4cde55bf12a41c8a69ef5f735573a4119ef140e612a6145a92e5fa50b6cc64772955ad91b1123c1e5bbc45dfc0552bd493a8b1097b45b1a1863342114ca0660031c2b77b538428823af04d2cd6e648699b739dcc03b9544921bae6bd443cc9f2c1b1211c5284ef82f30905ed5fb0b66d9cb15b6e207f499712319f4f9dae896b3ba28dee605b02e9997837601b951e2eec6d2e9df430d9efe17c6e6a141a784a9fe000a813622113019a69764f750a201ffcc65decd4fbbcba30de2cf1adf975de59edd3efb808da61645a541928d2f8e8a40e91eaaf8da748e0cfed1b7586b366316734e045e828caf415cc75926654406549e3154723b9c82f9b866671294d8dc010d1369a90a0c8c8d692ef2278ccb0ff184ca9f938ff4e1887325399c6b3bf476f8768af79ac8214967052fb80a4918fcf5b3d1acddb480716932a02085175244ae44be9a9e7f05a064f5c0aa47925b88448dba745dae6145529acc3184ee718919fe87f15ae9c306f5b411484d08e212dd83511fbf82de3b6c435545a12c0a2e8cc1d64962f1b5340ccc428f7ef55027285f19ddf4607c1e611f7049d68168ac2b3e97360127d1f9cefe46a600760a620dc29a187ed0c60d5ec7d70425fac80cad693387174ac90e98429ea23f9a4aebfb22d6a2908f460543cd2b9a5a01ddf615c217d575dade221c3d2b8caf44fdf5257ba45d2f966936ad09b2905f0122a4d423dfd2f8f7ef6c2ef3dc59c7fe066cb168af032a68fe15d244a2bc2d90107fe1e4f5d3eb9ca4f1a54c6494dabd175dc01ee02379aff0433d77342066524557c1f96db264098d854f6889f87746bf24ea089ae5233a0cc5701965d6c05fb4668adab7d2649bcf848a4fe6e4b823ead6fee901fa636fea609ccd7c69b4c849ba16496744e375c6dfd4da90430dbaed08585eca71f35adda03908537616022cae08e08cb4e971f262ef45e6779f1d00d06d468f5725092ba91961a72a7164df79b0e83c46e699f50b10b17c191e8ada62b57d51720001bcecfaad5cad32138f39bd5d467d6673238e49d125df772cdc5f53a8e43c4be876870343f439bdf88e818bfb2269b1f2cf9e699285d7121bc83eddd7bfb2f55d1a256b8b5be22ccb4125c511cc9107a754ecb44068622329c81f339f522d87fe59dcdc1cc3c08c329c1105a369672793c76af856d4f93baaedd8abc7778adfa757590c8d2a5baf977a4a598e7f14b2d12bd1a959210092230d3a6d6191d9de0575ed0718475a3cf2c0c4c2b14f7daa9e07363a2f3357058c647a0fad6a3be7e4c204423c01132b6c021d7999b0c4f838ba8f547c74a81393a7136e3a813b034677a53bd480a0ae2b31381540eb9032a27b2b4b36429752dd492bdeb0aed01ea06d7b4e4c7c0afc352d46f7d6cfe87dd774cdc16ea53d1cbce00a5f38fc732864f7cf3408a87e1ab344213640ad855274680c50f6b627265bcb0ade2537996bcd764a402c330a2d60c3d5ace510930e7c0fc9f304ce23cd30f836de0b6d22bd18c43ce258598ec3fc3909501da700bf928e17aee59e13bd071e5151c8a887a03fd653b9985cbf24362cb03809ebf44fe79bedc948c9fc3b806550bc2ea405553cdee00b9e52fbd1a42b06a80bc66dd70642ab921e1466a28d649d156e7588c9b9fa8d80f13f778d2350ec08ff4db1784189709152956215d33e3101bfd44600244c1918a190d1d722f580729a0b8330075e18349b3d89ff62dca9aba9b3b54a376480c7aa8f7eb1d8aaea0d2ea6b522582bef87e842dcf72800de8a4a64677fa04ecdccef1ba08a1b220f60fa78a2395eb308559892f89ee07b3eba5a74662b369c50485247a6dce1515c9da9e4eda16ee9ccfa7ddd6347d5a6e7c992bcfb459953281ff5910283508305db385937e01d08cae6f77c04743bf929f1add2a8eda955d59bb6ac448456ae6585480a3595a0b34b232f4892dd911aafa485e775562b68e26c45a03bf2c7c9d4c61ee41afe5cd0759df569e36079f6850ad9defa6eb7290af3e43546acc1ee8a0c0de8c996563d94a098b20467cf4cda036d47989dd33a5af480c9b740fbbf7d4863c2739af526c5b7708886e3d9cc84dc27c3a976bc41ac0a685a92c8533075e512985ac6a38a06f1fde936cf9a0e470e251bfba3ce24d75435f24bb0a9947412d9b81366ae7832e4fa0080b6f480b3b30fb90a2d65e900800250331c7e731f1585811d3e2b5e212e67a643be361d33c2d630e03113b1dbd67b49aae36ea7a5233463eec7deec49be13f32b415c3005deb689492b37f8deb7df7ca4376a685499db1ff4b10795f99038de9d159b906a70bb8c9d6f23b438916efcb409e3538c14848df8429476c63c6953cb42b03b54b1ef2083c1cb1bf1c4a3bb33805519bf66e2ee3e5d76148b856232d375f67ba1fa84622c0627914ce629aa7cd6dd08320d9f77d87d4691e37d2b6f4b8b393dce36592b8f5f5d2b763c7828f337608d0b20802ce956686aa1d10464026d5f334762c1ddd1408713bbdbf4d4d44cd06696fc1802be4343fa7b9a9d4952f90ab639f9b9668064d4a75e9a7dde536b24e9977e30d1f4bad4bbe6861a7950f51a14b3fb004277ecd7c819382a7bf6fc4443107012124da6004eac69165ba1573a563e6eb73443d8a421b06d76464a7e37a06befa8d9a238c2b36449324ed83eacd47dd1d0e90b8ea3d8efdcae903001737f256f12819d5a3d9893fb48f9fc3f8905cd6c49b81ab102edc5b86ddf063312e3643e5d48f2d763015060381b88038dc6e8256ae4a2971dc2c8f9c61c44bffed08b79c2b83a032623fa695ba7e53858b9ea970f90183c24063cd4a332b76de957d457b1c73511f0e19ec86d2e90d14d3c06ce70b94d8c2377f6aa7f67d1db54a933a17a339207d4fb42f8826db9cfc703443a086fb9896856faab35397908a6a27bbccc93ecc9b896c2967134f1c6c8324ac807b28cd7d6c0f1dfe81ec3cd85944633bac8a6e50bc6d1368f2bb696de416d3b28f80a1438e0f70dec91d68e4e5d78007981e64485f7357395044536da443378484fdc68b39541d889ea4ac1ca10f6c41383eb513ece7c41a9fce2fb4562286e10904eba5a221cf30caaf75499eb624cdc9872bd7339432a25005a9f2c5ec6ecb1b0017ba605f27e113f3b7099cd7b3e515f3373f3d9f8bc889079ad8aa9108572703c33e68ca64281031e19c355d9fda15f36608f2e808467f4c949cbaa94f649e3bd924b44075335553fa4bab5234eb9483f123d6612e8f54b755680eb108f4a117a94fa62aadd25f5eea9ad65be36ab9a0e382b908d3a128e2b8ac66b1628e9ea0fc37c1ed5f20d05077877ca805f596b4281eb93f8ff5cbbbd6ca42ed4a181005dd4137e467c8fdace0d05372176dda2456a38909c5d668cd34e3a1d3cecb48ba257761e0196b4e0f7fe68d936b5bfb3c2da1ec6db7a5b078d5247a6d9ae5cdba2425666d824a8a6207f54e2eec6404e82a35025631babe60b611a49a2b480f74db76309bf18765c7ca2b195ca848f0b695005a84baf7b333ab93ede4f5ca1b54273075a34b21eb03052410867b7f9e5c0dc58f137097a7f78eef4feb3deb30ed3d33da038896ecc256d9db98bf9e9bd92e6e42d2fffa22541afdc2912b3d37a44869da23039a1ca34c4a776e0f287a040ac047f2f8e0da635a58cde9f6452f159f9712f9ed3f072fd05fe5842c041521eb9d0bedd93b5f2b0a614d0d37d31c64dbb8c324578b479e1dc2d980d2573275264ba53529fccc4982cd2843d18669e0af4fe840937b07d631ae9e12910c15ad07a6809041461ac3b8eec6aea6a97c2b8240b33c3c260885bae5c23799ac40f4c218b3de6e2f391c5cdb28d2670596433db4cd7fb77e4c4aba3fd921ed7b7892d719006b509be15a0941d35832c1863deb341168b202bbe3cdf2fd2fbf6211ff8fc5bcd57e03465d9090ce150fcb7bdd681aa58f701626376f3f953dd96099727dea0c1645e5b769a3621a76c25608913a8d4377916472764a5734bb974ec8fe9f427832f3c22ede077736ef7cea432ae555db1470c0c63ea99fa61609f9ee6dd6df598841e7b7ee6759ada37ebcd3db06c61976b3ad33ce7992c756ea537bd88a7061b96400c1df8c78a077356793128675bfb4da91ade2cb165088ae5dd8cb0b583940705e82f3b814117fde2f9d6bd8bca540c8d19c103c320be47b61cb2e7f469069429a7868f0170088cb02ba5a705baa48da41c59ea365ae553c953a78ac3ef90711803d3c07b86e3a2ad1c3e5d5e5ff804f5c309b006053c3cde1aa0109506da8ad89fece8bbb6ac7541c527be3c4063b93b02cc28f5cb5011c310d593aed630aee8945fec76a5355b54eb17a30787d7593e3c5708257d5c9a28ae208cbc798679fa3b898a706599a9272539537d4ec2956d07f14e2875c89e148deb76aae7bf996c4a7ca36fc2a15f75db7b5eb824a4852474e82800fe718a603e589d52ecbd26a4de2786413ce9ba5a736f7d98930cab5c070fe8c0dd1a768735c676e5d722bd6dd8e0eca5e1ca34c757a0139b500cbbb6bede7eefbf9521c52409659ada751bf9b45cb951323d82dbbf7022cb84a5f285769120cf93dc02463891ca08b950781d9b8a5c3a9064f3fd16b2f89abc119a830b410fe5c0511196fad1ad9ba58b794a098e2201ac5ac722580beea4d11136d61a20bde97af520547e1c1bc5ed0fdfa28f55f08615ebe3853275d0f2fca884aecb4c9b7429b4135ef7fefdb4f7a4d8cd53e408f7fffc0bc7c5d7c5f0a023b26eff781c6cc9e74228e68a51f31834135c735a2cd8486719130b52e4e01427d581aaad5c9802501b3af208d489f2d8f17d09d3e1ca7c2e82f89fc685c20f030cce422c24fc125caee52a049c8d8b0e1d32a41eaa578a9e308da544bc0e357ac6d3e445c835c50c20ee82dd22188df7eacd38c2afe4ee96c54a132a6a2a9b9421837f4514c33694f1c71292fcf5545e2aa32ebe852d86b72f0fe9026683143240332a48cb647177def00d975848db7513192a7ae4a171fffc4ed83661e2590ab075b80775ecb82d04c891257f08743daf975f9b65572ee6a152c870730c8b4d37d79f1575adeabf43da52c233f33a3295189ce419d6bc014c379865e9394948e398a49f68b72e8140c61ceee3199d9325429c6a95bc61c5ee062dc57c77a58e1079c4ee984eb1079cc70386ad76f3462e503e3c24409896dd6c4e3edb0b3e74cf7261543aee5df39b23ec79f828417ef9f9982a40c954bfd4b1c94ed5968801f10671635c02f6f2a031f1d1cf42deb716ca2a986303dae9e637dae8a00d28a657f97cfcc6fd50902d1fb95f830e9674910034220458805c28b56b9d99724872709468ad2843fcb9b780d07510d581d16fcb544002c0f3a819b514a269ff1fd4bbe7ef18ee89ae6bb2002bd7b49885aa9d879631fd2670dbde8fae5abe9aa4fc40b359a4b0e6f7939adcd2179a917bdbe38fe5826426eb447544f8679a23a26831de3e908db35b02270392400bd63d4a1fa7f9ad8e7be6eb6389877a57bb2dedde6df43d1c533768dd22940b3bb2873d0b742167a7e4fcb2759e9ccf96571f9faf89e1da2802137b637d881d2c9ee6fdcff08ad3200054e9c3d7339dfae1026f6ee5be02bcc16bc19dd61132e12032d7222de9859b0a30a977d24fe692e37f57592f33959c96ded10a4c606f4049725f0f5a15c08f5a9280088f193726fa63f16cc329c639d460da3653e6f961829cb383afc1f12c3d9d4a3913a02388a9ad13bbd0235ad32980a1b89e35a6df9eb83efc0c88e1b2a259267687553ea34d5ccf9b586ec6d961ce370565c1a712801c08c80e902ecdca21204641a4d00a76823a798d385d1c37f1f0d46080d99f3b873c569364005ef45e5611ec8bda38b083e9c026879564e374b76549b8937ab6c9737169895aa91f44d66f763e9260222b17001581c1dd08fbbb026624411d8a87899d5f3fce715e90054b6416e09f884ad8702d3a7f2f26c57daa1aa7e193c91922f215362d2e917f087b15d8a99c14228248a954019f855ffb3028e127721fd22fc53cf1a1401dcc31a8e0fc884643160dc2951f1ac16a8db7060f22a3b969c4a636459852afc3ea56b96af0bcd07220ad4b8b94ef48044b2396e22a4fa2720030b1371ebd563aae6bdbd9f5b6d3e8629b147bc02e749c6b12329b75638df59f41a2bb546ccc4436f9724001009131b3845658692577e5d18ea9cb4d0e962719fc9ff28f01bd228f0477557a5772d4c1eca848953eebb49fe0cda8f125e38c47da718a39315263fb6f542df46f8d2acce14866da4c28859396181b55ff9f42699a11b16d876d6963c5284d10bd42ed36412a3781ca2718a2b9d12111038730153a4f5c6afde536b47685fdbf89b14519ea0d94ae6626bde30004b11f251d6fcac38c56cab894c3777969d699102ca1c0861dad48ad46852015b898b80a36c568a9f7982c552ad69d117ab31c84b375380b9ca430984086639e5f281e5d35c8a0f135600082c66cc6a6185305529d9ab9a615e9d1a25126d20a448e850d269f1e414f1b319d2a820e13750f4ecaf80b5861dd54b0729a22901051ed2321ab44d92d6a1c02f058e2b0643df9445e69f88b7a5575a0ad1b44cd908ffd25be53c719cd248b4faa81110f601474f759886bb32635362defef5d104f6287e40e05fd234935d95c7e3d7592285afa7bef5effe58151578797cb55fbd61bef7edd2f8ce309af49ed7e77bf72393a165c21cd957bc2d9f8c99e6b809e8ffe8311240e0f32263f5a43bcf36a277f759ac9a326462348ed0c723badb9bc6788863e50a5b286acdadfc9ca634dd236fbadf2b05f9ca62e48385563c22a7d4978f3d4d9c8063e06da57deea44077db15a5c7372a68ed5c931eb111f6c37f4fda3b1eb324c33bcd5d7462cb3d4661c6161c9d4370db7082cc293e2d057ac2513e68e574eed30cefbfbbe9aae905f6c1f1a67382c959bcbaa85612efdc7aaff4276c6dcc177a15db69ef30c70abf0cd121c7c8e680c8c628499268532ce9918826d052e85bc388cc207340ef089ea6f1b8ead1032e6e38d126b167d3425d825032c7a20a5bea563f4229f781714fae39d57d3f42474ece809f3e3c09adcb0725e0a389346324585369c16b9ca17284e638ef4e51341c97e961ec3991497ab418fb9c59a43ae29cc408dbf1a470d079934994b8342862e6841bb267a39e2ed2456d5c1579f1d832c0f8be43b17a64c7beb906bda06a0a50b63f8d3905e0e47313df1c7844413332218a852595e5ea93688c94e741c5670ae7d20441908bf67743d35dd2f9e8f59a4fe9d2d94b142c0a5ecd7ffea0c2fa16057ee0fb00c912de8f4113dc4a2511473d999cd4ea93f0b3e2117b930ba1f535530df64236df73292318c9eed8af424d32bea29a957518f64bd8a7b48ee51d423b99722bd927b15f736ea5db857aed0ae588f643a453d927b15e929b957718fa49e8a7b24e955dc2bb9a7a21ec7bd85f4a2cdbe6f6622f14603b9848b05c143f305328126bccd8fc676eaa70b230ca81e318f1c308c942acabfc569cd917077298189733482aeaa697849143413fbc11c57c3a3c6b6cfb747cdb27e4c910696b38e8efc01fd6e3218f1f0522241d60004cdfd2b3cd618b8d66f8c8a1fa1b500f3efedc0013ca11f0c485c46ebd61ea6e8a1300ba32438ba062e4e928fbee822c475e641687991bfe03103d34ee5203afbb0269b2af03051e252c111150661d90fe4b9e70cad331dbd26e3c2f85818887b8df0e265fa856103d87cc5efe35b053d7e300102d097b043d432d6e8958b33d49b4f5f4c955a8b2904aaf288638a349e9b36cc22b045d99487dde609c71b4b1464b1ff4371239245d9b62fe1fc486201d6dd2f94272e5dc072e70fe78aa51467ddfc61f04309856cf6ef107e406a31cbf6279c1f902cc076ff84f389250bb0effca179b13485acbb6f186e50a298cdf6e74c2562593615be869f91435c982167cdf3a4d5ec7583dd1c14e8200cf41ba4d5f54b941a1894f546dbc5219381a83b0ede3af05ba7abfe522c1cebc509cf71f2f11f840e47f6cb2c41103c737c249051bff083fe51cd57d48b3c7dfb9b848fe6c443543d6a8e7865fbb866082eb23b7a32882d785835d7955158cea627d5f7adfa6d1b67355e44460e63d93384a6a7fe1686ca43ed04bf01113c058ecf868a8aac6960d52f486482a3bf8247ad1adba962054b687ac2c9152271834a9bfeea739d7ce9adb111e860f72f88411ac1ba7b9050f5ae59d5c264cb5560c943d5c4a5fb8260b404cef27ede004934bd4991376fea3bfd8fe7219dbf30585b5051004826567ddf9d5e37fd951bda56ab384064b96d13524c18cd484d6f8dc23f57d5e3feee05c180fe6d8be9301ebc36cc2b87aa47d27efbb3f71df3a60f7e02d1640f913d963f5860ec4d0595f3077f07260a5cd35751f9c95df599f57655e171e0452bb0690d1d8ae8643668e832ff47940d41a4ea358fe95b66473f6691566a9bb4fca423be5362db6efac87e9b9a99553d4c5ae6c9571f4d1f587de137eee86d32e2fd2ca2193b09c8fad6c47d993472785eec5dea2914346cc66e619878229f12c5e23cc54575ee2f4c64b690d8679d50c4d3a91f09254c7ff731a106ff9091db7a3df0340e430ede77d33145368be091df5fe6ef583da00b665431332e9e4623ea4e3a76c2dff59712c1b5c06e5f9a98fbd037085da7c17e0d29db4488fabc9eff11ed5281888c0e793df353eb76c5c1afc3f471d89e9c9bfbecee6406475edbfbe3f7687544f4853e9ca26a4ce4f5c41f1f9dc22be87641e415a51f758b1b175e0bb0b87ae415eb40df1cc8eb89bf975f66345c0e9a1ca5fe9386bc82393c3313e4f5ccaf1dba56c28b458756c2c4a25d4fe10607027925f077ed1a8bbbb081405e5bec4d22fc784114619966445ecffdf169d1350c3086bc4aa6b3dca21ce1bfebfd3a842d51c08eb8d3327ea65715f2aa4a73ffe42fea373b02870fc7bbcd55073ff2da624dab43c86b017eca5576fb48ae62f37b9b4a735f25d3ffb090d73c7ef153bf9568425e232ead1a70b6b43ba8fb3293fee63ee4feb9f508ecc8eb2f9b9d1e253a7e0e484ee455491917bb445ecbc3774fbed0a5fd174e246aa8845b23cdfd636dfb218b359b05560f82415e79129cfc5a70ab5cb6082d2877f978d5ee8ebe56c66e6ff23dd8dc87204d03a0448b1e725a722d3824bb86469f43fa3af31193a79d88dd51ab28127b18931c7383df25269bd930c82907fca17182d9e5ed1fed34277044fccefbc4be59739f937a92d44dc0ee2f459ec6ab30a42153bb728edddb61ccfdb7e981e0ac4d9b55971915ebf3ec32b761cac237c402b67327eb5f7bb38eab1c21dfe3e4ae485f3e96db422c292d6e167f453bc16bcd340b6df9cb588435fe6ffc5776c70bed1bd0f4dcf0a17a66e98d4be6d79508f4be1d51c12be63f94b242d2e411392cb4e68b034f32383f3e5e01f6a106576b3738913ab195c560fac937c3599a4e848d6f8f5de55db75a621a5f517e845838762eb37f17d2da11bbe4e13540f636b11e0d82f49cccd715c0e3ddebebbc03be16a6b950cbbf8805cc55eb454aab45438dd674610745be3a8c1c4449a75f62f270b184ebbcb7562b6c332aca8f108bcef49fd9e9cd33b18089c6456b36aebccc9c9bb7b1753bc6c0f1edb7abbd2b5fd1b6f9a885df8c056c8d93755598e0c489f734523822296f7f10d4a5cd27197f05fc4b547dae2ce58af44f17ca661a75ed97b080057d9fd1432600bb12354197c4452f2f1df542c641dbd6c24e387efe2a6878efe315dd092d0bd3c4e8e55fc46276da4e0aa20d441279cd36429aed865a4bae9e9d22bb051b7a79d094b97a390a3de9e254f0bcc11b21ac90e98fb2400620ebcba96494a9ba4c45c9a9086123f13858fd2bd203290b963934e55fc22246e22c4d5252663f8ae6e0055a03566a23879a23d162b51d66da23a047269c042f36bdd104ae20f3b24df21eb9efcfbb8d6efcd1297390c51939bad5fbe010382837fd4242edb563000ea41e5dd7db4a3c9ef3d67fada4a51c302911f0dbeca99861247e30e38ba26b05768c4d90a3a42d8b57a56d91293c6a7521889bb14a5b8d16824fe33f7e42633b47b410e38e006e0c5d0514ff7dbc26fbe345659b07bdfc8b58b0d34ac57df2843aa9d7fac1aff0dc2b9f1e339e1298017d9086099cc7fe5fe1fb93121edfaaafe664c03e69b69758919deccfb820aee8f2a015d80b23664530f03a17b46e151725daab3510c389c15e3e1c9c766e7d44436ffc118b2e04a6872eb51d0c941d74f01eef1d6ca36f2007249cafea36022ef3a2958775387de3ca15aba175168a17bd828438c6978b58e0a1c116d42b22f11a185eac775d5e113a11316f3dc7d0b99af6a2f05a874a3989ee31185fed3d417cdc13613ef7b0a3491d7d62d727a5d7a6328891c5e308177590ca1f49fd988ad717c875aed358934659e685005b1f3460d98e50fc08af18d604f7f8a1d23581383354770521b05820aca0fb61536538e3ca6ef569b66b6b2fa903436db1a04cb1d97cd7196055e6e08d2c48827a56fba1f9a7d2d7d69e97fffebe3755f4d40da7e509af3d6cc8f6ff2a82ee85fb23cdd5699fc4c8b94dd0b26375a979bd5c4b67c729e434d30a42a0144f252ac081c0c5bc60abd8c05ac5be72a37844e3796dd3a4f08e2e45758a1a2eefb8a70a7945422ece8b91eddeeb5141ef63001c1e07089be79125236c122db3876755c06257bb545c561b15860661fc8ead7679d2d2f789fb91a065f761218a13c7dd050cb717c8a57fbdacc16d65e919eaea456c736212c43e6c4016b3785c0438657d29d05e48fd7ef86df7895fc4fa895fc4fa8b5fc4f2895dc4fac45d8e9d40cfa09f813e875ed572fb053a9fd845ac4ffc72ac9ff845ac4fec45acbff845ec04da0c7a13e8d3ee11a95712f62a8680ee46735e8e2ec1ff93d28396cfc867a5eda8308e38e4d2e4fe90f0896c101252ca6b5d4cc9eb0e3d2827f044d44b4f9b862a0198cc097737bab9a418275a96e914fdab6ccae51deb839865402259a56756521bdc2047268f719eecb5fe0ccc2169aad75a3a2d83258ae330a6ac0ab00329c634ab08746c26d901a899f524ca87e13e128f55baa58a28e97868688fb8b2597ca8e8af981e77534bdd71a9dbf1d6a722ac9c1671262dee6fd39879158b47c3385fe6f9af62149638f162d1814cf05cc7c92e953508c208b8f46d57b5a25b2a86255a4c1a304b61219b8bb8768bc5e605159f2d52171b5e3c1bc14c116228bfdbb94426f015492d9250ee558f205ef4889e925ca939d388636e01c589be38b4b9153e946975f28d1f5f9f69576de6d158f6d32cd82df619bc6ee074417dcf81524deec95adc579139c5099f20ce862b53d5a6611fa28661d6f12ec9cff3c0dd8e29926f51942a1043c791bae41c86729580628e1c187663c0b275c1d4089b98f528c90c071de03a41728ffa51af59e96fd3be5d971790db0780d59ee212afbb4832ed018a5069d5808fe49505b0b03a92ac45aefd175702add6eba27f753600c4da2cc2f2c1cfbffe6926176c2bd172780aea74b04923dc5a8db95105fc1f298423a3bb6fc278213ec17551756138c4eb01f0e9164d46c9d85e4b1f8203eba708b7ab16db649c5a6fe1365ef29461bb18ae5c67870b101a0bb4d59d34c06123ab09bb835f29b758af9ea1be139dc890b602019dcd945c30ed8e1582e522aaaa2f953f38751c7fdca0a1601d29e6165455a29b3c2c7d31bdeb9f5ee2f006943cd058490e3754d2c146943eb881d20768acf481c69572b871a50e36a2e4c30db852b01e5e4d60a421309104004e97dd0880a7b09f88fbe428a8ed3ab7b04314557464e647e13d396276f7e294196b18d3298de56badd9369d56abc7d95a43736f8c5c035ab2fe8858305a554d1b2991df178f490e223f2e36912c4d058eb835e2b2a422bb6611461505b893f7ed4069e1018426fca72269bc9f6107c6069767a94bf304a2350aab5d7b640d581e888c30bb8661cf055a6f33da924864776f6e1940087a08700874c3e1ffc33d1bdb36e87760e48c16e8b2e1bb9ae68c1b8eba6b17b3ed8473ce396bad73661f2b7fe69fa94d71c4a8a4d2d3bd7803cf18b5995368fdd6bec93bf7b94ca019146b68da8b463468d0a041a334495a47e7a603fff67802396a8a58a312e19e04e29e0c22efbdb7449a7af4d9d87289bd74c6a8cddeb89bcdd2113acfd8c4730affafe54f264eb3d8187f0653139ee4638cf114738a28833e84a6fbdaabf5bbdb6fc819df721f00b6fcca7d78cb39c5049a53c43c408a790b10bbbb3cb6efce761382d4d5fa2776e640995a411f5ff8527aadd359f7454ab1e8b1bbe9e48473b18253c14149f3a8664dda604cba1207258983fb6afc56e773d7c66fb3fc1b71fc07f8dd85b1c7b6282ba9f4e62eec3fc0f869ceb9ab5fdc18e38cf186b108e311c61a6312c6258c4df0cd449abfc7af9cfb0f620d1cf44bae418151e0c3ed0f59e4938aa06d609812250e44f437a300fa832e18477d10d6a3c54892554b44e9e8f9fb435126128de04944cad1303db8e78f4313740dca500695490eaa23c8002584124a931c3e004d723802c2204f14c3dd36d866cfa963c5121b233162c48811c323cc42402356891a9a7d13a26ea79bdf8ce2c3a44febf687b333923dccba0e641e6e20b71cf229852852fe40611c4314d8c565ba00716fdad8f3dd867b13092d7dc0195d14df5d20cbfc09c4bd19213482779f3640f7dd8fc68ecf025341e0697e8c4800c30836119065be17f4102116f02a006b7851c6fcf941e04bfd39e34172cf3bdf9b73874d670dc115aad70c593c93671fa42ce82fdf8f3126cac41909c09e9d720102015883479451bffe0ff862bf7eadd872d145a666f0a8b525c6853ed0c75e7b2da519bec85d3faeca30456b095e69e9683ae139e101149e004113400002aadaf1718debb8177d0743036c4863c38fc1b265dc831405581fca1eb6fb4c26ddc9163cc587dee33cee398f4c6d1dcc6c1df018c23d2895c8211c8ea0e5d53ee2477db2e5e3a84fdaecf8b3e5a8cf3f2bc29fe21cd941f62e04a54e6dcc0de0e3c79c089e07e9e334228bf501d6e481c9c8f0c46de30c17a78c4cadd05629d2a60a266178f2cc1e3fc5273941b62c279ffa76cf2e5d394ac217afffe24d2c483299a46f04bfdc998fbb226acb8b3bb840c30ec51fc57df24c9e9f47eaefe88b45f9a6c7bd1887dc8b30c812ede42962be43f86205f802e153a84108e19ead7933711c55ede37839b8a512f7a2cd64d554ee37e48c702ca9105393479ea0802f11fe27c7f0851da5163614908547a6660de0297e133bf2f860faec4884bc918ed0ac1de712477d93b57962fc0973949084752850b67086e6c7973099f22ac053fc2d5c30683298d4b0236c47f9b2c7533c4a2478947dc7797a228c5e5324e2c0e26797a4250bd38bf8ae038477a0818ee11efc3827e76d9c730cf802f77c4a29a51042487fc00b04ffc2699ab03efddab9a029ee7961c730ecd8861d27d0f5700fbaa42294c981962d371c44b837238610b6617fd1e543085b624016d84d121198e25efc8942df48fef86ee32823f8f38c28234e6e08f7643773e7d1a37fefb564fc1b63fcf83cf413fa8b403bcaa0188b4b3a1d700fa96420f7c33d888240dc83efc3391f54253da0f90a123f51fcfcfc44f1f313c517bea0050983367d18c43d1a67e28c130159688c44b8473de7903bfb74f1ebd2547beffaec4edd10fd88d5ba4fb88b504ae9ab6ec8729428479bde9b429cb6df609bef0c1ce3c3696352125ad65ae563d2892695a648f4692fbfd6fab3fec428714bf781156d8e53b7ecbe5cf2b884b9f4719d479b8e23eee5b022bee26be5ae79dc04f3f7710f4866d928136d3f586d6a7f025936be214f0e8776af497223ce9b90454ecee431248ed22f5d32e552aeee4bf39860ef834db845b6f2299421ff869452aa206491ff23d68850861648248d55b24f0e0c87a7e534b2e4862bcf361d72e7c7349b5a7cc52212e20bbed84f7422d262f1155ff115df6a60eefa1718eddae1d7efdd577d107df6d86e39449f654548967b946645ea8c9cfc0154bcab44a217894422ec0262971ed34e7495842f72d35f4922d67e95abaad5155b0eb989802c746ae0eeedc74ff483ee92a198233eaec4613932e5487ec41ade0494e1388ee30464a1ff412dd4cebba0a1a4812f76d3972e4cffc405fe2fe5934876e5ec9e1c8ef8a6fada24b134fec969f606e94924e88ad2455f0776f4828e3fdf5f7e044c9d3c7d57c1172d9a74f70559eccf55f53fd2fe5c55ffcb7e3408596a7d0dcc2dff02a32dbbac8b343046012883fedd84a823ce553132750b714d4d2e6ede35dd7214217f4894464e9ea2483762520a4019d6553334d7ab1cdd18d34824ee511216e83b09a210f5a7a3b29f3275fba1fe94f2dd09f8a201bd9b8035ecd3f71cf71e478db4c879b40967d3ca4dbfa1a5496b5a69a59556cdc995f63bb440d3ffe24bfb22b2b8e328dca1901ebf91132386e8f872140aa993488c98fb93abac33a2820250c66a2608119085bee4c0b0e9cb954ca9e8228d9fe8d7dc222bba8a48365d553a61a4a2dba1cc7d3b209085be9193d7b822eadf8f3af025eb72dc6e48dc96d22e367204a40053f950de97d6727263e726e742daa965f5babb6b533bb5ac5e77f9384823ece3f0186e9ca7add54ecebf72397cc655cc69937b71810ea8eed5a69951b81739b8dfd0d6122f037c791d8d84ea686d32451de9bd5cfc177570e83e6f5f142eee77f1791362fbe15e948b030e8fa17c9103e735367ea22fa535226e1717074e6e2ea5c83e0e71cbd8828b3c2cb8d863e222ec062e2a59c1459f1ff7e89fd037e162508c717149148a43aef2189ff1953b8d739cd7bc80b49c3246eeef64ff863bf7c33d20f4dd6ddc76406fd1c7de846d39ca669b10f6dd5b378ef2a7ef38f005fe60ad8d71b56964b188f56dadd656b972e12b84bab0034f449758da29ce5913d172d391edbbf9c4c1f909719ba20d07cc2b5e5a2b15d1d927ff64a023b19d1fa0a0181ee10e050789361c1868fbc93bf80836c9a734e8cd47a63860b3240a693632456a6e6c727274781cf55919258eaa7f046707c9cf0ba827685bfbb665df55312ee3a8fb479210da9f75b5f6677772f66761db26a179edcffe08415fd4a1887e6eba23d0cb5a6b270e51f7c3fc89f5f0c0946cbbf38324c7a563ff6e3bfc08ac884dcbc61c7153b8b367569126b2b21b39cf3dedeb86c364eb50281c22820e70f60e3028499df809773b827ab67d9333d0401648c3dadd86499210c18e1b0deaa194ee3803cb3db87704f5c023345c4250cfc670479012215cc810f8e2319f0188f21040cfbeb59dab5672b4b50142810a04812ff1ad2a0c60d8f689803b2e4c29a6b8b57f2e86f4bc1e0f8035347021006bb82acab06fb50ec2b635d1b72b5e5adbf06764b6c51671cf6d0da00cfb96896dfff30f6cab23dbd45a9b6c0359ec639a29d7c30df7ecaf6841db2236eed91664b194da772086f0942d6261a06d8ec13082211ec5cfdb73c54b97461b6632f085c696a618e3b427cfc5d1794b79025fe84b6badb5d65a6badb5d67e9dd9a4a91ff1ad193aca6e983f72a36b1285be1d0ff76afd164f4383e6d1c387a3b6afff43a6485f9f88208ec25f5fc80076fd21556bfd43fc54df45a67cd41f751f74d517d5ad13e25e1d82e381c31a74fdace5be3fb549a6e4df7f999a7fdf6b7114cbad0f3798ebf7ca7be7bdf4de7a2f842cf6f270af421eea83fd52e1145bf4e8f8747f93078e0fe31372c691f5c9ad3d7cfc40edf9401cba8668f56c3798bbc16c30346b1b4ccd7883b1396f301f5db8475dbc7bb18b3f5d78f034ff5ead713c21f6706fbab7e8810ff77ef0706fbe13340c1043c0171d96d0f4756069fad1bb6b2bece0756fbe17a3af7757b42f64891d448169dfa6807189887687908866692873fcf62ec6cc0559227c3c2af54047019f68cfa527740c29a54fc8d2b008baf444e989921134ce252368084440babb77a527748a7b50965ab0c20cb84444c3c7235c62699862c0be6c38a92f1fcb0cff72d069c4bfee3e37292b9522da654c031085c8b22f0a4ce77c1db5c386efd4f7ec3e06c82978e21a5a56fb926276193dcbf94e5f90e5c6a8f14096fb5fb5992ed0d9638865f529a728ae26173709becccc72448b29e8fc9f8a8d630d3f6247d17cb1efe3b9c1d4ec59a615672b8adf0615a2bfaf220a1540b2fb340f36962b4ac01afa489471efafd0c097a979b0c4be36475a374dd350d9f6469a54323951b1023fdeb6d28b3e7652c401e1de85e1e1de7d16e84fe79488b070a9051a924a4f5732556d60a0b2efe393124b7ff0a746b75841965b62e90fc6108712bff4274fe2506267a43ee9e9e313ce64a5c4022d75d8d7e42b477fe508f88253f6fd134e05b629c079a81de5545e269c0a4f10f4a7c2daf75dd98efe54845829ad22b4423706d42f3ddd18409fd4a908c9d3fd12b792b9f4f7228603edeff322862325c70405fda5c8e4e8141999f2fcf75352005ff0df4f61a5c8e4a4b45270f6a581f65711521972549429e7e1fe8a8c4ce91c79baefda5745e8c5cab1c941057a8525530e63df2fad407f3a471b791bc690a7c4acd8606ec8710bc2c4a24546a9d9c50683c30613b978daf7f18b0de673c45c768c25c630f2c43825538c315ec9166392268d4cb6d2127a7bf9a32e484a4c4a08600c394aaa1cb58d60c718d053511172e19e2945fe677a15a1df4108b25c95a11595ec5484568e5801018b5c22d2229758fad3309f7287164ca5c4d2d8250eae4226658726b600f69dd9df8b58918d35568da7487f7f65c6532a2aaf7dff5ba1d9f731491320c61931d2881115632a4697180b10e34b8c58935b69093dfaed2f10db70218bd45af67d3c6383a191232a7b2a4b973c0b90e94bae35a404926d1207795071c9548a8c9feedf14997d555cfbde9f5c49051a887b578b9bfc9414c01a2f6231e57eca8ca3467f3f65358624f67ddc62c3bdfbdb4797162f8020cb6d11e49eb7007a118b355a88a10519f6fdefc518f6ed416edd8d16412d805c2d622f5890e53ed0be3fbb16b153e1b3ef8b321791e06212d506b6efeb9c7bb5d639fb3ed62a4232e57f7f45e528f9f757eeafc8c097f8f777c0812fddbe8f4d3698931cb7205464df82b821cf2d0853a65b102c72dd82d882d0361dd98edd10ee5ddcda3cba0559eee39c451cbe716fd347620ddc049471ff6227f6fd4ff3608b381feedd176d39b27d7f450b1adfb4dcc33890e58e6090e57e4c0164b9dfe2fe875b42dcbb8f337bb34c243b205c10c8727f7244b877b5ce081d71c72eae20cb7d1d2cdb08e94da6988a907bf76f789602bda272ef7eec54863a1668ff4f4568df0f729f0847657f65f620b4ac45061b302c9520c6e8de695a3ac1103f80701445550d0d4338aa87107c01c09e7e9aaefda7653e96710b42e67a339bb50d06fae5b27b93f250257bbe1247c5fdc53d8574ec28ccbdf9ded19e1a99fa87d2f5a61efd41d797b55e31a23fcab34f3598f36ff4000384a3eccf4ec7f3e8e1e3071ce2a8ca0147a178972fa7ddc36144ed4e7c2e125636c37982baaa3f18326f783ce2488220d3450e76df10e92b9f91f1951393b484a635366a28a5366c28a53658365660a3c646cd1cd24188382e9d1fe4449c1f5057cc094c6c4838fa0b127da6c949cbc4669edc4c2313483f417f415c964ca039c58ca94a258010c6183ca59c3f33843cdcd34edccbb47b19c43367bf219d4ebf9dbb5b293729b729adb45606d687fef5bdc6990bd85dab73f2dff363eed3dad6baec5ef6a5199a65f416d95f907cc3a78c724ee92f219deef05b4a2d987306847486cb1c408bbffb8c22cc09a16f948bcff5a8f5de0cd6dac580927ae002b60377bee0df27ac9dae399ff83b75eed481f75a8a23eb2e37dd839dffecc9c9a05a6bac7e81d60ae43e5fa832c8bde81e9041f529e7736a50f84fd6642181a28352084a21d05aa0ade5ae0d76c98d50aae44aaa642af2a0c4512e644854f96c60fb8c638caba25096ad1c4593b11c055da4d80b83b9ce5de228b7f48568f1f3199996cba6d3255534fb6a3c8eb236dadca1d8ab44899e20211c24242485ea876975b47d6cc2915e73268f0100803d04b6a42f95b81ef2446db847d1a0a5ca51a7d3963232a56d0e7c79981bf244efc77dbbaf01db762eeed17781d66ff2fa4d54aa7df106e301c852469e5400b90e005f6b8a82be1e7d13fa9abe54c9137d93276869f3290dfabaecbd3af6de2b1385642af240efd3cf64b2996c2555324616992f6532ae9246dac89b4dbfc232ae2e377a80a1a64ff1bcabd1cf5eaa3c457a7a5dfb0895d133a5d726a91893a7b113cec569dc37e166388dfb25aec569dcd75c0ca7715fda784ab76ee84b1c9943e2b893ec27fad7e5a82fea386ad621477db766d3477d2e9bbaf67cd791696dfa17b6e90b19b2010ec824666884f6e7499490d99fdf968ea35cf11577228febd870b9d1030cad7161fa14efda8c6275be9b0f735f1a201b1464efb53b82828296e060f7b1060c533e91cfbeaf29c12c4c8369f00c96c133788571f08f8f7bf7e46fb816734f56bb714c0aa9509cd0ae73156d0c5d108e449b4dccb01e80c0433d7c00c121020693a8a066dfffa1b3ef0711ca328efa70d08e214cc35ab190d8a834d70f90eb67e572b95a6425400fd60c5339479e2e2abf9e05c6af8a87fca4041c7b390a97017af76fcebecf020a9d8f38aa06c92af7728cf653fb22669763868240d8d63edf38aa661c474d2dc8bdfb95d3625add26a9cc8299758d98ac0df42596b49aa6695a74c2d69040845406923ed8da1773b2fe2f066ded230e7c31d95af704fde1a11b3099da50eb3e1804db771b01149b6adffbb4831dcebe1b9bb61f918e48c7a4f36ae9984c3f3f1bd016a44722150b1d1e1e99da7eb61f91ce6b67f37154cdd6da8e6cb0cd6653b2ddf8e46c45369c0dc9be2fda9438aa66586e30518c6846b4a27154dc5c3c1beeba096d432adc62f103d4fa61c1228956ab55a568551c2c3b2266de68e686635be5982ce26cedbf201096713926ab86a0f1d0b672946f21477dbef15f9b5dff0b12571bbf7c8c392cb4c4bdfb232168dff83f1cdb17bf50cc51f947afb3972b32d0dac79c65328fa7ee491774104fe1237ebaa5bfdf43a6b08dbcd1c37d201c357afdd90c17aee1300bd370788567382c2323539a92a1ac8ad14e5ed3b4ef015fb4ade1ecd81247b96ec4954ce5c043f5f1d3adb5da946e30dca6a4f55533904c6d2eb9b94a4ce8fbf53f4d892b28880bfec12f9a823c28f9d99706ed4bab941a8610426c371c19c509ed516230ddabd76e3a349e1ccb419af670cb40eedd37629658baa6450fb4f637a4c67ddede6cdcbb735bc91476f9e9aeb08c3cddbf576b93e9dff3b69aadc8bebfbde08bc4d5de9ab32ccbb0cc99478b02aca181ab41016b6cab2803065ff0864415f6d5fed3a6b0ef6b3d38fbfed460967389a5e9e32d47b62111fd4117749fb1095f566cf8306b1cdd714fa3af6d156b50274001897d1f638dbeb41764d1724677e80b739407b25c4deb3221e81a69adb526695dd2da44eb13ad5568bd42eb7bb53699fe3dca8d34a96472a262c548934a26272a566c2bf7ee8c7bb73edcb08c7b77ee0fcbc07a9cd01fedd9f7a2c480ee501e1acb368d01e1de10eedde881beaeec3eb8b1e08bfe4c3718528ed6ad95d64e6ba9b5d55aaa2925515aa2d484d2134a5550ba82d21b2895d8b5efe35aebdf90557276e32d56452c283b947ab599b1764a83d62f62c9d41673d69e17ba78603bab58ca0d4423535fa4e3a89a61e93b7cd74f84857d6b86e5ed01c65393874b44b56fcdb0bc58da0d47acb943a9db12ddc5c8eaa9521c91e2887bb076521cd9ce62cb91bdec3e17978d1f4359df6ba12f79badce7b273d4ee83d9dad70a815c76c54f5f526edd67eae80bae14d11fcc962f3107b7e43e97bd8284fe44585061da7cf02945b5db210512c832f70e33c4361cde8dfeb0a664df77a9dc07b3eb4739ff84a32feeab1de5b98f02d32b46b426244f178695dca9997039b25dd25748334197388cb3e319852ff8af867be4bd5a9b4cff9ed7a209ddfb1a90d47ee08bfefb9a12cdc784d2f2e526698331c97e9225bd21575386b5975beb7eecb9e5531a34fe1b32258ad132910c16c56c9b0f73d3f0108efab618b7e30c2cc9c3517913caacdc6a8df2a8fbb61fbd0634c2b18d3a94118fb6e1188974ee855956c6a5709e48b4fd0d4fadf8fb5bcc519f286688a3bedc825101c40dafe2eff330ddefe1a9fbdb92dcbacf8273711af94ddc0ca791ff06aec569e457c1c5701af97f38114be891d9f7856cc0539187fb2b388d53c1cdf0d37d518ce8be6866f4f713b1be2cd3e3a84f84e3e3a80f0739eadb5c5f065aad1cf561997dffbec68265d8cff613d3a143221e997db54ab3ef531e1947e99915a6c12caa84e72ee1b33fba64df2562fbabaa7d59aafdd5997d2b6bdfb79b10da93baaff4a417f138aad2ac5835368e227da9f3417b52874212e9388afefd1c9481722c2fc9428e9aa4ee87b9b5df7e361d4769db6bdbd9781c25f7fdf919e6281decda71148fa37c87b31ce547707290b83ccb88ee8b582fb2111511b5f468f498f3dc1b8dba1d413e225187a2cd40d15c66dc1789967069b9db11e493e1d7348cb58cdb11e4e30324f2d97c0349827a827a726cdb87d11fe29e10f72ca543dcb3175662e92fea9111bde2a5e5862fada244030d7b641e998a53c848800720bdfdace3a8f8ba3cdb42017af67536a9b3268027fbd62ab16d4dc4c2b63fad8c7b766606bafee72a1b19996d734b9e5a7e735b1e03517908f00888ca48dcb33255c23cf0e5b71eb55240fc6475b6fd28e3c33dfb3ddcb33f204beaca535c72854879082d9932c913922fe3ac78e9ba61b4f64b2ad02d90858e9e7630eec17c2e3a7edfaf38f0e56e8f73b0459f5fe3ac0d6481b1eff5d11f848d5ec41911b7ecaa6f3691714286b807c4ce8edd06dcb3012fc8421a4cd082266ad0051fa0bcc00b69c879014e0d4a500424a441474bf9a33fade3bf20043a6cff2c6594b0a7074984400a50b4c10a311e261b7a6e7610e312010c673ea15221a5869301ddecf8da8da33eada5b5f486634e25fb3b29a282a20814db5fcf6007db5f33a120e1ae027475f6fc4ca320498bc89e3fe75f9414c4242fb8b177b2210a11bc73e6da04ff8b8218f4c00afa9b1a8ea3e68eaf1dc971d49c279b0dac3d93c02036b596f3b3e3101f9ed270fc143f090f1859a2085b6882064c70020916b09ac14feb88170cb96012010a9687d97d41ae690afad3496ca25091631249f8c008f29599dd9df1cae00adbb7e6a2b14519e6148386b161921b7ca046e70d93dc98a1b2e0c60b28d07ac324375388ddb86817f48b0d93dcc04007fa374c727304b581ce61c3244c6c01079ac586499848c2113469c3244cf8200cda6e9884091d5432e81b364cc2040be6147ab461122666d6a0e78649989069d1c1842a890d724a1b2641620b7b6e98c40647f667a2310c533e1627ad78d2f21861acd965c49fe10e663ed890ae4840579005c696272d130cbec49f4d6c23e2feee49eb4216fa23181061d3cf4642262860b0e94b6ee28c84367d4fa5bfdbea36b7b1f40865fc2d070c183b07b2b88c31465c22b2da54c507f379604b1c1de2a9b158bb623c717ecc895e90857ee6bc0fe81dbef3c76d7bb8e940d93afcf9e5cef9e46e7a37c6b89b3954a32b183b538d257a656fb9931664a14fb71c3036ee4c30c842fffae8f943c4446944cc4e5a4644abb136852ef8026353182c70b0095eafe89ec3a34b0844816890672044b8672385a5e88f9f646a86d3ced220ad94036d5536c6cad0233b3f43341bba3f766746adb5564c87accaaa648ac662aa6d9f0e5595a3e8b65f63ea90a3be961e323e1cf5d55a73b67d5b79b63d02b4b36dfdd9d6d6a16dedffa8408eca821c45634b6010148a3175db517f1cf5d19f5a6badb4eeecd45a6b9df588a33ee82a62d3ba89358efa66d45a6bf517a631bae4456318139179bd5e3cf4a706d12820aa4af1b3edc30ae49efd1e4024a002f1a8405398e18230bb17ef3d915adfc0518160ea175c2e4c2926eac3bd1eee5908c02dd42f3c843d85c598de0f2a33c3c5533e5aecca46164a886d9753fe2002080b34f4092244a6e69b66f8c97d86cef68f30315abcb863f4c1a3879cb4da9b692ee4c9b690c2ddb1154829638c524694098148a9e04991524a29794c18a59452be154edb0cfa73b1557c21867bde92e2026eec2447a11f2fa52e3c65e24af164f6fc189e52e161fe0c247bbe4b0f78983f20c39e2af604c2cf9ecfc369f4d9daf3f5dcd357d42748856afb2562dafea2a2bd6dec5966cae5b0f18433fa94d38ae473a33d69a0e3ddd446a6ec68c9484843831e0d69958c9ed1ab93167c21008da7e88d9ffc7d14a3476ab623d9fe9f2eb2fd9dba8ad0967b3e8af98f62a32523a197163bca1e1e47d5b8057137986c83c11b4cde60b60de673d0d170a33f8a8d84464b9aa03fd3378aed177c79b1dde5a4d5bbab42958121c2a8849a21c90d66103dba92245930c3feeede49928520e932491670f6379364e19505d73e69006541e6f521116d23a535e7067e0f99519fec6b8e4c8d6a13f000a5cfbe66f5b3ec214461e8657f53eaea12e029fb2cab4af60d3b7baf2faeee4096ec4b33d0f73ff8da19dd027cd180a6598035641c853ffb3ae3a9981b689f3d1592a9ca92272fc0171b6c2ec01ab515a90f7cd19f3dfd812fa3cf9e02c9d449660293b9cc88d1124924279124893449244a225512c99248974412650fbee4cffe53262555128944da3691ad55a354d34a44b4b5b5d60eb368988406b67189a5f38f6ea6e1bc89ec2813893a98b90b59461b67c4101da18cfa95d5aa9f65bbfe576db0b337e174073169db4adb66b26d27dba662db566cdb0ddb66da60e0deb611c92497b22fb174ae3dee65db5661ee6555897bd9d7d7cebef6c0aa1247c59d7d7502ac1103cac83efbfaaa3b5fb300ececbf0a859d653f402fd378f43542148c7bd9d7faf7ca941018473900fe00513ea0ccfea3f3b90e468aca8a0ecf75305254567478ae8391a2b2a2c3945c0723456545078feb60a4a8ace8e0711d8c1495151dbc1207e5e983403aea26753d204bd6419d7df6988b31ee55ea234fd9dfabb5c9f4efdd707154feec6d38aa7e76aab8761b0c8c1c53b2eb90a997eb29db2d884d74b7527db92b76d91f063cf0b9ecedb7a73eb2a565fbdc7ddebfc924b7ecb56d477ddc5d9d57bcf48a4d88c7d9d57026c25d896b8102a8af7195b4eda87fdd9bde0734dd81e283dcdbcbbd75ddf7636b1d0aeeec675d11d5a72508e3897380b7f65162a0eddf9072ab2fca38509cd0f345f63169ce5904be4c6e53983cd1cff8fe902793cffb8f3b8c7ad0a6f7d2973380a9f8443cd14fc20d368d4fd8c1a6118a4d890084d6d4dcd69631c966cb586f198f6e5a3847728ad4f8a97557355b1e79b6a944dbbdd775d3c23992936b6a5470535353b371cd3276c3b1839be311ed5b8cb18fa1063d4e51c45b8e88736fcc718fde15aea98116c3bbba3497156bb4cd96f128abd93216f16c194791d6aa812fb17559eead6a8a60584c561393d5645b964ae8384b14e3a86ccbdb0d6a655bce51e7155d4a20e1c9b69c6d99c3b8db21c56a7569562b2558abd58aa5a528a2b38323ba5cf76ad3ed3ed37b9e546d3a46db75200bfd520c340a4ce390c38b0de637186e83e93618181b8ccd291b4c89555a42cb981579c54b4f51de361c5206be4021f0e5c6a018e3a9e89236666c3016737101988a6880276a0368c6fea250f4f9a2d017613c51280ac5a2106459f1d228f6e346c951b7edee6a976e8deb400b6fcdadb936b78868cbf75ed16b9fdd9a5bd3f297efc899cc266b451cab829f2454b0245bfaddd5a659a56d39e2b69307fa427236ddf6fadc1f47e1146fc321d26cb26e871431354bc4481123458c1431337725535148686e4b85b49018f7a8135a2555140b19b201f8227a7a2fdefe932afb39cbf83920eed1c74ef8c1123bcb5986636c302c3906207b4b9e02c87400b96e4110205b3b12fd90dcdd2b5edaf4f7a54aa66ecc4f14069e9da8c44c1696452d95aa91000020009315402028140c86c3c2c150928589a8b90f14000d86aa6250421749b3288651140442ce30820c2140c0000080ccc06814007072eea6d659eb5e04a5188ca7aea23d4754656e696e086b650503685f6f4f0ea5897fc1646e8ab14500f3f2ecfb7ada35d251260f19d80715069e8187b99b177d8c70d85e7fa338939c24eaea2da06cc90939a2dbee53556ca8b2d4f7698e47aad63ee18e48d2da413f725ec42f8d2b143822c76210a86e61117651cb04a03922367adfb731f1d928105ad35c70638469127d668a425282d94b2b3658ec666640b6c49b34aa5136ed06fbf7daf0d5de9fa89ea2b70f592d5e949dfc5b1987cd8ee8630ac48807010b91f0508ba57a3944760726cacfaf976d7d99d83c50aebda8db05647fa3975b508234ff0dfab2159290327bbaf0ac98062050d508ddd92fe9542d347c08aaabd8b25e79141121cfb1d3ec12398ce890f2223ddae6571ca1cd7b330db9fa4d5a63a28b37172ddef3563e141d534c5fd2bc809a6fc20b20173f4bfb730843131da0193a03a57dba8fe0ecde276a62f12ca80bfb58f928688ca94fa10976e6233acd040826c3f0da8eea8afe8586aa27a8d2d9a05713f50505378d81d19ece9bc8a91e0e597e4d87e0109b3601012b75cb1c11be325a9f3d58dd23725d4fce3c06627c319923768a59ad857091125d558aa91ab119c95f87d8ba71e0a42143f137998ea094c81c4a7b497823f958319c3989abb81c47ccdbc2558b8088973cf3c1b9fbdd72e62c2a6e535752b169937a823d3cc5afc53fd16dabd2d30960b7f94d2560572ca00e695035013dd93d9f02c85f7a6ec85d06eab56607940d30b5f8dbe63ac586258c33145168635998e71da7f75dc8ef670ad17a10c88c0fc2305a4e1b471305111b2c1da82e31ac8258802c7dffe288c8d108453ee93b3a4bf9ed62d7d6fec2518ff865a8ba7d6e9be28153292f744ca8980d702b92d60ff412030b44d9bc780816a3027e223489b0bc779cade43106c27b08cd1a960bae76cdb917146fb2edb4b221336d96ea163b3eafea335bec5759072b4cc83df44b3ce0085526ec427c696c81acb03968ef30dd40cbd09dd0b867f9a7518b1043eb3231ec24b8e64fdcf6dc67e26e9d8fea33e2fccf09911de784866821682752860dbb288e40a0afb4d274bdf8f45f6721d3af028d6ce5e6820385d0efe6457f8f565a381f1ed9c4aeaa76efb1119f104206bd2df51ec6c2737f1b7e5ed272379284e5e25bca2bc187762446889655ec0bc53282ee5ed9652520955ec5b9b0138bab39e6d13b9f85149b103bf200156a8eeb4e1903cd214d050692fd2fd0e6c10f8167a637e0f99fffdd701da4d70bce7098c4a7e6d9e19b521a014480e07f676a5bb3c7197077d9f2945aad441a13bf558e9b3afd0a69360df1a4ac425d776a1e76104b26d0466a340401213c6125261fc68d894198b173d481b9992d4bac975278941fc25c88afef94b69772b4814c3c3a3415d1224bc462c34d5c32e3997b7874e7466baf4c3e415d0ecfc4a0511c3a0cd82e4d1dd952de8bf38477c59f2081954b82c677fb62b7e73bd06ef8c8ab0aa92876a291d2e81fb0cc69f888d72a4c5d361b646d045109e010be5888a1d285b0e88b48ee43117f842442a1280a3a616767e1a936bff79f73dfff2aa821e50351e90e49c6d090b879053901c35625ee5be6500321d9d9a832a4b98d217db06c691804840989ce67a80c521167b8c96f8c2d97bcc05962a05647700d126c5b776f0cacc45ca6a8c5226824244b69b398a0a5b14de5f329c40f5b2b5d1a3e69a11512879865a9d8fb86ccd6fa44283fff6fa2023c0a0cf57e5405b45625156cec7721adffaac822200a83214d2684973c2bc527d9efdd2e4372d2ab4c225e1e2d38568b56f1d360435ac2adbbe11ea3df90b2602c2d1b18096689c24187255dd5acf14ccdbcd42d5f5652a9999b381d46b861891c8c800472a0c5c4ff728665821357cb10e1763275a7c03c02725fbb1e3af43734ef61badb2852436c105a83e4fa06075e01d7f88af10b363575c9bd71fd8fa2dd8788116d0571375f3a189e3872672dd2b67232ceb49ea6db73e21d12221403b6fcf56534de7c7d4194e68b190f22b727cda5d4186531b738bca7b9f45834b5b4597a4c724b223944ba17dee94b8ad5b02f011fb18c851985d898477f0bd57c33aa2029bdb35e7d9b3dbf46ad505e7adbe61119f9a54d0a94d1d85e008fa69ebc3b4e98525cb5cc95909b8b1d5d8ac5321a14346ebeb853b255599d82932d257074f72db838eaaffa8b947b908e3dea5b9ca6d8233bcc0f3f373f0ee30dbd883245755528296d3c42510d4e80b374139b803c3d4daf760bbcf83d2d029175f7130c9db89a7613d6684a6f220008c81eef0b8c412afb0f7083f43c82169b65241c059e9bf6e9acbd796a5b58e297136878543fb185d8d1df1cbebe3ae5a504996254c022c62059cdf308f46cf66b51ad452f063fe3b1fb38b3ffd1914efe66e81b666cccdbd0732c4b8e95d56bfb19dcbaa342263002cd80d044049a5d0c12631d358793dc1e4d45b15c5dbe03a27c236edc43fa2d001a7e944a826c6b27c9055e8179d0c1911843446b70423776eb233723659c533cc935ab6d132fd924ac703f5c8fab55f9ac870cbf2953cb132b3b0c3285c9f0eaaa7abd4fd905d850601c46482c687f17f1945bd81cf788d799f76e9ca258d3439365c7a3d11327234c3ed65a24b5438264fae55ab61fa9d7454ee07299e69bda91f23cc38fb2750557474350232ea0732bb2c326c32c09503908b2daaad084f092a87af023ed7061add092e5151a14c86599017df015c0ecc0b6e92ad404a4d19661218d10670b69bc8803d2c893c990c65688dd572967977cd5f8a565e657d238f778a00b16b265767b3228735e06a78b56ba047ef20ff28810bfcca0cc51db9f48a358d83eb4fdb22c6a9343da0a83332424d223162c5fe45c1f0e3cad655871a994ef2d8ab479055e2902f836442a415353f631ac5f7edcf5dd6281ddd35054d9a20d921fa1103a16d138953c13bddfeaa7cb214ff054d1084025f60be8cb2cc6b088ef49941b9771539584527f8019ef171b36a7c2c75c3fb9855766b433608d47347f21872274d68121c80ea611f974b1bb3cab14f73cd86265641fb31817f32d53ccfc969c768880fac0c089456e3174d231566b0eafb05b3330d7b421150c334a1a971def00c23e29b661be582e42989608ec4d1ced2032720f36ec9640c1e94dacfdd5013e49509388fba55f35da841d0d0f77a02f6d2036821a3d6e148af022f1208157d8573db867b23da822e62d017e53da005c022e2150fd3cf3aba97198e647729e26f1919887a92ebf06a4a5f65925dfc316e717545f56fbe556945b6e14bd6ab9c9cb69a2b6dcd8f13fae5914f8a862584535b17b5f74524a5f15a289c910adbff52ee3ba84087727764b4ea0b113117aab296b356a389fbc74b80118e6dac0226013b167fe677ed188a1a67051e1133ba8cb42abc1520ea101babe1e73df165212186a440f52a28974ee9dbccbf681c05fd00bc28a832a8eb4ed7922721db0203ff8823657b17f04a31bc90b5811ba7ff988092888be33f14b6abd6dde8ca3c6b7a9ca568c78f2337ac5d15f388842bd1285458a2a4fe38f138b1226253d5b2823c3f9d4e8cce648043dfc7a6e1c7bf1b48d088ab52162f24d83e2736fddff390a2f79e342d3ef3e7de3822fd409546a8fa2650bde384028660539ca7e9f7bc8c48489397442f8336d1ad1a813e8a1e9858369586b96465626bcb04f942c984d8f45536953743abe25230b66d3609596758c16256b66a663a5495a2eec892a2b33d3b0d4248d145d876f64cbc2dc34665db27bb529be513f32a2085c6fc1fa715ac7c937d22dae7cbc18a74caf6613501b5cb86a514706c569c253829c83c10993f0d12afd77c19e0bac3683cbcb3b3960b8ea9aed48fcb5bd637d57c5fd460fe8b342e284ceb5218cc10e98519faaf02dadc3e41ff9bcdcd00f477c9a5b452ce1e34ea293d2b5a56079a1594ce7ff04fd2c698e605d08349ccf71e42d9e687a5804e6c876ada277a26b9d066edec8e955a5fd3c4acf07cead276f8718fcdc02fe8d36e3f95cea519e49e79b51f482a90711efc395329dbe732cc5a928e41afa4078f06da2805eb3c332f60996a73a072a0b310470697bc1574a825aeaf61e9808eddd2e75e0aac2aa134ff8d0d2c3afbae31ae84961cc428b01761ee1ebb816eec18858745abe5e5b4948c8f74b5c9bb89f332d3cad4c18d3aa52910a950f12984ff6cb62cf59bc770c9bcba08298c5df24da41dbdc4458a7fa8dea4ce8681d5cd2acc71f4c49970d4f5c273c04cf1a036a556892bd0114a7f77f83cf65216be1f73d143899b1dc9fd5e045675a333523e0da0d3de100720daaf6feaae0f1222cda5c14093b1b75432a12d96e90688b6e695d0d574c57be63cb2a758fb542464b74c5e0f4faf606ea0c0d61faed17f5b83f99b45b19bb286f78e65df98ddd8f78dc72fab78b548200dce2be3816af327119666d7b22a7c94f30124bb9060ad65b778f70f8317dcdad0566edd528a2656ecdd3158a34c35839f3dbe89420ab7c1c3dc053e2bd0798b871dc9f136edfe7147972c830260c1130bc68d4fe7364154a6e4b2baa1c20be54a7c42d77e400a1868e52f560a8fc5a3bf4d1f3e108c5c0ca533e83ddf2ae752acbf7a14baa1c58f8fe7970ffd30732c6ba0994b835a23279dfebdc841f221891c84586530a1dc8b23e91a775f2a72a4f0cad1906c2894e9afb2408474a3912d8407c84e787390dee27e0170cd004cfe1e49e49f49f6fa665f7f25b5767a13234e2ab57326b80ddf9062398da624fc2cbcebf3a42e2fa6eadb2f475f659eb0e8e606c96b65951b44cbe4f2ec4acaed717857b54f8f0fc95bb0b9ac5277f5c5b1aa1aa53b0f5b818e6cb3d3c97e95bd2ad849fa7e71dca5c5e131fa305b74c751a75f20562bac478988586780029b5993dfdd8acd06b99467c6e09ceba1d892c420d42fc566af950f762b9679c2a4623f24ef9021b9494b72237868bfa5a2c1bd6c186826ad9fff3ebe6f9da60f9355c04ff150201d58e4bef2e76b2737ab9ac2d0ed15199f28defff61c3a96abef88facc9dde54ac057bf3b223102120081500246efb83ddd7e7a3d784f94801c470c342da7f2fa8436f69a5431b31c3fbe44a7b2e1c08231e2104fcb02840540f36c92c629bd7629460513196a2de54d13344c8192713fe0e919b326ea8b60a592b108bfc859a9d3bd9a844add5e05e56141abf1e8b2c0911608957a475a6851c514b983bc21971e665b869be4ae96529ec456892d73b135a4eb0850861ac79010419d6d47ae68f35383f7e052bc7b85604f6e285ef035caa41b8bba221017f2d167097d158fd96b899515bf7ae8139f347e8b1d59412d026e5da8af945c73e02a3bf59f254bf50783ede751239d279ef1b1d706b50154e653b5adb192317eae76f498123594ccb21893195f8e076c6342752e019c604adaaad73181324e635a680fe6459845c5e0ff9a935cf23132d503f08a3b09de6ee02e8d7e6b9ecf360829031c3c62c3e8a2e8aeb065030c58a02e09d9bef039e6b90bb3fe666aec37f7e3df0f658042290a2a109e33ddba696e11497305563a56a02a8bd97ef711fac8525197bbf2a6b8e300bb753eec6a5945f8e1de27538472b1037ebc67038b0a6d1dc8ae2ae91ee67367ded2ad484536a7ab9d08afc546566958dcd91bf1a57fe434bfe3a48fb6c7300d02b4c7838aa161cb21139327f3a8e1ed484d9aa0ae7649593c4cd9c3955027a6e97af46394026233b8bdb3a006dc013ef287349876e4b0c92537a62a3e437b8e367058d700c7e235d418d57ec7b4ef2db70c8a43da25c5cb7f7d485f3a7fdff61ff69cafcbfee4212c2e9f9fc4d9eb576977b463c9c670b2a474a2edaa5145299e9779a73ab28822e3310145fd170cc9768aa5a4f61f3c8e2d650ff408aa6da1f6d0b6f5bf45b922ca4d25360a1c349996ee4bbd15e2f1e81915c107c6373277046b22e78b4578344486ca6d7d899b6e4bc67ab3934b2ec4ce25b78c5be4bc956ad5517e39ad8efe956c923cd96e3961e144a544756e6c1e40b020948245fb09372e81bd75d49fe10f63a5aef01546a655887185010f4256d00cf43a8a522ab860d669df0f41ec08e4827cc4b8ce766dcf815c04ea825a3601c9720d70f478746e753f3f31c81bc3a179563bddb30e5e31f77a48a283f4090e877c0d5ce72c35ec27ec852450d5badf0b22f899ead4b59f1438a98da203721ffa08de3d148340d19e95b0ccefb1c189ebad66d0e1d69aa5e5991eb0940e191740b585dde12b6ce3633355ec970deee6146743aacc0a5da65a60a8123c5ceb01224919bf15f41619a8bc458c0b87e4269fcebc9483a3f6498d545b21606389c525b88cef581aad2d4baf296902acf1c10becee87b587674e98f98835a68d8cda0a213198d78764f795fe2ff815cf9848720e3e88e2e6a5df8486a37e730b9f86466b0f5f31a2a363d46563e4dbbacb6cd39761265cac04ea57b84b9a8735d5bbccd360bd4182aaf98af67405ff3e7caf3df7dad1ae332ce664a33a9d3fae27ea04b310054cd34cdbf2331b8bf53409c94da863c9c7121fdb28fe510b57ea9dcfa972f09a5b71b7292cc04c6a48c7a54ec46df0fad70027b2f62028017ea013e6bde3a7e4f7e7207f9f133b25134250dadaae6cc5e301c58a307c388cfab2a5d08a74dd48e64c0bd60fc5b24f46c7145575c9921b72e35562e018289a0299102cc1328de26f27c2521a2aa7c709b487898ff3748a8661d769b3896024a29f8c828f5cbc07a819d418252867fd616ff125cdb7fbb6a0ee653058c0fb7c919c4790aae342bd1a91b6ecec2f13911f2469c961d1c6f57489f004fdb070506b46d07105d8468b195dc51e3a1004db5b8af0b167ed94f399d17ee14728a23ba27364851f774b275eed77b4c2d0f81cfa9844f3530c059b86f58ef0405c18b46e91b0db405c3b67346e6418f3b979105195c6701bb99f841755f843b00d0f46e5cc7bf876f0fa589a13adac7658d03bc2bcf6cedbb30c601b18aec3a638b5bd8b0491d0cb5eb412b58fe7b2eb0b1bd9d55d85683553dc3ef770a550c6d6b5e5c235195bb2a5512ab111b772fa8ac8c79ae4822508d6eef9ad19b81bc5aafb95ec944a5a8b9e2492d1af8f338a3f0960ec9be41de1e7dcb943cffaea3fac4157bbacf6c9bb23cec5c3706e5385a62946ebb8345a515a1f6b4841bcbde37744c419a3cfa5e3271372a07a33f14ce042a0e482a193ed08e065c47321e11a6303154d08fa05e2c9970fd4a702158e23db4491c4de5741180db140bccf433996d583aafef8f8b98b190262a132bb1623d7208d6f1772869cbf8ce8c5499ebb5612dc5a7941b0b097bcccd0fa64f6f7f63846111318a400c5a1559049006cdaa67fcb4e780ee3aed9e1f0dcd86cbf702507e20341c419c649514f1a05dced98bb1055fe18d359179c8704db1c0afff50020138c4e64a839dded7fbc139fb817d42705d498803a60e06db13eac8ecb4105313edbb4e4310c1641054ca11ed07f10a5cdd05fadf0215a3af4cfc7e71fbcfffd372a2585a18631a10928f223f522a7486fac5a868674485ac1dc95a2bc0693d8423676725d452f04032b545226367f339a7c1d9166c20c5e5c50e60108d6246e4afc16393136ca2c30b89aff28a460ddbfdbfecda14f96733966c6bc50d6c8317c52111fafaa1ffd97d06ec04711dc245db5d558baaf06987a9a1ab70e69cf385cb4d9d2f4c908b73c93a3e62ba715185e8f4097018589a2d35060d08063f3cd00f0b3263b046e713d5994648d48a978fbb81b2642b8938e424e48c0515d29810e2bf5089d8d3b672777273d4b38c7248480e6a56734b7b05dd178bd7c482f22ca7dd407d34b063d6c19c527eb1212fdfa19a0dcbb88a1ccb1e8ae6d5f683e24e95c354d8c05984074429d4265ca447be33cf8a43dabce0b4933cfea6692da4a4aed9d3689adac19753c9c91acdb1518ec8edd1f2336880cb2763fa16b45287e4038fb55415510a7a00036cf093cc227793f4dd877b2a4b91f14e94c2cd7b7a8b4ec778e5cfdd465df5a8c14f7bae44373f172a9f71c53e77b81709499aee5f7d6b0989f491718324b2b4f032ce8e5d501d189c0dada6ff65dcd5069a1fb01fba6fff1eb033458c5cbe09698a092119136f877287ddc24f3260641a5837bc15e91791130461597a060933ca1d121c9e12ba5473e7103c2495c02bf4f2f9b9c0a2a32782b14dad202707bb9e8b9f6fd11d435ec0c5c6c56a4ab11352ca8a53c50a11955d957067e670c227745b4133b279a02a8e8305308be93ae7ba33720a0296da40736c39cd89bfe9d7da9eb4be2a7b88184cad21d178a454dd03d976606a63ec86dfb1562524e42997ce7bee483050a9be4a41390a028e2419f995cadb1ba614aad13e91f703cda637f8648dd50e241f2fe2d43465cffc1a0c3f2d8ae2176368799d31e6023ff4a6a972773bcd95619b64f3e3b01d419e4c2a7d2cf3ad4e40d4c73559de65753c430d0f79115c08937b7cc008b402c1e5fca2d46e14c69bd2e701184a1400abdd9fba970d09533e2441e09dd2db520b674bf73c34a186ad894615b9f7c2e7b319d9e902ed09a242b90d08face02af9140a2a400ba18454f16037e2b219da9ebda8a082ab5f868b5e68a0fb61e03eefba500589c25df02579130b00977c6265d6f34f6866970a2ba0d77017557462d3b52af211382cfc99e7c3f00e635bfcdda31899d3da77558f8a9b678876e730033d2f28b4e159bb6d43197e9e3591f2db8a01de6a1bbf2a4b10e15cf8dfc89670eed31f4e058353d42391f6021c8f0734e07b96680488a22034a4b13f785a49ba1a79ec870099f299bd2804a6ada219d610db82c0c4b2b01350e1a5da9088007b12fbff26c682081edee105f433311267c17299dca9d91b8d0ef43aa15113a03c29aa7dface35d5265563091ec0509066ea66eb6790af310b8fcdb0eba93c096611e27b7cfb1cfa58fe64f89900b0eaf5e1f7febabf8eb7c125a85a009ae2feb306827250940d41a40445fca893fc834c20105be572f3f05be522a2eeb7cae5622fb92186fd41166b07d1cad01c39780f183362e5d070faa4a468116b6c90de591a9c9fa339a2a7c4ec9177f7c837787bd6d29a11d9bbce476c38d4f13b88d02a54d614412e861e144fc4d9f6cb9180c2809ff478da07977bbcda46432f968fbf6e2a04b7530b5120eb797aa3abe6bff478e655937b1f8a4fa242bb6dd582a3475079075192bfac4dba4d201631978ade0588236a41cc2b426abba90b8aee2eeef36a3ed6fbb9dcafe57c2ef6a30ac034024e3bbc7d66c0c27f636d97eec19549017fbaee884422d6be51e37680534def0a0fc0d833e61fb8b39b37326161463ad692b228b032230d0b4c960669207197b34c612ac2dfbd7662613113a68771350b3bd5261cf8bb1d962a7fbf26133f4512d3c9c5aecb06664371a94a2b41105d0822425b855e7e281b4c21c0a566bce137d5936c45a36ed8f1bb010bdfa773ae558711e9d5009bd61f36922ddeb2f559b055c0946895614bc3a8656dba921979a38c1f74f5a61127aac67b4328d76ccbd93b8766ee2af19a3e0aa9e341ce6e0716904a8ed29978503c118908c345975acb09e837267e267b4231f1dfd2ce84715429722085410a1c688f6f9d01c1682252f66c5bfcbd7b12bb923529139122bd10f03986a551361b9bc986a392abe3c10bd8cf3b4ff8780347001db8b27b3ef66d4c0b1ca5eaf657c28f2bdb387c02143814d0ef9149b5c1a6ef7c38fb5027d8f19272190bb547038ee3fab085a49e3b100b8b4c192bed5689477b023244e803302d53d1db013236adf49a700cb0a7d5c40cb4fcfd38d9db43397d90367dff7c0f8b883750ede38a638ff0563be984de9b173fc509dab60d8b183f918883bdec830e640a3ae2db1d2f9255d5bd807c0617aab76871a668fe128c4d3b666a8a06ff75b080dc352d10fd7dd8423febf36b67609f0317e7a76ca22ff5ad0545fb591257230f38879352ece6ceeb6ede3d8513f8b857e0cb9d9f2ec13d915033cc2c1bbd65b8a1d2e358926ed10596aad2e8a5a705b1ba411fb9583b3873b4bb01caaa4c640b811f8468dc163b8ff97c2be92e0013f71d4b3b59b41129676344375b84ef60009ee2e68e1e39ed4e7c59cfc3ca70499bb84fed04a7816894b117446580b71bc9b215c0161b8df824b2f4dd6c5ca2ae43df3e23dd23bde3a662884f5b9e921e0e50c117b84371c97b31892c8ef7db4dc30af9e616baed9ca50564150e7e76aa377422fa0ecdd73a0aed21a95efa5e9d787ddd84aecad52d98fdba4dbf15062b1af1d26d9a2ab47cab0ec39c9acfd8512f2156f385895baef4303c8f257307f73fd984c6d1da39e2b612f03bc70ab793bfda2d9905121931ab8c37492ca920e03873105fcd20c978352f9c968e2ebc1b080b0a94d0df7306b9a9ec420a4978f1aa8049eb9de22a76d10dde0c63057690ff0498797417077bbfa447ff75ff2dd9af2c98b298e823bce671ac1e6904e20be58b812d351f14d6e903e10f9fe1f9fba3938e1e968555b43a946cca3a47cc5d62a5bc2b765dc787cfd3c911f3e0577365f8c359ab0c5d65d44eb9e3501f6d182540e8df4ec03748501b3dc8cfd403d91b6fd8a8a0967372fe389ee1714c8791f363cf355ef12e97e3e489fb878538ea0fefbd0210bb1bde42e9c474ff948b500159c49425150f06480149c912d0d2d3c4e93f7b0279cd528013446a88ef67ee713e963a88abcef22ab2c44e2c9e01b0366f032e98e689f633ab626bd3c856ceba5f4b1e17b1f78ac0da646842da396d032b71ee4e7b21cdc3bc57b8b947767c3df662c64e883a0c9e34d7d49bfa930d5ae2c3116f5e405d927516b368476b708750561fffec0653cecb17a9a03989c115d0e67487807117a0bc4844072fa707b04a4c031f30b72f99fdc294d389a909d719186a7b2e3ba5d2f359a1c5520a84b084a040dcd081cef57ac0726026a1360a3828eece8416bb7cfab3f5030dd8a3caa19bc44fc7fc8a58d9e890920790ef681dac46c989155bc7b5740359e0a1129a18b6c00042cb5fcc483d9dfc3077566d7d829dd70a1bf2b717cf02eb4efb7e308da2a86fff33e81dce59c15402e4dad44d76b26a4b84b74b00f708b4fd6d0059055880f7fbe585a65963bb082cc49f58909de2724fe63fa4251814fffbe8f7eb09b60216fab7dfaefd0e2e5716ff663545c06515aedd60e8c98110835e4c87525c27b8f4c5203c1cb13c55fb50e3aac8c8d2ea052166347fcf147fd65eb0f9708af73158ff873a25585960d9b873034db1511b2b62b53800978b074bc8d03086b62ce0b467e5150c382e992b948615c5eaa36b4b6c5a932c1c49d1d579e2eb13ed76c29ff6d561b1a7db011fc48f7adfdbc0b4a1b89267462cd296bb98c7c907987236e66c1fc91652def72cd7606bd1e613a37414d4d87562846dfe792d4ef3302baa54176461d09ca053fb07a4e8f796d1e9a90fca8e71b2ee83c0608ec220325148ba0c2fbcba09ba5d760d112c35f99aa7b0b7b01765a31a85693b15e1436461aa4b13fbfc3336c9fd16aa802c60229368dc4aee3fd9ec1c5c698cc6c0185e43c4304177c2b97148065d82ffb5f1e002581e063ac5743e8860ff0b1d1e70a1e675031c05878ee5809441a96bfed41bdac670fe8218134440b75e7e0ae56ef9b040e01617d779fd5fda713022c705ca3c4609c9cfb48833840e16e472ee6254366d7e4aacc3a4b97aeb67a2fcf4cc9e9ab9bb1d6a9010c3c9572be5cc10f5e44b85703c8dae00bb8a01480f53d16717bea107207d40b9d2103f80e847c3f81399029e8f25e7c13291136361d479cda486214c74b784f2b5894e5366f9393f3dbe72b3238b54355be618684214be869700bcda26115a498589f7b480574d31e6066d1c2572665e37ace61a029da071c890643ba1ea6dc63cbaf4813f0c9ea8b28a99523f32755d887e99de9bdbb254efcd6d2ba061dc48f0a3303d1cd48056998e2428244cc4d799410112f7293da1fb210b8d7be3b55cc2bb442ac92c687ce17bcd2c14e57c35d471aae6d097dc5db7b9af0c76c9cd597f07776459f2900a583e3a0a16400aced53f274eb9284c56df05bce380027ca123e7767cafcce691d7acffede20a3ff03fa34daa543ffbc1e86b4e59e02f5e4fd7bd26ed17c6ff7009b9e4213210e5bf130cf7c50097996e7bea9588ca95ff6346af8da7344007c13fa5574afcfc355d4f88ae5a264b6d10ffc13e8573e8ce9be529bced5c88fb7fbc812a3c31284055b208f234333b17c3a8c5371c75be8ef4687ea72f5942f42268bcafbc62e0ce4b3f4fe48886a28dab685e83ea043b4ebcbdc46cf522f0be6ae70ce94c582540b53bd7152befb20ac5b102f77f6f7b334b5b85e909c6a2fffd49a540b5259d2ae18ee6060c90f8ba6db815dadeb3d20bfae7ed9c94afb478285252ab9b780e42b4d8bdf98b6a38d924d74ead58540c9b0361beabe3a6ecb7b9ac22da5e66505031d56263c75ad80e5963969d8e564cea81f28f0bb70ad127cff10114e667d2ff8487dbff805d7000c222dbdc7eddbd5dde4dd7aa49b105359ef52a3783550330972670ca8486cd3b53d7bc67a6a45c6bba66c52171013947054678f04e67c73ac11cb6c9a64354021b4e6ea494171c59f624f547c0cc822eb61cf8d67b4a1350b86111309f4194774571ac84d78576491b9d574c348d263acdf5469809b60c08e5e53ddfa802512c5de25511274bdf219944bfc99217f46750d65fa5d3c3cee0cd4f6d92d3c010aa339cad7c993afdbfcb24c2e2206c799f9e59c54b1079def1c341afd3168a93f02166f7477973864b5cc3d43dcf1782ad04ad85ad05ac84edba05cce8809e4100fdb401ea95443f875e93354b91bff59aebd515e9b2a3f15a4b01c407fa8bae58b05a8a40fef39eef889aceddf2cd36938fab26b775c2e2d172e2f61f1851d4505a43df1bf984fb2809f1fe829f50b41c02eddb96589346a0d5ce7a1762665b7fee4bf8c9e29d2f01f9215a80091b6f8c075764103b6f7b9a79ee8e2638196edc376e9da11d8dc96b6b4f6a38e564779124a72a75749b5d4904a792d7c180a1078924eb827c3e48b779f3183e5113da21a3d60253b6e098f42d49e52e4d3ef42e006ee2abee2c99b02c6865ee250cdfcbcdccb53b580ab155af0400716930044a05c20774a0190b8605941b60da0002e92df9d7eeaad3af6a03bf46d5434565879ceee8a2fec21c270e439dc09488ba9b41163ab262e23142d8ced8a146400938cff442b2176533aa325a2ae016a0396f7e1d24771ab89abf046d655e11ca8fcc9a70b9afdbc6d14209f05fb8f0d5c63e57a3fe2b6a4759b10df87aa4a1b7111bfae314fb3ca18f5dfe32e8b99626adacf8fcc3e732bd1fdd328dee658b52dc234cc041c87d1252c2db1cb7f47a0a162bebc7c53cc5afe67d58ab5fcc2da1dccca7686aac90094f47d02a41b988ff40d1a6157f28a03d5688dd8b21963201e6c21a5043affb315382dbda8ba951abad4175112cfa42c26a28f7ad258fde7d1f112013aa41d45bfc63225c2460dd52fb9e7af718090565e873167e76b182275e2514e461d24fd3326464d0e12e1f632b961cac7a9f8d1b5872b6bfdfe4052c1ec25968bcb2df9fa9052f228cc7eacd629962bb7e1a202cebca74eb558ac3cabab7208d30d895d58c8c2b4574316e4efe23ce8bb98c6b5f7dc7089af014b82690386a7c17bd08e822fb2e7ef0ba93752c70f919b070f2a33d7def346b1a00285c830640e5294eea0a2d0596abbc7bcb9f5d09fd749c775f061221c6f253c5d308f5cbe780a7d16136c1bd98b88eafad8c1e16de60f3bbaaf1c13d65c9d65287a5dc3871115a1f7c94491eacf69faf5f5efbef57595adf255b8f88da53510f7d7135d7c13fc35050f53f2823edf5fad792afb009394c26a4fb11933e814135c0c6f02106c50f01090711bc0ecb9018b2ed5f1ee3ce6163bccda6fe86b04031d6da442c2c9b25d870ff97248c2e8810c73d5899185731ff0239ee197f1c084d1a61c81a2ac5b1118db3275338d6d3a29f90022fde06c16c1831f557c1dd329c9989f4b92f72f26b2d04af1c0fa0ae53132876400f1bff9c600414b8b40dd52195b17c891d5713bc1fdc70fcad7c17c857d9dab3710a539f0817b6f0c90eb27231f207861a7d168af6f40f21748036d3143abdf523f40a4cdeeb62fce38e3364053a6bdb1633c00d80bfb1741f883598d0230fe32d0f48b5a1b651db42d2889f5e80b9777be3a88d8065db456f493d174677c0bf4639495ed6192189ea40035fee7d27dd0b831c07a95a6547db741aedfc783684f8ff9cdd566a4d195291c097538e119d4b187e0a5d287348230b437b10b7d3ff6de5ac8a1f8e936a1774374ca3348e0704bf93d3f4a148f64a104abbf472cd88ec520c401185f61e8b0ac3a23e68e761b0b2064ca9413e738b33a902ffb749c7ee8b1df8218226852e144ca4304cd6d44a500a3b52b799aa079566f8328f7a22ab96fde563eb55168d7a16a92224aa8846f4edc503059b6ecdd0f3d3da46e4cb3b5e9eb0db9724e7159c69a64654b4e451a586965458eccee162ba9e7afac2d78d77a001e7d8b5c3701dd61e2c06a98612724fdc663f471bd456087282f3d2167c4c2a41f6e277d1c2733acf571b8e771b78836e6e28cee1fc530bad03173f6d35a2b271a4d9dbdf648e0d97f4d8ba36c0a41406e8314c688f2f8bb89a9fcd05f943a4d25cfb46361fb7e7fc66e9adae07dc6ea58fe4fb06b1e394e4ee37c960a9593acf8a1bee5c06ae0b851c8f500c541bb80417fcd90b8ec8e769b7872e9c80e1d12e2125823d186b483605f33d0879386e2dae39b4afc6c4bea7fe0b165f3cecb86cf65b3fc3dc4195de9457707942ec6c9999cf1272451080d57294943560b2bc29aec1dd223a5a93b30fcb7f6660df4d42828579c207a0e1aea2101824a30d9385836c9582836cd39d71c7e16ddf3dd945551ac1bf896332b28d93b1fb581a21f9dcc07c9c84acc063016404783ae68933f2921483a8211c3843b2788184e5f490b7244cee9da271a95cf4c84a58a35afca6642c0bfcae9cad71cf6cff0e02799fc6bdb4bcd32c0ce822071744902e8ec1cda291b078e58afed7481ac3728c293de23aa97d781dd383b382f672bc5c6ffcc3324226a1b9dba5cb86e610b66653f5efce46f6fd5aec45e871e762253b3299dbe6a942d827f9217dbcf4760ac8c5baace493fcb326b55f43f04cc2d044892530ea8b5bffeab3beecbb610000ccb190974c9644026872826ac0227a14549ec3484231310b6cb38a06f410b826c10bead5c4b8d0f7deeb04d6dd8027296813bd61740c6ad8cebf66087e84aae08903e24b45f56cd02eae0b90dcebd979cb6d4cbd70f907fa25eb664e08d6d8bb72d79564b96e92d3d782472785adcdc400b615bf0c5f996508513fa138b8c0e17fe38f093c7850a912f3c7b133b70e9e0c8d19992fd2f4b53853232a4a8c0ed3d32bd4cdd6f40b3a20b30decf2d92c52b21769bb4a6e84efe86813781bf69664d2ca6296bec113f10c21778849e08fe7ef23ede7491cdd8ec13d1fd673748d475870dfbed73cfb6d7e999e0ce1ca533801dea80ce8feacb1a390639c842aa3c2313e9569f255a4f8bca1955971e825e06eabc62d0464ebbcd0e18f4eb95619c53a47032d9b4352c282fe250199bbffc4a27c4017329b6da0276460c946654890362c0cdd9dfc72af3acc7958ab5478ed3c6a7c3abe5d9a9a38341d9c92d706aab2511ab10b632ef74aaaf4c0b51795aa9fac34c1d49636693cb6b45c468150bd89f886de9766f39de464261c7d0c7427ce9410ff8d758e4f9b114e185ce70aa33b399d6822b6ea80f97b8ba2535e0934fc6fbda14780ed9ba7cde22a4ce4fefb5e17fda53bd1023ac32890d2e614268d81950db97a6b623b768871f654b2f0091ae616aff4363c4c478bfea654c336f410b143c6fdbc547b753dde07bef8e229e395195a5e1a2765cbdfd470b0856cc1f31892d9e252e7be6c70be83939384cdb2f15c4386b83dbee4fbe54b55122e0d0f4033feb3b5e1dd4850307f48af37f8a16e60414367ef80028af42ed840c477f2f9f571002f637ed673fbcac4323f338c40249d2ece3ebeb4de395171211cf2f7c84c226af6b98030dde3c7ce9b4f2ca1a5dab094b053f56cf39292867ceaff7412027b63ad0a565dcdf107191538331abc0eb2bf4a1988a6cc59dda24cc22ad14abdfea00ff5fe4a700442ec0e7f9aa4caf0c1db81cd7c579cae45d2b66970e2d35b4fd14d0eeed53550e476cc369c8dcac89b76a7497e81b950a2708d4147dac0112a84794957fbe2cba700c6eefe7d1ca24306ee67e5773a11e14867c20dd5c378906ec9bea5f359da27307b46598acd01af2a0cb152e97dbb7042694a1ea2de6096f41e2d670ec0236fadc142d15227ea0052e241e9f1339808a52ae8e055a1e407fd2bd4dbc29a78c3dbafa916b5094070b237d270923f6ac4c9a491244eb3933d60448e047e7d90f596ffbbf1b13e23081c05763c72a81eee1fa85daca139fb4133ca184ebcd676c432265f741543081a1f7896fc9f75e1fb7a0f6d617e413b14a0b4ae7d7524d94a69aab8b8e508e8608510c5008dde4aa041a5ab1f98bed698b180ee0b717b336238dea1aebffa21b111880f3e1c42b99b7de44a23f1f8258143566f5e046f6d8eb98075241fe82b29b68be44290d8768f7d7e7d0a1046958a0f621eb1410597268e6fbf6710136624af403bae5cdbf2cf8703108323f0683b6f6def6fa0af9c41ff2fadc68d8b59dbb6adf7ce5e5b0f94f1b813207072a5b277d3c0829b5c826303ab4cf9f8ae028d8a945e645f9645ca6fd7cd26bf49e6d806d5d5f7d855f89ba561e45c532bf7ad813c55e0785c65ed5c71df42ba9c8cfecb0ecbfb30329c15e0e7c7c426758d87bc53e53140c01568e8c9afd34a8abe718a270e4c89edfef6345d19dc5624a815a05a25a18e034eb06f02906854601ba21afaf6665a8b2d8367251308dd1360297615fab28de63a29915953ed1f5a4e8aed90ec790eb77cdd3c07a24f6bd96b0b9cc5390cbdfbd0cf570e8d13d78218a3a0d8a2c6f82135c5bd0751a07fc53c6a847a63a6f99862a009c3fd554543741674874d29b745eb4332766c40dc35f105c65e48e3f406ca99711538106a038adf926bff1c9bdfb422448c6a0c4721a3c6bdf435aa0523368922d0228ffaba5c1145a7d2207b0d48d879546af257fb7c5527c81fc6e362571d729ff342958654242164f1e134ce84521d70dcc4409eb930903a7b7d3c6d41e991b304b94e3279166329489007a875f2307c32f0c5737aa8a7bf8debf70230ffc6f6bb55c4f0f36f24de3eeadf6ad28e9338c5a44ffed0280e663f3ed6d9934556a2758a377f7521b6d5aa3769c82d25a6ad3df3d3c315abc8fce282a3388457a5ed4273259f6d01f9382e2bac9be8734825265108838c4a74bda1cf776a752031782aa9447d22d89594e78723a51485e58895eb20657fd2dd75be43072cf7c2aec70660df0df6f1d9a3ac1ad915a166e48c0b9306228bfac3e2c09b5c7fabd099c04621bbe959d916bbf520bcbfbde60ce1237d64105ea2ebaec6b9213b5b5f2b54911a5b38720ab38ddb87bb9fc7c7a0ff1dd98a5d3ca196c068349c1cc3c748885f1b52f845627deb50d3ee534717e8047f3cc915960c857c163bb1e56c2b57f2f19bc8d4f2e247439074dd05eeba0ed1a3c079c4a0f8e8d3d924a861652889b236676378ce41a49b43b7a7a2aa47533cef13b287282d99471eb7fc1e31535ef604d7e08d714664743fb468c994a45f4d493a45b2db8bfe186d4ab26d740662440a2781fda295f3a82ede41aa0d8af0da4525a7926860434190648f6f6a8f543b903761a8789bda9a296a7ed62b5df250eacebecec852295e035a0e18b92a1e2f99c4033720dba92fda2c41cc5c50767b76068424fe79de4385c407adbd1c9dce9f9ef1e6749ff7c8b09c0855a3c8050007f38807debf2b2aa4f0aeef7404d37cf729fb246bd68f90a199b56ee8130d2700c3a6a644dd5a8de57e8744185db3ae6d82711a437fdbc804609ebca3c4d692872d0067805630ec9404cda9c132e3e6f8e2ccceea0c1be256578938e24d222e3987c000f868551b4c1e2c2177e7d6cfe0e1b5cb2a838717f6851deef5f56e6a1de6a3f92d0a79e19dde583d9e40c31351626a3ea3632fb2100ee22056f878a549158e98a14f7b23c4c28e59a8c2174041e80fe5089347b33a327eca418f3e4945c540b0dac1bc891b281fd711840b23a02223fe340d32a4c34397685788888ee5b2652d20329285fdec538e2f81630dee3aec359d4af884ae13d5d55be53fc6e10180f73ef2ff7b3760fa50bce8f43006d7876ca9ce552f55d006b77739a9c8a1a68be04bf721abcad3d4486d6489c0def7d210b56d56125b47f8c633a628c2ce122f58732d18aa104273f7145d32ae1e903eb7383c435125bbec90b7053a11c2b2e883b20a6d094a815b0a05fd4b85a3388eee079d395650dbc0392b4faec1c01986ce01576c9ef8bb06302a0d8acc1f5023fa348bf9513a8c497523cdd8f942cc62625639ed4138623a51ada4363914b866091d74a95440809ae32f8f2b05a1082dbd50638e2e7b9e31591e1efd64da8ec35674d8e26d0fcceff909a90069143aa943535f174cd86b4b4184a208e56c0513a2ac7fb33154066333267373b5a0ed92dd727a89df5ea3e967b0c40d827b2db622637271120c08fee7a5096a56473bffba1051fc8297782a67527ca9fa9447de1a05d09cfb4cdd708e94203d928d3be5e7ee0449aa51f66c7ba99c8905d5e8d1bd39bb46c50a024766c0608b05aea36c1b4026b19992606e1d3e281e25378648f529d118e2694a453a9c27bd65506356fb49806cec5eec1838e8eec9d5b27432d14eaf80fc50adc884f911988abc82212c20da8ad604de5d26f552a3763544709cc93129b94c9a697ce1e4819f596bad402146c505c1fe30de1405ab05a65132af332d7a84417078229144a80f3ce670268e736e17c3da6c2d37e6ee85846b274df45ab52d93084d22418e0fcccfd1286a3a1b56187fccf1521b171847bcd52588fec3f968eb56474f78557509be5fe2e946dad75df740295b5cf681b4ba1aa9049ee861f338c5a401aa434903f025369aba2134320de2f923af02f2060dc0930f14a21addbd6cb02d442830287ea345a7600bb519abc54da3358676aa1d9b25b7a1605f95b74d2e6ff403faa43f7fb1054e40aeb9165a938046e15674a938105e7af92e0f7a475fd7c805fdcbd6f49bebc65a45258e879b9d4e706fb368a0e6b76c7ce9cc1c5270c725447eac052bbf457322a0c417eae00022714ff69dcad6d40d2b04e1ae414d56b3b2151e559a000c8abefb7222c4796c108a6087116094c4c066ae945608abe82bfd9869b903c3c47452a8d1becee7b637fc4f40ff24ea64bcbd11dc32600df4a3d4e027c816a743535ea2cc9b5c6a2902bc7d0ef1f6bbd61442485502227b5843eb4873dd5df0ed5ce8e119cd4c308792460754344980132d8c5226b5d8c300bdef308a51678151d1d90c8afb1cb7a10342859d038797ff8901915d8400febe41f7e5a548630495c9243ecab5876b626250f1105a50adc6f22086ad2c6385fa8904e5ac555131673feeeac1be4772df8bb937807b34d53cf0d007a44ab9456004d2b023a5cd326b4259debc86b67aec37abe5078838e5f04b5b2947e0ebb707d27e6d043ab84808ec3b3d5409c463705f87c50fd6e8b8fe56d80429b6d54e0ef7cbf899f625395c83e70d205508fa8a9f85eed2635d6deae5e396887e353aacc52fa024024a9e1db740a70483db7f392e6f941d2a01c70cfc8a0b5e9cd72789de56205cfece5172066a039029ed1ca8a5be3894598f8bbb588a28d3a31ed41152f30faaecab71171b7eeb3158f8db8e7d5b12030de60384d1a5a7a29ef765f270c5378bae1df28e5f7db987280ad724e789723656ed8eae5515c88f99fdc79b1a7ce3260aa7d410bf971c9c2084f872da3d6367f2db6acc1bf9db8319df186cbf3d8aad8d6a04dc9386ee8671a5d8ab8e4bbbbbc7dcca3d41a50c49b48e3f7b759dd57a5ffd24f764241c816958de04928dfd9b25390e8c85aff878be73372019cc128b2af690b42e25c9b9819eb61d9dfb077e1842fa5ff16d06d0769501a4b28a873f43277155442525f6da738f0f06d2580d000af1c0d178264c26d29920f15f10bac76de295345d654747b37f0cf40d3e063ef3a8e96587b285d9d6ec465c553c108c4b1ad27e9339f7885bd84b0313a7f7e99f5bf2f385c42e4f80d203b84ed562791d6a61281c081a48cbe9ef058cc2d222999b9bd70c7d3e0ec6a3fd0334c1283740e7a3f40968c95cbb9f7ccd349e28320341c786544bf8a7aeceeae5047603055d684d959050e84cc9b066086238de8d111b16ad37a97136330025cc4462ed90dfc1e5459932c63ea5279d2d77b9e52d90025f2528a3334a2865795617d4d8cd92768a44648cdd24afbc3feb02c3eb351841113620cc7c319f22027d19396a0d527ef823293b032653ae5481b8e1b9ba2739cc4ab857fae146c1f6b182a7d861e8a033a8e2a98d32ec417a35a2a5a86a32a9878d2a9794daef284d5bec869592ee2f4a734b41a9a18fc0193ca85ec5f56df3038e891aca6b156a534b3aaca16aa839dba424215fdd72d957af640d4d45372cba404311dd6508e34b56b21f9c9acd5167f301229bfc633c9174fa0536167075396316a1a884a21fd1c9c1b2eeeca6288142752239bf20bdc498933d50381c2db1bfe25b6d9963a354ec892dd7039ba830f7e77ca70312ab91946c706c3fcb01b37e385814e9d5f13e1988922d28a893c145e2ee2a7a580a7edaf42e52c52fddf0460f7d7c66f48494d60111f97a6c0b5a8710c66b8c81f3fcd9a756f14a190a5da57bf75731e79e3b44e9bc7b5fb1ea41da4609bfd02bf6bdee85f94bf21c7759ecee00132db148f4a13ad04bd9c743dba91d8841638504fc2f6ac0840812533a20ee0da649719886d4c494e5a4ac1e90f60503dfb83f4723f7af471599e489a2e771af2a750ff746f3f7aab8eee3ffc520d319230facce486362c7dcf0ddf801c7c022ef24587f52160302f8418f8f8dd0f357067b911cd8b2857704ba1c2ef2df7102f1ef27cae5a4bb6469793714bc45c00bb27bdaa5b7af63b3883a8915d35545f36788a30b77d2438098e25dec7ce09a719295d91f185f62cd666746c6a9b17f885a9d1d692b1abc1401f16a023857d13ed2b3015c36540d26b723195fee2899ee0765fd57c100e7c559b59f349d4aa30428968c3dd8a44ef34c85533e419d599beaa97831a954cdcd2bb158ff8380aad5d96ef83d52fb04b1a5dac4ff397b05a062b707ee9a42cab4692c21270a7fb42cac3130706bd18965afdb08a318317162354a6cc5d90321a21f78bfe3a58b8894ed003b521c558da5a0f5e30da0af1f22a72d70519c804c02ec9f663bc9949a78ddaa98d624802f7414ce746fc0e86c7c09e20213e8f34cb7ffed9a474a87971caf09b1698d2a96594186485c73ad342da1cf56de10302e7326f59866be51ddd81afc568af73e5fab5819486acb1d427bf9c34d1a375a46b0e3abc3e8f8eb60ed2662bd106781d2422ef317f3225dfbca0d1e7e15a8f82f4f6205168cfdb1980cd6eeda63bd303efc891559ce5fd0eb22032e17420579c7adb6ed9bec23362fcdbd575451a388896379611ecec598959a4fe47f234a8c1cc36a22f201b5391639a1b861ef3063d6544e5f42bdd3121d601436031c650ad175cb244d0a51c42766b9a496d631330600bac190d9045259f8ca5a27d5e585ce71cf173e5beed56213a3119c8f77f148d17afd80937782351648d28738a15787f79a61e3dc1a4a14d1c078877cb14389d9c2182a9f7d9f663d0a5fbdf9e5be76bd374052134d012ee161bfd60fd41c0fc4a7d3e7b1a754668a3ea2388b68dfb3b2d7c35c48755592f2cee220a626fe8f0f61c956150046aba26b395db4ba119edcbdcbe7e2ef6988452a1c03901b7cdb226073e22438ca4d2eac4a024ef128c44278cd75a4994644e122902733f4b45f91d4af9f9209677a8b3901a03ec78870d0e6809e32eb274a3bbf7dc7682659217d7b8a44fee93940045cce872280ce563db97a313dc21b8648631f09fb981572ef56fd4f59fa7afc804f6739c66af7d20e76a1b752209b8bb3c3ff3d1e96d3ae4ca5506ba2521d02d59c7b251e4bf1be3052834742973263c3b308cf3e4e1246ed4950625eb4de120896fd7066b000861ce720a089fe923018910c2c8df0f3526c54440b109809949315f42e2c1f4c53928f557cdaaf573f67600b3dfe59170197ede831bec55b2d7473dcedaf8aaf67705c1615740a6f80df8655d1e5dacad3799941c1a556f70375ec58072cceb7e824483e9c4d3e6554547f8584952a9dd3208244da0d4a5188621819654f53d581528e3c1fa37ac3a3e5fe3cd24d362d0c2d05c7a19582b471472812ca5c11cc2f34a0b2e254f342834f92e897ab144663e47d44c93ff9520d2743da3dc651b8da26e3902fa9c9332c15b2a165be0658306e7226e6d664dfd539eaa1130e34fa9321a0f8e0bb4384a264f386763ec2c6325358568823993acd6ae24e05acabf6e08fc8a6c3b2097ed3f4968f8637b3d689ab9c4e7c736283d980e52df2e0e47ab99332df0cf2eaa90d8336e9f64364a35e5f33ce7667c69b9d3b95e0340b95516fc248200083b0df00af5e95713a0b0772e7619af0c2748b9d1555284e10e4c8a506ff17f15435f5ffabe61d14166624a4364a69c85bec77a95fc76b1a4595991e846007fdbab471bfc37d6ca8351d1e790d478d9636cb0fd910f7c46b3d020e8380c9d1a7d4f33a189c289bc37ee2e74816c0c3996d3f0a9aa6485c4379eb60986b8db0563048f1b8ced2b9e73e1143ebf8bfafaf839e2d8ac0c451dc2d8bc10b3d2276229eadbfff96efd3dc4b50ddbfd8fd2a7b5f4af773e1fd8a7aa568eb6fd10b1adfeb20c4aa43cf0b0e52546088b21b9b34f26d8dc8389128795024db4d1898cd6ac7bb504ff504350b371eb1f1ee882363b0597efb8eb5e84c41e4b672192362a9721d2e330d7bef767a3bbc58ab3bd00f486cbdbd59802078b00a1a47e045b37fe1f790b6b2d51c57b61712afc11be425821f5b419bc42304b834f69c49d38c5e3ef0289670a5156c3e666fc9c13b27710c1b16b1ee8abdaf490478faa94f4744d23eaac09a3d03baf3990db4046d33af8a1570444868ccee44ffd47a20d4785c57979d86865313c5bbe17aa318f1e88635db850ae46d6ecd4ebe6d8b7d2f36b1c72a629ad17674c62d4920c1115515dc5507788b3d474efdde2f7ce741345550ab989d4d4329986cb92a819164c6257c8ef5ef68ddd43fbe7766e7471e9c25027f932ea33fdacce7b9a879d3713e54bd4659ca0e2e891bbb0e1466d5dce9d15c91e1067f0a3c743ea674dab651601cfb4a90db32523cdaf279e4e4ec025353a23cf84255de4a132a773e4c82b6960f61f3fa13ded8e5f30beae4e76c9c5a726a553b148938b42840e8f607c983f8563be9a229538406e540d4e393857b08dd5107bf0aaca404964e84de7aa5b033d652d16b7629e93e98f2c2be9aae7ec6dcf59187cf3da992b97068cc02402f7309326f51c001b41e805cc95ab6ad2ae7e62a683a5acbc8ecda8fb2c9aada6b470e013122ace30a2c8b4b6f3d506de17bcfeea0e07e587ae9fb7eba07f927c85c903c8c9529b4a99c8e842ccb52908f26730c5730d904569d4b91db28d43b8140ac420dbe4c49fc0722acb11842bcc1f3188fdc1420cd9c17cefddfb5d94ed039d6f351cd32c2cb2d29c06026d84272f03876c65dc97239e33e327069673a09909d205be59eb74f140272e0ef6b609149f0de21b01141206fa08a0fbcffd4b92c05230210ff67b618eefa0c52c125bb6cc765ba3fc81961a0bdb98670741c802c334f4bfadc265c436167c1d364364d2a38e7349d4078b203c200d3c9c7f88fe957e968f0ebcab6f36caf9435a72147bc7c74e76a37949ce8f4b47a9ccda1590f02ac2d2aeeba14d37182746890c805dc69426811482f5c4b776fc6185d6b038c644b5555783bbff620b020d93e539b8e5dec5fec7807ca97d97c79f0de0856a1bf2f32eed7d674d9b63abb4cb339f5474afa2083d27d9f3a2dc3bfcc97b30154d0c5133206bb1d5caf0fdb0c036ca4bd5e747a5715ca8b52e03121ac337e959348fa5670ba9b2f41169033ee0da905eae7be9ee20280457cad3e844f1e45bd4e21303e21f9e16e245ad209898d2a0afebc9f5c8c14955688fbf9121162860026e90a72835eec1a2be627a66927a73c44e1316ee493489673cc375a59eae3695c830171cadb6e903723e780dca42b37b8f13aec1377d225aec748bbf5394c891921df1c2306feacdc3de693092216a157043a325db9c0f3523d5fe43c54292158961393127c72feafb000410e0e0cf5a57b3f43dcdb70bc837c173c4c225c0e9ba2b41ce15d1bd89da032831a85581af71bbd30d3e3c7890b363697c6d7b3538338a6d3863c59cfa54c997139a15067aa787aef79c037f64b74540a7f484335479143ef4fb8b2de0ea4aaa185f9e6fb968a19b5241b318860e88a7e2f1c493578e768f2613212739fb4fb8c6b50d871000bc2891e0b8fa83a2aded132a4f5ad410c1c7d69cc3fcd7c1230ab1a8cbd08af151fa211d45717150a6f4ceacbb3082c9c469fc782670e28f72c3bb4cc20e458b756fcf565156be260c8da5b5ef285dfc1e7142d74cff604c3c13cdcb8570133c7454f95eb9b54be887e9b2ade0ce0aa953cefc0fdbdf567f8485184f6a7b9ac4f8c31e5fd95426a966118471c4a2cca20f7fe7a0c3238862452291e6f4036b9241ec13bb09b9e22ac3a7b92976e763559bf1ba98f52965e3edc4c5bacabb5e4116b77dbdcf6011a7a88e64188f697dccaef8fb9f4f18d60b5ea980d7dd1c9614c7a514915faa243ae7a030da0b076826c8b77a7a72d6161b8d2bb1ba78d218fa72d94b3a521d2d342e8bdb668386eea7a76162ae1d6a81b425670279f502d44fb9926f500ad2a5d01f712d73a8729431189bb2babd96828c3de57e2de2a97815d27f87f38550b2aafbec5f3dd76b282e1f3d30a8f7b1c05de19925d2cbd7645e22086e57cc45afb5c05a5162eaa5d00c53f6300b1277ed751a142d506bcc26c32914c28172b812159013977042cc967de1478e3993885ea56ab8f5a10df9a92d1ea16b87f1fc55a46779b6315c4ecc766526d7166f68897d58a31e7ed41bde59bccf46f282bbe8a935828605e0829eb58f302721739da2d94940e42e5442e98bdb4672f1e48ae4cb3d03edb3eb4c5b37878370d6877459d36a83067dd194a3827dc156da96da2583634aaa53e10294e4bf0fad62f59d85aec166403506208963fc5edf0d236c6a3bcc0037304ed70c3754a4d46e0b79a737e3de789cc91c100a7df2080abe474765df7a3e15bfec7e3daa22633f9c11aebf666760e7b06b15684c676a6418273a198257bc21d1032cb5354e831341e5548ec8f1ee1af39ea92b8d28fcf20142c32a07b0b6f058de97229a74d31167ec91df3c0f20d3151f2b64e114c631d1ea74e1e47509cfe0b71a9664e6f138159e9cc88b2c1963bf715c1e23e4128a7a90ee799059d6459265ecba10bb2e076b1b3b7b6394efaa41659871d3ed5410359d4fe7024ba61202bea787fde473229e650c664cada90405eba0160519f33684f8d2f0a24a8732f9a10bc4cb03fa12b7917cc612b35c10aec7d86a5dc1b11e643a005cb964f0644b8b29f69d42dc3162eab72fdd6b2a65b3592459b3e86cdc17ddda6a6ea5040b90a59240af3bf547c5342f1972d93e5c07f6fea41671f8a6bc540bc35f538dc9a420c48f64213be03c0279b05fb5913526be1815d137a2433125f897bf3fc71bd1bf1cec04e5e1a658c1bccb6322abc41229218acd953121e1936c34aded5e98dbdac1b5e8d94d7cc83b6f74869c14fd4a3018666c0ac7f486b5daf8d60331b37e8e65773e945aa1a65ab720a5da7f3b0edcbd04e7ace105b7c4336d9d8fa9fdb2e4216e19706011ffeb92b496d60ec8da8fb12a022a1c6a6eaeb02b2cd7f525f79fe4fa38ad8d74f4392ce33ac65d2f26aef233f3ed1b85b1dea7565505a8f84c6d580451095a95f2ad2df20119cde030ae6ae985c304cca9a0e7d89ec14f1fe1da13fc47faabb768ea9efaeac7f7f7ffdfcf76b37bd7f065f46d2aeacde3d640980383e87d905b2a502b3d3d5d14d5345eddd559121c6b5d0b6833eae20b54f6aad5ad10ed292ecbddb9adc5bca94a40c5c093009cc0951264fd2ece9b782244f1ef2ccd190247f302270d9c3c74360da6c2b409939810f3f4011c50da9bcb96f91cfa377b4a3c71aefcf1c0d4832f5f74623998e443c772415433598527928d6ae21cc1c9959a25f548b14c6c052387a924be1875c9534b9a5262a0f511f7dd0eacb188ed954eaf36db5d65a3be79c63254b7b6bf7e4b2c090079052962b100b822d1618de4b2999908a5fcbe6cf0576b6c51263ebbbed3979a6cdfbf6309dd376d6498beecc140f4b467848b0e7995bea628d580aa23bca2dfd806bb97d969858252ce980673fcb33b76494c13c734b4a7239cb258e22820ee93aa4ed90067359feb56009b7bed5cd0f28c7ba6c24d3f17108d2f1e64f2c5bb99cca44ba2cbbd699f338abfad8d836b56f2382969c5ffe5e25cdac6c410b783a0d2c6d9ed56afe2a1804a81329e9e405e938a4956de6d3f94166548e8174b4a04acc06f6c2325995b54a983fb305751a37d9dd9dbe539f9402fd9479acdad8196c3625279627beaf39e79cf8deeb815a5c2fae7077f759edc5d69ce19793b7f8aca09d75d6b3d7da2b23cff766e4f95d4ef8b5e69c73b2dc6753307577f78beff45e7776b3db3cb5c99a3db7c6c0991477777fe7711bebefa4b3677edf80b3d924bf56af954595f3b1d933bfb6005fa87177f71637b2d9cc959d2c3dc855004494b5bb7b7432bada6a2b59eb9afb38c487b8c3c81ba05990b9d6eae477ad7366633748e4aef3f3e958f6bbd7b23edfdddd759ebc360fe93ccff3e87b9ee751259948932b206af726c192669bcb5c135d32912641f06b851d09823662db859aaf32e5f977be37bfeba73cdfd6f90e4384eeee9e93e58db982e9cbc0c1f504d3ff9c59086ab101eccf5bf5592d27dcdd5d46ed6fe775384464166c8b7b256f05ad60fa33ff3a06da80e9630a5ec1b4ba8b3ec25bf7622e6976d2d51693be7e7f56762ad54dbfbfc9971a7777d71a77f72c8a709ec515cd80bb9d12cb93a18e34af1d0a798c2ab87eae64bd3844b9bbbbbbbbbb5777f7eaee5edddd6badd51d167b1cda0b0b481d0b640880002e0002d8806a24e026001bb69ef1b942c8cb000a4cbf87044c5f462842c120003ca190db7021cb138a502e00ba115e60c10e0074c81b39fa00393e0090638c879175d87aa683f12f72ac8e05b016b0638642be8383dab366bb6c3d60415039e0616db52ec21bac5c49d8930b1c0628e1ee60955928e40be8a0ce5ad89b507c527bd81a1301d3b709c5273814b24fdcbd01edf6856c283ec1fede00316a44a1a9d65aadb5b6d65aadb5b6d65aadb5d66badd55a6bbdd65aadb5d66badd5babb77b7850b0c8b3ded458c9a8e05b50cbab7ffcd18c00c20036c001e204868068e6a9d00a1d025f110a63f8050c8134028d4912e2a307d1f2c307d0d42f1859e50c866202314aa38c01ca20d808c507c01830d748418f0d42cb86047c78ed2ceda05413b3e1d00903fc6b0c141b58ea606e385cde180078b0703c0c900b003109cdfc3d70158846b0f10ec9fdbfcdbd5065018e18e0f94f608a63d6a036a12498846b8561e0ea82e705acc6e7a3c00acd66b5deb185870cdb51b29307d592832c1f6690cf6805a6bf5d1d5be42211b8a4c707deaa3eb5eb8294611fef9c8ca1fbd02d36791af2b4c9f953bd3abead4ea0a87d04fb6e6af9b327dcad355052ec27916571403cea97cea763ee5544995a92ab1bcca44a52a0de59bbf16e823ad9f956ffedc0e35518c29d89f0af9508f93b657db7933878f1ed126b86b94c6047b3bf15aa0bb7badb57e212c0bae5fd77516e89e52dab59b892296b5c88171018be7f2644000b0595fde16f903ee80b01df07d2070d63f6e3beffed509852ee9b202d3074028e491ae29307d1209a63f4e8101190a55a0119605fb7f8ccf002988218346f8224783173472a6dbc09b006a7c34173368355c14e000e0745beb36ff75faa7da59ab6ad6bc59a8a366a0196d71801b992c0141e0749b77b374d6ea57aff3c8201056843d8360ed2aad3a196c9853bd09855c0bf61b88ed6d0ae39b939b50c811a0801a0b922920964158166cdf061873b714160a59d275851550ebd3b70985aa0d980df015e2f01c4403a64fc526d8855c089c6e73dde67fa2055d4ec0b309a6bf6682b30756b750a6aac1a194cd826f548502cff2cc51615202dfe499a3c2040d95115427b098678eca10941548c1385cae702ccfdc141c3a1fa63871c2b23c735352383265842f38bcaa61700b253a228c85e84c80613944619b3c735292ce48592205be79e6a428d982c33c73524ac0826179e694a450527aa232815979e694a87c70284dcd6e9d39a51a58b8f85e8cafeb7e94dadc1878b18b0bc62e66329b7592118e52095c2d64609a4901e326ca500b9f2f2e64e0bd3886ab9b198ab03650a464336c01614db1199a5a2f2957363e516c700c4ab014802e19ac5e2ab4c428523837c3919b10e9c965f50295a24431e2e7c42afc36e26b5551526509887f68b1a4187d4de19d42d5a2127a4f88583e2a103ba5a16b642d159c97938168569d307553c07c06248be6f6c555a29a67de31dfd292a5a35c14a9ef89d5932b1c252acf8b67e5eb88f0d174b83b6d779235e76c11da62570632835c68b690dec7d651d7e6bbb770ada23c5b8625351c71020a14153fdc968012e10967c30a4e70e5d67587352c1dc09030838aeee551591d5e9d0e5fa2b877d699ea9440504e70f1ec919a665f7ba2e6427d349a3df7d36f486d5abf91dc17acc146e69cb77abba56f2453d7f2814eb8bbbb9c5fbbd6a0164cbbeb4f7fe30881e97fdddd4d4126500ba6ddddf36beefba63b976e77eab19c59cc358be1ae4d170c05d875ca18ed6ac10e8459c15eedbe3a6e76b77fda22b7b58c66952c9fb43823ec334ba21d98a565e43617d29d79e10d0db24b090ca64f678f6d19b58c5862d4ec691f412e2d6efe0972fb7bb591f25c482e2456d30deebb9072df71f6d4bf9eabfdd943bd2372bf8dd4a4e13fa79074689ced266db1dada32ca9f594db3a71fcc01d32f2d0e9c2ec694c018be3969388e114d671651d47c66c87496b3e9053abb4ca93cb1c8eedfe1c541f292ef540eea3e220a5720b0cd33f7848a6cc413aa5cf54409afb3d5499c7b52c48767ffe5997be2432e6b5149f800dd19a6f881682b2a7fb41f37bdb2e58812df543f1119b8e556c672746470c9f5ef1228b93e5e9243aeff3514c47cb250e2faa06dd3cacbdd36a1905301c472d5729331a3081864c83024e1e4490b26cc5c0b49a8402ad7878542b55620ac704186297dfdf438a9cafdd89630a348e2c8a889124b4dd4308ba0228514554c95e0852427185cd62ee172810440b95f0ce3420fe1a57476ef0da9941e7537f140af4e1add96d2a731c1f4839c8edd0da94420b88de29cc784f99367ad064d12347b6c8d3335bc1ea991153c8f28f528ce7e498deec5f8b3eb781487f36e259eb5eeb5a7d3d4c8976a58a70b958c6679b7f3e1b16e97f355236aa463916a92f84b4e995c359d6c81ba00fd3ecef0e90214a06dd593ea9ae5b14f95605b6302511f74f1e02a835feb06a2dc3d826ab486aae743f366d88a85ca6b9247d550d7ed795d3554355445d4fed5eac565569cdc01f10075cd044006a021c4962c5e77818280808258fb03a86d2dac826ce4e8e163021e7c80cbf58578f966fd70f2bc2006bb00f56954ad45951e017905e64f0ecf7efd5a6bd1a54bcd64a951dbe6ad3e4dba3bea03d5cf519ba6c5de65b5beb06bdc4349be3f9b341a80336d0ea8254fae5e7acc9efaac1b9b02dffeb995d25a9f66bb5a6b73818bc0043cf8118408084598727d4ad474861fc7a8520bca37bf99db281138d221109cc73e2c0473b27e765e37821611108a502444165fcb7aea758eb3326bec74528087d4cc8465a44dce53026c23f04348bf05adfd2f7b4e337da6260a7581d099ffa03839674394fc39f8350986a28be257980366e391b54e869014706720200394404040b1d9a4b181c799b62193c7bec5c9e5b4c2c93484d7b422db9990d9639f09c65d03bfdab7d55d61fb4e04044ad442a8c0f66353e0725ad9ae034148f7f44848dbbca122b61d222cd93e4d629f2ab14f8bfa4796edd32547d922b5cd6ff6afd701d920b7218912b3dd481fc4b1fef18f7221f9a6f350df51dfd2ee4f1da62875f7da6856b1598e4ecda74cee9f01d45fc3c6f5ba09c47e255fa7d6586ce79e5fb7d6da3bdfeb70a5d7b9b4bc5ab69e55fbe9dd0e1dadefc8d97afb1e597acffaaf6b3d0d3b6879dfeabebdb78ffbc77b1a76d0bdd775f5464581c82f01aaa0c129812ab171426717a48fcaf5e1852d47586062c60a11e80ce0ca14940057a84822b7c83377e569892f366974fecb6a7dd9d3b2f97d7f05d80192b1d963bf622177bc0e12f1c6da16043afd890f870ba618bcb6d67e62b2a870f7df5a59647bed26dc68be397f6a9d351dfb5d9bd6da9c6a6b9d916dbbced6dc55ef8ebc1d0af529f81d59d6c8ddc39cc031cff3bc32488d6cbfeb0e8cf54f07822008fe478260ac6b20e8fdbd247dd0f33c32488d6c83d4c8958c7df7d4d5cf7adec531029735976d85acad23e8d9ce5cbdfaf6bb2edf1b56845cdfeaf07a64567fbea51df568cfa89d5febbdd7c9d95d85238569cd07e86fd7f65eb2c96a23a8a3d5876f5605f7126e1a15dcf44b9213b314839a9c98a39c9825772ad7af415dab4d9492dd94613879c2f2a443e1cf4b34698c524c1b1daa4972ad23e4fa73fa9a4923fc4ac972f6b9a4463c928cdcf6ce53ddc6005a8d72c55101269250dfe74ddef4516f72a7efabaa2dd615c3bf5d55d5e4d4c5c13815529a99660aaa2a57307e257a3d556bad7f9f4255fa5e47a47f28adb29ab92b4999fa902371221b0ab1429246adaa925481e095216a491e5549aa925425a95252bbc96d3d2a07b3a7fe8b29261459e2d9d76a3e3ed9890a9794eaa7cbf5bfa6aaaa493b2b5b55bbc9e949f43ad1eb685593f75d53f3d42ffbaaabbc74a4dc5248566989894caed956ab727d2fea42212b45c56ea251a78ee47146aa09cb19a9335267a4ce4c356ea4d9531fc6045328a8dc51ce53bf8a6437d6a28a6b9efa544ddb9c4cf3d4cfc9d1d1912163c68c1ad9a33c9242e5fa1c4449e5fa947447ea5aadb552814b4ae5ee52e606548a6a50bde44aa4a8da2a58c68c253c85e8dfd921b59abeea2aeab8491da7a406350f1698c719a89fd29bbca9ad3a0d525fb9cd91ae5a0929c98ad6eb728a70c575adf258814b6fb2148cf8de8454ba54ff941d959dfc49a9f6adbe4725e58eeaab2db97e4bb90d49941e7275a4fa342fb8be0f4d1adfd7efab56d33f9e285e516489624b143f51044531144551145ffcfad48aa6b9628557a4344aa7ce80a9ef792b14594af2380345ada567a0ce40d1cc630aab22fd53adb523082e2eb89c69887864914e04bd900130153a924ad10d363035e842a1a2e613d5c90b735016d82dd03fb082a00dc14e64b158e2fda0be9748fd2294bef85df874ecbc4e1c6d74ad766117de1c1da51ea5975216a52d4a3f4a414a434a6bcc9efaad11c8a76b35c8c6a441ab84e60f3d336bf52bbd4113556718606ec0813fc0dc8052d11a93862d1390eb06b9ca00e7fd5c59b9ac1ee5364ad53c75d2f02e5d738fa21bf8115383fad5bf34919dc44970e951376af6e4ccc460ef123573a9d953efd0eca9b48a5275adbe0552f55dcaa33ecf033d2ff43cd1f35c9e873defe57930cfeb4827ae7fb7b9488fea19751310022ee79c73d23a67bbbb7bcf39a7e7d017d0c0650a939a12ece0728289aa48aa367575525a1ab960770fe2ee1e7773aabbf7c052a5aba1ec4f952ad88db5873aaafba1d13bcaa8a33af0c5825b75ef462398b16da9c9aadf1d59ed38676dc24bc4901283861851622c99704215363042b899e2668a9b29a898c215851a1c1a709670a4e05099bab991624d1a5c028e0d385370ace05ca1b55237e29f08fac8ca9fcb150a552cd89f3aae01fb53217f75532155956f0a86c384dd66845825c25456c99715b4ea602854bdde8442de82253ae9210e965115ac3cfbc77d522ac56b9d010598fa90110b03463f8c7c9760fa38dd90bfbab72cf2860e53a82c53db68086c53ff383dd928dbc582b9c9fd964cffcc1cd352eeb756acdbb5becf3139f5cffb9a790fa4ebc81b0beab6feb49fe67596c9f34c68ba37b7fed953eecfd1a922df5869faa15ee74c6e6b7d3ff53cef193ae9047ca6e6614a22fa1bf84ec02f2d937fb4368ff79d0e8fbc0d228952da2c9e726b4412a5923547bff39ec04b3e7a580e66747bedbebb6dd75da36bf308fc853940181a1a0a2137b9a6fe4d03957b1882295b0e7af44ff8f39f66c8d0a9cd82be1f976d365fe4ed1f9b87fd476dbe24f225915abb061b4b9ba7b0af31191d63b2fb35c3deab42f2ad3546d249c93208586bd7b1eeb39e8ef759235da21ad04bcedb01a5815eca743b9a59acb10b720b7e3786ffc23133f833142a89d4fc9645af6b047275dcb7ffddd17bb2a4fffddcc02c8d10a9b47b853f9794224ddb778210550471250d991d6efd5469da6c84c8f2030d5688a6aedcfa29d3b4cda862c18a2b23905005935b3f6d9a3623374c81e40a698d089680ba35ae79ba6849d79250a2aef9681f37df48da875c86b9a44837230d47569069f2848911dcbc02917dcbd05576f7fa1ec49f88b514056064cc7239238c0f61723f86aa92fb3f6726f7d79eb2e47e9f1e5af06148a881baad1801a722f19f4ce66efe1888a4ec3f7f2893fd59b12b8a8861108c01e57838e1d61faa10bf844230c1a585ec8fb95065ff2e14f299b5dd4aaf23a80bb79c2f973a4e52b27ff5bf44d97f0643f61db2c85ec3ed3485ec5754c92ebb7935e13683005384116272ffcc881548d570c4098c4822d43e256d770e841163c2131713c824716b1c9ed00ca9194325835285c2c204b9df4605ebcc0c2eff7dc83a3905d9bd646fba22bb0e3c64f7c071db8cc80085104c9058811065a0aa0f5988c9fe36daf6ba4d314f6bb0e060421216656efe9f67af23d525e80ed3aeeebf4454c9fd58044518e52e624dee2e63725b596a4ba2d572981378fe785fb6debbad56cbdd672ba8ebfcdbbae39df792b7b3d567ee50baf23b6a64a5260d03e47eca84dae8508f30abc1a7e4fe494928936e508fb45ad76bfdfd3c185b70498dc46f5a47f64062e43c4d955e8f3f8a0fd78b2f8aff7a1fe2bfc822513ad187f858741989a27fe9c3237c0ff4fe8380190cc3efc6d2a72c4b6f02b9fbee73f7e1b88122f96bd5fc6dc0f3f7ddd839dde89ad660245d9bd96d5d53c9943ba76eec9ecaaea923bba7a6ae29777fa704d53575fddd539775c83041c28a841ba44e30a20406290a27122cf9b2c4ad9de6aeab3f7604e95f62448d7cbc5160bb56984070d7c8b9a3a43c289ee57c55a243513e2210e3d6ff41ff8fb67dbf901839874355ee2fd2b6d984a42b7590031f13f8800ad8fe4e825f47204edb41ffbef7b52cfd3940737dfbde8de054833f28478ffe08f41498222088e26f9da479ffacbf8e3a5d5b823f0305f5e7e83f725a420a0206207a30e2d6519c4c88923bc1ea2804374d9c5c795265c4101063c210a1e15245d465cc0f238e9a2cc1c2e5a84b03aea84880aae289243810d65869c221891f982882023ec070c3d59222a69cb8f5f7c3e0017f76bc4f60d00cddfa8d5839018b2ba4584db972c2140d3810b1821174b872c32d0bfcb957182169e8362fee9f755cbef9013c73382cc9337703159967ee86abecc1073f82dc209567539eb91b8ab014dc7a1831e0921ae1f079b3ff5a8d32c148c2e59cba1f595e8fb49ae52efbb3debb52e87723110f3ef8419f3e81cb0945abebf8de23699aae51abae519a8ece499af7337f37be4b93688204cb9416355f6cf0a065861dc48c708212dce853a849e3cca4d1d49462597acad4c91b9d3b496e9a699249a3337d7a353471e02f98f5ae2f6952a61ca81988eb594f9f52d6909aab3456555464a6c04875899a4d358066f8dc7ffdf79f8cecff1a67f3cc9cecff8dde7f9fc71a4bd6bb3e30fbe87acff578f4bca72ef283d1832c98a6c39ff6fdcc1f8963388a284f0c2165a5829598215c50a086490a31b480c28d7e38ce40183de01952b03f0c1f28656a1e67b272266ac564459948c0d389872b2bb460041063442872840a1aa613887095c48db23868dbebe9b7155c06a99189748d0ae95af37c80c3af1a0c0c51c50f67741801949a0c5744408108b488c0cc8d921449d7a88d12f50774fa644aebcfd08325e00e4456477448b2c30e5a44a0227a62aa4252c48d3e65ea9e3e08413ef0a06bf455c09f4bca94e933d1a769fb004d0e659c388164872e373a9740f383922b1c9c54f0e446491612dcd92a59b1722204294841654405252c61d1021aa32c8e0460ca92a1a32a67a880a289267468a1890a5a947cb9d1f7e0879069bb400c493cdc3064c50b4737fa204cdbdc925425a60c0b564426681cee1b1f3050ca9df33090300b6465ea6369712eb8b8b0128624f835d431cb6427bf9c49050b65863ac4c193777050fea319c526c63743b3a78ffa8786346984b5ebae2eae664f3b79f1826c9a51a794fd4bf0bf16c9d2e2b2f8de585a1c914d427ae90f86ef63e901abec202e771d59fa833e26e99a2876207e481265f17bf0c5d81396e54f47e76f07d73fa58c28f78e929c7336f8614ad30c71ece052c051fa01861a8e24396da940519330446c913202196efd1f0c1bb0955dc9ae5c284d1ab6dcff53fd4e450656b4420d6f745d28018715ed14ed2879241f37b1fe6175a0cec7fac03183fcdd8c30c09b31848d3b453b4a32c837a37833ba72a763e5b67f6a9eeefe66fca787fa281024ea82cb7f9215b9ed0511ce6d34a316fe7e1d2b590bcb74ac76947670df658a8af585b198188bb962311c8bbd6231582c66138bc562b17fea5aff473d5497977a30b41b363bc9f97f828a9a3440efe7dce0a7a806fdde7fac714749d7c21bb5bef8f03b7207277ed3aefb3b709451037b76dd23b9cd05f90263dabd8f94efc5780a7f6f897229bb928de58e92dc8f77703b4b3e973b38562e59b9fca74ca3c5889a3dddf58fcbc8c864525da35456d5351955d75a02ad2a7670b4efdf29da41721b12115c5964914516b7174a3b4c9386cdf7f33c4d1ab1f0cb2fefac303708bff1ef5c991b74df3b228c063d1465e3f737a3f73623f8dd3c6d74ddba11f63623f8df288b8d19641fc3910337cbfe9b757a4f3a3dd13fa83b0c923040ea9a4c06fb6f043f36cac82f467bf34627447fa37d370ee93ec4e0a86b4d04e7ee6d98c018246180c494fb5d0881cb1d5c89c151eea719e16426981af4e78cb224a34cc9ce50f7cb98644db228d993db5e0ce59621754d461453b3d3bf83db51d23fe1f7c3c62144f00786a20bbf60fe3135b2211992eeba17dfb9f82ee7bbaeeb9eccf853e353cd1e26c0ff6eaa7f6ccca01d255dabed14cd9e27d33fe0a4117e3f38cef011836bf80e1006b8d9e3a313daff94db68e40ece799a664f6d08bf18692e72461a91f3943215723f1ecb1d1b727f6b2cbf5cfed45f1951b9b3a4dcc1022b029732a95c43f0825f7ce9b27259b5a85c45b421e7695155245a54ffb518e78d367e2e93c23229999493374437485c4c619b915490fab718e7eda3218ecedd47431fb36ff133f4d1bae17c50b718693e5afc8b7f4116d1c0882c439f8b2330f8a5cd762af7bb40ca02dbee69435deb2bbb025b474634a470072782bfe3d43fe54f89e24ed30e6ea76867c98ed10ed24ed2ced20ed30e969d2711ec7eca6d39dfff546e73f135c4f1b9235d20d198c207d275240d09cbec69d9551896b2ab9ba4fe2977706128bb4242d4b69b9b25374637473748374ae10ece6d396118862f646aaedcb6837b41cee6d9c13979f1e50e2ef7d394dcf6e2fb69491f4da96bb4a417a34c4df3843afc6f8646999aae350d8976d4b5965d758db6a4d4b57e9a5b4f33729becea622c9bdd28710106b7be945db5a474ce58d28c723f8d742123eafc71c65991f3f4cfc6a1d66d86935df5b8e124072dc679bb57e0cfa5ec4a76d57f33d47f43346d3c529876481a13c50c50b7fe1bdcb4091992018a0a9ca678a1e2d68f8192ac8572dfd479b7b92dfc39631a189165e8363f1ce95217c03ecf51f3d85dbdb7bf9b5e67efddab7fc91f5dab6f0303cd1ed9911f04c97182cb5909eacce0c0079e3ad5a9f8a60e134ef97e338181f4df2ffbe75ffa9d63224d76c7ff7db3fa6cca2cb2acfda009b89c513ddc46a479bca7b9964162f9fe7c226976501d364236240b070825112e6753ec5db9269064fa2d25197f9361a24b7e3decbd1e3e22d03fe1f8198f9f615f5e1bd6bd174696176771da7a4c1ecf7bdce01545ef43fbfafea0c6f9b6c6b2c57a1bb20c926d9e359634c770c0fe861d60b27e797fbecd08e4924162b9f5dfb73ea8c31df7c6ebfb3dcff33cef09483fec5db08f81a009d81fbbad870d0c13f6d48667d48ed7c36686c1469a0ed8df87b170d8bc6b669b7176cde675b0fef52c1bfcad7f7d90c3c61739bb86c570c41a88e53248fec8d658c66afffcfdecbd77bdeb5defeff5bcbf200d7150107fe0ed9ffb9f0e1576f276cddf136928f5d17c749126aff3d037729befea0641d3ce84cf7d4c14263e46856aab6d14c69430e64815eece7212e3442e6b9e3931632e4b4c187c291d71cc7052314e62bacc2bcece06258894802197374f5de5e98212455d37e7b4b1eeddbbb6ebba8e65aff5acb59dbbfbec3a1bebac25693aca6cc9fb4f7cf373f5a4d1794ddeb1d6d14883a0bbb57c93e2ea9dfe7529d34c734d3865cbc3925787377194679e39d4f3bcbfdfdffdde7baeffe978e9b0b53b68aee4f5114446ee7e0a91d77728c2ff73cd9f3adfb47980814c3a94e7cf9e29802eb1fee9ce7fcb3f346f8902a5539b59a80b6690d821334ae81599283258c82091514266684cd518a9314e506bd218e59944802454b8f2dce2240acd99a932b3448e75b7573361cc7031e34367264bb7846ae9cbfc10087727fa392a273447e5029592ea95ca8425aa9c2a8425d4e4d8a78415a2cb2da1438ed1dc1250686e09159610a1cb3367664d8e758d7e224dc797e77b9507ef7699a2945e961394bb3babb5c4d402adb5b6f51d1d799ee77da0123238168bc502c33248d47cdff785621aaa300c43d145864c6e4c97dc981b4811a90c993269725796a47253544938e5d925cf1c1256f97bbd5e2ed707b57539765775d763f276edf57a1a0abdc80b052e2992952cf6f3b18d65d7cb45969d5d2f7a11776d0004654e7e91df359bd3b5a744fd53df3eb682711245d420319579f2cc21b194bfaf411ae2089f92658d4c91dc863379e88f993ea58f646fb3873ad17422d25503a6140441b0eb404ac37e2a1b6323d68269feb4286a68e84673b207c5f1c0650b6ee806fbf059285ca1a1cb910c6b80b0c28894125448e3240818c28881bd93652c833fa78d124d1efb7e3ffc22a2c2a2dffb224b8c84c9aec260d7c0bf5d03dfe6c511770d7c1f5d3560f0bdefbef5ae91f51fd3eb5d6f5fafafb5e2ff3049d3815f7c1c989c65933152d6d2ff75ff7bd768247faf3ad2a1492df8228b74cdd276745fbfd30192fef53dd2f53407b98fe1bb28146ce64f88dba8fd26f6a9929d448c09e104228022044a6eb8591923dc40c512282741b870b3b1fcfa6f2c63f9fbeffdebf7cd307b1924bbc29fa1eb83da759c2c5d1fbac6ef29d2ebedebbba7ddf0175fc7ec9a8e1924bf1e93b30c127b8d652c86032e2951e85127c231e759c0a58e4e4d86cf0ca01a4136724ca07bdfe12f3e1e813849dbf17ad7fb8b244dc7eb5d2f3e7e1ade7861fcf45d1fe42e9ab4f4bdbaf6bdbb9720c8a2ebc1b1a4a2ebf1fbb84435f017472117e98f47d01f9c99222529b56ddeaccbf54e9646b2f83ebac6d208f81f055d22b9012f83df8be3170a85a18fde7fdd3d2bc95b2271cc8c85a42ea4eefccefbde5a6bedd8fd57e91d67be351f7f20248e72796ffff8d77cdc86801f6e404c3098609b27ee7754f8dd66953c68ba77159e5d6bb5c8d2b2c6723a79e453f7af0e8e45a8132f6091ba8100245a21c92d4afdcf2ab96d9c3cfe3cd9dddd6df3c6d4949fdedf2a5926db346df3cb0c3be0d0020b69aa90e0e61d8881892928275015adb9f9571b758f34968281f83743a0f99e97324b82d50478f6f46bf4bb09124a9048e21dd076d0dc4fa90838229a6e309e708ea79c96264323104af67818597039a97edc5bab8571ca9e0b83257bcd6d399cc7df6b3ed70acf1c18353907264d76c70087279cdb323079fc9fc8ee55647f7a24fbcfdbc5e17eb8d7dc366f3e40d967d38fd9e37fb1c04f71b4881af953249f3d3ec4fe10223753124c0dfc89cc322dc1ec71929260d6dc568a6bbd78a0c1627349713ffc857818a910604833258c0a476efe7468da2cb0816a0616a4a07242959b0b691e7f3b0a09d235fff0072d83a9d277edb3bee669c96f8116775f330dfc993fdacf9fe9f44f28fefc6fe6369d991a4c75808272d45828000aca018e17040acad11a65744d072828878caee99c001494a3ba0f50508e4e04768606fbf74f1a4c7e4fc09f9b52dc45013fcee2fa4789ef00bbd6d4e22cce1659238b34c30eb4f0408482965c13dc00608001062835a03005092c13f0e7ee9f65abe405470a5bc7787e8c7777f77677f739e79cb66bf2b9bb539c03a624f8d700498061696607e17d2dd835259cb668eb5437f97efe9c550acc0e5080497a018c922f55d0ba4e160a46dec397a95cfa7c791a12c1bc5f53819b6eeb0b16d0eb22d697185eddd4fdf202686d8480c9fb12c28b6aa304abce0b950bb7618292f522a66b22291b445315fb5c2f40c428cbc60957cd332ff562830db69184a879e66d91c996c1b33c735e9a8cf182bb20cf1c5397a6a39c17125ab5b6d15bd33e422a83fb43ab6f83dab10d10b8976615cfeaecbf5667b3daacfa58ede0ea26ddbde4ee2362399420fb577f9c4398ec0f80d90a385aa2144186428ebbe71ca3e4625626835c823d54711221fb8fb886f1fbe76b8d5c8247fcf0042a2afbc70885bcdb498005971618476160030c3195fd5f50ba068a6c4b921cf75a5114237240a28814acd0326ad088b42c0e041031d0665d9c7e035145bf8b990a210ed29650c8675748e13f2dea06b98cf9e33459b2cf6af784420d165698400a28b00cdd3a888613ca4049e128055b6ecd25771637e46e6c05e32977d7a189ec5c6eeefe43e012f4e0831f1dcb6192fb865b67c1a2c504b93f160af58b2c4a60d142982a5d62b8cd15a4b0454c530aa098a212c20a2a2f9b2d5d56c8fd30cf9b8da68419d60cb14488352ac4503aaaa2ca85114849ba7954f610ac64bfb9b997146eb11c1d1933b848310410eff30222b530b1057785042c4c31bf6071f913c15f86b8050047e172ff2c6a05b9bf26040e412491fb5d3e51986822810a0a5640418559a6851c9916aca810a54c0d228aeea8163d27bb8e0d63ff99ffe766c8fe6128e4b52c4194d0821568aa9849e15542a353a8742cd86541eb10d58800000050009315000020100c088442a14012a7999c2a1f14800b6fa048645830964863390ec3280c8218638c310620408031863043434402c33265a1026506f4513121c7a4b673f8b98409390a73d8e814505117f4ffe9525ba2bb3a1b8a9fba21dbbc8c8ba3f675af286effc83c202b53932d838608ab452718eea39472dc0e6c84cbeb6de9ac22293bfd4976df66d57deb20d653ef227906692ebece0f394db01255f936e942f48a1259e5d3cb589836ce63d5e3aba085e759857f6d2b9107de50a4c1432ad0ba21c9cbb8cf9d26bec823ad3bab16abe382e9107ff39f42149106ced47ed2fd796b283526f93c0b7cb73bcd142298c72408d04ef2c5e47b1c05ded066983532f6ab14177a75d22ae04412fe56012fb3c534ac9587aa68d44f56638cf8c4581037a6be182d9dc56fae068eeb2c5f067afa0710c73e369a81de332a53e31a592f482b74cd87114ec47666cb5281272771c7182a48b9b8149cb7a6c49dd4fef640c9b77bcda36d18205ce527648f9085c3bde17c5dfdcf5f41c950273d2b41da66573631e21b503841c8b21d6e2d53388d5322b223069c83dfe33db156342717927192d4443f09a4ffd62cf9c49e62817472f37516dd4dd8ebf8ea01f021a8c14349e29199200d3aeeedea34624a50b8b0fe65b541cca8d5540697aadcf893c6017e17a0000bc453f00c03db00330d27a62cb06be78528a1c5a6bbc7cf360809e355c22542096e6e17e4fcd51b9cdcb5201bd158a0ff48c093b5c80d977b3c17e57378ca4d55e8a58d1dc0f1945c887c5d01e6de47e6108dcccf25c36fb1fb8b3e3bdcbbe3aa7ca8006c1523f93a5c1e2ef9d6962a569fd74b277fe783d1e9b2908e7b162f1f3cc1e9a8fda7fb1307c2df2a207d628e8cc4989e028e30c7e46b0204415d06816f5de33188e3f5b10390dd11e390015fb00e3aaeffb2ad56eb12329db9f4ff28c32d9da2666775a8e623e6795fa9f1cfa894f459547ca53748432fe36d823ee572a5c7996f7692e9d5d88f514d440d9dacf3fd6f0746d0a6d5e5af2c39effb1d0a4d9222773f5b8637bee4ab65e5e4c3909ac4f52697986dbd33c9a7262944da2e20c0e6cfe9cb595ab1e3794b3091e6a8d0b2efede981272fe325833b622df6a95fb9fad4ae90761417156a3eb2203fce571494d938789eae152b2bec5bb0cc16f6db16d880dd20e3d78120f174b0a1aa22607695b27d2bcfa0719865e1750026b8e08fcf1bd33f81bf81f130a33f77ecd8edaa44b30e281456b66d4fcbc61c01c0ed52b126ed6b88e31b509f2cdf3bd2abd8c45c1ac85bc716e51bc3e50dc7a816dd24e7cbba8fb7bd3bea9894338f65a3a5997ace1389898c3a35a60d54d52349ee96e6a734b5bdbc64636a3bc8c6e0310a1a744252fb1004a1cbae963e8e0c0e93ff430d11225da4bce2278343e1050037d5945d9d77677886703ced7560997df90629a285deba303bf7cb8195cdb55c095d060ef3ede2908cd6621202514842a493275ed281f15a9da3190f7c541768ad704e05c141dc2db29fce574ef56f69f058eb1da6c4b2fbd7522350de452c9720917d195b5978778af83ad09b64285c39b11646420c59c40678d7a854f527b135342529acaa9e2e9969d244d4c9af2438d7d9d06343549a66aabe34565d61d76d3a09551d6204774868c76d6a829fa22c149dc7750662fd6a91b4a86774c55bcaf94e90bddf53b9627ac4ec89980e57b96245782e04a83c612ce6a58cc2268332c38143286648626045cb15b50e024613abba246f6b5aaea409269cf9548ff00266a01926a912796eb07d34c261e482d90b455a90e3a4b5c395a6241a8b1b1069648eaaec39382dca9c4da704fb812eebb8a0e9e65553b5ceb348a468eb96f951e84379805ad915c1c5a5d85ae3b9e9047a9659ebbfd74221f56b2d8806a5b8926c2f455b1fb1f87055eeb5d57436c733ba13698dc3584df51dfc73f7c2bd69b8970d07e65cb86ceea8eced47659f0aa456c61764a9efbd549c3fa345dc7d1d00f5a668765a68b642b3c017a379391fc7746e5bf3ffed4db77b76abb7db37bfede9b6dedfea769466fbdcce1041fa0dbf7a0513d26dd74f520be670563af5a95c13d0deb881124a0ae306b353058cce81da04c9d5532275128d593000bb47238007fa03f3c35c346fc00dbe3fe3c2528a09ebf25aedc535652a5a9550be85f5715eb9c42aff4baf515314d0185a31174a8c88ee3499c043a70d5305e45f7b547aa49c064c68c50c89a30e2c99d4c0437376a9175a02140121ac2a14d11fee713e8ae2095bd8edec1b290e334be514a09cc9f902e5d8a8148e54d7d651ff19d139bfd32eaadf6d24d6c1ea8adc63a4d051191c77b48ecb7a8a13987f6c908ba0500a5b1549a93890e57eaf7a6df6a15ca0cbba09cc38adf746e021be79cdf26ae31dde90b6142bed4395d5686b286f1858cf2337f04593239da1b930c83a07e460548c23c576a611a5a4c56f529e293e17ed76a4019531914763a258335c3eac6ea287cb4f904d09eb86e587352b95e30f6b50a94d14f60fe7855526faffd8a63066cec0ba090e5d5a3dc9f78bfd3a1d2542132aa1fde31b41ff25f83162200e2363bd0396451d28e5ef8063971604080829467d03b16173d71b03612b2a5760a83be8675551f702cf0e905fab84f158022567865bd796ff45bbe3064a19ef7ae4ffa7c439e23aa1e3e02bdf5a1a5863e143edc961f24623ed31907ae911b0464aca2987deac33615407d7b80a3b17652a8b82988c9debb5172355dd404909a267e7b83eeb18e2ee012dc4cd741571a67c0873f6945f3c8703dd2e084115d260f890709e8728f2e0270e4c604041b3237e6112e2627fa51a9d6d4459e4113942064bda6d7f463673bce21a15014c9be3ea4bbd11b0651bc5287d9819197e67fe0f31317c95e28311859d5472b83be7d5a58c623994752cdb729b430d787c16bbe1b0ac2d5005c525565879760c02eba0945c6215722f82bac68a782a000cee1004e64c9a2ece2df0c6f099544fffe0b1019d0fcfc89d3956e3e02d18e8df68497823cb278a587fd843b9b15e7cd3f82661ba6531645d0caa20cb5e9b5d2b056fc28839f36e502375ba5deb954642df9501837524768e7719d436184389224c3f3dc24f07d082bcdb8376030e6323b9be8fcaf28720b2dcce0cd85fa0a0e20d44346bf51a662e0431bcd5e357499d486021dae0b814111aa81543017e2c2aefabe1d9608f579c4b20dc72b0f006f6163ae6813124354ed3db3fda0e9ec0a497adeb86914ef2a1e41cc5614a821463b1a0d4761441fa5b52d279ab126b472c53d8f08af0f5a50e380677b330352aaf6c849f0b140cc4cce6e959e5f96cf70626898daa603392e071cc5e430a619b21804896f9d9fff2eefb442ac9ec881d20652c466506986ed803e74b06aae10cc6f70d4afc63e78d3c7903a7a4f839768b5fbcf773c130a7a91596562363bd4eb609d9033792d839857378a48b8840f021b50cc23bc2d5a356e751f2ebb6ba2bd11e6116b0f804eee401aa5c2da7079dad966f8de9139bd130ba03d17d20878330fef7425867eda554d04e1f1d11d71f5511278bace513102aff660a7d4a314f952e593cddddd9af3ec88e4ea09c1953c943f2cd1498ae9e4a72d712569e0a56e64d8ee21d40153ec01cec9835035dc68c7a0a77cf63d9803b9a704a282013a6c1da38c18ccb70174ef9c1bf3d610e4a589a9c8e1738c538c6090e25ff0e4706f287b905867ed7bc876f8264703dc099b6b108af080f4bf67a934343b32d18720fa00ee176a9b7a46c64ff56d9848a3feed52779fa45cb152da0e0a3f007a8e43db8ab4673ff81aacc9f6598d8ca407fa9bd4a013188e11ff5e0faa1ad7c6c45685ff2379117999ca57343329a21a23321da8740d67d92feba2000c310e8f96aaed6f9b85ec581b5b2cdd1108468e16640154fda597fc92fc5b545eb6f53382822240dd5cd9d35e498e1fac9220574322670bd47bcfbf508e75db63b6332c15ff9765fa4c57b25a8ec3291eb07268ead32dcb2eb6696575972d1e049b422f65894b05d446589a4c24cc7703f5eaa17d746dcca77bb97ad48cfc27d21d8dcb35ab55967351154331a505a034c4efc6fb039a66bd6dbe34eb4757afbeee17c8f5a49b17cdf17ddf7a2bfb89c9d70b294de0d1e647008b4bdaf5ac9b8e1544876d26295a2ab0dda5cf96fadf79f29270042469aea52692383f94ff062ad9578f38f04820d9fb1c7bf1be958c55240c6114ca1f32f39318f8a7e76cacb86a372fac664a2ea4de4f2c7873029c57e8b7a26ab2d8a5a3384f43736f5db6d1d528b5a87fc67a66e1c8621c17117c2a39a22f4578bce443b483260522ef3d3f0091f400e7cf2ffa348dab6c22b2f17f1d9dce5025a42a19419b53f8d2c53f454061cc5f4d8501f0021951db94271f60fca45e2895263ee662b4690cb709b32e6aef1cbea5d226c681636c9166a203df36d3c12686c61e615286e95dd533baa14a9e41022969b38c8215a2e1243d681953c84daccb02fc39d018c531acfd49f6686e49e52f554368da6e850089389ecd16c3d579ce562cd6493428a4e36d3d967f81005be0b71ee1afb11d699c8a893654bf286fe6814402a3450f818c1d60a7fbf01cac2d28e684dd3542f1f951a3065248eed1ab4253d49c27a0fa99da312342c6242225d3de961c3a5643b2c498d0fdd682a4bf6504a8ae700a8c3cdca838e5a9375a6e54e77f79d37687507048f8ef875153b8e63ef7f49aa7c8623cc2c7dc8b532b87886071ed20abab520b2ad9d36d4dd567b2ae99548914635ec03eaec9e34df62aa1acbb7140d16aaa519d6611ab6bec925b051857a7f828142e6c8824b60ed4bff01eaa65794fc6a147e3befdd0017c4942b414e6c3ba8bd5ca2ecea52202f7303722ed082f1b2ba437f2074396cc1540c921b78c927d83d8cbf9bc017c96235c8acedecc8352788d535eeb38aaefc8642221e2aaf64ee7e99bb786036cd91372f98c88a774434514a181e8a380691625a106c5d5700e9b065aa53167b5f0edb2fb33b1804467ef0b8aa106d274330acdb999b598216045cfee3add4d066d6c14ccc5af0fe8526d2e119ec054214550afcde25c30284a25d91cf636ebc99b2810e09fbfbd1545b9d60672101a494ae3ac29f67cb31788edf904b446bd4ea109b2e6672e65d080c320439ecd9f85aaa0e470325e60dad834fdd13fbe22463c33bcc667239ddbf43bb6efe41c2f6aa54e61d7242f64bb48d9d914607bbe14b4016e8548b33e37a21c955369f18b4e2cd92ac5ea7f7a84328782b40a2f79fc7494876992680448e25085b128eb6109fa2cc17a1e8b6dd0bc4c9683b8d5e3938a19f85eba73a831d1067532d079d20829566bba4dc5f82fbcbd4292792726415a02053a6fa14d2e20179b8d6a5eb4b791ae1b7a24383eb01c64a232285765ebdab5d366b5102f432985dbf7a6d2186433b9c1887fb0c28bb0a416cc6cbd1e9e46b2b9e0ecc5f262270a3654937408b4710c9231e806c231afb70f9066dd78fc827fa1204027a797b7dda753f1099226a482165f0af0dbe831ee612fde006f66d124ba5f9878e16105df681e82e8fe170c03242211c46e240c188a804f7636b6bf799313c322708c34dc7f4b0f5cc11ab2cbdc91f84484fa896090ea150a6a89ee10f3e626f1c0e210b452b59e33b48e58ff4504b4f68f4a1cb21fe4d8e89f4333a8258527f5e62e5894de21df97bd68a718e3ccdde5d2846105c6bc1bb3ead06e6b60367f3b8a59711cfd1a4812a24897827e5c70776597b01e73f1f563c8327534e0d78026ad9ef06397a805b8c9b737aa20406a0eecba0fa9e53919bc33fe14d70057d69a063287fb9af50c4944133f28237124332c7894e831fb1d5c9574c690fa214e2774200419cc1d0d5a62cad9b59436755ea459e7078453954aac12b7887d8c7595debbe2be0851ef0eed7ca60762e4541a03fbcf34e256383da7b02b6c0695e7b60010a47a407d29d1eaf6e80205adb9a715da68b3e6b3aca67e0a5c3c67c4660c5995fc97a055e89c6924f917bf79f47374bf1a9040f8113183e8d34a0d7aa923625f10099dad82147a7b1f550efcdf170cea6df6b86bdc9f86ee9eca291773c98dcdb9077df83abffac533ecdbe4a5234e1ac729a259857503b0104967ae9fc84c69bc14e8e2f8f15dce23bd705cf64fef33eb0e0054a1fc952f08d20d2e830850c36b86047decf9dfca9734f6e3aa6b05c5fd4c87715a65655b6344e782373676ef18a1b7e4ca53b7c6718709a9ecb223bbd344ca5fe23f565f0ceab0e9d72714a5f743468aa5997409c0b423b459d023869990b66bb5ffd2dfc860330d50eefe965fb1788360de6a7474b5688ca7c5ef3ad55aa0caa795b27b02abe889b449610f7fff36ffeaa77964ee814ae2c19de26697272b0a4e3907c182c02b9c8ccbbbe7463e9d9777bf719522bdad2e1c48b963900b545f69081ab0f1803644c2c0da6ced0d24d6c342a37e1cb8a8ef66b99fdd21e0ea8d61fb82e309ffa96d1ea72d8619d54dceaf9fc3cd6ddac10bd96d5f300fb80841b746f0882edc8c2e4e45895642cc0d519b8f3802ecfcbd533b1c43cf253fdd97a8cf22f1da3b4e3c87db7aaa01ee796b7b68b3b5866e753ead501e77e7fae8113c97939967716ca6b8965c5939d5be42f5e7144f57f1d8738b22e66026daba8f1d2d7c23aba4ef71b7c69da894850c23a07ab718b71226c853114f27013a7c3ac38398029b2cd39ecb90877305da5e0a1152846d142d19f0825b64cf2c96b8e76291b1558f4b9d24a736e60bdc10c7de37f5a67c605ae4d83ab5b76b1145a94fb5ea56f37952499f103d0aac9a63b1956a85904ce55017637fe0c54ca29eff5e10b594055e94228512c62dc1e17ede3404ea62ac02d018b0000ba6f30a835431b2f0624f5508c33052800e18efa3a28bed1ef9f395dfd41acf460f436fdc59714613954dc2817c18045be1233ead8f8c2307effb163b71138918a3d9425528a209d1824323be9a95e8eb934654833113c53865f31abdd2d9f39cbe6199d30a2e01d118ed35d116c47da9318fab1bec80249e8c79cde7e79b9372aa800d53a3af77db2f41b8ebb197858579ee09011dff265a51ce09ba05ec0bafb293a3efe3721076f7c42e69148731c091500d4e6e2b8be41eed6b090fea795c39908b69be7be6e107a5fe1979f21290fa1dd5c849166b03be5215d3cabd72c6c5b317eb8c41760a21914ed3744140a9767bdc39f41901d5b6575e08404d2e90e9c89b10c6b28349466ec1c607e836c87aebf7501f2169ccaa065501ee4fe0bd771e5940ce29fff4626f69f84fa314fa1207516eece1f410eabbc44fc52f5d0524002b3ff0e53884ed5a32701bc7f5ae239d20205b3cf4f337b85f72c16647fd1aab3f0282a43f44c9209328049ec37ac6f79288828ad264b20bbad66dbfbfa515f3478304da589b2bc87f3e7a5171e8ecd6292fb5e3f5ca235d800a6bcd8e84129b072cbb614926bd77e37921222ca7bbc303a551908134f8be731517e6689036bc30b15e2527ae092fe99b1f7eb7549600bf42ea3093211bfe94dc101945d0a716f86eaaf0f433b5231e2c05ee251d3db21be694b3cc2bdd18ab9b0c4d9c07d0e219b04be7915cb226ac6eab88e03b058fb763a5b5b095b318d00e1cf399185489f21f1b97ef1f0c7a39aedb247fe57b6a42caadc988e07c96d89933e3afae496f89a62f2f34bc8a341491aec5fc1214ba6c5f413119669e1200ea87492c4f64a73f0a3ebfea912704d9755f19205b9efde0eacfea8c06abf2030818edde8cf040322f7b9f13afd37acbaf11d4b57ba7872063cc37a568219e5190b1cce9a2b8315a09365dbb025fd16607ab35b5a2d6255020b83452664561f6aa78fedce47208f8d2c390bfa1200e5dc3d98d3631a706274c1653193f118c612e2d9aba166ab990e3461eaf89671c8318df0b2973256fa42bb27793e0d175472f2de6251eb87adb04e359fb8d28be2d2223ab1296f081e19d8077cd4a8c103de70b2d23457be85a6946f5175dc580dbea7d4211ac3cf2d1a85350a34424eab79d1805e43eb297dd311ceef878a798dbc9431a0426b59dd5801208c9983294af7a3de9ca38b3ddf05379c94270310a1a4e8f5684afb36c1f62b80173d8fea6b21b3a35a61fc619a11a5ad8286a690dd45be8f55f41e863229e6b9998f70baccd21f2c4c0985c9d00ba7743a2d6e0414de8fc71072d4da6e01dadeea0c2ebaa215223985be2441e445505a7445138ba589f3de0fb2ad17a7f455086b4e31f365600574e40723ed45468a7a4f7e63b07f234707a3aa558726a990ce12bdc8a548351929ef08c22a4df8f5b6671cb39e49ccd6fd79a4f99e9d3de38a16bbe002e8a48c16ef57657544ac294d59721f748c43207a38e9a6ce89ad3134a9e34e329cf7aca082199e2f3642dadfa6c8b02dcb78b53bbc67ff22f69a5a9c148e728185aff253fe66b9bd2063978eb4f7cebb9d1021321f54992128ba02be065f59ae1bcffd0313dd9984a82748c2f349df868913827e81d7d695214f8cebeb9b990cdb2f35462d521796e9935dd176336b930aef4b0da18d3405a42473c27c424f74d1ba8f96d1298109e089ae5268e91dea199070011d721fb00350318448df466097ea9cc18464380587644d9b2b06e48c72287e638c87c585e9587703c462d916af58199c96d311cb344d0658b0a63e4ea59d41dacfc3b22004692eb1ab9cc3be6758913c07bf19e6c065072bd89e97ffcb82729385f496e029a1ba3ad317f81060c4263f39d83c1ea1c51561068f57f0231bbc52943309c5d3044332d564c97b7e9615b5184c87f9ea35a6103c4e094adac2f40a3c5e07960372e8099155b39f14508f30b9d7bcd8a324a52f39a4362eb43ed0962de84de6922b43d89c21186c460812e56a8a7e88a1f0629d1351d06e89dc08bd4a70b3a8c58230f6de87126529cb92930e99a48ca5875c69abfea4ef29470464feedcc910db800313cc052994685083e25716b426c77fa492319a8d18a5c8c988b0892a05cbbc581f235507142c45590b060b4aec1aef8e6e10342f6e491f63675f99e41bd19ce0b736c71553461efe88d8689a8de56996058bb6eb4579612b3daa0d985d1b212a5d3b52b21079df42cfb027063695d9e1010816a3cf04168042c8446260b402daf350ff4d85d7334fbb588f6ab5940b2e734ce370341906b5a8ee8c1209dc645eff9124886d7d5f99277b3549ddb53d263d3a6450e9075500f1d69e350f4e925a7ef1539e4fc416a10abdc42afcb0fafccd3d7b4aaa3d81cfd345d7f8028272d42f6452ac5a38aa74521075def9b7bd1d1df2bec134ff2acf286e2612ec5e52e01175beb8c2d262298b80a8464ebedabcf2d258e2cfce71dfe1706a56ac92c912ecfa23c813439e18f03174f1841df017ef3cf78fd93b1a3a909cdc738ce5288a35286ee4a33482e6affbb6b0acf6690efdeec2de0eb2d30a74cccae98e821a1588e66cac2ce44001a99001ef8d822b5b2944c40a6d7e910773f1c85a62cf92592d2e2f9bd0a1962bf6c04d280904d8357cd58cbee03ae67cd054b33e6a5c5bc6d96491598d07bfd8771e9bf3c9aec0813804f596df27fe45981b1980527f4e52d36d09c7acf8213909424bde1aa9b13a880f3b997e3ba3ef4d89c8909269ff1ed3f67d80d73a511ea51e9e103412867f48e7671d0efb9911cb431bb49ef530a307c9a290dc1523040aeb1459f2ad1871d5f73674100f2ca61c5299f466bc843208de21eac1f86367abf1c3d26f8450402bcfd1d36b16f0d82b1c77cc2810e857fde0bbe7ba449f02eb935351c5de40319ba129ba092156ffc6c8971ca6c3e3c010e3f58477e80fa7c9375a3339e91ccde3a6e5e18d5b8376fbc7ddef0ed933447c914d109e7b0b73ebf7aa7f93f0feae89be6568e09073c20ba0f85246253482a283519aa9f57fdbc4450ca96e40c73e3bb01ac3e9e8f05c9e654a487ed2ac8d6fb159599897b3fd939018bdefa4fdfcbb8b4584845e21ac8d6162af82b5513b054488941fe4495fb62fde5879decbe4f046e37397335ec9350248d63704ec0fde710522813a4fa7b4f420f6ba4f1ebd493149a9ad4c061859dbe0a564abda69d3f8090812975481dff1db1fd5799040f82c963631726f07466700b740aec80e3b963239e34444316d70ecbd73635a0c6acfa903c5b25b623a7d910e23f573c523548d594c5723ad939f19a8ec18db7497bc38bf1ef0585710353f9fedf5d2e8c645d6e240de115c31b215f60679b93a54fa98029accf0bcc38db5fdab1548e7866075f609458f8ac2df95de90a9a4f9f80a98baef87e8175fcc76cfd9b853da4cd5e7afac5aad7b9089015b9f4b86a5a26c94cbddcb7a4fb54afff1966b1c084ca0294390b21469e6d7b033c3a6b5fba15b01cbb5897643b90554f58a1552de81b1cebfd438e4cef143ef67a820afe07625ac52408738a4d89744bb9483105b30bc2fdee6c76ba4f30fce3011d58a34a10802db74d5c1b3efcb1c539bf417da4b8011b43ba5d2cb043c7c0a6036aee6e5fa8d0a85ada94337e37949d34593788ab27ae9b5f67264c01deec1edc7f0aab8ae616e48d3b0d567c8093c182e1d858a0605f6d409facc6e9556342812242641bf9d2292f364f3e1fb89bc29eb6a63b1cf116b84222632b0d717f3e20d5117eaae57151ed7a15ae710bcd6b9529b13dd2ec7a891d481d77082e02a3b75fda31ec0be93a63d7c907331ef102f98a7e391e08dc0512fb32471be2c4933e68770ddf603e3f7e07f658d2d9353545ab76aea76d10fe624b23259cb07e0c4969ebb30cb87f07ab0257ddc40474daad91c8e4ae0d76451f18db4e88a76733801722721f4032864a0ff814095e0e606c41eb26693ec442318de4e9cb76bb90a4b65697b2983924bc2db7ceab0ca5fa83cdf0723358de0a971bc3c24db0b41d2ccad75caaeeffc5879ebc73b3100fdc6d0b502ae32d7645b6f7d9445702df64a2c6781c04b82eb63494c73e5f55b4c5245c4c9d64683e4c32340bd5f32d97e89c8d20fd9c75ad4e85cf3ce958f613e1bb63d39539af74f95145ed991d35de9e0a6c679f80d9014cda23a3c691680f04a12b89cd11cac78c6ac8f34046aaf5bd1eeaed87b8ff47bd0ee479da3557449e2b890757f7fe5cad2bc5e5c252912ca1c6db1ccf0947849123b8976ebe385797705176697bcdf452a3f2695ae2279a314a23fb709dc0d1103c7609f01c429a66205368596c613d47aeaae69f5c12141d74448cae0b42ac9009801d30543c602eb5646f5608aa9f42fc5da7f65bced00eab281da6cd0be0e20d781ddfac008241c98e99f1f6eac7d8ff5bf8713769a127fe6d9a142d839be142b6fc9356c43f1ef1f4a250316beca5857e76c9941e58df9ab8ebfc4885d4babcb42a27318258422dd566415c6988654299ed402477877d63894f4eccac949cf1be492fe225f9217d1b36a201dd1ae1b72351ffda61656226e103913c6ae670362182b199e9bca00fa153b963dfb8cb603c4824954e1c7894de6bdb66d99821aa5ea635f68aa6c6304de964da1f8ee394c34ecc88a929eae09337747af1ad9b08d9c3490102a1a4574c25d08db9e288c9b1a923e44593e782bf9c5327651c26eaa287ff16e8dcbbd3dbcf5ef6b5c75eb15742f9c4d13659a6dfc6564ce39cb82e58b4b41cfe7bdec3c9106992c238d7c79d0522b8ad04bfe8fe3509a117c575fc4bedd1be2209286ee7eb9223480712033f60f57b4a693ccf59bb0d3a31b1dc0c0833067507b749050eb5b1dfe670e38e9443e9a536342376dd53b11c2afd2a9318562e831bc33f01901800d091a7442b7c5584d28c749204d63179fb2d73dd1e2df3c80c254693eecc29b73452507f40e05e47a21b133c0732b2e4f72574f8f6e548507c212c601f4d0dfe27de047e18a0a0c7744f6b26021663f61f6ee2cd79d0c938d241f2f04e6a0015708d19accf29bb1d8304a8d14455b6eb85e942f4eaa014abaf0e702ee59d6fc720de963bbae7d6b7fba56920c146126fc9a2ee4c1a6d5e5821a39c0a36d575b7d0ee82bb0ebbfed5e5342cc67db200ae2be4c2f708fb498ab6b9786293b36a0c177d9765dcf1d660e6c11313b5967a83bd8c99d47b88477d107ab715d975770c9276383c694003d48ebb3949b8ade38b9d9f6bcba22b8f79dbb5fbe2ec992e003c9dd4e811f5a5ab7d7fcf3b01b8896719f3099dcb9fce3714c050430cd04413298843c07770a7006105d7b8cf4ea4d46898545765173d89138a4f8056e9c85761819209903de9d600f212c077433ec97f04c85c836104986711e030e4e675790b698b62321c5ac01d3f78d3fed0ef01c08a766553c1afc52f01191b7eb5ef005aa07d8b33b20e239b03d89debd825e3fc1d0178003a42372143da68431d80d863e7c21f5f5efb769cd67d272d36df36fc0870d3922acb89277454c309d8487534ea67368395bf810bf653d6173774bea7cc70317ea17e0a5e2d223a0aa9260b08ca1904029b3d21f6d47ef68614186054aa738abb9c7087fd93f79fa6b7f6d76908e3db3f9927d39fa5df81522a856b78dfa74be5db0e3feb8dbcc15c62445ac4118687a825c91cf9b58661b84929bad8da814217324696212e73ca6c5b65c113d124a9458762cf53b3ca5842f43a142d969dd3b0d7296ceea6d6924843b1588b0fb4053ea23af4d591a5f00a3259ca4f1bb66461dbae2e1de5b7108b7e40a38cb6d9517553f4cbe732574cc55f0b9aa88c43f5710b466851d0ea29f0cac2bd7f8f94e4cbf599569c8aef66fd8e6b4abdc33b1032310cf705629c1b72c7d816fec4c2dc5a791422bab91ffef88af64a10ffb636c36d88f7632031299547f7eb1432ceb60714fd0f5ec42e73e1e2a065a133b40e3d5feab4e7d3b8504125e6004e0eeba09a36cad5c5db9cbb37f577eb5cdf7efec8214b9461c25b2f1c4b27b5bac4798ad1bc595700257f1837a4a70c8c69233db6c0ad7cd4839625e57a75aaf8ec70d3317efb3444871291df28a26c981c046af9ea66b61cb850174d6a55bed6ebdfdac701f5a8ec2d32c97992eb9424b30c748c2f40e76c690eead13c6355c9b64376d5e2344e28a82b27a69925c7724ef6b59be7285c44191146b9b4317561577afe35c076cdd5d6e7d7a8b01701e0534c584de24a50cd7e01c3baf59fde666223c1a1e6d9d355eb58b364e1d57b45c82429831b1c1f597f2daea336bfc33dca2b4985c1665c450d7c344f5da506006a875235af3891ca92be7d2a7918782da8ae1a9db5e6d4dc61f846582b50ee2a3476713906b5065a9d31baa052170697c46b0a6fb2b8d6357831536043e9f6033b8c2f7e2acce88fc8debecc8196823957a14be522d3e27c7c9126fdf27bf85092ee68aed6775159ec6f43be1b56e16263a44cb31a03ac55f45771195714a54f3d702da2555dff72646c1d5c1d508beec230406137fd3fae1647f6b488c2165424b24bdef99d0528ff76ceeca8a922d19c98af4854d5adf2362e5d42a3036241820d5f0c4a177171edd711355a16d254c35d321adea9bc188b970e5bbc7a342267d89cfce9ec8c5e9a97b6586cfbe0304d731b66ad684e350262568dc5c14c4760a54524fec844968d680048001a9edbe06fa3340697c4a404202bc1ecce722c96a268490436d89d75d560573a0121489ec3a59bc45f824ab26cc08dd94cdcf2e2325ea22fe75608cc3086741173b85359b464076808d149222cba2a89be9b867801dbcea6a30b93df456911b52049068befcca0de60de16c76e156541645fb4537fbce5f632be2d495818751d49a864753bf93bce680e870026d619e0794756625bc72c86c3361ad7a166053f020e263aada5395a891e8e12e2845839fc266443c7a666b5b5d1a7ab302cef3e272be867d44b78629f1bf44323f8be0fca42ef67e85a2faf59ce67f8da79279d61f820ec17c559c65f3ace321edb1d94c1f12e67191dd0be2ba3626f36abb7144d43dd92935ee6f3478b60a4d7a1e4782e6aaa23ba6430f2f18feceb245568e90b81c755cd0822b2348853887a9ad4051a57dd77180c5fc5c6286d63f894491ae9e7fe4bb29632fcd512fbee9016f9537f62d0c5761438ff6ba984fd9691408c3de0535fc7779ded33687cbb8ef3083a343788550ac95285f8b05a2568dcf6077fb14a87ef410956ddd1a17258e98e31496ba24497ff8275692861fc3a7d2a768629e9e18bac35afe16583185d9d2d34e912086d777972e83d252833952988484424edc2e7d6cf4c58cee4f96016824d520be51f66a44a12b5442f54d4e423a2a6d3f30c7c80b36ffa946440a9f18f4903cfc661ae026ed80c38fb1df40ef2034ff6ea5d2f861d1d010bf3a45b959d14b142302a8180fd955af8e789e586ed0114e313e8d7435b9a414955eebc062d872d76bf8e64810c929fc1820a91c614e4a209b5eb98ec6248c052f4179e0627527e8021ffddad748f6c3d15e7835e38ec5eb1b9f8e255cb71b3d1c7def62d0e256f0ff618e895acd8d4f5278aa3f5372acf0376c3207dfea11403380cba599ba8726cbcc1ed071fa98f9ccb9786e206a2ef8a53931f96d2c6068ef379ec0a55da3a2fabe5b655052c663d4938fa5d7d990a78d639a3ae0f8040f744e7a2cdea5d0a79b633a9a7f137587e77805c84c197cb32adf2e5d20a6eee6af5aab94b27f1e32f25b99df760d177cf170b31f617f3a16eb50ff9090f7f68eba79503318a29b63d3cf62953a3c6ad70a6002fdadf41bf3b802072c141642556bd21007a1b34f1eb704eb790bf8adbbf2f3795dfd582df2aa6e0d5e19d7ddd055d8f310e1213a037fbb69a2bb352ef5386012389cdba8295a0ca3101beab3cc8016140ddda061a7530b9afe1cf2392483f7697cee7d5df9dd8298f249731a01f457c9f81ce3a6e5a5f56ff4b10f99c490b2d0ac6fe68012699316524fb31f0349adb61a75920850cf490f529a9a61cc4bf4eb95b3a8dc1bda92e9a93258bbae3e549e151a0899d680d365c19622571df8c25068dd840c9409aa8a14e26629b5e13bfd2d74b741da7e5a56fc57423bf338ba1e8ab4e8a0c3256750250b0c91403eecf4843328517fb0923c810808a6e8df410c2a3b9279ede94f510cd2d30ef31bedfe116a8da088aa477e0fac922498cb652cd2d2ed58f2d198c11d830bcb63ede9909ed7ce507bdb8b85ce686e509eca5ed163b184ad41a61ac1253ecfc37a203f10e2cf9eeb85f671465ca6734d7a5a0eb45fc4d23bae5625130e69807d2c686d118b8b39246ce3bb3b935bd5bccb4b72ed484c2a8c66403727c15382bef1e4b40162028179881c0c8a50b8a83ea6a90c46b2257431093a004e2ea2318254cfb69d523d3ca39c1bfefa679ab4c895e08ad4fb7e72f869eff2a24065d38d48b05d06f7330595a9930641d4984f9c5a48d4218ac5265ba35b7de23604c27c1d042c18e34ca04bf6f24ce5e85a9364e88d0be101221356d4d4c45eac030ee12111429d35382349c56c871ea2df74756122f0a18dff8cb53d1ec46fa0d5400132337f1aaa87e636b0edc099de90ffe01aa42f988880dfe01340f3a5f136d1dba9fab9038b2588af3006e92295132f48e8342eeb0f300ac289e9d7577213f235c533d5d5fe28648a73e0fba36d7a45eafcb05132d7c1e10dd440f0ce8fc4cea79f561288332b798a180b585a67de138653ed5101b699323cbdfc13229a5e36f861593af8b5a785aeeff5f9b2f4428492074b691bf76f2c720e3222443b7567071c5d848b10c295bd5a86a310fe7bc5250ea562c0e3188d9457f8d2e88b20e9aa30a58c85508979c891517d0d48c3c6193dbf68d0564ce022b6c99373bbe39ba3f995bf489cd0258258bb9b4e170d0f4c2396f46c09f946beb053a03d199d1b936d65f6d4109c3c3744dcb24fc6438b4c443d2027157362e222b28fa823188ce0a7f8afa113d2a29ecccc458de3b03499f27d1148f5515470ae6569582ef2a790372fab4478242981605a353037ac4d31962b59d8e9b9b8779fced4eb6045165668e3320d202cf7ae554015fe61fbb2d9b06a72805c6019a94c31c905da6a6770fe62f4445b58046846bc9ed9e2531e3e31f4d4430b03e1d49054658e72925fb63a093bc20dc4dd3a1c98b00e4e5d6ac92e576ed73174f1a77a26102abaab7286b9f6c982bfe1679b49f43e00516af6be5f89d543ed8e93fcf289067c6214564bb6c0935af0c9d6d8e8b585c662ad8e1fe268f3cd8728accfff05efe44496d363aeb248df6af1b1022d8b91cddd2c8ee6277fa42f8e5757e157a4fa3e8962e759a1f4ed53154afbe1cfb013d514dba9de718e6c443f48cd215678ef823314872294b16a3a4af587484aa68d345bf6c9186ab1c415d0969421cdfbb3c9b0390202d32690ca3a80f0d4bbec7915fb761c98c7acb9133a0bd731f4fd38a8308e4af3066b88052c33e1d04369103a60d1f00d80a292ad6ce3cd29d63de5420bad40d4ca1a8c6bce50bc604157da5538518ab3b6ab800795327b8ae884364cbe22c6cacafa2d3b7f9d6e90cb3bc82a14d829d1d89193228daf4d4475cbe1a794c59059172e88463203413ca537e47396623300a0f57e45472c518af90f9d894410c063957cd5aa3d22d3629014b5e39d675e6e414dba8dc7bec925717b340c240f83313e42f38f748248012a12a5fe54111186dc42db60ac30061553232726c8e4faf6099a0df5af77d19968bdb6acafbb889c8b2db37f5f1e865566cda57506130fa0f7959fcd07903618868bacb624a3693077642468b1c271c0556e7b94bb3c529f4b482666b3a6c66116039fad2e4be78268aae013bc7159ec38c3852b58209790c390e0b709d8c726940832b2da52133581fa04217a89016a7519b118c11045f1c559056aec361614803e5af39b930ce528eb49a01c1854e53c0a72b38b62a0ba19f2c37eaefc21a3394dfc91bc4c208422fec24fedbb69e7d19ee10ccc02b2f73e62853a03c3a3db0fcd9f13c9bf58c2935654eaf0e5b66df095647cb74094c4e1f1e5e350d2e0d39091cc0225b88a7da3d1fa71d4009da4a5f4aac27866c33b8940bca355a8b8ff0c352b5eb2cd0c7dd9f439c06d82ffd9ebc696388b9f2126368e0b23563aa5b407d9fb724ca06f5e38b728a69fcb11be5625a215b20cb8e0d5d8f20ec29e29682b2fd104f3ba3df26fefcb0e0579c9bbdeb2111c2bf483221f07ff796936745052f990b6bf157a12dde3c586efa8d6ab04f8aea14ae217b0700f740aab02480f2536663f2ffe394bbf0187008259de133590033f7b90a15a7d7e828e999ce3a7968c27f513bdbc28782c40e88eb69ff7512e56ae29d5c4ddab16ac1f129bc9797af3b02253ff24b973a63274493283d7d43851802e1c0616d14716572b4468d45be4bdd20928cdd0c5d1ae47637b00c154291f9c3578e9fad1dee89fd55b394b76dcfa0bc4e817c7d309530b46dbb5617a154d9fb3891fd6d11578d0ed7f3e6c9b2f69a6420da5cfe506d4d4573b755e705e8263dbbcd03723fff9e5a4c645e5f339d8c2604773118b86d7a46745cdbf1d9aa40ee8f662cbda681191a2ef8426eaf9304830f8426d359059eb05e7267f3fd6891ea0bda34008047557b8e0ec5a41d2031c0aa500d53744c8919b2ca5b8049fcc430cf71bd2d7b6b12a015173dfd04b8fe1a0cb2ff969c17d3ddb1405a026aa7a55e5bee444e534a5028a5d89d05524a1928adcfe6884e24ca43affc9535729aa41404a492e6060771f4f9cf772d935087b5a002440b46f890a7f24eb35d99cbdd4ad9422f56c6e871738a09a98c4dc5ca09a7084eb7c14653b96593eeeda00d15c60eda9d1441cdc7c3bbe1ff2d7b8b7b4278b78da6a8524e1fa8b66e4472a8d1194516d9f03c83ee49e9b1bd295ae44dcd55b294d235437c815fd75c245efec96424a26cad8594cbc582164504d082224885523247dd1feef2d54b345555107a363f03392f63ed63646d2bf7b99d6bc19c9d96169d615df0ab99f9a2e7e3d212929b077b59b8f9120efe46e78de48b89bf21351f8396b7d6d9243474deb25993935d60a21065a706cd157466f123da61aeba1aa3ce6bab1e4fd36cf5b11f10804fa9f6d6efe5292487ba021e1e88b4a19555f0f0b20f5fd003030c8677a107fa21a17e34cd462ec2eebee30fde2b9475ace3f0ab3ddec2a1090f5fc12838302a663bc6bfabfd6afa51a48c554be02037e6787b17726ccad0a9550bb2aeda117a3b8469e2b9c5b914f0f657cb00cfded0d97ebae8c6fcdf3e25d58022e8f237f0a2a5b8d0b23dd8819e646109f880e4c57b281442e41318f6dcdc1fb4e9467fbcb319deb10adc6c9047088c98c7ffb33e98c311f3079fbd6084407802fab277751e4d500438f7ad51890ea14489fbcd5ae67acb3648c69c7dca588e5f133952a2d484fff5265d8429f244a7386eb89ea67577a646d4936359d5350aebc84e6dc2ea76fc1171886cedcf1401bfa6bc441fc971c7ecb0e7543846a2a86bd5630554a4619cb53d4e88feeea190eeaf9821b52967c7e9630ae672b20e9d6dbc9ace9af222a644c75d6057e6139f2b2ae5618773d03d44d84d79965bf78b890350825151409b6c069863f46f9a7b4a4c11ea40b312b83b660458d8e4175d59d9b2215c09418f4f2a8112041efefb4044587b87c637d4242cb2db0d2d1acca2432b5ad1e62b9927020dcb82286164a217bff97f92e4bb1b7f9b0cafa337b074876c59876cf6de82e2fc3a52b14fb638a091e7e21e7e5c1527d2dfcd2d3d9ae2b76e45641b9ada50e7aca4969ada0e64968880a422012ac8f82c1ac963d2cc5e0b4ad110038a2903fa2c515afd28ceb28cd27ddd8bc205a5c6c238ddde85ebde5f8fee3daff3999cde635dddd69d6e4d8f6d0528735791a59d02ae8486a03ac791b09c0248774715d23ae645c0952c217ae1b5393e23e5a8b15ee788cbbaebc0c37665dc53ba60fb8c8159c6a9635491d4a957dd635b27da3ecbefdebdae6263cee35c11a2e5b7d3f5d7a7ab8774cc3d2bd64ebdbf1234c8dccd7c0b085a74b1b507207f68af4ef94503a51100300377325e5dabff9b8543bbc714d81ed7b0cb4eea357239d3b688b4f61bd78b051db6f1b5e02078bf89add250e069c45afcc7f7902851994014dbb7b16f492c6871110e92355fbcdecd5435f67492cd6a599c6627c710ce532f691c179aa17a6cea647cfb73f181c8641fd0f4b857208f02c49042c58c217ece512f49a1fa9936999d0482dbd03f470d85c1235d053fef52a87a5220032189d520398fad1aedd8b09e8bfe9ef97f571bebccceedf50c95e536fe8b42cf4bc90dd8ae4894f37b4922ee1426bae64b439ff08677231bcc9c765c034712542671375575d4728c04ccfe797413ce6abfcb49723e744bd029c115940c046509707e3bdada3e7469cc2be53853703231b05ce03c62a03f4c3c076d69024dfdec12d15816eabbbb31ab09c386104ac664cf0d17f08f25ce93ff454926bb886b0adf32872096442119ddb83c157f84eb2023380c51e6c78d12835f3fea35dee9334f8e6a6c4b961fe04094c0d582e7de663cc6743df45af3410ebbbdf1630033fde9ae06897f872f48ae7d9e92425a43b4cb6b3cc83c8c16f7de804dd0b8874f37ff35add50c9cc4aa8d0051b5a2af04e4802c66fb70a91c988dab89ed9e1cd119c3f8139a1ced442bafd599c4467a3748b07f228daf70945a5d9bdecf1ad87a24078753129dfdc39dcb70d497f9b0d6da74fed924b88e4cbbffd59ee7ef6bedb39b8c64688919df655350a5376e1474431bbe0138785fcb0d4a19ad538138ab8a0975e39ae683f7ad68fc7a00c95fa25be6b74e20f691ebc3d85f123196dddf4749e71edc233ba8cdc651354b6270d3c8e01013a0b30a633c550d98c4a84d9f3a8a921a102dcf301e869a07612586517e201ab5fd7d67c0ad848c7c4e963c78aee3919b3b80fe26e2114ff114d38b4dbb304c3b8c1f7770ca7ace04c50de1469fe04877a4ab0f625dcd288155878c4db02752a0624823073b1608c224844a0453118ad576d55e3e361781ec280ca02d3450065ccaad61da1c0f0235a0e071b407a1077ade82d01e3f397098bdb4f4049b42f997ffc3a268bf80e004d9feee4aa2b55be9f8502d4e9fbab528261e19a725ad52125d580d8e28989a7d4e768f8fdf63daa415a0201c92c6ed485ed16051ea146860b7709e301107e992f728f77deddbdd27241114416385f990593257a008b007c6406360772e3bd158d2d6c984ef876968d45b26e9377b3adca75b01255965e459bb75b43f4b3c519338468585ec94dcf35cc95adc4329d372190f88d31bd4fd305df05875777e25c73cdd0a384b3dec1e865c65cd0fe3cd36e9459a20946cbef517e7dac71c57d7d8756cd2ecb2eef1a1d9ffb26eccc4cdace72a016ee81d55ce48bae7d8d06e98dddbacdecfee46037ca857882513913cddde256608eac0007d85328814b133b8cfa0cf80de1bdc64c09e80f32e697f2c40fe39ba99535a3674c453870fd3bd57d6e944a9f6229340835937e924830f5a7e12815500ee9db7d28dd962424aed65cf277be0478ed2d894a339853a502d2472163fc62dee9b44a00d381b8d6a17ee1b75110e86769f56b86f9d27947385bb9660e0ad441de693bb1617d296ad0efffad05de33ac51b68a08975e2b7d2f14177ccd4a77a1642929be3e819e48e5194777e944e2a33fb6dd378c84d3feaf8de73a76668204373489deab9d74f839d4022d9adc63b08b2b7db8d07285a37c13214ac4a0311ca708bf70010e3afd5c18a7cf4f0b476f91ef9802f3d74016ce87d0d180ef6ab48ecee1b1a72a6a13628db53ce6f56332bfc9665543956962145013fea9912f5b287200845b3c2620be4e07c136ed18b28e63e80961470fb56795d584d7c2507dad310a282d77f31ae18a143d0bc65e45a2c3d82e67522087977248477bb417538161638685cf8e082a235ce49f2d4d2198df0565d48095ba949bf3e6548b1f47bffa336c38f071eb89818412b1a352512bccc31f4533c5c2ab38a2eb5e0a26c5034f3f48fd5d97b0333b083f4059de47045c1ce706c109d9e5ff00dbb3a9339cdbfaa1ef51e6848f316461dbcafb8c5584e25c7850a1c94926e3a95e3405cfe40a2f7ae612db11a26473e808e75b4f104502edf7e19e464e4864abc33c5cb96f956c51717695e420ae9d58fd662286cf567566adbd0d7b8b4e3c73418de38c84fdbb668d56ea047d2ee6ff61aae474a1d847cd5e813a1d463b01ede02813b5e4c7531d85f9f89defa74d3ffdf0c2942e5c8015d124211c61dbb4c213072d2f26ab035d47681dbcf6257f820d0b63c4a3dcded92abec68cb11851552f70b4e72cc208bb8207a078a5ea42d4d8b8907342f3188e604864989e1da24c03485c8d015ef10d3763fbc29a0924e66478bce8c1656b6f99e9567e42a161ad5d93890a74d974a0c5d193bebaeb3831c1a041639ae6972d9b1c914b421fac7db4217262d300f72098617350bb0ee73dd252561f724dcfe3c6def0cf5574c6c66cd6feddc608682e7de1bd76cd4e7d398893ff66ba5e7c59087db4214d418693218d8b639002c18846c8ed20cb1c632e857a2a0eed467fffddab8d95b6cdd2844edf7a7a293221b3dc98ffc18519f27a8ba2736e7d6cacd4e573a577369a906746612d16f00f1d999cdb8cbac7da4a2a074eb7dbe7f96f11d2e0f1092b1b6679f8b150c0e60e64bfd6f20f25f9f3a49b605c31a037c906f528ae3b4e84644b442f6ad318ed17ba4e2e51426736cb4c33d31e1b6778bb9b3f708f5a560709c17a170b83b8466b2ca38da13a8f372f34cfd09019db5d15df0d570539379a78db0ee61fa4de0d1ccb894add1a8e9b9d507e7041df26f4106b1249e0201804561847900e481c1bf34c484cd0c830656ba4ef0b4462978a78b103d2674c3160e775c2862277511a2bbf0b07d3043f99a48ee21a46b473297db317ed870f8b475cf25366e0dcb403e220239edb151c59d7e3816076dc7a5b211440c5d6a1d28a6705061ec6c4c0520f77fe1a13d34cb8749e0b9b4dee2d994310aa8f2f97ae2141bf06ffedb516e4502987550b078b76abdae1e5c2326e84d6483f12347ef8589bc41abd4ffa498b5c180a7052d656e67e56b033de7a9a3f948f72a9b4403f3a0b869b94cbb4620e1e81eb6790f7c689a19b0df955e87be5bf7b9e026b53f6d20f6ef69504955eae21fcae90accab8adf99ecef2a033fe82338ca1b538e2606c7cfe35b0950a279e1b3c419cd87402b7a4306a67f492a543db981e29224540dd69975826cb5c6b75df2d46a0e4dce2a7f24c5e9d5245672a061068d9e3006ecf7bedd96aafc964393d612230f1ff4f9aa22e0489ba9fd8f327f2b46e33b110484825f81c7fd5b544becf2b3bc1e9d4de6b651ae62613afd27cbe614ac5d208e1986849f4c318e656c712680ccd45763587dae28e724ec75f21e14e3aa489f4630ced5e341d3c1a4a8c53a9d737ad63077491e0cad403c34cb995516775a423a3782df1ba69f8b20a69f8ef1dc48cde4004f8972c206bbf607b27ecf256bec69e3a1d702403237549e8947745675eac9ecfd5b85372322ba42eeee6f2e50c60dc9dc3fdaa09f11ea8f293d937069957141932a81d424b52ad5641c93f6881e0168e42452be648d2385f82b23085c93088f5c3cd68109c2324bd7e6aa3539ca94e130229581141a3d84fc276fb21675ced2a40c99dfd826a86be694792b9041b68e3401425d739f2b76fd51fdc07cfbfe994b0d0dd7190659685036fbb1d7914d73bc8be72557fce2bcfc38f96a5972a711fdf08d7bfcf95670e04dcce0b69e009401229135e341547a05524133b22a7c8b0472ee29665c93b5af71c65586b20a710c8f6098ad9960c592dfc3af7e1c8b3aa49c1482541e84895791f1c258b89c81c40d9c1920a1185a9e3640a8119bb0db3aac245e22937719265dfe05e1dbd829aa664e12ce0b42525264bc80eb96c125bf46b07b6bdfac2011d8fe519d6785df5e95f1365e5d092c749df97e4ce7377bec11834c25c07331276d34a9f39ba0d073fb01ecc9928bdeab1996c7bc2501162fd913cc9528d6f212fe0eb1f25273f478d946e5a523c34b43aa3b2da98f957de2c80cbbe208f951040adbad8b90b4dfd7bad51f3e5639564960018ba2a48b1d30d816047431bb761e645d1b82356e01f73fc2adc870ab7c2bf9472684ed0d506ea0b795af5a1130c70711aae59c4a1e2ffec6a936e559d64316aad56f139102ea7cbf6fb2b47889a1d1c1d7000227d5cb26fbc85856caba5284684230de53c9945556b3c2803b9ee208714ed6a2edf0f2b8fe2f749cf992077ab8a5ee21ec5fe86c6488ae1292d1cb3249a20a628bf369e1da2a22f280024b32b777f8994cb1ad60c5aa4dc2e270d3a3a032352ec59a29233e8443a9e0997db439f691fe781060028baa95cd81be8f2df975073bb8ba5a2cd30575e40a85e4bab1c24a857f97e869e6fec53f2b56f339fb7be166869b203301f5013237ee087f4991c29943c8fd272c9a161f23808c4feb9d659a2dc01e9ab1cff44e9cd95e3429f9b7b528c8e670876e018c203d34c3cc846860c69507f0a17225711788fc1d54f7564aa637604404dfcdb624fddbbfcfdf4de0f8dc5109db89e5027e986cefc41e189c56b27c08cbbf6b5f864afcc512f19598ceb259d40bbdf34157272ec82f0ad134095bc467ecc83975bcc57ae22564bc4e5e2dc3f957df51ed4a5d855182a4206f7cb51287d56192ad2125213cd93d4d1193c7979dc636e1a9b56c784d0a72cdc02028e49fb0009dbc3b481f9bf83ba0e3ade3b8895f85abc31ec3e37d8518a829b6eed09a3840a54a8bb0daafc4101435328c6e133a154c76f144f1cae9da0e027714486e730bd7a85af8681a05eb2f12113ed594530b40915b3eadccdd24e4d5bf43506105a15df4091a3a7c7b53bc5737be71049aa94a6115315c6077e57651ac5d7d690333779bb8d5d7857f0481686335ac48ac6b49d6415ccd6f2c59212b0cc47ccbc304213365dec61bd14730d1fe258be093b91737fe9c686987e0268142caf2db009534d37c423c5f6a05f5e5b8419b369fc2bb7df187b5e2366eaf0cfa3e0096022650d79675c5217820c6778aa10a0f5af8f638a7bb76c052dcff810361ddbccc5899b6db0530aa8f694442642dca336ace3180173b821c5d056b31425ea43cb5fd8ea5800985714e23b58e10c6757e17c7c0460f088a69178020ea165ddcdc97804e50846343fadc90b26022a3bfe4f19a21ec8e2d7bdfccf1633a7fc88c0d5ad231db2f7821906fd7c4ee6f36c6e183115237481dab083d427fa63543b74e26a459bfa9535437cc535407d457a374049908c969625564982df66326031654a4153899796a2d55439680a3ff703b9614babb32ff88b7afa6e6136f8d56e9243fff1b016d8ebd53d5dc489e67b35618a2be084c4cfb00d8196790aa00bda9ecfbf74050f8f15bffb8376e91b18fa89390a90a80e93b7501b16b0c95869f82cec9ee3b79d9b74c32ade3df5b153a92bf071ac15d92d9817228000d0e063ea78580d49325a564906f798a2f77328825fcf31d47551ce8342543ef2de5000dbe6305dbef04c4d02858ec52d6bfdb784529a0431069d784befb750125d52cb9cd425ca65bae83bbf739dd0329cf30543358da7f925817be41e30276cf69e57f855396801ebc58ffd5390aaac24533c2194ebbbcb7496d6aa799e4f0f97ba290b84db7f2a27d0fc7353424162424269f4a3bdebafd5dacadcddd309516f5985bc1ea11fe8419b61b1ccef757cb0b341ecea54f8074355e1c18b923fa86300374089f4c367c51d70edffd985a237525d2ad22cb471d4a8e6661fad9b6792c6229351445581916949c269a8e952762ef316ec2e31ed71f47ec99b4efd0a0fea9d94297c68369e438c85a1303c28e05f29d341f8ae48066195a4466c2691c7754479222fbdeffa88b13632f349403338bc0ca168d0dba1d4e24055427a678dc53bce105d976974d9ec12855edb1f71b700c066b73892544ffd44dee41f94d4d0e94cd70af78a78b95b77037111517e2aa460382eb163df00c648bbb9160829d4a8acc55ddcfb30cd69553cc12bf2a44a05c3cf6b8dbac4d88ed86456e7f4d4392d490cd454bf6d9aafcb45351a4e227f7cc9bab4389c9c2aa9098be1c8b0fbac7f48808ebf4292d69acc14c24dae2582dcdacaf711715ec5461ac0ceeaad4fe69b38aa5aad9478f5d09bb5d2108aec6876864a53201a08083919d640a118615a2f555e066dc8d20942b81ea8ab4cc8a3389282b45d00faf98880fc4dba278bb844688a7b109dcbc73e5815bde1b3f580e6731a1c5c1ed06b4aca287c0520aaa2e708d46ecb84313c8416d5802ec8296ba5278e14fb7488a52a938bfeb3ce143f10d5b1fd979ef470f4612a5ad0c0f8bb5c1061f9ba22093d1492d4a86e7d6b96add5843ae8f40f5bd9d12113a3329ed8f47ba42c287973f09687130a8c68f5e7e329f11516ebffd51f7ab07b151e55beb8b92a06b4e7b3db7514940bc36224a4046ab6022181885ff5e586182285b9a6782081dcb413e777228e09d582538774572d1dee2bc6beae219391a3f41d115f15f63578fda281fba32c209000092ddbf3ff0a7a9e6d3d15e6be545aeb788eee2598bd2251e6351dd530e31c8f4daf4ebbc8d744b39d420b3d799b8095ab7ab57c94a09d7bba643b87ed5de851099b7b6bfabb03cb4dfcc722e0f95f167c406f3496e90fbc7c1362f7d457f133ad285d646c185ba58034ef7a31f32fde8081a28a485a58317770ef2e2786d20e71c1e2f28eb986f7d60584e45bb21fe58f182fa041c017661c856c5aaba28bb83735b2e6933e4e7ddb37f4fa543a02060dd9459662125b1f081632ce3f416e48234678bbfbf62fef8542e7750246b64f40f23523b2e2489450574190c5ad5b2ab1c7e5f726628f54c4f45e42a59927e0c3176f2bd330dffff0923685cc86fcd9c7445a6f21ca12055e27e1f74e87e0ab5167bd554e1f47ed2f9f3ec2f816f7fffe97542b25283f97e4062967d4e715c911255351b284974417cd1e10e5ce0b784f66b86a43609b4423bfc9ab7b781776c4add33d5cb3c5fc6426a6434b590bd604ef8af35547b22e172b28d92fc142edf45ab41f9d849967d5a8b559f1496f30149fd542043b96c86b052fc41e552156139c375d4d6a5d35f91acb6549fd0a90cb621c30ac5a588436d8ba56fab2490c0285e9900be0991d4a86f5fbe62304a68622ed7ae20366fae481887747905d63a3002773a93c8e7305d06c59dde04f070a813a7e1ae3105bc1cd09b211dd6b2bad07dd97babf08edf3ce88bb0a74be1530794291c640af0369e341e9b9363a5ceed264adada025294ddb898955aef6e53f2568a4c36f16445f2ccb437b7004164a354103e66a323e81ff2d3f5fd74893aa7ba32049d61f538c52ee44acdccd476c5fc9f93dea8dabf6b3328e6c72c656dd32efb0e7401b9dffe68637e746a1779d0c5a678212bcd7fb073b7784a6728c3cd433923c53af410a03720643069d352fce68a6383a74c143e0c33ddd5569067b17ca39e2fa308ec028bc4069b9d32ca60ae5b5632177e1609c450c9ec0ca22cc984e9a713f996097922ec916e457d343b92a0df57a26ae8ad6a4dd4116d0f06e91c2d0eb1546165d889b5c202dadd405c7595429ffd3e638e1ee0abda76beff0d9a29c7655915f1babda37ca431c5d31d23b264d74e1d0b4a5b91d911456ebb4838c70f7e83f19520acc99d8f693d309a66eedb4882efe7b9f37593289ff27e9222260a3982beea753dd1042cdeb38a74956ab813cf693151d769c15b6c623de37eb9c45b76394bf59694c5025f47900c118d39b2e2addf5d251390800227e10cd139ddab1b8767e65749587dff9ee09ec63ea0fe172997ba84b50c1e3d8cbeabae3d1a84a355f58d7ae224ed6180b549736fed956af2d8d5b9fc2c3f0d71d7b2567f0225e9cd60b0522b45b3920d1c647a90e270feb332cf67a249f3d9f23c0a1d8efc84dbcf59c1fae4eeb4d923725997720aba30fd99c345c65d05e128a1540387c70b36f8886a2e271b95d29f93bd460a4533e0ece8f8da77ae302d72d55c050e87d26250a78d24f09d163f34328405a04777467924de6f4adf01a09b143de4f920fbfc3635ee234c2fa8bb4154646000aa5bae2dee48d6e6d5e1ea8570441e5f15a1c38c5f8d3dd7cf63c9f8665002e5f76dfd802ec99e16be66460442c5cba3eebb53dec09b95ef2101804364c69b0a4d396432783d36343aa1c3845408e38cad79eb0c08c1e52cc7b61f55a82bddb58f8ae03462d445a029360c4f20e03e0796a68abc57cb0f125ca46d39df351fd50f54a9a80112aa8796827f9ec86039863e052804b6b9097b3f2be1f5da42af7c04539c2b29913581c5f0c2e6f8a24b1e20817efae155d3163884befced024230955dda376ff59513fcf0123165f0fd811c60124add5068a417df811abd14947a4a3ccd6a26486b2ea3a0fa540c322d1145912c71eff6e179077eae99741f8a741cdf233145ee8aa41373eae61b91ed02c1f2e7fa957f680071b5a8c7efa6dc8d1cec2496a830c7e71602b8ed0237dfd52fb1a40394e7bc88a5b4e679b0930b5d89e5a57abe011799cd35529b2b0612f3d095329bab1e3bdf7d00ec47477370e9002eac6b625834771d64ee78077eb46fe0e1a79726fd2b57653cc92eefef80e8639b83c9179ff18017e6fef05e588ceb0d6b26629e25e499f5e5cf155ba5e6eb65f06c598db7813a77a0c3761addb9855c19300976407b1171de8af72870224ea1ba3f7ae8c01061b7e4d41645a3310d992745438b318067bce84609c841696174199d5ff07d34d8f96e219fd970ff64ab12b1ef3ecedcd050c66a58d65f29bdf32a95a38d896d0c1ce7a5c5858b8c3264951c398306d00fd7903c9ec542895cf8548d4222d641be4d5d6d357f787aadbea0a6309ef2fe117c965c30ce73ca9b0a19244e8bd48448c4f21a07cbd60f87f2e977e05d3ea9cd14e54d4c3ccc983d29aee44b3b5187e025946bbddeafec6be9091110cddf1d823a9e0811c31fafe17c57e33bc37bef8a34bd7a8d2bb0967375f755a7b3cd31fd5f559af5f5518d2a866c4306c35ee976c6ade79566a3ab24926ee16196b06a667eb3c026849a879fa8a96a28aeb0a5fa82b956ebce6e651af437a7885e4387813e392300f670fa242308805df488a11374e671a5a14f60c4eb3f9a9b3e9a9e253d3a747169f1b438709fb32cba81e2aefbc511325f336df51803dd242577dcecf3801cc20da88c825dcaa68716927f1593e0b0a4e3876614a9d087d262becd02e50fb9d4627513e6d53bf4da5c2c8ff187c6277ee46416d3ea541daad2b003ee4d530439adaa8e073293346e108ad2b7c49714cbba1081f23437a10bf0357080926c52501f14c01d9a58cdaa30e0f9a66539c8bce30375deaef7de838ef27608e7f743808460342c0261842854407a96f6a4c1784af052820acb4458142e5d463bc16c0005a1ae11f0ca93e442cab5b6e51e4d223955055d20a84403e5d4167f9864d454557881b84502513a8a2a1a4aeab4532fe560f60462cbf51338287bd5b69c46dd7a51744f07ed2d6db4e8ee7aca1cde7d08dbd1fdf33cb7e53574c58a3d4c1a73550658e0482183cdd206ec38a9dcac1ce4c2e08d0467eba41bd8568458c2ffea285ac70999ab506016888498aaef0a0b8dc162bead91257eca2e7510cb2a8dc1b6230ba3e30d720d0b549aa0cf9ee9fb4c2cf5d1f36063fb38718aca7d239940f533e0ea0971996a5ef490d435d72b060b30184d5dc6904978156f419e97528f1a9f887c6c04511ab5366feb8d4600a5c6395a254ec279982a0f01207d319a289ab0fe93e6289b377719779409bea17b230002d23163ffead3f4a3933c9249ba3035ccb1a6b4ef0e5a757c3715656dc8d9527e3b7d1958da0826f75038f4ca100e6dc1883959b8116c784eb58d2d6972e89d9aa7dfe55e69a1d594e5bb065f4698f1b3ec7dc2242d470e3d6792ec34c9b401f95a81f5a327683f557f23af8be6fa87e2d6bd1968dd0a38f9a67a00979db90757daf505fc4beb36cc2edaf56505a4d5e6203c96af0d83289e8b6f3cdcba71e0f19d5583d585cc72c4f1f18dad7df33b1bc1f9ef4799bf75f4c93ba22368a0a66ed99fcddc474335bae7d702b448cb7da3894b0162bce7de008d11cd46511163c0211f44d0fb0b206de4a9f9b8660ee512e29d4e75708770f364acf3c11e6bf4ddc1e315230e4e6d1dfe8f9abd6f8c6c7572a1eb53cdd0ef74107bb0a540077d2a7464b215d88c8276c4297dcd1e6f65932c1fef6dee0b0b20e075794e72824ce291a602234f02c241cb261fdcfa30550c4e529ede6fa2c902e09c7dec0be5c62a2e9a0e0653afa58f2138a955c98733f43d3916fdbc8281146eda323cef47a6bd11fda80adc54b626e889e1c242080518566b3b6201d58e5c998431064fdf816b705665c17369a8489d4aeb155c2668245ce5cfedce44b705255dcfab3347e6e43971d8c41a43b591aa32c4c7454b4bc629e8af1b5d630b60a45bb8a6c90191cbd162b5a2182d377274df362df695d919f5f9f62a9e3be4520689dbc4ce23e0a5f686b053884006cea6e718402d4d314edbaf481b6633be8fe1bef7a3e1940d132820b9f3505ef2461c32fa1a58ce5c8e066b4c04c3542352bcce517e287bf80099fecf8bc8c8d0b09222d1c60cba29e7fce53b7174cf903d8b495249afa2f3e9d023f4c890c7e4acd6e478e4b233f70bd561717844a1c51389cbec85aa121df7d166976df233a25dda7712d54e3a03f28925be28b2d50349daeb0da133bc671a0b3e4652f10db068572b076b1b538d6ac1fbf5c1d6fee9b4a97c14931354aa463660e2630dece9b92584faa7438cdc1d583d2a81cdb506b377b49ceccf9b4fdd96eac6e96f7cc8689803a1ac5c87082675bd96638d5c5802d329a89297a8e55e8c03b66c086f442110b968ef596feaaf0610085f8ebb5cdbee21c5d94889c388d6df38ab022c9964e4741f60c91e094900065812814ad2ae868c1d43e88cf4759d406ee77ccf0e5e8794c3701af003528fb35d87429c054c5ab2fdff51abb5533e6cd0ed5a9b07912353946b4563ed24161078fd3ff16c6c5ca2efc3428428181be9ee50accb5b735988038a2a0a32253e0df60a4da619a2f83597fa4ab3a5e70ab67d34892900cdcf536d8614d2d19fef8e23d82bcc9cd483510e23a19816a59c6c9224f9e698d1f3a06e4421a02a69215d45773376b4de2e24c59fd2f9ce728220f4729f7e9b5311adeb07699c31db4225ab3299e61f22d0deca72989fa11f00c4d9bc5960d7ea0547ffa9508fb7a0eb4f3bee023fc6d95a168c47a9aec931364b050d1e5f36506a35c8ace94e037963a3ef2f81f9396657a1a162ba2951b0a709b098a75544dcee06718fbc8df5d9ecb0914ba119dde25eb1961d16ddbb762e03a4b7ca6bb72423eddcd7cf60993bf1d4dc5cf2b6988f9ad7b3c50a1ad316916187144fb1f4c2033b81d8ea4de016db249889a51a7869768bc407fe8b4f7c9b4c6b735dbcac5fb5938871ae57d086d2e70fe99f9db6b76bee6164c76ff7f83b34fc8d44d23f3a9499bd09e6ea7000b864e5f464b7e994e6b76baafa5d5e9b301efad7798f7036d1738b976c6d3bf58aa86b58990fd91210fdc783f425bdae3430b9da05aa28afdb46c04c940d91d67a66d74272d8bc7758c53864148dcb8ea7b67978542671ab353cc50c748e037ad792955a300a434e077fb61f3238440c524d5f32e4b40348d74da028d23f7410903340395811d0841949ccdfe923b66e35eb4edef0b99fd7f9f666e177a87594f7d2caa8292f3abfce19a8ec3580a28a4fa54f92ecbdf7de724b296592329d09e9085409d67ed441e0409c60040a319ca0e4c3a8240a50f693e2096ff6fc7cd8aed3c91715f345933dc57cf185dc48e4866d4b2de0f6fc7a852e53dbe1ec3ad62ff46a4f2f519cf608a97ac6b764b2765ea2e7987fa0df2a09df81b707344edd404f8eabcb6fd78c78d865de54347dbb67cd480a6be94c0d1c094e48b80423a314c0a2a6353b4546505fd194a75a1221e653a6b4d2a5af9644a9a3d38838b5323aef594b32854792249d6df7ac9900c5ed614e6badb5f3d6fa37ca6f16dd4db6fdacb3ce3a65b1ba8d41583cdb7bf6180f32c7e0d8b53823e9f1c49b4250da842d8344e794499dad9a8d0c094cbbbeadef7965ea083aec2a805dbf0331a8f7fa9c312a4aea8106374d6e450a391c8841b53d5b6caf9590c584a2249c74b780123e2c45c1a921116736917002872f6ad082051eb0d831ce4892c2da9f5d7e486c6862fb6724606c7f5c6b65e20c2dc0f72f674d615e23a186119ab6eb5a2b985d6badba7e396b6ba3285f4a2b6de2268cce0412b42063040c26a490e5e787e562d58a4eb8917568c50e9a3e100f33cfc1effde294cd26cc5f8a9f27fc4bd876bb67059a38dcd6b103cf89f6e9338d377b4abab46b9660caf218349606c8e12569fa175811a1cb59e3513f97187df7d413f46007d1c61f810964eb8062e19c68201a38f99ccb55e65db94df352cde2cd9f3d656ce2a04fe97b4ef4fcb22702b9cab65ab388bdea5150026485f9d03ba1e72c07d1233a67fa3e609a7169d94dea6992bd6848dbbf822a337f1d48ec5da31d6d9751fd8db0fd9b15096ff6947569d7f77e96ab6c68c8d268b34dc807262864e8062668e2ccf0ccf3af14210a3f276e41b8a70aab41f2e371c89d2aac4660cebcc909ed4f7fc605d123f729f277b47813b9252d017dd7cc619f9612d80d9cb0eb5b91464d1cf587fa16c4c09b268efa5c0edeb42bad5b5c466914ca65430ea3ae898396ae99376975b7119645f61d5f412a98e5c75b4ace75c932e1cdf9b9033ee470a549d20d370ca185125294952a8a7093647e9e6b803073821246482c4881a5b064450b4a740803677e9eaf812a60a60b1a5cc022cd0e2660e0892f6688d1010e1267e6e7f97abe6bfe6480abd87c51221858e244b151d9a841ebf041221d4680010acb0c5d7489e21ad18688a82dce28a1c28c7ef8314ddf45ffe993719ee238f588a3644f26385f647b32c119b3b5fbb8e0607131c1a1b22977bd7bf1dbb12442ee7bc7d2beaedbc83df740ee6289e99e5aec6ed43f5abbdecb59cecefb7e7f7ba05a2db677820fb891822e85ee74d9fdf9f6b499c147627471438ca51dcc6c30376ca64062cb1a2cb8d8286aa105353f5c31a208b4328415d04a0b427c443aac70c5511032948166cb3c23831632512881c2c60a127826b0e4b4df94c1043dcf584399ced0b229fd5ce9eb4a8dec2c3731136386d311bb7e7e43c39b2ef27fc61243637d0a2fbc6c5467d68c1fb6ff34c385edfe51aaa7143b148142102a18c146cdacda35bbd2aeec5aeb0c4959c0f002c90c354d6695479324cf53eba68d5f19aa087a32b1b15246a8421fdb30d25f6badb0cf49aa77d039270ecada3bfce9c790b8d06ce1c1052edcdcd410e00b2a74bef081cd9731c06c359b0d2d4c6ea06476c6e4e6e9dd50d993a98ca79d803d99ca88da9aa502944d29f839e9dadfe5fffea4c760438c2e4972e6a8b22111681120988136ab725f04fa848c0f50b32a2f1f40aabb0ef00220bb521a3e15baa15802d9e0573128b6e97b58bcc04b25f1a58e17fdd88b8e3b26d89f0ea00bb476b94afadf7b207e1057afd25a45a1bb71ecd37101c995c02dd1e54c1465e0100608ba30a4bcd0a54c0a4a70319bd6490ed49cc1c2cc0c5a9461f2c08b1dc278e2cc98229acc586774396b41b1fdc25f3f6f1e5eea006237cf51a649c809a3e3062a6c52e02fe4da039838e85bd824bdbe931387ff2427986abaa8c10421b0b8a2e405a2a215b2ac59a2cb98362c4c364b9ad8b283922c57aacc29b45491059a22aae80266e6b692d45a4ac691ff107f225898d88c116b466fc407a079f279b97aeed621c6761e408610f1f0893d05b0eb97344a3145799ad03f8e6dce58ace0c7c9fa2e31ba52221c7460479f38ea7b142452650598b0fab556221dcc505350a28b2b76fd211bd4ffa920b5eb7b4e349814a5619ca46ff2cf6944194290d90286236878e26e48b2258c1b1fa8786146dff3961bfdefefb4299b4a9ce1820a0dea490a4ccce8bbd19411256509c30a9c12c630136746df8fa64c005e64d041891acc08c26846df91a6ac626923841a2ea086f862468bd06e347a11143d6b5a9af62684b7dfb629d22627297d265d5233db3bb90f9de2f753fcde9b06d0f33bd18bdcc8ab44396b3ddfbf823be67bdf7b9df87defd96ffb7ed545d34e9c7302d5cd7e10f74edbdb3bb5698aaa41f9d2d3d312548df646e4516cfbf2676f53f4e746a1bbef6f356fbcd893690d993561f6645aa3843e057b32ad69b22954957991c32893365e2462309f3aad8b9efb13e714bd77f17baf46278686a2cb09e5b48983fe8a085dd2294694526a5433cd3f3ba09f29a5fa29f8a52bfc52a7becc5bf5ddcf39e794014dd8fc099bafc594188aa0389de634afeffe1efe592d9773399736be63b6ac51fb538a0c2fbb0f087817994e4e9fdf4f3b4baf88a64fe9a4352d48352d53bae0a2a2bcc0c20406161b460b68bca3dd1198a2683927c00a2b9e9eaaaeb26a67d439c85934e9c6e410ba9931471806ecee6ac0d8feee6e460d194c6aa250f304ebc3dbf59a09ac3d99d474f18debd3dcf87958b1f16be032d80c7f10251b0f31c34f8469bb36ae4529daf883f00b6dd56d20a175e0525fe03e65e6e13ee503711f3cc12bc87c5a7f3ed10cff04735bfc3af05f805f03fc4182d0e73e04aeca66787c090961863f575908dc732297c757907128775c165d2004886673943229cd885ae74f599273c9556b35c4dc15754416456c34891469ea98245cec4b4d49ccf62526366336b8674d49cb2e7b9cb0210688912af3222576a8cde81fa9d3080d2374a4062347288d1ad10792b3d6467486bfc80406ccf01bd1d9fdd89e1a7c01c1e36b3677109fddf135c797904a068d0b7166f885dcdfc610aa04c17f5fc8d00d4cd2d02608647b200762e4724ef490204e96b1589519a9792cb58e3232e4932aa1451b67c142192ff67c3b65538b2c4e3d20418313659a6673d226eb988a47016da710ef3befbb9fbfc68ba2cce55c75f05e428668174ab3eeb917a27b08f7ded3cd31d1ef60a08121883734c49024ea82139aac24b139a3059a99ff4fd98f2d9cca7899c2a48827337f72ca888680134392182d4c5e8ae6844d276f1a2668d2ec3466f6644a1365e3d893290d96ad9a3e19a0314377a605f3296afb6b0b324003452998bb6cf9d872ce9fc19c3333a13133c7ec2a36ab0940958adc7ba2e574883fee939f32993836197d61c2b6df785aad9f5d030abbb7b96d9fb3d62ed77f49e66d23b28193db7b4f34f56cb04fc55961af3b02212bcc7e4b08fb530c1a33bb24c9f87bb5a9ca863e509b92a469c6bd1df2001c2266dddbf77245694f2f4a4c7679e36cfbf3694f2f4a4dec1b67e6a84e4d4e5b7537b84e9188d9ffb10f64ff02fb1a04b14f64c3b73655a717985807855c2446ccfc3bfa9306b9488e98754fc120564b146d9466dc4f104811f77950e3501521f48b2c6086b8f7ba51049a43185268b310441869dc73e38daa305b9b2accd2744902f1a8b2014041461925dacc3e902aa3514839660033658936b3be349b68b4999fc1e588e64e9e3425a7290633b4997ddfe24755e651a5236dfb6e546934aa1e1dd11a6daaf6ad912ab37f84d2885459551a52657466974877725a759fd29db23b6d23ae4a3bdb8c695d92e782d2b456f0a3c37decd75aeb035de06de2cb47f7e2c07deaeb1b77f88eb9ec15f5a30388870641866c509fc8e7754bb508f7dbffd8138ada456dfea960fbb2090143a8b23d38ea9790f0c107c710c00f47f2e5c31679f5e076febc33c0ed3cbe7c70df7d3716b1cf8d3d4e8ea40e204740f57ba1ecf877a9ca62b15a63fac91a73b2dedafd12a3394eb671a5c2dc87ba685fdbf1ebe654d01b38e99ec7507fd23fe6ff8385b8c6a19710333c0e4d31599620cdee4f50080748ccb00f60a2b8b8c1eb7ef694776973ef7562f5cd71dc6f40c47de890fce07742b60f1fc8f1ee44ba63413e5565e1736fa4cac09980e9652ab1b939270ea5a41ec8696c739cb544dcc737f73197d12e34ee75700fe432d68c7b1edc6bc07d10ee87b84fcee67e03ee3be0de5eeec917ccc4363764fb7e2352652f213904eff3cf19c77d01660e0fe896123ef7dc736fa1660e0350d99c309a631ab1b99f7482d9dce7ed3707013c4b8db10ae37e8af9e2cb0ec59f0ae31e14812a8c7b57139a7bae13ed8eb90fd74dee670f974ff0f07b75bbfe8b6efdac32fbd88992f1e1bff33b63919df7ecd271b95eaf2b2e9d5c646767fcd171c518155b22cbc9308e3d8af442530bb5254849ac8d6b2e1c0d21e282d1a6b5273a88f7d6c99d9d71d21a7eea84dfaba277be9c53f4cee87d1020cfe330ece23b89318841dd15ec41e2eda3cb49fc2206dc7b9e0e27f1f800ca89af1e5cce734fb4bdf74620fc4e9bc274a2739023beba27dadd58d29df3e1109aee580855bce719636495bd84f010cd78c65938849e38cc0e6e426bdbbd2115861fd3da0531e0c0410fe86e795fd25a6be7e91b2f0aadb5e3dd78fcd42db5d56f588ab05bfffac3ff86388977ececfcb88fcee3d7e13eae1aff7aec3a5261f8e9d3e0a2e3fa1a228d12e91a27f11fc979b6996dc4b95de2dc3b62dd3ae2dcbe337ea131d2252e2b31f4f5bef541cffa956be7278881cb35d230bfe3a4e389ad313b89432174399d5c15567dc872c4d18ce7bb21228801c3454d8e38374b6439897f256a2731074ee21f52f1ab468df574a235af7bfac6f35c4665886a1a839f3ee1a766f0d334f869147eba06bfd35ce633fc5ee7ff361e774d4ee2ef461ac6c91a5068ff1a8de02903a070a9cd789e82423e6026090a0a012a7384960d58ea7af18648db3c6f4374da3c23f8001f758021932e591b6b775519ad5518fe9cb576b9fe6bae8dc91fa09a9318bfe3f79e689e08de5561154cd32c829fa0105d61b4cd126796663c3f41214459accc2218b31011926081a18819cf08e4f848c76f3ce15c375f3e6691fab348fd597fba735d9d5eea5594aef32bb883baf2bd2ea32a28d56e14f9f0050a20be3cb166db0772bddaef81b5bcd9e591dd2971625195f9e509a829967a4f1c83577f7e3ad36d836d9b64b6d7f6447b0bb96832de38bbd15ed1f33d4aeb2463136c3fe736efe7b5beb23e53eeebfb87548c39f933244257cf3604491b86d68ca510b4646c8abfbcbad7594652ad2d98b02babeacac29a5d737665c1ccae4f9b7c9b3c6a2c4c01526342c6924ed6e7a0689a54030a0d8e25514991be22da9093f595783c808c1adc23264e2ad9b568f53a9f14050275b6d2114930a2f32b91049d718a0eeb2ccf8068408b5263155d52242b6a00c449ba8572a15555656485e57c8e687d72be3e6d92d22a52a9ee54dda1d5e0f8d19e8644912ad112322338e142062d388031ab44559c9240410a2f90d8c18759fdedc55710d657f1673dd1ac25be80ac7e350ed1d9ea8966a493f53d9126d1284ed65f89748ad29625917e71b2e24d919cacb1099cacde133dbf249fb4b3c0f8a22ff9f2518bf8570d5c2e7b457d8f7dd27ecc7e05f675d807b27f817d1eee6373119f56242bacfee80072d2d639e3c4e972d26aef89b6aba85aafa81d4df1f6ac1d35f17cbcd933b246292354b17fdfe5647d1b34687ba78d4579d97de67cba3989f15b5dfdef68f51d5d24b222b83b0cde3480deb6edbf0a9cdc46df7c73a74f038aee7309bafcb6dff0586e5f6bddbe7e91ba891bf73497774fc03d97f118f46da22f53769f8eae2bcc33f889833af5af355aab9cca82beb6da5c67adb5d6e7cee8fa6faffdcb5dd1e60a9b167f4be3b762b82718642e81facc758b84e793b93d410c8014e5f707a2d83ec598de383aa22125614b97f6ad3f67f5857021e4e7ac90fadc736308fe759c85d0fd7c05e1fe73757156d8e5414277e32b487e2f27d491db7d04e6ac1befbd3f442da0b50e8cdc5bc55885d9bb64ed77e0321b4a42f78fdca7b4fb54c97d7a346b4ba8ecbb610b749fc7bd0fa4ca8aaad0f7f1fe10bd699b7dc92a8bcd9ebb415953d3a2a40684999a96a2da951af0e74d45ba7352a0143fddb8cd5224f7b94a306d92148552e11edb2f72120f39892f3572124f7fa2d9f61a02f57d1caa727fc8bf8eb3c2b6318857df1bf5246710efbfffc6a118c5773ce224c64fc6d1f69d4795d57102cd1c78e3efc0c8cc41371ef1f320a11d6b2731ae2fa58eae0ac3bff1532ad402ba31dd18eb8ddf7391ac30fcb3aa6e9b7d23a0d1241a05bf66f8a9127eca053f5da25fdc67d656f8b2f1d39afbd48deb0c7fdeee11726332367e8a9f1ae9d898cdc63adce77ecc6999856d56ceef155df7acad50c2ccdccc5a0d0e2a84019ff02c3b144f38cda35ddfd6d729fcb0ebbb4cd85589d3ae35159462b149867ed61c1824474496acb5dbda1cf3b3b712731b8e5661fe148480cff0134d9331ff9f2aa35134f7258a5493b6f814373a72247f4ff2bbc5badf1a75ba4e4af442410de1516594e22d3569cbadcd996fa95d324e9fea3d1b44e812537cc5b481854eaa30b7914597574ace7b76c92ef54de33ee05d73db349551ebe114ecfefa5faca42fd84d4ebca7d168391c814c65d06ca55017ca7db00a49db1f033c057f3eb00a49d38aa588a938e98f955ef555dffe679fb89e041fc82775bd00aa7a206fbd513dfec692a8552bb9ebaa35b2e88ae6504ad2b7dd03d54f5475f89271d2bfa6e2c489b3550fe4aa578df7498b176ae2f07fd5dc2fd476bcc57d2817f7a1f7c94c06dfcf4dc5d7b5e2eb7e5029dc30b5d23775fce43ea9f16e71721ae0f1d311085d42a0040f623c81658ab7428309544a401a218519387a4037fe961b7f52086fefb576b9ea2d85f0be9fbfa734a7364d5135a8a527da9251adc969e96b649313a50f54ebf7406e450f400cb851e86eef5ff5bb1143bdea889fcc6c0ee9c99fcc71f4ef5213497932451433e0700614261390c1ca9a273550b070c3cc2d9e4d2e1e26dbff491f665305457abfd9fe403caeadf8e96e11e36e69a3fd5d218499628c12922bd28461012d31403d51f252c61b233c479baeb8a31b51a6f630ae5d89b2af8cd9e5db5998898175d2861119fbb1d183ce3f4121f96d38d13db23746d4527d95d179e3719b61d1abe00efc3703fce09c38b6b1c465d00e626d5b590ef3b736037fd23fe6ff7351989ccb8be73ed12009739138016661c4143ae0285c92595ca961b567ed4a0a5ab8787ab2ebddc7c593c32a8f413fdbdd9f6c5b10799b4b176800c4c3f5e4b7674d29ce2e634979b241d2a49a81b48de53606fd38316355f6593861805f68712288c0172a4c8146892ec08cb1021733556ce84109174a62bca65466777bd694c6a040763689313d4cb4b72753184a6011061071b4de93298c1c66d839db9dedcc633019a3c91851c6a032061632e64c6194d0aa15c4623f5e44f1828a1758bce0927f0930c4a88151851460848975572e577feecd73ce49b78aedb5d6baeeb5b86e9eefbdd7eb3a7d86975df4de4c75dd1cd77befbdd6de3a752a7bc78118ccff662e89b63785524aef57fbf55e7bed86effd6abf5e7bed575b6bad750cee8b19237fcf02fcd8153661bff1ab34d04365f376f7ddacb51a98bbdbf44b0c8f3ba3a72a97147c49c628d06941d32f2998139652e5e410dcc06f85f7e7b1ecce37abaee007fad0804aef5c6f7e74fb5060801c23eb73d273feb8cfe3be4c252a45531c2812af4e20675243c48641abf6acdd90a48e118ca0dd1ce99255b3c18b6d430e4a4e7619eb71a2a44489d66f54d02c285c458753e02d60ee19cddab366058817f46acf9a951bae1aaddab366a50510581961894ace79d543185a3b15463958e8d41434550f4cb0422994740f5d3448c553aa071c687c5138853d20815e9929a01911745c154f5f95a71757c6c9ab0246279359ea763632515c6c8814fe72d63487cd5c65cacac9a90a131699a32a469f15342a6db4d3142a57ec543185526952e92994720d1dbcd9e369bdab76d594cd599b3914b0ebeb9963db15b6ab4823b5b58d1774994acd1cfe49148ce7eeee1ba8eed5bdd69ce7c7e67b94521c93524fc0a460be906b5b30714c4ac7044c72be53f7f167e29873feecf9bf4bd7d4b1e7bbe693f363f367cfb466b8321b55ae6ce09001eaa32f679d35ab8799bf9e02c69423b6ffe68aa28a55fbd5c083183c80c065850e5e76ad6fefad204a18275fbc2cb10298998e2fae8082840d1327679ca1045626b32a48ffbc03971d9a78a8a20316a4075196449591c58c2c63dea401e712466666c8e18a246738b152e26cffe211418320b3857c6fdb38270e4a69399b763522746488d278389116398c8aa13f4a2b43b3404d951419511a1c4da3ec92e793ce39e79c93054a9ad9064045943670b080e10b992f717e3821051a4220258c3503890fbbe659add9da8f0660972c2952d4a4dc70af93713fc27198f96b1caa6c7f570e276ccfe1090723720b4caf7f9d4b186f22e93ddfb76decc924860d5328d1f3ef19badbb3664316d7061b9cb0c2861a14adf7acd9b0c506251b8c92707df6f237c62a67fd654b9763b50b6fe38e8872049523b0386c6ece3597ec132e9cb517265e9a7889e2858ac3e6cebf94731bb0b4c584c0d608a41d74114a50382f4f34e0239e6a508eba3c35a00b1414234d0533e99c3d6b494fa4d1dc9eb5242578f6648a5a52decdc1368eedf997feaf1ebeebe3eddba394c62acc7377b2969403a2dda562fd89409dc5bc67df8ae4cbb5564bd65a634ee99ce0b7cdaee93f71e8f8e9726a78ad395ad46aab53dae364ce3953aba9f078f0bde3b5f8e25aad4f8cc77bafcf9cf65efc40ee138f94526be78643b771dd15dc1517e35addad7d6a2fbe4f6d7d6aede6d688237bef864225da5a95bb0e5b4fa5730febad50007fdcafafbace5d8e0bc1cf7de6ed38fbf73197411f1fd8db5bb1c4db7e91edebbddbf6f5ef960375ebfdf4ba7a64e7aff9ebddf2f5cb59eecebbed2cfa24f1630fd46da41307f66a999236fdbde7c6b3928e894a6ac2da255dd3c69b247994ed3fbdd6c4761a657b49a76cff5952a5ed4bbba45ba6acb60162c37677f7d966bb17c9a0b4bdd6a6360ef359b276ce3d3bff24b3f3db5cc30f3bbf7e42c2ce4f9ed839cf1c96f62449fe0edb3627d8dbcf1d320d6ef6d624cade9a64b1b79a1321b69816baccfaa8c2aa2c7cf0fb101c47da3762a4361546dfd2e8ff00d1e771849434852ad1a75b6a5baacc9b4a5f4a9a42df951ccabd0d8572aab26a4497664542ea80be91237589ce68d33741791ba5a50ad5d4510e6ad3369496b569d38f01bb646d2cc3968ddfe2cf4b369e218b8d5d795f6b4b1e6b6328625ba417b6454ab3ed476dd2076fa1ed5a1d2779aebfe4bcf4e54b29fdac62f3c5cb153560d982862849120f4f5520098193286e884860c2a8c99320a85421614654458c302e90b0830f48bcccbc8ced6294b6df99dfeb2aa3cb9f167425380c34ec9add8f3043c97677d79452b0eb0b6936159ae5e2818188036c4f678e4fc78f7fbe8fc7214152a99148856d4f34eb72c6d0b9d65c73cd474753b451f4d6c48d13b598fbbc7cd0d8debe0615bafe86454b4310b31a4ebaa4479b1e6d9fb3d6f582c7e3e321a2191e5d15b6fd9c6d1f76d1dd973f7bf36d83d5fd862b3134d18c68f6e5c5bb832abbbfbd912ac373f60482ff8976bf1cc460033e6ee0071027b7ef3e83fafee0fb48961fb0e29c935aa0916cb188bf7a74ff8de525da15ece1e0587e60fb577087dd01f4281441b1ccfb1bcbf9861a35d525bcd9537a493b3ff7f37eeefe73f7408ec19ea08f19c5b6dd18947f7b1a4df4b63d7e20efc40c744bbbc8f6d8c3ef7563c971df734fc11db1bd8da10bba9c4fddd2eebe9c4f597f374e3c7e760cea96761d3d8f5624cffe68df82a2ce11fae29b81e9b48b6423b4b53cf4cb9e041316286b5894f6c4b2b4ddddabad615192b3670d4b13ba97205385138e45858a7b2a72a91faaef5be0f7ad56ab95eabd1c4e7fa91dba887efa33350addad41229bd30e6ab15cbdd61c07aa84ae2627f8e19e7a44dd6ab59a4b40db73fde818a93f60409c3870f6ea535f7ece5a7b3bbf0ab915c709ddcda99eebbcceebbc518f2591ad85aebedcf720dcef73d69aebde1b8fb6272a8838f9be98e20a1ca4285bafa30a89ba9c994a3d1886dff7a5be158661ce8339aa9faa542a87e33ccefbf07f783f522930a7e5e22139a22e7cd7ce974a7dc913c462bd2bff07ba4050a7d56a813ee1ab47c8f548a5c0564bccf994c81acbd56b87f7ddfff0ba713aa9593b67cc004bbf767c9f7a0d66f0daf17df83dbeb187ea7b3dbe76a43efc1e29afc709504138e4d0cf8993165198c92104a9562bd5b2a9562bd5b24752e317fe83cf7d38822b60910308150504abef0b7f157e2b1586af411004c1f057ad4e7fe1f7f5ec2fb5c1f63e4f81ff7dff02c71fde586eb0bd1eafe78b9113fc74441cf83b3ae0583a276e29e717970e5dc107226cf073beecd99ccbf5bfd3852b70b56a05f5f46c6e15f6749df72f70ecf18de506fb0bc732a8e7ebd15a7fa9b7efd9a931033dcf2da147f44d171b44994b298c4173031434be068d30f4bc55aa86e7adbcd4ef84291a5f230c9f3e8da06f8739622896748720e81c08fece98f7d771a158821fd2f81a200986618d7ffd8bc6e800a21d864f23e401c3f0f5e1bf6af0f0b0c0f05fafd101449b06c871abb1548521d1b7f3ae9db1f4bdf33aff89a15876c0dadf0722ec9d577df9edcff53be2dca1ce4e49e36b7caa862881164d630cfa36f8edef0bc3158dffc6f2db5fa8b3b313ee7cf8ad9d0f7f27d4194ba19df315d1f99cffc652672c635be753df124b20bb15ee7c4f231ccb1a345ce0bbc00f272081b86a8ce5ab060d1a0fd278f06980cfb3c1f7e06b7cd5185f3c3cacef60872e1a6319f4bdc6d7eb8bd4781affaaf134bec648639c4ebac632ef9df11b4b9d4f7d29b4c3b16c7d8ecedca9b10cfabeff520fe42b91fb9ca06fd3f9e0675b482374830ecd59f2c4164e64d1644513659a3001821cd74a7d9f9035f2a562af852c1772a190cd11a9116d44c8cea558c76285407697c1b104dffb176bec4164774684ec67f3173ef76068029e514f469a844c2489d1f8555fb2bec571dff7ad5eb5fa16b85a7df87d393939ab577d5fd7fa55a97f727a0c626d0e0cda9f83abe73acd855335962c8e23eabedfd1c921876c6ef565cfee7876f78108256b775cc775a9cf9fa3f254aa56106b7b2ab10b52b1bc526f8ef3543fbf5389e007b1f67c2ffcfb5b389641ac0d8e1960bd78ccfdc1f7de7bdffd071274d1ff8d1274d1797b29cc26aa448aa024cfca138d9f1526ebfc4e059f8ae08faefb5e7f9fb3f37d5df7bdeeba9cffca14fd9d2396add7f91dd103ba43f5544794408b6e8daf124bba5520e85cf6582cb0f33a4e2596e0ab5ee0e800a20d92afd58eea55aad6abfec5dad96165ef57a0d7716508538f654a25ce3ddfc543824fdffb88bc1cd5c87a4f2c9fc8fe4ad7ea83a9fad497daf3f4abc2cfcf6a81a5ceef7c8ee801ddadd68ece28643d954aab54e38ba57a96ea572a6b647ff3bd9cf7bc07e1c6067f56aab164adc0f1a5f345540fbe8eeac1578115b4c66f2c8148d658b658abd583ab077f05f63c1017a8f3606b2cc107e1c66eb1c6d7ce0e4b67f45fad5e3ce66e8dafd61761fdea5bac5f3d6b9c4eaac695571ad95fce580a59d5ab3c9d1acb7014b29ee7fdd77579bcc182c6678c3c5161f1664f3997b0ad2f59bf02c1544a2c733ed50ac3d56a05545bde043fe5817a48a76cf8a0178e3fbcd677634bb71ef45aa92cf46a8d3f8e80ff4a8d3fc2d1fb7268779ea8cb10b6feb9734621b73a7f0fa0df89a52d8736d7b1beecbe48370af9ee5ef539b49c088aa5910dbef9ced8dde708f9fea0c6891307ceee3ee74bbb39eec194e8c1d06e3d27962c1f43bb08edea6eb556a2045a346b14f2edd9cd756339b4bb55cba6deebc6d6eae773acd67b465aa92c3496a9f7fed52a8feceec37fa5c61ee087e310ebe56315c25efddcfa3b21bfe04f2741f05d64fef7a59efb30057e38f6006fa020647978b828876cf83d014094a21f1937a0b0c0dc484246084a2c04b3bd77a3068dbfbe7fc75242875fae9ed53d15bbda0f6f36fde1ad58a9f041f0bf6f058edf83ace7581268d139a32796747b9ef2c4f27bcffbd737fee8ba9fcffd6a2cb5e7a3f6439a5d5ea4bdfd4dba48ee336b3f34b9531cfc4114a403fe27965d12079eb6f781089bfb9c1f25d0a275de134bd5e707450f4428e9ee74c472f5ac6f3ded9e3e4b94408b5e8dcf71e07363798fbeef9efb32e87e630f0f4401d509fe003d31672c5563d03ddaf4bffba1ff1e6d3d9641f768a7c60cdca3dcfdf7e0d8e3f3fe860a9afbeebd9f4b6219c45ada5c190480a8cd8d20d44d9f670a5b7a20ea460d57896b81670abb296897309124e81eed3bbe7cc476fdd8abc7b4e2864d8fcc982143060d9aa7272db4b8e28a3367d2a4e152451550505cacb0020b2cca94e96282d1c50f185930f5cc30c0da0a9b3e585b61082a2815940197a6a01c6dfa2928473f785c642eec9ee09d429e914c3727d036fde9e60420bc69009de2d962528bc48611499bbe0d23928290428ab9c50f3288615131ae2cb44ddf9585c693054f1641dfb62178f5209d760081eee7e26d7fbfe4f909b3579c1366eb156fee17671e8f9783caf2d7041400b667c785631bb07a4bdc57587d3a65799c75db766ce374b23e77c732abf408024d1cf5e9934ed6fae5e7bcd15c54e5eb1847e6b3303b30d9fe1f75214f55e3500ae801aa08e3bdb808f1ea2245003c0a68dfb3e6c398d922430b534d0b10940afacd286601a8f05e43bc88a81d19d125262441b3a1a47df8f213f381cbf6af200804ec5f578589f5e7cc3ee7823f0f7ffd03e4cfc3df8bc5fc7ffc8178d42a3682d0a5ae32fb84524a9594b66fe28a8da63b6fcfe56dfb6dc448d80ab6327358a389c323e0334bc3ee741dca7a9b6a64546bb53af9923b3954ad5527f725ac84ad704bb6734b660e0c75b43d9f2ca8540c57e5492c152a1a09002008023315002028100e0744229140309eca82d87d14800d7fa648664e960ac44112c3280819648c0184100200308610a0a1a91901a0768a7d818b7eac3fd089e1e02090b2aa5f6c0f40436d794201583f976c2b742ae7b143737d2f0f73528b88e1dc0ecaf800590a2eea4c0c5b3a3e1ba4262520a01f590725f6d17ab392f6e5dbc5e76e9d9082e16118440b27069720a28951b40515d0e9919a69f439df911f56fc5392e53dc71b9354fc2c3c673ad730c4ac1d183c48851c0a3cfb22190626a60000987cff88102662c39aa737c7873b49f9323af0ef2b5b136ba4706ba240fc257f959908ce4ddf04d85687ae11071bdd97598caaef3c6dc34745e02c6983dae976251c64ae8a4c5d827c0983ee644e37d8edadeba92d22802514122174eeed4ff82440713b14ac3a32e6495b1c4e338182079e28ec71271d86a08c0636f1482b6f923b95fc10a19992acc674b4267bd2923f61b80b29a08fed79328fd039bda46bee4f05fad0898f62000b5eccd559dc8c27fbbbd1b3791ea979a8651432a08e2b0ac2666396d8ab6e989a8f79f2562424b82047eb1b8fece82eea58232c3d4b6a968c362e878a3507b2b0dbae3da9d3f1c3a616403f0ac53c244164402dc31e13ceeb421c0b41f08c81f512f827d2e30f913493893e508bc5dd97a6b711b9220103bb94ed390ab4bdb154f2f59a723943a035ffaa958bb6804106ca3cef6ec0ccb91045bec640b5c17163e46079569257c4ae9b911a3928c592502e480561af0e86dafb9aedda79bd34c5d24c1b2ee75fae2507038c287f6a07c8b52b112040f427eeaaa99cb5be0985081eb8ebeabeb48a229c1ef1cd20d6c8303a4686982549e57c9ade882602e6539c8571e47749131f67d6b01e382aa869bc6279b9be04c0f20bae36043b2eeee31609e65eb00d61039069458b33bdfef974057419f31635bc88cc82a935c43da2b6528f28e13b60ac2cb14be07a052463eea48860b16a948da52ac3fa74e44d5518facd210bb139899d382c1db1eba15e2a7bf3122a90644bd74c08c94b8a207385c7e0ca4c2c1921d55317e68ae247d4b474a5df07efd2d79b30269fd0b1276010f192c38fb7b71a517238a81b15544ba7e1727099fb810487ffd8f964b9a5d320de0360076b6d43ff358a158be0b95b5c1fbdbab9abb56993d908ad57d4193a7368ae3ff7c45ab6fbeea343e4a4c4746d54699a7b86cf30f86b56b8ff45dcf013fa526e18f8c7f5eed97f1c57bc18684b2170f82ad682de855404bd487da78f8cd0dc7b5f73334cf757dc9110f765cc8ffeb821e17dc0ca45e9881ad4d25f94a213d4ec4b63e6fe18281e18b734166b01a56ab553d26628b5ff64389e49e8effdb446a68c75139d9947c6c2db276fd10903c249fec1a764028a9c5d8faca710799d2ec611f3634c9b1678e9b44052a3253ca2523cad4b622801f49d6e579f4894977d4a22d1d4cc6a3c30851d6b78823b47b1e03736329b3ae9974a9e0702f9917d9832527f2ebe86c77e2c663c6eeb99d4c8a8e69f89e3e9c79b44c14d4f94c882a77c2185f265fe28bed124841d1fc085014cd8991af2b9ed8569aa6b77c59700b117656c0acd99c7bd3cf7b0d875db068ec1cbb4b87ca3ae896fb3c49d69270809bef33bdbeb14d68f3d03f0ed057a38f61a08c335b67e684b805483ebb6f608def6ee44817f92345c54f67b3b0c4563aabec76ce79d3fd2873208b0931beeeff683ca2494015b1a7767805400eeef2d743560c8eef239571cc0980b39356cd346149a5f95ecac26fc30c7b7f36b4fb6b25730b1a167fca5d7ec7ffad310885d9e35a741a6b8a0a7fd061b25cb566d985db4ceadbcf9de6f626e281fe461a9aefde9b975a5e191e3f429a5d0a0936133efadfa75886bf00a19806a70108c903aec9fa7651afc2f5648402da2ff77b2236ff1369c0c888a0c8115c545871e7543acc07ce137d47feb440f2c7cf03fa720209300424726a17eaf8900311baf9de2a43d98b08b0405703e5280931b2c474740fc1ef0f9004de005710c80409b5d16f8870448b8a1e117e7b0790b347894d841ca818d340340aa86361607e2bf827815108d41ac02c898f328120e88b7ca63ed1214a4215f6799fbb107517f8828e51745a3889ea295cf7744c86c1359408aaa12be781a3b80d22a0b255533c4ededab00af6d407bdfbefef542f1046d9d2488ad7c48e34807c24507cab2af6e0023ea722bbfd7fb1510ee6d1cdd7ea71546c38952a7ba765257a4d78dea1ca278ddfad8a9f55cb36cee74245bbc3affc7ebf933c5eb701b5e0c908836c16824eabe33f0b58bcfd30bfd1df4443b7ad1e3551118806e2d2efd6205fb15c0d064f01330aebe43e8eff00587d536b6904304158ca1559c38efe7185aa75dbd894078258cfbcae13a4168cb19cc4cfba1840850777855ce2b3811b83b9f039f571057e092df56e858645dc3144d094ddf3fdc891bf1640140efb87bdb78649d8e6eb845c2be975c4077dc21c1dc896e81d2f0028519ae90e45f7eefea6a820edd90c00017c9c42179d75da1dabb22f2d520b05eb81c2f001b41d8353c53042c46d6d5d30fdf3afab8376d05ed954c0eb7b8d45f2e5c69539eea191c6c8320ec9d6a5496dbf0011e2178067006c2ee8b3f8d188120ad5c40b87d820b5e4164167e3cbb3010760891fcfe3e2754d77840be481ad30261bb4ee15ed31684fe942509d876b8e115351e80a382b2e7e365fc55d0e83a967e91c47e283f4539368755d1d7b9d030b860b4b040d817d79e64370b14b52b12fbdaf3a40843b6900df6d6eede028178b840911e1471128e624bcf0c391eb4a1a69b935587ee30341925df93159c2a1bb7690b6d31667060e1b2a9d03a0b9592b6ba713a4e2a4b4238e225591aac90c7c5b22283f88480ccaef264ec83bd4166fa978d5b89447a4c9ac51ce15cbc8a051ce26493f81887afa0fc8f08cd1408baefc952a83c30d150ee0cd338f10e7dd40d024a95e250ecaff53253c8e34e9e395b4e8637a658d309f799a854771c584412b0ce8b90608ac4961c5ea9bf8e0546591a5b64b5c5061dc26169f01a46a6e1d64faad96f8b248c6daab15b8fbf2181a9522a25a58f8a01b0a5afdac3070875153e8d8a664092a2687b6124cb8fb45547464a54b62442eaf1b67c4280052188d0d00922a4b48013b5a439cd463b19330e52d8b01c84ec1e5602c489e73696d5a282aff880269f1d3a5003622b60cc3b85b9b9a60a84462e807c3207fc387d10f92aea1e29b786b2730fee3969896e5ef0ef62a0c5cf25ae5cd17ab43ba7474e2b48d7da4b13d35d8a905faaeead05243c5d308b2fa26971ecacad981858e07527b4ce6eeff3e57c888000ea9f3f297379e83a3875286f4acc3025f2bc524d747aab05db8b1801f6ba7240bb227b45d2a03bccc813a1c5aba876741fdf4c140defce1e85cd232e53e753a94305a8396dc06be5faa571f40a8ddd8f3f4c882c95e52c125e1ae36d7ec236217b2310ff197fb52bf58ec4863ad34d649a9f7f7904f62aab4b9817c02ada30098147d33b43447daaf88bbf23abaff8cf365a3fb570309a27c30f43feff585138ca2fe7fccacbea3350810317a10e744e82761da1cf142bdb17e8d36b319a48523f92304dd65d83597a150cbc233d50b11323159aee13b1cf92ad37bbcf01523059f44a19ac8cd1020c0f83c76217c744a62cd2ba183d38a3931a6ff81a201d80f553055c8baf8646c610b441686507979e9b5e7bcf167bc459a45a72f4219768644b2e53a2110b1109e253721958cfcd44b06bbdfafd0d412c4f306cfbfdea235a8e67c76de70c1cdfe0ed9ed1a098bb10711a4253af1a9b82bf11911fdc813585a9d045541089cbe81049d442fd2ec8c1c023576b9696e3fcd29491230d4de4ce80f2b6d4248339be4daf30e64f850c103f54a52bb6c797e244296bb5e1b8e36fdd362e08af4362ab62755d6aaaa3b181df0225415e49c060fd061a80dd983c7d17225ea70fd4e3df007044d108be8c173dbb5e2158742f7271d526e2083642c37e0ac9badb95e945fec5af9b0ea29a6341ac0ee18e5837b9d215242bfa2d93551d2924e5c37ae5ca3281e66b2175b68a0b360f52ccb652b91f3acdb8c9b243259416359b78a09783f87222e3902c3cdbf6212940c11fb42a66d98ca3ed194e3be815194b036e0f247407ad10810042c2d3b493fa91b9964ada724e21c835736794f9fbb36027705437178fd0b624a2f83d0d020d81655c144723e2b3b6de7cf4c4dc9f5222730363687f64830f59a80fc651dcc450e3eb44bd10f29394651d97e201cb419557b673a41532a9c115295c24d8be6553e5d08dd9e9411ab840030ff2760cd920061df018b63c5ea9bf0bd2e90670bab1a87cc0510a3842c4cceac8aa865af5ca25dea4e36bb90716f41a89f9d3cfbff91a6567a81b519bc8b1d7f5e47b172db83d7fe5677d177dadc423196e496cdb4e691032797b3ecc425781a2aa66577b80731ee02ba5bb51610e87a9053eefd2e6e66322f55917c26fd364d36503d40eb883811fb2b3387787feeb7b0f32464ed72c06eb4ed56a9b3474a5e1e9ccaa980ce6fcf64a24b4f450955b4618186aed1e455767f9d270258b13463d78c0c416f7e5970bf0c433b86948f18d599c10715b885d06468c6a413576d53af52f5f04910d09cf20bf016275031af61bb4568186a54be73557699bda500ed040da95126807db1023d17814833f10689a6b4499be0655fe97aaa5fc2f0da14c20487ea91917641706102b515f56a9aa86ac2ac5630efdefdeee1b760bdcf258205621554a803668fb4413fcfb9f945e0264e725d3ffa25f39d9b4ad8e18f878515aca4066015a43fe25365bab96bb1a657624551915840d300dc88f97d8adbf52636d42cc374e9ef0396e271aa65e08988a238464600c396532575ef7f32f4d52ab9b25fa7e41d9a116e3a50a52ae410c39e63de70ae29afffacec29b4826061addcae1f794372493f20d6452ba41ca04f8bc9d437c07a8e791917dff2dc0d8363107ef2edcb0321c75c5e6b4f98032005b7c67fc7b1a6e9469e153b61a458b8e679e06abd0265093ae3b35c6a8e3dc6cc4cae51207dc6da7fa1e3588e64859f93ae1821dcbc1db10c4831e069e9308cd86bef0491e8dc0ca279da16c5f2d295357f1dcfc6e688f1fe475b54012fe96a94ebb38e6fe4c079e6b2baad1958847dc7f1ae983aa2cbb1509af9a8181ed1aeb8d0c32be37153f05ed448d967956318a080c4ee32749be5544d48e2caf8129c96afbe636166daccbfb06d69dafc2bbfe68063c4f33ea0f235ef6b0637aaa1e6cd4751180469d731168128024f3e11b5f5565b47d0e179a7862461216fae9b2779e5d9bfce636f41578613828446514663c0968bd1803c9c92681edf76cc2424e8b7d5868b4a044aa1b4af2e23d09840d4b1620ce0862490fc0b73cf7ca1dd3bdbd8e919b0cba513931c30ff79a6312e9bfba5805ff5ce7604c8d9a9b32494f2aa97aa6fd7ee39939d672c7818a8e69f940a68edd87521d5867b4f12aaf6bea9ca58c889a7468400141be10567701fbe783e1275ba16dcf1e30ab95e609819f50b8a47005b7f86681cd91ba6de3205978618760e6789a69ea89fca24bcd927c296aca645c67b7bfbd058dfe53d76c1a5da8d06832ccc9b9c42d80cec29d54245e26f182df44d0bd8725a181be60359522b44a578b1e1fe0b702b1ad9a22ce46028a814d28d1a95fe49769479a08fbda550007d7d46419e49958c0a744d977b109412ba1e523626ade01f1b96388f5e65798da568a40ba23292c6aa06dd732fdac0811833de917836041aa9e0a367c205311b318e9149a47f0cc4d7f021feb4f67a7bf39d4e06293d04f942e2c26ab212a0998513c05d0fa9ee622cfc7d9d27e96b7cef147e3df32657dce4cf17e4b349cff5878a71b36ae25c65cace40377ae2976e8658ae1a1b94e47b585f4cf335263c5bc785a6ff216777d782cd2f7b764cf24e2c818a141f3daaaa3c64bc91679cb7ba3d2f89070da124f03d7e47a405b68885676f8c8ef9bc4c3ac7949d276d5d056e537a881574107295b1701a143428f4923b7b596fc6dc091aacc5aeb139033c7e6b6183829866e84c08c4d79e1c83c847c636c5102488b0f0d11678ac4b5d0b020a837fa417b22bc1900e0fd5c2c1a90fa2d080b809d862d2e9173c41420533ca6644e6c217db181564b5a745e7f369b108ae68120fabc42e3bee7bb277ae819bcb4a7bfe823b8b915c1064e227eaf9e23dd3c9df98b2617466e335bd60be5c35dcc2eb6b177950bcd14d21a83341a56b9d91fab80970f874d432f76bd19644cd864f21e62b3872f9ed5931764c32202978ba6152c1a070705cc1a461801e18011c699745b241f86674d5c34845342bb2e494d13bad7271924cc95013a49a8c87a201529105ff28579d00c916c14c3f6c442780e2d4b6f0cc984cadfa4ed6036d1072cd9ee05140341b2c78cf205472c2b9516c1bc4c815ba9fc11c66c91db56f38b7b18ce035f7c39a2930fe899159cc664920f1e4ec347f660e9b7d6c508a6b0b6a06296c21bacb8272d5a52c68b29551699ac4c46004f9c85caf60c3e5b0838fb36ccdb52f5b99022ef060ca8505ced54f06a6697e749fd26958f337652d7640b0cfd3aab6bd8e55dda9dcfb89494c905d45769e281295e1bfc3c6c1c39ec00098e555ca50afd14683ee2ed02c71a4b06ca3113efda2c7047c2cdf9d334124960df56bc4be18beb3f9905b3b226d52663e559794bc91751d27860b9b4fd00ef8316e11036dc39ae3853753060d5280f7f46a29e540f4d494b6bbc098d11b862c7b5466d7be9c0ca13a0f5984ada99f28f203c10333a09337fbc8c8ed65061a8940a5cf874406286283d2083b0ded2e0e00d7a3aed639042294347341472e77973beeecefeab5a266be2a40702c0021c57c1e993428d21321bc40b3c19707010369c2127e426c880a8f07d2b458b56f4c02c338e91cdab1e6af8229abb27dfdfacb3123080be5c21eaeadd937937019571bcae4090ea0003af322daaa510070257f5086694393478a0d9c116ec83fcd6b75efcd41047147e01a71307535644acd3534b13edd0e38ac710d90bc18de2a5cedfee95cfd7efc0b0a3a55284f24e80c39b9a138a038a7035a6105098719d4a97c95a98d06af0326ec5d4e84f60338dea20a15d40c0933a9d47d25f8915c0b845f4df930fe11a7730d1ce505046cc83814b6f796c428c430374a0e683e70ae15a4bff9df844c9f3c6a8c15166fe5f08622a5e76e4b32f55c395f4f8c39a763e3d804a484ba0daecc2c4f57d57447eae156e619083c1fb91effee082a55bdc1cfc5cca3b0fa22a058fe8accddba9fe7a5b2ea43facbb10a96f51887332f061ecbaf509840998a553958ee2b2b72b9e49acedf848cbb87d9d811d52ac35f4aa7aa415d8add9582d1d59c44614b3e8596843c20f243c8281f9dc8f20f89c973e7ad93b5f65ff651eb5db890b3ff41dc1a267dc1162e620b611726b988478e75c762925651e93b27c1183f94267abc087e4196189633c5c71320a4d2c962cb5618a73025b596c0361a9802a30cd8e88abf1ac0757686fed7ee88701876edec9b506cd11438a75432a6d2d5ab906f99e49ccaa715567826000a2429d653e10c4bc5721bbe10934d85ef8780366843210c1032fbb62f19b03cdf43702495353db950dd5524321c7e5b5c18c18473d627af884113181403e5498a8a861de63a0ceebc3530c164f613163c525d428f591e7d6b38dfee4deb5d202f42418b6a112d44abbdc0a9955eac615bda6a6c080503d6591efa35f7d3d01ea10223efad5b4d888d49d197ad598e5114d118b02ae04022f684a7dc11445d85a1a8ce3e8cddb2ac35288820b263e886ba932cca5a1a40040acdfc2dac0389fde32c5807d5d3a2bac83e187b7bbb3eba34c0ad15fa4ff4a59a84900d3e0f7a94b2ba4077934d4299708889a09bf6941897ef4d54ef425afe9521aeea7b5da47b685eb4586b3766888329cd28b028be7ae8343f314c2864a46f2fd8a7a68019b52746282329c0613b0d810048010cfdb02fe0489b29eb5bb518ce9f5d44ceae9e2297dc03609e667f2f5bc872d2e0174f19bee0fb4d65afb124a2a661d1b00409f872317e08ebe00a75350239f495433e8e1c439d62e8b41959103400d1137b657822a277e6c9f4af126dc582c26c0b80bfb4c5e796b6f53b4538837df3789e7126d836c4c9ebb76fcdafe1cc858b0746840dee93f074d678498712c3009db34800e76f5581a8a02cc196691200645d057233a5e2fe6fb4e93fc414dcc8ea5cf3291b09b903e4747ea3d7fce2c7c27add38cf82de84d581cd55381a51f633e39b8c93a596fcd83f6161f190e8e445b4081639e0c3a2bc16a0ddc1f724dd9873fccdfc08fd8c992bd3d848c25f38b459d008898a07fca0221488b735adbe527f14b932d4bde7446456a801a447d5c9187540d3d44970cbec3ce406dd6a3767c6f2bf499b8ac044b7949087382c09fa6133bb325908ed0a7ef1fd750670108aac62d1999084d211d40e9ba04822e006d215c3338e35ce8c3ee342f0eebbd14d64d00b7f87e821cb600ce452713f0dba55cfc3f514f13320efb7e82608548b4f80cce21ac7b45e0a4c642ea2fc99f94c09162c097441f0b4c2079bfc963f17a04c5abc8244ffd3f6842d7ad118b6eb98d2221f2d3d7c80858101d4b7582c8bb93da6aa051298527ace49a05a56d52ac3fd37825548980414049b2ae2cfad02d19624c025a18c3c4ac804af6dcc22c6070276946f326b87bbc34799e3d60714b1279c2a00cb10ef4f4728161c368e3cbef67040ef05955506f6d2d729b9538f210abe21f0a246d4d70fb81a8409684b4d9c7d1bacd8ac0259dafc16c6077e1cc363f2e7dd53ab80f3a62d3e311ff11d1715458bd139cddd8b8d839892c78012c5755c4bc73e18d1130b0d70600cd99f44eaf3c5ffbc89b0f11ce348b5210bd67dfbf958f8a0018cf27bc60e3aa436d74e93413e506a5710185417b4301701ff40de9d2f9ac8a24e4b3d774ce9ae0b3606492590e01e8d5b9c22eaa1ab58379c602cac366edb79620a3d0416e211b0c1c4d85f1eefa78e7daef1060b88bfc7dd10710dac09d60ce3d7560659babf11be018ee4a84c05433d65d8609387cbd10c35860e1894a0477ae62832f1c3571e9192686d638dd43a5d56d3ed0a60c5cf25ef25d66120707da4ce8c7f3746f2a02d04d3cd760bf9fd30f68acd18103578e029fde18a0ff6e8a173770c54907d5bc6e3733af948d08b5f19126a12d75825d711c963c5a361b2204a264d7c4ef8be29e55d388da1a22aa0c0bfeb867a2b39d43d6d04b4fb33838da1892cbc93060420b04213bb97dcb16b8630bd13fc0c40d23777503e60e385744dbfb8968ab6945ab0d52cea56cbf1ff482bf3ea2422406abc282162e930f2992bdae36e0398bbeb005fdcb168315f54a245e9bdacf8531ee7ebe6aa3cad5453c449785977a5a1436229ac5c1dea661ddea5b522e3a0d374069602829be13e161eb7293caff4bdc64e90425ae3689d81242a6227776148acbcda47c24d7576d9b3c55db62330b63dc8028817e2c7a4f4c6559ac92ddc05133e70701f415febcf2d1b064b7178148f60a32bf10b27b328ff8dabb0d688ab45480f822b21c855ccf27a6d7f16888c44cea2b614eb5beafa4ababbe1d3f66baa80369da1e0d2b7964b4d5d6f33b8aea95d4719141300861d452e318fc63397d3effbd8cefea207c82844570672a582ae64f300b6ec358355bf4efe7c2f659ca3ba42a801e5d476294e62ed53396de74679314eee626dcd1bd936e54c5abfbed10725d2b950cfd5b7b8b1e303e27cf19a119b811b250b19267a13e9f63b1614ed94819a7f588e20d42890c0bd3755dd09a12caee7f25a6ea127b8171a312bd1c35d0424018d7238e4aa55d820c377321b29baa6f5d9a206ee19f877aa5d198e33d6167262b06d9b0526fd4c88cec127275bc022e55c56c8e889ba98cd8dc01773209b2d501ae3297b2a7035c652a645f075c3449960ef15ab1abb84e54c8c666b8904b881d49c3b5c01b74c0960e0617dad654cba790bef0866c8a64f296695344d02ac04c79686240d47b0ccd40c82b807660ee5d80de12c8410668c8f1dfba42ca65a1d518905715f96a0259aaa79fd5afad03c9bd6d56217e01c32a0a0778f7461f8e953954dfbf239c4e18b2c41084d29402399a4262d078da644b091b5a2a70b7f149ff529a34bd60311c64623d6d79af9441394d471c6b4353219f7966529736509446aa31d11452b38c65568a9460a5fccb0d4457367baf58bb5e2de64d4715cc6a4c8f6fe79d682525c3a199485b9bec25f6df0e2fc385549ae6739b644811b803a0c7be56260708635c294e094bd9bf84e60864fe90834577aa565d495f7146e79078c953487327d1c264be9d9c6277801395d68c768c7cccb6a872464898772f54d851ba12dc3d2abd6901b6280b2131865f1e0a123b07a746945c409ce39b97fe716c2eb52e5828e91019724479f07ef9273dfb34a8323f95d5646fa9b272c2a801a5594bc3ff86ee37b6c381ace488a9f9d8849be8b31a89dde669e65e35a4eca44d416dca80ac4df94d976d77ba6c5b3c32eff095ddc6eabb8defbc73a6639491ed93caa1ecd8fe8c383af34ec6144eedbfc9b077f47ba7b7fc2489c2b12427e0824bf8ca18b2152260b0ba1650d8175aa1307961902d993b843301b3b00a20b2e9817c96e3220c7c3078943f7a877f8cb56e5d0770c821479848536b55347d36d8216519ef8740c78c73ddab2dc5dc0325d234900588e902f95294c6b5b63c424715ed3490c071afc65c5bf7210003f3182c0626fa2413d6c147b79a0de0c927f47bcb224e908cc80dafa42304ae484a76f1e22b24d66c81ed279ea7c2426bc582700c13576d7dfe7481a41a87d21a89eb9c708a900ba93d8cfa902bdfda9f61404d77400dbec33bebfa314abc464a96f90400f5263988f1b42f45f0f9cd6dfec0ee17d7578c80121b2531337901a7785a0bf7e19de7cdf8ed6d772e24fa0897a3e76fc373b262702e9e562fe4036299370648b3ef8cb17000f523e408e1d325a529257f92b2af7ac73da2821a1e88fcd3775cb8ce4ef38643dd19fa2dfb78ae78f9724256b1644fb85a5dcfe5fc9d70902c15daf1f7d2ed055337d21ab37efbac62d987fb5e0dbb1e2954eb97810171658a13ab440071454ff8ad7e9ed25ea6484e074d579d88ebe4e7fd9b811bc6806970257668cc9ed83240f9fa270b31c37ad59067bdcdd0a76ff89c868ad132ef883d764ffb204ad2b37f7590f76c2ab38c6c240f0b0a1e44c5fed391ef4666553998237e05080a9d77f49d6e59ccd7d147faa6ac28edc3465c8c7e02dec216810e6c835e492563695a8b5144d969ca5aa749eb49d2a8431327ad2749a21a4d40b914ba10461339e0363afcc010240fcc5041836ff7be0abc8bd00fa2edc82a7df2c429dad2ed78b60278ff5bd258665c68d8aaf508c859421a131667ac98502276a1dc14d6973e088be092ae982ac4d6750a9f8a1c7aa58dd041127b4ab1210226bf43103e218bd35fac069470a73660233f209d9d37c905623145e24292b50beb628bef11e93616481506e18cede242332b5032e2139ae97b2643fb994de9a255d2f3253d086effebc79d101b97a7ce2403fe8f345fa2c19c1b44dd1d940fc0fb418cd260ee0e2654cdd913dec700dfcad8452c31aca7a1346599a30d221ebf789c8cf42028a9ebc182824fc446c1767b832080794c063c32b255a3c493e49e2a3b133cf7e898123ebde9d8c2c223746962874c99d5bcf1a1a72a6624e842ea9886405f4be3a30d525196aede763a669ef9d616a615df7f1eb43d2b263ce190640aafc2587b2c35aa9ff52cd9980b6611194ef713f11f14e2c8c07080ea6a1a525274ba1296a22e478de94c71ef87cfd6b15050d75738d3e840b537e288153a3fed4c9cab60cbaea47a061f931b3bb459b154b05229fd52d306f40da0ead1898d96ec5a8db0c4f856b4cbb2384789400841fbc7802f927d9539133d18fba86e509eea45d3bdb97214ee11280f620c9191bbb5a0a7e7aa4aae179396021b251e48837fed3a8579b9a3b54666f1cfcf47f40c2cb965e90293854d6584d9f8aede2b442ff35fc10b3771ad7c38feb9d18499340217f491feba5b5fea1337e5064ef5d0259f32be418c1ba05bbf674a58356e13ce5c9c1480a30a218e07e645c8fdbd5891a4741803ef05cbf8a2e36231482956f00d8d07129413c04c62770b59ac752fcfdefbd1229ab272ca08e30607bd5a016986f63dc4997305e811cad06b1ca14228a7cd7b85e0155e919faad1d6cb57419b5d3debe6bea92bca680cf3a2fc7b314ea7d660fd7703a81e94c63b7010cad907cf7f21bd651947d86932059be5e8da4fd312fb389164033994440233206ec709261bcbe2c92383480de5d6e39abac593f7cf7b76276981b4eeb098e59e202d539d2d306a7436035b68a8f096da9f1916325d30dc662c2b4ef8b38d5a3a9c5f9691c217cf805605342d0cdec944889085feb7a7fc317682c6e953f4e9db0ad5810a06c96a935f248b893d637a688cd642119b6cb2af81c9f46610b5eee6a42e9d5adeb4c764221321da08852568d94f50a977a59dc5e6843ba047acc8fd219282af624db2743d2fe588a333a195e4b121326d97641ab86e7f27a72a88c159111ede1a3c351b7418e1d30caa4219f37f25ea371d6355cae23271fbf86c2ca1953fe3931c628075ff7529465fa5714a2f4e083f2e8111890bda3f595291163433eaddfd2102a5458767b7d326285b42b4104cf5a97658b6c61148c54ba1a01b4457f6a0a1776f3afd7efd21b62bf1415e678985bae5d31b159d255d712f19c30f6ed98cc5352c27d9dbfea31c705c90de80ab94890130eeed689d9350113e694bcf2d27b4f05860edae620c9d7daa7ddd333d0a93137096027b3c40af378d2ef5b83184cb132c17107a0204ea5b05082f589c56045a02ac451b16f10273a15a576cfa4b20ca91722e24bc14ef2ba007fa6b939cf3e1447bd09043b5f46db44156d7fd6c05b7035ac8c665d7fcbb69924f65965d450ccdab5598aa460073c6452955adbe1514c1ab44630aec7b91c988e0d80a4c2425550a987b373bf3562ebc39fb424ac73f5120e18078bd867260ff65007593c8f713cb162b097b75b5502a6e9fc27e179e9f72ff1bffacbff956fb8d66e641c2e0e1d1d6e4117199d25c2beba34619f88682bbd505360f58b0a3c9a1fdc727dee54b5b0d2d9585868851c7ce7596a38dcc78ea6bde82a730a34ec79ace947668c0878195a0f8daa6b334b8adf341af3e7e27032f295b697c24fca32234121e13558db7551d22f62fa49324413ac1c1ca65538ddbbe33b866ff050b3bd667185398ec352099229501f2d6e3d837add8aae26a176f451848a53086ad7a895fd40d841e2af22b7ac842c0dc2e88dee1194ed97f623b26e830562de6bb6be673ad9deaf256dc168e9cc3cafae86ed96dee3d3d25c7183dcb80c8b016decd1372abd7a8cb7d0f78dd23261492aa65749b65a766e16dc33fe63c2ddb6bad4167bca5646bbd792a538f4826e51af0d0f59b244b619559a0a2be45fc19cda4628250c1ad5528e16cade085c631b3bf57860110bcc1e4c8a960b8ab888e9af04b45a0f85bc3d474395643d82da8e9d1bf63ec016324ca590000e563afa5e3f5f05253bb40b0308c7a4bd3daf38b2b0199eb22702983e00a25cfbe528cad65c2a74107638cd6f4963f5c1d7c75b39c331b860953b23a51e9f3529c68e715537bfea15d5745e00f65c33bb0f295089030c7e922c156d157211c2cb306b1aa835cb7623ba0abbd92738e120b542f814e699a5f9bb842dddf979f43e23497ecb747d2d48496c0cc2112d188d80c5ecc861922e1aae9e434f9f598fc5cfe5dd4c70627ef91617e9530996ee3b303506fa0b874c484c32e9ef15795e4fba7fadc42560a9615e01685c6ca58e6e0fb2cc8333a6b1b5ad50cfcd4aaed85567597003d04fa590881dc83c15ab134323bdc9b62d3dad01e5d42c66bf443743791edac5029672c004adb0d4bc17ed484d3c81273491ff51a0078b215201d8f12541242ea1fd7a524333788a5ec2dc0ada2ec1f72015f5f211b13c0475d8e7235d7e72b082921b0617c8839e09da057dbaab5bb56e5030b1550e032ef0a64930c3a817dcf64a8d61585e90317dbea6e1fa41e7815251a29bce51578746b6df4e65ab4d302edf6cc3e0457de4d7b094679068b252287e20e9219f338fc4a4732bf609a6c21b1c5346987aa7a10b9b62b38aab5721f9e7e8e8623c5e91353baa389aa06147552cd9cbc94f56732f52ac02f91c49822e3c1707f63aaf06d5c139a398af0f47de22474dc2898032a4f62c719e7cdd22b65ac5dd631614e20685e3203337c72a4d39a02af6de28e39d4d12fb2b050f30b5ea60fdce81646ffbe5a84436edd3520398e77bf95bdfbe94365d3b1abed54b4bf3b9b79dc6c14d69c650f3b9f345d27b7e918ab3f4be81bf36c282144b35f3aed5eab18a0e38ffecfb365df799323bbc0f9704579c62ec67e15beb23df27a1a6c79068545d04e560dcecd01fc8697eaeb71476c31ee9baf40d147d512852c80205a881e52568f42c56b79d72e3d7e1d805dcf11701a10ed3afbedf62ce6112007218a42ac3464f75649070da00c1d3007c1ec6d07fba580a9fefead0e5db5f6227723a4804d745b042a361e11e5339247ea67f35771ac440b56a5a7106914333cf15dac7a86d54975dfa4fb634edf4e49615c8c3ecbddcc9cf426b1ffb0582b8619d2b1aa5c398c37c3497ef639671448b1d5a9f7b1ad097d31baca5db3d905ca63843543c43d0f3ed61ab040ab8c253ad173eca9bbde2d1bdadfec43f1ead4474978b49ea9ac4c1ef2ef3b77b7baf8ab16e595d67cc9be108dba59f3118d5283acc9a9a183fd341931c7004e723012c5bd92015c45bf97740098986f64877de730e0944f1885f4b62d2438141bb4d408a3decfc9e0efe3ede7d6580c8f537c460d13f88887b56c12e59b37b1e2a90b8724683a51064fc7cfcb6c496d26764063d3c47791a6d9735411db8759275058657806307f55c8d8ed06c5b96b4aee462e6ebd1c92a2de295147fae3d90fbf876bbffbb36ef396f4d75d8af4f777a725f10732937eb6bf0e9c0bf18377b51dc20470477bc33380fc343ab83d609caf3c7c8a7c06e4c905c64616ae04e77fac9862d937a1e7a147a5a8532b0d557845ea846057cf6e12887ad6d300a99933dac8c46a4fc5af141e685f6b17a2c802ca167282d9ec0c04174fb0d00f8556da4b87f3f3463013f8f10e0426dc0f7d2b5b36a08f34c41b6c1280283a430e189d3ab18c28dea10550ea4044f1edfef3fc75a1cea711bf98f16f59e439898ef6c13f70eb386ce6419b2ae608ca53d39d3705df74dc87f3d6a234b3e41ea66dae2e167ea7145c113fef82a13c2b1373826d72a74453cdabc9d712b046963f329ce7b2ed211747bd52656b3eca823d536f5196eb7f4062fba59611550878fb24305027e37bdd7fd424da3893cea2f5b47313238d7363072bc897becc0beca77120b875eb6c1cde7c782aebd0ae17d073c2b1b80124b1881aa68beddb6abc3b2bab1e9a84df5fc533c13c63d21e3c4405765c4c6844df93b47f57c3379f8c22abfd2bcb553d071a63122077d646a4f39201371f2ac5c744258d8383891c4cffc4c193b409038c38d9ddef1021d9eba9433d086d1352a4ab3ce1b829524c33d2b4a120654dd96f9f8015cf5dbe92a9cae8a01a184d42a24da370e58a74f090550094e40a261686e45377a4555b9e6724090c59add67ee9c7c9103bc2802eb38faef8b39d5ec4117760febff4d45f3b204d54005173e513fc5f34e3b56647bd9b9a9f0ddc29d3fb56e636a965868ed5e648e03e582edaea842f632fb0824852d03bba4fb1051a63eefe8798576e674b54658a83159cf34f0e1d291b759e612ffa5bf40c1ba0ce700dd6600f783085f7fb5cfd59705236ff64f5cd1cabdb2d434abbaba3ac7a6320d24d53782d01087437c01af90d41da9c69533790cc15ae3509a086af53533084299165dda351cc3a5735910b8c50921bba1602d989e4a41c5cc4c8f56e9b22365b03756b9e10cbf4cb425b1f7b51f97c405591d781486cd6d920158be98c53663b4b39f5af111134d7fbf57f888f01364407da061f0e6841b3ff9f25c73dddfa395806b4ffd151dee699ff13af6be73ad1a1d488af7006563f85a17d572481aa9dfdd39fd6b039a98b58d63c15ae720739415f3294c96d2915c07a56c682a50b6ace52bd050b43b0a997c8c50d2044162446affdffb86f6207b04d302d5b5a01836618cf80cd09bee836f2cc6d0f0141f6249775add42d2986a05c5c19439aa98dca78f4ae634f8eb6105b8202f761c5e431fe186c3402e705580902d800bf5de6d596ace11487229e753ce472b99484f4e5150317426e98dfbb2e57555d5aec38908c0a9c18a417a0bdb7d8c94dd205c43393f0186465eb0bc60ffc818ac24b5159a977615597395357e638dd0a4217844626397eeb9d651d11d774dc64dac0bf7085ff6ad832e9794f20595cdcff3b353d3767ba08d022c0b6624e3e21279f305675909759bacbcd209ab58398cf481366a645eacbc1c322228a8d1b53c6480629c0ab339275f41ddd3c38484a0529768689533bdbe6dd5fb5be778b866ea9422b6adaf0400c8b9735a8cb06065cb3c580b2feb31d65241fa5b4063af19e886881d357e5546775e2e26575ed10939cd93ad6b11c2d78701fd1d2396ed9a980e272d7458bf1d57daf599b78d65e64a555687559632ea7c06b679b6b2fbc98d4fa131d33f8e4e7513c25cef3cbf56cc2999d23fbd2b89b8607e3c50a88a04e32c5bf48e95991268c62bb0c2866a40947ba6f231fe196059bbefdb49717898787c9ca49c9e80fb689a9ca0ae446728b3699039570de805d97ced7beb79de918cc786a6dcf663857c7dbecefd2920603504c2265571b868e611d9a058d799786139a7a141c34c5bb8e88aaa656667fc202c78ca33151f2d95fad839d8a7ad1e3ba45912c9142e81ab93c29c7b3bcb60abeb940278cf3a03e5fcd1c1760129eebfdf96194d048bc66eaad50af62ce5cb9406fdabc9c1cd478f1755c5e904132691a46b393bab5e2eaeca0b8128ab394e85febd8dcddd7618c5b44697e51b81e5adf7394e3ec45275cdaa604a2ca15b6ce89e2e3dab7620efa048990cd1b02c6bad87605479904a5185f7b3e581abe9056d616a2f66c7d71680c6ccdeebf40525277bb742f59c0a478eb9e71f8c71eff25a05619f18fc0de0677f06d4cc712d3d3a31d1e112fec199c7386bd2082a14ecb25ff9268f64e181fb7c7f9487662be72ecc9e04456c29f2672157de3d1af9752061f10713e064fcab4481c8781a9ad25e313b2445b9ddd57235fd2197117d8e9393ef04a05dafcad76235750a4dee2df80942114dfb060223db2765db56a45172c328a6fb914d1f558fd0433f6380f4e109ed13da1040d4c30afd766c4186669b561fb1fdb6b3768bfd212579675c9ac78b42497cc8d3b2210bed29c0c6d6924ce6c23912cb8c7a2e211250169a842388e6b06daa3509f7ad125f4b5ab1765adc4995f1220b5dfbe6c1604a8412989d55a1a312eb0cc2d7b038f10d5d1ccb6d6358b375053a8738aad0be563698fc5cc18ce9149daa70b47815f9186af088101b4d2108791348d564fdba9ed0c3c17c66eb764191f8ef54861d11c84936f24052ec90bc829b45afc8cea619e7159c6ea0ad714fc2e8003fef8b0cd0e135b092d0650c43164ca59b0a5e5de3cb13dfa41b097522d059aa16733e6c08155f4e5834f7426a84f976022608eff5541d24868340a8edac619c33d1bdea2c23a2b7f6d31234a127881df04ea714256f420f0303d8d055be09c8c5ece741393f5d7e2d5a0b2a05737edc43f80a39baf8d15f6c7126ea9cebae94841cf0cbc417dbfb7979a39d4b2143771e2ba15beb7fb04e9e894a2d45197190fa5618779dd004544d9920203b4ff303f5194daddd80a1ca30a2d2b23e4c826e2213f38529a9c9c3a0b050e01887222a71115c584a0073b247ffae0b9ceabdc1bde80655195b71a56018b862b3f712f454f170de292b7016f2ad8d5664161fddf2b134b6524536b4105c2a14c042908791947c733cccb09406bb4afd7fa9bf37772ea7ad4b2b81ce3b5422a913954b2230c739867236843f1c9ee9c16a9408ed36cfb4c4c7da4c2e7a18f0daee9d7d1e5c58c84daa599c360d0d457543c2b5b2d083c4779e91a685a50b06edfe83d8e9396c71572f02756b35a25ecc4d530a39b6ca05b70f646705fcd3e740a0485a763c7b52951e586c688f264f2f75d50207ef73e072bb7e7f65ef170e3424715f4543acb4c27ae6c237b6b79e75048c810eb6a57e64271d97166110c6c49bbbe3dc416a1111deadcfe83a20806362995c8c18c99beb246baed5599a68f194c53998e861779d3bb8bec6ea22c2a07c80c5d54238e7a050f686e08f045a6234252345dbcd15980147d3aa856a5538541e55fa60bc0cb671dc28fa3effb90995a679e30bcf3035da5cd971a9e9ce3453730b715f263373c50b1cffd0fcbd6c566c5b07f9407ede037210452bab539d54c8c17ed1d888cb342f560179d4fc0c6a98f23c5b08264474cd88457e8746ab100f2a16638abdb04356db1341dce4a985d7495a60e931c76a2e0f16d1e09b51747ce8de3ebfe34bbd6cc39624232ec38f754158ea924ca145e3733dea39768162b105a80c2efe204af541c38ac74ca49f7fc1d64e0de2582ba93591b85d0147af3c658756b698c26222d6abc4a4cd520c10caa069cb11fd5ac85d9b8a5239f19a46ac22f9215a2ae399cfc1f590a51b657e7789bc3903982b510349750bddafff77a4eddb2ec55ec650eca87af36d1e305814596ac3f839e5f1b12351e4f448520d56931319804ae63637ac32153446419d35804c899e0bc5c3467f50a8d0de54949af96fa9118b3a68da6f1637774c85d2f1cd239ef316d26f310b54c8391d949785e956bfa0048ad8cc010e1e819433521f872d38b6bc9a62cdae01e65ca6f8ccceee0dc51b3d7b5c9efb75660162cd080f8018d30dd83f74e816acf21c2d244749be50a38d0b4a7ceb34b69e724fd809516a682a42a03653b2a20d5ff985e660d69add2807a4f8ce455b8bee94ee82d9227d680ed595769af033cb66cf60122b35a22e37dde2f0a40ce7ff9321f96c7a0421a9ed901af7019c5b018b79ecd178d6ce6eb459eb7ac30264c6c7922bb2b60db8319111be4ae633661a3353e7ef4c0dc901e2661432f50cc6b0119fba62d9bf71145bdcec7aa8bac1d635f5e17b4ad71458af0b372a27a48a0b4686c4b1ecf8a5e98fc3c0a2380eee6ff69700c2646f60c583a409c697e264cd81e8ee36041d318a58fbb818f85d3d382b480e74a311cd9fd387d2c56b0029c49f8d89ffa5fb9f4eb3686765b2fa240cd04889ed100381262fa2f09a1d12e4063001b61e928ec02a668fc82cda89e7e3a04f04211ec2364f9fca3398688690580bf923ac83f849b515d90749cdff083eae87057f3f356b448708fa5ca66a1e9b3c8ea8def080e6ae48421e8adb0e103f8a891b9796cf1544d58cf80a0a0015c98f2bc518df8364d81681409d6b904b62c5996441b1ed07923b06a3a4259377a925fb42a8a7b99fe05b6871299b1861c75f63cc805906526d370440694cc8c5a0d81eba993929b1150976431b78f92da850c3752c8ccf5b20323bd3e364a6246359d87cf240acbc99c14291f0f25a5ea2507f9b4e04bedb53e44c724ede8a369f72a03f8b56654505631e175afb1154d3349f206173d6a2d103b1332c463849a8a4d28a0c483755055e911a221041a4c6df83131fa76b9260fbff75231e15370bff45368acb8c948a0369d4d0836359d6234b66b56284263e840d602cce94d8d4c10a499e274102f6b0eb44eb7b77754acad07d07158e73dab2b4b90cf696df273703342d0f71aed6ac8d199fe374de928cce9e66d9610f7dfd272d5f6916ed039c533ee38a1a1487ebd9bbcf3256a1781074a3439dfe84462bb91727c0c16bc7af83d08e3b07f22f86dd94d441c812f4ed74e65218cfd31e6a5a1bfec5776dc544fdb0124735994445184bc2f5a0a22f3a32dc9c531d91eb1b65f5a097a17f1dd2e08c4b4c9cbf2830057ac5c707095a4cc70cb4b3257681c7fd8804a4ae6a1c57da0c0f2c386893b08765ded76316e44d915fe818f870d9e6ddcdbdd47442b124397e85e75bebb2878818210bd1e82636558e9a2c0c32f11bea25814b649b2a028dd64a2488a3864f7e3e194911f93448c3f145ab73394b3a19f295f055fa64e88c1d15ca1a2b9a6ad484e2df68daa6c78028fedb4b08613e6678e994ce86d48526ec8655b92d849a31c995894f22414d7bf0a2022f87a29818610314314361d2c92eb6f805eed25ff61d31e66edd50bb2d666a975ccf2c4484302980826e3153386d568d4d890c9f56167efad615b437beac69f42887e68e7ab97ee3f7d6c993e6e759ce4655929c00a0da4209912ce6a082d50ff468bf6d596ef125fd24ef544c5054f7090b635c2b25cead012718b3dce01ae38e75a5b2e358fe71399c00b38d6c91c589de026c1696eb29dd12998b2abe8ee08dbc35c1fbb01980f732dde60908e0e7223657d9c6483e66e054af81e69420207d88fd18c1570609d36efea726fc0ac32b93113b7fe22c94a4ac342f519ac36a946dc6fb6f4bcf08043b879e988789de6c2e7ce4a1ad0a4cf30372f4b0779457b9400951a279b763adc287b85b80b72d21555160194c4a3ee1fd1b45caafa40683be2e022928532c674e0e56149b68e53c21d270afc64c4d5e2f0e4d4072de696fe0b83f18d851b467770e8876315dd29cc82c5396852813725eb71b646360a68d8c7794a64fe8f71d2b3df49934fe9119e23de020bc7752a3f4e44f0f7df89168a9560da68661e4d91633affa2443b2e4c6648a05a7b88f0ed8d7c7d34f1464aace74c38e91c41b09a350192bc1146561c3e549c8304ad3ab2a0ba541ea573dd052aa75877e983e7e7acb0130684c5314aff95513a21f21dc15879efd7e03ebcca99b1b2fb41c4619a2f8f36b6163261a2d18e94746e242c2cf1c3f9b1e2e7a841b5060896834f63e8269d853d4c5baaf6c3c1e13875a36467a77e938659dcd4858e8a3f65c045a80b8adfb3ebb574337a73c6e39b3523f6eae5e0201a2a185864783641808650dbf180219dcc055b25285d485913e52a28b5a142d337b156eb64ff8916a8c058c6059fd649c51e171c40e4f8a9d2a8ee4625fc279d99227f1d529088018cbba92bd3aa4027f866208cba4104099856ef378e7859f4b60625a39b7fffc1cbb954e7f4cfa4591ff2159159e14cd93e3d0e0ffd3c6df7561cd317f88b958b526542fd9807a7c9065a1819b318baabb4c168c7e8da67cc4164992c5c6a14d4f0b408efb93a3886fcc5905fb61de9d43741b074944de61b4ccea752db37412b22e71e6976f8362327e95dd937810d7d4373c6eadaf2a889528ee67e4ec93191339a7587548333f93fb998dc433703b7f58f1f5d3b35dc457a222a389bd808fdc2b8d3080b89214ac9f29435e83bd57546a2ff6b06ecd95e729ed8ced1d5e4c7c72b5080a74269780905cd38dda60af1a4d3d337f5767640ff52c2090ac5b557aa859dea683ed4f392976fb205ea301b4d9b94093bc33f18447886f5e5f56dfe54b6041fd82744e63f4bf79bec1436e6c1ab800f4c0f1b1f2f432f83f687b482abc8339ee41188317fe81432d12cd7924f8d1d0e6e944bb1faae9ca9c79a9ef4eb029a9864872368247cdfc01cae5b4921377122641503477f1794dbdd4d13c38b636713405274f5ec65e00813e6fc26dd24cc5bde880abeb89ad01ebcb98f6a8f6e9b152a209005df2e082e6717c68879e37f5736a764c0f4e7185f7b623125c821fb6878c8ec309845284eaf1d61b02d335e538404b9728ab0bd76c503c2246db3e330e340d3791311558c8ace01ee43f60a0f95785649bffa78bcbbad4e26041e5eeb702d022c8fece65a9b753accb69afe7a6bf2642eb4ca8ab3b457aa4464a961b87563b877cc7c388096410bb7bc2367aacda9912d2270745334baa80add244cd3ae87b5360f2ab610f25efb08fe1cc722ce8b94a00cd1b469648de27a04296a032064cad9064999ad06c6643ba5f7659f408235b43881af62750aa7c827fb244d0699daad4deb7b04b87d653b6905cb3e9da762844458bfd9439b08d6ff4591643f9cd64b118c317649e03bd624ff6db375acb9bfdc76f908b4be4a68982fce2588ab5c86c2e9301c3c0a6751b984c3e110ea55df4e4a9614e84dc017440228764a291fb77e31def9a242ca1e460c1faaeaa27394b25b57ee7fcc9104df8dd149b72c69ee94912abbc452605f6b89b106bca1a2bd9646e0b705e4e2e5359f053dd95052f23648bc97ef3d06bd4d847c4d3e50f4862c63796433c0964994d4453256093911f37d8f0f1528c443b0596974d2c8215d9c3521cf4f5f06615be2f2f1a46a1dc8576a6ba69ebaa7915b0e32fab3ed2c73cd58bb5de9ac443818d91c9f4bf5aad341cafd071d3ef2a3203dad797e4146bb73974c4c3a42704dd64a904c9dc700c5ab22dc7cc806dd68a59ae3e05d93e81d03b981cbfd45c0e081b239a133653ae5b3631574037c070f87eb11a5bfe2778c5483fa8ad9b3056b9e4ff4614a5b58b0218c6ad29fae3864ad8947ff4176c8e001cbdb1985284237123103b4e68507890ef0ff7153f114173c257cb5a765e6da0c450fff1cd60cff452a60f199964f124f73590371710b2c062f613de27de394a0da3718c2e106da14438a3fdd04eb2097a0a6d0bba32311be22d080f90ce5a273754d0672178ab6f754d8f5c4621c58aab4a1a7d6f06e7aea89ca26cea91ae113853aaf87e3656a6ee2abc7b0dad093a88c9d5a75ded43d85d2a4491dfe178403ad60b4d0b1ed6ac78ed21998f59a40578655fcdace46ab73fd962938686aec329fccbb0661c77ce54fb35df9b23e9bbd0381e64591f5ac2a374ca843350298cb47c60fc58dfc48ffe7f967c2c44fcfaac445eed2f1827d5d27265491de4a9a9c14f16412a6cdf9bcaab98cc24d3af383b5041874a6cc06cd347d9afece40f030ad8d9f501e7d98127c76bbf20089bce1167a56058d450ea7c96b2ffeb15a5a938f583c2c81ebf4ca69566f803ba00859f7847301513514fa2628868c3730416b4f23c365425761d171cf99ae4cffdefa37005e177941bec89ba8fa0b2269721438ca15d89dc559b43282d84e8b80ef1cf2e8c070f84cbc01d8678858e5b67388ee47841e02b7f7c5d6d677003f40df9ea1cc7b43633fa074d9a08ca518df0b7b3b75987d9fcfc014001e3df9c4de810e93db39ba767386fd0db643629d668e61e70909df28a5ba3bfca05fc207e8b48336385e857299c15b5ba455d33a94c98ef30896eb49cb33cc27652a2e2bd1a8f630ccf23f2155e1fc17cbff5307ca2f4e6924e2ba04c96d3ceac601f008fa30902ae60ecb732129171d6f9366b02b0d8a3ed26726673e16c2bf3e0aa99c69ba14447fe859709385ec3fc3f1bb0b7cdb61a182e7c526a002a863ab01dfe60e9f1d842d2fb85b8f9ef15d19cc3d0e838fa2a06b6e26df75ffe7548eb231c46f008a56a702672e35f8b89a2be11fbf87e861e30967ba08c564c0d7051bf98db7b50247458e681e8145cdd963820b4aee9a8fe668b5cb526023b00a09cc354d9cd7706c952e1cf8191b9324b037782da670681d83e47ebfe1af5d2afba2bb1c82554e79acbf3d06dad590ca9e70b44c4d01a59b113c9d1b20e95807b5004509fc31e8d407c52de21c6da0bc6f4ec758535be47528f2ab2775c28b4e09849ac8561ef2465a24cc151273ee331db4a50b01965f7cc39061e05a7c838a002e464668f0a44a36f5e3b66d35015b9bfed4d0bb1191834f0aa3930a89b922d595f0b1ce04ced9ba9b21eacac0c5741b38605222551f75d26d9c1f055e172e06933740270a40e70484d2a7b4e12ceb97422e2e18fd57a3e7c51a129fd80fc7369de98d6975b68843c32bc8406aa1698a060c3cbf0687c699d1ca3f2c91c61c085dfd19bca5390c9a1243a778484d6272c32b115b909e1f845aa7ae7f38180aad3f0cb01a9bd62718a36cde5f01bd4f676fc2744b4623c442bdd1a2938c17cd6b3fdb877d342fd4f3991673de6ad2f28eca81d542f80b11ee8b604c4d3821132690e3609d1af69a370f572acbf4f50555d2ebc15c42763c8642173f76d0ea5b457115950d94d776e58ae19b6523c1504f8c56a5791fb40db1ca06d50c1a414d18557d76fd790d89e7df59bc6f2f0d4bb12c94a0b009089eb85d45742bb236c7f506afcb74449f9e1857d042c01c4bb54991591eb58f1e8db6a02d2bfcfedb1b2b78f56e3fc942ccc928582c39c560635eda284cb48bbe858173f5ba19eec2fdafbf4a4195ea6669a0000a67bb8cce488ca56944738434763828e60938699815880104895427eae9bfe668105affb26a29fff4b1e1a65b76ca3ed12d086dc214c9dc649b2d0b0afc4e5ee5363c156ab0da78362dc91327c800eb9ca3547ada86065d6ed944e9d54902acb8fe6ce32cde75f70ad70dec68a84ee4864889f24b3c525481064743b2814006fb89e7dffd8c18a38fa9fa446e72886d25b0cebcf9e5be31de49ba66e3a1783e75f14c1e109fabb60a92c3203287c7e142befc3958982f281c2fdbfe2e1874bdb090fe65dbd53b0ad17851cb11f435bd5a1d5954dbc90e9b197dd9f5cfdbd64b95e9e8b8aa9ba1ee191d56ccb77bf6892b587671286355005e4b3dab3f614c3e5983c70d3b7b84b6350a09cb7fdae66e3d53b25640fc27e4393e60d00f865af5c52264e52672cf532c62ad326b8392c01016cbaf8f50516325a253d8a44bff17215bd2c1ff913e8f568c5080936da9900cf0dc09ec666cbc41d967b5c41d9080923e3b82c2fd1ce2d491eeb32724c8f73feb725ccf97b196cc4980c1b73264ad83cf67f081704e90c567db09af89da29a6c298c5a0b97b05038e8c78ef8d0d236c5e440884e1e20354d10bd4091f1900a51104a21c7addcd49792da052cc35574e139108758f288ce91824668837ebdde0a00805fa594966a8338594ab478b35a166d02bd60bbc5c86943aebbd2a91c88228dc769680db2fdbd1dfbdc76d4a66044474b767e6248175a3880926d1e68bf758d8de11570d8af8404f834577f368a229cbf7bb4738be557635a2ad1071c663b8e830c162272b206df234c1ec939d3e416f77e272587c0d2a13209d0bbfc12c7c288e9d4cf7829b8109fe7e8019ac01635fd793e1f6d0750a66617c49fe008c1030e675ca4bfbc434a80b9f68bf5be3eb93283e29bf984acb17e61a523dffb904bc90338b6acffa4bd8610130423306a067247e9b4081ab95741a22f87025445920d609a06bbf57865d2bf550105a91318e9fac318348422aaa248ec365340c5951a4f4bdc24221a342822a904249d89d1d5e7634b2dc4110d9ceb31b2ce45bd01aaf6cee3803aefbce2fc4953d51c4694987affb1c5e1ea814a836b994083df6af2dca1cd2010f6720022471a32d88d432cb62ae65e7b5175dc1f61029b33d6025eeaf1442ad56bdbdb2243989feac5eeda771a3b35c4088c65c7298e7c068c8095a15baddf3a45f68b3dda379ed9b3ca51a61a3453dcd094c9ea76f5f417130100978a98da94cc607746f57df7608139cdac54fa0a3a3c3c572b361fc2d0e5c7182eb5dd527406def60132a4a80b7c6823168cc3b880dc22c23d77368ae2cab284d2e21f2a7a198f51e8a9f2954b74e808ed2294077b036dff75924498c0651faedcdaf5ba5c0186c168484a809a46de4a28519a9685d4b85f07cc854c25ca4b1492ca18b3e07bae40361ef8eecea7f6a642fd412c2773bb2839c29e3a9fb4b94a976b2a5a220f2d56dce0dad42559254e6f5b811db4df907a8356c01afd621da69ffc33428f982221ad7e9442b549684e4272097d84c5ed12bbf5fec1da002a397cfc2bc70d41a55ffef464d1536d78c7c4d5b0225e22c206f3168098ce7b58c9b72d4c60a1261a4de42a040930b2f20983a5bbc79f51bedcf445ffc87d218f8592cda824e247b0b108b92b2be795f39500fdf0ddc57b347463cd754d79e3ea553b8dbda733821db73385595d6a7134217f133b65feccdfcb0f5478022345b9c03bde4ad4b2bb7ec5dba80b6d59f7717b097634d20789ab0013ea98e3d241c2b24db6333b2a10bcf66dc24784864003f91058416b67d1951106961c21d83b27d3e461213c9b4b1e22763ba7d568d665f9588676c943acf1f7c762190eb4d7f70ba8002f3c45ed34ba6d7894f93c900a549d89028a40a3e0c36a29251fc4c95cdb0a227e821f2b7e965c253a858f378da7eeda545e37537306bb8004b661c7dead719148601bf83a36eeacbf3d41973a68d69fe98eff4b57cc2fd1ccd584cc850d401aff543bd31fab887ac8a19f67d12eb368bede7bad265cd81ed944c0cdb3b85259933a2892f8b4280d388a6f646b96309cc19408acf6b0ff72e141fbb55ffbd2cfd4e155485d2e9adbe088f54ac025d2520d3826a78872d227554569dc0667cc649b4b5041b06350d203ebe4c6ad6a28733ab5f3734108c8eab045cb61a6fa41a83a3f63df0edebc3570e7173215e2861411c6d05275cda07b81b4e147b58049df02f1df6c66a7a6fa8a20de2f957d6e0fc33287b42a8182751f64c45f9e34340812b2cc6348aa29eda974d4cd3d848072d6c39f4c9344354f9dc95a334f68a1737bf211f1708560bc9f96b63c799c2c337f319e41c837191102d79b24c592bf1f485d7476cd77358190ece9e00d66dc73aebd3417fe6c669c6ea129487c698ba76412516bf9c5eef104da870dfb6321dfaab9066e20e78894dacc4b8fff5d2b00cb0d376416e2f0299cbce2a9fddf989157a1872a400d48df6fc9b4d5ffa36be359e2c8b74cc181386272f641c92a8bb79834eacffd5eb5edd617a994a6f8391c4cc5b2c1426c8d7927287b6d830956626b2790e338c76c1b66a6daa006b862b1f8a4927234e376ec4042b69c239c5eb3d289281726b2b30ff0070dce77ebf1485b1748fb4d5186bed296c56408704838ca57b48626e1ee98952a14c2e4be947aef83b26b0f9a2a0436e69e21aa81873b6fa78dba3f939332d00505731eeb2ae0dfa62f4ff9f73f86007721781d0464cb4048130c43add5e4b8bc5ee83c01f313ea08948d0d58801881df2b2224ff2df101477af37c7aa9d36e97de6dbc1e8b29a215a6e6ad77aec6162eb1b199730c10707a3116038834ee420e9f6e481918cba27c225f2052b22baba3db28055c7b00d526f01b4a6038aa012885720942a52071a2ecb7250757d5604a6b2821ba62bc78f58f9e3d389f53f01aaf328e91c17001797c3c8eb3d9f8037d9e0c16f9579896fe2ef94cec369aa9f2b08266586b7ac867d1812b4601dfa976caddd726f29534a327e085d085408a89c46c97a94f588cae9b1b740c9504e4ca3973840912b061e2528ba64d322c01eedea9fdc58832cc0e5275dd4a84057a9775df7d58c8c5cd0808369ba2bcd629733e60cfd534a870eed524314b0691df0c00762ba003b0acd06a631dd1016f0eb21e5e6ccaa696e39f50390733d36cd5b20289284f44a5ca510886034bdd560cbe7d0b8338bfee36e632d40df99ee01f7f11ee7f199cb5e608ddc5f704eb839d3a7038c5eb08064ea96aa920242f5ecf848b1fdd47a0c80aa15e0c7059b8c941f3e2dec707516aa27754bddbc85aa350ae5d32cdb2325f5834205a1703658155c1658a8894d4e6ac747caedd04d8cab7b526e5f50149f4b7eb9da254336b74b8670562421de65f6dc076986eef28b13ae5e85d4cb9ff29971f5019066a0973fb5d32cf245c6d555a99c1cae1e1b5336385770b92c2f01a0826ca85b151450d86324056189423c1f8b1545274a94283ca81d1a173b3cdec87972c5ac8a2c6cc13228445227b2f8d2a447da2409a62f3836301720171f171f58e92dd2b35c8aad5995d5fd0e97171db016180833c14ab01de28b0e203453e987882e59dc25e7e2e2b200942d254bc91600c34e640b203980de05a06c289bb33f6986f615503d5eb92625bbfddea768de42edc86ef4e6f6a36c2927a9997f9466718da76c56e72ef949f102765738e1fa9bbbb749dd66dcd5147aa8400ac22ece10e4a486513b308ca27935355dd8829c38612757c4a320a3142ce809ed77f1e95c7c3ccfc5c725e72d5229017f75aeafb4ad7d204064c4215f088c5e7220ca86fa41dd4eb89f9cd4ed845b21758bc9518124c45320cdd0303fa2088c547280e40977fb45289bb752b75675ca76fb4fb866915fedca0995d32cd26135188f9494ccd62c3245bb4dbaf8d87c523f3f364038a89cd2ab007910ae59554a174097fc6eaf4b7e413897fc725164970cd908a9d71f85d32c97d26337cb5d6c2e3f2eb766d119e8f547d9f49c7e4eb6d3ed54e504d42cffa945a1d578ca66f50c6ddb71229b3dd169160be1eb9f92d22c12354bdda460cdf2be3f85936a928a791de5410ae2a9db6d4f558940044a2f706517455c2679cc7f86a0588f3376453c2a42c38844e15e9860e7e725cec519826473356f478a40df19e2c0f534cb7dcef01bcd7d7aac358bf2e0a0c6a290efd29e1d146dce7815141793e54570f186dab9fda12afa0d35433de99657e9ff6a2efb6a5fedabd1523eb9a635ad694d6b9f6651ce718e739ce31ce73857810a8c44229f2b7e9105e88c3aa1329a435f9e33524a51cb619a1ea3bb20c389db2f6a5a7371d3e3388eddb4fb7604fdf684cabc459abd3c684f4f8e3a48202f2e3a5a4053a9fa18c3bf400816b209bdb2dc6c18689811dcaf6bc334cd448eee17718eb134adb6be1a37cd0b4fb61e5b9eb424489e7efcfc06c4f34237cd224fb610cce5415b12c9073f2492e3f78924904b7e2e3a7c80b49432a0389ac4e932ae621f3d8e3d9e031c185df1b2811dbc63870dee4ea98b1466c3884709c208f12eff77494d9b33a6db2c7e351d55802e52e68cdfb8f860b2bc8839b68bc37640f2ab7d1f200da994993277056015ff8c6596095d08271016e99129bde106bf399539e38ec5ad781bde554a29fd6ef0d5beda57fb6a7315c6b706ffa8d223508b74bb3d021ec188e4f3d5be30e6579b33f465536267dcc6f9c608526bedbcaf5e57eb4f25f7855f5e262d550f392c75497798d3d0a9d01f6c7aa422128cc070ba4ce174b113b0be88a70cc03add9f5e2a52588ffdd4c681fc560230fc6caeeaf7f0bb7d54e6ea5b8327e394b8fdb98123817699c4afe65f197e3eaeead78105ec9aab54ede2f3d5fce6b9f8742e3ed5c587baf87cb5af466f48dfa5efa730bf7562154f2029b426382204861a05cc200746c3d9ba7fb81e3b488716b835b97d892158e52cdc7e92c39c890c40d2e5eb3becf6bbf8786b36ede6adafe6aa7ee62bcdeaeec385c4138c3cfcae889999f90beab15ff4c2049099c50fc8dda9d3af4a8ffd9597ef579bab1d464e0524534998e10d1ea6691affe0c12d3762e45265aefa51401262ba2eba4871f18111ecf2d334a6712982c3bce59206abfadbc508b7fb453c8c26c9c5870dd0c5e7f6bbb5207862919469bfd5af461f88ad81e5a9764724520a276a260215223a9fbec05f4b16fba1bff40bb5463f85845e746a1515cd17f5cb17e0a8c717fb03d6ef7b6b434a92c17ee8bbef43df47bd86b58af67b8b2e6e59bce882f6f85ce5568be2391ecb5d91e2a3f3a46f1a065485a7c632ce4982c44891972090c517264ff6d24b1038e76a4e270de1dbcf2d488cda783982fdd943f4a2a7648f6bffd4c3697af00f7f52f41204926c49e5e682d7b3dec8f2ed972380d65a6bfd8aac3847b607d3c4c03ffcbd447ab247e203e6ca89f8c7e09f4924c152e85bbc00e9877a70e871bbf72a1aa27efb2151d43cc3ecb1fa835fd31cbe30013d9c3d763437671230e35dfa813573453f2609907ce18539335b48fea41dea85e187a147ca0fa485c579c11046061e018ce9e7bd41807fe9c7409f087d2394da110611488fb4366ba05b0ace93cc0dcd5a0964ee08e6f37bdea3cfda4511711134bbf389cb4fcc19cf39eef29f7e3c82e405c389cd7464af926f6d63db6d20a09a6cc635c440c469b3c62e2ecbb6d1742ed3f2b2b7c8ef044a29b52390ffc4fffca34ccd75aebf685a1b805deb9b88a714bddbfa879999994924e6effbbe5cf374d93d939999994462febeef0bd2352f7e9aa74baec118a17bf8a779baec9acfebaf63b318d1eeee6e3be79c334b95eeeedac11821284b951efd3964a0c9b7893bb243fbd03844fedc4e4973d60e4a09050a185076a05401058a5a6b0d89a0d894509a789ee7894640a516b9effbbe11490b1f2d6ea4528b2f4aa516a6916b110370542a954a608b16375ae4b48c3c2cb558a263e4e5ce1274e8d0a1c3e5e556eee0a6694405a45fa5d57eb5b486a82718e7b3220ef64e1e3a00473db61c3e0c631630cc09865dcfdbc184bb9fce3589487040927106104cd33f65e080c0cdcb820046f356ea346f311cacea9783f6c31042aec8940490ec26b75fc4398fd94ad7d9efb39dfb652b4baccc16124d7073c6fb239a492fc142e5e01f4d6d592aacdad4d75cd51fa6a9b61a496d150821e018579d348b5fd466fafe1abc55fad7a9db540a673ab4f2fb70e41ca269df00a70e72b58b53438e76fb6dc0a1af04899e786202f6bf4f84b12e6580567cb58073f19c619271ec1701724b9e27b3e47172fb6dc0c047e413614eae77550d371c41526bb8e1483f92a66f73b3df81d9b23f304db59933f44f5826d0810ecc36a695253232457aec5b0dcc466446d9880646b8ddca71a7d48d787f13dc64daaec84da480249026979db052e066709dc6399c44d771134e228a24a0502dae88829b10678f914582198cdb0f8070fef0174e20bfc21c78890e2d1ef10188d38155fd538edb3fcc1b0f31816ae52633573987b9f2aef32373c6ef12ce8191609a2647e64cfd890502fc837dac2ee38ebccc0e1680e4c4c59a85c3f3f217acb5fdc2e4e12b7c2dc94f86debe28148e7af4a721b5a127e9750f891e0a7de28879446f4dfb1cb50a06a5f54867ab2ac1aa7e273e29ea0c3a67e64aab3924213589b9ea5addf3bcd61a4512150ad3f0b36ad5a25e51a3b85d9b98ee356fca0298d2aea33e4a0d600bbe93e4dfe30c6f34a9ee5ed7d9efb35d170279f142bf63b6422ca6e1efbc03ac02a0c20eafeb3cafeb76a81080d5017af8787941824f9feeee63ecbcef72f05b3b2fccc16be542347bfcb83d9a3c67b5a4f912c733cdc73ec61187d30deb53af220d736052c925e5c04f18e2b77b917b6e3da7ddf775b63e9105a6e19f33dd9f54e03a575dab779dd7fab549b755b801e8c4e9945aaf36afdc23e174f11b6cccded1f9020ad3704fd923a731a7cb0cf6ec9ca18635ec9b8aae366780dcfe1d5eece6c9bcd96dce5099add27f2da89248c455fd24db37125b5a2211a8fb46a390cea8ac61a2b05d479ac5b7b982d9833b47925e926ffd36b8fd5ed3b973b6bc98ab4a40b71bdff8c6b79b0502ec9f93b5823992fc91f43859279863ab26eb488f5fd32b982c0acc9148775dd775a1afebbac943c32946efd99061273842aa74baaa7b4cccb1bd588f4cdc78b166319339f6934424ae6a57680e1fb78bc4dd72ce0b49c87a5dad952b0cf807d7ba0412f6514d15bf0bb67d7434a0c6a6c685d6b11b08926f32322f20bf9a99fad330071f2ecfcb3ffc8b768c5ed35b485cd53a22b16148bcd538b3132ce72770e43b9680e4ccc9ac05c15ccec70b2058da46d57a9ee7799ec71e7b9e573b6bbbeab98ccd372f231193b9ea1e02065c438226c48509d83dab721ca0f72421ce5fe839f431733f6926b32512ab609a27f44f5034136b9b491fd14b34144dc5ed6f58c32e7f1c048dfdee2adbcc191a05aa094e828b7841a6b32192a3f0a47138eceb81d15e5773a8e853305b22714e18304d453267e89f8ea8827f740aae3b337f0aa65d0232b9cd47fc125407f69943c2acd32cd28bdd6e2fe62d1957b1a8442ab95eed6b0760c183081e44f0208207113e5fe2e011040f227810f1c2e38607112f4f68d115c689788c4ab6df7ddce6b7765501a66cd9ea19ab9abb69a8dbfd31300049d5edef1accdc8a4983c9aa5dbb2db617abcb33674ac3a6c263df6d2ae6aaabe8220b21a328c0ae52f7ba4a7d8edb4fcad03858f504d03ff434e4fb5d17d948a3268ba797aed30346a973fb4952a9e3ad1e63aaba6d60dc3ea367ae6ad3bdcd19f69bc90a7dbfe34c9668b69ac9a469270d8302d652900e7352e86958c3fa3595b9ea351a8e7eb51bb9ea95bdf455b16f377b0a856c13f20bd6fd40dc7e27f4eb79408ffcf674b23517c9f1321bfc330aa7abf39e85d085fc5d2f9c97995f49a611fa5d68ad177e73d52156f5a7809a361d79c98a36f459ef8a6eba05a4554b26cd6a1a81e53403c1966d77ae5e90e948351e94351e945d4d7d7902c83e7a1c59b643f0d2bee60c0cb7dfcb98a5f7cc19bac5cfed9fd30748e6aa5330555bdcee37e2aa6ed76706d8b7becd99792b87984c771405487ae9a55776f95e7a69ad95b19d684f975be40ff508c5bce930114795256f783107f9977f92e3e5232e396b6c8488cbf09f98b9bc8204efc0ca4f1072b212f786ab07d81663d0b058c30d2c150707a2090494b9f68a4ccf3f2dbf0cf38882ec704386ed483b6626a2446177efae3d292ef11881cd420b3d46578010186a4a0f3eab68fc867ff4932cb8112c4e56f5be348a18e140280d0e1431c2812246d228e1708407d37c83071c8ef080c391f9c6ed676702e8dfb4afca5713b90861840decc5092972643b46a6248024e529485fd37ef3164974f169553f8bdd37283bb42f4e50bed8a1a5be40ed304d0a958282425999abf661ca4fbd875fb453bb9e3f89eac8af86c25d87d91bd8d159abedeccc66a4df5e75083a3000ee27926a3a56ca7722caa747544f8f9cda021c51544449e9b1dd8c1e9f344233d2e8f161a641d9a070ae4a835ba838b827159b2b7e940dd3a496983fe2609cdbde4a0d916222053b7998bae9b1df73fb017ae3d383f29142afc8a66e27dc0967999eaeb0e5d4adbb4abbfad1f6f9dc7a7cbb64dfdc7eefb668e99ff5da7a5c45180ed2f948413e2f623a3a3a347afae953d0ed779210d5659c1eafa84eae4e80a47f544fabfa51b3d9c9076089448444f534f76579a827e6d84fb22ce562938b9f73e68c7d7e76d2c464a1727a44d55ad54fbff3b106a8da2dddfeaff4d615a45f574799ab7ed4136d8339366cd6d12e05fbae37eed09b5ac8028dc20070e7873a7c8cd8e016bfc1aa7e17260d57e11ffdf365b8c54033f3867306f57ae394abc234a82527385040b071fbf9843b59992b6677d94b0e136acc5f623da241434a4941ba9726d4e6a270eed8a89d1efbc9138ec31727b29c988e194872ee927e83e1ae8b75d2a3acc7cab785dad9a9e22d144ea760de42d9d8842f7280246a860390eb5f6d1cda6b0ad9550aa7cb1562c181fc08de86dbda54cab977ce566d221d50e589f4d5639344727c512402892239de26d78aa48c8f4b36aec7226a4eb3483a93358b745b055f3e971e80e59ca1b3b91a63b6281553d57f3ab5b4d0924b97e459a454246609903cbd7ae4eed28ef2d3eadfb92ec45de9a7b32c7468cdc4cca6d29f4e35ade22f5b8a4f7795ebf2320a7dac593eeb9189c9722be6d88e65aefae9ccad7039ac4ca09b89c27a8e1f1cc89ff4f8aa54cc1ffd56cc553fe94db8fda11b90a4a5ad14bacd55b8dc5c9dae6d2401d3979e0693385d45c30daeaa0108f0d6cf369b8d9dd25a29756666e63a46e5a2627103fed11d364f8fcd5c368ba4252da98fea35575672bea4e100428815ae71382afa32730c1220378e52aa9aa5c6358bce1a0893c55eccb199d623c728947ed257e80c58d574569f2cf533adbd982c2623f4236264dc3812ac7226739ce5bce4b2d225690903d2be665d673a1ea3c984986e9d1901ba48d2d7e5d071e6eabba4e3ccdcc77bea8ca4406dcc168dd5d711b7997859122950abfa9902d519587ade2c2fc9b696d96cfe7201861343a1dc6f3eb7816c2f4380ecb6187aec8f81816e4b8d3c9ca720b0ef0b13b0a6d56055ff1a37371c8e2b4a8d38fc3af3d6f42cd7a7b8cda7b4e1535c0ecfd2acea58dc8ab7e1547a942a75f7e6e959cb624843f8b23865fe6820ccd5931c402f3b804e99634892c11d502fbf0800b6154841586c40480a52636b9cb76a15aceacfa2735ddcae37709be36a4fd5a1958ab9eaae9b512ab0e0228c31a8d8410f1d230c2e2e16d4bd6532994ce60418b98800a3019086ac461ef2409ac175a03e442d8c56a419dcc5b632c7fe154906bff4572407549fdc4ec51e0029888b3e82342ec60c9002350e276a5ce34a1fadd4dddd5d342a85ed1227ab6573ec9685c0a8b3d99cf1db9c49c0edb7d559d5a9b4665b9dd97e6eb11bde7e0682b56889c69366d126059aa2f435dd385bab1a272474d9a604e8b63aab33fa6a16e8b60712fdb0a421a6eb3b9000c93abb5d677556674872211e80e84ad496a8ed5848c19f056e4d4e81693808f3871943b8fd648cfd9a6e1b10b491b58d8ca58017807866d0f0804a848d12d8506ff016827610a94bb68d8e4bb64d5f6e9b11a9e4d912d870031b385f8886071099661c21aa5d57997934a2410d199682b9ea5700bf5e601a66327fa88088db4fb248424c31448024dfa8e7d199cd14f63b88968269ba6f9f33f54f4ddca083a8b5eb6a105d44124d44c1329eb6699bee6edaa3c9d4c454c359bbaed690cbf8e999abfe18178024f79086f86d1b8662b6f8e6aa1c1738c60288a9e828ed9847a3494b1dab989f7f3557fc3414f947ebb4f0432587ca13374239f01b19a9d206382518b18a440e4205f891228a243481078b8b3f345b4a8888a3644308287660f344f95384943078c4c8b9a2c94580b314e3e68a261b01cef20659aec88e46942ad931037b443b5b373bf0a54725f43bd269e420f5bce93d9a2d36021ef5aac61cc2ed17f174d7f51712e76988e9ce1310f8f53d4901c9e973ba3d796efd49860c73c6736686e13dd449859a8101012ea8421fa7937b0335ffb0c062197cb2530cfe85edb2354b06930533c7fe538fed25fed171b3cc558d53ba3bd0217d8aafc180992c26628efd0cebd15b8cd32ce662321151eef72debca3a35b058b0ac5dfced306d44e6aa4f97c379020297103bb6a308b0636d73fbbb614134ea646f71b79833cc7f39eccb321aa57ceba6781edb784a1b56b04c918369bc0e8b95366e5bb6dd1f3dcb1d7a94c99891246916f9c2ed2ff4fd313ce0851ffbd64029782c9babfe1d3720394baeffa58cedde26d0c8f628d2aeb544c1b5ebaa7b28835fe182339e30491800adf8bac2fb2bf8274906ef61445880fe4a6e277ab6c7ce9979c473d574b6dcbd2288660ff2636bbd765f88e4d63ab7531aabfaddd21c8d5698df6c72f4d0f225f04daf420f5714313df83d94bea507d3830fc35fa4e54b5f04846151022d5f7a1a5a447a7355bf69075be1d1b1a85f6c85734b180e20447d3203fc66e9605f075740e9491f3dba657a8f4d2964015aea39da240815ba910de7f521a6906136af26e0bcf3eec0d76faec2798b7f6055ff10384602065d174e76025b3257edd6fa6b896572c4128e01d3f88ba1f0d7eb556da8cdfb92abf80b6101496a9bd971fbbf7e7ac3790b7c0a4473b73f260bb0e5c1f748301268791de274150de08f2eba312f0045e02c3df834985e87d843cb9b1e147bb8a248cb9bbe07f04b3f5d221e81210c3ff84a5c2d210c8bcfdf2226b982c522a607c5242d6f7a25ae4972534b486931319004c2e1e84e5210162dc99286785f4ba9eddc3b779edd40503557fc32402cebf0139eb11386f1cb441a426f1488fef4486d5a80349308c36f7a6f954418166188944423b4d218dde11ffd340a9fdb3132e88ec9440a42df441a0256ead65a1bfaac7d25340bb579740a95836669966dce787fb212033a65ae3c6b3d3a8552a16d502b9762e157f7a4c7d95cf10edd611aa6825f346605146aa336eec1f10b87c3e1702deb59eb34ecf52abda8adbe4a22b5bdbc456ff401310c01daee530c1fb5ccd4866a0d7db5d65a6bf5a8f51c6d4269b4768249f197dee4eeee3f36958219d94a5dd402300577779f335eabe6aafb15da651ec5af5861850f5de6515ce632ffe2471ee6e0430e495e2f21a62f0900783273e21ceab083a5b43fd12dcbedc6b82c54f4592f2618548cd9f297ab684ce339fca34f2f979c260de9392444bde479231269e479a5672bb35512e911540eb6e231d32c7516d6273d76d56915d0ed76ab4ee6aa3f54b3304d8d62fe78794e9d22cbf5d7ab7a0e738943d22d223dc3057195b649b93d46cea10ea6bb83bd2c8051576b7d799dbfe79de779340bbd51db5c75938498ae29f4d27be114b0d15512713cd86b45f71b12e0bc2a1a2b6132613661b602f4073b8ed871440d2f73bccc41248c2b5734ab9886904afce7557247a399e4923bf425511f754f9ffd32e79c73c6e6dfb42401f2e4263ddf26a5a7b4798db8ab5063470d1e92f5bc9fb9e90313b6cbcbb97b78c311076fd8ae10c67f8557e24a8530fc31afc425823d0192f496fa159ec322301f03b62459e1531f1326897998d28429b2429215c4242a7cea95b862420b13c21449e1611e464c02f329bc12978b11206dd2a38e77f97e98f004160192b456c6c8bcd505b5aadf5a103c9d3ce83b4806a6e761759af5b2d3ac1d3c4e2898de43f004a2c00305144c38c0704d35ad0385efb7240c97c23ce942ea4955cc93e10a3fbf2747979f7c99193d0a2b885ecc63c4e92a71ba8a0423c160b87cbd98bb7b31d493a77b9aab492dfd938ef7d0051592a8ebf2289c4e3f4932b4c078627075e000c3d5412f8f905e8bd32daf147dcc149f4b0c768476c7ea785942fbb236a185f578537acbe2f4ec7665b6f2daaa1ce0ced2f6a4b3e514e0cef20740d78b95c42a20d905756f70cb8b75417386f4fd1d12e68c28f4a4a78467047b4270374717c7ed17855e96d073c25c79735829bc9a131ecd2ae1e918e1cdae10b73fc6dd0424493012ac59235107faa49b6679599e709dd906314e40fe182ac098786769bb628d0092f4262a7f602b7fe033f2f822e07ab991ce9c01127a4a839e2bf242d1d3d0cbf1643df614bdd8fc9800753c01241d7663059c651a57ba23dcee9f97b67b89a7945366d7cb79cb8bd129367a9382bbf4e62dcfdadc1e293d3d65fc5cd28be1faed8b04bbfd306212b7dfc2bca026dd7257bf5706cfedf7724a359c946ae45cb9612c91d2a2ae19c6929ef9dd2c3d76ce9dd64addadbd21d1e0f67b21d17ba290e779d5e646619e52992d18f107a6f17cced03f61a142dda9e751774aa50d2b58ea127aa3b7d09c7239648398ad0b48c134deccfbb04461a5893692b84da5883bb234890af8078cbd3121bb527d29659ac5ff05e4f15a8ffdee30e00e08739b1e3bcbcba65937cd9a4d9ae5a277965a947f01bb26360f3805bce91116e453de7084693e116908fd1c58c811e60c7dfa201620e9cdee8e90be5e4c88fece8d39c3a2b7c45cf593ae1763038cb101b8c4645524ca90a1a1e30d90f47236d65ececb79b9772102744b928125907a98afe98e44431276a51ee649306fa54498094cd7ab059cab7a21c4172d98b2061aaefe01d0009cae1670aee94abd87f3168ce8c95ce5cd7aecd28c9c3bcb328256b8b32c23cbf5b0f4dcdcd59d251954dc260521e2c55ce0c5bc5438a7ea5906efda578fad839765ae3c27b415e0df590b0ac225a917d4ac865dd2cb592f47041046447a391bf8bdbfacdebc4563377aa3b79a50e8e564e8be6ce2d31ea555fdd486a7960bfd4a8f306fb9b52048a908bb5eee65611667085f2be620a485f8754a29cde9d1be442f3757a1e7e53c2c4cb04c0b03a6a9df6f73bcdc9ca16f97b8277b8465b2c40d2f7d0c2fe7e54886590b82a7d3ff68a49964d8cd6d3ecb0774946ebd50d34c328ca473ea05dcf89bab9ea2ebde69add469e88349655060ae66d84840a0d5bd1aa0fcceb8fe3dbcba088ec61e7374fad39d652d766bf546445be66a52fa0e454911519488ed3099c3a60366fce08b2fe2c0820b2454173ce9b2e8dcfdad771a37a85574c00e44159a0d80d494e8073b8d17d488f080e84eede0e4fed61f6c96c398d02a4588dca110795773e22a31d3195d75ff67c2f5efe15dd7f99f9e70ddabd85caf2ef7ceba98c249e5de32b78180d1845ad65e178c245cd19d2518552e297391a8e176935a6a45d2dfbdcef9355e653dcecfe971fea706c8b66bad959e219a3d4890eb0bc04e1c4d4add99ddbdab7bd78973aeead7747b13ead3878e2f35c5db4fa29af93f166bd755facd16df1e3df2f7b5bd53786fbde589f6072c906151d62545cc091f9001b6864b17d80c982c02cc5126e6655ee16109d05ee9aa36e6aa7faed80d5631154f244bce7070c9977c8e4d02d48075e63855e08d592bda6cb73f36c77e970bc64aa0d79c9962dc7e1a127ec9945c2dad9536c91d99ab7e2e9100f62519688892cbe2c82a01d6f4c7a05303348220638a1846308212236661ac2146134b9cb4e1ea1775a5b7a60d65e55b73c6f500d3309da993ca9c32cf9863545a964770381c8e6d286bba21b853ea4eddbd56776666e6eaf408264bb078807f744dcd86b2bca1a656fbee86aa751d18cc33f3d6cbcbece5e525889127f63bf422afebba8e5a6652102bf28d6901d889e4dfcef945cc355cb72310a63ceb4fecbaef6ce8e97721f99786bea669a8811e5b01a3efd7c08401f6ae11ff06260d8fc4d1557e0107ecb82151a6c750d7b9bf780d2219072d12d187690df0133dd1adc8b339f3ddfe5aabe51c76c2329eb69c0b8e4ce2ec442077aa1c3c0581f5922fb7abe81b55d210b63cf405321092e10273ec1789d81af9e11a03197d4d03f1d6483462bb464a4a80248f3d36777337cf98ddbb50b7d444d335b9e4f4065e512a460720d9b81c86e09ae56da59251c66849469906eda166f0d42e67fa58e73377cf299d0c324a9fb9bb0d08e80a92b1b821c2e7e2301a02773bae8d33e1709e63029254c7c62c019232d7d936675a6d65aa1ac7b6997be3a84e0ec85598d954ba153e2b4b1e0a541a63aecab9da01abaaada2417b925dd87b8e8e8355ed44e694472d6b16c76aa04373158d739ce0a9b99299d6305b0ad9e62ac6715fa93a0e8452e3d8dd46ebcc4b61bb683845e9bda0e5d48ec25c68812b890f077099de8700b84a3ce50b275eb892f8e005e1b84ce21530447c6033a0b84ae2acab3e4634306d3c679dcfb874498e35cb6b5d83c97231e688450c662bccaed2773e73b16db8682273f9301a02e725dbfaa590be6ab7f2b35506209902ce8d51c21367221a00792269455980b565cd6a1c13313aeb2bfcc3c96c96c50a12a5efd8e6aa2e6db852589ff401fa45a690159345ad60570d638e9527090791c6152f970f1f1336d71530f4e78591c149251ca227a8242d7637390b158d000000500033150000280c0c0643a2f1903c8ee3546d0f14000d788850644e998bc4b12c096214648c22c818620800000c019899295105b844998f63b1ebe1e6dab77b2ddfc762d3e2c6daf9a9ba0529ad06903a2571adfb3fd0f0b736fdf90bf1df0c7e94ef461f689e5d2469bb02223b8acf82ce6375f3f7edf48a738e03144c2378cea167d2ac6e80d6648503d60575a8d3bff6681ac870e305c339ff35a5e54c16d4972d5f731c05a30f7b28566479ea33fbfdba31dd72a43d83104d3f0a284d009374266aaf50c40a2f7b00039a820bae86ec0046d4831ec0a63cf516bbde77fc106768a950f72ff6ba12c0890c87b4e79c1cb10084783b6143060ed4952e57813d1b0c54211a5429776a385cda1c31bbfece6d78f7df4b4dfbea5c677dce0602df47068f968adf3d0da9b4a1c189c097acc75a2f107a3455ec92c8cb63e5e7a5b316feaeb4874339d2a7a8065e85deee3c91dcdbc1be7b28ebfc65ca03a9bf5d5034d055d8e1dd9e07083c2e531425e3ad9c19019483f00a4482c483ee3bf931bb4ee3fdd2723c782a88d4ee065bdcfc13483a09c123fcdd24a2799779c462377cf65e9a27bab7685d55cf60793f291cb8c6bd934567d502d8ef2f149aa5f366d4e874701c345560f652867b1e3f70c0436c8afd4b60e05a29f3d02cf4fa08bd2a5374418872ad10fab2f52fc80df4f6a2c28b25a260445865472cce691ef6c53e7ca93b0f48beb3cbb3a5556829ed3e19e5075046722ef0eee605c99ee1397a54cbb05b3a5a74600880fe93fa48ffd339c763c033a3f5fc0dc0aadf730a9b0305a386d3810a38def6968b5278ba7a52b03f5938436820b91706130654633722951d09afdd1672524f862e0d82993a155d903612928a9020f031a743b71da2a529630a8bade40492ea9b07eea20520956cf049f2b16b9cf9c96aaf590760bd672db10e1511b0c4b5fff1dc88ac28c241d92113e00744b48366bf0e9ed2b67afe97414368f711de14451cf30bc3343cae19d0382b1f94c5c3370d0483184c01e06cc08c365a95720eaa17052644b90a35bf527a977b817c3aad06f4839e0de7ed8afe9df53011b06d47ebc77229f589399e3e921885470a2bc68521ff3598d717c335498af10808a7eea2d11be114bd59e1615477810f3d32bafd939bfced067cff1d1a1db232c56abe80be265b47d83e7f08ab58ef408886659c9af707ae03e805b7e20e313e7591f28efc20badba82b820ec8220227920fd280e3321a839c700e7fb024bd123d1ea1b61476a9758f13ff636ac339eec20f5dd9a8b5bab8acf465defa03c6b4f88f5928ee2681b63afe5897ddccc9b76c0bb6543b6fab85e38998f43b1e272ce50e803f45127b057c59a7cc1c550d0d043059828a1aa6b354499704748703ec7d8817940cc869207290d831afd627e58966ac9ffa1a4c800a7fcaad029f078166ba4707308ed60e77eb183bce4c60e67abcf0133adfc5b561dd7f4163dadb3d9e1f20379274a5ccbf295c74df3d7a4669a801fc4797afce54989b10af438077aea1e4a14020d180416c97aefbe7db57a05d8ca264e33d0572643c3471547f09927902dbe1104be72e4960d2ca45c2d61a06195196eaffbfe4966934678a0551251bd3f4716305f0cdef6938a7a64dbcaea73e0fe6af444f2227d51129c9b4c422a196a7164372d40f4b8d7cf03ff1c01ec21bbb5f420d3c738559b419630c9771c84937eb213b09e36696b8135b777db4ff430a496cecf710d9334b990d6be68f7a025ab8a2779168842c897955f88c8b315ba0918e1c8f537081d3d01f5614932e6e49bc45b62191f60ef032f668ad71d0fa2aacf99af39d382ff16436dd512bde3aa60b04e197652c51db8ad29fdfd446cc59f76ce8a0045b5e1c29a495c6dae5ca2717aec88218b458f917b1edb94a6f3c684aab98ce76204aa96dc3fe1adb74cc6bc5adfc89d5f08dcb706d71e6e16d82d6a7e13e5131945b30df2c37efcffc1e4310ed3b7f625e1ec39c03e05b45c404f6266f769d278b61a0fc4fdf92f111603024e4f6686082613d7e8f16988f9812c9ef70beaa8c911ff7056d343c7f404261923dbaf7309085b3f737c5c591ab4090ec26b03048b4c592f64fe4db88404a3b0f536ccdd25e3b71484c6e226e2019a1429c44b73aca48d47af665d02c4f3ebb42cf2124ab43c88885fd1ba1c2258b906520c8b0b1263504bdc0673f1b39333d04f70218c90db26e810c4f3b1d0a9bff0c66a72390bfe823df8c3d3a8d5f9dfe9c2d61c07674f069f77e4278f18cd4b019a667c1eb8226082070102b96dd1b33c2facc484e964f837eecd38a4d2ca55314bc9d81faba84f41f76ff0d907a58e73f38b02c4d2f28cd9cfde2940888be6309e1d3de79695794801b4aeac186d217f06fa34d9a7a67f6d92437da3b070c14b33f21531c4a2cb0729488f3b91ae57ad9b9780db87010ab40021c8c4abfc5875032c00cfda1dced37a13e43e88df27e55375c75c8b6ff5f6509e509c931ef8f5e84b2d075497c9ed5edd27396c2ac1d15d5cd1504188cd21500dd144225f5dbe04da92f4284551e38106afddd05654db478e296151fa3f3240f557ddbad7c20bea216308ece622fffce98d20999432e577a40e6d8b15ed2e89cec991e97ca72747158f8c590d65b6352ac1f6d553c246806e466ac7b72ef3251ff182d9df3b0b8d5b9e0e0f2bd96fb5165d3b0dab044373773e93f0aadfec49de012eab67c7dffe76a33784df77fc6b11c6f02e1963a23ec8a4e606d0f89a5a85ae6826da34e852482ce53ad6b5fea5086c7f810511656286330780837f9f2210e8203bfc1c4809438c4f3cd3904b6ae6fdd70459d04917baf71568e180e3bb960a2d07201b1f260478db015f306197ff30858a83eedd3806b38499c1c0beadf88168e2710f1933b0618119b748372f5afba50b15ab9d741af1f07a09dfdaccf6891aab4497b268b81c152e9246dcfd82fcdec16e62c44aec445d11a02016bb43a289e26c6a16941aa30b6622c0618b686d43eb69588d54a9889086c83f6de164283366eb3cfb065bb284de87060a2a16e6657aae42911dd261f6c2463d364619d11c5401f0f1c6c4428711a43f27fcd4ed2467cbc430be964d4c711e32b877bd0e1cfb4e8389ed82af45789e840a4a3f8433b8dbed99951a4c181517ca8435bf5511476a4a5b84c9cf71a5493594717dc604dc0963c0687b6140cd26e74dab49346b2f02e19401d5e3af6cb3d178986b0357d6165066c7f0b211cee500d7add1ce107ee9e23acab028f6e8ab0b677f181c343090cde6f66d11b5ea331c96760d8ed03280e06e3ca030614a9e5ef04fd1364dbabdcbd6ba8cd40593d148624b7f87437e4d65069c3fb013f8a5bd2e559e0301dccb798744150ff6d6db07ec039399b51dd1ae953b87442b77a2fd54925a7a79b3ae8a756cc8e5882fd62e8c92d75e7c42bc922bd813a0aad4d725202143401c3fba1d480a5d1ab6326524a471b2417183a83d32a6ea9f29a496eba7b26befd426996b1928dd0d4d8ace8c424a9d2416e5f32f9f572c401bf8e027f099bbcf52a77d2b73cd993dc41b22e71aac1cb215f8c6f541ef03fd6cc06e4cf90cda807ce2ddb5cfe3f50df6162390ee632ecc2636e738008ca49d8bbf60cc0757015ff7cc3e3adbbad062efcc4ee1304758c73e11a89bdb09b87d23ae79b4f992e620d9a198a3071230cdf8c3a9093c544235a1db0c84426e4156b69d7091408414f823443727052c81a38d46669fee20007ccd80943271f87e7025004710ed6758634ceb6103ebbce5cdeae338a9c902de7a0f65b9acef6b49f57726dc92ae6aa393c0dd75726e814aed7961b035f11559602814b3f0c0cee46c72c77c2e62dfab9af60d58f38563e185f6f9111cd1a9a9a467045dccd5b895f1d85b5082e635b5cfad6c152cc26c49bb347ebb47f6b8ae33521ab93a889dcc15ab8586458d384eced28193d76f48fb1c6515e6f9ca82028dfd44a96aa5b38b6c2a446d30ef8acb195d4be75535b9685d46b3c2adbfb4af96292daae028b004f82444cc3fe4fb0775c53f71c8c3f9f74102e893db59373b40841e29cd0c503e45cc22453cce51c51e8a5c6117dd432fb2ad186544dc17386873abc8c69896973c715ac32dc17910273a6dd9ed4eefa8edeea83cd4f2ddbdd8eb05f636abeaf58963d7b7e305adee1bdb855fcffed5eba4c2597f937d6385d2cd89a5bd073a94a0a9d2f16e48d0cf1c2889c98b5952f94b881763483d7d6893131020b17499069e54e800774d77f69fb678606836dfd718a2430365dc718de91576d5d89dd37302d8b7dc23c12dd1b16336ba411e63969cca246d6ff8990817247073f85e5e69184563a7a68210a8cd1758456e7b4b0728a5483d66b960c50f4ed1ac04f77f4fac81bec844412c6efda9b3b75e736e6406be79aabfa96f042cec3e0ad6a50f5d897afc75be8ebfd0847bca21c90030322c3e2bf0fa1f6268f52860c6c75487de4a12f26af5d13acc5f21670bdbd588318de5b337e59f1b35942868985024a3437acea63aee6e37b1e9f24e929c9ab50ace196f80437b235202f5e339a1f8b8f9d9c4897247421b77779b82ab259c89f2d6eb5275f342275c02cde4c57b9a9590ac0b8abd85d2993b4cbfb95292ce7fb6b7a80f63146b28cca0d3f14a2217ccd4d57de28dc1cab67c5fe6510eae1cef03012e1ff42de84ba432642df905081ac2c4f3662510356496bd00b2afa65d0e8b240dca2859c87179e77d384c4731d4cc5a380092322d2ce35c0aabffb4246e3d72831bb10582c421ac75b831b003984945065e58b497b085af6229e3b9cfd861b3496b826e27907a5f4ae71474458130c9e7703c8eda6a86fba82a886fec7da58dfccef77ddd7ed1e9053eeeaa0b1dd998e03390392527839d0753f0ec72c8c1bee91c7f65bd423a3b4004b51dd5889a3e4d4fafb026cdabd80ed6b2a2e703a8d7a1f2f6cb5b542f5878d3117e2ec582a9b32226038b393350ad6b537eba70afd92c2243274a7b5b5088b2da220525c1bd906ab44fd15f064591710323f0773869d67ee0b6915b4d94fdd8468c5c341ba043b4fbf7dd52dd22cde0f8435e7fef798986222d56cd56d352e76e76b0d70ad45819f2d342ede0c706d79664fb47087d7ffaf2d01d634d708b405a1263ce3154f4f080b2fb920bd3a9cefd08bb8358aeddb2221ca76278391e5fde5a07126b771d3199b2969ee8f22f2d62bc385d89a080d6a121efa39a1e116bb1e2c1d23cb2ecaa4330fb51902e20d20525ae8c8514faa0cff839367a447d7163f9f4ef487936e15de4da09d990a04e6806e53027b69f5d4d11e24288635d9d9480c460b595cc663af807ff51138ba0f8838532dbf26aef0d83f6db22a5b6a52002238799139ba566489e4168ea0f02a2850ce64e2044d7cbeb7f876427a1b5bfdd2309ff53b512d6b6dc818640e59dcabc37e6c02d8e801cfcb115dfa3ad16c445a0d1b17eb2f589790275886cd09ef0e6985ca264febeead9e462710ba1179c0bd2ce2884e64c19cba017678a4168f94c7bbd964a742d636d16c62584da18afa2dbb0f6abaed94cd77131aa48946e8d9d009e773083b8881b9c9b7b184dca074fce45bf36749c0a854b524cb11a305e113ceb2161a61701693c8071b7048309c7a375ba0277384c59a3322253a46f185aacdb55f4d578978c28e2c471566b80d7390c64aa4b28d92640b8c569771efb85d3b386d5d8357f6516a3d0b095ec52291e16c584879422b7ea7b284d18e3657413b548db7e9ec6ebcd876fa0a4069e3e59f27584e0e3232cb33fda74c8ad3d3a0013b406c35a6bf30f49a8b76ef74a08b9313139ba68a915ab9df1d9df722fc681b6537ea686560488bae1b33677b115c29fbada46528660ef8f97c4b84bfa898c4e651622c12593764880d2b5f41b0c2765dc2d233c7150e13b644db70ff80fb9310269b867ac773430e721d65f9b9ce215178738013b1df246c101e485c89a641181a300e35f161ad1ed3b8195fcb9db7845439c662cf7d8ffcf469dd479fc3105a9992dbe43e0d045e84124730524d97bfeb0640206f88cd7a60d55b8c722f2222d5e7916fb60cac2a2d8ab0411cff41eb4f1d1113ad48ff99f864db553cbb8ae8736c697281d4e3ca4e87d591e0cc658197eb57806cdf79d269e875b5c6442267f7fa200ed30ebc3647b0814c187f690948bd7661ea1de7ae701acebb5bef1fcce73eed3819b29da1b8a3def8775eccacb91286528d054748fc8379369eb448580915ace93f3e895105859b57c23396928e3895fc83956e1a6080c7128936dbab6a60c4acaf321a49d7c4b93f869d05847d7a67254ef7b9b691aea505e1c66221541c94564a2fa1df45d75359d0bb206510c223020c96fb982a6fe64be71c326b344cc5ce79d48d63ec17457870988f48e2740dc32b828198bbbab5f319c05f608bf631ee949cd8118121bb74f260868898da71060691f9213c80eb8d64403daf0f54a0e790682782bdbdf8687d82465eed1bb76d444d6be461ba7a3e36e28bffb1232a29bff99319e7bf7048e8402d5c4e587ea4ab060818f7284cfe129517a81c143d9deae8533df398362b36e98831664e6957f291113f19298c3cc7b0767347144b69908c10149b68edc8bfb5f461fc8de43c6be87bb0f29f1eecd371fd91d28f9248a56934b0aec95ac9c8326147ec14b6a62660efbed4c0621d5628c883128642f9414ae7f9039c72060ece837ac9738706a3fa45464c868c39ed28f7ead30dc927d98e4a22b85e8b912bd407b0cdceabf56e44a6b3fd620349e233f9b7d826a25f72378e69e3ef222cc29d1832198094b21ba2dc6a290ad58b2431a2226fd040c6d7944502d78775115a91ac21944cad36f63545f607441f2ed781ba8c5283cac2ce16fb51685301814e903cedac0c0905b5927b3f29e98c0e076e50da628f20a344948da7a0796f5540f3c1494bb26c4b5a3427bde90bca48214b0992eb7f2bc5bc2705b2b382db6096d17476255d76af595778e466668494641485fd3310ef8e8445c1c924de230ffb4f7a5904a2c8f66140b9b81b157005283f0902b3c33e33096593c4a54cf3aadd21c6c239c8f2a384e5d7f309decad9053f5d9e428228e32dbc49f66950895ca903097185b8a8ddb4f34bb0b0742fe5420684c5b89ece309ac9688b8e58c518ba41d301e33d7e2112a240c71c13b59a6304d75050cad61987cb25fa82c21b596bb289f527167f7f643d8836b6bae475bb4c89a5c44ea75fa7dcf1f1f515efb5446b57bc8735a94471d80a9ed9f6019755eccbb8f073c7592f2bb743441fed98d66749c44d692c081205d6e276ec729aeb17c7a3e9f29f270ed89710a0ed7801a914c0778234fee256935ebe250ba056b6874edd6739919ba91abd5642b314f546b9b227e6d5093d0cd67a24aea50cea022a282d9a3c70b3accca3fc31b450e5da3c01e8d645d310c2465c63b2d4844b1ec9ef5c4214ba18fc359c2d29d919929f577ab9a7429bd251ec3ee4e5999235832af94f41c86eaee92823ae6cb440a169c4dcdbee895f6965dd8e303bfa0df22a5a41972f0e563a1471974479e3547206430e6409ec4f780a5beeff716401878ce6fba7d126fec41feaf5a02a65751813b8d5ebc6d163b794c71a297bfaba25ce65781d184b09f320066aa9e00cd25120ba7783d17f09d6930038c06db871f4ef655262e3511f48bb8f4bb07ff38ccee3355d41329a52ffe72aa2b9215376b728ff104208ad953ed1245348626a2a363498b229192494cd1c512f6c4111b36610642a8e7b434be946a6421c199769738f09f160704a3f224f0273299e4e92ebadc4d4206e9e78f6fcee892d979b89727419c3fd112a4c2cd600ffca85ffea14e9019c2b9de11ad0f63c39952ceaa8c1fae04c7122146fd7ad64f3bbbe08a6654ba825d357a12fadb4f69aab674764d29a569bd35617fa28e8a1f204b63d411f496ffb0bff57a24c3062416b20d2a3ef3287177898e39b465faa394321a36f0fd8d9d7ff940e1d4faf303016d91aaec674fb01b0ecb0ff040b183a233eca2e1b8764b838064d32fc1ce0ef669729c79816971add2dccdf2dc8f77218bb93cb8b874a4e5da7cd20cde469e583d8085b2e4e7849000a0be05b59985ab775579bb7a74b33a9a88ec4c3d250cc5ba2f2c0b894fd18a084a4199a3c3218cda82d18f963adbc4229679419ab233003290200afbbf2c4e22a7fa84866e9a377fd1415bce14d9b640814d822f9320ab2e4cb774426b2705554188085357c98435a45682c5c9dfd7205a0efc9bf1703c090d9f64d265aff7fe9093279fb17aff484a32c6aaed8d3900e552d4c45433adb96b147bdb4db64847cf675943ad4e8536643c810d52e613bfa03a3824bd8773a4d16e37fac03aa7734d0d0ed29c641a461eba0347c360e4255f505155185c2dc5c8e39ab0a1c8d67149b070ec69eb11e633429cdfad523cdc902fa4287cbf00f84382a7ef56e583ea1cc781f3f91babeba298f291355937a9659d195615212b802da2dff64304c9116ab1dd3dc85931e281cbe970a61de02abf9ce1b6825cfe09239b2dd30fe40cf926f24983a1ab986a43a1894b7b00405fcc79907c08eda4fdb8c076f032446b2afc908b3cd02b1a119bae2dd3b5eb972ff0e7c43b5e62101e74be6cd77cc8113559954c17c51e078f9da083e41de80b67a53127f8261e1a1c8e577e44729218d089e6d824bf3b490279b4463178912855e1c9e91182fc591660a8cc0e71b96c29cc59638f1ddcb2cbaafbe5f5a5e888713cc3e94f4c8abbc5da2cb765dfe3d05274c088eec2f519c463fc22215fe3d08b0293c123566c04174a25faebf8517695df921f15fee92ec824523e140ab4f1efab8139b2c96e1082d849fff997cb193bf69e8951856e355412f16db1567b5c0e2d84e81923b1c518802a052efb6fc4201c6d857666be063f1eff167cdd783bb77460516cd05d170e43731c58d19d4fea5ef24306f90169346a2fabfa43062ce1a1195800c64c7511477f05094d30eba93f82816050f0a72a46ea927380c51257833506f680306350cd289bb3099b9742aa298733a89d83f150a814ae054b0cffa1ea2dd049dbc46b032900ca4a53ba0df8b99f5602664bf5648ebc93e814ff76d11d83d353719c3486b4eeeb87e8b7439acc741581156dab35191d900dccfef9630e33ab065a9102dde1b9642a7e5b48e6cc25e5bf616fe2648c554703f2276feff9fb94185eb680e44805a9db98442e510581dbc3c9250b16b35a449462910b6822015e81a255e8683369fff189477c994145edfdcfe7435cfbb0655b5a0da5ab8edb310e5ec71e63eec9180190051e9f0054714d22f66d7aa0a1059a6dcb3af7ff50faad286d09e99453502c60e7f0ef8a4320ce04e9f3c407e6b8934026df54c858548838305b10d7c1a2aa62952033cd746a885d4b9c8ea15272db7ac645abf6d1556ec06f4da23b0c4f451fb7886221b22e740b2c2eede025ad92ebacb7340423232ee55733afcfb34650af2a350e5c4e568c38e9b75ad44a5ca0c268f5219aa93bb76b16862b1f3e837d9227c2f06c90ae02a1932ea026a97a10e4831b25b5670f221065dff56fac3a0b43a17790967b977e6c54a654a0929ba1de201667a5b43d18c3c20cfc8bacde588cf6e088c207a6f3367888ffafdda1a40412e6fb8f7a93ce7a129604515862c0ef90e1f843146429604802e9dface569b00d13f167131ef234aa946fb1835dbcb20a5676f7a33754ba119035386c64a7830300550dc33c365507384313aac269196635ee2cf76e7485d1d1cb0b9e220c13b383328a0e68024578d380bd55bb6e661f42b9f42e8ec9f3a3ce66f79a4cc528f3525a72a8b3605a6d299205da7e5a5c3b60aee1bd34012b2e4705e5da05009f32f9ffaa1112228d1c1e3fc9d2229c7c3d910860a4b363a11259cd7020824702cc9819aa2e84f9936d027eaec51be202ecf6df48b435759902a15de8f72c300d5537284cd87d15fb23c553892d0207a561260362404e5f58d0d3bffa55f228a871c0896b8e5ba7637dc05940e0098ca8ea4ba5396050080f031e0f9275d4e1da99db1aa083fee01c4a8508bb38ec7902315b1866397946481ef08ec44ac40d255920d0acbb71616a78e20c3bd1997274d30b3c3caedaddc0cac11442834aeb5d96d98968779ef67130ad1ec6d40473b88a851bd697e8787092d8b03892718887eced2e41f9990b3a8332ebecc4a80b9c2841f1654c2a9b825e1c14edf07b42ce274fdd75fb87e4eb001a7c891dea29ac3e5234bb018e861abe85ae799c0261fa658e03a473188bf7006852dbc66338aac0dc8300b1f7bcf5fccf635bdc393d601286f5dbdf30f4865be8e4a96c658c8892ab187b31ae69d4989ddd9ad5da2cc8737442301ebf894e6064368a0a77b913728d6e17ef36306f6111384d2e493cc81acea699a89bf3afba81e9d6dcc482a5478dfffbf0550a7b2700cba8ef6e30b6d2006b799018b5167b1be5d4110145999534ef789df071c5d8f2832ca57baa4771f492add1656ab1b41ea00b66dc23bf1dbcd3dc65bc9eaa80b8e8ed7cbe77fb981ee8a0f1d814b48e681c1dab46fb422e01150f2d88996590fcf6bc65a1bd43420accbe1978f01c5684989a0ac0a10fc14a64df171c71bc7c665acc2601c1f3a47d564ef93739d349f61de2ec2f961d23e9da632ddf3bfcb0ab7aca4031032e16fbfbf81aba9191d9b060c40cbba6a8fb1059161763f9dcf04280201560fb19de955e0039e986786df1ac5591c462202c8c4ea2f2932962795ced045bcb893d16a760855e25f9b8c61c8ec7915988e7dafba5fff087c39f624426cb1835d817cf8aa88f576907f64e08f55977cd37ed1661b0d7646fc0bbdb3e08f5daa06cc24351528937e2014a43b0dcd01589402464c8c606c7fe889cd12a705f210796663d82f8eea10e6a8a26c424c688f35358afceea2ec10ee9577bc56a7bf100d0222cd5f15cccca070f2abeb0055654ef4e9269aca3aaa5bc7792fe1a084860e4e6ecbc3c189538454d21cba7a87675b71b463e45e9bd2dc98a331c6965bf63efb10250481b42d101515cdf673ab0297248450f307d124309a2b5352abf65b10cd01ac1ad159993e27bc7dc861c8ec3425474439516661600ab360a8aab2a52582b9d0e32bbc0f55d84ede2dfd2cc876a5951d195500f9a7555235b5478fb4d184b9410ac5adb30ba3b0b56d3c486ba3092d142f8685625199cc301415871b419316011c52aee304d6568766654d506a6b70dd4eb7f9f6de8d968a2ca6b07e9ec460c512a39bbd08c77e74c225f5940ed05bbaac21275201dd74663fa2720ed5a289b9ec5f4ea118b243c13afa4f22ccdb647c67afb95072430b53925244319015b79f1a2cc6a477bd8da7dc12d04789f3aebd5ba8dc41a034541b7bf0d977a5881b95131ce9d55c78c73991113c3a91225888152d7d5383492b372ba8a223a40ad5c57971f2224d938d2d926dca2c7a438a035a40ef65009e28521b433c9bc8adca826e978ed7887d1279016596ff2344a681ad344e5622c5b47e1f9af7be91c49bd9d14cf65c057ba309dc2156d2f8130acb128f482273648ce0b805a7721c8e21d3f83d88a4e51cd85145959c837017b1a0d78c03c84631d810742002e54e465496c7717a822ca7433009d2936002ea7be09ff851c47ac1a79192a1809c5108e1714e944821799555f81a403802c88fc2ff19665d2bde382e1b9ec16f4b1b250ec4a426a8039401873a8380709e44bf2c7d0d270698a806dab9802ca860a147c5c594a9a4ebd9870fc094836860e9580cc88e7757c419bac349d060eaa30c127077348f9493d3e501a75f70a0951c89724bcfade5738f166711da6e94d18107b7907e140f9bbcacb9455995a532041d520675f92e8786a72e304f086b083cae51a76f03032710107c0df990f6759c80651d1a45eb0040aaa01bfb74b272557ba9b2097b06c8077bff816a771400d769186e07566016fb1a06231e4f13d4da01b17c6e0428453860ed1b614fd59e8ac1e1f7d34878245d9e32fd432bea3dfcac03634ee1748991b122e1135daf2a08fea8d7c7eae09796b0fd559e69ed0aabf45ae2ead4f10861ceaa03c257d3d6e08a5d961004a6c71aebdf4a6e1826119721b318956f2433400c6d4380db4c3089be07d94475624f7ca7167d0dd66783a95c34e3319e5ce074f6cea9c5c429dfb7aeafe0bd1e872e74167143a1f4ecdefb869893d1e35144c027e2e3de8f610c7a2089420f4fbba8ac4487474721017b62830c76463b213740d535b6ea40dfabafab7906972dbafc9e7260f702fcf121c3944da45d07342c14f8628785302dba87a26151a8c3728e48f822ceaf85b789c597afd1da2c982ba7ef7f43ec8db9084ada7607befd3791287b79fce9777fda65045242a23e5ab13eb7484a692d2212a5bc3b1d03133f89517edd1e7f44ed4463c37d40a0154512d18793b1d806ed8219a632701bdb99fa1451fc94f233530c1f38201ef60cd240ef03c7907cd78015cbbd5644eb40f87485f6909526e211414f7c7ae96fb3c60fac04fe205b0ea2172b622c5ab0af10241ef3bfc929b3dd87f6da2a7c6e2486a98512e9a40fabbb582cd2bb6f34ee6191fe250ac8776945d7707090e535f0351716a38c4a1ce1d942bdc31c715d1a0debfede2b0562fbb815b66434b0a764b042c577e41c61b16ead5bddb162b192c09ba6aeeaa1a30ceb530e46f60570781ea51b8f4a91bd20eb28ee71c4d385d017d2409d128b463db8ce1441de64a15108d453b055614ad492fe73d6149015c8759bed0861f27a092c450f9f41af7c4561d66848b90305581bbe770921ab90841662ab5e4c5728f5d03453390110aed4037c1a2c2fc708df5252280a0d2d720de67461931a08df71c4694449c4be29ac088c0c70f8366295247e64ae284a0afb8c84b443360192847b832f9237e7dcd6a58341e75c511a3c5843a761c4d45a13665f953b80645f80bbd55c627e80ee72c1a47a50500cf67fd9cc94ab1b0752124f1d1c867ddd6e58c88bf9ef667609fac12932e16b1f83bc381000350df632786aea32682e64df6612cb6c9d1f5d261cf585ccee9cbd3d9a6ee906718b12ec0f731b010b88f298cbf476f8d457921b347f07262d5907346e74e25c0a82bbba51ed31ee5352bfec11d550fa099c713b1c91b8e93b1b652e845b62b37dd62891ab360a310cdd055b241ebe1299e570eb775ab98f65a5b950947a3bacfdcd171e2c8f3444620ee42ded47f409a376dbab92261b759b79a3f6c0cb7fa6080ca69dee32afa7a2ec1dc789d6d8cb0ecaf5fbbfc80ab6458f93287b7e98fa239a32ad8183163f9657ac7f0ad1bb2fac108deb90ff23ed7e4f1ebe11e535ffcce5427d58102861133fe40d0774e0d56ab6210effa90bfa1c6c3f4e99bfda741c9bb86bfbf5741f957b36db8fa6be071b1e79423530f6fd65e59a9e667637824d3dc844ed5f8aeb78a148b691aff2358150367c327320a97a184cfc508af8134fc8b5138a30907a49dd46999d43cb5c3dcba0aa7bd204f3f6dba53513f2418c3e88500d197795a3302e6089999a2be91e62a8fac348c8ffdc855ca57b66e7666140a7cd01f87fe43432e74484359a302f798d4d207ce1112e903e87372a1b17e5c5857ff94d46c4bc9a15314ad434e1a4462037ecef1f59e7da08cd31341938e53e0b23810c2e045d67d7917423748bd6428460a95d52282db4559047d3f181d42adc9ca9a1e92a5e2991be35caeab220718f568302045e94b67c50ec71cc82077314a2bc129393c29d816ad7a008b9ac9a8911dfa4f098229f1f50a59abbfdb69abc327950039f409d710b3efe086cc1f93747d2b5e0a90ec8e534beab8e6db6564d3a56e2609b2b8594fa37986dcbd4abb9192186b9701f887c350169c613577d8a652b9b5c7b9552c81b09279af91d5d2a134aaa78762d632db17ab658ce00447d99cc96ef8abfbf11b94252637ee543e6ec388d7fd5d0e19d3532e25a7520dd66193a12974201bd3ba5b224c670667b0d82f8f0d7c7b4720caf1b27009671e34481b109bf6be59635d48ceba070acf9feb51fc5b3cb9af4cd590450036a63647a627e98e305ad5cf0aa26b693e292be34afbe51f5c359dbb3c34bac6b669e9769e7b343d860ce461795a912929d2326f1c4c2e6c4d43b6bd348a05dc32deb7811fba9e5844ea88c13d5d73f13ac99e42fb99c237505942ee464c9a726312a0f32df6cb5449cb0fb1c8e10e76441ef20783001e43b726d265f56a1dc7f27d481389897cd5be1447c860e6440672ac64dc2ea24c4b2d294ed88a308b32e851d286a2109b558ff85dc7fa485666c25653730556bbdb3ae65418087c0203d94ac692f19e82026f0b874ac5e015188366a6bb52e72bbd0e94c53acbd5086bd80c520b70c9993a1eae89f25e514f6594092d1cffe6c21021ada9e34eda62ba8a4d6eb817b2ece1417e4ba35eff0fb70bd639e6242045761863e03df8d96ac0536eb8631e3ada6a8aa704059754f785c5c1fd5ead157076131cffaf2694d88e2387d99e565568a921d83bbd3a667a2e002c6601361b541a2669a88a873915af823d9ffea05b3d5c69629fa0ebb20a02e198faa6318937b8d23bb8e5ab95f0edfeedd44233163cb656f76f8ae2ac0cd06e1fad7c90ca6f4cba0d6c6db19533b37c6c8d52103fa04d0210a192cf659b1b0c58e52872df0de75adbce2edd8174f99046c36224323ab24766e4105ecf702b40591c2cb1d2ceca754450278143d191fbe4150ebdc7b63c6ddfdeb252b9232780f1d646206af529b2e9fc0541cc0222f3890486f2fcfc82710025667772a50585c292ec766ee73fd4a27f88c3766177d540072f87619e002044efefe34e38264836705de23088918403f84d02e685451f8eee7d8389242847766364ce1852d44c2a96ea51f698cdc8d3d195b316f97d7f91c3a47c45dd79dc25f41e25730b8a23c9766aa931f31a7f1bdc1eea9321e477a181b305092d7d5629944a2c26b1c7d04b749aed333e7502dd87ce5712531882fdd6a8c07610602b462dbb06d15fa8888b7f3ca01d135bca1f477408195f21f66eb12d5b89c6a104e45cf09083c5f59e521757f43875c4428e90283ec9517585b8fab29cf462907a570cf43e3adb849e4b809e8188a84328306fa8af2b45a363415ae59e8876a5638e4f8c57617369e5ff046c54664ace63cd96c81105bfe559a4d170440c768d27421aa02d29c5f018f194bef2780f81f3dd375fa58cc0067687d1a96663c33ff6a641441269e2c836b606fcde79c49c1068b4ecec1d8c189ac568d393a9714220ea620db7e60a884d74c98f0169a87b23459fb6776a7040053fe43104837d6b06ae933a9854e4d83cc380138f98cca0aa7b2b927c0a474008b23e8f605234607e1b3b394caec5a0150998dd84f3b30bbdf236071d5cb0f4efca4fa782aa022cd9d69c13e6fb280b7d99df7f631170eb4f97da4590dd0a7f3b3b9207136da1a900e4440c49fdec5e607e0b1271cf09cba41f51a077028e438796a17fe921ac465bb098fef1879948c3aba899969075b66bdda82e76c5cccbbe79e94574461b1a9dc4156c2e4a67a16374668cc104b8892123607386883c5e12298e27ea580b86e2b87fa684869c5d2b77319a4dd512e35cb51c85a78b90543a2082720956b8277b00102cd8756424106e94d74ff1ddb6c552f4df081acd11b4ac4a4f9e9c69557b28e62d241af337e9d5497066a7585c05d98b6c53602ec02d25fde450a389a7884b7cd3cbb9376768c20d5cd6e856d2206b56de9f33283d8a241e8b969c0a812434bbac15570f9f3d92a5180fe96ed57df3ef480cb612065af956635dff9b4598ba6778f6f8aa6ef573c33adcb03ab0729b8b82daeb8c3c60c183e1d58fb23ec8e0aff8a7a55e4e3a7d323243794f2bd5181878f4ee90a4339d41eb0f9a8d3d6307c2f8ada522fbdb9bc96b990cf30dc99f5603762f4e1237f3936329528621aa10150997fe1f15c2e8ab1b449a3c62f7578b12020d6d0aca7cf6683addd79a2f5680ac1703cf7e25c9d851ab6db5439af25dd7cf0cece3c86f8fe0d105ef87741514e39e229ccdf78298a8eab5f907ad7b62aed55ea8a8883e0821cd6d18e4a212b2f34a6dd56983ba871679179ee0f6d684867efddb0bf6e6be1dbcae716d0e295e19983ec7625f01c2b05c844bd881b1ae93847f4c580f71ef838e53a51f44b6863934e871b961636003f568d3c7cb799e909e8406bdf4f84c5c4f3018601c2e76419d876fd3d705076e4ae21cb0b0b8ae6f6ea279d357dea472fe19b75b3102fe70484407c6dc2bbb115801ff46f36ea80ac31a0666040ff1946d556df1dedcc24c80d987db71c5568004cd70c270f522b444e3fa9a06b8f3487379cd5b6064ed1802bbe0ca2b1b3673006bb2ccf48182be13a5f110269ef7c33704e5adf5caac55b665fcdf2e585d4755b8a67bd65672b6a083a7450c7bfe79c5e75f5b0b7a227632d243010a3111dedf316a1b678cab95b73c88ec0543703252b7e161085db42983cb01aaa2de043be102e458018f266013e949fd5b018db39a3c70093f080a1fbc5ab38c7a8b67f277c605894518e0e5b3b7d7fafee05f80290f839e79a9eb776ef17e80d307f35c71dcb4655c0738e72558f6a48bd1a0b30bab8981b488247dc8621a1e4b7aaccb3f96f004259fe1fbc23a2925e0a6144ff030598fcc83ce50e0378c1c0e476f536592c0377e654475a6003a1994596308d307c9d8bb9499be4ca47538d7cc0dd4869ae88d210dd14d5ad803350667c07f1ba90c0b5bd856dc789f49ef7a6b270edc8eb74bd465436118a09d7c10265213c66d100d3098e29bfdb1a371d75f6ce20a42c58d3176a98dc4a908ccade5dcf48a94273da3283f8384aaa91e120dc65e35d08ab788de36e5da2d91f1cd3f6ace1be8f25ac79cd5f67574ebb4f9c1a95f6db8cb1592164694af31f7638491d3737d953b3a4804ecc048ed42c87e4c6735ff1746fe0da06f936e98b060f1496a15847ddc2f1c4dd5a4b665fbb3221c59709bd18d34fbc3176f6feba85afd571ab542e7d06be56c2b11490c779c82672cff60059456201fedd71c0bd1360101a89eb915bdad8462c13d73270f972837190666ae6b5ae31c2503da9448646ec54fab74480901db945214b2a5aba085e31950f1c6fcc82b4415b05a5fd2561edb88ddd5e82942d20f53d4b9148f063d8c2a97ddc9146c3c7f5a581765643e078d5a9cbc064cc9ad1271dd62634ec37a4be02fbba9d8d84d39092b890406f1e803924d79347b7afcb8ef0273ad9d640a871f358e2b6d07f46e170fcda2e693c63203c1df1268ce9aea944f99535a2185ed886f4f843f99f4c32ec23958ffa932a9decb02cb1db2b59b8497a47d7b25f314fb6de5b429d8b6ff925e3f28d3ea9450d1b9047765a0f689d765fe488428bdb2be7f971440937cb94484a255457e9ba637061cebea3912abe91536dd28d74d591629b4d23da56f9751c71491bea9b7d54f20fdcf2ca42244004f39c24704ae26550404a23d1853b58cebaee17cf333f3ec6918dde2c0cce6704aba031e47a8316ec668c98d249c250a4b763c5812227847304d10d3f37b5d41eb27991ad7ba9ee9cb50c259b94c136453ab475d39636a624530d75afef90ca6c231e78d6286974027f575ecddff0cb67b104e9b87b23cebed782624f2b7f747b9491b7c340c7c9ef3aa761565843d7d1cafb82200fef8fe41b10ab282f72dcfe52e6b45f9ba835bc618b31538b335745b3821ccdd592353ab80a754c2be84ef9511076a09910a2d3febf7401c1836de8dc502a3d27653951b6e339fd20bdb1b44da637e72059374865fb98d764e50501bf9869ccb5c1661df6fc9d0b6aaf81b3f448196e895983254209b7716abfe1415dea2461231fbc94c198a5dc01b4228c6deaaa210aefec54429105b1724dc20d136b93a44116cdc28d1d3534f40f0b7a39ee22e266fd06c135fad307305fe41ef8aa8ef4f1dddb839c1178ec211421f1dc6d338486b335c4eac4db6672b7e199690479714b2e9a57e6cdda065175d00370f2f15a88c71b06fb9772b688bb2ce20a86adfae5fbb78536d2b4a9fcb626f9fa9607b8ca0d223856938192a2f6f838c9a4468034ec7a92b8ea7061089cd1c1b4484c3a904c8b2c60129c9bda651154890e3e04de896b15c7d445f00c5608e60f4042eed96f40b04ce0b5801d506d07a087aa43394d6c045255568fdc8402dfa5b9ee0967db27b0c4e87c35ca94a1d8de0c1675f50d2b0940b1559bb9fb277e3347536969ea3542774e017c21b355d26d02a6556c9072ecfb12737ddaac0ee1a2b2b901b5e861e7f2e78583b63cd4b4466763f6d45ce1a2de9e0a42632608b9c20bd10af6684015f1e8d350b05c694cc519f0cdf5f068b501d55964adbe36c3aa219b29feb4670ed125c1ebf59494d201454b5a114e0f569f68f1e8b8da6b465e48c0b8bfe5980ecb50ea6c33d28db402d0f1b01b230a1ea0bba4a410da55dc816ff8424c934deb1e7c0ffbca69189b1df50167d8cf34d9be800b2d2788ee4d6e0438ffd1f82a4adf37207d2c24c63e181fae12c4adc54e670bea1aeb0e1391235bb2e06dcf2ca213aad1bbb05fc58d461bdccdcc9ab80a1517b3e34c1892e4b35c765b4dd6aabf7ac35a2f30690569ae17105a9f249431558b1fe02e83bbf10a463ccc3a05865995668c85cd348253397b18dbda72464b919a3ffd11966cc2af44b5717d08d0d5d77a6b70700515a2441598a2f63ff0c675b67ead7a8664bbcf2f65bc11674a20db88a9c9b062badd8e2add28080d47a211b54d5113e795012d3f0b30e0135bceca4948f91d0d88d613472daa68278b9255d383be50780fa659eff8850718cf49a3733262049ee72a4fe071405f10dce3cd03f704ec33721a6022c7e61066189e7c8b287d1425211b182f720d7881249f0f28ee526f7da125ae7b17627ca7efc309d1f28c5c35a17070c8fa039d49cdcf0b68fcf54faae4f8fd95d1b9c7af65f343705a4060d6b866bc1d163cb12e90d78bb23dd8dcf2038521ef30fb4e447931501a52ec49d3eb1ba22233e68c101a7e81e5d86702310032307452075eb9309bad7a4953f39b152336579606325c2a7d9b4d8f1af8da5a1a2d0d726aae1c205d1ec1d6429a0b89bfa1aa178bb95a30163bc11f2c995fa722c8471aaf945a7e9147bb89d4863b3c96e5a988875b99ad12691ff9397b6db454051c1649135c25ec90d333991bc9e13af164f93a520a09f17ac00922542427525416747063945a508a22f5ee2bd28eff645c10cb96884c2edf73a411117b8088abaa304c98c4b7a43e20c684ced4d5fae1c581b241e84a325d1cd59789027a39e3043b284e9765c5b25a9b9fbd775fde5de9cbb07bbe23608c89b500a3eca28854b785aafb362c66c4d2bd1de9800a300f26641142cfd5669f3c4bd8fb33e8858c475995410f0d2431bee8bc80e053a37d9765e71904a15681a383b064c2f30e744818832baa11c83da251634eec6749f8f6b3537fac795954fb63e1291e485a008aa64e02d37c0bb1fc76475519dd93d4f3dddb56bfaa79d7473a6ac1dcc3ca96bb30171801fe8d88564471f92718b586a4bb70f9d14ec31e559c344442d9a68acce4696573416f4f6e17ecb3f168f0587138dbe661bf40b3d936b91959e65e6a35478feeea8603f5942a82ed2018ea0329a4fffe77fd5b04efba736e152ead9eb51b1659d4912a642bafab505ea57459d8c0c5198a60ded0ae22f0367b94ebced332126d8aca35330ac9948454f1795edd94d5419f9a54f9ecdc422d970e8c135326c37c8a1d5e4eb4ecde0501ebd5ec23947cde4afcc1c95d004648dc59437cfb707eeb348f8fed928cd92de2e587ab3f52e35f6a611443fe5a5a29fe563c245604326ae863488008a7cc871142327e7fb7663759fa3e1b7d98c2ca637d3b7e6d7847aec5ef1162364f8126b5c6f0ad2efd7bf67cfeec77862f4e7671cee7b216741fe37c440f6477814c71488d9bbe63c064a0eb2c3146df8474efd4d8350a9f740391cf8a639514cb102de8076854029408ccc43ff94e149dc709d6f01784b6cf5da63e8b9852d56fac1a84a05cae1fea1eaae421728914496611a4214c25f2162aa1b4a66d3ec45b37306e91f8fe49d621a33988864525f6a57f2f5dba6b3b08d902295e39ab4df5e7947fd7279378fd6a366ff1a9e7cc0a323075f309a45a50f915f791b213653c0a09fc44bc44477090db485df5c4fb6bca3bba3e8132ab9c0401c563b0849636f53eb8fe86a71d1dd96eeb2d71377604e3a5cda1a3bf7519a34c6be9fa088a0a352d66f909fca531f3925f720ea3bf442c9a2f0c8873db2fb4d43501a56e18b6011234ab7ceb7b8f1840824a935fbb79ca07a6ac3ed9ac8d09b716da34c7fd8fb4d3efc0f300e683a0c837cdf08432430b08ebc7aa1e23366b064e8e43e6f5831431ba057202ba8ab612414df94504d656c868ea1d243eb4a1356f0f85986b2a03c4298526e7b9e6caa0ec77ac170e84558718c7ee58ba2e3bcb71fe81878c5ada24a6a6b1f9273b45e86196121e8dda2914cdfda109340a474dbbd26df94a6b70f5955c2105e832b2eb372e0e17a3b6754de7bca7ddc94e31a8adb83d01e5526c42f7bc3c286ac0aff64c9ec6bea41ffa9341d3d8b71e3731878ee5c2cd50771b934dd8f0c551ae93ad7a1a94c1b74b3228c40a73b6014c127294a70479bbdd8d01a0165c86fd9deae22efbcbf47924fe38c00947138c3dd482d45dd487c04cca14fa5e93b2971f4e1eb0031b5afd4b3b785e3d52000fe651c4bceb8912364bd368473cb8ec4a155b3aa2afa960e99d36af5ff2ae49821f8b49fd2c261fa35e0df07e015f62fe9ec0254ab1d584e8705a5e8bf72e0586462f41f52b9b7cad936b9c98e65a3a2f68cb7e5173b14f4b4db9d0dad394bc62b8455a2cdfdb253e11051555f1dc7223fca83f56b84753a9feb88e85d9fd1abe3c1264197a592afa685bf6c2fca9273b4da84f3e1c80ada4d13d951ce98a0fdd93c181854a184163d4de4546e1be304251d43098670862204dc93a364ce5cc2653bf5eafa788663a41dec3e351807fbdc0b57b7fc5e355ed8565e9a6d1e9ccea63639b11dd5947226151e061cd9ebcc3b99783a4db88352852ae8cbd468b0a0d6a9785ea8242ec793cc717b2cc0ce1c3259f559229c3a26728636f0c017baf26a4e984ff044ae17c77ac59df2b07e50e30fabfd95d67141031d65662ab062d4b105cf1600f085f168cf25f2c96ec7cf9546326a032192e1004524290de45104d0f2c46109587f0d90d94c7b9b23a8d763269dbc3641617a6bef06c548fb9d4f26a24844c342720659fa1a90fe259ec09454c0278c2e00ed68267b7981aa8efd60d37e2524d1e4e5cffafe60ef934d6d2124aaa95839f2912efd5717b164de3481d0f4f036079f2c67ee5ddc24fc21b44b0b138cbd1e78096b090fb6e207c08f09b1dd64a0a673175408a9b03ab96edb81303bf273dc9496d19ff2a8b48afa5b577a4bf495aa6d4c602c8bbcbe6604fef0ec8eba6be0350f9047d8613e4f9876a63f9b01b6fcc23f466212c419856799676d0015f3d3b380c49cdc6bc654700356a1884031c287022bcb1eb020bd50e241aa901fe8053d263eb7438c1e8cef8cd1e1b098417b21a49dfb30e285eb28db58f56111f14b22249e2024d9a7b440d7ff69a2e5e9ac378134f4809a69674bd2ab39217dc6a846eebe1ba70e0fce672476d5ad5e5077db876500cc445a8dda55b4c60fea5274f6bde126540b0043d2f68a29ad2e1b9ae894b2ec77a88f25bd99f2f10ed7bef1ff203af3f7f515529ad2325cb03e3a7e9eb27d1cffcff8d84fb6b8519f3f03d7a6325c23b82a376c2268771144867223c4ecfe5b4401ba0c7003f1006812e0cb99a5e55b142b73a19990f2b24845125559d4e054070a647bd7c4045af58d5b0d93272e515fd503af65b59f539856b039b4736bf726c1977e16ef208dd2e2f9aead3d0bf86cb08a96843d37c1ba943014b84aecb1cf578261c77c65126d1ad8b9c50f2543e7624cd25d69004b01129f78e51de4b76f1712346783dc49d4f0ea139bb863b963d083afc129ae99c9b0468e6156591a95434c3adadf31b02a93856c33844fb7229101b21b30580c148075dc890c1e0478f4ec461d9d7138570420cc61997621c1022a1b3c3c9eb1f15bbb00ebb5e03139b7e450ea3aabbc8f1bd0ffb938c0bdfbb937458390a8281c308601643468d3c970b7685ac33c57f88831b9edbba635996f7cd8beb2a1d6c788b51a444c425788aa948e7885169f5e3f43bbf0090affbcd8b8334217410d6f1f22748d83107e338aa93012679a8b2eb46694213dabe216d22f59239a129da4fafdccde5d7fa58fcc178b9de758656465c406a2728f706b4546bda873a70f478e7a04a2b878834542356ddca2dc1ea3614122ef1a8f8a9b07d0d9a05a4770174c798a89223b08a620430d15498a93f3984fd62b0c42e3038d5653908feec8803ec0185c4b026b6b892ff8c1d4dc66f7a426816434611f085cb5d6578db782f7c3057dda91a78e45033e7f48838b8c04177050a37d987c3e4140685cfdd480e80e5a712dea17872e11fc8031dff7c6bd912e2abff9c3145ecc4846ba174ca6afb8045dd2c0b7e570efd354b9c25a585925d99c1baeff8288380ba8b45c4fc4878ca5c89277ff3b3bd1ff8826be10d0660031672ac71641d981999c9c9c281cbc54e2297fe9e0671577f7ae06a0370145ff5334785326febe2097ee14a11425179b721a74de3939f0317775e9210d734c8aa7f606e2ffa596e329a5153e4c7f9044d98d6affa8f512c4daa336100c7da5dd01b352ed3c360a80392eaa658ce4f433c277647471e906dab78343626b71da7ea9006c1cc1dca454d71836fba4dcf63733c70913053acb475b14b9da5516c98ca78fce0ca0bd10a12a5c5864e4cb07a4dc1e4cc4cbd613f5c1b8397b2fd54e9d5b04917526da38d6cbf8635232af012bc8ba496aac6958e64e24f65098a0a5b30254d7c9ea2655e720fa06ce8670616ca17efe809ccad425a4490cb7959e300d06e5af1f2226c4c351f58c874817d6563c94d92ed94d853be302b88bde9a7eca21c7b2171218417eb44bf332755bf9d3e820781b4c252e34a93278b646366f6cebe23bb67828491bdffe7d4a565bf763c9a6826088291b103e04402901f5f555fda8f3e08ee1bae151c4d995abe2b517cd5385aa74f10afc230785365caea8e7bea281edb3823fc65a24c12fe5ebd1b790d66b391652342ad304af4f1b6cdda3143f584e2a6d9382018f7c9f9895aaddba32ac695d2281a16c107f9d58482c2690b0491c0d900108206ba45bfdd5497948b1ec3252724f079a484e59990fffb4a141a713265ff01979061062382ed022a43ebb7ae06860e5b401e336babdadbc15282b103f3628d3a202fe043db62a5ff21ca04cd583382a3f00a9a216f676369e94239d25966e47e8dd385e789e00a4ed3f658fdf9f8cc947512dcfa6307da5c9d7ea600acabfc39eaea593680aed9beedb143ada29ed27136a2297278621d63b1dea1066ea4efd85b0982e3c16c1064f0eb78f89b554cbe2696bf455e17cbbf224f91b1301c4e226888a141869399a8d458883cbbce9f72b6956f147d5122165fb598ccb32b1262331d73aa82dcb6b2e37170ed3b7caad9bbd95afa038a56610e2eb54d28e536013bba76b54dea44143a5a3d4f8dad4cffd151a14be67f586bbd6c1751718c106b1ec34eb3eca01be0185b1ae4de612dae6e9614499f5192c4ee0f02d04aa8c35a2889c4f88cf3efec5e0feb1bc24a1a079298c0d17698974c5390aa408279c23a20304ef938e69c3a5147c8e921228a9ee53e0907c7539fd82af97600b38c93856d314b407af5e04b66658ea7bc11753567486291e7dc8767015e67c88932643efcafe4e676cf094f6dc9589d1633d1f231429f31962af194948a3cac90b0ad8b1cb643e6be9e961e2bc2e77d949a56058209424674df7f195403dce1860cfded4602ead2378930249db8d20e53f0a278ea902b6c0b452ac05dda5ebc696fe2a94c8692f8080af3315376f7e5b2096905e8b186f2b20f2c7d6e7819753fe7f7b0bee7f0067c9329ec63e63997f5e209190e2223afe704a7ba0b44f33ae1e214290a6308a1c3a9497a833a41c403f1d0897ffe4ea702553131fa3efe1ac1822f88cc2848a63e309072903b87f9cc1c415a8b2235c076ad8709edc1d1efa563af71f2eb0108b9b8a6349fae8a220b8f83fe5684fd5d36aca69ad753d7e2ca153d5d2591941cb7fbc2260b38985e068925b80884a1f1719a92997b00b823c21c6aa2143b92646a9341c92f4c1316420d064dc48b99b853a891d8e7fb677a66e01035f878fe1a75866acb789c2e6ccc118d15016c9ed5fc3b1fe2803aa74c971f01a173e538e6169f1292c4e2df8128e733bf6cbadee437eb3804c23f26234df4b52fd9d83199684aedf500bc44149ef128ab2a0db98c28a9808ee55fd9ef17af40cc47102ed63f9eb4e8db682ecca01673c33291b419316ba556bd59c3c2aad4552f19472de589b440713b7818285b4a109071a6ba2e5670b702fc54a20ce7e6ae603879623f567c411e980e44ba5e4a709249eed539547fddc79311bd39c9f947885a9070993cceeb9a8a788852e5b14c6eb6974d504e937dfb48b32f0afeadca0bc7a032dbafa18a201a3fc6adc33f3d49a7e82d88da07b19ab042238aa7fc3c974c149fa4df0f3337d1c23b2141e84b304b267fa158c4e35d55663f55e63e8da9c2b98a8ee7ee3ca02b6e87acd824c371bb9c6d5a907fb1674f8db17d5fbea700fb524322c57f59c445be42b215e10fb83c5e9382a0e2dd138f68a825c9c629a01c437bcbadc8fb174cac4c7757a6fba885514e059207312a857d309a17f9dc15bc603446beaf18d9a9d0e15556d86a1d47730fe82d664001096dc4a3f59310d2543786ca068113368a0d9c059d5ee2c0816e2335667b4d28be3a780f3bc535755592084d8d297d16a3bd14a532dabe0640fa2971cdd968acd8bce0b2ebeca0f8f39334e8e246b4b8f1ec0fea0ee7a69954b534d1a19da414c6c16ba57184157c758b02928fc3bf01a84b8c72734e02349251642a6e63dd04de672a138ffa2f3ad2b5d190fb0efec95394a92ce3bf6f6be38418e000d0612b809eae7873d2c57ca5e2a20100506455946e1851e5a0f1c8fbee723e9b20e40ffbf4c70d744ae57aaecb986718f357b291b57acbf256fe694431d859fd991a1c002ee0ddad200b5a7e93f3476524ab515294ebea90bc2717ad087a73a92c0f5540e527d17b0f5150b642eaa4b76e376095ebb765244830219d4816725ccf07bda4016dbfaf35449a57eb89dd645c4d6c32422035ba79ff6de9922097a97ef757ce2955f69f195273dd9462836e1aa919bf6a0d0f8e0e37de6afefcdca6fa30f86cdbd87cbb7ee984836df49f7303a627f318aaaa565723bcb609a5a033fe64b867d317d90c065b547dbde547256dae1466f9a4412e6219b46b257b65ca5eb0aa0e085955ea685afa7f76ad647b54c0d7f7aa39a74bdd6fabe6e1a5058f8f1fbe248fc0a03c498bb0f84bcaa28ba8c8bcbcc7a7a47d9195cdd403358e0ee1b7f21bab32a33833e7c63526035f3c4242219ff424808a8c3d51648598d06783a6d5df55b7dda3f875d3d3095f92180454d49878cbb88639e16cdd6853f4304e5add0a5e29cf74982d5ac9d99334bee3a9498e135554e8d3cd0629238360cbe6d3654ce72d97e21abf197bcec9929487cfb0f1ce82de62f560eb1911ff98c5d68b9ec535cdea5fdd06f7edb81530fcf490888b1e2214e4033f7f306f8919073dca1e440ccf46162a2cb3f45fac501a2e05fb9c1c9e43c2cb2463541f93186fbcdc0dd291b047f284eae9c44fcdc88bfaef0b5113964b13720ba64937a99a6f944a2e7cf9a60e5990ad9369db94c2c887a3fbd2fe0c8e10826d49991018bcc5d6e5f7046e48245e47d7bbecf41c8fdce4e3f998b47a2fc3f01a7a4b848a827b6038dd235ecd7bfdff97d6462d0a4277fcd7a07626c9c5e6979b74bfae5e87c65e9deb6f2653ca2d473a64909d876d1dde7a30895b89c87d4c2bc176ef901ed2f947e08eae28ca20a3bdb77acf928794f475532af39b5db192dde39b9382af9687c81b7650a26fb991c54bee94d69efee02cbe032d7ff74b596b5d39ec957daf2261090683509e57acefd788de72e6887330721a9a1694b5e1deba56b1311c3553ec1eef9354c883f72f01a24cf2dea65140005138f362f5bf63a49f18178a0e11f745b88eaae65713f084c2f012d78febf9641fc5ef0625910fbc0366d95b539b84226414a2090b7618fef088ab1e2765bb7413c8c1907bf954d2e82c10d4b019398180d177b20137aa5e2ec47c798a181599448ae402a7665980ec12ba61be5154f78cc621dba434ca0c0bd88793e22a1abfbe5dc42e8119f7fc8b1c1792d4893a169d0fe2fe1386df0bc36fc308d4dd6729091b0544a1ca9061d732bf0cca9f78867da0da15104953fca607ed2bf838a00af4f85b9c7b4eda1d40dd684fe1cb574c83bffddea77abde65a4a139675d7914bc7aaa74dbc5b5a08526d1af222b9a07e61d8abc05db4d07c2f698bd05483784b14802f484a2fc4c2122fd02cc388e753564af31c8dcd1888370456ca226ea7ae6b218ea6e42e95103bc9c86d2133feab6c05634ebb5ec37209b2431aa7c55e72ef98abe14cee91163d63bd418b44da10752aaaff8b7896c7e1e014a724cf57de0d80be94e185c552e7b82f7356397be9d8bb7580fb5dc96d504b2c00408a6ad04b92ec5935525ed68c325dbed81ac5d38b2d86eb74a21fbaa783fca20379cb5ba68c5f24196c0a8bb5e85efdf350bb22ce7558957e8cce616e159f76ce2859fd73e890971a735858659514d0b9dd0760c5ab9455aa89878a869aa1915bb87725e80d59e352fa1976654852948e1c19a39d924c5236a247dc100e2e773822a7c894bf17ca9c4cd9c4d95a078978b1db4b1fde90227d33730a3d4a51344dc4b5decd111dbe3d62793b6c9d729f7fed770e7d34343d78b456a8fe5a967b6d7ce37725ee9b65edd3b7b5c26fdf6a5b538a60ecdadcd97afbebaf1c205de4f237aed5812d40eae9c3ffdb24273a8581746b1c19a6c472716ec3fe5e7cf55aa066ad005e8c4bbb1935f3729d11b09fb441b42c6cc9e2390070af666c8a0cf96ff9b826d658c7e067734f3153c8dc5900141ca7a7f8ed08fe42c4f52956203e90db1050a6169bdc08d065eb7016c8a5cbb2f167ff63e492e60bd6a73c70102f849b606ed89cf723f120dcbf207acfd37444683255a000e6bc88b0c708fbfd0d0363f5d7516b046217a87df3ab06c6924a13fb0e21d652df8d0c075f1370934c236b53320c74ba8f5fbe5c1e7b463f7f73405b0cf33af8628b1def14c02ce1697e6aa1a62674a6a4c6d9fc54fe423096a6487a006fb4f2910bb7020d59f68b1560856c5463da09107316c6977ecca4c292d20d03613aea0a15c00397242da1894ce44712bd1694bd816be961d27905ee292bcfa57477bb40a0f1f2d722a6916b76aea7310a9dd6688ae2aeb569a0b0f37a9a50e8766d2d1475c9d6589a28d4cb6aa9ba9e22fdc14308cec13ee7998324a364e75aa57d9b6e509e6a33099b14e8ed8c4b64480283127f400c9924b69a4c90884e8dca8d71688c836a28914de42e36710fbdc24a875d35b399cefe526b032b09c77667a0ffe642cb0fc9932259e1042826bc0088ad95aa656b9bbd4df7117c4691ef128c647c9d01be0e157b2ab1b1131ef6a323572f4bebfab0cb683d26f3c8de307f928cfdb531665852cda0b1aa288c0421faea7d94995153409f7194bc02c759750dbfcc8f484b1774c9efa8bd34c795ab4d37cd976a1e20d7ae2babb72577e3aca79cfb41522c2136af19f9aa9d8450a443ce1c5b89fbe74889ac4b60091f40c69fb8b63c884a4ed7dfa2323d3a207cc54b76abd89f19155555f8f62cd27f30ad0cf170a56e6badc0a352700b0de08fb545c225616b09cde414d81d9c236964540b3103ef365705d1d5cf76cb16b864210c48beb909364093abf5a4e50dd8cc50a7cc2b1b1c0f0d88c6dcc2797d9d89aafe992809a9be754628158b113436adf6dd1838ad0e6df9616858769a4a23d4e630a5d5f199022e9a6f16139dbff97ebc69c64405fa541347d43785588bf649b04aaa6b9de046e03b93c8a6b677f81d6f7ecd24ab1d63077a569da1ba3dcb6ebb3d1cc0fe4852c1f693cc4198a0a8a363fdc7b55c35d3c4592a5b4af439c3e9d5baa579f4834e32f849319098225f27f92feae819b24da45f5dca4d24c9c2929218f6d60207f9f0e6616f60219b3122ef6dacdbeac535b38ee1442cabc711ebafb97018b7e30439943906b8375960a0a5e8b91bb2762588c73866d9f3da74ddc2ba248203af64550bbb77306cac19f7077c865589dedcd081bba5869e8eb9b17629ca1ab028f28f877fa090aa488f4c13fa3f7545a639891df584263b336aaa46deaa548a34bd9252ff04be55754ae2b78e0446784d98c252a5ec5ebaead0f3733062bc904aaf4f7da9cb6a2e5f7b7dfe5058bd01b10873130af9aa73286464a6d70c95f6adfaedab8985556d9d9ff9d473bd29f85424c9110dd2ac9b996a9a41fcdb36b32a3c7cca2b550c67a6ea83665e955b813a9f772ea8b1892a7a419d261d879b2b35f5122445a8344316adc238d0a1192ab942397beafa69907deb005c3a938c3e4b824f58a15e2b0b2e237b722d91eb79ae86711428f386579ada0e6326c8013a926b5ad797e8493b1d4d6113a0caf3834ee2fa08eebc0244783e6a2ac85a10907026d63f75d52b75ab6d04325f8dc1bdac191c83d712e15c603324fcd852697758b7a090b33c443c78173990ac4416c7dd068407787040d54f1f63d0fa97dec810b4bde8db0224a06c7c1e4ba2ea645d1d3c48e6ab163f81cacd525d87babc6ae072614ac6c41abeaa06d22d83d8aaee4e1a71424c58ac4fd40f424f8d52e8626061cc74900f3a0021587bb4c473dbca3cb995d11a1b63d6200ee70c7e9e1dd35e00541810b71f0ccb987966a45ba4b382b2a7d6d449512546dac330b5da59416014ff8b4bb365b0c84ed385b9af67aa65d6075f262eba6085d036a9b7bef6ddcd9c8d16aa36c0feec7ff7b446439ed9f1451f1e2be9b90be9760841a4a3525101084926953fc251b9461c6528bd794c8c347940e6c3354911abf8752c0c1415009dbd8eb377619ca9c94e753d810454485c78deb60fa86997764ceb0d341aa380a5aa45ed9a3c8cc90fe7f18ce08c70b73e1f37d0ab53a69d19c093c4d6a7146853b8e10f6f61de58b9542fd6f1431619d4cbe3661f4082f065119b8c42fd4aa045de1bbff109fba908e233bd6693e16f0294b8a9fa042212e29e0551aa3ea1086ff69120827b13affa69708b734efa3e25a080c2e27daa9f921b82d47785cd5502f121e6d43fa116e4eccff0250dc23cc2408708069646c747f958e184bd8cb700c04d5704a5bbf3e60ba7eefb776f84246f0eefe30f1cd2fde4c980e2e089cbeb2afd783df47c4422b9a189bc1edf2fe4ee839401ac0346a0b30f9923cd76bbe6acd432cc4e51e6c4a946f262cf4ee4f28d023d42f46509e95bfdfe6386f7d956fd4fd0311ba29da29dcaecff0c3a4b1d63d60fb6278dedc2d7f72154e8993b426f152f7c94e48f4bf88ac8d1c44e868014c7038acd328d5b309c55a503c9156f8d689b5ce0c5d56ce4656cdfa2d35c730d32bfc23b39e966cf5b6cd42415eb6a90cbc62a40339a90700818028040886c9da828a398f5885939f9d982c7c6c2ade08119163f37224412b2a5dc7bef2da54c490626097d0872085809ebb0b832a6e8e05a99b3e670b164b5472efbcb4a8993d37fb1e4c930cc9281b040e72b33c20de5ea49b973f080e02045bffcf1d107c71297fefcf9da77e42919d2a6c61a390820557a2a5fdec81739a42357476ee8e5fe9104fd10bc4ce9fb9dd55c03aecb6e75be1707209f624d7e385c39c35aaed25a8abd1af0a05fed7b35b81a05bccc556f49161af5e94b4c77cc891521a95c35b328787878789ce5434e14cad5bd34fcc66fe68d36b727610e873b426f9b5f8d97e5b77d200e0743959766b97c7c69b7919fc5b0460e1f0e1bf9325bbe5bb359ad9edf17fa70cc462e9b21cf793fe043a948e967a51c8d2457a55aa9301e1e1e9eccd188a26ffaa6716eb2b6699be666b064ec932b19e4607fa971707087fbe02f12e7e293dc6171a5bc912cfd5976afe7fdcb1b50def438d839b2f7a8b82b73d2161c57be44191b525aaee160471f6e460e35fa71b4a4744e1807bf2984731a72a8c6d9f61c5c99df75d0a53d09d13059fbb6f6392fdbab22bfcbe14be5332d681f661bae81c9e8c42c99ed39032143c8d832dad12c79877cc988321f1cecf123bb102f5762fc9eca1d147b0e4a0c72e1a587b3788db3f81fad765471af8789443b6cb2b89ad7af2721e6912c9b94ba8eb59d7dc7332cb3b63a8e141dd84816db2d9c97d8d2402d409eca1ebb9e0a35927ea969bf03907c1c5a4afb56dac753efa0cb1c403876d0b4fa75abc95116c50d6f6e8940d2b270e10ef990fc52e45096441aa541a5a6c52499460b373ad291380f3a64d5b6d6b4ba11a95b6fef3afe624a798bc708929e1b5c4a29df55fe72dfdf579e0af27613d6704d0fcee2a14b0f4f85ddea23459c5df5e4b07fcc687f9932ab234ee2541cd49ec32b18d43e9a2b78a6168752b41d57f4e0731544bb82474a4d9357f05cc173c5911d3c15730e4d035dc775e44bb75aaed3729d96ebb45ca7e53a2dd7c16e6b5ae7a06771d8da8134313ab271f08703697dcd2ef2a55b2d97968bdfd8b4acb3f8672a590682aee33ad28673175808f121b27f9c12b3e494192c597ef5af2721e6ac5f9af32b527aff0bc77c32c10d7b55b1afb87307792a66edb32faa9846ec1b23dcb0571de3bfd59ad5aad5bad55a6bb5b572b5926aa5f425ae20176e189bf4084203a57718a4b8a10e1fe847a052ac63091e7cf054d89f8aff8e94fc3a1d353149961f81521278f874b4fdca83fdba89e464486e78f33b083ab84ad1cfdeb2262651c9722d408e405eb0c9bdcab27b3def1f04b1998e7e6dd999b5597e3192e09e4a70b7a6a4a9e3e00e8f64e91b296ed8c9ef95a7b8cc8a3182b84d9639e885fb15217d8cec55af5c88a9094e90428a8bce39e98c020b51445153e845e994524a29a594f273d06b4a29af7972c5f5a7f3bae835e7c428bdae39a79454ca29a76481cce69c534a3aa59453b6b8a00425104144a665512f7ad17b746464d4e42807ab55102b4795128c9690a32a0848c6c95e2a8128475512903891a32a09407952f93176e73ae3c7393f82dbc7acf9df38289d88c794eccb71f008f92eaf94cd3edfc1aee3a0bc3412c8dfb4ac723ab44d0732a693234723c70b39f2b523eee7b08910e99cde91df46e4c7f0e3864d443b927ecdeb9a73ce49678635e0a0cfb6b1f3babe99d3447c64e73971e16648a8074242434345ada1212121a1a22156115151ab6888c512124ac21272c289295c8c527a746464d4e4c808c3b09b15d144134080b450a15285e0fac710dc1de894524ed984ee8aee0a1e8810b10152f5fca0a747a55a05a9543d3d3d2b95cf0a6815b452f9f8f4f420f1e919c210ba1a7180f170e022830a17a9e5b719bdb0cc2566e3663a07db5a178e3443cb59e95e7b4d2aa7d349466987c5ad288b62734397dc8f82d2b568e1bdc7cda8fc8a0d0c1674816273532c2a8b5b6358e166bec3a2f33a2cee7333b41cf3851b66f8fb669881d2154aad8d2b337cf861c08021430c2f545260c0700183d702060aaa830103c6098c9309c6850103060cecaf5c047381c2b9d10926383903c26d079bbbb1651afa98f3c22c79e2b06998ec4c303747b365fac5186356263b4c763626466898ec48263b4c7224931c26394c8c30e1616284c911264632263bfe1299e830c96192c30487894e6422a443021357dedcec663897076e86848e848486868a5a43434242424543ac22a2a256d1108b2524948425b4c41294523ae745e79c744601a4eae9424f8f4ab50a52a97a7a7a562a9f15d02a68a5f2f1e9e941e2d323032000815ef4bae89c73ce0ba3734ea2a19959eb99fb0c5bee639cfbda19de9e62ed338cfd85e973f8c29ef6a93838ff84c97dec3b69827133a10f0cc3fec2287d4a2737d34a8c308c523ae735278d2c4d881f5c96f94d07333aa594535e3f8410a20612238e1c1122a4650942ee952f4e24a713c20f8f14770e7e1c17fac0525a540b6e78fde6c66faadfa4c88f4339bc83580d07b18ff1ccc86f9eb8344b9a78aac9156c32cd92a352921b9d5862942347836689916a093859761947b3a4c992a26c49d192a225474b8c961c5dc166c951137ff19b252d9a25459ef241b3a4680991df2cc1967892c648a3a45d5b07e517e49cd7e537cee212fbc813875302e8d27723232b6cacb0b10288a4b1c2c60a1b2323a3232b8058f1c30a205604b102881536474746464d8e8c8c30620448d5b3a4a747a55a05a9543d3d3d2b95cf0a6815b452f9f8f4f420f1e9c9410e6c6c728c6004974e713324c412121a1a2a6a0d0d090909150db18a888a5a45432c9690501296900c6c60032a94e462342d21a1eba25f8a8dce22d32fa494d2eba2736218e6829d9d9b21a12021a1a1a1a2d6d090909050d110ab88a8a85534c4620909256109d18006179d73d21945caa473c2c07177773fe5983252c1849892ef0215832b4fb47033c922bfeb6067334a889410519a2969296961dc0ca5344a5a9446494b0991122225444a8c9434516244a3e44889d12b69f98ba7a4480991122225439e9222254443981ea7b8f4e775cd2fc54618378d9b9933e366ae8965f1bae6bcb278514ae9753319142972d139279d51509772ce96724e3ae99c524aff39e9754de9eed29950e34a47012827204715147c90695a59b317f3c29ddf9f853ebe141b59e4eb0b6d7671342d154d4bc5cd5c3395af8fa6a5ead181a6d593334d0bc8c198e575d1b4560ec64cd30aa269ad5499a6e543d3eac9ced1b47aa289a685e4b67c26b634ad9e2b083387524a29a594e66014c3e8534a29a5744e6c66d945e79cb40399fef5fde0855b84eecd51050514f962c23de5a882020839947199e07239aaa0e0c9e1ec1e28745e050a9b1caa3ce1b2325bd2ea4275fbb3f8849164a158c559668eaa277672e8b95051639c53fa74776feaedd4779842adbcbd0d960cb3d5faee99cd81638b7169f21409e3a94d7e3398c8c1cd4b0cc2d9de76fe8292d95e05286f4664b6075b5c64e020abb7a6c6f9a7812664733d46909508b7b506d23e3d41d77ef6bdcd21eef631db08c9db04320495375513363d3d8e85bca920bfb4ccf6ddf2c9db7713897d7334b0a7ced5cfb22cf30762268818637b1a1988377806e20d8de48883db6f41400e6e1f3db5f57412dc42983414f423049f5ca9ad9c8d1c8d24a527bdf6b5108dd237cbc1ed6727e1665c1b6cdbfd6d33fdf67dd4cadbbb8dfff04d758420797b17b2fdf63ec45329da47d58f50debe723885eba11133f7853667aad36fa16d9f06f2d4c96f1bf793cb1ce4beee0bb3cc7de4932fccf2e9c4f564f86b22269c7dcde3e0f62eacb8adfb611665d8dd3c1747c351f5a3cadbb7cef61b0e07bb6e5b09d7c8408cb17d0edb5b1ce3e0b6fddc9ae4309c79db5c00617b6f9bfee12fa8df62dee66f1b0aff48a206256e7dfb5cadb556b97d3cbc759253e7c7959f23b99790a3ca082cd30d6e68779052eee8c14155905c4d9e1d2443726c3410c43fb690833e632a12ff9b73a64cf949198b8e84a4e5610707b9eb6df6d95c79e2663f75c89ec7fcc21df26bbcdc3d3c6734081d3372f8355eae33f54be94fc7fcacc6cbdaa7f4cbdfbe987d31a5bf1b3473349d536ed0dc2f8370d09473bfc6d698fd75fb6ed0dcc3e474ece0d6d03e55f6b9e87016777196d661847b5d2d9983998c39fb52e4639f7d9c9f4d1a1c376846a208403ebb5bca96dd1d63cbee8eb16577c7d8b2bb636c29bf20a49d9e398bfcc29acf698afc6ed01c84519eb99f3e51e4595d81e2c6ebc31e9e352dec91b52f4c29b5e4ed9b9bd94a2f2d533a9d5a5c8605bb5c7aeb2fa09fee8d7fe3e95bfcc5863232f65d772f56c24aa57aba5fa555f1547d17f0547a6ec67ee9abc536ffcdd584dd3b5b6b6358bfab1f3f34e113beb5543f3a58aa2753e9df5f62cff5d2977e7aea725f6ac9a72f7d35e1fa257cc2a62beea6fd74702b9dbe305a5b8aa5b71f9afe74c2b3647aec4bb8f4a7d3c974fa8e51fa221c49012847551484b24b8eaa2814e5cbcecbde6a2d2e7dc41cee5ce3fdd387a6af26bce2c4bd6f7aec9ae22ddd8fa5c7ee177a1177d64e7ff105bc54faed96bed6199bced2574f4e386677cfaa54af149ebe9ef08a13d7f4a78f9c0ea68fc7e9f31c2cfdbda6139bebd70f887a42b1a87b7a94fa28f58b31c62972263a656d972d4929363c9bdeb3e9bb414f5fd3924d5fb999fb91337dd843e6fbdb76391a9d4d5f4ff8029e4fdf71341288d3bddf713666367d45d98ebb214f5f47f03e8abb21f3e9519c0d9a4dd24f5f8c31ee57e4e44d5f11202f73624fb0eb380bf6f5c4dace4b9fe7b810c20dad942f27783acbfcc8a5dcef06cda52f7dd1c1ed635cab263063da5f9b4b0fca170cc4a658ba41dd6f1cc43e2f0537f472b0f79c1d20d8d7d670089ee0782ae744c753f5b73fd9f194f6db77346804f4d6ed80b63e8661f631ac5e1cdfe2faadd99d6c9fc3a18fcc99be9abe941a319b9ec649fb93f6a6afef236f38b4251cf6c8a53f611c6679bb98f4912b5dec2397482739d9bead4189ab6d9af3a82051e7afacaedb9f5fabac8f7de7c5975cfc4aaadf0dcac5afcfbdb5918b1c17bfa89363b496528efb52ac7d4b69fd48e96f1bb76d4fa9a556c3316fd1c6aff4ada5947e7d4b29ee766cdd1e6b1976d12971eaf571e5575677be7cd287d98543eeafaf246c81ce2618dcb0470e5348af7d8dcdf73bcf53a5a7afe229ec9b9bc19e746fd05c831217e3300d99b93742901c8f725411e1472e7d2d95627311c8610a57d3924929dc778366eeb92fd6342b733504e1fae78f7d610c2263b664ed63588914c326d51a9f7bacd3691bb0131c0731ecb927d56ec741ec63c4d53ea97eccd7c417bd96a84ddcb003b3f953523987d4dac425c20d3b391fbb4fef21d4194b9508d7bf94815e40448e9c8d52ce1cb480cbf51e94388bf7827fb9d488e121477b4fa11b065a2751e5755db20b47b2ecc24e4b976c45ca570d0149be39aa86b073eda07d64cb6f6e62960c94b30789628be5c8cda2900fd572a5ecba324d23d1af071d36f95ecb914ad774c2b1c30ea7fed2a7779cc52ffab5cd0f0733256ef68511a88d248b4f4cf1884cc4d40db3e8b7bf3eec491653e411539d86fd66da562d472ac5e0f0f223096d7e0db776d28b6b00c635a0b5aee1a096fbde5bc4c626cbeef5bc7f1054d9c4959ec1126526448b6cd96d23bf7fc8283f958c095af6c145c55d5c6ca4c43e1067e66d4f42c4f8f153d134dcc969b517b95bc3f45b4a292f6a821b7a309eda1ed3b298e58b8ab3f8fbfdbc1edccc417b71e7a978d88f98857ff82b1e63fc18196f85a1a9f02985f1171a7a6272a891638708e8e881880f74f6833705d9df6595e5f7423784b93ebbd7f31e068656b1611b56ebb6598ec3308cb3d956710d4cc67ec31ace6e96d8f5cd3a2d9df8ca91a371848ad3829226b339bbe888f4fe94d27a15c54c6775d343767323c3267b21d5686047cd500f2e0e6adad7e2a0dc31d4438da6693b7a38a2b3a30dc97010a646d35c34168ba5a3d32c9d66e934cbc26c487a76788c1ce9d101ad8e0e8c0e4caf563add4ac662e15c2156a94dc3aab49e07f375dd027658a8d41c9d1d1e230eeea8f1a147101ac070d0116378f4d9b21417c7a4b286e7b05b70b4a06f621f111b06d979b2f6f30b61aa946096f20b6bdcf4cbbe91a4f9d91782f3270d28b86164f5b6cddc6171431cb23f0e30608d2ce5570393b76fe51d4ce98f4399c61b70a892bd6b64ec4f42c8232f35ea4d3fe6a65fe33491de7190a76fb0b8e1cd321cfcd9f4ca33b42ce3ca977befbdb9e1fa1251bd5d4629679c92946223fb17622047174adc108c13fbf0963fe61c2277fde18831e6bb38327104b59a64d2b3771ccacf8bd9c70cd5e462797e686b11aed7b6b294e5bf0b2fdce9600a15adbc92a38a0aa08c24c71cf37cea819b26c22abe0842265fda07e845cfe5e1339ce5600fd5c08217aaebfef263da2431e39ff5d0d72c1ed97bd74f8d52accda7f329d5326cd28c07fd1e998aa77864ef2f1f46bc616e73fa4f8d9bf1ce7e511929697e6d895dde943465a683ebe5b4f167a478464a27a5b3869cdbf3298ff9d7fbfb531dae6b5ef3ba40e79696780310f2e3055e2f6b2002a7c807337d30d30fcc33f76889319aa8a33b464957142c9b1cf39c73da29e75ffff9f16b16d190837dbd83f7ff617223204a2c23387fc35804e7535ce79c2ad929957446d2ef66504a2059ca482119fb69e7cb2fbaf464961fa5b4d23fec2c5fbe733a603f9b45fa8e1a016dd4367c47326211dc5ec335825b29b26cefdf469eb7cf5ae42df44c39e9b3b36d9667aced43b7b9f1b9bedb6c33939ba98f71333ed72f6e4335609edfac1a7ba394e7d7440ef6f7e4f0a524851ce0725699a23e765027696e06e57fc977cec60e126309220b9d21fdc5de576a69d6e5fb1a37e3c2df5433eb1895bd93b71fe328b473907843e7dc258829efef0a8c0fb3daabdaabd68cf675dab4d8dcbd85a9059e3f8068409aa6a934dcd9c31a6b45bd9b579e6b2c96c6d280b495168419614d4a1f6eef7dd5b0ca7b5b8aa7e114efbf57f11eccda6fded79a473ce57da18fec3d7d2d45fb161f3df52df00cc2cd94be14cdc39a6046de7bda872d3cacd2c2fb5ea585a7799ef6295e0b2dc5c38ae68f096402718d65b1224da87234261beeae01b9b8335664faf9c36602c18ab022146e91f2e1f53ccff34af68783f44bdcccecaebbc6f3c704825994e7799ee7b5782d498b168fc22d3e72dec7b81624c36345584b53790aa5ad3677f7ed7ddbb4fff0ba71f15af87e5eca474ff914cddf845d2c867f21316671f7f38f87f7f74d5fe8bdc9f3a683a8477d0b1cda8c42a1fee219de24a70d908ac2a4af6d4933e0cc416b32f1745fbfee51beee4f5fe8e518d96118fec32f035694e95f1aab73d841c2e6627881a9e03005671886afd25a190ed21607e9c3805d82c41b626600ea346fba3f3d0a0acafbc816a370872f0e75dcb73ef2c53d68aedf4c41db707a140e5d289f3e46a2586badb5566693f5ee4da6bf5f63ad76c8bd9d65d3cd2653f4175386fac2eb5fe8755f1210e4dac44111f4f681f8525b71132253538ccb3865f10546b2f4a0f9f42e52b8614b6ef964c03ccd3eefa3a60fe688c667fb83b3360ed62307e9db16d97ee46cb4c8d546ae9d147647dcfbf5c87ea62c6e8b1c554fe0c9f771f0d4ed3e84417de872fa1044f9f0f3fde8d7fbcc5f361bc942ffdebf5faff50e700710b2d00c677fa38d7ca9de176e40bc47f9420f3c7de1a3be1084e9bed0c5f4d5566d82831b4e2419121f4f01cd2096a7a2ea094432fd163afd858647fd872b2b1f7699d2f0692cc9421fc617a5c65af9e8171dd4581c37d3f52a6ca17c851cbc9ffd1351be970341f2e500907cff070e6453503e3d0a3e513a8d14e148e4205551b193ef9b70678d65a4b532d58850b836a935723329eac1704d41ee77bcf70b6bc06c7a59a4c3fd64eb934d5e7029fd1a5e7079dcefd1d9f4282728187b94978242c1292894f7502827281f51f451f0a7a0fce9a3a74ebed0473e79ef4f7806e1664e5250b00fb97190a2bea230f617cf20273c855c144eb97f3dea4fa80ff5cecdcc2328944f417d3883780ae50b7d64943fbd09cf20332829a8ef0698a711e766ae4b4af2200e529a3bc9cc0b1fdce9f3cd9ecda66e1ccd7dd357ce86e9ef7d93e94da66b4249c1bd1f7a90da8a3798b08df165b3892cf4b3ecbef0c195471f4e2495e3684cdf3d89b3d19c0ea6efbaefbe69c441fa538883d4348f3848af34aa5d931c7a4d37c9f73b68095d7d9dc4c1226ebc17572b620cfa91abad1863c8757c82427d468119d5922cf451ef58aa6414ea6d467d44c9a050d2e8e4e42377fa18974d3cbc6e3c88a7505a3c7d49e4a9224f794f5fb62a0ac7ece1797fe6eab65edc99fec6dce11a30771fb999ee6be44c5f184436fdc5a1f780ab6884a3baf7efc7c88bbdeff00f58be6fc23f0491ef173993f7919bf1be1a30a34c386614beb97a8ced4e49288fc29d4db87350267d78dd64fa2df0ec417538e6daa26f6966fa42d0b6ee177a34a37c138983f46b58c166837a9a75d9f65996515f6dc928bbcff4f138bde9ef179efefe090e6d9ed1271cb3709230922059c36197c3cd66e670b30912c4c88a5bc9e03646e01e73d6e1cfafb30fd99d44c724a04b8d2123b2bf8c298cb27f4b0f126804dca0d1ece35ffd62bfa2f4872fec07cafa85608b1c663368960cb50d316fa0bbbbbb5bf5842c7332eda7fd57f75ffd587773343e7cf084035071001274dbf004c824f77f51904106a0152e40f08449050c902106333f44266c3a907b60dff9e26e7419fbc20a0465598665dd7d59da7f5d5863f6eac7fab36ed9dffddefdf537cd06ed332ca30dda835aa880ec1fa5a67da1f64e3ffbed8b8077dee807c467edb1c772e46874f869b873bdb6debe8e31b0d7b0b78dd883e5ecd3e1a06338023378b8bc879f1b46554d0ef9028a1609062aa11e95d06a8a22d5143f504d5103d51438aa2988504df14348d5842224a89aa0aab1deb59de38ee35104f724e4865906575c3c0535aed4ea5e299547a85dea865aa3a77c7417808922423be2fb7b7405d9ff5b6eb2bfed19c28f9cbfe82008ea5b61a3b24290da6141c382a0d62e8b1b63cb6cce2ad139eeae85c017260de5cfd0bbfd11dd1bafdb98cdeb861990ebc23e7e60e602e3fdd77b65d8665f78ad4c394504df1dfb30c3300cccb2ac9fd2ebba92454a994996d6a6363b4b11fb9b9e47b1eff6ec7ad08b3b07e79cf4a240ccdc836656c526f6dcb94e8be119197c40b3fc89a3cb28e7a431b6245d34c715e92bc0e8c7c7be1b638c5f17a5c4e667947ed641af138b2f323019a5b06625d5068bd2e3625198b840cc264024c000ec0c1de2a04c0225e2a0a4370e4a2ac4c197924c09d31d3722351a68209de7681ab580bdd33a6023119281320dd662920c868b743fb176f28e57fea4625fc5189493b9f005b0ca003c0258027033da0b29c0a6b96f5586456142fe9183d2286507b7220093715ca4fb5a040828d37d74c759e497f08f24b677aad3834b59439e22b2f9e6d1341ae2d8cbaceb3b8566923b593186fc0dd321a269245f5cb532caf26792027033da9c06b0165f32190d51b87427cc284f8c3144895a3447a2a323c3a2f4dc901ac0a22c712b0e76c35a46a1524524c05a4c65344c8b1ca42d6a448f8ad0af48e9af22f6a3acd23b252a82eef80bea43bd7caa236988c2f5fe561c802472b024592c9e063880b558ca4c5ca4f418fee890b3c8bf52dc0c4a830c6ed83ab5c506e07355a7e59a6d90a932560032750116c790a92f6880b132c3875f0696185e5452f6163b15225f9ca875e3524403c847c1de836800f919f62331867c14f60e7b923800f927783b3921cb375191e55f1c63014207428c03087d892cbfced020536758c70ee42b37807c8b5764ea0cfbc9d41756c35185a30166ecf323365ec85406d812768b5564ea0d18642a0e4b4291a937acc555a636c0927094b9b0f36c3295c65aacc95407d852cb5cf2887c9112c6c50504ff3def4af9f22b4a102e2bc6c891972acb0fe74e4bcacfb0e48931a47c12f62392457e55c0454089c4dd70009817cb52c10d005ab632c3875d3198be0cb305ca03a4775400f657c63e0b648bad8c03c91388ce0c7b591f9aefd0aa932708cf8de7e49f1b24c9eefd3eba81a8914616eb63b00ff3aacd322dcba47b1a98650893fd630fd81f4e75498518638c31c618638c31d2eba7889d9f2290fc1471c44f11427e8a08c1cf102f504ab7cafa19d2c230ac5a9f21419aa669b67256f6cf909d9f21487e8618f13364881f22be10636ca21f228c7e8840c2e974ea13d93f440051c1a3c943efba5a64c0e090438d1c3bf0e0df83ff45db7312646f67798c3f2c3882123f2ce0915262d9cf8dea87052cf8618110aa2ba4c00b3f4864810a3f483c41850518fc2091e4078923fc20b1c40f1235f841e2e60789205fb842d7752dbc9f15186d610ad5b3518021c8c50f119fdc39d8f887080f542c0d5ec460555454546290e1f1a9095f13dff77d33acf0e0e78819fc1c61c40f114e00c18f115bf83182f56384eac7881ffc18a1838b829f132421fee0d8e007a7053f4714fd10e940ee7e88e0e4cec1194ae985fd1081e487880a7e7050405242f5810bcbb4ad6e5555052d4c405505a15c735455a128bbbbabaae033e79c1ca94512d7755da4d20c2428d92ccbb2d2ad82cdb5dbb66dd77471c164adb5d6743a11914824d2e984a80927f6de7b4fbace9e4ea7538732fda06cd7751d0a858887a80785282777d1d64de3482757544abdfb8986488aafb34c1742b8210e36154628724c0edef4bc063f40417229c79f267e10fde821a8d0a487f09b5501947b88dc0f932311a4a0cacdcda0b270637671b0ad837de4621f663252db71506ab86eb5760e72332e9e9255dbea4723662d666dfbfa0667250937e6ebbbdc392da45661840a4339aaaaf07113e4a8aa222823e1569193a36a082887910839aa8684b2e466aaeafad7130a6eb7a722572af7f3deaf73f07e8ccf4b9adcc7788dd164bad3dafa1a86bb95701b742c04d58324953d279d73ce1cd48a04282ad08d5bf520fabe8ae2860e94239007b50d6a4ce9408acd9d55a844ffa5836c0b8cdc5a5c482492ddaa4b8c0c981ce6b4a46dceee397b725b871ca9fbb96d4eaee79c39782aca4f7ead2a272db82f333ce9e9cf30b326a5f511237fc06c4babc927e1b0b17c0ee366645b292fa55476df758f7af9d5ad6b3528215f6b6d4786af1eb9ff18ef1e41f8e0207dedeffb5fafbd0c19c6d190b9543269a62e35d65d2a61d8d7c6bef4df0cecbf4fa5b1c6ba4b25ecbb41730dec79687f5f868f1cfe1877f115c3f01bf6fe845b7c0c18655a128ed96ad65e6bafe5384c93335254aef6fd734edced68f5fb01cb5ae823e6d915db7e9b5fe59c13e36654beb917d18755185b8d4fef1c8d187e7be7689c7e9eb8197bf21aeefe62d4478c67601f230970bbae8bddcf902b4db84d39db5ccb0f673ef9e9e0c9d7b4b43cf993d76165c09da7b282c116170ecbc0303806d7c091030547c0419a83835487a71ad59d3cb5d15b3e508db6b8c8f054e99aacb5265b2a59d2b5d6254606857190d25ef9b0fddf2b7370907646e1e86087a383b7fbd237c7bd9427299fe34e9c34ddfe12ae957b89fb39d9df098ee4be7390ae780e5295eee8434d85fd2cae5495e9eb88ad235cf05a174c8436b362f015276a4dc26988c89c41b8f2a7fc00a661188665fff91aaeabeb9f611cd05ac541cd7350d3b4ecb1ed717de2cedfde034341a0c873258cab16e9d9f5b96458d62d2df1fa5a5af274e2ca9615a552fa9c734a4a29a54e293da294524a29a594d2a794524a29a594be8b20dcf938a2518d2ebe70b93a93b8bedfbd363703c348a40bd31cc3a6ccb24c4e4cdab06f92ffb1bbbb48480179d676778184d6c8716f4f258da32133e935ae723364267d25691ae935d25fdf596e06e9af4b23691a8944b999ab39ed79f863af39f6d9898345b976bb7f735f1df3087d34e6f10396af8fd971cc58e63e1edac71ce391a391917c64cdbf131cd2c7481beedccc667abfa7effaabadc56cdbc8d19059fbaa69da93fefa95a93d492369f7a783f76b5ab276351cb3897b1cda7712e9bb1b773b31f2072c939e03597bed2407d7342bcbc7feb2bd1d77bffcfafdb85b8250eefeace7e0fc15266ee7f9604bebf417c196eb5b71703e922b576637e4c77ffebc9e3e865d1876750e90d89065293ea6418a31e4bb381292b2c434fc7b45faa8d25da494f22f39a57744d197abbf7ba7942947a70b1fdcf9b41ae1cebf7d8554655777166640997f3922375fee2a490ec9225fc65554b510cf215f562e616c815225b355fcb2b8b2a5e5e3689ccc7bd053292d0326879b1a9ec2e4d7399f4cc51cf22588c832df264964c5388e10f50587e7707046a0ecb607360e22110b7982f33defde39bfbfbf1c0e82f2457beda3892cf367e86d1f5eb02e21db9c90e7cf2a72a2d9a8842c18c541eb181a11000000003315002020100a074422a158301a4792b47714800b889a4a724e18cbb328865114a610320619038680008c88880846e300a2a960797975c8ac9a228adc49e1c3d1aac3951473d08c6d44874edd386e79b51752912c3b061b5687dc8d5ab2bda64cb8e401c7d6256b934d176384b5c4802ae4add49c0e9021797005466da61fce2e0e4000f8e169dd4b6ab6dfe6cd52e77b4ee176a444c9a5333730d88e73af59a3dbdee9d5712ae714c4b5c48b4209e2f196901c15dad4f23f90eeb72f0bf05f7617b9115a036825386820abb882aae5d10684296e2d053b55ecc80603565d53c5ae03d6ac5c38fdb554bdb7c8428d3044bf9b62526e1ca9ff42211899e52d537c5ac68791b02be95281c87253fc05e216a20e7799bb32bb0c07ff0dcee684dff9e91b7fc58aaca69a97790c568f0a50a07e962ec1c40a10501093f41114112f798f564ae8aee295b879e1d9ae516fcf9b579e93b9e6c70442314d14e6a00152a323ee5fe6c7407b6ba1012e3bdb9b3e2ac45b3c948454e14c0694faec309c030e18bb96686ff44a5608dfd2b5710350b451507c661dae05cb165cc4b4a50c59e8670cbde51e4651d69adde0ce2ab81346fcef87cd57d61cbb73498e6d516a97684dd6e246d9c1e3e2e0697f9ae69f0e697d93290eb595be971e03ce167138d8c3c3164254c158c6dca8b2f2ca0a8de9e4d43b6580d9a781a0fe990e7cefac16549e51b22de2bd8a66345a8c1554a84f06af85e79996e044dadac09a039d130c1f56b6cef47492a9ee96b8a79f3bd7021982dd38bc8a831e32f5fa595b31757d709b9f41aec5a90a3bb3eb24cf7fa3f3bcdb03ec62ebb108607828c41ef49a68b39ff867b1db92c11031435c0abe00c457b9962e62f9b09711356760be25c8b29d52b33cffa26cef79d0491ff18798f9103a9b89ef7d96380224c18c1045b0b2fd7011125a929cc0772e69e07f27ff8a40c5ca062be8a8e07e6811c7f665649ca5cb46beef796995213de82f402474b428a005d2353762f4ab682168fa53afb5d5e1f6afaa3125dc127fbc52330e6a1e01d9a3c84275677ec62296980ede5189542295c60614322146f689f7436f080c5b6f807b0082344fb03f5fb6618e1581601810ae86e914213994c6178c91ff8244df6f4aba04d3a0b3ae78e0337d951e004aa82938110decbb778bfdcc26651c97922fc53ed3ec6d847b6484c5ef9538b10b83cbe0a5de246aafcf159d61e122607725a0d789cadb2718eea2f1342af21caf0ffc948a4458b651738bc5b12b318672319e8af3cffd4d090452f510e6d53d0f9b286874b42bc584590e46c92c6a011ba080bf81edf38b442768cecbbce897650e1bb813e79fc032e270a1a97f6dc30548aa64caa3c4ae2033928e7567868499782f6177faa5000a26b1fb0dba6b844474c20c9e3dd80e10d1f1366091889e51e5d0e336cee12de77c557db19dbbcc84754dcb59d76f1c7a6eede7223260d440a1b39f190e3d320fd68a31e08140a1778e05ba77f4034f3e7fe45e1ec18da378265a90625372bdcc08eb9146b28e169d6095c3fde97c37f487dec839dce53773a3e015e4d0b40d3d83e260d18c0c1434022e50fc963b0699c09599b3a12768f79ede4d745bff18fb095c7ca25ecc8d4c30f6b9960df7883a708b5751f264e1287f5db4b215454fc5bd4d5fc26a9fd69ea951d74866798ed2e91b2daf654803e8f959155a3cb5828dcdbfe446ecc8244471420a5cd70bcaa057e1fe0fa725453f4b5302035681f4d829defc4bbb1bd47eeff8ac218778c25221c3ad76f73397d01c0dc52b67c63bfc63e12c4b7b472cef3bcf1aef18eb78169f229666145bde761589d1cd803475a9a0f65db74bfe4b5cc9519b10dd06ffef35483bf03018da80f341bcaffa63966e633363fbfed979882699ea1a42a0d1ae62fc71cafbad5a0407f28fccf8e03e9834b2ea73209cf1545934f174fc9fb0002d58e01bf9b1c371d6f19db2b6f71c59ddd955bcf8a07e664af06c833c098ff004d5393aa16951d0b4e275acd9b8887031fa405c37e7ed9c6d647bb8cd8603f04ee134a18532bb9fc7b5a1ac2db055e41a6078498ac8fca3a25e81c7ef51cd4fdcf6e9e2867bfd9ae47707b3a7649d6560bb488805958bedf91fa82f1d1657f9116865dcf56cad2ef7b1df3eee8b7d00c8fa55f7057c1f3a9f0a58cfb90ee4678b9271343c16ad99fee703a5720daaba124aac6046707320075d5c23f487f89ad9864af8eba679542b687ac0afc2226529a8f67e06c265b45f10ce411bae136e3c982815df74077157f839a4d6286e3dfc8052542bd878c0abc26c1896b5da583248c7511674432190dc7957cc2c42f3a81b7a9bc4e36ebaeae523da4671c50af75f5008d4c5590fd746e650021aee723f675000945ebfaeb55f0f65268fba436efd3aa03b53eecc5a4301978c7ae0d520a8556e82a2acc16fd8fd4275e3ceb008ecbf7ee7a7cf043147e86a9264c040f39fae06ebe9782b3709c32ad9d1e7ee15caadb612d572892e69f4e0716dec764e1ad2ca74ec9ca10993d2401c060b5cda2e712d51359161345b415aa8485b2fdd549cd0c6b30ea55225c5e7a6e48766b567cc1af1f14396ef6b7e76361cdd8e6a00252bb262bfa5a708d8591acaa5fb63e1756e4cc6c4aafeadc2d82d5a3844b140c7da63224c54d357d6216cfdfeb5ef5c5e883058ea3e8201d2c80a941e43cc1ccaf22b8e11354de252ee7c34656d697fa686501da7b65a05e683f4587c37ec53f300dd897f2bc32ebfeccd59e8042613ebd0da105b92ccf0f66f98afab79bd3f00a9f4a5e183eafdd4409190d065bd7277b139ca0494de61ca8e4477fb993052e30e6f42649a8d0a0f692409ef25e256de72d4180c345ebcc438d2904f4f4499261958ee0228533ad7609a3ebc148269617c1a742588af13ae4f1b2b7a812a4217c305fe211320e0583bb3b00d5380b3c20d1cfdb93798bb869d55b5fd01a36fd4ae2f88cc827368f5a57bd9d1959fbf43cd7959c07d962c29781033eaf141b76e07341f202f32c389107e25ad4403aa44f79132f4ac24c2458133597b541698cc809f4459360a92a53890c660731c98ed17ff3f7af20aa90365bf3479c57a6e458f3c54d667ca1ad46a1ca1d28fa5aad4c33b56a9e633ea54cd2af652697b136c35cd467932bf8537a0439af279b56849b290d7e8b69da68f29bb24a76f01916f821480b5ba5e64baa238dc4709f37169561fc8132d04e9ba13774d71b83e9f28463fa5b00a4fd30453c12d0ec9c898480b54a6d6855e47165f112c0a8a64652bae151f02dbd5822df43d55e0ddae6e7596dd43eb50a7da0271e2a858e7d85a8fdaa3f3f35b0a476ca340ecadeeccb2f18b64ec6e4a2e1fe0cf689cedbafc2c65afa778bee266a29094894678fcb6cf2957040ec58ffbd07ff31c2591d12758cfc8c00bfa1346ef103308de09cf3c52dcca6f76e0debbef1cbac031d03a25e2993fce5c53a7eb8b93d72bf2040171e577e9539ab3ae33907f84777ce96ba461b5485b189ebe2978152fcd31b8bee0b91054c610d11c355196042deb5017c7f5d2e94bbad1abae3de3dc7a851a4d5eb22488fa78ef155a1b0cd1457d9b3e725010d407bbc12d383b0696e782a71b60844530af1fa91f48483ca9465cc4da6d419b5db675206c68cb97140afbe9e96369a1471e23273282f44a2d389a3a26677c70536da2d2555b23b2c99bf6ff7acf8bf29f79c06059519618906831b032fd8da0d1044758f994357d969877bdffebc8fe140ed64c20ec9688f7bf4062f90c564b5713b10cad419f96fe5edc123c9485104fce1f0d69f35bcd738f154affbcc8b89bc8ecaa0235659f55425cacd30e986bb0f5601b889a32a22e8092686744d7513eb1396b2181d172b8a2b6427232fde1d120012a2d3adbb9be3966429417717374a7cc4965aae58143e379fc76cb6ce09265c2ca4de59bc2a83b31b9e4fb4ec35b56f47b626f373caf9f21a63d85c9e10b02f313134c28814924e2b5c804af2dcd8b2ba8bfd05f865c870f0f2022e546378203e02e96e19d823cb5a039663f297552fa8b499b88da9ea054c1dc260102a48b00a9c2f129faddb8f5d3db62deab1eced214e5dc70b9e5105bfc144aa0eacc6057a86a570c51c1bcb8669ea1ca77605a40c180e146435876545c697751468e120b90f6793024f76898d8f137c67c46b21ef5ceada0ee3015065379f4dc913ae66ad99ad8ce46e9f0a3790b47c3848f1d63a588c10a3af6e4a8185dc51f5065fc1c2f6a43907249f1cb9bf684fd84df36fc860c12457c687589f58e0acf8c73533bbb0b93d6e2f04787b27f6a0347017d489d7c341f4922213c979d429ff739779bafd13bbaf60e20b5cc5d4188972babe404848b349e7de4992ae2810fb3b4195c95fb1fe5d2e74d1730cb3d211953bde6e047dd19b41b1f7980e3c5974e3056919ddbb1d7ed326cdcbbba34d27aaa5e37035fc8469cd39a48c792a8d5bef851efe4814a2d547fbf34184467d7a4cedeeb13c40d4593cabd20ac0c16bc2c5b47ec7afafcfdc7b2996ffef9c5180a0fc4ab94a45b2255fe340c8345eac07b1413ac6bf6410014199226810f1203a7c824900ae4c11846965e307f1c1760642df6a3b7d75d23015905cceb8f794670fb68084c6e3116b30bfe06658eb49fcf9d297581858d3db049752c68c3089f4b95b231e17a5a9dea8377102e42eb2fa042be570ea72221e1da58b12f3e2bfe24a85cb9ca3242be6031d166cc412348f6f837722e53c65a5a6df3287ad999e70c837e7a944c9be1f62db62624e743034dff6ecdb28da938cf43697f1d1bf6a06699b1db4d1ebc3d0cec163296e51db08b38ddce5c0bbaa9cfbb46738a694b923045a2080aba6123b37f2da1d3e962152bb4323798b0418e8641518e0d41c806f3284242380f15e00a53f32ff15d697090372166685588f47c11494cf53087f29dd6978c5fcb76475ef047525f0a7e161293fb6945edd054bf923913ce97bac0a9fd2ba9a9df5cad4d553e827021e7ca633a2f15575561f1b82384fc60c3738166e89048c28ca2bfe57856e1ba690dc1aacd9c0a709cab5be56b6ed00282499da824c0ebab67b2110b5614b4a135c9558b7582baabfa43f6c237adc8e23fa6e0b23ba4d2ceff8d4434fba7c2ba90a2eef0145b3270e7732794c94eff3ac9d23ad42d053153efbbb5605e9dcd5d47ac8cbf3168f48833c7652068191054174552f44e7b823c012ee626bf6850ebf78ef50feec9b6e9af64622e4bf2fd2050487c5877c8852528a5a0c96f7e006053a065fdcb9cd9e7c402a7dec0d6f1465736153648e86f636d7636e6cb1ca3359a5de3c01e0bb3d374521fd50683002c7a36d0118119ff888402e6fa7abd80b251e7b333ce8e4bb9406da97ddec6a867465eff1ffadc83d5428362820a2dbabd41e287dac4c74badbc4f249950a6eed94f9f173bd133a340ac4512af46aeba64766ecd405d83fefa8c12041042519bf4de218189e7b3f1c8e2c4ce47a0f41d4ba4c04e34ec5518b8292c3e8d0249e68c6e7b45a316ee621780686825f13338846956afa80bce6e3500a6241e4fddcfe6e36704991631a24900f574437b60a591b5c3881171d1aba4322a4488f08e04ef2f172d6629a84c7cd0c12041fa1c30c003c5a383633a2d049529a2750c5d8f9ae6367c20d5a9fd684e9b8da8516a9af166e386998b527eeb47dfca3f3c0bc1de62c5b67c4fc163d19db46cedeab42c0aab5de3e9ad91680e7a889ab65f36594723ba462eb2700f906f9206dd03ffc8c269c5858124653f5e4d96fbe89f5192c8561e3864c3604e98f0d2f7966f7ee3693c4b0ca06f47e07b1358f73a766762485408485d067c2d8b2e668e804977fe762d38dfac7dec8e96330f0e017f6139b248172636c0d6cf1569e6bd46b4f991e5192a5c0fb34036b07cf5f3b0604d04b5b02d8d09aed77e1920415d741b7abd011df7fb0a2328e5247238aa0f4c54720bc143dc8680fc966dad3dfdefd9e654106075a2365b592fee42559f472d4a6d5266cf61a1078b1d3c9ebd5970dda13d20b4b8dae5a94fba88584616e305fc4deb15b12681947112b34e1f48aed00c7aa709f91b30a6193b67056d8f822dee4d92c353370e4e487635bb36433762717ce20e635a5482fac12b53484b90938235f09e6b2ce7d27ebcc8e75e5039e5e3343837ac6ebb23528cd7b5ca351c02478373570f9f37fa13d40eb82aeacef75718edb1a0a50f42ba9f9a1772a2df11bd2b21d84eb342bb68f8504d47dc79cc44998241b8c5f4d3bcc8aacd79a3fd52c12ed0e0e50c838298210619159bc85cf7ce70f5bfbf0123cf055f7da60c2e40c8f54c050945c51cf1886a88be6e22a1ee25bc3c1a99b6b0dda53c509f98a221df183c2bd9ac041052a7c9b3b64e88ac6c99b07e2b69286776192329a7564b450e2fc8aa5fdf86d30db4376bf3c7e2d50824cb03bdca59001a04ab4b8f3163e48033ed24e83ab4eb9999ceb7c2554fc45396ba2ca00022af996377bf7bd8e9309c3e018e9a17e9a45f0453a3e18dc7d83a42080d1ac4e485241dea3f84e6e093b5bcb3997147e27c51c28b5416da2688464e0977bad6d42204465d7db4182c231d242cbaed7d5984b06d5b111acbf24dde17e7c3f349071b04672714bd4319049ccd7cff2908a5dbb9dcf92ac591a49eb8745fe2c58aded7a0e5afec10484db4b91fd6804552efa1feb0138e1e858d0ec2d7d8f2a286ba09e7ee51cdb6f5d51f3b828acbef44c8c41e27c664dda7276004b437c109ce64faad801848736f26a263dd140f397622730a966bb57164fdefcccf467cfc6e3c98c683b0a16e42cbf46344d0986c5e4cd79dc5d2f68773472fde9170946b318cd50c0c3e7c1ac5879184576682f2443968e7f2798f04fc02a49e926b9024a1f04f943038204e5ed4eb6a13fc6166ca53f1a8cc8af830c124e9ee0122497701411495569eed89280eac6c465688e14510430bc416c63ac9cfbb26dd6d09826d8063deea9a08437310c49ee10c5aec58863002f322b46dd14a7dfdf0973e897c62be20051dd673eab5b9a92e04693af41567b83d8a8f3abd2264292bfb75175c5f14cbd211909220f4628d928ccdb5615032bc44fd5748864ff477d88756884a9bfe5454b27ce60a810148672ba015a090e433acd68aafa92e15bcdaeb8b647a43642fe2bb9621b0228d32b07a52f3bdc8e82c5495fdaf01ec1c3d88800128d00b8e06822570f998e5963425aa43970eebbfe90cd6d85b39962ad12812d48e023a936280c3c77169670fb4d7bf15e04fee65c4e6641257e32b560993251bb644da5e7b6c0d708f96869664494fa9c541cb84dff0cde911fbbaee28a801ab2ed008b55f5da9aa004e55bfd5420134437b859c5e3a1d74e838d88a1663410ce121a3dfd108a8a91d9d995293c21901e9bb10531632092c34fb5f5c035f46937a60050dae2e8c04b74e91aaf4743017300c3b3c5c6c93d7329b9a17492263d0d68f607d6ea5f8e3b7cab4c4806a0f6ac7f21771d36cf2ed6ebc09b2d8591be5834a1b9b5f69355475057eb95f3136e2102c0b926b5ccd93d8aa8a73b5e37ee5bf982e09c562a5facb4b494106360be19f4052769b28f3e0778ee4ae75580fc4d99e2a5ff3388def9ccefd5ce6a4eaf6d581dea480e8f625b0a66d9c5a7abc8fa1c1c0363e21d65f65795654d74d465dd56351aa6a9716e5219ecd4715738b4f183af92873db7f85ef0c7415697ca9d588bc1815f92e69111f248e4d6f6db65f5b0499e7c5f9212665bd5b5f8c5d9119f6491f75428a09d795743c5eca0abe603c4d99cf6a84b19d07d9c3adc1f4c95d9c531cfd02c23deb78a6ac1e6091077ca879e26a5b3032d0f2ed5375d08456903bbb43aa603fbd366204591ffa90aebc019466ddd91a676f5c36541a336746c532919c6b9142e6f68aca3e720eb5110ac4f671b0b260878d0ae708dff05767a3e2d852334e5fc47c578aa5a584d2df17dd96665bafcf5a2f53ecf9af431ec8e0a105d5eef888954c117193130661515ba798014809bbe9259c8c717e3892bc5b4b6f5644f66da0197e67a6bd01ff37d84bc375d7120a501c47cd34e02ed4f9d4c892db43a469c8e52c8fde10d34b130d24196d67c11546f81e7b15823c9df17eecad9cfca138f7294e16ada8f5a641b40d5761124846a4bcd3a4fcf9262bdb3014ae900ec2c2dd91946908aacd8cb86027bc17a139fc1d572664779b49a172cb937dc8c32f03dda358576b33085503a629109101f073315502672a94ffa0b6be7443c23be6509a0ddeb5029eb8f03668c23976394b53d5927c42a095fd662a12ad88d349f67a5380f2f595b9b845905069615e6bfb34fb22e9f4442faa6e63db9694c0269958594f1a9ae1efd2ba1ebe44f42854ee791d8573efcc75572e3e5ded12a4bbc0e1dda0edc195151d1483650f0f3e67678ec3797d12251b7662c3a2307596cc93f3bd4d225345a4419b1d5cd79d5245bbc358b2978bc5824fbb55440ef25a76f6ffa4c2ae384c0cf64520b26c4e265b540c26bfc451fa8ac522929ea5b2b6476648b8ede906a94e247e3fd1076184953bbaa740c7ce48a985735e8f68873c67ce36f25e2098d148ea863599b11362da6805f6957b498d1c1d41ec98d4832da69c4e74916775785f31e0b598c0163eb784aa2d4129073bed6633c7c3672e16e60e7bf6ffb953950b9fc7a9c978e139fa6eed728679c2ed3c8f8661e6d89b74a71dfffb201f07b0a03a95a2d77bd84f03b97ae9b1e56dfe6b134a12b9f5e1607df86d90f528c01142c30b96504250130a10129b29fadf949402ad7bb1602ef429afdda8075e4b6a5e99d3b38a348f74f69cb4f7d9a2077cc902fbfb52099353e1015ab01a0b8a0425047b66045b4c34aa626aee8b1f5a971756595b204c770e7713bf145dcadf1a56b106e33943da100856a786b359b0a117885c761309fdda4a791f8e4152001e1d99cbc72a068a30e68f185c035b080a744569177e9974b8928f9826656253e768de2e93d9370e3ab0d8b7b2e031a8c9f4ab2c0b9d64f318c68e7c4753483e55b87e2aa9913b4419625388a48cd8c1315a10d5588894615032051a792baf39072d62721c5b35c373ef780e2a90b6d0ab2901ce9d51d565e60a0083f0896f6e438962f79639aa75160acbbb412c854d72a24e956c8950e38dfd53d9643e50862132bc1228d63a03152b0b71a54ed54c6f2aa867ceb1841854939f3b743b55fbed1f66ffe1773a28f0ff95552de25487098d6bab8eb08ca58981b855f1bec1b57940ad423e7d00c779f998fe76a0d2ef8934e305252a6c07052016640c5e26a8d5a1cab456ce34e612c3a352ec94dd5787d1d7301ae6cf36228c304edbc21c33f28f6787509f859f7f388f01b77f256092ec750c68be1561989f04ef3167e715903fb9cb24f8673fb80db0d91f361c2d7dd55e5f850efece97353e400e09f17d5d47c4cf77d82b5957ec44eb130013549716567bd8bb04e5f88b71e9b75fa0a780f9e35f5028f96bf3d720af826c3d5b9602c8026610534ac001843612735184e9aebde1fab3ef537bb677db0a0d2fc65896ec16b186cfb808115235eeeeb53d1c22ba2786baf6f47937b033e2a908525670e05f3ab903b1733fd475176fea3e6018fddde083d252845a0149de48fca3ff4e059342f9fd9da6d30961b488f585c1b6b84a1d3e14c75c0d8e218367b56322cd3b1e2ab241ef2018ea4a18b4f7a502e267268d2c46cdc7a5b30d439f0eb61d83a5d4358d9700bd68f875704122dd7f084cbbf96b01f7cd547ccee408650df19f908e3ccdc232cc00fed5ce49e7c48e68ac61ee61ed9672e9b1824f631142e453ab70889ef40580a567402c801160dc8a9025d8b3fb8b74052c1ce5c686604b3e99b34eaee4a08d3f0f2839c42dca956824cf4a61a40cab0b1044f23db5e8945802e15468a73ac9a477b0b45d8705be450adfa057d4c915396786bd16411f3aaa8d79d0c577f24bcb101f0d15c79cba226b055bf6c0bb5f7326cfdc6d00379ed5bc8da90e9ebc598bb4edeffb5afe339852ced7b502912d51698368edad99d9663c4b81ce2b362b454668706af8ed6c0352e8b3d3d4bcf42c98a224c0ba6b37b4e1b1146c0dafc40b8496895d7442d804a426e36b541be7b89fc217da78d739eeaafc7964e9b03184dd961e406f435e47fd2a964edafda413d7ea3307e1e5b041f31f63a7afcae1d746225b72a1c5ea16a39898fe5f0120bac910d4d5f6feb340e01aa31f7f0b220d1bc6e81acfa8cee563d6fa9ce5b6a9b4d8e8a1622079bf879c61a61193bc756f0866b92638c576d638918bb96a50ee0ffdc319b0819204c989b35765a772f4f1bfd2096cd804daea1257827b47a9d5fe27131742f7a27e860e7d8a2511d8a75169f0f49f75b1024f0da4086113281354b23cd526c45b385d50fb4505390de7b9480f442f428f15ef31c50ede6570d278b4066573db15a4d1e93982901a5c13527ed4a8f30b0f188e9456ebd477280042a2e7d325a5ac634d4dc4010bb65a09c65cd66f8635db15b39171889b715cdc487502d2bddf25c10e84aee27258a42a2ea25b1ffad8f3c2613acb6aff30eeb15e4363442c60dd407af55b3a862d854cc72fdffb5be0e0e26225225e08ecb61caf2c7c11483b5daac6691ce837bc2d9aff6a8673794978a5e54fbdee6262767644de1dbbeefa8eeb46961bca29d802145e316e72d6f11847a28573f2a38141393b85080940c88c4208f8706c99399f174f9d00d2fe65b6c0e1bee99c38c462070368244ec0e6ce7a2a88eb8649861cd813214439b70f67cb76e291352b9d3de11871e7cf88d7baf84d2a909f57795f8246e8015aeb2c3de7de3904500d0ac8bba1251747d2340ee8aeed93fce752f036960ac823f040e94a86388d4e04380843cfc25e822ba1a10daed22ad21951cee160e48f03b15145775765f30ec9707d96cd5b58b22b315877dd019dec1bf84141d021838405bff27b3567be4389e591f44e08a2cf999b49456fab85f0bf223e1fb9613f0d9c8d29048f9ed0649937fe129e5d8a16a4d573c1354a81e3f98ffd343e54814604c82c74bbccaa2c86086069e0ee0ce1270e50d8d8f0f707bb117e19692f056c796686920015c662e974f72b1d1d9a2c4019764fbdf17a738e9513699573446feaa18efee9fd01f5a667d443992e3671c37d0a40b2c0921db61a1f522ba953b0f351d72343980405b4518442ab19da137abbd0f4c177f63f8178f6cb57320fb23f2cf50381d549f8f3b87b6901b6a859ec4ff94c9305385044f93b879cb53634e8095f1fe95ffc3690da3049228cea82a84e92aab303bf5b23489d0961b71782392fa148f9ee5a826277a9a78a818f2719dbf04337209d88dfb0252b0a0fb632e029f777fe2bfa250b191154882ac4adb10e55d8934864233b74101ba3a100c90ee45d0406bc1882ec1ddc52de8db0637543bde987a4dd5ccdc98c73e3dccbf2f7da5acc3aae697dce92b798617309d92d9f42363ca219cab1f4478b64c3cbc7e6edbd3ee0f1a1715c7c3f39c56e89070e5f8500a1d005ec5328d31974fb026eae27a38af15962c352f8e2219a68d98f3e0cb20379d4e82decfdc5166b9ef2c688ee17d120614483f3d1ca5aacf6d7640f3829da4842f94a50e2ae8678103f36cc8b43156c31863ef79cbcb8aa6b44cebe331593c6a5bc3d8861b327201b4f6bec330edf32fdc78ffeda7f724a45887362fbae273f159a5e973565a078bba1ea499b3859fb5d74cbcb57cf6f0e646244586c0ad4351a4eb8126ad308919716a3c85bb3efa1f5cb5f8fb7c2a32f783047c4fad35d3103ba2bc4bd825bd129c44e60aba8f68021c7058027ed4a21d59f65911d45b0e4292a396a34d664aaee2de6d4030ffadedb7a58831d7d4f6882c036cd9acb48a122c7ed8a62b8a450679093cd25f168260f7620cb1fec3df8123cae2515354367561c7fa249f297591bae1b524473ef4190284fe05fcb50c6829b25d7ec8cd4e12bddc925d8283fa61482f8fe1ae415f26def411902f46c39334f25aaba57f7012235bf2bdbde6307066ed46a8025292064fa339bb3922c9474bd22ec9b68c00cbf46eca4d1bf17f147a9e756b7bd67a75f73ba106c9915d51920c937323476db7b10b526a52ad5b87d076e7dbeb5355a3abc69eebdfc795ec62eb33a87d9c2e7234b1f91a70ec2518f9a90b8d6abc373722f77b504793227bbe96cd44158a96183b9f7669ecd8f9c843b8f2e8f526661a27b6f511f09089b015570efa968276dd83c511dbe9d3a6d8885011487fe9d20a2bd37eb2dde5ab4f75a8135302c1a8828e602434455e2495dbc9cb5268073bcc7de9dd80349a52506d7de13915ccf833b8e78c90ed813e7a8a43a83a880eb34a7607509031ec5e0567baffa0de63930e99052600303a532795aac7cb727ef69b049328f6487573b22470958aa1e90f5a69736b06fc98c36a78f9686b084631e6808ebfc6ecd2a46db4ce6331ebf3f67322bb17521fb43b8db37613db34fa06fd821c69547eb8af78630665a992b06d490fd735241dd67acac9bd21e2114d485274aa9f4741e4ea7a6da0b879dfba2d27a783756b368d128cfcea3ba8856382adf4b430b1e9450f073cec4c1e1378e2d2909c62e462a54eb67e2f990944725d6e0b42aea390702ba327967f7896befbe7314042861769a938670546e99cd4e97bb0998fe909266abb49dff41b599d8230e7a9020556b2e531a3d0964f5d565e8a2e9f54f821ba79bcc8fea782a6f514987ea3f046dee34061d88ac2b2d7957e0893a03ac69b53a9c32af5fc2f507498f87836a13134b96f71ac4496b4e9a15c3242d9de8e1d4a285d10dc6e5abc6ae78b356d3f70caa514d0bb94ccfd4eb3774835a1c4093767ba990f6c32fa807f86aac3449571eb04609e12def1e5bfe2b2783f1e8a3e53c64ea5e7e8494e023fd141b552332a859439d8c58035e1c6a9754d7a9fee142839fdee0618262f5a86ff0281d8fbeb971475a37fa582f97a77cf8b8a75f89feb7447f1e97b30b3523d5b4de1266881c083584b52e0e801042534f04caba41761bf0d8253640fff2260a86bb0e54f06105c35441185d176023340e0d3a96201efc8ab92472075975806a8563207fcc72eb48f6faf58a332c41f5a5ee3efcd26e09981f9a163fc6e1276467301fd7fcdf0f1c327d5bea4e575c0ca7cddeeaa10513288348d3c306f060f6385d357c9707b5fa28615953b52908c2e3c801e6117654eb25d52683a45d416f441cb4dadf4551f0040c6ac6be1322f6df4880b4b63649c48d67009be8f49a4ca72f3371b088e90de3e04e324729b8f3d39b7eb6e0f3c5da94c2e298dd748cb6b5d905bd127e5bc426ef48759f469976521dcb583a3cf66d5c83352dd432e1a7809663e0385a54d38a2509690ff2781d9ec4b050095419e1364255650dc011064ae9ce05fecafcd0c62cbb2dc20a95bfa244de16d25d63e2eb42d08c642c152ef23b991a4e9ff50370770091bbdd43281a233b4da1ae6a9b30c48d44fda2c8c8e77e7f9fcab2550b5a03f3c7c3ab8a81dc22919e8045e67cce9046b0b5ca2e410bfbdd3d6b7527e4bc2c64cb0fc8749dbb95b320b6153040110aab813501350a1a77ac824bc967dc0b5f38858e5fb41b868451b243d17762bb215111a919a1a2a0922c5adbef91ace12a9c8b1baf586b0a05479522df4927cd3555d4649300ab1fd4b1ae0548137a8cbc6407fc09f8d6b13266bb64ce735ecf171e24fcaaf96862766863541bd7ac898dc8929884cdb100da1daee5a98d6fdb1b3d3e24eaf48c5aa872ab80506384e9e2f4bb578bdc4c2bae56b6dc02a07402c83e0e46e8b70322f0ea466c5cf152cb0b13b7a1f4cc53bb40d67f62c9d00da840b1068cbe8ebad386b64e43ab4d74195ca1da80d818356ac6a52fa5838958a2b0170931ca68e0385cb44223cffb2d7d20f4d93cccc8a6472b1cbd451a103b0fabaa6110d1a1f1d75f704847e1c9c74e21e3f3120ee90259b2c193c1e6206905c3cf91cba1c9477bdaedfceecdb4fc8ca66eac824bbfaa1170e8c1f0675df623d17485a3bc843d10a9ab07e892c351ea15c3415b3b0cb15203fc9e13fe43fb3ecc9c478cf0db7ea9f31191e2adc2c75f1ad42c17ce41478203a5260ae0e10db6874322ea99d175e4d01d8173e682c6e85c05401a041bcfda8f16a42c16ad908861b274983beb5fc5fb47c577318817a05a946c332c2a554ccea9a80cd75d6933d7d68a0207a8306feef141cdf05963a98256dc778f56b4a31011e934364650023f658cda8d82b90ab3837d22b4b22cb06a21b1b913246fddaa63d9ff375374c5fcdb5340902c8f995c0cddb70a803500a6f432b511998ac089ad1b76b77ed0878e1746a7981649e91e8ff11b47f300c1f223505037c162d54e1b29dc2ac9fede693b39e1a341c5132b7c298a5e83c24e2bf82ef55ccf9f82d36a9891b561fd0727a46d79a0cb626eca76944c762a6e9abf846bef3862644c33967e7673bf6522b415fc0519f3b3e2feb31a130718020ed0056df9be6827161a9c4babdba49db661d5b345c38f26cc5d19a1a597851c03200a70188bdc3e52693f55c224fb853f6033afcee2a48575b6003f7f06710c98a22899c30b8fc803fcfc559833f857ee8acdcfaccb3bfbabab2930971dbbceb511d4cc97a2fdcd1aa47af4b3c83623d62c2346f0af6bafcd907a6036973922484829e5a93ef30c3996c17de88a29dcce141470fbbbd7baf898e2c3df74a2c75ad763dba68683fe12cff74d6b04305cc2a1e17348ffad1900ff304f80b4230164129ab43435b836b3e3ef5cd886b346ac8c13edbdd7573e3a5ac75cdeb1baf6af1a660b88da98e9d764b5bc449285845e7faf4b3b4b25b7bca3601c7f8e7ec503524cf72bd683854d73111851665b003fb8d4063d2f9c084fa00c6538eb6d3cc03abcc2d452099d4a71e12f3169aa2a0c5e2441669719045fb0a3240ae46201053dd5ce4e99d3de448c44eb340e2b2954d9fce5fe03d9835f17a78c925ef6f512af4b0cdaa6d151dd2ba96a85e9c42c41a23a0952106f9c62b245a6e607688130456b31ab0ae4791982132f4e4cbc1af87a7a2420e70aa35817a2ba6e548c229001be9325bef037e981dc099f5c158bdf71e8b005de48ff9063c0bff6762a1836055a8bf45bcfcb3ad2d6ecbdd9cd34fd842ca501c83e3c951d867b2d0586aac152ef5ffd681effa4dbc1bf0fa5c103cc40d49eb62a31aab1fa6050252964ad0f590b5b5346c96e097a624d81c8d49b777291f15d83007a60341e56e94d589fbe350f0fb510f28766a5e08aed73ef21e95a0a1bd789e726d28bb17d36f1d88a14f05eccbdc6bf59150997d8182a0f7fd417e1f332ce506a5eafffefa17c737ccb4899b090e95082460da5c27ff51ce377816f5adbe99978e25e78375cf9a9f0c63577b0ad2379ee540c45142f759596ca70a29eaa502eb2e43fee3850c16e6dbc6c8e88a4cf9f25fedbd1bb1f8feb9027f09187a5e4e18570bec06aa9e6ecb764c0e59801782aa013b493c971ef9b367323838bd89da05363d4e88b74b43ffe86ed4481aa69d0f6251d80e72a87b72b6c24cc935c95b1f7c656dfcefd04f9a0565586f2ed120e054a6d292515415b598c0ec3597aad3d2acf25fa87de4fea26e766098079bf855c71e700f046e08052d76d49cc066fc159402a7cf1da8adc92d66c1f5bd9ecb10baa5f61288f03bfc616c8bf2c5b931c5b55e01b6d88c1e0b43f432c8548194f7c6d85c6d21f454867e08e0cc1245d525aff4a1e54b52238c6c18321bae4ae044102fe606d442ba9b4f6a0b0e08e8682faf0a0b9b046cda5afc6c23068925f6b8da388bab32833a44a3a6bbc8f5e0e089ed646af11e5e26fde2b18f0f11b6ab9a71a9eab4f8a711900e65f4d0715891eb2b4aad87e67a937107e49d2311da90828916da551ee7b75a794b22395c7bfd6ad0a7bceb96148ad60fecbb66d317955fc29fa667b26cde8c24a873ab7d6e4dd3dce52410be8d77e90af0dd60b6ef97b8b00120fe02e951e3a466660b8026cb01915ca52f642201a82b824a46692945aa76e565a3347eac1369b7b22045fb703b933fe674adc52f081a1387cbad348b476deb8f5f1659fc441e80cfe1a51f82e6ae075d4fe2a648f6cf1e8c5782aca19252d1bce91cc132eba297c57dba6085cde91baaaa274e7e7f424a28851ace47fc4280c9798a1450e5bcb8b4719db3bfb085d2cbb4740b24f41e1f013353f938ed5a1929fa9a78c880b7c631702237e1315b7569b97d7c8e4bfe01f31056f5ea58c2e8f046e43f4d7fdcdd373ec6440da264bd2e6e4c0b6861c987a655688743012953108e2df20ba266504f704fea3a397ccd0ad1975f69d682427383f64b20ea3df5cf20f30d50efbd30b255321ddb61486f3c4752849352f5ea94eb8d39c640ac2a0f0c8bb23e14aaa71cd03823d9b0490fe2ac4f7239de2c67b3fd772886b5cec584f73b61d7e4df1e1d4edb0dc0c71c2a227feb8fe1d6505d6042ac0dbeb37e86a031e02e362996634aaf16fcf1257a9c990a10b33f9af1e9d3c5dc1b4698b9a77cf0238a37129379e19e0dd5f707c5acbfd7bfc5b22e6fee6855f4cd11220e6f44074f0297c2ab53f423db3fb2b20f1a14d8a88864c67dc4f7a9c1c5b67dde6ae0c77cb8f5ab1b0d975aded94f8499ba97fd6ae7810d41be9a0509b3fba5fa8c9162a3cf0bb02ffc900868e19906b7a4387b80faa8b755beff8bbdd59ccfdc0c586e006eb5130075b0c9f02df4c9b9fafb6e0ed6ac8d896311183c2e626ed5c7a4c3970382011cc6e025e406cab13980f2ba86956019c81a85f028f2b46d2af6b6faa7c89ed4de83e1c9a13420a72cdc870c42f08cf9b86a79c340fda2571ef776476cda4fe9780b3d4833df0161ac426424591b32241abc55389bf4faa22199e56b74193e07ce29ecd32d699f9338404d984fa0f33c56562576c748efdb08c562ba582f62d6861d556f70bfd28b5df3d6d6b931f52d05a0ac747620cb33f8879c2d1a3b5beca363523ef74382bc185ca17c8ea26ebeb9b7a929ef78373b7269b029ea7792d6a542953f4a517e024d3f2fb29c47fca3b881f254e2bf7afd117e6e5e71cfbe1097e6cee979cca5f25ae5d91b5535424946f9077216d1b97c30b7f1751f25338bebc339eaf4f4e3fd187198c49d0f52a4ba4677776a99cde9812825d72c54e4ad3815f8514bb3a5d68714f7e329f69c601c8dbbf16a642c503c4a652a54900fab3761a952b47b91afa7980ea1699944844aeac210ec73bba7a6998fecdf9d9c8ee45e798ba39968d1181fc229a4dbfacdd47e46314b635fb4e4772e0e5d8c1c8c9aeb79f235490ca0f6e8441132aeae5598950fabab8a7b3f126013c1e9319d68de70b1880e49d0f5f842f13d0b7e6b41880d0f0025af664cb205192659ab25659658cdadab911795c1fba95149000284d48fde6c975731378095b9f81ccd5903862cd2dc65d8a5f826deee5b3a3205ae45c34cb7dbdc570cc1612a58be3c2fb90e2be1ae4108508a21ad79b3b2b3481bbf0a9fdcf96f6cfdf74757c61c6aa13365d935f268301f4c6a0500464c80885ec1e50bcc58d0b43a4e68622c77c1fec260cd9e5f4249bf1c4f164c8a7aabaad157c84ce3c09d902c82db3921e3cb5ad33a4c38aed05e6215981c7f7cf187f1145b4dbca5aa1d06c0f6d6d81521fa01a8283cd83f11b2294c2c46b01d88b2e3c93066f0f38957d50963fdd190846841a6708510f2c6700a057078f443de6c79cc4517405cb5f0e92fe1d049eb2b3c277301edbaf934bbb36a6237a03f6f54fd38deb14319ff7727dbefbd8636d746fd860d023997f9d0966c093b5ca09ad467e30fdf16ab65a716bbd2e5c9c04f0873ee5b14618f37586664e4ae5531e13d4fdfc6f923d12547fcfd4b5ca1fc6a80efe555955befc9b829b0f099b19d2761145118d581bf8a36311fb4325088c69a52b0407cdec6ca3a8c69fbd1995ca925b5c55ee65a6258a2beb2e245aeb8fa78ae146df2b9aef31b787d95f31054d59942714905c03d91158ef742a65890d8513397c02cdb94a150fe8dc227dd98a56903225f0ca650d14bfdd8a791bf3eeca3d6ffbbf7f41ff954e192cc0c9d46ff74a389dd9f64b1f412b0aa238461bb2062525f1af4cfdd499417875a474a6086f1bd4c59304d2bfecf660e45f3f70a330b12ea1e982ac5eee914ae11314e2f9177829d9a0ec70c4e9f19fa2276fabca5c6b53a9cfd0370161dd4a669711903ad6cad66c00489e29eecd9a0a0db085121a2f49f7ac58dda65a4eff7ba35cd63250ea39aa1671ab22e689aeaeb51753768e0065765bb44e68bfd27f4bd49d8a6f890c2ffe1e5ab540f44afe2272a5074b0606753489c31bd61d804fc9a548005acf08d504e7f5769e4845f1a076b19c27c69741ba00ba3a2389c4d0b890586db2fff4a3d8153196a00aa7b18b966887732a40c2875d968ab1c44da5f843179c6dd3aae84c15f703289bb09fc3422e0edd6b717a98191a92c9f8c4af01bb768a696a4a292d32a9b069303f3fddfd8613758a887417fde45ad30dc702bf426727aee81e5e7bb26c342a53c2c2e82fc5bc1e7df49746a00eb4b85a09ec4d0e0f65c185482e1920c8dfbce80614388e3bb85648870586c3214fc558cc5e92b32709b17955bc7a4c2f52947d521f08ee1cfe2e5d472cec807a241af2fcdd782c071f091cf3b614f74939d917c05c1cbd29a2dd55742bf02022531cd034cf8ad6bbaeaaefdea6eaccc90ae1d80d5fffabda039c94b24d29d84db99bd8a2ebd4f980d0fe340784b650f980d0aa68c16646f95e0bd00306ce37ed0de122139c2da58902a210da0057197f64d3410021cdf3b8ce15ddd6cabe7e2570210975a95702eae9dc67203f066ad18f5fdfaf90fcd59667d48eb966faeea0323c30b2edfb35c6dd041332c984263834335129e6f7a2215945a3cda50a9ba44a247a02d0019f351a5709a1e1fd6d80a0a5727947f4a19ddae786b8a560304f67efd49f66c28b21bf1cb86ebc8d67bff671bc4f1f53766772f70c564425d43794445a24983b1f4acbd2dde616ae70529b4d51821c681540f90e307b07a2bf8cc951c4b2cc24b0445e4622075f9b2aec58faa486b479345dca099b3958221dc5557013ff1dce809d2aba8dc1633329c336a204134c68e2269814aedb33cd14a4827be4e479114751f6259ca66e6d75ae1e32f3b0af81424d36020e812b4d5d861a7f48b3408b3929f11a70518764868af03092cce5464c8c5cdcb75047e7624a284c2d453d4af524c758f49c3ff4534ba4a1d9b37c944d56cc5495dfa9969f8397f045e02870133c0d2d6bc57a38021e0aff68102a062679d6924ac40ca42170a00ea7df71ddbe3342c668d7cc4a4e29550e56fed0ee6b29169dec8b2158fbf28da5a995c1bbe0927370d969486e5688c66fb99ea1c4dfdf1aa4fa253c9aaefe5a5ed8a347a49598bec28f340c64b6f6d0f48d54c2160f153d5be0f3aa3d1626656a8460cef952c0fff05213a2f65b52f65df827184143cda51e5e166b4455576ed28621d7f48d2dffaa0859f36dea1c075392c55f1d512ab86679ee4e8738aaf0512c9d7b338049fb3df8981e7725bb36e9027e0450421c67ece6ca42fb67618af6cab1f4b1b8e6ce43764b533033a1d41157ddf201befaa0aa9582bb0a0a655c73c9fa8994070bd81de90f42fc49b230102a4d41127ac959854ab96eedb4c1f9f5bd14aeba0cc2866da8acb571b8cb8947b32afd2208d8a392542541cf830e6944cd5ffc13d1eb3dee447674376f911cf649f4e700930410b6a8f558948cce341f0f46e6d944e6de310ef9fe70e5933b2bcaa6eacb763d39ce6c762de7e304e20474ac4f259a72f60642814b494069eaa0815812a944e061dd4b97d62d8789347d76fa92a11f09e75023a86a37e434a5a2999b1d23ae3950c6b9fb0af1ccefca87a67022da9b3f3934297109fbd8a562924798d98ea481af1abb566a1e30ac9cb35be5d506ec4fc7e35f1d54e978c96bc050557305f8da5c9d623253f87ad9b6db8f8ea8f8f1eea36ec9802ede09695f2be237a4b92ec3a15997a13db35dc8b2b45c472d2cea7349e85a75195e27c11cd5cf7e48cf1cfa6556372bd52725a4f7bf1caee880850e98f5d50d010be1efde3e044e5c81ded68d4d10e08a097fb02a98287e767a2b1664bfb4e41ca17e306a8865a1402029654052c21e60477a89a1591bf515e333f57c329414bcdd957e632273ff038ad7710accfca934a8d1fac6494f4e956b0fc0c8cd2f1bae8d2344e93da5c49fddf450b608bf815ee0b1fcfedbbbb2160a27e4bea5d0de3bd8c89277b4fadc55398cba0cae1873f7833b7404fce1b6697d5e5673b7a2cb65a82d08f3abf0a43c6de1d966491d7095a4d2a973b7524d8859010a569872aaa69329b6636d3e74aaa59e8ce6c80f7217e2a8cc5c8e4880b8170e0bbda0ebc49e5cc5486192091cb5eb3060edb692a36ab40e7a893abe7b1e7298610e148aebbb11039626ba70b8fc07d0945e7ed539e070570c94e0324afea86961afc2e88b2cfbdcd9c052dd1b4b4e22c7031c7779b2487dd42a5d706501bae549a73be071e74049565dbe675070138ca3aebce1d2c642e07f09f90a11f595a201ad616ecc068e325b781640786f65001fda9a7e86d5c4377d77fa5e8600d82971af6a6c77697d78895bf63d3b816736cc857155200ff4ca5a7db7ef3130fcfda721a3cf1a027f1dc86786978c742f0c11586440b74bef8d6bf55b67038bcf64a5f1680242a37910202611080714d8d6bede9952d8269275df0e1601fbb6f308df70df5d68aed622ff8b7013dd9f73623ab39cf308f54eddc4bad899ced6c6295d7959135355e71de8e297e85c1a88514cdf86217142f901e42820efdb6f0ae91d76c55dfe40b30e55c7649044130a851de34d49639d7a746ee8fd1e951d3e1e4d0d25ad4b21636498856c8b570c824358d369e8705aa4f552339c76ef2f71f6557a2f20beca7d3cc79436648496b86b7153cb907bf52c1cd61380f384855e2d8543bdd19194dea4747b33869d53ede4dd634b8c07232d71096980dde51c636b02425db104efff02408109167fd130ea47b50e2f569f842e02fd6bccbdb9fd700677d6c698e61f515c73ef8316b430394bd121a582892ed62eb6fab7df88c40bd323e7383298899011f9875a52fd631f4993488f6bf000eec1bd085ede133f6c362fb2bbd3a8cba0d57bc8a4d53b828dbc25a95e4b644f50964e192382c9f4f4c9720df5175fcacb7045ceecc7c1464c00fe4df84da0a570203f06a37f13823ba0368ce1e78109c029f4ef32129840553c722da4d81cba8aa3188301611a06db42cb476772313ee75ba30f21e543632b8b3d0d60b68ba197fc1b0509d013187614eb875197ff1aeb877a662185b3ee72888e35559d60c3b509c806a1104ed9b5aff35d012faf41b3557e87879b5b07f1b22b14abcb2dd7c84cf71f82d115a203a6662ee43339e8f5bda26ea94dd88b1af76e01c3dbc661e2b75ce37237d8136e7c85354c13c8e67350b9f816e3db1bc5c1a25fe893022ab1a2eaac28530abb597a5a51044b0163c820498a34fc64057d79f26bcdecef04fd65923288ff680f86f9884c72851cdd58ef05c76a1f59d86d66b0ae9fbca985dba8db8c5dd50548aadb8ac01f69491096f082ec8027b46a9a34278bdc6dadbfb0fab66f1c8ada6ad42e07df5f65cb90eb9491c55699102ddb5e13a8067f12ed7f3038e3070e77bea5aedd67f3ae1a6d8b591a503a6cbf99f69c01b7efcc55ffc6b8035a30667aafeb5bc5374f4a1b84cfafd5486df7a32d949ed9c95b299df15ff781595e058c4a43b831aaf9e0f44d0fdbef68423380aaf4cae95a414325a4bfb8d5bb35e15bb0d9eb4c3d717407c0c6038a354f5beb1f223b3928e31c84fcba839af2073424270b4989710c704a39b0d976083ee98d4fc5547d980974f5ed8af655b25135c54d18487900ff00559e51c3d5191035a4c3adbc1cfb0c9c9d1157f5fb2ef59dd2f3ffe9aba55cce7e6a876a6db3ca436632dcb3fb5fdef2fc542a17d5142b4b59903d3a676881f2f5c671c3c4fb56e19ec75960a4312fb69cdb45aba20c4a012eb8dcd0dd8be4e6c80ce8deb89aea4d27cda6a42e09e2e963c247e8b49f09392a16fb8b1c1476f1312dcdc323289c513a9c0cc73c9fbd2c550111de1ba2ba4d283770c5ba51e3c9a5fed004d6858f75534b6677a728408e19aef2f89f30f87d5681c3c6908cfd7730ab4ae7473e420ff3de001751d80adc3845ab9e964f4db35aad494f69085e8adb90649660264bb9548b06ce77813333d97640d8a3192ac41ef5d74353b607a0811c61cd73df8d33c688b6c56165362d5f734cbbd3ffbc0203c982c82aa517fc39b12abe0b3042796bd4440fae3de1cc4bca04c96831de1796023f2a2db4e9eae14a5e748a4c98f14ef4ca3d9956cac06ceb6dd095e9645a05a52293ca80cf45f7e539d72262b311c55fa9ca0b351c4486730453459ff31c9136fdd5c024d7bb2ce67ea4cbf60c8b55ee8b55e48986c2f8c29c5ce32fba92574abc140742ca5be49a8745ed50ab6185a76728741423327ac2401b3bab3a76748df2e03384dff882670df3740d9e8b13f0b5fd02e30cda120be123538a8660759147a162bb76ccb490159a35fa0cc577ab779bfdd1fced975e707eb277fe969bf5b13d521d09778d3b940ba0c8218b95f969813aa7db204d623072e95d68109636c5dfe84712a0d2b110f46d149d38a92cbac5454359eacc35330397ca00446053f0d283f579c072b755fefecad0e28ab34ec13450066a60c63ca7685dc1ef53b6d870d4b18462b6469afc2b07dea7e79948ef69cd58f3de24b9971396f7fe01e73c52aed60a5b4cec86c44056b484b420c12387e346e105842f34f45bc19e5590143017ddfc4d1af2701cff79247be5e83c5688e730652163ca318e4bf4dd494e9dc376687185ff45d9dd2f069421575d854720d5056abe3473602db414527db35e7a99825a6ca4a9b517540d58493c3642385851e47265e175b596e81c8245f278e02eb4195e45520c0cc7f21278c30cb763bc38e69102876c87b930c904cf9b19d42690bbf01dbfc4bde6d5c654513a69c1c21f6ba6de8507bb2c3b773c479d033aac19046322fcf52019c3825f645e76ef805dd06f0987b2a042ba84c88d283220a30dc54e9b73f24544e875cef5b5258afc5418b43bb83dbb74b39a17db02c78d86bc4dc35e0aa1c305475cabe0f9426265a5d1d589c8661ea99192a313feb447073e22bde3d9117a13575e1939e11f21fbfe52b6c9c77dd8cfcc6d02deb61567f6a8088ef20c5e2d5ce02740abf2ea32a9995c06ad07fc6d60f6a3228ea5047e80149c6651360e748cb1172491fb4ecc72ebba648b4e3b4155eb9878efe6a6020e636ca3bdd58882815c57a0c99cf543799600da28d78d26c131df83faba08d2ae881512dd7860d57237eefea8f38e29f2254af133f8e159b46d58b88fe21a1ddef8883c45ad2d01251e68771abb96e507f9ac60592a2a9baf8ee8ec76a084a0874207e4dbfdbfdb9f68d7b2e4d49178142945b20131a3131000e55448a21b26f6bbe5d7d0fff825fe6186a1728eab14d21eb7b4b9a1d59452d85e6beb6c01521a6a5a599a68e093f6838a56a3ae11a04add920dbe24dbd7eb3479e8147ded82e44cb1797677857538bcaabf7149d4f4b2cbe91fc93a8e612ed1d56a16bc905bfb516248f0c3c3a1d3e99017b18742b9d267c77d40a0e8bdf0c8fbbdd2b22b1970cd275bc34b8fb02a1e27cc689ba29017933638529430254406c157854bcd3345aea55db8d49134c611ce0e5339e87ae628b346dfc067411f18804e7e168c2afdc949d1357bd4a2da2897261293fe5d4216913c9052ed3194cdc0aea8fabf5f5d2991c0fc4a94559cd2f1e0bc1aca5ba453919d714e5e69681fd107b13325aff48ac5d4c050b0c40b1fe6d5500dc2598f9aec3aede4d56f1683396f73fcfbac4b3c315262d619896d1179ed080f60f2ed13ebe8725345a92b04067d9ccfff8ff213df224e9d1af509d9bea09d8ef17ec24fda7280b6e7edc55cba710fc3c9fc09ae189abe8521f85c2bdea4b5f1f5a7fabb0fd3054e98ef79bd0fe38ccf75dc903dca06bf973170c70b27e4463a3592032a63d3ce6e681b53ad3ca7c65622f9fa030a7d6d2bdcdebb4786cbda1124e08dc7266d88cd94a6dc66daf839f3bc98fd13b798ea78d6cbf874894d038705174d995d706902ab0717909ee49f9aaac8c604fdc449826f500d13d0ac73c072c11a170e4d70abc8651ae7adc65134f988cbbe4a8b3fb974b076131a3a24b7af07f24f9b78fdcdd74ca135ac7d331d649139a1501cbdb5b86d9d46a36d325e7b6b403871abeaa1962f7385c20525464c9379c89c55cedea6973471dc2b711882dc2dfbdb83b55e842166052b6cd251b827bdd27eccfffb08cd3c93eff792e35a45bc544a28ad1fd2a271c84c53a1af266e58fab72c31ca9c337445ebac3bf4940b1c1d43088380e1203a45468d18afd39cd8eccefa146fdf75d10e75c8960014e1983e7bbc491ac8b84e92b133fd28aefe813e10e2531074a000b028200f200328eb0538cc58e18159f22818049dcfa8097b54bee522af273473682ab0a9ad62535db26981c26e8e3887682c25c964d3604951864084f20ba1eb5f14b99344d1735bb668cb81a6de581eb21b67cd1e2711238708b6778adafd68b39def6b457f90b154180eb82da301a9734e17279669d2ec367467ec514dec71699035833c83038f081ea018534426db152027f81c90f01bec081e2de521f6ac701209e4b1ecac0aed8032ff60029e7050a51034f1d4fc25cfacb8bf191cc9a18ee8f5dcdb0a8bc50c6a9edbf039d5317b2a8f3b774b622f44d43aa3541deec436711a8d87ed472db66db8ccba4b4508b5b378402c945cfc78658f271925f8ea25f08ee55d58dd9f6422e5ec2efc1ca342634f01721dceb7e336539fdf43830c21aa1bed732532f00a6e4640f21248cfad8c2f700e5e13218c11741225430fb51ea86271e24a6ac3dc2bb2d29931d040ba9630300cf12fb70cef7a25fe4dae497a49e05a0793aebb6c8fabbab847cc490729579cb133721caf13016ef5b710054b3d751be1f605e9ff15166f09abda0aabe8a6a52e1b0617e31b3dfbf07a01b86f035c69f3ce4e94c14a87c554fe53151cd840f128a6f3ceb0fd305a8cb38f4cd21c2b9858df834ca831273a89935d99259b95b9c6444df0389676c0261f90b243ece414927b8ca471c6a01bd239be72eb75f108d833fa5cb809951a5e88940721fdcbe86f51b8ebd8162c4c67393c474d9853a18f673bba1117a3dfb2878cb90c2f5ca4e53ab635870a6e138d0b6ed8bfa3de74ae12b4316b46a6c02f3cb1c6e93d41781ed3a1bc949f8b90f342a1a12fb4c92f0888433026e39a62b54df1b3b013d2818016a6ac15b4235e8505f4ac292119963ed2e857dbb67616493d401d133ee81381044637c18b9a239469ff35805170dc4c066909ba22758943751dfbd2b57ebda97a2759a6ea16a318594ca5435e8cafd0d7271cfed31b784aa635c96eb8d7f5a93adb7a237e9c5a1b804a1e5ac86682ca43911da8f0aa1e58cfd85c87afb3b3c3f8e52ee86a44a9e46da1c09881a6e935432cf5823a8471bd6a1a62311e73ffe415bda6bd82a8e7bdae31154389d584f093554f2a976734889ab1bc94ae81dc6f8c83a9c87c8180b7456b3d3fcbfbb41f5dfb3988ac8a6dcb1f0877d375830e79e6bfc2d6cf240f5a899caa326e510c7a62abbe1f6b4fd609efed959f51ce7682ba0b22f69c6a46120cf12288b05ae3a49c2e1a8afb40c724631f8c3a084f63139bb59433c135ff43f0c3a004c63c70b54f04963d808da00e6cdb58499d729277c863adc565d20703e9529d2fb076202ebc344076aed3a7b6850d34ed835b4118c265d17ca72aa8b11b118260ea476964ba2995fde9fc5629d911abf62d8b2189f46e72b3faa934126fece715b7171697e68a6dbfad848100bf82391a970a57841a05361b3572294dfb8122841b09e98f6dd4854a4a78d40a26b8b164003198410a97f2a585e886b4f1a45eee83a26533676a0fe886e6125f56383c07ed1c409324c2c244ec761087f49d34276a3cd44f5c9a9c7a4c340647fe9be1967acfcc41e893242213c0f0df6bf59a638f1cc8b0086f078131c3ead56b91e16b33015e4865e46f962e2a4904bf59b06c5f792c47f4965e1fa41b13a41f2937a5c77ded75099d17856fdd2ce4d482e50c8b0a29f463a6752530a850c9a1fb2c8873601e3fb25c3e3470822f924bc52cf5ac88c2901b62100738a1164f59426e6ed4808cd8cd31dfadd720a657a70a4a5798d5dec8f3f861483365112db6ed502c2fea5e3851704857dfa7c97a9eef67e6a43fecca9c3e4e391872a024fb101d086d4b994c9c014cf7e43c33cd9ab0f3ee324b72aeadb1f80d2b7ae7b4a89c4a52ae9d8774e8018ed95c8bfbf7e17c59ce03d4ef88248a362683859729d2681e2f257d3f997e1faace58a39f06380e3abfbc6fac9792819b0f3a79c51e425c90bbb9f7d478203712da4b1daf12b45640f6130c6570a50c3ee2dc071d4a878aa46700e3537194b4818606e8686e9dc7f480be778af5f96e4cd1b42a8afc7c202e1a80adc4f75441185a25e12201a4161d3fe72e3a76eeb1c10c3f6fe31ce4c082f3367f875d005442b2eea5149cb397f88d9d44bed08afcd798298fa8805f54ca5e11b89a760e6d0f24c96e6488c9bc36b18abc36e56462a51ca62e73ccf1401e7cd7ce8a54df69ff51a0273956b6d5f49d8a21377b038856a30eb323dfe3e610903c21e7371e10d194299978d415f2ff56d4b3d3144d861c676fcbe34c5958371f10f0adcb6480458f63d3b335841fdc8e64a19f8da4df9815d4068dda5b517d1e1dbeb1b554113d26f0bdfd21600095a21e37a629a044e8471f99cdecb12b68425f51c1e62baa43000523f42e100153c5ba7e3bfa3e824b7365d1077a9e973c6a5fc2bc3152214be03f4f834e6525e7cc3e6f12342ef8c87fa9051865b635f78342fc2dc464a38e4fc265973a581ae9031b912956f3bc574f1cfec6ab5e4493f0957f02c6a033117cb06edc9518571e4274515111907374dd49137b30b5a4fbb558a84ce855f9e20843f862bc1a91634ba0fec8377475730d1ff6d68afd14fd917e4324000e04fd27e667376677bb7ebe0873fe2372c8ca7ee5631c7e39765373eb684fe4b1b6adb4660fad0f09208410bbb6946f1f3a8d0abe925fea0106b96d0dfda050ff0bf1d989a3832814fb97d4831c37ccad887a6db18fea02ae7b1a818d2e7a4c0716b683d355a923aab5fc580bcccd0d9436286c3850622dc68fb5b937e4e91544ad9de1c19142a1d50dca085d844504960a3ea47cde9fd1151ed6338174efcaa5a101db13f6f5dccb931465063778d88f5000c3740638287a771e27700a4a0821dc254dcbbd09a6e40fb2d19163815ddbb4327b8ec712053b3e49574b07649981ed0a11b082f78453a2079b7cf6187fc2f4fd58259d3dc125b5b7a1732a526e44644401761a99b40c3b8ba8d5fd20e1e50e4b975de6154128389e5005b52cf231741806632ca3c7d13114590cef3831f298f3a5f86ae39847d0e969dce0cb393366b7355429031849acc39c2263ea416c74cf9dd4d3cbb20fc32b430e243de4439345c1bec58b92a933a4ae559a93e0de61a98c4020ccd79a0c2266f6dd1046f243526d3dc47aed136990af09d3c79ecc47c57cb1768e4a6278224af4cbd96fe28729600f3e61726768b5e1d552a35a355c829b20bd2c7847e07896c50f8900d6bf6bc9c5dbf7983e12ec74a749588e87176edbe46a46b69a53b97ab430f82493f4ffc626fd577a8c24181597584159e8c34d4923debe9a4ca8442919eb69979f97370c318124144f60e218a77042dcc66520fe5149240a5944c878179391eba72fdb8db4420165895de638f9672aa02588093f33e8b5b0cd7206f3dafbd6e8e9295bfbc1d0352265791ebe81bf2e2a91539bf67bcabbf98b1663d22ea47812452ba26dd449e8effaf29a345baf14051246885dbf346f079d4666be512f74e14658b6067fd4d1be0438ed0b43492c7ad88fe2feeab152a4dd347077d011502b03d8f254197fa3b750e4b158ccc2aa3da5625e547820dcb8afc773914ca3351fdc0b5a7803962dd31fe8883f615cfbd550228836ec159cd67a79629420e10de6a35c9b503aa42d031a451b5fe9540998c609b6eae207592028aef0a0d0c6dd3a654f611ccd1eec3757b3e4a18775e2a7fc0a19035c95a50332e861c302eee7cb41d66b040abb88a5252d2ceec3a56f8e020eb799fc8a1b968971e1120ed30f9cda9fdce5105e958bbdb742f629a665d8616bd94db598d2ef07bc3e5684b98a1aaa2762d0cc0e71bf77602fa7aea53673d9a78bb249a7782e3dd4390ac9ba1e1a7abd9ac2c08490a78c60e5eb21e82492259ac966733e6e01f4f04b5baeb38c88bfb00adb22cf2da02cf1537970dfe2b431d1b87c11ff63730675c585f5fc421ab68255978186d6ccf9835b3e50b6ced2803021e4873cc63ee6489de55ce4bb7af88ce1e82893955772fe4716e1656d586f0fd1add9881cabae2bd6a8a3de8e7443d946a0fffda204c90d12a81fe986de6eac259e74b8f62eb336a42ca1b25cd9e1b6d804814730610c0821f117d7e3eb8c3b1080b63f43ee8e23791333a5ec6a777d1abd53a8bc2be1078e5bafc0976dced19a04196700fcc13d05c6838638d9621915e5466cd200a06a01b2efe295a9b680d03c99e00ae1259d7f3c4f288a9b5e204b218a2752d4328b200c30e372ad9d94947bd0a4ab1c4be41011f42a2d0198c8efaed734fa74d11aacb469bb44d0dcbb116be1f98391a161511278ab34ebc068359df7c66f93811a31b97dea05ce47d2d93374094d4edb82a83ce1f0456ac38d2893ebd7ab1f62617b2f0e782926e92cc12e9abc689145f350bbfa540a0239774e29e836bc9c1ab47315cef1cd83a51968f32350d73c1e12e234809b925be974705d07246972f01d4dbfe8192c728ca9d48d22352319bb1538ec9e11f0dcee46bd55034390d22994e85d9f4ca62b9714912dffb8f8136c657e71973f031ea7eac9472e353528edbd0ea89f44b18f331b4bd7fd7e918d230fb6d2a03c876da4429617b330d92f0411b91069100bd19ff57a0acb0d2c285b296164381ee1bf1f13063f4bc2fbcc53ae6af10192d66bcba5609209c793393b4e54d741020123c9c43976ffb11ecaa5a46f38f503d8acdeb1d8f81a0e66990a84b7abcf27149e5e59264595dfbaa297d9097aec5edf5f2590dcd0d75cb740a05704c0bb0f8d86155fd92f9520a3dc36a67e54a89f0598ecc4d141144aeca3e44e7a5909d20d12ac1fc90d1d6eaec5377bd118c96387eaea95626d469d1a32b04f8c5157d1679f486083906e1840ff069d1ab47c262fd7200d73369a7eac227f84e26c94473911bab55f608ce9c5c45282443742f06fe5fb082e9c75ea83818a03287776dada718d0ea10a83d2dfc9e33c9bc3f0b8f112fc977152b0811a64ce0debbe23aa305e752c91f837f786d66cbc8f263698f96e34fcea6fe7284fa0d24873c39ce0bd1684ec11d38b02e06da171a38e0ff2a51b9001d7f6d38f35d4a7d08c5d626c2c40cd1e143d90f6a654b26c3d6a29ef28ebf692630c93c77dc5b8d3678d88c60420db4e400ce9f17f71be601110e31e7d091ddf9f3ddcb12cfd7ddc709882049de235edcfa2f1ebb7a3831450d9aeefecd3892f5516db2e9c872315e44d51bbd03f090518b225710a1863b6d27afa4924cd974e8ea8ca0cfa4e3722f1b26009399541823360cf90078b6fd2968361188909c5525d68207bc92642fa0df3d3b92002e56d5c8e134d7eed14f991b253690ea98fd1bbf289226430f3823b591384b0823ff825ae1392c4f5abcde74560ba6867e02d0a7965a4b4cb5a19185a103c96f9f34dc682a8c06787b4dbccffb97f4ba72bbeb2ba3362ffae08fcb6209e61259c43c348d881a8c81c1cfca7850b10ae6045176af8df091823278e65609b65e8da32663d304ff72bb1e0392ef37298bf5875eb8a0753d2421ca4cf7f91bc7de6164e98dedd0069b1a4e8908a5030c9c558b5af5c0cfca95952b178e8aa0276f8f8f38e4f575be0398dabde031305807cf964c13eebe24179ef75b2006501c8536ec3a6206190e3260ea6331a82914622733ef32c5d495cc4be86fef43fe6ecd1d25efd0bc88e39325dd05e257481b2bb667d36f5495641b3db9e177446f2b8b106fb58272350c282da058ad28e7c9b1fcf0f704e716b4f417c63217a6a07ef2694f89b79584d64300c2e7346816e5c82608e293e85c2a6d8251805a31b1ebaf0e296f5b9055a5f71c0dac4ec7395e5de6717b6076a13bebd370566c57a676f8170e7ce93e970f9d0a304b3166c8dcdf74a65020975166d7aa5272a0c1f85b369724d178805c0ab60b9c9a4fc661ee691ac204c05dcfa461b2bf031b3b9b05583f621fcca3a0a241ff91e785b420447f00f689153cd07f46aaf141583a67411da7ff99e197de264c820dbe9ff48a11e67cba41707a8bc08b72434f5431d43615a37ee847585b3e5eb7d96c48ea42a8a9f204fbb3ee0a5eb52b4f802ff6163f6f00fb64396117cb61a6c0dc0e6cab04610d26df403babaeb73d620b78bf2196c12c60a46fe243b20072c5aabc564082c7a029a02266c5e308eba9a0e0a7600b06f8d12c969bb364e36b1c28e661eb0c2ad70cb74987214198f5b70696c6337e4622f1e2a0028c080068470050927647ec47669ba4edd9e2ac70c793f887cfbb7ffff6fa5514959371189a0b04d64db7b6f29a594329b066d061a0751978a0b75b344621136a2326217d87167a5666aa140f92f54cdbb9af0d8eb58370157181446b7e12553f62f8c90899f8897364fc74897c4562c9973c04b97ccb2d8dbb839a4e7f363a452c8492764e2259908fb9868e300d83dfd929fffee2c51ec4765233463e268c76cadd8a7d17cb15fa86ac83e169bc5740010458c64c752f634acb5228ae2e37c17711e47a78b62b7b99e70f1695c4df361c77cf4383a2ffecdebe03cbe116f701ee74289d90a0ece8be2e37cfe6c85caa0381dfbc04776cc3de24d49ccf9fc628fd908cd1c1166c77c2b8219d1a1fcb38e9decf874848b3686f1305435641f8b7ddd369ab3a7612fd863bfb8264bbb29f5e7f1274fef2c3aeb493d3d3c1de3e850fe9e0e5453837562c7e696f5d00c6ae973931d475d321b5d321f651f76cca2988b5ee0b68b1465d26e4ab3d8cb3eff4c2682b8b7812605ba4820d8ba486696899629265aa6d0d419e4742b821a949f0ee5d734b4cf0f50a53f4040403f4d385c8fcf4fa53e3f3f3f3e498e1c217b7c2aedf1f1f1e9198284346eacbb19345b3d3d8f91b446eaa994ece9e9c1e956083af63c919ba75b5dcf53aef7ea83568fa880b263a640f9f192b6e189d9e898fe63b7e750a767d6c13ec3b2ce0a4163ac8006e5ef4f62b75b28507e9c6e9db0a30d7231a5053d366c8cfc7a833d76f26ca1da603465a2cdd9313f35350700e840c600ac8e003b0001c0600fb1637e0c543530520d7c4481f2eba8a2d88b220623ca441b0e53f6b16e733de13122f1f37fc679f1312ef6388fa3f2c73a066369b508a7e79b132ebef8f9b1ce12d9b2cee9c91281367c646651ec1f13632f7e7e7c44655053ecb10d94ffa7dfec8e0ee5df9181a2f387a7efa8614e9ec7ba8c6795ca66b39917139375db848ef9479e4e6f8e8916879381ca46688ae20d3b669ffcbddba8ef16cc07b82610b6ac5b2604ec9b6e7fa040f97bf293ddcabad559a7fc3cbd9e63a7a746800012a0c1ac27f17c8c47c44e96cc5f43654326e29d16b80debcc6c23a166daa6cfb1cf31d1e22c931df3ebbce1e23e377a64c81815cfd63752e1ce0df0bbe1f5bf99c96236356e649c6f8b1a988d68972850fe166f8f5822fbb9daea538b6eb34b476c966967e667755b5ea2e936d84c7e6e820eabb0ce669d2c11a60dc2849c3f150ed19270854e67840835406a80d400a90172a90b5703a40608ab46861a20ae262e9c0b886a04556f5bc0eca8787242a785e2cceb81a5dd2599a742351a1a585aebe96beddd94bef055fda664c209a0e78120e7b0ff51c8de70ef4e74d24ba39df475b517e7235713ee6ac26d4e0dee946a83bcfbd0695faffa4da90e597dad482b3d2374e25e67d855ab587bf71f1f474731adbdbb0f0b464632aebd7b4f2daa45fcb57727972e79714dbaa77b53422177c99458011f4776fc4187ee675d76b2342fef4a10dce5dddf00ce6c1a1ff9a8663c5b5f4fbaed57a95a1f53533fa45216ca3a61c74b844f28017feab5d6bb7fa7aedb6acceaf46c757ada53a6e380c10813941b66707224e4ac9de26448c106d3f60b58d1d1c493d9ea1e4e882666b0b2e59603c70c5988e08205540db9a2d3005b9818c265490a9ed872d6eee1aa080202d37614b8dc9ec8c16c75eaa44bd34e0812125e8c9cac8ec14975558a9a68a1470b4e8a4efde9d4debd9eac7e633dcdf3103a67fec59f87d019f3aed7397b771edcb39628309e9177f4c39bc19262de9253808506794597bc438ccc5436562628e611071d1d563d72c94bc20d04ec78ff5fa6d78c2323d6232919b116b9a4034678b2a3153bda1835c7b371af3d89f556a6df564f4ffdf591c7d4767f3a787f3dc7d45ecb5ce92fc6d4e096e9adff7a68ea30b4610dc330a4e118f33d3dc36e996c8e9e7a67ffee7f7a372506d8f1ca3c985365ba5d3a6247cdb377bfc99969d1017776c6bd7b8b13f71040f8dd613cbbbf4670a91ad92375c98ebbb7ceaf27edaf3defecc8ca515e51d8ea37efc6fa98bce5dea84bf358b03eccfba4677403b7d9dceec93e38eae747550383b915e9177b77d6f9f5a4d6af7afe6149243bded67e56bf29651f36865784c57c54a4c8bda2cd3e81b1b49b12eb5797067569327f3f73a0a343f6c8161cd5f201a2403f3f3f3f40e56acb162734a0e182bac84730ce47ec78711490aa86fd9a8d2c4972c445ceca13cd6944c44b97bc7889c9c7d1510aeff0931d2fa849c9d57414a85631efaceee929e7c2719c8d04d120301b5992e408f834ac76e14255231563e6ac3d69f5d988987d58f21ec94b36c63d3a4a59f0b396accaa109bb10a24241414fb81ea4d8f1e6f8f975bcd3467fe2271c75c922bb4bcb474732623682b12e67657785aad5dd0fb3fdbd65524a1bc192de6b2cdc46a15038325d5fc209b602f6667f67991a67b7facceb74f17b6fbd4fd7795687fd6af6b24ef3df57662c67a5d377bea6b33ed657ff9dc66ce9e8fc0c4ac3a1f1d0c05eaed6cc779eb3bfcb98332806467feabcf7357ac10e116bd5927d8f71fd8c1806382dd60af65d6526400459aff9efdb6cf524f2f9ce674089602f574b87b51af7774deeef61cffe0efaecef5fd09675d77ff74cbe0195414def6b54c637bef7222c6035c225bdd7af5d40784f5520eca8614e9da7614758795fc2ad8045c1d6a24bdb790fa7feb04434ef7d9d81ca9879ef2bd2a5e5bc27be378e0fa62a0da6f735089dc153ff7daf81f6f76c6e408958abf7fe830488b00017fb3b6ed174defb0d08f0414d777dacb75ed661fffd9abc4f00345f3f8312d1c89899bdf71b50a29af79e009428f6de7f406938efbd0894c6f31ef79e469dc185f6773bb4bfd717fb3b35675022d87b9f01257abdf702a044ae1744658c4d08e5d9f39e99d423a2908173fc9e326b7ad2cedff4249e9ff5a49c97f5249d8ff5245234fa73e76d7a12ef269cc0c14bbb71bff79edeef63bedd42b4d5a3b1d31a34e87b9e5ebbe4f73b7dbce4f73a3da77370c9ef611ddcae16abbf1dbf5ff51c3b7e8f43d369740d28d0f76207e192df730ac5fc62baed479b90e9bdee265cd35633bd23a6f7a18d42ed62765683a6d80bfffe7defa90a01f4cb0b509c5011810b3c5c39c3a72a04284007a5a51d4b92440173a6fef24b7a9f97818bc3da5a44534b643fd1568b19cdabeffd5ea67fdfc32e4d258297fcde0b61f4921f28464008dbca829096f23afe54bf297961bfa5b40cc517df0e864cefbfef60c8c43d4baa573d4f55c3fbfac19087ef77ceaa41f753dd4587ee87fd458760de8b1e68c76c472bee9b85d632d8d5a466d2cfaa1a313b8629bd798ccc8aae64685c4d38cd8a05b65435700e8bc358b018875523740dac85aa0657c2633117132e9bdd98f47befbd77578bd57b5f759a99de7bef5fd00c136dfff6fc1c50da3dba64b6f9fb95cab8f9fb96cec879d9cffefea644b1bfcf296dfcfb304ae3e9a1917f5f8812897fbfa444387fbf46693e7f9f0625ea7f5f034af4f766f4274fce5f274be3f92cfe104bce7a0e56fad7e0d0d0d86fe30fbd0ef7d871d4d18f19908a181175b517e7a50d425326febf26fb4da93a91b6ea9a68a4dc9c65cab970dca562a24b4b32466c91223333e01f3f567bd62b0b1e05cae20f294265c878be27eb689068907a3a3d5faa0d5c2e558dacbb50b3d9cd4ce766a693a333cbc999fd0c17cd6637b39b3ffa5e6f35f6b23c8ee338d429d9e9d9c3d3d3718c0067e44b114e86146c38499e9e5e801d11262082ab2208084e52ec1898e3d8eb297613c4ff9fbdd67becf41cea41cf43e874f142af7f76ac6330b9e892d9281fe51f438a1c4962420c77a141f94f8099f80b05ca2c560b958d951913711405ca3fe2c05f6fc351648721de481816a784824d05707054356462df114d105f02192832776e4afd84ae6567c73cf61b8f3f95c4279fa7df461e4df6f1d475939de7d476937de7d458076fb2e79c3a6fb28ba7f636d9fba9bf4df63f35b8c97e73ea70937d76ead426bbecd47a933d76eabdc96e736ad5267b8b53c76cb2d79c5a66931d766abec9fe3af5cc26bbebd4abd639fabc5e6db2b34ecdda645f9dbab5c94e736ad726fbcca95f9becfcd4b04d769953b78839758b4d76d5a96d36d9f7a9639becfad4b24df6d4a96f364ff779b287a7bed964074ffd9becdfa9fb26bb776a71933d9f1a67931d9f3a6793fd9e5a6793dd9e7a6793bd9e7a9489518d638cecc94e4f9e4e4fb15b2940af79fa4d89043b6659b7b19132631ffbd0bc8140ac890eedec8c1d6fc14ee02d0e1861b714f442432c99bfab6cc87cfa0bf1e6f3e27b3ad093fde7efd1eaf32a2a8acae84289649f7533b0ae06e50f5ff3747a8ea30bc7697e90808618298244db3e3dff60c2352de996749786917438cfe3cff1798a2eade7d2c8cf620820402121010109b970c1873ce020c739d076e1e2e84550908d1299efc98eb1d8125d2cf6e242dd8a8a669fbfce969698703752bc29c58e625d8317695c2a2e14c5dd283a14939189c16033332e9caaeb82825435b0d3122b4ffd69d8a0d807a96ae0c86431f27b3aded91897d45d52c44c756909e7655dc4354cb1a0213a34eb493e4f767cb3e49325f3fbfcd86f4a31271b237f4fbfe19d8d918b6c8c1c3bf261693e6276b2e4d050d5b828ebec98a1b253d665dd94accb4ef9a90892952956f2943ce506dff7d516702387abd50a12602862261ee37b2f8b0416c60245c69ed7f1bdf75edb08df3114d7f35eab6270be35dfaff765541fe08ab158717e9a2eaefa170326c65997c32da076432869acddda026ee470deb779fd8069eb11ad1ea0b4d45bfa2354197638dc13d71f18862918e73ca5f5ce9f13227b4dc82281b555740b148d2a52210caf36ad2ae3845418bfd7d09911ae0b94b41fc3961aa0704052549754c8623d8a82d7b7d5a3dcb589555bab623c4fd54136f16b991da3fa005b3a7a163071079e895fa656954c6ab542d5c9703a8542d920a839878242b2a15d507c8666f34b4301af255cd23e57d948992554e12b96f8b15a6018f6d155b98cebb5299521332304d67ac8c4c0769199234b9dac85301ea3fa2aa52121d96aa098f66b660da9e22a141714afdac42e53d76c4a65b85ad038d5d4df6bbb296d2d08c3aed65af35ffc378b17ffc57ad3ad8d3809b48c9419823a7fde87c130a5c7e7f52345f8420a847a6b9d0a35dff183922574ad39b58a38db2c715bec3ec6db0ee1366ecaec3678b7689ad3646a2b6345925aefb5b7622cda6ae5d6d618be6c6b0e0b2576dc296607b8d070434e059c51bd3931b2f886951708d5744c9ac362a98a2ea62da704e3ea4898a2f1af9837468080fd12ec53fa36b7b270c3d268744545453988680e0b25cc9dd37e0948a6fd13984efb285891317359fc30cd4a9530edebd13e1782f187d1276b348e16f7ba1b97b44ff4490f252590b244952a494efb34280d070c456071018a0e400124eab405b8c268d684d303d0f3f6c12ecd133180dffb4fc440feef730fe1b309e1fbc0ee8936217818ac14bc24fddcf925e9e30ebb2492b51f161aa44d681393529c19014a31c698624a29a520f823a539033302985e1cb8e623d30910e6fdeac4ac36e79c6b662c1113b303da043faed7896e62daaa497100e1a403dac48c40f67298da160026b50d9974a4389898f931fe94aa038abb268e5c71171da3a294524d29a5a1cd5a6badb5d65a6b3d4a29c5a025b211b06fadb5d6da14100f298c500f1d52c8b1800b151ca08529a2052f48183d5501c4ad09159ee02b2a1378c0a90827b2f8f002140d61cef803416f0bbf26cd41291902aa8790db092c990288103e4820c4adf63445142d18c1238996d32614c6b4d54c7b94c4045312187cc8e249093c90c2171c9400c1b2443501140f4678f898401655785981d31212c43014839120415200264a0e4c3c156e0f1d270812458fa3db8f304c4870dae76076e02d2e1f86d01205123b54a104c408fb7c851c5f0ab5c86372cb81490cba0d40b550c3699fdbb0e3cb9143772b82698a114441a253aae4e0e18a0d46f080d3b123fb80b3440bb81f2b20193102470c61a276535031b28220446425b1dc0a37a31da870e205c9134cae1ca123488fa72a45c71539b2100e7424e1a4c310503d78a138243e52a0f2f444881f4178555059b2c509214c3c2431c4aab0830355470e01cf0a4b3224a12485a627272c2fd4b0811b951f5143e41b1a90d345c101872b27c0828386215a5082071f4aa0208b12782ee4c0413c20870d505c78628515b72c966e4144d1414718221a28c2068890203a7cf0a1ca8f28ae2479e206255cc23039d2e15df131c10dba2635a880034ea77d4e258737021c4c60761499220b2f392a57c4f0e9c050f207f88ad268d00a728cf1f5be4f06303d642af88c4973606cc85380e991a5e09f497360368097e032603ca728725f9ac87d59c2094b814224f7e587ac42eecb0e1e0a725f6ec84c725f7ee429b92f1ac879b12273c979c952c50b220a8eb396f030928517a4137879418b1716bcb0404bae8b15238a725da0e45caecb52ae8b0e59bad090822e3dbc259c9b34d745c753540bb670d5956c14156507be4d9a8b62b2836b93e6a2927854a29278d40cba0a52d40af8095f5d8181e035573c1570b04a6e016739c93f28c122774511cf88dc153d320c392bb0f07ae4ac80ca5981256785115630c1bb7c607efe71968d841cb3b463ed022f1c9b340755c4b4519c7dd8100c5a51a60ab7f1f127630e25040a911e9a50f284104240418fc89d6007051057ac3072d626f87f0bdc33690e2a88691b757907de01670aa1b433af7882a5326e4cfad5099d49734eeccc2826cd390165b260988ed7c47406bde2d6815fb39aa6104a51aa13ed7d8bff7a8bf1de78fcb9a38639955acf633c95f8df9a8bc4eb6a72f743f37eedd2749eb572fd7d0f2c51f7a1d52ce9fddfd7adee7a597fbdae5ba7e33cabdffcaadbb4faea3b8cef2f4cd9b75aad3c549b41896238341e9a1a9dce2384f7f7a09aef2ebcfd1dc8d4dffefe63d6ec0596e845e4e2c0d27458abef3e668dca80a5f6f71ebdbf937b7f1f55fb3b0fdf3a9d6767a6d379ded567efd3c567759aafe94938afd3717ed54d98f9134093cbecef39a65ee17c17cd199488b5aa69ed565fbdacb3fe7b5f11dd244004bddadfdf9c9181003660edef37669fb9f677d96b7f8f7db73175cdfedec2cc80d274bed79836fb3b2cb6bfbf4c2ddbdf5d01523c8842001a176880f9a58d231ff92865c441404a2fbd3227c5311a8b813130c6823ee9839b87277d70f30f0c51b0d588caf090323e1fe393b3be1cc8c5094c89df507b1c5019df531c0e64a2c690220a9c5f7cedd2fe7200c5abd049ff2f2de87c988bf3d2804efa20145c9e70a205323c277dfa97b673d2d733343a2c58014b3cdfd53987c17e74bdfe5fb01b58cdecd264e79613c67913975673aa80894b739d5a529005cb6e892b31e7c3eeff7db0b64ffae0064125c013532b49a0e0044f2f60601897f6119df61cffd2884e28ce825a2df63c22d8e630d320b08789e02f9e85d9ec11816fafe655c206edc0b7f8c3f64a530f39963c4de047a4e9fb3e70f77ddff77de0f7e1af5313ccd9f372cef8aba22ccb32cc501f78a170c5187f80cdef03f2d126558d5d6a17eec0dc3d1029d40af4158ca1a46ad6ed763b98ab45a4e44e2527a2a92ccbb22c893035e97bb14cc599c3b8f53c8f0a229610c14d445459826f1327a229b4d65acf5a6b3d6b3d0f3ff1eebdb5da7b6bb5f7567b6fadb9e26cf3b51ffeacb5d6b3d65acf5acf3ec1370c2f543dc1ddb7e3377bf962115bbb44a452916ba2024ab5f77af90343afa9e28b23dc81bb1dbe5db6570a10bcf74a15135c10bc614a6f956aeb54083a3981e0bd477cdff7e1bff8de6c0415dd9422728061088629ad5321080411524010043d00862118a65221180504ef1d0204efe50282f7721102047739282508a2e98993263826601882614aeb54080251eb6d22c1bd57984620821f9640f05e2e20782f977b2f08decba5f4ac0d945aade7210131a6b8dd86ddfbdd0fe7ef8e30d8de1e7850c28001821d6da8c0f7639656bba4d55eb7b97ab676691e50292ad30329bc52208cf335ebe59b45f14e0436e85e9c6f7d2cf4bc4251428c7194b9f243c244bf9832a889ef679adae2ed756c7e3dab92406fa45fc65f296076107e5f7efb26c6a00ebd8c6f188616ac54c77822545996e5be39e70cb6a0bd9c133113568b5ec23ad1394a59c25c2d2425a7527229b9b22ccbb244c2945b52964bca1d2e52eebda52db145c28471ed6b5fab4151b1fb5e8befbd578aae291796dff5521f983a436baf98ad258231b62c7c318b8584dbb88c6959b18eeb27525bb9b09eb0bccfedb06cc8584e74b5d703bf30a5bddc97b3a55e875d124b0a8b915068a7ebf7c99878f9c0ebbf94e0fc7d1f18a6f4de3a15821f9524def77daf1e72a85f3cf0fa2f244742b0830c2fe23334382f255eff6584d5fabeef9bd2727d20f88161087e3ab85edff7cae10593d5c46c5e38d894b19a87159125bd88f0fa37bc86f0fa426ca801898691fc40f003c3542a04bf1708c89ecbe239def30af20394c46b86970c2f20af185e3f5ec0f83e286094df0745f97d50c428b3b571f2b11625edbf30e9c328ed7bfbb54bfbc49ffff9a1527ecc7c6dd55929583af0af04cb94aa860d12ae1a12b2ff96b42c7a833a85704402bc1b39b82a87a055d9c05f7b5bbb340658d2e26e14c175ced7fc328c15bb3763a8226d83bed07e19e3d0f4f8bd3cc5e5be70efb5b522e92147cf2b71be04c0efe58c7909b70548a510a62ca02c8bb22ccb32cb67aba62772b0e9059082b39e1e2fd7292c510ad7cf200e5329937a99833936a92ccb2565961d9dab6c0961c2213952f2235cb8924f295db82c65599665298429cb922565b9a39b92f3335229b8c4a5878530c9a06608f9d66a5ffb5aad8e66a6f9c78e96526a2d78ed0596b4175c605e2b5c380c131a39d0c0039be55edb24fc3648adc0605585ea959f8cef410efb314783bd9ea90e26d14b7a2abe04ffae55d9b83970e0c7712f06cb6909e136568e6db58ac13e0b2ca62b9794597674a1950b532a73fe3c304cd1b8a4a5c9c26b4881400d80a189557789700910e4fc5a08d8d13e2c04bcbe8de2144a18267d990aca170cbcfeeb055e3f211b39447a1edd7d6098d23a0583fd8fa390103fa2e8e583d7d0c5cb032150ea676886ac3e26fdaa523d05d2c0ab07afff722166bf8e64f6ab055effd581191d9322a15b99b58802cd74e4b0705e1ce0f55f2c946fd21a2f2d79533bbc60374fe26a20c839acc7ac46af0dc0c0d46b859a16b21636319b4da90c9b970664b30d5e9a5763d2d97e19ddbc7ebf54e8fbc583d7a738fb9542ce7edd787dede0f55f3a78f62bc7b85f45bcbe2b4c8fcf68233b5943ce90e1f789b588eac2b7d5cc229d578b5c59709ff207639a2c3f2e2cca2030bc3efdc2eb532fbcbeab0baf4fa37819c3c62022dab4247f28cd7b4fac456551d1cc135cd336a5324a93be2e63c028efa511616b93844b0a97143b8ce7612c5f96d3a8044c1b29729619aa110100000001d3160000180c0a864462d1304bb334ade50314000f5892465e5a349649238120465114045108c5300cc4000000648c41c829c7181a303e192271c090e55f11beb93fc06e3390f66630548fb1ec771c87ea49cd7d452ffe5f15e9ef62c8df75ae3ae62ab55b73092754caa225f9f45d6297e9be2f46c95b168bdf53ac02c06031ada3bccec8eb88a9d7128ebd4f66c05ff1000df1a4684c0e2a9ac07f23de61ef3006a058aa9ed83183c43be4be857ea3ea3d0eba7446f31151d6833481f61104895c7e877505bf4fdf73d5038f69556d8cafbd163cef751e6a7a6d08203e37d35a9e5942a758c2eff03e1280a170f02bb6f07f789b7ef10bc4fd2270cd8a6eac85de7e16b02c2d849387a24dbf6bcc4e0b59a71423cc3e3e44ffc1443bc505bf05cacd28188dba2ab864a6e46119f9c102643bfd03f7e39e14860ce2d4c00fe736ef6ba2bc7de30d0e0c477907a0c9ea98d76c712e81d9c398c4ff070793407ba01763606f20bbe8aa98bec5b4b7e912984d10419a8b9030cae8ba88fd109fe0c822566f943d9ce3911ee9119533a06bf821547e6c26c2190e9079c36fb0c610b6a1a95122d4503dc20c5ff61f8faaea682632556b46529abf91000e95ae85af1c0bde8580cd99844c278e8032950308df67bfb9c4c6644d0d8f200a807c7de87c5fc700c19fb1464658a816be349d56c1638e11f145267745c5ef0a1c366ab1edf6f318cea2968db1179794e204bfb18ee0de5b660ca0484728e68e335bd4030065d8d09b1f495a5e4152d8d5befb9fe7c509afcf6265e34c3908267e8fe2f37e43224c560f26042dede891ea3ed4634bcdd5fb1b72c1dba25b84a83a189b13d147411878e9025e349367c137fd82b600fe34bb3af20d3c8a14d8ce2ddac7ae939bceb602c8dc52885f13ecbfdcf4bd50a0121c3483d5c96c1c724633309f0b3c4736d7efe22d3d32425be4d8f2b65158631b1d3bdbc04c786798f146831ae85685ebd4d5540ea0e60aef68527c7b295db736ce6d32b010a69b8a3afc04a6d0ca3658118ee1bd01002157b7e41ad91008b8712bda018d0b411a6b0de0b4488f75a071e501e9a0ecf5de66bf5bdc0deaa57933313af27136beb645f8f647a936b665a816bf86b61a3a3f442fb4ade31a71c56fa7d5c3312e06b309b9dcaca1d3303f708239839c39af24adca86b20421bb3f8a1d7b73d1527ad4f0e61be9eb53b3f942212950e30f18658c01260b73c87d6bfbd6f7c5bd80c5dd985b91c38c0bd08f541cf3ff8ddfa826d79a4fa57d5d19e84107e116da9ed779616679f499f67b05f36578b74dbe7f9194b2dc11bd6bfedcd0a0e71dacbebe812078f632a052b62009207ba08100187d472c7fbe03b8fff3365a341322e9c0fa9e1178b6f142c0eb85321da195d34f3ae7387e5947b9887d706298802186180fa71f65cb0b01152a24bc17b272fc008a54da3e5013a883a1afa8256745be6b671476b671b6bbd71a590dd065071932680ec43129db02e486612eebd34819ad7fa12f0a87c5f5d6de4e1054a417938d8a100c92388367282d5bbbc40c297a71aa9ea21f7a40d3412ccbbb2135b66eb966bf0726409513229b4e84c7209ba17591235d47cecdd4a19a06ce4b0032ac7f9dc69f195c38119b27fa0a1b58b1411f38f0465e0b13c4e6bba4694cec474097812f1cc0fb0339cc4bdbcd6538275b7c994ddb71ab859731cd6d0240401596fd0d0f8d99a5cff8481a8fa2cebada7ea9a126bb46f61dafcadd21d6d344aa78cf160b5a4e444b99c1dec5dc0ec4289d5a5b4f5fc0c73bb821a62c6076066b1cc8f04b5791ee320e67532226d95186be64a7e10d24198d8b3c4f32e1cb577b7656890c5c48993ab53353d3275d7dca83340c174d3c950e9da0634fa6ecb965bfd4944f094f20b55c3a249ee38601ee3a9481596705e28d424ae40f24ef0d33c61015e3e277c3ef94d2a9b78d21f2c136326573de43947d8ce87ee192ca680afbcd524677b3a8c713de22e2e39ed0e7d7c97c59f380ff4d96ca57d2c0f42859c8e65f00a608f19c18bdb88407e35fa78c9a19f6b7680290459e29861928359ea9d3529ae9c6610c50b1b4789bc26980079ef94d16637f27a61273958f70ca281566a5451975dbdae4174773d0322f401719e6af240d83d752c832d7a98ad10ac4995f3e558f4c851117e4426ab2a4085b23006138f1f66c9dbd2685d6570e99b6ff594febe24f56c1d12bf762c2cb030c74d269831a1e023558c0230f866a0722882c982379a2aa3ef077b8a04df84fbb948c675b6092378377b6ef8daf832eddcf0036503d8f4d16793c05521711e112511a971fd9bf70de19e1423b5c47ae1d67d794ff577c9ecbfcc4b937816bd0a943e733318a6160930893c13ff3c2b3850bc9ddf84ebe35d6843bfd9d5813e6f98d6be798e14071183415ea080637d29667ff7e6094778006d497de8fa7bbd3974524cd5ff775cef0fb9b99b8a5c52530a2bccc3e523f8f0d6786b7e1cfd54dfb9cfa175cebcba8b7f4b607dce70fa9e34358ca37cf2f388444e9e6cf1646350c19890b43fcf009afbcd0be86d1a76e32ff8757c13a50297ed4abac18d4934294e0d11396dd00d712c7b4c53498655f7ffa1c7588d0e10341ba67e8cfb8c7e831abdb6bdc05f78c3b000d31f46669b3219cc6d2f74e28fc70acd13d076b1e0de8e675eb667893a8a64997f1688f62b9f5048cb2c1b96a3d3c3e3faecef2934a37a0339b7646a0cfc035121eb6b87610e5caf1057541760d5334dfc6e5900151f304b500048f69417cc179017d48bc2a8881345b3887f0188ec4c512b08177de6ba21462d82a073998d0463802f7f0da47f6983326e12bf09a3535abf1c597a4e532c23b24799e16cf8175de14cfa7b1a976874a68f219a4633e12a8da994c73420d7f7de5489ae4324bf951130fc34c79a1c925f83743793d9214f6461d603f13e64a5ad1807d92877d18993d606efe36df507e955233b8decbb5bdbb1c81cf8a5d23804ddd4cbc03a7e1cba75b29a8cb1423beab6b09712f3e9e8ab78eba6ff71d3279fac235e3f320c56950f3425bc5557b4e09f5ade6147e4bef0a641b06c6b228204297034ffd3dea293fd639a43f1d19bf0d4fab6838de9ee9c1cfbcd4a768079b89db751eaa12bab066824d2ad32b489fa6388b7c17ad596110910c595ff96acd48cdb47d07f8a4599190c4e0131021653ae2ae8d84d6fd169e2122c0068aba5279acdfc3f820523ed68cac9b41a9eec523b471fa21bc1f1933e9e0a533f3cd6c754ca639d94e81e9d20ec57ddc41bf36e2896e8b4bb6554af550eca70c23966aa383016922b408a19d83c14c9f2d36d595d3db23e6ea0bb1f4756b9d66b70b09589be524bf4dcaa49567c07030fbf00c2a36dc0e066e09f5a0c27275b734ef88fd68f421e88c03787b20147469bb690cf7c48a2fb1b4396e89c832965bb73a06bbe3fe070ada827cfb5d35904d4c73f4d83c9541d11029bed83973565d42e375e11d0502784c96af59330f1df744a7af2331488f82ba056438f87f5ab4f4bbf24c1fed468ac3ae934a767dce52b3ac40dc345ed546919b7c87038fc9daa5a888b2dd4961a472588f0e5f51547430094fa97ddca10db58195cbf42906c2d2a92a6edaace2d6768a0e2dfa4e03644f2c6523cd6af6d432a8ad80d169f9f62577c2e374589b574b37f27f73f50c14e092af005139701eda91378628b2e4df179afd8448a816eae470f2de220dcd8b97c0c8661e8bbad403e26d0ed806acb13ad25e03e58f184dd0adc5042cf4d3241993bafeaa875c4885cdd7bdc5efac87ab08b031ef62b5aa5e8f9dd098b3ca18dd1c65a4dee935a62b874d41225485bff73918482d69c5bd5c4f78120922b59e54e4654275a89122edcaa6b1cb466b710ef21d1df0878e805d0658eb1c2a07eb4b320fdfa2918685717bde4d12de43df3c47dbabd798a1b04b45e73818da29e1efffe040ba131e819e10570fb3ff6990bd9962c869c59eb38180fd9079673651fd7b68fce3f44458e4c65af1f3c8f0f96cca494c0d09fc0d2b29c34eaaec81642f5769d7004c06fd3f2d3d041d0a129357a6a6136266249f135644889a1ee0145a91d8d2429326dab48d4690ffb25bb02d0c641ee75040b9ba0032cb070f7033f4c7e20dd16b21901020aeafe7cad79dd7040e284578d4fbd18aaf2a6ef1fdcf2828644a6e744750e29c136f1d133800967721524ad735ff2359b9f4fde86fc40d3f9e4a80e1c4c41b8fce071732d5fc181340b8ae13c5c018c47a01c836776a70b2d47905e89b87d1aba3e6cd908c34ef858907aac6fef5f80ad783fea5413b02f38f3ab25bb7fc35d43968d24283216f362ca59ec580c888f1f0fe093d544cc021adf398480028c4034b8f3cb0032d324f3402f8176e4f90ad7c0174826c613282c9136f70093060c1f4433362549ecd14390ca3d2a9ea0db0c03e41b1d4fe71fd47798c08fa52f43d8a90a60697a40edf7d75e0fc301c8b8db60adf45fd9cd052ba46d654a0ec03717ace7ce88be16a2890949c25e48c4f504884d761d9484992d0dc0f1b08a83ce9186f761d89bdec10e04ee143cb9493af1bb349c3aa6e08c36514bf194573a1aa00e6a828656d4e4d64fa68a878db7c79e129758ef25d3ac671c78397d570e7aface4ec2c8da9c4247589242f184227262623969284f9fce56829e4153cc85123fb1abdaae345f8c0667a205d82afd8298d0c99fe75077c24e3a57ffe7107b5cc3a72dc75f1b2c3f8c668944b3ae911fff780a9288977da3664289b95dbaaf0c8548e9660d1f4706d681ff1cbf91db00ab3e8e270db618bf869380ed1bab78cbd5fb576811381d19ec880e2faff2ef235d36e79dfe2085f3e47532c4c661c18aebfed047708ce0508449ff91842ad2a3b28b33dfb007971ec2fd35d7ecd32104832ce997886178b05424d13dff2626bf7848e07cdc96b04c0d91f31a2b32a624b3519343c01909ee381dcc6d0ad87da186ab299d083cbe9c2d0fe23360eccc803a9934c4a697aceaa31e42b357fa0691105229501dfe61bafd39f591af41fb0c28da5bb4b1ffaa7f0a0a788538038bba99e38939d8bea274173d31f72d95a46e4fc6c4a2b10bfedf3a7343a62474bfc23dc0b5445d0c3b8139a3d5a306e654f070a76e3b42c75b13382af11c5d3172b342b758b2248c15bbe107a4c85eecc3233ed3e10c74d6f37b23143e3e7242506380419815f4942a36931a76aaa3f6e30228bda1f13efaeee6038e71ebdbb308fb5bb573e730dc7e15f43ec7c2935fe52e49d4895e9cc1b8cf4451069bfe4e0388351bcdb51460012baec8585d918b35c63baf999f36414a648c292ef4dcf127528301d2ff0965d569b7f6e2f12cb6c6ec349ed6c766873a4ef7c600ac65bcc4d9add8c9fba40c8c40d912fbeeab561d354ab4360359b477aa01e7ce79edf706d80bee0b90a5268c0022a3668bc1c6053d12228d86d9d9168d48c0005f4c691f6c1176544c6d183a60500bc51434f0baa0ec2c4d0c507e929ae372a49f4c6d04e04ea6c90debd75cf74d381193f59fd4b1f6360f9defeafafd9e31e7e69f87990d8f5ee28242f96b8ca5555462670d309e65c493364d0aa3c223b0c445cd5cc733187db19dbd4a0bcca544a4c89d1a06ea1c84f8f851e6b3df45669c7e40d999bd05097e28d2e29e715c4b3b612a0aeb650d83f0d4fdafc236bbd83f7ff426cd386fa1d14e483b92b566dbddaef2cea74ee494306c7078e1703e2039e220b45fa71c6e51e1a5a7d99cecdc914e8376763edacfad1c307b7f6e3c0dcb26341de852350e2395188817468db441c4a44a2e610fae7f0ed23ce2bf2101d8c43afa78792bf0858b5a1da843482a6433f5abeedd5904eb5dcd4c3f3e7ee348303e3c2d4130df901a1cf06860ad44a40c472b850030c1e097b500616a57a0d8e1f30f92ded7fc17048424edbec4959965ee2faa50a9442ce9540cbac0bec88511a43a5cb1bc6a0bba3308c570ed722ce1b7615d7f760494a9cbb1be76bf4178122470a5e911dfdaccbc530caf3680a180eba03e6771749acc64dadfef0228bb4955b10256ada99a016420d44e9e9b0475eb8e4255429c34baf36d1cebcbc245121d1a8715478c8f34e948b7ae8c0817064c27ea8bbe6f3e5737272359f3883a4f8a10217163183a9a8628c76ea12a60cf7adce4bca5b22d9ac154b3621a57ded402fbdc3b6d34ed056f07040b2c006f0278b5fa898edf97c5991ee4c9f5835b039187b1c264087cfee623ec5032e5eed801374914d18f49576f7cf5edb5c9f14e8c6b71b03d4f5d16934795008403120e46945222e76870a67b2f005d706e877be8a8e42945bd0afee4d53dce2c708dba65629c4d550c19b7f50602ed8d9534268072b44483fcd8a23833ac3a19a2118d82ce5750463f5e31211a1c7e62c788f18f68381848fd79821aa385e76262c194a46d13920fe904ddd593e183d68c4a75612eb9e2ed810363893b06aa52b61f05109a36cd30d9626401475dd8cdbc4d36e41458443a1dc47bf1d89cb030361888932bbc2e690bb917248484b8e29680025f991867e5c1b137be45497921721cad512004d8f343d2cd37fd12274c45534e2e1f28116625c6228cfef05aa1b77421fd6a57a2e885e9207f9718309ff6f6196d98a4845bd5d4d842aea22104ce078455ad4ca99a16814f19ad5328a4b79676a36f60ad731f0282afd8ccb8b36d3ace6d54f2bb2d5701438b2b7126c55ed51b8f14681bb3e713534a907db1112adf73b8a05365a8e472a60d415990b2fcde89ac705b4aaedc820a1f286690e46ba43a42d04c80f08cb62094be1ee2a8cc749db590f1cc4eb81d7fe990f7fad1918b087b044ca38f056b6c111ba076735055992a9f23bd8700b1046d07c8aa08be5e0b3c63a60a1d8eea736623b04e21faada1390260c2c63abaa86b8a527bf3c3e48ef9a0ec351e312ff96b35ac7a4c678e46731a8ba0613c7206d7262e691298d53cd639a5940b263bc5e9c775518dcfd6e02a9fa6823b86d0352f47709ad1968b79b8ad9fe9372b7781795d0545665d29ca83924cac2924b949537fa28f1a4e6a19e0a74daf59807e9310f55608420d29e6c62f9789186ccbebe036b4d80d04d4c9df32a13a521d0012e370a0e10d1feea63e3a90421c7f0118f52f1a27645004c72145853dd80e2114fbba8cce1975ff8e2e7a9acd13a6c0fd08622e1afc3d7f4f7c1063415b213639bc208c3ec271bdc8218aad247704452fc8de1c2a933ad8c863f70aab38957c718885041d48d3ca1952b5a6b682c092a721e1fd61ad5b546e0d707aa575728c6e66622f46d8c9341db3f40809879eff7ab13ea1c8baeee46c48a9a8b42fef6923830549fb833577a9f8891ee8ad982ff61123ba3e27d139ab87faded9792560f6f908bb5d301317143544d3a8ba04b725a0fcf10b5f9d5ccc87bc92f91c41a271062169ec6a04a82ee6d8a3ac068bc6ae3388bc7d170bc06861830bc1f0e626e45e3782371dbd6eb40da26cd9f0ac485bb4093602ccccf99dbcdf1b57a8647852ff9317f9d7f735aa269da50f203d9382f1573f4e710971832f79890daef8edab45cc8e7ad5bc588ab8acf13151e484b92bd1410c4babeeca8fd5587629689b65d21971eb95d8f71913448b9ecdc9b17623899629028a4d1417efd484dd1867be648f5a94568c7d40e748fb122e6548a775c5303f2681db6500638eb979aafc1753744394c9a312906e8071d4e8d7611b51e1ab419fdc25b1143795e720b16a93c56acbbc5c5c21aed4bea82b14d568a16acd3cfb9bd0cd040ed06ba2b226358c410fdfb3e828201c8b2f5719537c126bfcfeda7b00444bc05c93ca51e1421bf11d39bd83929acc339e59a4089de5a34734e6f20f6cda9dc64af397171f63d159fd9e3d18324aec9233db2ac31c4686da8f8b626345538123240e3d42332836aefe67fc15b537bb9f38cc6c88e21edacf1340ef4e435a7d5e1dd41fde2a31dfe94f29a4b336778aebbd14cecb2f73ac5af4547262e1ad8a94126e6e990cd89dbde5073d94fec089f4ac7531b2cedc0ef46bed1c92f873a47ab88f80c2dd9e3e50f3602f454bf10c777734f373e39573cf9498433958bd3a8b1043989b982950aff050cf8b006b871abfc661ca9d040230c12c94505f0991fa7a2127b5d60a7bf48f88bb50b4a1f8f6eb830a89796d4c2f9f1c1ecfc41485662ea73bb81ff09e5a5ac79d7538effd074dd92a2d4ae3e7d61cb8b5e13cb803b4919c8c5be63cbe62648c2b528630cd335af7ae3b9a0733509e5b3907ba9089dc9fcb0a38238892812bbd072fdac2a7d1dbee5054daf67cebec9e7675a9d2169df5728b254025d437cc7332f7c4236dc2e8318f0e602258e5932ff607caae15513c56daecef09eb9f03ddc1ba9bd4309e5a3ffe4a9f41b86dd16312ea451e33ca0af0e320f7e191bd0ca0708c6bbf70b835d6d607bf49ebbf08a5675b525beeabff62ee9dd2fe998f82018c4b5b6c6d16305d52b4c2414d1c420ff6da45de2b8176dcc4d571dccf231a876b67315857049e063b763c558a0ad877a97ff6ac438c023e8056b51406f5eb2ad90baf74b1aa4fbac9de21e25dbe0df0140a59b80d036f32eb1dbb79fa0df18a666328a5d2e0cc168170adf8ddb58ef63469ebfb0edf127cad9d32e6220b2f07c166ead518f177a5a033edecbb9c884a290a4b125aeb597c897f5a0103063a8dd3d416a6d7240abe93586ae5c46f81678ef757aeac296c979e96be301908def7b80eeff9c971ec8bab191b917acfb0355640890c2f743b552aca932d8967347785e04b59018bfa17d3f548771099620da765703a0000d5b2e14fba89d05050162cf6866264489f78606b5a0bd53fe9dc24c6b0869c4344408042385f103aa2b78c47533778b1330ed0b8ef1453093153271cf2d41e6560d5469dba830317cd04c13db717e418747ddc9c600cc5f466441de1e1d3707ad20a3675b9e74af113d7ba4f8c154bafdd4cee3a9437494ba6178a32b8d066c97abeaddbc36fbbd1618eed71d2b0cedf05c15bb63ac1fb8f671c7302e3f331fc7bbfa2305d379f3ffb9832fcd2e982a5ffcb1b62f5aa909391e582cb70c590f6c74ba8619334f2f72c683b7c7bc06d2040458766212c9a0e35099e360c605522189616924b561a2419f6084b3832c00d525d1b3d52296ddbce54dec0b2256ad92e202b646a955f05612a28a1ec56ebf6ce82308016a4d05b9d32f5fe9d37d1cf8d9a47e81d9217c0674119a7314dc8c7615590a090828c41324f69be6dbeb7a99a33df266b6059268c7538db8deb6907aeaf1334d9f0f3a24d64485084e5b9586a3bbcccee3a013564eca8520f5ec3f8f7bc0dba79e8211ce05b48f646a490e4cdcb3f79a9fd75699bbcf04719533e2ec5bbee4c4dc0345b150e1ca436c88ab964cd1731eac03bdb41e88e367886e407755119fb6dc8b8671143cea3d8ab15531f601f3f557ac0810886bffb2d9fd3cb74051e5950b12c968ab2e440294be32e8ece144d11b813d14ef9d3b6a3a03df8584fa50e5ace800044dd742a344892318e542ac4351e88ffa9d02a908d26a173d6be032104a89f856f0c716a447dfa40aa0117a5c6ba0ee4dbbdc3b6b53ac1cfce49d3cd49ab98d10c84387529381ab032deff886f679b2d3e7762fb439f0264cbe86e91a2a8391a2a0dc1b8427cad43bbac90a27ec1f74b421451abfbb7fae8d0f6b0a1e5d3a576eeffd5513116a1946545ac4175a454d8f2a7fa63513f2681a1b2e0d9afedd8ad3f564baa12ef18f695c7db1809ed51e661ce4664ad0d47f765b882270b40dfef7296e6ba8f2cad35921acd425db94fa3a5c3ba06a9cf6d875622c0ac7cee44a49303dc3f6a75399cfb1dd52016c39bad163aef26953c512cec83dd504737a7a70c6f332de90ce23bf42f5f594edebc862faced4b149a8e4cd49679aa2063e27c23995a4393ba4b32a70e3b70b6476ad615049b14640f15bb831a4912426a6e3a7818cec49e4a5988c32595ee3fdaa0ac62f3e875345fcede47bc1e7f84eddc021273f3011865497c609a022df0db9444b262df417a5ca814f5b7fdf8419e8a8e592802fe0b90d32d6c76af91b391a70ae4cdccdd554cd57006cbefab7bfe4b6fc0877e8f878ba29e788e566bd3cf3b554f2cca2e84a5da68b63c18320f5ab2b5b929acd323f68a2c3d4321162c5ab7495930907bbe2671912e411a3388bb456085419ec885bae733ba6d2017d5752bc584ea22610f64c71a1955175d15846ef5628b0bedd070c1bd2d461b85ed9163400c77d093afbe7d7b9f2cadd0762ca0765676638501bf5d317a359eb73fbcc6179264150c711365948db1100601b989b0f351d2dc168db6c3a08f20f19758d819ba8fc408999cb4bc48bc4907477f1992712b3a7d198a7c93f7c9a121c8a43f4d2b2fc664e01b3ea2ff8eb1c5c7013790132d8cf93c92b0143554dd4e3dd9cf880cab2cc86079818324132a91d9ae4ba5ade7db9385a4f73d77f504e7486be8dcd6428939f8fab71a81aa35cb336c2ed0d1c24b52148e25c1dd8fb1a902cc2eb7980d40f50eb116bda55944ca6fcfe9061e4859b6418f0c98b4592e91ea0e577440af1a65a88e44bc8517d78c9158ac10af04faa237dd5884000af73c8770e298c078f95c86efab5c9025e532279b19b9b357f3d7bb347a315bebb85d473191114679ba3b876cbb8ebfbaef76056321da39a1e01137acfe986cffced81a0d0c6258cf2bed02b1022f4e9c283de11b501d78fef8243498fb51fb239e3f798551b5b60c9a800c752cf47292ca1fabed25fb23a81bee7ad6e0fd13f571702a2112f2f75a5bd3d5c8fdd23822d98589416eac0f8d3e7d60e067dea20aad17b1d98da8b09ff6c887b58e91fb7629b2a996703aef79b1e945b3ebc8e2187e89dfb42e83c6217aedeb8cd58ebb3c76efde3ea7c01567fb3e6787d777a40b83145a5d2131df84a071bce7109d7ff4acfd85b8cfe7fe73581d0ff0807a97be150257ad58343e9620b096ed90c2142ef56830e02ccf469a01c84651bb084ebad8e0e36521643749f609ddf0c00895eedb6df7ec24b4f453ffc9b790791203b303d80bb6ec2d99abd27ca56732cac5015659335b614c36ea0ca732110378cd0333a68d48815e229eb25c4b13ec5bcda08aeeb09fe75c3d5b3600f993baa60602ff71fd7f40877dfab9bdf4e1ba99091ba352dc04394391dca21d89dd55a1a431c1ab5cea277401543f49840c8fc1081905ab3cd1b4b22026bc4161e270cf055a6f30a6bd68b6516c15380939181a846786f0750f0594b58101008ab9ad79429295413a9adb92d12c2e95118e70462ed82eb43686c06d202af350a4b4982862616e97483924df1b86a2e2a2d20d28565fe50ed8758edc7161e6a777a752ea28a3aaf3c75b0ca2c4fac7abefd2f556fdb1db8ac2006c0bf636d855b752584b88083c4121724d4a3364e8fa925e51f52b348b79142b40c14a85527170c73ab1aaccddbc24fe0eab2c0a5c10d7e3319ec9fa2116542fa04989295d01587aae5ba083284bf13182e96ad0361c05679e0ffc7b79c049a1ee82d2059a83acfa625816289cb12398dc2caba7620efad96603c9057d3abce3bb8bc86139cb0255979606653f01305f9e68c779e634ae3703bdb826197f811169877af24eaf6f40b0f4944745fcf9a84fce9cf6be17955e17742fd033e329456ef22014db3f5c2a89ebc6cac48f6cadd3be8132913d5af2364399b78c62a314c8d17f70d226acbda1fc9ddef3eaa364993098729310a09c2a3939eb1ac6fc913ba860c02b87bfef6d1b4b2e4f8c9a739f2795517024a3ebf5c832bc30415372c85d7ac88f0b1de497ae02b3d9d18f18f7802ba854d840e2683ad0547d4ca638e5a1623d9c71962bf7dffeb08ee66c753e98d533ecde3cba0ee8ca1e1e3543ba5db9638fd99bc4487a045efd5a044148bf8b05b6c2801fe26e3c073833c261e55effa5a55c31133fbf46b6eba41d95b4faa563f54d4bf597181f9edbf29e5ffe6e9d68b1560e9393320cf48ace3c1bf5eb7f989fc22bb0022b6d32ff02699358246d9f2b7674c6044467e822a97d0e48bce5c1a7039a5c663cde8613a7ab595c3a3c9cf190d6dc7a7f32f6f3325e3cf4370d1b80f865719ecde5ceb20038bf4989b02af9482a25075502769d1698b4670b53c8648e846197a98ad9c0b2ca440edf59afd52e26516f3104b98b5151d437f57efc216f9f8a67673fb5ff7a32013c957426b4f5b90f4f04a484cc4fee13352fc9259f1e5216e85e5096fb0c8170259226a64ced71b2fc8244c520958c07f4ac708c579c53560ce9095f0f7ccbe829b92c89009d7560ed44ff821f308bd1cccee91e29e6473a97141b967273a54f726e2a3cb9bde1c9019bab8a87b0b88da3b04aec3108c1b412c5c6d903b80fe86de3a2c35e11d67ddfaa452826b59c85e9803fc28d3512cc849cd6d27694c93aa7716e95c2fbd6967e3b430735622c7c66bde38b0f3831fb896f431121569ab7be2bd5d72e91ff3b34254d8a0862cd9024eae67d04f718921d8c7b49647a00e32ac948a882e41fefb75ed832793342b3913fbae4de415e3d0e295ed3cf433290e2ba4f69b517745fc053c398ad996e2b38c20f9e326bed15da2fb7d5c9f38412b9be6ab653ea9cc8769130b54ea8ac08d4e5fa93746a31fbb0027f59095e96e04646786adb8f7b99065fb3c7c2c9cab6fe4b187f8c56546cca892a68fe9c2767dbfb438bbc74d26a1e1e50c63e269f44ab06b63c1658567f5a599e0df472aca05356f69e9c682ad9f4a03ec1efa63e4c5de28f16b84ed284a1078712bb01efacb8dfd4ffd9869abd87c394bcb47f6c3add8c4775a9dab189a7a41cf979fe664e160a48b1ea64c3be33897cd2436a712dc3826eed534839e4c25108db7e0028d49bf29bfc26920965a941845b7670f3d05cbb1c74f282375d804b1b38618958d4634bb07c589966d0d457f9510cf02b11006994eefd9b367a3a088fcf14b6bfe4d11499301ce6a6a98f8ff4b43e65114c4d6c3e0864ac6623cf6449e3c03ae59794043c8f948c7a79715590041e5524663870c97434c03cdb4004dcbe9c03454bf32466640e69a843cdc309379fc00bce0413bbc03478e47cb221a25517e77462e0048a5b0cd9d9fed190a480ed60f06fbc552f096a308e7461de4359b43441de194b59fdd383fa774aaea410f4b7e2e221f0ec2fa4c5ec95b83d47d68031471280b14c1ca1206b0ea25963b5308b910285881bbcfa0b3389b9393aa880970b25e7bae93f3e0057ca6747dc1793ce58cbba0c80c35f7698d7a78794e05875c52181139da7737d2f5c3481efcc27d5764dec36f15633b39f30e9a324bfb4818ad0753a52d706da5eb02cdbf420f3a9039839600e567b97c66154745a81a5dc89f3dbce7524b4cd73ef2a958812209290c4039a2fb3dae8e88faa2628eb6812db6c135df41bc9aed9049968c92e0ac46943c8b0fe07dba9f585922257da3d74f31b5079c5be278a5b8a38735207344c96e177893dd9d5956d3f04e481d710ce5df35e190ef77d374519b3436fd8e3e576a82695707507bfb1ab65462b35a4aca808fcd4d4d652117cc89c1b4b1de9e6c1f38400a7566538f5d4a1b1101ef0f5a2d93eb5395d3e771a5a4ccb0967a037afc06283c5f7f703bd35c643b2bb831b00d866f80e4600d773ae2511abc49697eb21b6093a900ead0573c4e572159630af123f8cf4633298a4c29a360a353bcae2d66b763cc52a4dd8014946f7e1aa82b05b2b73820fa9c7e0026a96460f8ac4461b13458da97733e7eac1718b2569b1b4532d6508d311399a21b1512e9375694cf3e7559efb8fe165cfcd80fe44354579734da275b153c0eb8f6df2f64f49ba9fcc2b14c318041a7c34b24f861f5b9a8948424dd42c68830d657c747347c529881f60f4bc270b33e71657d4d4ea258e32605923a1e764b2fd67c586528681e9e01ec5de9e3fb2e760478cb521ab9fdc36510790012cc0586bd13d462fab0e8589bf611ee7137524998ccde166bc2447618c53f57529bac1617896b327436f8c26b4eb04cce39610341e2a21052f8f3943b1d8c5f4f182143a101abfb9c908494fa3721915734244313d28d5e1a0ee97f3948090fedaaa63a62844162e6d97743ac3ef7a921e553757e0bde78fc3ad2545ff0d8d4047402b158fc4797d3fcba6025b400c6dcb46be0131213149cb41968fcc747a0ae14b11da06ff03e046ed14dcec0d1c764ad0b6c8a374b9719acb27aec2a1571c0d205af13ae8c3bf474807bf71ab1b4faa36a4c0188ba2e72ebefc72d961bd81fba3e172b91b835f1ab6232d330875bcfaa6f584ee3513388b3bbb3e88c7ee05dce1a71d418bb03271541a25fae14731912e271d381f79639ae00def0cfd0418fa8612c3c967e4fdab613959a003ee505d027cb107abe70777eeaec46852ade759a02e107526902777eee713a9ed8484331146600c23f2482150ec5506477431420ebfbc28f9ad12aac95e3049cd633ae1e61101b08155583b066e7fb35e4ffa5325fef028fc02e450cbab597f86f8591a677893ca8c30dd68062d5228ec5aa91d233f368fabadaea53a718e88bee780533479a4f6d30c74da9068796697ddd6103de915c8140ef075e53ee8f40546d5e5194e3669ca4b254caedb1caf04b5906b2f8e5cda7f4ace5ea154727d0d2d7226ba851845849d82c8e96f5c3cb551f7801729598cd675c5ffde45fda7fbeb329fdc8c722fbf437cd5c2f909b7db3b1740f51f45eb99c5360e9118412ecb6e92eedede9da371151f29a6d3d5ab2ebf7cdc1f4f072936bc4ef41e61ff3f709b1ae778800abc7250d7011ff947507f342b7c2d18559ab0853427406c4a18662425a33b6b372d211e1abac1660ca21cc3ebecd612f43c9d8015345ddc53b9bcb4fbc1b4eeb6bf0bfbe0646ff58dd42f0ce36a69bb141dfe8102ad31dfa1012a34eb955398c101d147ed6bdace556c73fb28d191f88e58b60980461fb2f014f870d23879bc1b7af8fb9fdd88da93a36f5b07c7a6078b5565f9476bde9a977cd294b9478c9d1b3496d7db24f6d0edf97f4add3c12dc7625254d1783dd999fec6b2f34c9c5cc4c33bd32728360dbc37f0a904ee90653d30e8e80f125b4763316e0822bc289a5a79c85d6c0d5426f876b72636f5dfa9c23574cceea6c217265b5c3b9d429655177694bf4dcafcaa870eae20426b7cf7bf0e5ec55e3c5d9f5336b66824c826ab504f589a9bd2fd90c1db26505aade6660ea6219bc3784c424c68401f26e19989e0d1264d8cfc0ddba5e431b0bd76ac5465956516ae2a3fb584b7fa90100b2265cb51a6bb9a11caa563e57c3a9f67dacd685b394a02bc1a17b4dad91d04dbe19c2609e75ecdb6d0f28ca667e75e04a0bffc9993055316439f605f922c1795b98b64372a597337db314ebff307ec4a1bfb29ccc89d71d2072f04b0eff14b35ea8d21d44d093362efeb9e13e8f32512d8b3b8d1f24a9ff9b4bae5c6d3ff31d749db8747155c3c7f5840247d4d0d53793cd2edc65ae5f58a93d88b31592fb1a0c4b456693e537045e7a9cac1a22d00b1e944b1f2c15f8b1b9c0ccb72a4da5cda6dfd64cf2d5118b0b699632b5bb97565b2f0e10fd911d0f7b9795325ca7013175869d7106e3fadc7b84793a57c3a61ddd7a3bd16835b31b83049419f6d005e4d9c1773d07f26da6efc2a5f5f666a5e4d47ea2c1fc491917a0f3faa886f26583e693ab32cab599e697b2c867f4d818d7a8f2eb01474109c46ab7efc69012ceae32cd0aedff4295e5299ca83409d7219d2d30708eaa32b9fd759d40c10c5485d2a1448692be5c91484452b14903238a0466d5d8ee21d184f9c5bad884bd80a342ba8110d23bb2a5baa53d07ef277f7040adc3565a53d602f64ee2b3f4dc19246aa7d91b9a9ec3408569a97f642e69ef23e6d79007ad503dc00e2d48804dd7e898f37c5b1f4fe952d0ce58ca78d73dba54e2941d4063cfd97af530ae0398507e4fd878a3b67376547f353c6066437a7ca009a273523866d0eae26feced20efbd05abdb575662e3565f9f6ec2fc8c98d07be17027fc1ea8d4a0c1a0005c036be17646a8e0de6ee1457f58ac6d109899b853b4844c213b6560c0e92288e83299b96d4c1482c097ec21dbaa64b65a72dfd08af8d71831e518611bb0211a281b2d1121803eed5eba4048735353cba01df2c80f3a0a9aede96b575b9661938083fe0913785fc68c82e514eaea6e03f503a31b9379a050913342c1bc534bcd8c0296659a23c3aaf663e7287ff3a2bc33b14a8de0f9fece2e39eedc3c43d3449d2077fe7a4bc70a7306c958499b2eda643ac554680091cd74d00b36f94a63c2a961cb2b31db11d83155c933e77ac07f360280c39191e7116850a5c15c730c39bf82907596b2326998202d83827ba9eb197857840fa5de7286c90e3d75ee846f1b53e19e0acc04ed6c089cb58b93790032c1fc73e0482b73936a5f7612b03b5da86cf4e7d9e4b5f6b32196105a6f265cf8c35315605c8943f39d8f5a183e35d48b055f6e45d50e5b15b67f50c7e2dee638bfa2a654acab0a5edacda2fcb4cd205faaa1b4b8ba12d1d66aa7da9f7de6734d767ee5bf67990ed4e32222ca6d0f534fd4bdf958568cd4c2dc45cb5cc5e772ae018fedfbb46fdff6b266b6a57638acb9789c76b752e66dab4cfec944cb703a4432b37991f755ef8f92bdd636620a8723fb047d0de907c7bf88def325795d6917f029857c2f54ecce8dc91e537f94fd6e8a1fc20afed094c5150bd3444ac9cb220c52fbb704948f122b3aaa933cea344acdd084c50cc3a66805c3b8128abecfe4c134e551a7cac9069648edb43bc9655baf02c1ed77d96f3600ec085286b38c76962b49a052599a0d599f722db29f3962a41ec0816f1b2da654b43c62471fab16d38f831fe33437e3fe05060ab16c3f97e47765c0e7d75bc84c2df8c1cea19f96d9ba2f0c9a2246d521b1be753451d76dffda13ad1cdccd6d4142b821749879c1fb287e2667dbb26b48112a7758000275606f8dea0a6be2f9cc16eb5671ac85cd14526699103e33831cc6ba4434dd64f46239b5cd1f98593a8fbe9bd98d167cd5593275819c815d4cd8e66cf9dfe472c25dfebd3d1bb0ec22668131e94b0abfbd45299b677cfb1cd99164b3503926426e7acdfd5037bfc2b9fe466f0b460200e413b43c60df4d88217325a323738d6fdcabbcc1066efd729f3fe4cb122323a879a825b2989f61350ed0a8912e0e37deca03a1cf1a94040709f67c759640948aab2fb3bee09b8c2b11b0d19f697c7c231fa7459b55a8e7126f6d43d334366e508a85c209f39ab2673369b443980e5fbcbdf1d88e16ecfd43c8e1b094225b923a044091746b9c3009fbc4cec0225a88d5ea6e853f3877b646c7a250609aba782a75de516144f26f7d3de21af5e15a3e4c06e62cda7dde7bb0a8955caae396b0ad17c35f1ca53956f0cb144b2e554e01aa6ed1d044ff5093671232a2b4017a2195da0f08bbd940ddaf2a7f589acdeb11bb5fed6fd0fd1817a4daaa1f8158ddffa2b69824b2fd9a9be580cdbd26617cc7e09aaef7a85085dcb357f558bfc63393532c50f060abc4ec7b1b2fa3f3c60664a88daa46789e0c94a5176f93f57e0e5eb2f31d067100ca8a61e62269f41639c6bd81aa0391e4b196c2ca30bb191904b5857c06201e71563ac657909396e96b22b902f46586b99bd5a359afae81b57f9ba8e98b4cc7c9db10e4c2ee5f7f54e8d1db458246158f304d107d1de324b43bc9c3571fc8c5f1732370a0d89e748403a8ee30171db409806208512762d135c5c99105b56c8c6257d311815d90e5c0d64723c21447eff942920192898a8461551046192fd4651e874c0b5a84885083a0cc7848587a137fb9340c5753489f55b75d5354393f93233b2c47597338225c7c4c7e5ede4a7104f185871600f9d6d5555372ee470e4dacbe42daec201d7c994f40d0a61d5b9ad1541a416042a80df1c46f8c62e642057b48e5c6eccce93e155f0dd874edee9a5fc1e57ea0adcdb9a86767265a7047d77f11b3519d9cb0418917377e2cd212d05bbdfe76a14c57c2344f5d03682bcd988fd6b2c64829d3d5075d81e8d74ed65cf8b9111727d00a5d72b63c71c57f94d9d6864142ec6468abb0f0b4ec2ced12f115fb3a73ff83cbde174729d1bec15fcbe05a758995a4b2883049f7953149fda3fa8e1cdc4f159c7aa29d705e4a751cbb1053786e70109ad0cdb360a7264a2cac020fec720b78fd807e0576e982b772e06e2a5292901f3a4879b7c32f46f1ba53a265c22d79ec07ffdd99be88c1fab04dee98bb199dde3d57e78915fccb40856f5f9bed41e8e3f4168bf6e391c4ae8e47fc4065448d9ec39e59543d09e09a40ae1d06594910980a304b23fa6a97c716dcc8c6f61c787fcf59816b294d5e7585f08a2d6d50851a0af9c93ee470831f4c67bb272acd5062c42026f2e6c9564503fcff6c461164a259d10f06a1d9ba61020da55691710a028ae845e18dc3e42ca8840069203bdc233c3000f522896b500348bd5a8733fc81d0fd069a4e6993e32cbaee671b1ca8c9aba794fc7d664c1a41611ca1b51c09c76f2d5b16d20bd00a401a08ea2e30ee8a7aa1e33e2596680296f1eac08822b09f9c4423c01882070f76cfcaabcb849b0b77a041ec531ea9f9c67106d48f708fc9e5c98b9238bd24668850da56a5705c72ad285efad44ba172e2372fce1818c466648b3963d1ed6e2889d87f7693446434a21ae702a9e1ccb93340274069c51022a6cdd4b5f400cca9a0ef53ef6bce4c1429833e2fdec33487d8c2b8024a6cbbcc15382fc21e9dc842d967a911d63ed5f908309b427cf71db6c3d3077e36b3589bda9d18e39a728907f4270e0b6de7da335df4935438b9be76f996d72ef2df796494a2903ad047204d104b4274a0011030f295c945010a3c44a1494db858b0daba916b88cc172c48f1a6c9f4d4a0c71c28dcaa850d90e71900c1f7206e61c63cc83ab0bafc5f7023189491b3346176bebcc31cc07639df1c6ac9d9934e53a634e62920787240f0e4717c7240f0e718875c658674c8776148d2eccda98b5493ceaacb3ce9673bd89d534494ae8cf67edfe528ee39df820378532086aadf507f8e2d79166ce2a6201dfd62b3ee642029b20feff9f089e73dad7d47df29da42b56009d3d1f80f3d63a7b6815e7a45abe9ba69bf6ffffffffff28c84cdce75f7eefcd53652dffff7f21d89cffffffff5f04dc5334e85469870c3a55aab1c05e1a59187492fc2f09189d4679b10344f760224d39ef25e5c12ebc16df0b1c3b8da2611774aa84a209162954b060078876e9153558c6651ed48b9de6a065adefc63bd541054e8a150ad60e10bdd36fa4687ca27b24d0d961e51d203a07c5aa53259c4e39a822ef00d161ff92d07447216fba76da396c1bb16685ee9483aeac22e71983ce9ec92baf7142d3a9924bcf741a453ba93acd018fd92688073be5e02bef00d15f0baf530ebac83c39d7ef3a95edcf049deb94832ff25662dd4ea37ca71c246fa468baa1d06a31a81968906b9c68ca339e342fd84818e40b3024d94362e1296ce4ab3fe89355e4d442817cc8275f75c3c674bb713789ef3a2edcb556ca061d72501a642b0a1458f2454751027b6fcf73fbe2a9e298a20ba7af4e5df419a407413c2888e3382620b8a259fbf78a8b707395561dfeb9575dcb3de73c0c9f885c2336e271d1420cf7578c1d2d1f499812264dd3344dd3344dd31c185ddfdfcec33c080edb9833c0d819ba74e972e2ca959fde9cb00bb3afca3d11b6396cec15468a70abb721be4b972e6f615f606f874e706363e7b4d34e164e4330f1ffcfbd12fb4364c173fe7cfaf6adb5b6b2aa803a77d8fb28d855325c77f1b780b155d1786bed73dd5bfb177bdfed381f1646922320ad059c701d8c2449922443d800d9e58d843d18490621bd4f939b9c4024f91ac7dd6433d94bf6d1565263439229903637a1053b89a67c2359e5c048f20264ce86912409fb416e1d1849fa20754e9cd849b1677cef7d64d7b069d846760576913dc336da44f690162ee648922e785e7fd13c5b861730b6901d64c7b061d8329f0d60242986dce007469262c81f8ee360a405faa971e1995c84fb5e1fcfbd17d788f5e671d1a207dc15a1001b33f6de7b772063e4555c23ce3adcac22dd4d8ccd663b2175858989d21bd3f2258bcd66fbe1c6264b12a92c5398bc952c4926b77d4e4a299d340b0ca00f59fbc075d87a8f83d4950e7b96bb4b70d92e6c0a68d94036c0f7dd3fb68fdd6317ed1650a8b259d82b041122082490d84418dbb60a3b858dc21e6a62052a284386892a945a2b29b096cb98259498924202098c31124950ee1363f1b87011de4baea0416cf2193c24df3b57c1203639778d586f1e172d865e316a9f576d478de9ffd3266b1bb70c2ea80805d898b1f7debb031923dfc1159b9cbb469c75b85926d43ec3c1f75e5811fb40a0cc27277d604674b5425156cc8b172f27f64f2c2716669c1ea8047062a4138d94d96a433014c626c7b41a06460312aaaa2e043442984ec38b172f230895007b4bbbb1a98941357761294e5a87acadab96a15de33806ffffa250ee3b77d7fc17e99c02b710ba723a3392d5a2be82a6656ca430357233b473c38a3b37caefa2908c1eea806bfdb93accff611d27d0554cd1348761d6170527a018a1cba73435747fbe53a3eb76e7c6ece9f63b68b3b54f6badb5ce5a6bcdcc8bd502a0cd52b5f9edb5d92185ca3d77ef870fa0df2a71ff7b33cb9661e778fd174b8e77619fbdccaa0f6e3aa1f964a76c0ded09b74d4597425466adbd5f7fba2cd4534a69aee34f30de91a6996e28fafffff9ffd3926be59f3de3d943bf4fd52dc1fe2c68d036b3ea48c791a6839f1fd65db6d29f1cc76be7c43247bfe20ba10f2408f0699ecf9f9642577c4da75ecc546c82d0e5f7faffbd1492ea25d49e6b2862bac024a47b2fed7a285d3a73e63c627786831ce43c62974f1cdba951633706164115d9ba960d1c74888816c98a92669308b56856755820ba04d5711b4e389eeccb825f8b0ed122fbeaf8bfa03b3e7b39a174088df033f68e47e0a9779cf4a9b5e0d43bcef38994068ab489f62a9609e874a98edd113d6333c6f172428da405027950c7411d4298eaf0b807b63677390eca7362c148afe3f7899afcb0b8edebeab04044377cbce3235820a21bbe10f6056186d3883b403e2362150899abc41d1fcfe2b4afdb759cb674ae4d3e1e547792e8ca33c7611f4433d671ee823cb78a78ecc394d6e196ae1bd025fbc2d8022159a04f1cbb95185d52a3ae9bf52e66645f1ddfb96180b8a48e89de71da548146a727bde354cb9458c7e996fa336b2c8ca07761ef66ad07148d75fceae8bef46eac6397a5e8920ed18e53a2fa5379774303141914da47c4045566a50251d22cc9caaca160c4d61c6073ce39c9916f6badb54e24781fbc1f4adda793297dd69c08e9d38952d77dd69c1c75ca0070ba4a4af6d7bd72c039753228270b4474437dc2964b07b9e5158a66819af027e2a04ab9475be111badc94833c54abadfed76f36929346d304d2d36aeb9657285ab75ebae595567f6aadf3b6b86a39d72aeb53afe20ee6d1edde7c24f9eca1f5d62d019290874adbad963a5acba7689fea68390e195d569b162ddd726e79b559208cb5de9b735c6d3a49467ecebe63451542907d7d0e987d7d08f589e47aaa728cf665778e3107b7afe7984f150eb085edcbca2a508e9351fd316259203da9409366b955b2dc2ed51f9a6eb965aa3fb33614a65b9a6dba3fdfe3df27eef8b82762fb9ae0dc3ca8e220edeb39286e8e97b01b289ef6555e8e398f188f18c8b5e8987e5b7508aadb59bf2d8ad4bf5552196feab7359dfab7bcd60e08de0eed8913b77640c03b361679c4c22334882d90deda5a6badb533cb2d926d66ca84cadd7b6fdc095c7a29672b5501529f4d65c0740cfa6c2a23d54b9af49c2b31071da7220925e8de71cc2bf7260cda53c5127c1e3da973fc551edc741166cf731b11e8520fd14bdab95be3a65f91aa0c4002eda5ee3c967ac971a1e9c4a35bcb678cfb01ee9dc8c376ca6f151a96b9bfd07c2a01afb67bfc5d2f179173ce39678fb5c3cd0a511c11721d5cfc769efd401e6a5c3b970b45085c5bc88fd624fe2dd7fd7d8e2b854c325fa268000328b080c4cd862289270146ba0c3b4d67882ef78ca271dfac156ddea05be28a5ae7ac75d649e909b4ce6460527bb94e8912cffbb26a66a68aa69c464563ea9a1bd555ba4bf5260c717028ce4d4883b3c259ad56e18dcd6a55a357ab15cd6a46b55aad3c164210a4b5daff7b39aeeb30f66e128dfbe443fa40306755a867cfa42da5999169347b66326d5d231a578807dfaf6cf64c968ac6aed13657a98e935b7a99e8bc4b57e9322d5da5d58aaee64d485bab1c9c9c9c9cf0c62627a746e7e4e4d0e4cca87272722ee89e56347045035755be8e93874648e3bfd6346446d6ebc75d321014d4e27134d595ef5299d901e4a46855c62cfdfaff35d6ff50cf5536e0dfefd2efd2f4f5bbf4fecff83fe325449af5faf7a8ff9f470b13b6279a44570e226141da42a5341212d21214921118121032569ad8c01bc7df84088e39f004dd7704ee823310a0168fa3a9ae7c97cacc0e20270537b9a10992cad28840e9b08991be5122fcd2a485112ce30ab14d56c510a697990c431d70b63c59e56c1d1b2698746097a0873b637e043b9973c2e147440a154852dc06ce44898915263326499cb381c4a2eab3c62406550ce233a5b0e2876b891413a22537d687e162c1c862c996705c8245e9d5628c9116de14cec8f321aca337e566099e9c2fa097d490cb2c31d2e1d29391c02c11b28f6c47b7232b5a8ac23a9ea048ca7c44eb95e7670a7b80914128473684611d8f841c1d357d503525656a4740faf837030dbcc7616dd123adc473ae377971c7093c47583ba9cdbdab1599f5592bf2e445054ed0257ad69cacf432db7b83c83a297dd6820cf51227f5dc27d8672dc84c3643939111941170354567a3314e8866507dce6c7dd6664abddc7dce38d06bfaaccdace05a0d37258628aa25a9ddbf499efcd0cbef498c30c96897e99c407af9ad3e93b0d0b1f795a1ffff1d4662cb378ee3b8f93404f53eef43d2670d09961a12da9c25ae2199d590d4509b95a921215243020404c1acaacd50a0972c3d2559a297b8c73093dc7a896d12a81ece257a10d5863421551b72b381880bd9d6cb5d1be2645390541b42ab0d11521b7254a63604c8969a0c61acd464d042cb31839191d10c4a523529466a306881e1060318a6a22a039131569ba082360b72e0efdf2c3f6060879a121c9ed4941cd59414a9cd005553e2424d49d1a4d668294913b6ab413527a57566a5506a6b47865e5aec0c4f48b72323224b44bef4592382d49ffaac1131224244fffaac1169e2514a5272a2b2a79a4c863e6b32207dc78a9ca67b3d8317ca8441c2a1005194a1284252fcb87dfc6b915082eedfecb11c5318765fd197ebb8e7b8ca7b0caaf8f77ddfc7552d089cd03f3e836a0b02b47f7c5a1e96ab35eedd346962309fb57ae2ac63adae3acebf3306c82777750dd54151ad28c6ea119b7c246141362cd0c7f2748d244c4610e5d3a6799969e64c084f81267d7d7c72d0f3389f3f204803a4e11d40d5bdd6f7e9ea94f3f9a3e2f89b113d1a9e4b458366d50ddb5fe561af45838405d411734c8316127548e98fce9ecabd89710c382ce1b0a5e583c9729026fba043ca095da34eb90d437d8dbab562259302891ab5b3acb8f39cf6dbadb52fce1fecacfffcbef7dfaadfb52dcb591c57ac424859a1ebb6aea0735819f11887a15e42edb407b5f7d61c7221d7d2974381e32c0a4df4392a560e773a3d1177d4de21549834e5f39bd5b1fe9859da2792f88b3b3702768a5cc5b559404c83a6fcd63bd481395598735a5b6b9ddcd65ae7ac5bc06a6d9d3db7fe5faed65a6b0fd0c6cb6bfcc08cafd76bfc4630be5eaff133627cbdb8cba3baffffcecbffff7f8b1cddf2af5f13be0e63cf23628b117c6f87efa519e8bada755baead7626f4e7373e30e7231b17afeb5cbffd3bb6dddba27def8dbb6bc2873707ddc8f1c59ee701f056a0e31dd7f1a0da4d5acbbbfe7030c78d7f6014775c8739d8306ee422cdf9c81dfad61dea9ff4502a4f7adba5e860c59caae8b7a37509d8008d63b9fbaccdf1cc7ba38b27e4faf57ba38b270c63bc37ba78429bcb3dbee963900d1c160887b72f185f1e6f4183660c6b671c7ffe5adbe39e176a990c1977678caf3f563f1d47ce72795ef63cacedcbe3e158436a19b0208f87630da965c0823c1e8e35a496010bf27838d6905a062cc8f33c0fbfe771ec43c2fe7d4898d65a6badf5ff10d96d1e3d0fe7f736d7ff38db9e198345f7b8372388a57fe4217946f25fe7c90f8afe7fa2cf18e1fe1ceafd015881400038fd31fed800fe2bd057b086b6e08ff65565acead21e67b95e87fb73a8f7eca1372a01fe1f865969382bbd99e6dc671dadedc1d27021d7f22177d530d88c31b25899b309a2ed39dab02ffd242b65f43026032f47fae4ea83cb460170c07e2a8d1bc999975353114f408a8a748b0a65ab51f566c4c8c8c6c5501df3a3e8091252488dac9219d36863f650eead72129539923eb019c32e71f4e6a3a1c2d4b1deab73e3a940f5a8ce58556667cc181de71e56842e793aadbc6bcd202a83015d62dbb916b5d5b1566ba95824bb549fb63ced701775a25a5cd469e42dbdc14e8995cd6856689ffd31c616d924a5245594ed363b9ae95d446eb240dca3c1544685f88cb0d9e31ad35bb1c9c3ba010d2d6d591d427942e9cfeb204efe5ad587ca6b526b8e9457ae73226f85b08002d491e2983d94cc216f0b0708addfa946e18930058a16e6d8b5e282fc379905a252b5a4379d0bd4c8b983af9c41ecb58872dde0e33a3ce819362606146cc1bcaec38472b0c5061e7041522c756caa230cf045c63306ee8161a71dbf492331609181053b2230c655815cf5455d21f659419630c2bc911be83cf9510194020680cc8a017270c20122c8d0d902c8382736f94e0774893b5805d191672cc7b167571d2baf238f0d000469640cd6c89347c0567b030266ccfbc09d0fe8728e69b5b07d157990a76c0704bae41bb4c1d1db98a73a3c681ade7802d80f32ae40f4566f3b4fbac45d830c3020b98a142860bce8e171d1e2355ec0c202d70a50fc38d79b7ce28e13629ea565004454545454545454d4d4d4d4d454545454545454d4140f51515151514753535353535353525252475151515151513629292929292929cbc3d4d4d4d4d454f5003879b75959a5dde84c4a4f5a6bf591e4e9fb3a046813b367064e9026d79f7cefbdf7de7befbdf7decbedd79fd733869e3df47babb2f199daeb65ee13670fe53495f698f9a81d2933553b22a676e4cb076a479c6a478450aa1db1523b9274a326f822438ab89055458a7a581404ea91e6ec09f2439d5130297cd21616c4b75959a5dde8ac0a08d2cc596b0527396b257d709f78461098a78a66864667cdb2b9a935ebd9a463a44bb2e4ab306772978e912ef1d409986da8b42d1be990d968000200e316000020100a068442499424499cabf10114001055883c62583095c843a14818c6611045311003310c035088310819c59872aab200b3d97cc8c5e0dbacb43dc9636562aa728f32c3c4a12ea22b2b4432591a1b6c80a4f08221f6f28c3b202c57a40b93656cce92dd15bc7e1fc1a04424ff57573e030483e87be771f5b5f8022eb3826d9b627d4d9dc04a01388351365d50932abca6215a250a9c48cd3e8482faea22a54419bf3f689a4f4f2795c0d54936cf719346a7a42b55851e8753a73d0efa791fccd97a92a4d36a024ed08ac4c62890077160a0e14a2cc94ab67f5ec15bf96114a4c910a4fab4ddbc2272cf9bdb4a429b3830a48807f08b4f69faee0592773a2b780c367402b0b6d83f222c99445e87dfaa680c771e4ebc67e324b8d091e4c40d602696ec9ece919a944c793bc40cbe4d19fcbf9f1cc72158c4876d1a5b2323dd10e72618b3f1c013268d2a976fc8e3230d394c825218054c6d164b2ea1e54c5659a03dc87136326436c21a405b41732558cb120bf7b01a78107928d0451e6b4296410a41d8758eb93199bcb222ba916cd5360031091cac6341b87950cfda51d4c6551d6e210e61d0aecec6be4111b458ccb9a3ff17ac06390f97b8cf616f0ba88fb3827cbbf603ae216f8571b2aadc826d100151642e13310a23a4cfe760489442ba2c6f0a7f0176f247762b50d94262b9c0ae0600d474fb8eff69ce0aa34fce8173ba455e3087220412e61cacd2a143175fc05761e2a04fa8d179b53212de3f9a7f0664ebe1b692ddf00b98a9bd67d0066d2509a9e50c8ef244e2bf7592dfd7d1e534cb8c9f8cf2bc48bf3f9f984502d5c14a32d53817039d32fdc93079a7754f7974844ba15aa0c627ee4063a5c8ea2c4a1cf7f78eafc29174ee26f7db5812ce024d02ccc138b417fcf98a9d34eabfa42654a9b1f7d98a400409d29d531c3c3bc5c18495afceb2e329ee33ebb98c6c613977fa5c13c429bdf049154a23e6bbc6e0de285f3d57d95386b24d792243bae674309e7030c1c8a0a19535dc1b1d2460e184a2a521c5c9c7e0c262cfa1f62cd9307f5030ed81b47e5cff3cd9cef8445f0c4cb57924dc0185f1a37d2c6bdda1cfdef382fe48cb56a8a17cfbd5885a7a5bbdea970cc8bcf448902996612dacd3c49a3c27d6fa3928d6c294dcd7c18545e94bd27a0850f02f1f9e0b69c83caa22f2bfe518d1f4cf9bf0fd7a6aa0b5fa854221c9ca805c88b226a6331971699b07e568633cadafca9cf9c255a9999f9e486556782b95f82465b6b2bbf785468a0a52f8e9d15f2729bb43e0f18c77a0acab42bb0bb6beaa8cc01658334dbbed204553e6bb0ddefb773462f986fde80dcdd12c41866e716e4f5b4d0df3db13ff2d992145a8d6020f9b06f7018027180d8fd96f21c71669727421b34480aa4d009094b6c226abc852554022ce207804bdd4dc24beed326c3bd27c3402105d8dd3c11307ab6babee7120fbe9fa18d26ab773822e5e4b84d60c10fef9fa8b97d25d2fc0da6ae8c4a3b625d7229eb5adccad4be905c405b128441f498e1f0af4ef3ce7bac7c23406cfaa509c4588f32ff4189862f15a158869c4f2ce95a711431c9ead2a6002b9b6958bc218d4e18eb3e7e0c8236164b8b375ca6d7d3927ca084c946bf2d211f6d503f0a971b8a897b04118dffa9d57ded6c758e728e41ece63de1c2426a0bdbf07b9c6e098db0a7ba4108c72bdfea89e9b5092c8dc3a1431df4a3ecb56733555cdc696bbc02b54322db390b8a3cd8a6f36d9fa2dded79d18a75b13cb26b70256cc070462a86d7e2ad2db0932d6e4156e7d9e41c38b063959dad183ee5a7fef5b58008fbb6a80921d62b8c9e93f27c32657744e134a82ade2a26d6ac3108aae320357bc686201b772c7e21a5745e2317aea8286028e1cb995b79d25985b7b93c49fe770e180c591d18cd755fbc219ccd8f94db00428b974bbf59fe951e259ba4aa7a752af07d727d17622c171236469fc35bd93344bb3fae1d39fe13655a3c9d12107cd0b0dcd93e8d2b7cad9ca0e5d91557c74e5c7132b0a2763dead50c254dc3cb5253dbaae3d9a51a726acdbcd04c4efe18e226f780fd042bcc121448a89a62a5847987565657653ebecaa61857d68225e49d5c60d43608108ec05eb72e9bce9cdddaea93b10b5cf86af12788fd5940650097b7fa676bd01fd3c1d7c07f97918d95779ac3e6a63308eb3a9f302b52b65551258e9b5405f71f179d5699bb4114501894387ad03e3444a9b5f9b244c592163af4605eee3a682c56f30586daf1a986455c57ceb21b861a2030baa0ca70a8b246b325b7dcc4af284bcf9b55cdd1ec7c29fd5b803a835f8f5b1c8e1fad0e72318fc9acc0a4840e6b0b11c0e3e0eeefa0e6392cf23a3b1222874072a3a4c5d7b85d8636e74ceaf2c37000c2be61fd7ab46f6ca30bff895e94a22298e8866eaa2c72ea118d73db289886d4b854a1edc1247192c7c31767b5d1126c34d75e1dd0be5ed64791bbfb2fec9fa45a34aa1e782d46afe35c93e2d699d7ad8e7d0da17b4023fa6067c7ede387f493a6f3032088859c22981914eb2de95648556c9820fecd89993c8427eb365d3d825fc2c1752a36691b28c78cc22e62ab5e5c68c029ee115939176d25be7a93895d65f3254212b32d0f62d2e60ff074fbaabd6cf3c3eb25e1c67c13607616a166d535f61fb68123952bc653260fdcb605384e885c480da12a186db3f6bf2c06cba15dbe60da3f3e571e4e8efe4bfb2227dcd38835bdac8b3a21381951c16fa665e999e46323b03949e05d45892c6b5e23b61a3584fa11f15faf623e4817a2cbac73af8b7ed45b01d2e236da58b3d9b4ceb60ca7a6bae5b743c53f844d9b0c0f1bd884bab2e6e373fa62351628ea7491fe307256375281c02e22299108ff5a347cf997d0ad73f4aeda61b235a768edee0ac0290deb4dbe7b0688d9e320a706d5c1bda17d6fe21ab3cd80c2d472f11063c44c7129ba1b5f9421a53153e1d398bb400dda16fe474690bcbb3d24079aca0a68b013b5e84a096532deec3b97eeba31debe72f700c795028415c35c3b54e1bd66dfcf2432f85d6c3a4345f786015d493645181671bdf92bcfe697f1f9570c8d29ee34426885f2eba20b1e8d6b08b68751817ece583673a91862154669af97e7c2b8ed25206def7e0b57096ccc07dc8df8aac2309948d489e7a2853590ad9ef82e54edea642b0fedea86f668e4b12def7ae94c2165712636896b6a1d2ed39a5184a590c942af1ab25c9daa503a6fca32add4fcdfa21a4c9720ec8310f1ddadb422a1ff0677c69c0ce907b3a8e192bd5de2b21a1e8587d7669bf00e20bee2d61c6ab028e76a9584571a3dc4ab6e34b74e0163e51bcd53306a3d1c601959252c13a953f04a493722d770e61f48b85d3e2444153afa2f308fa4a0c7876c840c34ea963c1554210c0aad3ea8fbdae220301fa8bd4455dbd732e6368e5b80ce975eb5d9dcd351b636f54d34b50b9ce586eacead30e3bda4428afdb5d70e61d6731d9b501839ba352553bf511f4d322c62a71c121c22940389a93ccaf869db855878d06456e385442f8d15f6eee539c9044fb508b499ecae102d45f7a74c718b3b4306a8664f637e4d792ddf8f77e5e8dc6d9748d49d35d9a79d70f0c8d16131a773f831394332b8febf033c28c51d2257f3c24ea5cdeb554ec0282f966e6407704e3939a8cd8c08c86c4d1380653f3c562016442029fd84adebf86a9346531b81378b72e9b4f33c59d112d77295dee814a3ae2ba456b4debf57b44768a8f9d909c4c090ac58edc4f1bde79644cf235aaf0e4e83e50bcff5038ff4862784cdb2961cecafc628bb9c85db05a3d4268ec8ab7575842147a47a0d14257eb69f30167bab4e00102494ff337aff08885bfb081b25ea414670fc59dd584dc2d64b62e4bca6e696cae76b79cfb4931d26d96f7e00ed65fdf8fc65bf72ee4d3a4be1d3815b4a6595892077a31cfa4f0a4a69c33417153fa170c957ff341b3776134745550cdd25d918aeeb7b7ba50a2bb58f6a3e55846f3a0461195009203405fa8bc1d06063e63b57f8d5edb9c7226729b5d6e69012a6d887c76afb7d148265a1934d0606193fb9b9207e74d94c1ffa0fac2bcc83e9e4aa5539ea6cc3d0abf5e4dd8498e725ba20c1189200b2114f8946a042327b6faf236e29054d33953fe782ec85b5dce76d297f84b64dc0d0246e8d0334679104c4a57155471b59ae94e3a54230245ee0433ed6bfa14c9ebc4ee8c6976c924b115779237eac8530443c7ce9538c4ed8f720059d1c0980edbf73f1aea512a425bf941cd57991975a535bb2e08a2b05979c03b5864a8a7adb36ffa0432f26d4abf1d3d5243e5cc5b74be69157f07b6a7ea3ebe584abc0607ba359eeb0ac2bc7dc20f7c0e4937be066c7421b4cae53e32f228fbf84d561c45fdc8133302a7cc9320386c96f80a8de158e8aecd4f7fad991454f976c9b2d01cea9778cb22f8f445c24ff17d71fadee83ae690526f0f11d093ffa3a2b05ea25022792fc1783a8bde69067b0e23d4115bc11f77040c368d05f5c99c6ef760eb06f8e87f2487ae883b1aa2f8b095442cfa2dea94e35c9453cc019aa5f228cfb928ec2e8f163d8a391e752981263a4e2d4c60d6371517f46bb1e4df5dbae351d8e532f42f7a14e67cd4520099fcc16b04d930bff1769038ca3b40b3514834c7a28df20e589a5c53805ce9f41ba5bcf07915bb84834af6d0e7ef94fc6ce98e8b1e05340b3973b1d0af2dbfe3d1a3846d8e8fc663583a98e952b6019cc2763e6a8694eddce458a008b99669d8ba9d565063a8c24d51b28dbc6e6a3a34936ea1785267ad0a3022cff30cebcedbb97d950dacefee44864b023d5697fcff3dfcebfcbc7dd97711509e91d81247d8ec51e1d908a1ebb18018665b1d313ecb86df9266ff0631ad5155ccd039c0fed2661d2049d0157b26352db4c782feff84e2e478256250f9a469ef3d7aa3f0ade0f7f33b3fd6e76e81a75f3c268bb0aa606919b9ea979755f9712a03617302dc28f70fc6e1b41a60b629b56c2611fd7eb3163c4db5f9381e84747221c559d8e40c4d0565b220e38df75e91c8726cbbb109888cca83bfd9bc68e1dc40509c6b458d583ff1a7f9453896df0132e2d8e8f689b6daac582dd472d41cae320a979a390f7cb27a00c771a99919476ef9b7f4c1ac6ea7d7a37c67ed35dc5afbf957ccecb19a8de8af84f2ec4fc400a9ccc8b39559b49ab2e145ea3d631947983b0a79c9eabe3abecf070708d4de2d7419b822dea1fd268d4d57637b3ecad780562e014bb94837e225a19fb6dcab887f00c5e9570e430903ee8d6c85a1b9fe1f23f853cda11d738fb3c4ea97f7e138982eadcbe0275a983d38c9c5ff1876839b2c50e468d292dc2f86585891cc2900fa00a7321fa5d227a2c3c2185428bb684b8ea258cfcd4f44c3efc79155d40c9b9b18f264cecc6ae6504a15d70f6a3f40817325a57ecc68b95a5e0dc0101411f10ee76ca7bb2afd36633ec8b283e0c954143e76d308ba6afb0c8d5812a06053b70dbf1f6fdcfbdd3a50d21e21a8f30421d8ce630ef82cd00ab727da1a93e9bf0a9e86796421e7c563851218bb291264b0a1d93809177008fed9995eb4f7fff4cc64a2d214cac73ea99822ac440b2a8c55446a64ef84143390a720af22da4e99a5fffa7e9de4fc4d95524d9e47817c8049b2ce452c426c4554bb7ad6a849fd142959e2700b854e64df0a051e43dd1511e9341b2f33b076f2f49e0e7c91088da992e4ac118e22ad4ad167f133e81687453478912c4a37ed59d299b818cb37dbc19286394038a37c9a58bb6c601fb1e31d061dd8137651804847bbced7d170b09045250378619c9432321861613bc484c5b51c9115a4ccfc2f9f7507f0e600678282204f1688892778d7326c3b3503d353c0d065e316b47c6dd555570d1e9016cac57be6880353de4fd3e9a1d8e08456aa8c0e4855c156d8308a3ec5b0812a5d720ec778f590b174c049ab8d71452e7f32f5d174d34181c6faf850b7fc172c29c1de3b8eddf86c86003f4f71196fa7f9d586c2b676d2c4cb094cd55373c665709bdf14348084bd56fd5ab52d1d9a3d577a11ba51ca68f403a92fa7b148055a0427f480eb598cf11b0a5d43de0890f8ffc0d4c638cad0c5275cc8dd7ecdf6449dbe22a0a99790ff229d268e1346efa6a1feaeac96db5e56fdf037ec4fca6f1c9d05e17dd082b44fbafb960b311ec1bb2a8e48c873cde8cb0c31125ae0e8c9ad438362387855f94b89f8f42cd985cfc99252d2dc3b5baaf8065eaa4b933bc94d6b0b7b5cf5402f26ef39483933f4796c40db4a5116da6f46a61568c3f44640ee06ef87f602702b60b751a0019180cd0a386756e78611f61b2e66186c86006357f2da2565034838023447879283c9f0db3796a02ca3343b4edc11242655d0ee2b171175b2345c6e0fc3004beb5022bb58b9aaffde1190c4d5f3ad03bb11315aaaaa7a5663797796dad8f7fd63d1f20f6299b48a458cde37735a6ae8a5d81bebbc53ba4c56aa58e18961ac3da588788d77edf618c44841c595d210ae526269ef3be1a55e8d96ab3e041cc9b6604b0e81988b0f4250ad3ac4187e4f662a8e3833554724934a478c69d52586d9516658294d86db3b5c8f160d19af2c40ceb2d79e209c2c31fd3e4caa3dd1665c59f6759fd9fbd1a30fabceef1de0c82cfbb8856dc808359fc1a5a705763283f33109ce7b6363bb2dc8b2ecfbf0020b451c3d1515cb83e264ac905c897ff999869313328788f9c190899474ca991c66e13d9fee74f863a7f65abde0bca0295c1badf759a0ee092ad0f2d8ba740da0dd5010c507caba2dac9cde510f3db55795437139d64a1e6e9634b0e4823cded8f04dfbe8d3106ea00a1d79eb9acc7c4fef7bb35788b48c92c40461ea859bbc529454aa10e212355a74c518eeb95838cf25c2f5c079dc4095c0aaba95c100559eefabf70e926b624f2c6ae32cd1cec19f2aa9ea8bdbaf3517de849985bd615c126392dbebc59e88a029402fd72d7e75e3635945e07f63b8ba3ee9ba206601e70c874335cb3d6c0d4801d9cfd17b5065815bcac43c930e29c21fbb5ded477a0b6476b72c071ecd34f0ddab872b98e212c9cd7270fa4f092a02b642b451bb55eb0d31431a53836761ece48476529b2899b5752504b067f345c5868e8cca1cc7fde4140dcd7bc748540010566ee191e16049d3ba9e43d50d8385165945997fa0e7cad15c1c0989011e844e670481d98f2bbea38ee92e77177be691322087ae44814a4d3c27b8b8973c40724d08039bd21afb55ad4e6acb83aba044f005566e11a2560281d16acacaf5ea22c3d2e0b86e1660553a48f1c29f266201bbe62d566526b2153dd0986354804e1dd4ef620be84bc9889f91c09790ca75043d444913b4bf7fbb2c0527bf2fd2da8c08596afd1d8d1ea5fa82c645f63904d6b106ecd7d197b7efc2d9bf179651b7b5fe745402d05e13772cbcf321910fe56417a98bd2ab61bccae789cad3f7f3e0c8e6aa92461a28261d05ec1d186f5e4b3a40521fcee845f40580284e2ef23a3fec44587e0ca7e98fde0de7e153d99cabb62ef8502be5be64cd95eee8cda25eefde07fd0a85b3ae74d762db7c50717665b0c6f31b5121865e5ae015d83ff483abe97938ab1f540d27704a0ead46d63549e889bac50e71763a8aa20525000c7f8accc74110bd77d4f7a1cd0bca74263d0e225421549204d13d81dc563dd1d564a323f51e7bae57d85571b51539a709799244fc3597e92abd6288d0c8268a4e2edd794f78a0e88dacaafcfde208920c03657bd6588ef7477fe0fe24552b49fb7c4c41a0e19a4d406f8a86f75ada654d17e08532941f24385f3547c534047122e1706b6636b84bbb1a5c1ba072da04fd51e72c9bcbb9e25e11c3c6399e48a388016a7244e71e6816491e805a04ca78894c25bc19800aef05664e2094f8202af1bfb64c6306fe7aba784d64c74438f38ca51a5e9b6468a4a9855b2656e3caf8b229fdda28d5eaaac66a40232610af8ed1b1f0653b26cc888f3a02690c5417276d432a749bf38e02934a82d90feeb5122b104a428db464daac41a281375e5fcf21cda51f04c80accf4a79359629c9881ffc855ac9db1c5af45d11a897dda2c5e05b5c13ef275eed4b5fe79a73afa3e59b54f11fe9409e169b42062078fb640ff6ae42ad8dad8f86f8976290b2af641c9dc5db32519800719eecf5a34dec3f779d29dba01ccc2ca8018758a1605a27db613c2668b2dc8a71fdeb2b74a7afbf1312d5d251ea28ae2375a9528033afceff5862932a235d2f8b5fd277dd6d584ffbac85d55f0ab65365e1097deade81a512c5a23b10bf358720b42e6703486b8f8c7eb86c765bff0ef6f93b1ca4f3e1c2c008bcb2dce25edaaf01ff7cbe310f7ad098261280c52657a853a3b5a5fb8ab116ed808d3ffafc33e60b01decd349656e5450d59a18f24ce895769efff43edb04137e8fc85b7f49e8362d4502d03ecb7f7f1fa43957fd4034c5c0de17d3e586a5230b17fa668c7dec40ad3085362c5d1714fb84bd2df0452f1eee55e70b7032c918b3a5fe871ac9629e77e91ce7880831d1d2c1839c45b90b4d4c74cbedfa37b312cf2e7432f54e286351e455f85df944ad5795853ad5d9545836a43f29704c30ccd37435cacce0a751dd7e962a04450a8073c1f48f3e29ad818507174eccf3227e9c6889734929ee2611322e444a51547fb4333518bfec71194bb8b3b7f365eb2c0b641e872494e51eb5f020e3f1a9ce46c4141c89480a80b2ecb080e8f3849e113ed9fe31666e31a45ca19228591d01b9dd442f5dcdb41eac65cfeac797a23919dd2095cd368bb642499ebd0daa17aeceb40c064b2a69e32f9bb8d977ce7f2168427d1d95c4bd16fafd944064bbb789967ea85856d375d4717ec8456347241f79d2e8f3cafde6121417257c6b9ff1627cc7f8f0b9a6312b7b7af0b214be36ac1654e1b6a67b57f67d2342640e638c7d9eefce514d8ac41df41f3041cf452d69fce426a249e4a50c50fff42b46600519b4440fb3ffbd6a6145e4bb3b4a4028318eb5cdcbebb3b502a6dbe8fdf822845e24f6221186e60d754e6137a66e532bf7cf98ed2c094f8a8b8540d0b6e7c7c0bf5751c89aa1eb94e1e611b257176414b250147c66ec4ef95307030e5f94ea76ab21103594e4b3bb85f5563530c5c327ddd2d54527cb501d33a6105d8e12e8b23efa03dcbd2223d7280f934466c7857577a8124e6ba2ff6cd224bcdbcf76d16163ab866e30b03132a645d63030032317b261f2eb9298b6a6322013745293008d8a2c369846ee5827d1f110145ce47c88876162bea21d3e6bc40c36f1411976cbb38590ed1460661eed17fbcdc0f878da91558d5644b5b27c0ac6ed997753d948e7e742d6161581e67ec28c570414c91cccdccb8de0f4430872405ea045d03c1b55da55c0b76d21451f71881a6a55ba086ca1081b50be8bdb9808e21c5150e4ff334e60f11e8f685f924b035554ced26690f5e379c6e17a171152fc0721e7ad14ed49b623f5678dbb23b4f4bff2eeb6d6772036a13596f5004c20db0761b05e31ef5f1a33a2fd59df16562541cf061ce1fe26b42081fb3179e167d405687834c0dda1efea5b062510e39b6c145e65be3dff27a83521353fe107f5fde06ccc1b5ed383742fc4532815c6c1ccf918632de661544adb00dce0610bbb2033656bde34c9da0c559d9816e903f0ba60afc65477612b52aabaa4eab115315f2f7a542b22570e31339a00e42d582308f573c5b0056eb7d6240619b9dd8edd84fdc254a92b2b0c476be18a0a25c9a30bc5483a3df8e42935e8ffd0c9ccee754ce4445c9f48135b7d47b5772cac95c2422675573ab77dec2e26a7b677b20b6b5e4e428472971e550dec71b45b9604ab3094e70df3e61a23828d27f48a881789319607ba9657afe9ff468eb40ce55b8734f5191c6b9b6df136055acdf48dae9058ccda9266dc7a386b2e2742f1edba268e5c3bfa530713fbf70891d610110068a7c48c8e6725a37074e644d43178943a7ca0e019f1a122a0e7ae01cd5896681b0fb89d2c484be4ef8018a62ebe72a69717679a4d0b341ae8d5488d2069d7c1a828d527422f3032e259a67eefe808048bf0f9e3d3cdf117743076bd5b1be5391477056b6c7ca32f12e9776a108cf17b5e1368d28946b51ca74b009c81fe853a84e7b6bacffc5b90f5659d4d5e93bddcd3e52ad52ed6d7d6043260d5dd61e41bf7e498bf8c391b5d446ba7aed5969302e7264c5214fbf2e79ff4d59301ed27c0b08432cbd34266c4ec99c70e693dd3968084312633f8d469da5e084c8a92ece38367c852556f74362fd53713da3e38609c00f10286fadb316075d2eccacc9869fad2bb9fb473cd7c801ece74b58c0813eede7d91110bce57fb9e5859e62e993cc53e992fd5b322a2a3ebdafa7b508a3b7aabcb3dfd6ee4e58c4aa9e912933c895988413e7ba4e3e7efe794650c34319cad9c1598bf3a1fb49a6422f880bad365ebef313b8c5d2fbe3d1dbf834a25fa8045015811f6685d81b786d196a3188d681498e5f23396e84112cb7fb59daf1752b9cc40bcd4578256d93e0b30b93a290bb99f974e852735f5e6fd74700fd048fbfe891d43a4bfe2e4410bea55273330666e1cc563949787e5bb2b16a3d395ee4a154679dd73c2204cf76e16ab5241b0f3e62222e93bcc1dd6b9a1ef503418a9aa33ce337867ece1ad1409510f4285be9310b0baae3276408163c31d3bafc83d13e97483548a3a528e4dd8183338f83918043b439a33319e801452c923b7950b361bb3fc4c8ca6a96a6abf9b130e7f49a3d0055ca613d9d94f5154fa6103197e63bae44c8cb09a662f0c0bc2446fa285e5183dcec1adda8113a9b4dd627dfa64b38b2d3213f8f50fae73665a1bd61f4f274824a8ac939efdebaaa3c3ef3898148b1767fcd5c8ad7264fd5f701564e158c8d2e8f79a6ca0686146c87f3ba3e9d38bb3a59a72dc5165650d062098481d79ba67cfafe5dade24fed42bea4fcab445c8566aaa1344e6ba84c75be6178f9997735522c60d7cc9c799aca8eace14bf5b39d8c437888c4b4a1046177c213b70b661567c752fafde338020cbe530d9060209bb2db8de5cfb01a1085e1eb3bf2f4b71927e89588974c309a1bc1deb9adce541b96c432d874ba15699c12a81c2f56c8c99f494f1b2f00a6ff130a3d99a43abae87ed87b8aeb08fc73726233e66b97f1fa56c43eaa23d26ff11364155f2b0079c0cd6ba9d6101d20481f5be2fa92615f991ac7f21221a81d2f42579af20c07b1fcf2a1bedf857e12279e5008a5d0b441bda65457ed3c54eaf480bb01f88f26795737028df5452807eb28a5eb7977dca8632cc9887a0392062210af155d7e27a54a50c3b1e5e511ca4e48c4aec41c071e3b68aacd998fc0a67de1527c4189a3135234288bb0853ff8e5b805ce301395411e6a39e6f1d7d7e9fceffe3841aa9184baf23393f780a10afb2e130aa5ba0fa8dfa62320bd6634672fde982b3e630bf8739d6a4ed0ce6c5ce747e266652809d06fa4e33f60f82414e459affb2e0e3a86753e55d7a054e9a5b06b4543861f5fad2ecf3a93043cecdeda8f137f9ab56dce963c2f1e843b4468dd0915bacb530400509a033fc344439c73f1af05cb4a6b6de8b5595e90569d441af09d7c113c316e1886110917500fc121a34a253c7a9e77d2c70bf9737d3590be86f0ab6cc0076b279c7eedcc08170b30d3cd25cd24f25df449ca581fb0cd6c08c31e34fc344dd6ec281abb3a7a367b78464eebc969eb4cc6267b129bd72dd2af879ea1e8111ce8738e57cbafc0b506ad188f4133f4e2ca09165bade63a5045ce11b06738ee6c6c6fc6abd31010e788a56c87509cacce57bd75a34610813f9c8fc23886b13500033418e03b261f370bb26c6f26a95595b0df6abb9669a100aa376d267bbfb6724ae89b7efad19020022cc7583498998de3b3586b5e9a1a86b438c4175fb668fb3c3880d406c02aab81c8a71235dd025ec15b22ba8c1af97e9a69adc9128699c531b316b904a6d1d46514ceff77ef87a5bc0c3bd2b267868bd3ff3500912ffb911efa8f9b7b8f9ca0b2eb370b31546f433e7262e9e307dfc42a73287d9ae3aa463aae40cfc88a85890c95ac57b0f35bc0d89b6a72027841fa4e1fd56e11ef4d5fb2bd4f376f0ff3caeb94c32e933f0a4edfeaa5feae8d53cf6bb82cf360f9738aae4554bc9f1044ed2c70111564b351f03fff6cbbee73fa416a97eef5a9b4cfe0778281a28fabdecf18aef13b82524bcc9d97c5c9b7db9bd8f9b5657f3f81daba60dd69a63d7316def1c14044ea1c22f9b2952c81b8da8bd1a0a012104053c636441081991cfa6d53f23184027d674abaec0b08ce2f64cc519952f18a9aeee541fa0e3ae8e84fa077b83ed7560f3891fb16c5b1af785764f29f7882bef74dde0c5e7e221c941a8982d4e271525a07ff9c96d81227c8c58f5711b1f43cab1c74c6d98546787efc740d8eea375fa670e2595044b1d2400b7c0c23a0994640519238ca128427b936289459efd6f5c0e3a37968354f7789743c3f5fd76db59b8b4ceda16acf274b46ef2e6608d511c8e3492123cbb7a9d18cefd77b8c16529714a22f8c77ad6bb4911e0be4397d3a79706f745c2035871f263e52a86ff3df5eae2e397955bdd767615db4bbc4faca8b8362b04b519f46626ceebf6a814ef3f8baf229fae880c3c70738ecd1fbc4f4acc7b7603675573cc81e4312b913978a3bcc49d3de4deb1d32e396dde80e0cb28b37093163011a856c5a24becc8bd952539b6566e7767c9081ec3b0a536a5a94b546074873904fb85f6de3b302253445abf7ff1c35320f104a81c3c68c96ec1e038a96d469b339fcafed506f9e2f80cf95db418b3640ac7919f62243b59c31682a41275590eb9effa4391279e68381b3ae49110c89a0eaf57a29d920bc36b71fa95b3335f2c31b7553182965ba427abf03c0aeb8c40b81366989d4b5852b61988ad3421f04d29cc443a6fef6acbc11629f9fed653df113db73d462bb4efb5b6860b577c9d3be318f0d17fb855859eb9ce88068e8422ad1aa00999d104e2e9f8ccb0101f04065de73adb6786e5a7675d93255541d8db92117036c88878366628761dbdd0dfaf9ee8cbd16e022144b2b094d4e075e2f38f07261042d4a83835e79cecacd60e47d1037ed33703a8e79fccb804028f733c31698435e8c1274e58c788ebe87995440f634a8c879df207cd51644a9a5154ddba29e9a4764597cdefba8d1d74b3c417eb7428e73aab9c734fe97715937ceedd13afca2c10a23934d1f4df09a2c28485cb794f07b876ba8b67da92b1896889c2d338636528a23016d3c8c29327bd8dcb6637ae3917e5460bd1c3774f0ac8e2d22755af2772fd9f25c7bf2ae148b1b3c3492ebe4366366dee346ac36c3a3f1ea3d285377e7c2784eee7e4585003a57941cf91b7dabe517ce56890b491e08f3e78a667751e1317dfb6181430fe27905c414a08bd1979a838389583b555c9d2dd81de7f4392b197647d348dbc5760326dfc3aa3ec9f3daca6fdea390108b33a54ebbfcc45838d57ea159d035ab9c1768bf4c8bc019355f28e42efe0384e8d92fbf05092672d617e7cdb6b95f70e85a05d9f6c8beee4955edcc9fbbf5226ef4c201f92433534f87cd73900ca992b26de5ceea6a4152eb0d1bc673b65cc8246a31c6cc06fdee7a6a4e6a409b01a2a3a286e086bed612c4a7aac43cd229d0ea8544dc860b46e812bcca45b1807d721104eeb63258987d7f665bd75815caf7158350c8b2f56542996281dafebb0c5b0429acf5ad200a1c84e468d7c82167d87188e06d8b67dccc7d790809b0d4ef6ee89c5ea0c05f249a8011b91d91ee2680a25b732a0a6fcc08b8a113d85d8a337c8a2737ade05f6384281789db727db05419c6055d0d876b5b343c6d4ebc7338462bbb78a80eab4b88d010cd82aa289396cd7a7613b9899b098a4044be80f505cc4b90a9c822c4ca2bd2db3285f21847fa6d57db4e6db281e15b08f758c46be10e2ec85cb3527d569a6c805bda7f90ea576e43a0783127329f73192e76349f1121b6f5a31d2135ea6b9f42a0b0367612981bfd366a48defe8dd68f4101959485bb17a81c8b458ba813cfcdb88d753c368b90fc0bbbcd5f471d81e1829a39e30515f3c233d15e46ec3f561d551b3299e3e85c0d302468c4ee2db46ca946abdacbdf3638b48ba52b2110e8f565e938d577203c2e4aa062b847204d9a9130c24252c78f86e354d97147b3ddd46243e4e6dae1a19013de36aabcb67db8a1d26609c03ec87c18f3d4fb4c5742a80e2fe6983ec8e472c942ca6761f0990bb4e56f97ef6de60ff00376361edcb65eacbfdd23dbe282308149ed93580a36495e495be1a22e39b71de9195c9317c58cf91a7052a35cded103532fa4e0aeb418cac10c8fae1c25ceda925626a285f2db98755b934499a5b771ab9b284c2282f1488f5228caeebd2caf31d61851e21442ae1b0571c2b6b831b415e6356e704a231808cc8940adce425d9cfb6e96569d242d7583fa79ab6908e6b6c0bb10e642dc74cf118d2e368822b24eb9d3d1edd86c7d68b9fcd749a156a019cef26a770a6a629ff6c02effd4f9c86936c687e25224800c9189f8646556bdc7a8ba433d02329fa3538213978260978ebe52ddee9c212d62de157abe1dd8fa3a65a8dd391697b15d07ec5d304bf1a97ba5a2d048f16e30e7c1daffa4812cb5f02daf728e46104918839592d67c20b674894c0a42b8e1c6a3c04823018fac2314c80dc8515b3263b5ea2e32db3a4c15c1d9f24c8fcd13376cc7cfd1a8b18f587eed146069c58f8fbe50913d78506ff55addca8b21e4a60d2ce96793e70a0a3d05cb00803071a6f25c41eedc03b09049031016e9828abbd44b73271c73d1c0740aa58af31c1a0ab3b8f30e16c0050e113c00913d5ae073f9c091a752629b5170f1c418f8f8689aac66098e319c42322235cac8a433e4d737fb7bc2e12a1313e272612f06243ddd00b46c43d9bf3ad0156a4593796cd5635c60125d0614afb9879e95b9f3f46701fbbfd049d805dd717ab9b11e09bbc3961a785510406cb0c7e5123a4b9ecf277acb0c12cc5a8b455f115074480502d726860f51acb67e957a27c32debb4665af101d72c9fc8589cc8e206a2e50c3d4be9011bb03d70579ad70d9d0d18c21fb4c78b861e377e21f80949d2f81d23cd2c59918546cb57f80667355ac9561ae1882f32434d4646e7bb7ff2680da667c150bddfc84b1f047f0ce8fb30d035fb1a61beb9647f363c8e88e3b4b14540b94b320595e295bcb9d34b5b3f799cbfa84b69eb5388ac27dbece9e6f532ae1be3b9685545519257a2033c1ac90e8c6bcc8a6ef73f6e28083934c7e363a2e0b3e78ebfe56da94f54b1967d6f46eb3ff340bad41ab7dab7887a092117eff2b54fb5997fd974fb11aace270d211a0f966c64593f130995273c94b789062aa696e8c40ce72ccb9187cbf585a353e002f08a19c1c4292a47de92021f3fea447ee85cf455d3b720dc1cc399549b8e3c8494de5754c6aee5ebdcff097aeacc7930d77adfb66955184af5baa5d4034029969d8fa138a64bc5f8171046fb051b0a648364c8993ec0cd416b67f487f3e0440f27045720082588020720d66c28239112d61159d8fe2e59254f36d4d8b881b0f7e02e4107ebb6e550bead8051203fb70afbdd560ba98f0579604b35da241fffb803778afdd29fa80effcbd00532e22c470d1b32d7b9e65ff1be02d7728d491fd8d4498c1801d64eb4c2d943f00f2514642bb90247382009b38481b30a8ce865404614d3f299adf211ba1ae9af11a574dc94325d75c8e61815e307356efc0040bf00e49d073854383e461b57c1bb69d764695b82118735ffdd5a4e0ad7222596d5f13384b895137b3ddd2d85f56371c7124f039d0a06528dba83ee72fbe6b9272ea3f7cb6365fc79d99c75fdab9cbe891b561108f4baa536ee698035f1080fa29fc220e19c8ae703a1dc88f2a17ead9c314632037328cea2d8bb280a8a52702e2d3ade1a6c8cc12a9f3e70387792ae10b830c8f387dd7bbc1134cc7d779405b065d51dde8431b550216069a98e1eb94a9251a36d721f0ce33d670021c2c69d4d510c402130b925d8634829a0c8a05fc71a2a5544549e2f4bcd30ab3d22ac1a331976ae9325e657828592e2c1c5ebb05fc92719c767c8608c77f06e9ba1ed948e72073933062ade8d2fd1867451eacce4666fb23af2f9279f43e6e7f2d03fbaa862d7d8dd89b895c6f0d95b31af434206f351213c18f8ca18ac8c276eb07f0ddd462b6e6842ba952a505aef56322cb9d0b8a84115e5f5f25b3a5e5164c48218cbed77e03304c98d904a79fac52d04ef8fd3d1858f6a50fc3dc237501957922ac69713012cb6c60cd576a0f0cd01b9e1978f1cc94fa5b2e5c410dd1aed8647cab1259b9a1be132cf69c3e2ec0e2dcc60b17cda2fbc9c7d57ac45d7f0e159a3b79d80c5c044797a61976951fe328a0fc3f90fb530024c0e1f9e9c2fb3e9f323aa31e3d28b3cd120ab6f9078a586fb286802645030f27a23286ff2b9f51940889bfd35421b208daa6238a0c32820c7aa82948eab5484d902921dd6e4a0a76256e6949c5ba8b3f85382f2262a88d9788cd2a310fd4268c7718c6c40397d6dcfb6cf537f7ae1b8a4780103b411072208b13cdcf2bec48789efd2fa831b25b2c685549094876c692ca4afd246614567f84552f89e5d0d5adbc5b71e191955b78411252664521db8c6f69a03aaaaad6baf1ef3cb4d343ad6893230058581445d4f3a88f8d298e6c871a11d4e775c081520a8bed81f5c7552cabf8122f0f938a634b8bbf6c030e80a438d48ba04d8e556a200e1cc36aab704a71987cc2da1084b93739def0d7e6238a8b81fa51eb79004471f47ed97574eba3579067150451f41aad7fcac18a8a63753fdc8b42444adc419a72742b7aeac49d5af18b2d0115ddb7b48877705e0ef9061e646fd53d24c647413a960a7e2dfb0adfe45246851de248eba7f88610fe70e3afd14898b3cf5f60c719365fb22422be7f834b2c038b5e303d3f152998ec0a3bb42950e6caf4a9bbfa1156e4acf41c6b585e6198328d37abbaa40287b14f300772232faa7e7b945781ae58836389a9885c19e059d8713f282dfd127065ec63cf98552cf8975bd3e636a77d69cc8816066af11ae5b50d7eb08b3e761945cf820fc30a70e52f0e72822defc01a3abd768810c1000da16bf63609257dea1361ff0b3622721297fd96ab2341f137e8077448c4b31c02240124d1a8e010383a225a9a209b59be8196f953c551792dc08c1776d06a3bb7d0f9a791d08d1d2cdc6dfa97c1e9691e0faf47743b995498d1d086dfbe4516510392b565b5a57252f4ad6d45ce99f41b24fee39e6fcc739ddb58db4e1868a0280c377ca5de99c132dfeec9b3d7e49612abefbe4b05b1318fbb94ac283a45bcb5c72355f4165c98f1069632c46edd0bec59869b1fab38835577dde092bb00e745bea6af2673be0ced4898c9d3d576bb8316eac4a8edf9ae883f4514bf664f25ec0356f183b94791344c4abb6fea7eb830a4c2088a232b2e67147ba3992a07c116ea8b0710234a2cc120206fcecc08dbd46768049485f29eb331264be4e587b8bc9d1d7615c63d5687d55df39322f553f6135df3f36c7e2ac51713962f5765fff272f25ac8f01022d5fc80781e06f2aa8c04476ba0772278dac3373bf85054526c1ccbbcd994cdd1229637249c7b97f51bfd1c237d07b27f8959f1c1493e7299c2421b58e6d36ed6a4a438c1fc541a7c2635e519339fd1a610789f5b050de7028a2b42e312ce232ee02c8e0d860a08cff86480e8ccbaf46c6ef70f3377e3ee4d1005d1ff39304733c2148e24d8247eb0fcb06ec710aa310007ec246e0c63d7299064fcb4085068e1d01648e105c28671b4b6695725f5d32faac072d493655d1524c57c6026232c0200010feda604524664a2a8c922eba554c7886233404e3542474f19e329e660dd52ee9f8588a60a610c5d83da43db1e18c55e6a74f9236fc1fe839bf430ffdc9f7f385a6378e7a10f8f301a1b84a32745db35ccae6b057ae04387763b9bf21ca062a0c1ed28a123e6a1e78b7af4db461010c4604b067e9fef1b45c811dad0c28962333ea23a6a769a7d44d2eb72477fd5132d89c03ae4710429004299fa33d6953977ef8695652520ba7b791d63b5a1d591ad862579fbd8ecdb25d09dbbee7cb525609a6fe73882ee56c2e2e71fa3b7558fd1a1f215ab36ba9e02d7032ce9358f418f85b5f76a44ca8d76a0d8404142ccd486a2b2947a07e57216f16249133e0c9389e14bd08fd3aece79207ad2a426441fda6fa2e37669d7b530b0db0e464e156038f2cf7f0b8113e6cbd0c6da19d75dac872436011d2568faec1cec0e176cb21658c3953857642873f6f9f123109d136c7149362621024b733c53f7fc4dc3cb24682835bea932aa9d554289e544239fa0d99ee4ff8cefcbbea673256327f67566cd1f430cafbf085c4ab6bc0bb49ef86c041b2bdcec5e806c92dcb0a00392b84900f341acb1602f6afc8cbd29e461cf55c2eca9b6b1b501b9abd4707c0869a3d2724789620e6cb1db251a2991e679e6877d8187ede243decf6cc83f7e6e10b63c69b5f8b9e2d86b3199eb0a79127b0a302dde3a17af6bd4a35c77a8c5230ab9f6875932e5201eb1eb239355c5a71fb8321b98232ad996098a5c19e86c3edcea7569ebad16ddf3bc75cc956b33d47ca006343220a4b6417d3732419dec282eb91be6a9fce23af223432771ec18b1b9c9100c05eb16ec10c23c0ba185acc7618dec1cec4ad6a9ad832a5a038248da9485861c53d96a8d67b0b5732ba99a0a799b6f4e08e2f002d286a8bc61f8cb552645b9aa5d415cae1f42c921ef920316f17f51ae00fe9731d641562fd16d631b7660a8ed7b86c8ca42d8e82736928a3abd7db861cced573d7348bc47470fa0edf4af8ffae3e9cf165d32796f68237d45c4187856d2bfea9ebbd31facea18faa5993712a8ea82a9c20e0a77d5e83528a14a1b546a4032d9a42aa4722b3205c803e93bdda56777d0550ad39e724c771c920b8027aae26f9ff2ba55c5c743f5ef1b12c84148478d748c71ba2df15a91aa46d23c979cad1874b1d7bc52dcbba2c77fb2f7fecb2958cdac2d704fcd765d7150f2d1f03b3dffd0dce7100c8d7e032c72143cae2159e01aa6a1388ebcaf862736927915a181f807713935f22289897e0d119ac46dc84d0ace618b560753ab520e5458923c447168aa0ff371b34c940e209e4787a06128ece05a76613ccfedf219b28a8def2b72514e0fc3f13c7d204b418a5cdd468ffeba5c8f023e06e4107eb2bb7e9013f5824fed95660216eab3c4d84a770a86939d1abce273cddb6563db088fe515b568f0c3a07dba6af24ad46f07f1fd72e6f967b4432c5eeea894261f27a00848dbd709b05caabe61b14b3ccf73974dd3d720e03a0e6856f0151c2eb851841eafd16208c196d3b017b0671cce4d5d363ec459af33b0dbcefa7f9c477f6c115d7ae7e910c3e0574d35da3e686697a4d2fc2ee22b1db0491698613b698a41afe666bf949d72edfc4cd655c609857011a34f0226b1e8643c8ec08743ba82cac19427c40bc90faa2f3210975b984130f60b0268f2808be438fbf95e34b03e959a7ba96f5eb044c40ed760cff1558907d6806309ff440586ffd8d21edd7f7a89e8ae7337af2cec2d12fbf17dfd499194067bea62078a6a430e0f31fe9fdb5a24774f33c5d50e7f9ba24ada6d09e417366aaa2bc52767ef728c090c3a472865063209157e153ae3e4272616689f714cfe88cc996073d230e34dce6ad2ab441aa8eeb6399419041a19a79f5b20165a4d07d592912bc458ffda21cd373fd57b9035fe0f4bbfae6eaf84d8d5c7c51e8496307f1068231b57e437be339c0b080407689d7f41c61627251b401155d50c369c0f40638b532e7320dc8ec9c4a4b5bddb9adc524a99920cac0a93098f0958481d62bfebbc2fd4e13dd73bd9fef6851d7efdee53aaba93ad2ad5bdb47cb0580ede384d8cb4560ac4177d4aff8443ab45a6bfe18a7d4419fa2e485cff4e1d81e3e211373ee71ee7f497d9239eb1a5941ff861294fc9939b11e625469683d3e5889b4aa55229ef65f662664c409eaff89ae00a11573e4e8e0fa7d97efe0fa43c1f88d3f08839f5f383082152cbf38b18ed2028cf3fe234de77533fffc5694e3fffbd9f0fe3d1ee7068732a75c2d2f35a89833dbd4ffdec9d9048171d9c18e7e78279beb52118cea687c34825cfe94bd9e3c9f3439f923dcf6bbd348fbfe67b2fb7e81b9dbd98bd979e0e6fca4412c4612bab9fffc3612c7f59bed587235ff3af84ad6020f2e222a43c64fb939c9243238efa3c1da8573da7c2ae9b9855cf2366150ee3d1f68546f2967ad517a6fef4dc478f47eabbd44edebe188072f719d9a269bce058eb2ca7d961c518a5f40fe7042623a41a8cac4832b2020a465634d159de9ee3b8248e43e9e0700bb375bb2e272275ff0020daba5d87e423a7eb929292945c5c965c5c66b464c46861b9840061b1ac569e0cd1888a2e12958c27b5e48a4a4180b85c5c95312e2ec52825e575dc120acaa95d89279941529a404942b7a50a85369972a9f324797b94cf9227e95e5a5a9a629d7bbd925e2f294992acb0b42425698525a6a4a51556484a4a5272692b40bf75e5abba87639bc66cf158c1b3c6b8fa4d99830290e960096cd2d4500b3d09a25be079cf14f2a6d06ce2793d3d487c46ab5c900bf354c52e6d329e5487eaa12199b49e4eea237f6d3f85669339244542914bb24a929a6b508d269155e49284e22eeb17503a14923d4aa5e4ac6dbf1ee8bb8cd5af87f93d9ddcb09178d0e742db73b15770a31194a42cbf7e8c1c8a89f178d0183b835c983be436832a27d3bdc2b82f8724ac896cd84f8e8286888ea448119ab4273d45485494644f9549685eda41b7da73d27ea494294c352a4ddeb3488a527b3a6450db9f8ab8269291f982562b8e61033dfd9c8965fbc4bc1d33b9bef476b0e4ea3dc01f020788cd8fde0b0788cd270011139acd4149eef789d3536108b2c573cede467ab60e86a7afcfca5c8db5bb6750ead845ad9c4da57e5d73b0bb6d8d099f967db59170226b91b76f253f447a96251e525f0ef5b4ecc857c4c1ad7db01107b71a9bd87fe46b7b077ac10ddb276c9f59e434451c84e234b56223ed5f67ddddddddddddddddf565fd883818430665284e89dd43a4881f493d98a2afed6b8fa0bc3d8ed3c81cf2f65c2b36b51aa8835aad233a4eb34d21a2a3254e86f780d373ef4962783a4e9805ef69f174704bac2316a2958c67c6119d29149b6e6c1a68cbdbb7902709fa191a223a92c284a489fa4869ca90921cfa522d6fbff21ee07df7ded3bd8759e8a5ab6aa2274334a2a21492253f404142de5012af22e3e9a0cca0e58062336386931a142850320780ee15c60d9dca0f00e48fab2e913f000080acb2be8dcf24a771010c9fcdab584ec5611777640a79493aefe15caa308b6691c352bfbd53d99ee66822cddf7e4a711a170efdceeeb0a606722a79dbfe52d59c4c0e9b352a54f6b25f3f1c357e96384dcddb4b20eeb73fe23461cccfd0929ea023c944243b92b2bd50cf134f5284b4bd03cd9a4a377595ee7f72e84d8472e8345a0e1dcad1f6512987ee3da07e875dddcf4cf314729a1054e2f3b304a8c32dcc5cc3d6e499b2d93391ccd9e94e6192b2a97612e928498a1292593472d2239fc82149934445edf20b9a0af5845d356faf82c783aa6064c4948c7334322205b9bdfa05ad4a595df882f6052d7374ab56a65cf80229ca7c516b7219473f2a532e7c51146562a69e0b5fd462989f8a81be2a876239c3d1cb999fc2d3e7933d72a56eef722477ff7118fd71200f5a024adede851c96426c7b1f5a6296b7f72742c4a2d1124be4cdcbdb3b14a789464ad4f2f67ee434336fefdbbbacb368ca26d051d77290247b8ec8dbcfdae33d72367d628a9042236fbfd16f9774b67933618273908b1d8cd3c8975c37b99f1d1ec2fde43038035363738323078e59332f5184711a1d73fb6abd489e44f2a49f076214a2100528c0e4497108ea569fbedc70cf4818072d0df436fd21e4c4949a0d341ac0884d1f3c91e7c338ac8bcd8f71d5a2c8e1062f7c000587d8a4731a4d1104a329888c8a20ca8183e1a9eb058ec3f69e4e31d781837a1bb7317e31466f64fa0fe39e3e8e849ddeda7b29c50038ba9efdd3ebbc0aebf8902ffab9e2ab6e5cf155bf138300f7dde770dfe9308938a77b15d6a9912ffa1dd689f2b513e3be18eb3ef583cb2366185a51c6fffbd373dfbdfcf0ad7c755fc77dae1ceebbef38578ef7dd7bd895e37dea535e87ad74e5b872baf7deeb387ce3d38fd2ca48db30c543a8907b6dc249efe9deb2730e9539dacd93cd3b36f971d6cc9d3a4b3387ea4e1cd7a1ba8ef3bacef376c88fd669a43b3501d760948976932f98e7e6dcb6ed7b73ffaa93a7edb294524ac9038e690654ec0084207678c10e0f29e0e18b13e400840c281812c11032d4c0880639d860054082a0c312663002102c40a23af9312306b42582849041900e2938e1054260924204146060040f92508207b52396008594263e5c313342861b7028e2022ad430851435142108209af821031b82f0841f24c182a4dae487112404a108342b96aa6c61c408163481841b90989ac0c10f0124c186d9124d48680045d06c07274344361421850813d490e4430d5e10460d8a2800d393a5199a70040d5d0cc193c40bacc84188a2a3091c01085134610a1334512274819d243c609183240c41862c9498c1081a74116507363012e20231c68881284508ac5832c20f4044f1c4870284f18416842b72309b22881f51898b264f04b5200c1818b1830e5c2c61a208551819c1040343746183309660840b7868d2400f4be001054c59d48014740125848a963002073df4a0450dbea8e28626474f6a4b000203af28c8c0851454f86881d4012b8060c10966484c14fd102515182080155f0cc107a41ee8e0478721285441810fa898a1073ee8400a9f261c66b3167c11812c92380210bc98d2c50c40313640053f4fe041184b7861829d190c6c3862c2cf0e7a10831ee20eb2a03d51e3228830a45001085c4c595a810b8e60ba81bca1881a8830c410582c99c00d31f02207198090850ed5063f768e380248054eece0045e4411a3f4d0e304309ad09082ba04024cc021071560c1f3449500c060882625f400a8890695871fd1ab8811043526c26062871e2f9e70a41f16a0b3f838f1028d43bfec7775acd60b0c3853a306470e87756cc618bfab0831f95942c464d6e4c3c44416549bc12c0720a5a320da0c7e8096847e664132206a3390291dd1847e6640c87ea8b98048e9e8079a0baa08fdf0c302a4d90fb21bfcf850cb21c9a7c80725202a37689243cf922471ae80463e1cf9c716ff62d0ba9c184cb89c200eeae9a8a9a9dd276ec6e975a919931b9c8999a901ae56accb6a752c2caccb6a75ac16cbd3c16a4d3b6d175b605a3c1dff9989dbb16e1456ab8b118389dbb16e1456ab8bc1c48da1c4fd9756113198b8fddc65ddee32e15ae2f6732f2f29a430a34ccb444b85ffff9fd19211e35b582cffbffa95fb2afcffbb5898c9f56baef803451cec9eee59b5808b968bb75bb9be6b883969dd62d06a4e3168ffc2baa09d9d9dd3767665b5626191116574b6bb333164c86875ad197686a763c2b06e8bd57ab92eaddcf439daf565ae957637ed8a5d5d0c5a0aacfba2c2cfdcd3c1063f8c0310801acac1c0783a28ed2c0e89c4a41dfe3bb559503a36386afa6b70d8b89cb3c6e671e4f01a35dc04ee8295b0cdc9ed2891d2a61f6d6e361c638c31c618638c9dc4c94f055dff7e1d2a55aab78bb30544888332561c444847fc31c6d8dddddd1dbb3b76bc486a0de45b09ebc1621b977f4a1265e8d8c891599b78a6883c72c448ed8fd4a851a3c61123456a458c1c297264062607482d484d08911f3e6a2da4224e2323930e524e8daf4f4cafd5d92589384d08ba376193c3073623361f674a9e9fe341b1f93f7ec8f381183d4125cf2f22db41a3277e668ca4e83e25c4addf837d1bc72dd9faf9d26a39386514a2d05e0bd1c1d07fe3213d378e51c6bd66b6c0f937dc8ae91077bb562b01f10fe2a01025202f2fa4850469a5f62b04060606464810204a2ec4e9074488cb3fc2f11e37404825204742822411c9010353a34eb89db7ba7e0f07716ae3af7ff33873c6f9f54e9809336126cc74af38d56bbdc1a9cf6addd4fe5a69eba82d74d31c8da410ca618d2c6b13eee4ba534a29a51bfd6dab5bddb66dc361ad954aa1248731411011e560c9d1688a219428108eecbc349b1c37b4960debe6e62607cddebcbcbcbcdce4b0a1d9e4b8b1b969392881b8fe58cc7c6f72f408712cc181e332bd7047397c7982ace1a07f74f0c65f617f8ffe705afe90fe9bf9e1f8e3b8d00f6bf5af0fc0d2da1b5ff4c37150e5b5a042eebfe98123e7ec1b9cc681c50d63d18dd344e9398c4f64f93bfa061637ac51a3288c49354e4313224992d1084a53962c25da03ae9b1886fa30d0906fa4955d51467ec433620b4edf2986a1be119b674419e93d8066daad28239fca6fbd64295da6b81ec56809160ed410a96182033521a046a8910d01edc901983c64be3b4d480426dbef2c1477fbf87266fb8010746ebc610ec0e48a87d05f0117bfe5208f9729222b4be944065c0a47cc1490b8fe292e85a1fb2b16eb658a99829369bf97cc3993ccdcddddbba779dc7bfb618d4d0e1eb1074e0e17144629999bd6c1b067ed63bb9fdfb3eb388ba537eb1c8c99a9313b47c2e3e0aca9097bd6d4b3e8d18f743fbbd99467d7755dd7755dd7751647366283384d0753fdfc1ad8fd95d48711c9e6ab91af896f521be876e7e479def388d9dbc9f50b8de41afa5b14b7932b87b26f234f2d07bb4682d54a58bbfb9cd3bf3d147011c6a98e112242866af24a92eb3f64b76ddbb6cd613c4cbac91140372a0393ebbf12854beb272a1e5c1e3de4206d9ad354da4653017177728475ac3e89dde7d64feea90f3f7bcbb7267fd147e1b069ad0e87f753386c81ef60ee5a77cbc258c403c5d383a42a59f2092942dfc8119dd4539ac27e92d47dfc0012c4ab177190be105b1b926fedbd3e6812af41d5abd75a6b50adb5a9049d9ebefbb812a7a9b05ba3e2b015d84ad5245f2d932a99bb9f4edd473d1d9dffc9f34fa5bc6e5a4a6bd6c77683d3838220e8ed4537c1ba0cd65160bb86f296a0f70fd770307631d2d48f2f5b91b682a3e31865c200dcf78fd865f2356f386ea4a6313fd6ef4604e7bb4cc218105f487da3a669cc9f3df0a122cf09458f0d1b51667ed857e4f92e4de0b826d494d0cd048fee2be65670b8f439eee9a77acedbd1a94775137d89146dd09fb8e97acf13bdb8ddbb471adb8e992976599499bfb284d0651cb60ed6e9200cad6a24a43cdf655fc7795f1771777ebd998bde4dcc2e4a8dd4b048ad6ffd43fdfdacbf4e1fbd06ec64973f96b8483efd669ff3ce9bcf6ae2766fbbee654e13ba10f66b280eceefbe3e0ae3d220313599069e68a36944b422475ac603a63c559067bb20cf8fb177c873da95af899961c113af60149ead2813e360ffc703c641179a01644347bf12524c823243d4c2916b843179c63bd2a4a40f090a118d4acecee32095f5ec912ffad6725d8547ba105126eca64cbf9ba6cbe28b7e92925276a64cdf85766316e20b9b96e9378dfa9cd362a7b51255a6df3d94ce39a750a642b81039a211124e90c3560e67329d2c3ddc4f156d4caf712750ea7193c3268883f471286d1464ba81f8a242a20c9d8f7a5b9700051d21e1f8f80124484ea5f5f4f3846393e3a607955bbda1428e8e888826cddd842ea1ab0e6a9f15dc3f54f6aca34f396ae334bffd53faf537dc9e95bc24c8e7dc48b4e17d7c10441bd1c6966d4db94b3756beeec7c3043551cfa1509ced50df4b94714a29a53ddaabef7d29dcf84bbebd1387b7d5fa0e870f360ec19919f99447d6551c944d54a6c4b8e4a05472502e715774705b2fdb4571777d286ae6345b1224253f4eb304a8df75924a94694a8d4a95f5b75aa975a54abba98acc79e4acd4a538084aec3d3f5752195dc98ba4ac38f4a0fa644aab756fadfea47a3a2a922744414ed3bd6ce24a1cb6f272f5f23d89f42533f992bffaba49c256b0fbc83964ab3a3c6cbf86b4d29dbc759d3b0e5bb249a29ac9694e5f7fd7a4ece9897d236131668bac9492e3528ef171dc18e91b2ef3280bf22d0e2d1659caaf3751467e51bc54724b99e73190a0cef2525e6ee56dfc6dbc8178565f07d188be9ed2dfc99ca781c2ed59efe21faefeae66c7729ad8ddcdadbe90aedee588ebeeeef23f57238932f355106d789e2a88363a2fcd975292962889712699d14f367db20a8a76b249c2260f0f65798a65151ef5a74ce2eaedb841ac70d8eaa1fe711a1e31af3a492f69a1fe5016e5f973e5a3d10e6879fe6c4e25f3fe94f2ae569bfb9cad429994ad94b2faf97269cad544e2e0fcc9b3fa15eed99459538c33ce996c9adda46925e51429e51429a5dc3a499e549a38cdd01287b5fcfc9ec162fcfc56e23019ac97f1b54f0bee1ff99a3f599d4ccab343fd10fadbb6f188d9f10a4a843da35f68a4fd0b57bf3db7fabbdac9f46e6f23edd633e9ab99c4619c92e434b1885bf9fa251479e4b0204080248c88bfe64f9ed5f6b50aa2cc7cd497e3e00f07e7af9e7be51eabd58ad25568f38b83f38300717082337ab129cff92e49dc70cee66c06f58dce73c94f23a2cc79c752dbd3c1f28d24da5041dfd0372aebe70759f99bf2683aee51abd55c3dc7f2e95881078bf3e47e1847aa2165f652ee5ff95ae8eb260ef693eebe9ef275ada97cdde42e0b8a324c3e0fa2693492bee1b22c65decd67bdf4c8711a20b30a9538a567e6237f78c0766432998c7efd53e8650ba31d28c9f35fdef094a5c43dd497b362d05f5207378c53843059be03a28d98651759e6709a1a07258cd2f50f676a52c6e538487b3848899e3848177017d0f3b1fbf7e36162df0445eec9f670dffe2643adcf75fbf6326f1557cf5220c6aedd2e6e207f512007697864c90a0eee4c3ae223433db54f3f087d2144a8d115331f25f4b9ed0b5ba80e9d9ed068689b6c9baad6ee746aa1adde384d687b50d68c3377908f9f4b85248d423edb534aeb86644aab75efb66ddbb655da65ba3d7d22f47dd0ff413f08a502e4bb91b095d3e36a3769d6de4bcbf45d9c7063de7e0275b9c95090d3b4c89fb205cbadb2bcb300b5929944860e46a64f2515d9c4c2d3e334489c86852569dc68dc523e5186be0b1297859b2a913f3248c5fafaf188597ecbe7921f7365d17730a20d225149aeacb4bc8dafacb05abeb00499f5eee9d8565fcc2b2721966ee5ebccf22796df3ef92cdf09a8696cd2fe38b86ddbb37c316fdb8a85b7cd035b5eddaf33cbcab3beed5974acdcaf9d9d5ff4f972e83e6d824c9feb221d28fc1b8e69bbd6cc3b323a00444889024a5ccd300312557450451513d35fc37fceccdfc93326cbcebd1d313584c8d0032747dee0e0f4c811e234e14d51ccd7f8ab85e9d5c850533d1df6ff3b1ba6ace4f01253bf20736220ffe35f5670b0165b5c1d94b191dc4a09cef6947d69310c8f8ab948db5316d71ab94ac93546998ac3fab362239e77d81c8d6c99bec42b48b8f1abc0e0709a1a5512d7c54287f1f4421cf4f7203c6a486ce4880ead5123c80e21416c9cc6272ef2396e7a9c4ed4e6e626470f0702242e596badb5f6bb7b66dbbf73fa66c781561e227fc6c1e9e990dfdff4e4dff47090d5c5b472378e71d0bd896d66b8211196252f9be56dbbd239299db3a507b63cbb079da607d4a3b0ea0a597777776fdbb675f7d15127dda08f967cdc09d5477dc30e40854e42dde0d4849a12ba93b0dfdd75de7b9e57b4c4c38d04e61352ecefbe72f5b93ef29e7e08f651d75e9f9e5bb264090c5d94e75bfba88f47c7c53e75f7d7eeee93c735964532f79cc77ded33dffec9da8e42fe01e3d688e141324b52637383335f0211c6f23366c145de6a548a63666432a2e0a2bbfcbc29c913c80a4adcedf3afdf4e8e18870e4d238622e9ad0427a7c52ed40fc1f151ffc92f8cc9f28f441bbff97526cf18b16d2d6276b26350e2b962831bceb8bbb5f7ce1c729d25b863c773ac82430937cb94eb3e0ec94572c34ba5841c4fa66fd3a19a688da64cb9af3fab921c5229be94b90f5bdc47e7b8c7a143dfe8cc3d87438914a7e19e3e55a25d670edb38f8e5902bf7e9b31c76638d1a1c3726d3d4100e87928efbc2112c65aebb7f3ba1ba6f27b70f65d409e3d0819b55598c28220eca0fe2a07c7150be64594446e9e3843a4d61b57af86b8bd92367fe09e6bdfc1758eae5830e537df7618d8df6c697bf8c43eccf79da594189fba89fa7afa27e3b6d9f119a2b965b348dd6bc15072ec284b5bdbe9028339ff31925cf587de46b7e94b2bfd62a831c9ca19cc99e28e3a3f4c3045413f2a6a13c2b2dcf9f45ddcf8db97ffe9ca2b83f3686309f498b3c6713fa46fd9863c5add434748836246e224499f913c6e7c6b8149b3b52e63cde236792d294e2b0a61213938b94c25914569f3cbb964abaf2a977eddab595e67bb411abb75294997f02e3761207275765edc2c5a51ffbc64a96e3df1e9a24ac95fe95ec74c738a759f1a4386c59ab6f541f1d5021378be46b7ed56116559fbe318f2a15797e2a06d77f49c9c14969adb5ce6f2614136ed84a791631b96c0e3b4fa53c5b697e33cdefdafc6e9a5f8fa4b3f8c0c568c3696c9e5f97e57733d7d166f975d9650527b4bbb3ee73568fd2dad1db51f64b2f28a5d4dae0862cdbb6ca8d1fd6083b4ba3b32ccb62972e2e6b079df3a68503e400c5084458d2b17582c965dd7657107726069c2e4e5c34b8e1bb82b82d97d0655d075d42b7c68c1037e6e782ae202e0c93fb0ec677e182564a699df467a5274fc7bc0e3a9e4981c14557cce06d3d18858b30a103408aebd57feec48f9e0ffa8d5d39ddfe235fd1bafc7e7ee04c8d7cc518fd626cc698c758f421a7c5f7a2f8b6e9c6fff8f13fbb908d7fe3b7e27ffc0813db65165cdcb931372ae2d9d1fd5064a7c921393455b97158ab501dc51b57466b51c65519e5844b8fe4cbdfda7b5b2d7ab47d90d3d4a52d8708ab55c4974fb421713dfa6a8208ab48c497cfccbcf22b87b4a9cec0c2528568938c6db8f270639eb5cf1ef9f29f79317f6408eb50f677e1e2f6d7a168e38878837e17430b098b5548be92b8b58bda45b4119bb6b6b89f3fd32a310c9d3b1eaa50f607caa7286e936823e64d691665dc3d49bd668a32fe2730ee7c32e4200f2ef340e50914652650ec1a8f72d8e2866cb892269f38e8a8a62bbddb949a867fd4218724cde991ffbf536ffbb91ae3442ba088d5e010e3fadbdebba7f57d6353da881065bce5440e390e0234b66122a036a5bed1b594921b5a210c31c1b0c3b3dc5231b04a2b3b7031cb70d4c83971b434c1050746e88084121b906841acdf6b0090a2762361ae9caa5371fd8afa9d583f8e8479123e92af7ed611eedde24607258a0977fb5c39dbe9b7df89b5347915af35e5fed3d7b2df7b71c3d6377304fa3c625e4189ebe2644a8e1e0f97ec4d0e7615a7b2c36bd265b87f83e303080a8c5b23615e6b229c374ddab49f5281a1e1865ecb356f72e9e2ca97eeb0486b8269ba437d83025112d1916adb7ceb1f298db058125e22254449681754b64fe518c511b8c8d1d33193371cce64dbc58d464d48099ba7092058a42571277c72d83db31e2766487cb62f644d1eeff1bb1cbf9bb9e9f1d8e1f18811ccf6b94827eaeb727b3c6294a98fc231cad88709e2561c6ebf3d95cf72b05d0e01b51c8cc2e5b4b8fe2e46b88e713ce1722e48b85b53eab9a374d013982ad6464d3c0122d6df44bdcafd0d05e6b1debca7bf675366774b20d8fc1172d9e246231e59cb184f2e0c466eee144499fe4641a4d13130dddc3fbf7d44dc27c6c094e9ce8d465050e98e753482624aeeef25bdbd8defe86c2df6f2cc168721c83d14659cfbe8bde031ce37f9ee73c4c1d83d12d63488af7e1f5a43e423bafbc7712371b051b21bee64ff181717a61ba5ea074e8a6bc3739fbe3f662b5f1d532ba7477d8cb95ee8befb188b1fdaa54c3f3ad185d210cac28a85151fb2bd7f7d23ee8f43e079c87cd78ee8446d4974a20ba52cbf155b64c5f542aafb94eb85184ed1f3e1fd09bb724edc9f2cf6211f5df716a352d88550dd67f3ddb4be18739ad3bca8df8ffa3dc99536a7377cf474c4886794f15b1ec17577e7d953c0e45925f7fb0f9d1e86c4c1ad79bc650e6e45f4fb091fb13e378c33799ba95183c388d36cad972ffae16b7ef8e5ba1261a8af7f51f3b34f3ffb1088a16fe33e0e10b3ed1bd75bf71187a530eb592bf192f236db67ba9a6bd3ba89a144709d83f18d684a397a2c4cdacc8c92a53e12b6e1997790521807e9d7ba0ed27ffd110ce3201d9a6102632a1da211151d2149515a62aad596f39d46a268eef388241c462661a7f8b2128827e1bd99f6388d7f04e5a9a8a35cdb4adf65f10ecf16766b3f740990c358846690e9d3263fc4e8d3274d4ec392e95322a7894cf850cbf469117d7a64b7aac305307a503fd2072472bfd4af76e51c47e8b5ceb771f78ac33264c41969b4d3b26e8bd57ab93366e00086755bacd6cb756965277676d64977670aacfbe2c4764ee28c2e4e6e38e7f7e100d4c41963e724ce18593dcccf45c40d675ed1d0ddddc234d2d4c401e7723a920cdc70513aeb31891873709c8b1307387921ca04a28b933b5b701925c6e81225c618a34b9418638c918b33c638a34b941b639c31c61863748912239d31ced400572b3a636c459638238db1f532edb41dcc7f9c91469728f465ee32eb468a3b193146972837c618af4b941aa01b9f7b7949218549297d1b49e38c2e5162a45f339d7125091cc761eefd5f26ad75dbac3d9d501cd7759de7a554aa7b2f1737fe8aa7a395e38a8535e7a495b6742c1e6d3294395617637ef4498e115d868c96415b6274325a2dad568bc5b26ab5566eabd552b5525eabd5c22111146d5a074f524817272e232ee71ac2c5899db1524ae9e2c4157463c8780183826ec87a172eae7f0aee02c2c589cb07205c36b827a2d8eeb1dd63bb6a70633b8e1dc4c5166098c9127b40957b98f2e5ee571c25916204475408890a1dddd450a9916249154851921c818086a255b184a27594263421b9975a52d46644b3a69e263df1880a2aa8206a2531c262ccc15fd9e18613365fb195109fe80112663cc8c28818448941f061811450ec2007a3a658a529612d19400a2443a0f47f382c4a21de99524a3b09bba1450ea00009914405508c8251f4451853809102246831ba84c42153cacab45699239674a266701d78c75c638d9de7f7d7b775083800c143922c8bda13314a118e8070810b9a10031baa12b93e587f46d6afa96fc3946faea0c7396f4c1ab2cc36be33db8bc0452ec670a311154d64d93e175d38ab18325a1fbf35b333b7e6887cb166bcea67601d262a9ce352fdcb97d5617dcb4720c65a3ad7eec47ea6e55d3e0231e682755ac2dbfa7086c6628ce5ed8ccfa57a1cf9b22a0c430462ec46beeccfc03a367827f6338eb265fdea59980907ba57b9388cc3a3c3046706d6e9588c97f13bb1f6747439ab9ff1ab9ff1dc6af5ac5fb57cc75a7ef59cec8e78ec198e0f20428a441b53e5aa9f9eea837172439be38dce2bdcbbae23719ab07db87bb977ab8252dbef25fbcde43057cc7ed7ec3715fb5d258b6c57d9be4f4992edfbccfe45713ef6fb47c27898d89fabaf63465424c9f69bc86954c8f6db89494f87cc9533e3593fe359d8f542c758df31d6cfc059fd8c6f273603cb5ae76241f5f455d8271bf1d5279bb60ca40597e55befe1f0b06057d77a9f6c7ff533f9b2dfea7399abfbd6efc4745c1d764160f52cdf3d0bd661d2e11c969a7cd967c1ad64638c65e28f32f3bf28335f068ef2855b5a96855d39ab677916acb3c23b31fa4724acaded59fb24b956f5f65b8b086bdae9ed3716d106eaedb70ffac67d9b7afbadc4bdf5de36151fcec0aa576198eef3aecb3573b4ebbecdd224ac93ecdfcf9b86ff5df9ac7deed4a5bed951afdb4edced3c95e78098559f4a7d9d57abd529d57d573bcf762954a7eaba9bea566eb7ba8959a5ea584ea9eebb8fdef4befbd96e47fb69ca50de7284a35a24bf42a9493f594eeebb7f61e5f3cfe91eeb309938c7dfc33a3a3b457080d82829c5a31cd57b5887c97c9dfbaa9f3847a7258cb5ec9adde79fc231a4de53d83f06d70cab5296df7d8cdd9f912fa94a799f7aef2fd6b9cfc22aacf3ac191d0feb44ef632cfcee632cf5e1f59fd80501d57bff82ea3d1c03939c96677d0cf755ff02ebbdf7700c2dcffa0aa4de6bf95cf37eae19595ef5ad3ec6efc4ba8564c85a58a77f66423fb29ec56059b58c9fd6bbfc4e6cc6c7d8eac3166ad65fd5b35e3206d669a5bf588749cefd16acd317b3b08e0aeb34ede8d2ee514babfaeb5ff9ce9ffb17fa57eca37e2766e32ea51f0451f9e247268b75bf7a26392b4cdc7fb85f611de7b0ffcc429771b39f182f652b2b2c4aab185827c677392bdf619d18d86bb8868ead6a2e335a5fbc1f95eefdd06b2d1f632d2bcff23bb1d58752d67a7f1d2639dcb7bc0e931c19cfbd4e0f03623172ba6f619dd633c969f90eebb4700bd671a11a3a1603d3581f63ac0f5d88855df577624e537d8ca9b0ab621c9e5a5493fc48f52dcf2487f59d0b02ddab9e497d9d594ef72d5847ce3a2c59587e588b6ad1ca17e56be2d0e68bbb0fe5acc33130c9517df73116ca9f1854d89593f2bec3ae177498b870ba8f408c75f885daf40224ecb043914c1693b41e8fa9be042be0e0c6d8d1114d3e494111534cb1a4c23194c0ab31b9220748cc641c764dcc82cb8523955c38b29cabfa1e5478cae44b7ef7dc87f248013cb4d88cdd2d8680f41345a81671df31ee6b5229855d39dca79efb1476cdaf492b1c7163ccf542c7baef58f7733665597add7bd835314e2d922ff9f5289cb32cffd423cbf237d9d6b3cd369f4efe4f942fd794f2e74ffe915348bee4dbef47fe03e1c6182a1663e1fcd97e720c4009583bd35b3a70a17ffca880a6135b2597da2fe79c53d22b542e85c04dbbd56dabd37d4e77993a02d5b474cbf3ed117ab2d6e28d04e0943e5970fa6ddbeedcb62fdc6c3c9dbe7a2cc8dfe4c76cff64a53c5929f1dcbc163c16e66f2e2b1071e953ba516c044abfad3d173c16e6669fb35b7b2c4cdc51075b20411e52e322a7ba0ee3396d3ea5a512cf1588a873ca3ae7fc9e3e27a55387ce59bf526aeb0e7fe93fc49f5259657d99f6a993dda9fb2604673955e7309ed37ffc3ac49f7e3391df1e0b4ec4f58f799e9c059741a9accbed1469cc37d2ca9d912c4f119c747ad3e7866d94991ee36bbe9c12f76401176b0f19711a4ff5de49d5aaaffe4e75d088745b3d79aa9e67ad17bd3f7d2edbc340d3f0de469e3c0fcb97aa232e27c4693afa9c8ed3a4defd8f380df7aab74fc469ecfb7736f5f6ebe729d5a350a8e711330abbee5fd4d3b73b797e2a55bfea8b42c8db8781a661a783dc737d025a0d47997a5dc7a150a8df50b1a863c557ffcb114cd2696e2028676a72487f16e9bc42c35d91c19df684aa5c279f10c2921c9f004295fc4410906c3fb3e7d9cd6e744a561317a30c17cab9f393b76fbb8212d7eb9f6c4b21e4fa199199620c348da9722955a6c08414c70ecbd3b773823e5bc987a2bdad5f519ac9351f9230e9d57ce211b882c3589443598b341f5903331214229a94425fce297de48bbeb5359667c11249264929d28785ab9047124926d524cc8f882ffa4c18c9aae8f9a265345a11eb6845ca956d63b1b0cc6d93356f92fdb75ad465d3e491d29ba20dfa281cee64548ece699e78114b6e6fa362ba210e1e38700edf5c7e6361f9186d6661c1a10b8e4a667c0c1cda960ce93efed34ffc459f3eb1295c7161942eebc37e92e9dfb83e9c71f9108cf1e1b77cd862a13509731f1cce802e3804bf0587adce4a1f7fd1ef66e016963519b8b682124ddc93193481dc47c2a665c13e533c1d73d6eaa4b14c9a8de3b934991c9c326b15d758704de29afb4499702a65fa4b93e903a4f3a8b4ac9775410b19aa1100002000f314000030100c874422a160308cf334dc7d14000d8da25466489649c324c6511452c818020c2180000022002044341b0535010668d81b439326a8519ab68420f9c4c3fe84e219fe45ecdb3f03b4dbbe1883467966feb2def20f0884b4b8b73e3b473f2c838fbcb77ac380db2db3b98bc827b3f7b4c8dd8d8fe7a82cdb991d3136ae340a1b91ee97a4c28b216e871996c3b7836ca9e6caff5a3fa8c6e124385dc06fef1311c0d519fef1782d07e9583055c2adc81bd2e236ca2ba32b348cc9e37da6a959cf29407579cef0a93f64e7da8ff15648ae36c4a4179f8e69ce36041ece8aab37c836a42da0789d5df852101bfbd6b2085ba9f58e119953359517d14f213d3165fcc7b1546c34011ed9c4ad7dc7b3b690b712b0c9da8560a5c0fae7ac7e21d2586938f87e6252c212f6a48057727d4a7b5da4113fae715af4eb4aea02d99f6c09868f5260effe502cc1c9cb930f47035e819856c56c073c50999b21e1cc4af0f479b16709d6c846af52e476a164ee02e6f2411fe45fbdae8a9aa67ecedaba1829b1b155ba310a35cc4e4a89a4b0fb15056886af0898a8841168b3fe45cac483c5b34f8327f33c45055c0332ec6ed960020507403448d2b38b4bead4e13d7a0edec73a58550142a93d99f4562276605766fa754bfbb0f2f5163129c5fa45104d52e6c694e8b0d563c05c823c56d12e67aa778c5033443f8fdd692610fc2daf2dee5896611bfc7cfbcbbf33231b2fd058d98ba70493db4ab79b036e2ae5a2a650a980b1c70ffccb0baf91757a2931c2415ee5bba62e92ab3640ae3f81545f43e225365c72b1dd2a3ee01ee4ae096a349465f0def07ee30b2ec204bea2dc07db052aeddad369f01c2aa080db2e271ae73ce801b76878077749be29f9d515fa98b605593cf6586824e16f2559abff887b8f1f96a696e05aa225dfcb7c81406665177aa933fd0785bf54e101b718938f1ff2d20ebe65a2e2d4717c6002e308fbdd954cce736b81efeec9753080cdb762bbd9122d49ec3f40f4c80c1111acd07e0977c00dbf43808d5e0ce6eb3730ead42c7a02d3fcc69bcc82897eee74c09d57bce1814a2ab82fe5f9041efd0874ea09593ab15480417345fcc227bd6003a8890e4878042fa816b295b27721d1562deb7cc01d298e48a67e4593662691a32cc1158f76d2c69e05ab50471d92a6302d2725c3d7e46255b9ed59d818e7073a71a43033a71da3f87c48382c4644a8c6845188815f14765a8e5d48e1829d7f8d0e2e95f221e1ae79a132627bb350145916382363e640d8d005ada811b327e16405e3b35ea38a70462e3a32c0793345210696000e3cb65f62e6db51fa629c6fb885f1c751afc9f29b96638adb63e03c84ab18fba6d4afea1f89747a0d400dd988f1377595bd6bf21defec1bef371240a80f9fa3d40d06fe86716a11fb31c43e3e351abf412f06b2d6d73ffc588486d3e05d272aba1d9f71fdb7d59648b0fd8569dc48453ae1a90309658099779234894c02886d4c5678a6043fa180281ceb301d2ceb4c6b0698072064606358af79e69ce80d20fc2524417dd7e9d29f35181838843b31960f07685c33aa140c20c510894842d92e8a30f74486b016daa57e7e2b608a30664c303223d7e18cb163773052bf4c3098ff8565112e0c10dcf55e1cc02074a33a0b532606e95b31f9cf5a6d8177e58037adde24c2491a78b0940dfb58ba7d945a9ee08e1a44e50b77e6ce08c837f6bab38b0c441d047406aad9188821ded0c30174b29f69ae62e64037d58133274ad685c8c69277ac6fca64ed140f627e8b450aab7785fc601930daa47d5a69a5646e7894ee192a4d35f95792d0d3e63e6ca58baead883584499ad55bb57f3f3afad2de86d4b48099b1409fc21f7134203a2f463b004e03c7961ae08e610e68e9c57f46e9ebb6d4697f74ee7d09a8c31a54b9390c5e558cf54033610cf6d743945dfb42ed86c377a2ed28c82882f42dbe99856c18d06252128c97d4eab668542ee8a1330028f6fdaf13e837518285e6860ccac9bbe7fe0d7ffb3ec5de587b445e6355c5c549780fac49ad0854df80043fa41b7b1e006566c38d4f253461870207a8b89208c99f2c9b1fb30fc42dc54815b32590483224df81ef3f6df33cdcec21f17ce0cf8a07dc0a7f7caf646fa8e9a1971d46f3dd55389f99a3189e7f655fc9c825e764d75e417d9985aa5df90a49bbefaf20c0947806d33b71e7f9c4f9ed9694fc4d145a107af9815847ccf2bf38759e8711a6198323bdb329cea9c9b9c7731b8afbf4082658bf9992d6a03019baf75d86d9fa2ad8ec1cf2f53e649c8c7cc1c91cb8f103ceb4dd786ebae254e14f2d17b049a5abe0033482b580596177caeae32dd663dadadd967768b38d45d37acc540ef627110e6cb8b894af3770934d0d6fc26751dccb450093c3484e7a4c4564febb9c341a500f8a84c5223dbdfc6278d44072f12f37b0c06f5e1a14cbc2e06240e703fd4167442843e5f657e0b4744cc67257055d115c255ec0a1d5823961870c0f870cc43b6e1b44f5539726262b0b302685b91f189c44c2cdccd3ab14dc9be9a3a6df7a2b7db2eff6bdfbc39ab63e0add7cdc397520a7c4d120e978ddd28311425f4d09ab0948527f86e4dbc191cb823eac092e472f6f971252c8060ab2ba737dd42a5c34a6ee90e25e7548287ecec2b3e7706f75935e324bfd87b9b05d35fce630809d3e5b2f7739e1e44ac7decfda3c1fb07c4ac5dc64a6f79990df4ab20c575f89acff0143181c008b47750e819c44fe98d99f56909db172a9d17ed590627afdc108d62d6eeee20fb1faf343fc77fb10dbc2fe7507881afdf8b51ce3877409cca597b0da25a5cca59ddc5c894ccb253267c13bf4bb24bb7e00e1f1b55f15af3c9dc4ecd560304d2c14f8a11b91f9316fef938e35a841fb5dcb0fc7a156431d0284c5ef47f9d50080674fb05c5273b66ade25d52e7fb0614e81a2df69ed8c69ac36d22b9588be15eefff149073603696e91bd07de22b7e182180d8fc873b2250ec1947f7bf5cade71d8795c59d1ba829fd8b2aba88552b6912e6f0d33d0cd9f48992a8c6c59817b4fe2f46be23ebaff4c83664be57015d7009e4561551dff2e65a1fad0ea03c6f509943c3fc1b8dd39b8b8b629819aba10a1a7ea604710e81beac52c9232950b7fc761ffba2742f3346a8fb0cd9b4e6ee2a3243e797285fe2f836d4e78cc3aaf7aa33540a9e22c678e2e9b6c418e40426045b94e6959640a853b573387eb5382b37f7671ec7f7cb9f257c9f2aa8c0e4faa5b9ec92c588d49f9ecc1f352225ae609f03828c291a227d261a912bf4412476a21c19d8745822f208379a7dfaff7edfd8eef83cc359104ebe3942277c46f891d3d683e74393f5c9fb961b398d4cd5c4cd51b80176c35d6036889124a9bf0fb866da2bf99032e5479a52c39e756172530000ffed6fae656035e86a80ba28e477530d150834cb707f1bcb70935e2f3807fb72f789cf6e58364839aa44ecefeeaab0dbcbf693336a5536d6206d27ead004c845605bfe73ca99fa110daaed3cd18a1bb5e2b869111a0840d69cbc1861b36e0269d095375dc8526225e52293caea77645c60c794020e924a73a7716701c79d49114835a0185fb37a18d3ba38909b99d751c18aba8f1f3e9d7a91ae2d1aa4f2df29f78262d804446517af857659cda729ccd1c7e55c095797b76d93bd1f99c5e89ba2f0d71709d1f6ed8033a054cc90ce2137efb0caf15ed228ae7075082d5e7b498717b523d45bbdc93ac2980bd4ac2b7d6f015391d41f20a41056586e2f8181689cdc15ea3940a285a20968a0713174fefebc880e93dc363b831c875c0fed938dd7d28da5da6d2e699ed08044efbd549a83001f9b3b75efa8a8e01de46cb381864bddeb8273159a62247aa0b77a2ce59cf8ca26a2081cd03ae05215b20806b1ef05e91e59ebafdaae80c935ddf0366c7e729922a7b593835698345a084fd3ed02c443b3fce90b44d814d26dc445e4771530b2c466cdeb49e4cbd529c4c6c1799850961b3be78bfbd3753ea81567df85e48e7bf2903913d9c671604a0cd7ca7c095fd67d263c446454b6b6b3de74930e88f67aedbf3cd02cda910b0a6b39391d79ce43fa9122b327742a3531551aa54d11a35377100428a410a58c185b58206518ef469e2caedf6808ef4ce164ac790d094a5c2c422fe2b823be53eaef0c58979116b83984aa51b02da7c72bc0dcd2cf527b1ea022beeef3e9e60422abfd3103b5da460c852c6fa5875fd0183c9b85aeb64cdab1a6c360fb8d5b2af6fc919210351dbb4160968a2ee4e0ecffb0947b8e07070aa90143800205c66cf704839bd8182b62f93d01fbe0b9478e8b37b5b9b3acd441ff1db2763585b9c9e61ec0e70a7b302abd6782838263a27c6aed9cdff54f9b53df82323bb6e7475b681b732281760b38fe45e9b3f7bdc754d8d6ac3aaa355a3d03c3a2cd6397272af7ba49db1d8ac9237cf08a1c453ae816c0ad9d25a8275fbfe72a0c39011009535234528f7b0184db02534bc3b7f43469091d8284e5ecc2d6fb88b9015b58b5481e1a86789a545bc0c9d5767dae9df29515b2d36f5de592bed9cb597384c4dd2e3d2743c4cf50301c6f1176a2d30059ba626d5a4a117c6bc50650c3d3b8c4b45fc3ecbb5aa09723bc49ed8d7f530eac2bde14cc424e41d0257604e7b221bcf80128a8397a9d0746e515ce61d0f5a1fb657e949b5ccd7c1816910863f5f372fe78fa423102e4aa3ed5511923d9e195514f643c123356c86153d90ace0a51c069ea2cb2f6ee7e9e861ed21e9a8de6be355b9edfa0d4475a9a2eafd09c972e8335c339ac8ac32afe00e3efd5e040c4995c30fc2f2918d0d38dc5fae39c55b7fd926d42ed681676d0919c02d0000cb7b653ee3be832f3138d027851f91a7d73ef30b206147de369aa11154495d623a880b869969c8d4a544744d8bd6bb8ed42009e08692f699e29ce3792c9d40b603327742d0e2226952b061bae44bd17774c3dcf0746f5b7dc299143bfd5a8dbb4b8f0268ef1e798c05805d885893af2adf2d99adee694b4856473a622d5f09a0fc62c16f611eee00123e40d027c32f9dfa0b6ad9345a570b300c441689824fcc1396f990d111cd545a9d344bded3302c3aa109a7b7dfb492c291c6a360cf95453dc4f5b8e17b9a245638056320df65fc19077099eaa615ae8e83bf955c69103e2125a5c0dc353808056669483d708e03c9807ea87fa95b06fc322db320abb381b50c07ad96ac539730f1483252d3d6bf231361c4162925687cd251e832f0587ac76ea2bf8b9f90bf0f4dff92a4a86a178f1446c88603d1acaad34802cb226366daae30c2c4433e640a2453b09228bcc49c9c6ba8129b2ce093cbc62600cead1c50e5334fc24a67f1ffa82c57d5e2538a5d7e0a8fcac65cdb0d94852db5cad064de9c1b6e71c58fc3f362ae3d2b3cd787af5e94113df65a6714704d678b2364ca47ec1945c939b7f973c7820f3eaa96395d7581858d147c2f6b223bb8006cd4e32c1f0e0d4decdee1d5972858597880c137c8cf379ced020df3683e74cfff4a2d0bac3369587570656d2eeb5b05542409a0ea071bff86cd059df999f8c234bb134e42c0bd1fc27eaf47f5a08992b79147e68757acf22f25c0fe61074903707d9768f77e8550a78a0a7a6af0d1e28a3a0274d686d8f9efe4b543c96d9f2368f9d6ba508e940d5e820484e6ea0b8a46410150ca97008a7f4dce89f095909e2fbbe54d4fe688dc615667873780f1d8188641c14fcda992e4614cd61ddba53dd156c7d7ce702c8d54ffe124afcd4d7e1a6ac586538e4c008703179752a157b9cd96490ef486b17d6fa9c5e9ae320d69057b8a877387fd19ea287e5d893f02138da0b0040d3aacea66ef4adb19ad2527dede889ee94427e8e43b58792e0a7c725993e9a4e502a6df75d820c9f40112dd67aba027412651210ecbf9750a77a0e1dcfc3b12784bb27f0560546d76f654c07d631a2343452b58c395abe0bb7cc7232845b6c022695c5a7d2559291c5b92c4f3cee50ebd45b8065ad0982e5ad4ca1d7caec78216cf89b8b332490aa6e04288061c17572bbfbb2e331cdc76c4d3729cb2224eb62719280b773725aef0aaf15711d19671fdf1791915ef7566eac28d98eb222eaa4f4aea5bb8713627bf26109ab807e37ea006940c039b1b6d3d3a8b044ab7f9a5333b78c4872c880da4d855c96beec66b243e23a6b2bbf76c787c84c15f7321955f2da37b68ec28b5f89af96fd260f6501200fde2ef0ce63d6d74cc1a81a4f50109dafb24d55c9a544dace0d516394004f0f64400b958e1ef5a8cffbac647b2ce7a1634c4be4eeb030383934aab0287f526ad52cd9a98c85fb76c28348c18828e7bfd6e6ea0fd85a317e0008312c735a3c19264ecc12d129ff759016ed303431e7ce59cfe5b3cb369032298844ef0ed53127844b97dd2a9e46a8cd410ae1c8250e0da83655828ac11bc6d678f926f8afa12bd632d300cd744866b8a07db4f50f0ef8b8e9a033eda8cfd8b062f9f0867242085c951d12f44424f6ebb1ac13a565fd12e9322fa31c33e73ca91d7a8ca0ac58e872c2c3a585d005c0e87791cb26b163647145d02c253106993a6aac222849e64e67b311fb659f0a219b3124b1dc77b0ae2401af054c58915586d36f1538d39f38b7e887fa81502878a8c0f9a419ba9d9c729dffb2c42d1ca539c49e78464819452ff0371f41078aa5127f58fb4119b71b47624f153d6db187240774f2155eeffb04b32e8305c1f5173480e712e22c4eb97707e09681b8e71e8ec04c945ddb7e274929bca80dccdbf438e3de85cbe76df96ab9ff96b8b2a2524c64a5f39bb4d0908f0b6d041d9983434b7917930eaa3d87c8ea7eafdef3f38ae595c9135d0f36d1ebbc0beea4afe2d0fb6626d5d1ba45cb8861e5a033e914cce2830d229ac26562d9963b7e8d62e10a5dded8fd0223e4623e41c020729cdf993f63c881cba6f59ca05ec8e59854a8139482b5d713cd8f317e82d1219902302c6b8bf609fb3cfbd5255258652a170f06554b8f08642b3ad780afd100055fbb74f3467cbf88361ae16ebc333ab1d46fb5a6c0ae932989062a5deb99e2676e751b52bfb2e55742b440acb1085932ce5cc9df6a8859c743686643043df302f61a8f9f1d80f06b4e54da97075b454b1c079623c9c73a26958acbd7239a5f3c394230e574f408353bf892392d00d2bc32f52fe88e5f95c3d6defcb6545541d7b3e5fc882751e49e07a88049f5f394d53318991e554fdffa7e46eda3760e7fa67bd440b9d82d4220386fa43625dd823e5fbb0ff8dba1c57214246170253b7cb526fbec65a516e766a4c12020a9fb7c5e34498770dbdbb451293f961d67844f8b3292e20062d13bbd9da2b908e1cf5f90fdcb95f0aadb000e5440b89f19a3fb200024f30c0e7fd2cf2a95964c2e151c38dfbffe04cffddc0ced24e2f83f2e8f4da7145f41c201dcc3e3a958b13c6f3f9b8e3a142096d52554671f24e096b5da495631d89e2faa73130fc176aa10c01a7306f2256dd0db6bf6375d938c61921793466d89529c8209f3ed666b34cb1bc95e91013497269b78e0ab0916ba570a5ef17e9f412af0b1b0848ca337d587db277909cec18034e1625a9c2c1b1b799f197a9fc903150d64ac8e1bcc5dca50f79be4514255515f28dd9b07f663f2d5697f221d90a264c110359e8f396a2bde46e882b72515c4603c32ec20a68bd2d8fd79aaaec60872d2148c5d363471a552cbf689c5a50dbc79fbeb22115fc3494dda28ccff5438377edff23d333ae3301fe039cbfa45044ca962a26214986980f71ea999e1a9bb46bdd49c18f3fe4f0e3bbc1cbc7d3662d96b6e2d47132ff7dba64d39dd77122ba32dc12a83491c173d0004a0aa2fee212b69c75d9de3288ab2d41900cbc871a3f06938422ac21602392ea0867e3029a618011d9504d6f39cdc0929dd247b31eae2937a560e0b5d7f561d3f0f71e25a99b6f854dc93bad295308cdc182f3b984299f52201c722141716154d532e05572e946f88dbbe9eab832a7abe45bac5d5971f3a5eafc3dd2bf457a291705ea819be3fdad7eff4855bbcebba9975c6b374e8562eebd65461d88b809e7bf9116c6a933bbeb2e87f08d22099fcdae81c12c1d19c9bf443c75369df50d4c17ae1b56c1b01206e092e942326d0e06560d6d637b37e3d0e5f92e9ca7193bc0a6bfe8855983b1bf81c50fc63d15d316861010ff2df71ce2f15f7d3cb7791d588a359aa6013eb457ad58d37c4c25f678d080b95f8bd8a3d42c99470e2563115623c77a46cbe6cf63de55fc50685035c86f1fc5b6e298dec7b5fbafe48afb18a7fdf14152d80bfb3077ece59b105f30276bb6b85bd90a707377d2d65bf3fc6bc4273d3dc363e7b8e8e97a95c659afa7216cada7d15429ae28d829a4491d3190cf9c6fb0df37a611a3b1cce1f5ebff071cc808c50c68cea8374d0446d700ed3b5b460a557c30f57236b286990fc5bbe468e7dabdc25542ee151c3eec521795453b8abd2875937b4c50c3eda3016bc64ff51b676d3f59b61cc785653a80cd326bdaecef3611d6bd41364c1888e0f679d8d8525cd9ba14204903063dd2b55ded99dfb1f0805b5245846c62b97097b0bc34d70ff23df50ca1ea0611483a7627205c949a79c24258f75ccc71d33b599ecd52f56199952b7933641fc07ff48c1491686cf4649f65b4baa17a28f778af650ede6418638323698c2029e7d746ad981620f9cae7f490576d3345cd678fc5fcad4a052f583abea161dacd4d5da28e923aad006e30eb04d108a0a3619fc43f3f3362c80da342113eadeb6733ba474f9339dd94c97a4ba537c9f0badaf954e0612d42146e0231a54a0f0bc5bd6e925a6ed18ef5358359de35641589f6f2cc042320cdf37d3472077e553bdbd54c7bd19cb07bef422939b0a0236c876301e55e746712d3a5c1c72c907f0c3da7967be801db1ebcc6701df9811e097c0cf8dea2d37918a23831b801c4af372b0e04450af4b9354f6af827d4f22c3a80818e62f3ec8a765ae2913f6d2ee810777af0fb85f97f318c47709ca13e2485d5327c1fce1e08f78b683a0393171728d583cfe71572fec66abbc487850f07fa697f8886ff64b2feed35ef8f0dbba5b5fcb5f9d7e0e9bd2676d96dee7d500215ba45fd2545806f6ff4781233eece26cc2f670a5198a3bc6d210cd3059228c25ae00ef430c9078a1a65783a429ea706d12d0bc400a6de83f2c612749b5295170c46929b8ccb4dcd727e80cf9d448d6e84a665d54de1ef747885371981523b244ece97361b8aed550d7f51bb2eb228d721c8cd11303bfae4064c1760ad0a131f519351fcc73bb39b96a27103839cc05a1e3c271edfeabc71b0262118e9e182e3d2c6410ba09feb5fd66a4dd11e237e6c667270dbd1a354811fa5678e6bef60ff71057c8e4137da77e959ed2064969fa6a90307c2f14f065b1d5c1d9ed48bdda854ab25b6c83307709c3da72636fbe309183f55849649b490f5efb8ab293c5b607dc82ec7f579fe1da5afaa9b99abd0cca3d9c5381abf79ee3f0b2c238a367a91bc08eaea59b493cfe619f3a8243951b6383238f267f4ce86c592ff7f242a52b663ed73381044701e9b5eb8053eade6fa25881bb2bc1dcadb04b82fad6f7acc28eeb80c1927f9b5072c7b49ad149d09568723d525799a0163dcb8267b973b4245f9fa2d4a29067b6251de48486ff5b1e855b85e1599fec940e4dec6c785097bb2103db47071075c52183d9a7bc75fece935593a112224d63b7a78a60472f87adb81edcb488fff6123bbe52557f53525d664d263db388e2276302ec87e83b7eb69c7532828e981ad159b570d78b393cdd1e2efe44d886c79ca6aed0158eda461747e7d09675570576cec230d46d2e3b231ea5354c92d83b4f86dfd336125418b171423948bc7d347c900578e6ff8c9743ca668633be9a1523a89ab9473ee23772784f7821e094f7a9cb8e78c9f42a1ce4a4ceff2971bbaf462d2dad208af271811bacd820c885a94f5585a90ac75473c0d2f6f83b0188ae3c8c601980487fd0e013f2ff83aa08f0b9f18f3a28f7fd029df257d246435251c7d83f588af5627de3c4906112f2880fbd02d1f04dbc777943a9851e722abf81644a42f581ba9f46162e7515015217a68ceef6c90da205a3313ad288c83c9700db30d78594aea8468257e765ddd7a0e615bd101e7eeab3e75307d59eabc4374c11341ad28ddc81db0946c938b1004e1ec5f32fcebac62ecdd3efd59dced0a5cb8405cc9fd5aed4642cc4dea78a15d5fe76853f1469ff6fe11bb11e673be1371665ed172e316502965e6d3c87acb027fc76ace695fc88849e7d240fc30fc0a5289e1499ee2577e77144654baefcd5978828a52b228b0e3d429884c2b17de2a28dcdc1423bd44131e8762ce7ebb7e60005b88e29192e4e8b0ab51a51f49c965eff5ea4aa486d40d7c6df222aa7954a1fcde8ce4f2282c94439adfa95144fa8f106100e4511533aa7575f94bf2e1759a70f750215a370926c6677805e2206cb97aa706fe6df8917889f80670cfff3d3a457e3ebbd2be6e552d5d06e6bd0b8b4c38892b0de87b25b22fe6bb1dbb414d446bc5e20a4148989da9314a603c1f2f3f578a2fcf37e73fab91a3b10d1db0fc413e66064f392ac7ddd4f608cf2e976a02035a14bce18f1e4684ce78d2347fe804e9e28f445bc3a1090cec839f5dbba9c9f1411c718d677bf7f7702b4556c870a3f21cf4ae761c55546c1549e2a05523f30cb1fa9a008950d6645d1afba9faada079271d137b00aca0bd4f5b9766627f864f5eea4cec022920ebab4154c53f0b24b50003c210c6789069a3381de56931f2a1aa11f18dd112269bdbb2a152eece56edb76234b7028e406d92db2161e695b37f3ce565e1213bd51067427175bed7648aa87168ca2a3540775a5e42c69703f21971fe725ae14184a08d3a17d7357e6d12764ea33452938a01320b292b1323aec90fb0e18cc54a7d3a8d268838c8b11f69106930619072ef86092b12920ce9c256bb8d08c9582b38048c68d0501518f73c4d776801e57fa278c5c93da278613ea4719a92bd72bfb23887bf697600d99e376ddaa12d4d117eefb4fab880e597750ef8d86d5298dd56673b5d8823e99929861041e5a98748a55796ad086b9564e997dc06c58aa6334222d56ec7b53e5ad9ca8a70cedb545bd95634a9f458123e76e9540bedc4bfb123e009286df280ef29abfcc511f2c405b9dc1efd957102fbbf384ba38e9830be6e40705c871280100963a32da8c38cd8e8044a685ab5933561f82448f0b258d0015c08ebe2c0fdf7cf4c3da8036a6db78a1cf6f2a7293e95c12c381a466208304f86ceee2257b39942f48998c17c05010b1e387ab0476a3802cb542b632aa7b880052fab8081b751715f0d74a65fffeca0c1c72b83edeeae81a57048ec9d6cc8f92973a1a9d87668067da3dae5691e1d935645fd07fecd1941a7f3225db766d123f20d50f56dc83d92428382e14ae9c2fb6c8f05171c45d87cb88cc0900440a0685024936d1c673552fc594c9aeeaed4aabb4265fff7385c1f7aeac831122fabd45cf199678e36a5c9884c7a0c7b60e43d48837fb6a475f8502f32d3a5e4905aa70e2724c33a352d4fd6cce19c3965acefec326b12ae52598b5c89225b76e255030f28153dddbad691972c2086592b1a338c2478c597ad2be4a04e08c58223e896009d9a75637a84236d06c377e201961bb4cd8693d414c7c97eda7aba093e7c3b0c0090dd6f4aa200751b27de53f59276ee69db5f6c492f7ab4e2d1a36c28d083e91580866aaf24448b1ea19cf22545a4db3ce30089f4334423d959498f6c627c4f3b5b1ce9c5c122d4c3b3674ad9768e3ea8b7f1a41a8509f55e65ca5ffa612417c093f44cdceba778d17d0920ffc0fd4faeb137ca6a5adff62bed81086d2e22b61de1294f386447c6f607ef9f745e19dfe20a083123c492b6b7a9741823b86142b0d72b775708ee6a7627ba72747330f8200c1b25dd890be2fb52438988dd919c67bc2203092d88b1b27aebd4c8199bdc239602e73aa2d15babccf1cdd4a1fe490c52ff4aa43130d442bd5d0a6d69f124ad22a0571563cb9548e34b184f165afa8cfb38b893a4e5fe281f55511c3768bf240cc591283365fc8756d336509c3f904aaf0681e204c3e16709eb5352909ce467b2e9b2122c8c4911df3054b86be401f5671e1fcc5590572468fbe1acaedc08417fb5e59fb15261e1e502da5cead1f25b80112b06d0c00ec562e340a85b87c261134942d098ff5f0fd3d9c40d1cd94e01764d86403425313b6fe00b839135e61523312ea03d62622db04f52bf77d5ff62cde62f546e64fb6ecaac4bc495177c1e2a39a216f66662966516d7a85c36cb72185f6d9bc517d6006174efe0d944a254f57ea9a8f54bd2c2b9c249af89cfebf5fa4b4b09dc24888af817083738f3cf9275311505668ed323166f89f8f4b59e500b7b31dd5aad2c8ecbb059301a546b4217c7458f4161dc9b1b0b4ebf9b0e476d7921227b48c358ac858ea4ab47b0f65e64dc971a08a628c9ec4086fe4b2f3b496f394c2525d0e1f805c1b6206965906f8d74765d3a6630a14116114775d5f50a860f15f042f43035443bfb324d51afbe8628478555227e82a83d06adb2c252c3c8c59ebe9a5b99c14fc05ecc8367956ea8b885142a068a9441bd53b79f96d4e40c559861ad8c045377a8389fd7d5c1c520d794542f4cc2953c8af67959791e26b6493b22f110b475bf8e1eed44826a723ec0c97913ca19bf53ce3ceedc6a97cf72a398628c32be937b2ac8eb8e9aaeabc3b51166c8c2d22c4419190fed8af644c483fb7bb3f840562f2ca0552800c49e683ffbba197ec178019a71770207e1fb1205bbe845618a249c3f01009e7ef8960b53292c2ddca63b3a77a150fdc227d1ed29697e58bb5d3560c7d4fbc34fc171e0b6f6e15c1ea644a46130de91d604d3b44bf24de5559a9cee9b52b4b14b9a14e386fc2898aa7f3be40895881287394459da885e8461cbe2df17445625ab337aa1de10a8b0b82aa1c1d8da277db6c2c7911506553d8443df8263ac7f0cb72dbed2905bf075d58b57447bf1f92d681eb985c7d92d55c7377cdbdc06c519d88bbb303dd0cbd7df8793cc76cd5cba698114258cbe44c7c50fadddcedae219b6760dd95a1aa5a27817be4cc8eef83f36367ab9550a59a377ed7f0a36d0ccce6bec8cd01285f73b718ae3740277469847dba85693618019784946ccfc6b0b8a118e6c0b57bfc00e80958f483417be1122e6f16fecf372c5de6e8c01b4aff244f178e44f90ec77d4189484021a08b0f940f8c124eb7a8ceb426578f638a759ed456c08720206f29f8305adddaabea8153bc5122c847a63a5e7ad17dc92428fee0c33f59b38e0c69032b6e842e1554a43b6392eccda4bd70530d025a490b27ee3c9f3556e41ab67283ca32072a5ee58dcc4e23799021f9b900539a4a747b0b79ad3e9305aa7766799c7f5725804d70eb1d3b61c6469536a77e8a04d8e46702ff15edb6379910b314935fc16c1473e81efa1b2d03c43daa510059114a3f6c0c62f92cb264b651423b49caacc2d83240d152f2364f1e4bdb08580eea280a87a5b997af67764aa813dd68fb4c848f06c4cb0d11419b599fdce171e066e4b6537291ae0a125d64d1c423b5219549939c951207862fad5aef6831615d4aae791053ed5e4bd1a3f6e05ad75bfb221fbb543b981719a4cfa2500a86639d14a03c4d962bb4df5c290af99861f97f94dad2dddbd6a6ae32c50f47c1bfac139024156aeae853a82faa60c5ac14d7cf78fedd61826bbc4937a5a681762f98029192118eccd9d7b0a1200819b55c9b2eae1fd4c75f26b08f2198644070716806a20f13b96c822ef305152e109a16969c3de8bf756fd261a8e76560a47d9813209ea39d33928150eb457feadcce891121d50177cd482fa980bbccc8f49e935fa9c5f1fb69909002f838996089fd5c3903021183b2781a480b959423fb60e611ae76a585f0bcae601b10fc575803e3d4a298c771d17878117fe424d3cd67ee82b455389b0f9f4e037e8e95593bc3cd13fda1fda82db708914462d8d1e82a76f3c1c8e136541b072f621278846ca5faaffc82095813570cd91c6c95c93d079f52340fb6e1c112df0105440faca129dc1e8d248c69056ce4fe97c49795ed487bc76304480bf5f235d4278a307a37bad9af4c4b20d8a3772d6848bb59cbcbde6bcebe724ccbf0fdb6f9d23324c19d4d97cb088ca884b2d122d3083cd382380aeab2a86a2b24cd51c276cc6d20f032c476e02b767deea34ff746d9714ae833d064fd1b5a7e1a54b60a9f56a985ba011110d223f64f0d298a5a4098467c46fa93dbf6cd28ef2630a22d2fc5dbb110a0a6ee301b4c4912455de11dfbceb526feb72ca15c5dbd54f6df4c89cee60beac23636a9094f3d192e92f6fd263b116200b154212dbdda8f66f6441c6c2938f58e7c9ec938b8cce940e42156c9fe12561023ad0fcc1299b253184c5f68cb4e22c58c06723b0b41b1936270fcf80a517b6222de82709290f2a716893b59d6c04b55ed889bbb107cb6dcbdca84a84b7d56459814185fa20fc16b54972b45e870deac4594117f823092d96b03f49d48424a1080495ad7b6efc99102e79fe8b36e35afc7436739b904cb5bc80cc95faef447dd5f3b9897074fb98b029c00cabd2598aca62705fa42fe1cc4c913e9add801121cce42edcc8857d54a596d793378a694b0f5700af40774221808cba8014a366603089986364e42443cdf94324eeaaa3de1de206114820923f8518146eb40790e6d1536488b80b47da5c083a82e439b93911371901158fad2d2a67c34021ff7cd517ab93934cb6ed9e4c9b9d5c32ee5324c67cf4df1a363701a57f57928d9b0055d955408770e313245a893ebe4cb739036ba0c09db422e74312d90d40d32f8c1cf4246da3080334ebeb3bdb0dd09384995727797cbea467c68980a01393dd8b48c24fae311d78dcc621a697e33064b79aef3868c527e0ca2aab62e8e8f52910e16e5e80a637316a2e247003d80134683f393cdfd2fea2e69ae7e0f0ea0e5929a08ad847a453c47abc6df3f89cd23c09a865b6e10d0af289372345faeda58450962a1d6b79d60ed5194911afaf0912fc3c15030cfde948dd77c7957be824940903c265021c806c4de4cd4546d94cc65313c2c0a238d81b246fca1f1e9cfea481ab258d7b457bfb37b446086db2791f1c798b7cc5c4fad92bb44932921da09ebb104625b1f22d3eb020350d5f0f9a89acc1c158a0da1945f61b8650e33e74e906924d08cfd309533ddf452eb53bc5c1d541c4c3606f324b11e9d13d85198e32e43fc2e223dee04e1bf7957f533d97bc27ff2b7285c7e695c53ffd3c41df494ff61ed8e2d4684401b10b36e3add938e728ed98f28175ca6ee2b0d7a5974062a2810cd2f0259bd2a9596fbdc6628d00f02517a8b7a1191c454fff844a1fdc63de49c55a3e78cc0319154a67ab811abbd467db22424640ffd8daf44051b1414becae8f098194a7db9ad608b86a249c7444b4513f90e39bcd95b174a4e75325b993d644593edcd7486ee52b0ec979d7a46a61990f6a6787197012381017a54ee7765410b9e68c7e6912c40ded26df028cb18700c3d1b46858fa129020aeea110e34d77ba475e4c94688c442cdeb034e1027b7692d88f1da941ac4d80039b73e88490cbb399af7b27b5f29716ba4665aafecc0d7a38385077eb324a03f35222c39d73d05490afb8161138c0d8aec187c92c183710e5062a454f2b0e457a188143a832c2414582d804d677d6073648022ffba03594c302969ed9e36b9906187efb06184e73c603ceb3bac853174831cb47438dc853387d3e56439f49d6141943b27e299feb944ce39fc758381054946c7192f208389ba15b2626d82bcc0e530fc6a1a815fef38f05aeb09ff6da9286121066ff803d9040ae2b1960ef43ec6f20aaed42025a13de1532406b9b3b538f50c39dfef5b46afc0b74013898b0f1deed068f72535dd89eb2073785e9979177ebac35dab0685ef87296a8ee2f30019656d6a41fdb0b9e4fa56c444c86f81b403fe622962e68d60785026c60d488ea16729ae4bf080860eee2ab422ad5af8af7f344a011db6a9dfacda3b09eb64a01a080dfc7518e8ef7a6d0a84bd6410d811471a08de4ae6f2a0e795f89a631a08a24103abe28832a9cf19208f4dc6734b0c3982103b1dc7890e88b3665260f45dfd402626f4742d5af8f4659305c0e8636c9dd16533849b273bf6832c723123172876196d1f9b059f23d8670a02bd0bfa8193188fdac01b09b82fab04976298911d46475efbc3dd4bb16eda5a985fa14978b4f4d732d1fbf186518fe81a3a86c41af397671cc381bd6d6bd20e5bf5ef68d8f8970a810c5666032f185df69fcd704133a823d8392970d02dc9c545a07b76f7a60138beecf0b62ef017e0ecb07fdbdabd82cc0e0052e260276b7f3156ea062284e63e155cd47df08ce2bb459fa1f5cee0d6abf54e647fe78b5b5b691f53b30b15a0c093bcac1348804c6da10de839abf49c5a4ef56ff2116d51619b73cf913f52b9f1f7bf1a82f07771adf7c340061d70d2242819470255120f9400bef1e9aa4e941682382aeb3878568de19599d77cb0fb51e66bb10fde40f3e9b1d108d2314f25d6b654f2e7419e9494201bc2882339f692cd26ba39f75333dd642c2c696bd6436a354579a4585b2b2eab1dac488a2c20902c72438a9bfaffa371a0ff069fbb9bca6c84940541ad86d78dbd9010c8f8e03fe92458594d394b8f3d3bac1b2ed6f73cff36f2e3f9481a9c214f980e557885e1d6b69585a26829aaff60a69d68121d910041245e0226ed0dfdc5f831e91c7cfe874bfe744abb74d29980d85b7895dfd930281782d9227500b8a5e44b386c35cc3117517d57548bec35cfbc77aa40deb20e2d63109394225cba6eb5abd72b2e799bad26c9803db1de582f13a0b6c0dd86cc66564c4bf51487cc24c8173750c78e1a21930e43f266218f88edafe401d4c4d16e2fe4973af81a98b2fd592919bd4dfeeedb46548d5643e19b96aa86318ca4fb6712f0826bbbd934e068cd03f008a4bcec4caab8c4fe91cc0b599da42bfa432a21d44799e0dbb668302a329585cfdb9e148bd39b1864a912c20532c5c6860ebf94cfcdc161e53b58bc8b9a7d308945a1871a64b4548097c704f59c66fa9bcf9ed3539be241b68f2c13e6f218d1cebfe056cc4c48fb88ff2a0e5d1fcf6055acbde9cf2db18799b2039eb37f15cbd04a42b48610a0be609a3a23bcc41fcc998eb88f3fdb2b60c002603a9ba5504ac952ae37321c49552e1deedf7d918a94d2ddaf9e0f6cc35337b2c6257339a908db02ff1d452e5f00d492af1b016b54848217804421ad28959e8e53cbfcbf2cf619eba9e9d60256630e738758fad0bd96f27cae445dfa057ca2d03c1360e9b55a919d26ca35781a9100c7cd6b149463f2db3434507aa1cf3a07a64ef191a8502a5fa01f194302dcdd5bcb2e45a2f6378b0807732dc17d9ce3568215d98e96d0b0837bab032cc72007b8f87f8e0bd4806eb24a3505eb66ff56e96f0809d5aa51b9bfb181ff15a7d1d55ac28c002a35289b422754aee9daf009673eba5bccd8d5107ba768d0d8baa56a6bccc747baf6a5ea4f82ed40172a952fb3d4bfa1829d997bb2f19f3b2e38a728190d1ec59323a55113919c829870475b46c64cb2dcdc911e8652a955b8e3c703202e862e8bb82598cbdf81336d1a52ea9d0754057ab4f533212b4db09d7f7a042e5e27ff66e34e4b7e684392240203bd06936fe23e31f127e99db0f77ba8e1aa0b74755af55a6c78a81c7cea2f514bc673e5383c76f0b209a7d40cb03717c0408830aa13cbffa072eec7eacd24e3f02bedd13fe39162ff75c85f76e2d501dd728d0dafacdbdf20077558413661f0f52079f165fd26785879492122bde297d9dfe6e8ba15782a7da25bfbc6d214b29f2b2a57ad18a6c0720e766dbdf2b75fbb59d653d4aaf632576f2528bb3580d6607a9c6ea705860ddabd7b491184400fdcd12fec66145db8b4a98523d5eb3dde23a407e076e622399a1b01c1ad0ad60b0907da38a68c785a8b37723406ac5ad92bd8c01eb48132c9b155a807ca594e2381425ad5e9177343bd00f6b26a2abaffde34ebad011c37a85fb2f5dd015dc040b2b9496a08354e5cecb6c5cde05de6eea69cc667b79c45b8068184a8495d5dea96621acf8e731551f93887cf508b9c92ed6266cb27da4816fbc39177883f06c8be5116032a6a42d18fbbbf13f38bcc2a3e0531781e5a764658e816a0a06f6d7ff62cb8084cb50f45cea544ceb1f85a3963f771072a212c7f0018508d39bd6631ac837770114f3c1ccc73a4c6ec437a03ab7e0324e14985a39ae1a4183352916ff1bd6ba39c8bbc595a3e2133f423155fa2533cadb313a6e00e20f3428c48a2b354e751c9c90800a27351e768aaa0e368c6617e8df263fa46ff28048011ab6df9913a42c377377470dfc6176125f020d468031f743e9435fda67d088824c9cbd2844bf3f804c06ea035d2a70e5fc6bf43d6ebd9e36be80c78b378c47d92e74a424f1143d782fb906ed2a5dd350a69f33a0f4e761f29cc62dbd9c7ddd68374d5fb7a569586aa41bc66c7b410b5666573e6e1fb2b876cb03f4bd754b891c4ee0213963ca77dbbee883657c1cb81ad90c6b016cde63f50d2a140779ecc54f4d2e39da4870ed2294c354858690ce9dfb3fb477c252b872e484a9fc7583e081c41d9331a2b78de0a44e4ec3e259a34f1597428aac69a096055b50f56bcd5b424c7f637d980c664e2c6eb3eba0d760d993e7c56bf9443d6bc91014229a0ba52865b5e2e06ce94256a63dcc4f72ebc3ff9bd2550cce9deb4a4888482f7d5d7c1fab2748f24a0d23d7c906859b35d4272368af1672848073513859f4d4f210c65cf8606c9c409756b2e3377bedcdadb6c3b8226d339c9539254caae4547cd5447ae85e4aec96efee158eb4675d9ee6c6177764abf8e7bd8a23a4944f107d18fb4439e020fdcc1ec0f65301808f07888872fc972c315b6b5691b58930e16a2f056310571a756a9da8851101b47f02aa6a3c41ba3a33c60bc7f22753c946b649b883c633e293e47cf8bb522b2065e0bb5ad9856094721948d00c21af46db75ec6dd298b30866d016a2813639dcc9915a199f200aee04b187473a4168f466e7d32afc34c6d4bd037d9b019b0519ca2e40bc1cb5be0458739bdc5e0accdaab87283709253b58c71e95a69ccd9870a63235ad16d29d8eec923de68c9edad9302abcb99466a37ec1c7606ca0643ebe043c5b494e732de2a6d36ebd1644ce43fb503656302a56af86e960052380ad40d411911c465416228223b3362067ac435bd055fd16a37b507138b961a7c810c105bbdbed071442b98f14a5f0de0372f2cbc02e19c8f047d4b30ee99bbc389a0f59a97e83f76ebff1607ead2c14cd90da399dc7f2b3a29cdf3d6ba1f8de0eae7e2e62d0c6700b7b830a708b53a17668db4dd0ad360dfaabdfb4f1a27b6af5b94b172228677b8225ca54845f871580d54d461cb05b3e5c8cd51aac5e172bd5e870ebfddf411b7e601d8b7cccfbfdae483f064d9c323f03383fe71bb05aab199f5cacced4c60c45a911ebabd18dcec7c328bda05c6bc75eea1b6cdec16e35089ee64b45d534233647d25a22748e9d02634f50011429b6268115a4176eda3acd441dc4b805851814f8cd357931e62ee03a5cd17f9a859d5ab5f6a5ccd6ebd0838f5f2ec0b75ef291ff4d9ecf9872dfa7793d4727aacc1276920f040e6d635c6bbabe2c3b99306ada18f56bb4903a2c51e29ba136f47ef79d9f618ca32b6f58780af4aa94f1678c66c0b97a5ca3d4f91af2490ea0509d53874daa8fa5670cc908f2d190ba11636802261bca5339a69b788ed76e34b9403570b11449de967019cee40cf3c6fb60d6d958781e61bb713b2341d68e25b998bab44f6bbceec2d96f93173295f3a19f3977eaa37cd2c17008ab32c4967e39632940d96e9d8b49aa3d173a9f7dd14dcd2083a94cf780a0eb615bd4c0d9fe264b7056a80ad7f8a2f3b9555e6fe83a0029e2956fcde6766872b2ae596f34ae0957ee17b1988371631f8fe7b411cae22dd0f8deb822e2df86468c25afa787d387837b7642b0b5c3b6ab6499e0aa116fcb5f4597eb183a0ec74cade79e392856f6ea29883ca6148d740db9fbfc8a0591aff1d439399cb57cd13863eaaa344bcd2fc4044ccbd4dd8bc52ab9ba33a7bd6a5e6a16b027b4fc5c20a9060ef0fb6f4bd481cf1eb6b4629b419801c91f35ea83343a8f5357018a6b7739085aef9b23fca4d693e83b6e2b781d7ef2b67f3008eb9e4bd9862fe453dcab6625a5d6f46e4a132104dd178bd9007182c1d3405c1cfb83c32ef6e4edc161a52b4f78f8bdbdfdd8569ba1b9290221b47d28ead94dc513a6ad0ed23258983f7f9ca45b864d40e3f3ef2cafe705b50d47bc5439d5dd8af9fadd47b5d9dba0bf436cdd6582cee3237844e05233bd1537723b6f4f703801d562b6dccb41037936f01d04b46f876dc90f2e19978f7f44c44fc800199a65b15fb95dad7bd51a5b903d851d0615e4bc33fb2753c025859c10cf4893695bf85b81721a7c30953ddd6b4a26051a430215a749aaae8639d587578ee95e914ea9dfc167a2a56afea20fffbd07945ce8c1a7b5bf80f185e1de403a772d9288354a616c7d85cbe7a5212fbf8133bbfdf21b94aab24555bb4ac2cbb5473eb120bb52ea4f581f8b70c4308e162a4cb9f3581a2d51b563d6f39e8fa79a58f856c5e3465c9e5488bed4e0f066078ba62a5311c41f68edb9b2adfdca8dde2f6648c80a3507bc3d512ba0c537df055fbd0c8d6ed1f6a516c8c007be894f76c1f7ecd6ab8ca0cdebf47e505145eb3a374eb89b8f5dcce1f2b038f73b96854aa03ff3049d27388b31d6f215add777b657816675368fb9153728a32752e353aed5f4aa1f6d170784d2bd5188c8ebedf866901da046bc1399e970bcaf48d6f8ffc862be238016d122e15820b5c43dded333cc4423e58ca8f3c53786e1095623ec410d1eb8f695ba1f08a59eafe6974885120c6faf2ce3e21d5657f641fb79dedc80f9887e017e8209cd995732bc618bfc0fc6806155e0d3be11da60b55d9b5d12956f27bd3a34ef1a6bf9f16722815f7ad3aa97a5a372095554976f876aadb1317911cc4aff1bc5e0eb6006e554d16d58aa565636d21fdb4dfaa37b1c25abd93ba91e8d1c2945839689b50158029d333b9eafc630fd9659cb475fff01c526e95dac3cb857a8c0d3eaa417a509e2a94804b3bd0a7a49951648e8c3643a4c6a87bcd4d3768a5707695aecc6b93f418bbb20afab56dbacc91d22c334bf2a8c691db0071206dfbaea9b9b013d1a49226b20c5857274074b0bbe91c13f28ae39b4948a7e5a4812622774975c61014b875581c1cc7d7ca94df266c3522afd4105338ed7ed1ad5d20b260d005b2c4a50f1411de3f25e9d8e758ec64f49db6a8a325eb20fe8206a8e0638adc523d393ac1a245b0a0424ae7a45e3a475178b7a2b90f5198f8eb249dae02b773169ae3c39d6fe506dfa06d7ca5c48dfa9375d0d5ec90b8bd7879772474b5f1f93b406fc432d351defb29a6f1900e2149f7a5b3008c7ffac4eea55980979a70954a80e9fbe8b016c5b4a8a9e073619113cd7c527bee237b40538d5e7ff1be3cd7d8931104b66b4ca49e81af77bc467f14eb99e8eb76eed033a2a40d45ad615f8445484c3186407686ba7c67ca3db7d8cc9a961d8a447f1e4e8d11424f8f5e8408196418315127cc282a0999cde82f93b35fe5959ff93c8b815ae5cf90a2b5f7570a24428f4f743703b9d81699f7e6659a3b5e4a4b30ba2cbcae4584c816328798516d850163a7f6ece18b774d6869e8422fa29ec96017122846390869125b834e4cddd65c9a1af561feb30acac833bbc0ee2ead158859536bd6c68c0a2f453141291cf02f3b246e80710c91c009a25238c4479c4bf32c289aed5529d06ea49d7072e346d5647b9f50a7d7897ea2c19f104dc38f40d19da0d097f20d8a39fb854149949f3d0938b67009c58069d6c7901445f40d8ca198ed622b6eafc5290664dce97aef5abee1a3c89c691bfca2a48d78a7243ea10144b03cfdbfd059d00cc5876cc961c12eef86c5493e813e55b6d6abc021a1b834ca56b8fcc36d834031021476b29557694552e92b9ab2a45b89ff7d8a7203378cbf91a23f60937594c4e7a5d4551b852becc5ac418fc4cfe1063503ce1a75b6106c1644e9e1e8b2ad510e91d52ad9f48f4ef96a74e3a46f4114f6f78be3044591b8f0dd4aedad3f6485a4c5a09274217314e2fba82d414c45aa73a376c65140ba91f80317b5bf4e388a006d104f9c8ba0a3f0f77ba5a22e451912bb13ca751d9f968a33795f45a06bf52bbaf20f889311e9444d12049c777d62453856ce83f05427a21f754c9583fc00f6f01b564ae93677990aba6a5c53d5a86a2bc758296ed1b58a71c2e43e84da0a6e22aaed5dfd5a43811ba8c3a3287dd57927dfe04c2c66549b25a2631d4ac40347caf49a5485b529df335f09b8e411d16aade47d359a21d693bb669857695cd7192396436efd8c55b3de0694cbb4e64fcd624e1db595aa2e7bb3f8cbb623e301b2579d8b3d3fa62632d91e8c88e623532891a98f7bfc6a1e87d293ba6361451e1b5d93374f16909de931dc3d9d3331e81eac5a0832cf06393f6521a99ebf29de0dad79c37aac9ba9edd3394cfc907600a549688bddbcdc4fd2b81a38f571f2323271ebed6eb48d2c1ecc971a3dae22c3c6f16b8e03cf34ce45f4af5a5b1c7695663d546d9f8b47b15de2800775dec5d90b320b41874a28d98cbb602f1bec8209b2d49a8992c6e5db1dea35038ea80883df142a9c1070ff91094b4ed3e33b9bc645ddf6dd42e0a503baa4d000a9dff4e6329f9620c2ec4fc6751753da02227e697d2e38da8d060366a9c42e6ed74dd9c2c7661a1be5e610f1e86c680690717a1bc731a18ba14aed6a26de4e547dbc06b8c49ac29d0b381b5c2ebda39cfdd6869e7ce9d416efb1f327cd80184821f758b0c745cb968c71efbc14421c49204e9a9d3c9550585d95e076959c11349c6ba620bfa511c8695fbef4c5774024722416b296168deeaa22eabd977452090c82fc7835e99af39931a2f013c024b78b1c5f7cd80991b7c489f1f848bf589d769cb14a15691724bc61bbef2178578b00240767649ba520b57856ca17227615ea5ead07b44a112a51343229d86b7d36448cb873bd25cf3a20a1f14a7058a805efa1b47dabda7cdfe1f11a5ae0701f10651b8cf94fd8a736df4cefdbcae48b2e3b7961f102d5f28b537529415c03301b09437f5ef6380bcf73465e4d719517b2a4e7b6b70a9e418276ea54c2a5bcc6a5c209ef36e955a2c946e0dc4ae0c39cdfbd020794b2173bc18076c38b70e7ba5754d3558cdb532d3aeab594c06e722ab9102edcd5ba37310fac05c9e977c2e1b06c9f80ad4fea3816fba8f9d19d52c0463acd43268fe6d0fb86f4bd4818005054422ec9d748aeda31e47c2c8dec8c1d31c8d7b3ddebf9eba6707dfc30db16661a9a9ff6d990f1a2fc7edbe97da35c6b6a315b81388b0a22fecc27f94a82daa44ef50a949de17b0bbee8c3ab7c94d2800a5fcd2f2c48d466fd592f99440eb85cf34c1695153630a818da109bf1c435e2513efc816317da6c40835097d6351e70500481e36b1d5aadee95d027be139b67bb7188034ea179066270a0ea2de7ff5a9e9767618f51507e5e96d746a56af0b914d66dfccca0e969aa201de29089f2f834c1c59273efdec7596bf14da42fa72f6771047f92fe3e4ace53202b9fb254d46dc62b50ac9b08018041939f6317ba25d6aa4fc3e8e350ae52982210ae88c4cf64a073fb1a631c9f7fcdcb3924dad8412d25b596c3828946f4dcf531f4007dd8d2af7ea0a02305071f96d1ba2532b6e8dbce4f47cc943d89d81add1e3ceeee91448a4c42545b5b9522b94bf42ee5fd1d56b2b6d104dfb4b67f002f445ca9ce7729e0f7ac21384af0a66eeb02512609e7667fc633086e6ab4dee71bf0d615b1dbe2e2dd4c8cb75ca8531936038b2eaa90cfbcd774c9b6cb94cc4adfabb94061e211894124690e399a18752a2631d5eea3a3af757a53846d1adb803fc0c474970007e651faedfeac616732d4df492f37025acbb329f2cce668819928477a50504b840581ade4debb9577f4e277de899201e4c522cd9988716260f12e8ebcf3c0129ecfa1e35ce7ce246d55acf497261ef0239585cd3021d84c7a2cb8fb6bd7ce62230b26a6556e676b6efe2e5935216a316b82f3cf2c5da0ef35cae703e6bcbf6884bd4197fedc043a34211363e44123ef4fd01412f0cd2c295425d5a37cad461fd29e9a1f8423b45d1338c310a6739d3421a0ab68290303c179409de2bb57c9899d54594e536aa290c3ffab74d9c88b81a249ee68fafa6a41d7ce7d444650adb3f71b8c8ea9279c88b3c8870052164c49b1a1a65470906e484a18c46b3f6f60ea14b25eabaee8f8bc868ae4878c3506d66559c5d1f0e5e27c51c5735349b6a31cfdb54495ce1f54d80fe2496eb39eaecae33be40f299064575079d2b7b2ac1e7078f8f89e60a4b76fb2bf32a7a004d60df6bd10c852ff7a3c41c97ee5fd094f1382619e3a41d8aa954bb58afbce67c7e06731752d195b8fb741655ec07da192390860494ba6e7131755a4fc9fe9d55dde690c44f569f976dc7dce3182f9d03e3a4e8ea73c48a4d244d4dcfbbb487c833e14ff4957df379e429ed445c22df5c474f445ed87ce996aca563d9b68839c4e7dcf2c3c6f1b624412fa26075efe746052d6b56e956d4714aa00341566f5cfe5b81f1b1b33c73120f9a2e8a071c130bba04ddf4ddb40ed02ad34a5e6babafab91edb8b37057120b2718c79cd7089ed39d171800e82c5edd640230ff4ac04be78ac3443b654e953dcaa76aa461f07984eb65f90666c7c9eaa0e7599139d27419d98336d5be73c707e181700e59c7488448483463ef42313b91838e358ddc9adc5eadbaab8b9f8ac746a4391d69fc2c48ae33cde06ab48950853e02098b8049555ccc7408db4c37036abad0d2e8d0cf652c3fbc08e7157898f4c060e97948f35c85de064bad0a3372c8a68639cda8a85b7d52520e4e07078c6b8b91f2338f4ba626a95001cc3d49979875343991bf81facb91ecf2f660bd1768a8366525f3fbb20e491fe4eba5ef79fc4e3cdb795a49b052c7668e934ff4640f676709383aea8e12e6940b7f3f475919c0e793cd7758b7e698ec260220c0f7e9ebbadf31b50e5d5290788bd0e2ea26a8d4040c3dd2514ce7cefccc70248fa314ddd809c57d247a8cd480cc251dbee37470bfeb66db011adcdc0fe4ca74958dd7e29adb7d0ab50e8638154c5c279505d6b85a2eb8e918756bd7fd3f2c7050b06601c17a81e3c5c3f87f085a19530d0d92fa018685f98c41a4c9747ba3711507edb9f56623224039103f062c16505f2b1764316b20c56a01d87a9113aeea3829a3353548282f6417ea41ae568d192f81a5fce89a03c5946fcb7e80ecbbc2a29be1472ffbb742e04bbd9461c6476a157a8a83a5bc107b20c861a2e481d0a6e987fbf29425bad4bb97bb819c1886694d8c65bf6652b4eb049ccd676fb37f69d03d1f805e3e0aef9519abdb6be59be95ab1244e3cb77c9dd7208d1ff3f5394c77347706c96f62f396f0fba819a0f84e95e503722947fb90ceb7220372b61fd2e646c11d599b43f1b1ef2b63e4c3b875d49132e225373b674c9eec889dc2a65cfcb6cb5825c1c602e755262d3951301e61d0145eaabbdc87f46694d070ebd3314caed545cfc120bfe4922f5c2c4ea6ff68b3094b4fd8d895632892fa2a2c55843c22d288dedaad865dec243f65db9c996784322bc6b2b4b880a887d198a56fdc1ab4e6433972caa6df547a31cfbcb86a9568c45bdd4616e68c202cdfa1d978774190a1b10982a0be7910439ba8e22cbd221d81e4e0b3423e254cd9bf21b408c4d2de2c0abe926c4c43d37534c553c40345843a27e43286158ae8c68661395da2867dfc990aa29396dcf06c5aa25dc4491748330d7c0bb3a3325c06d2d434a212e62e0a1fc9e8511c86d09e4858d7d1298a90bb605eb6a5ea557bd7fa83cd9b501f3c6e0c6ab5429d3f310ac340673d0fa4c6dbb2211c5c9520a5afd6fc339f7f59545ef0dd0daf5cb624ca47c6b2ae36046aba60271357544cb88c91359636942d0671531525718bb4b8b01657af63263bfb9103043171d2762fd2f5e110328c8042934316c7530343ab7938e2feb124d3746dea02f84209049e831a3585bf23ce01c3229e2daecd5970ecf07516e66803f915b1b220f007d7fa3561b96891957f3d5ddfdab70e380044158fda571d5dd85c24620b742bd212c9ab9720d8de3c7fe8eab346e8d7cbbd0577f63547e54d0b9aba88456d4c8efa8ed182b5090b585d2e3aa1832b6c6fda93cd5596edc8cb9e951abc13fa7dacbadd40e212a89d677b6487e25b5b18c1cb3b1b210fdcc60063a2fadd8de6576cdf4afc27147ec5190514964d02ef986b128e01d8ceecfd2479d3bb4094931ea7dc5c31db6454a96282caf800c5d104dfb528e91dfe0ba8af2dfc48a8b8bc113111097b358c9685da1018ed771a908a976433cdcd2468bc0780ffbfa0ddca06b10ff67e062b5c3130518f4c2de6f2188b85db3c412a3233244a4fbf214176660124817bd0b5d8a002f1889fcb3985ca8b3d120ac4846ccec2956324a4a2e63a6f9904599182796f36b139b0e3d24bf1ebd8284e83d676f8c0c19c72531b2ddeefec4b59711ba8b6ae903918bf90c230d79d4067cf04182b17b47af727509c69d4a88e22c65bc5c1d281b521d46f518aa75ea6c77c939e76c30e59c1ac48cb1ee1e1d9d2d947efb6d4ef969bafccdd35550d9734faa559f2179451e74511ce4c03bac58e9dd505b7bb90cd193648662a54f3f815921b9456784a5552c456259ce87ece5cc6131694715a7543082220652fe691107989a4993106a0301b3ad1b995863efe8d2e33218c0bad4abc14078b90d721cd4f89c128c55516eb4b00a9d30d2778f34bafd139d1c16e39f92e46a25572962eca77f078fa1488eae4e27b82c0d968184cfb0de28d085e6039abc60acd42c023f253d7f60f803f31d1ceb40937732b07f87567c700427b31c91f7a821200c04449315c88c4d1b74f3c062bc43eab489cd71e4d07bac92e5460bd7c40d5b698cef4d4c432478a0bb70c33ab5e8c4e21cfa6ddb69229e22af6a5187eb778a32c94d8629b4c90a3063a30f3a2fa953d49b73f5e4beed31cc28645116d15e498d50cf3543ed79e4334a296358aa2a70d1a2f6b2c74012a6ae894b82ec66b3a41db2a419b1c0ba4e0edbe4a8a91bc649a3ddfb7492b25313f021a0951e1d4f9c5141b270c2642d43d5926672a56102e8a1ae482d439347f9cc48b6780571a8ea9913ba6a60920520e74f3ceadfb3ed642901e79bc7b0156e1c1de2584aed95aeb2f0af0717253fae9e84593978e812c5f8fd1564ce4c5dbcc33035fe304c4d336a2ecf01c3bf707ce6d28940a625644988137646193fbef3de980a33117d8f2a51c6dfe2644c089ed5e9ceea58ce4157b910726b70a0a59a6838206aec203ed3bf6715bed4ef6009722080b012a7584cb032df1287f62d0647c30cdcf41c10a7481335312534d6f6421ef5084f8c8fccb409ebe650f365a8034adbd76088d63409d6f58785d84e8528c4fb839c66fc4cad0c3370733733f4de51fbafb9653cdcbe4e88bd8f4dd383d0f2b2e6152fa205d7e50032e1dcfea1cf5c62f0317fffbb2e1c5fa35260df918f617422e62fcbcad4b55c721a04fde32d893994f2cb6563fbaaaf468cac75a3201203aa9124a66520421154830cd3b5212e7a7d89b3c66254db2a6052b4d403648efa2037e71581d3cc90bf685e7aa8b85ea070087328c5e8bba710d0a1a8842611fc1be1eb2895acd6c0ece2a1a2422e021e3401ac78f6becf00dbfc369da25c3004302256b18cb4b811c7810981e83ee3b950ddcc2af0ba3250b5fe8ddb5df8969c67e06f0bd44ee27d387f30e9586d784fc11d7a10a739bcb68928c96f89a1d644524499a167d427e8d43b6691012f88aad2b49aaba9290433714dc21d0a76174617c45afa503a66661a9e4ad953cd1c7aa586afaa6bd4e89d4baa44aec27e3bdf443176c72db417f88b29c5f46a31ecce6080e9b6abc14fa3a166950d7e6991f4a7fdc479bb74677174737855a2a12c557f0acb108a52b10064096a6a287f6f3668d30ad2b9b335b2d5463ae6184d318dcc7a3f314f0a0069ae80a38cccb4fa3cf308cd6a479f4d02cf18e3133c9b12f5a4a78d3729a0c03283af75a380806d2c9ee0f2fd71f5462b529c43cef2478242c31adda857e0b420b7d1528884475287d75c258af00a58b52084823a9b57f4b15c966bda0c284df2400e66b03214094352c4536c03ab59d78c329150919ef924b150ec551949368ae40296508b074dbce196a6fb2a0411f32a63b3d1064e3379aa7ba9983450a09985b60d1aa4d36883b87876659846462094f6eefb093e83d81192127c109791ebd38ef1264bff5b98ce85643455fab78de584bfc0256dc1c4ddc9b45b71e123a128ae2ba63fa02f582f4a774c5df79788a7bffba69af8258af93e4303680888e4ecde0f8f19f818bcf2dabae012939de727e115f9fc88295e636eadf260bdcaa3583a46cdaca032e399b25a716e75776a06736d00949190064b14b34346e71046ed7e70466fbf8811eb90353922b3d7c8649cd2b3c1d938a0c59403d46d2493bbd74ca26f5f654493562d71de8748b1e965ee03ae2aad9cd8e724b552fdadd883f6f8465ec808a2c2f58749170c7f605a0e827d4cf22f80fb31ee33eb3f1515311232ccd8774ab758dc32dbe34add647433f123e8c86870fca57fe10a5885c5249aa8d658788483bc2027c940056cade89327f7b324feda8634402b3c06b4f189ee7b6f19def02526e9c6c1469e9e8733c3378df33eff9106f2b6031a06e74e928afda5e033540fdbd81f4f382ef4cfcca2983c88ac2bfaaf99c1e6c2fdddbd95b42c4d770a569914686a2cf72daccec0073f2d96fb055e11245d8000b977f63e3a9e974da650e3d67f047eb94b740153946bf1867119cf72ffc9d5c520a9306390abd4620c62284f726364f43dff39cf9bbb2483a2fa8d3913a9825b22bc1b17d3801f0a2a70bbfc969171ac886a4f77f95859fc8290053a432408c007009152f7446bad3e987ca0c970f003d3f12106b45629402032cfaa9d6badea17af98cb7a258ef0dd5dd1ff22f1851884142cef6dcd3e5525bcafb79712a8b54a2fcda53166ffd5ee262c5d9f1247a2b2c026712a290952ffe17b17a1e22a72e96e7b22215a1c1ff18232b3286923ba3bc6b5afb3b120d074e3119fe721d051b83eeeaa252273faabb556e42861500ac1e78ac77293249604cbaf14cfabd7c7293603bce5b0cb2a056db58168ccb6ced708a85d02d42994f65c95a6e7ba1157166eafb486325740518aecc22e4f90cfee7f3d529341dab758da13b7b25973a64240bc1255db97f654c71842e64a8ab9ec1a480fe068503b1eeaeb9bfc7a12d9e2143654aa5ecd09511ec944860c10f04a95cf51521928354d69ce42021e0ad3934ac7ab00e00e143ebc4f97670dbf5faf8b8fc082ba758efcaaac0e921b85f2f738dafe384a8a85f7f00389b7739f8385e4c39cdf7cce8c8e8410075fdf8bcb6e6357140875103b2e1cde85fb6ca138b7cd3e2d0a87e6b27df013629a23c2d6691f14f017d4ea8a205b31e52503a907413cf1e99c4985e5624a697d50d0d5786f6b0615e9dd88127aa9e37652670732ec15be7cb09cbd018a8fa8679cded5544765dab3e373383ce77a8493950c850686abc5f6ee234eb1152070be2f0b25a2d65c5f56883f1a548163b185b9ba21157f6963621812fbeaf0465d2c8934692a086f88f4867cd9ad5c5549a2b137f01101f9de331526297a7a044b0e8d5cc92f16cba1916408c301e28b8325a024b307467ad3eebf419aa3384d20a769baa2b9f5666008f7ba1aed41990622e3d4efc9e94b88912ec6d8b03fcd02caf8195afc138f9cc0fbda31a547a01db78aeb3e45201acce882b4672176093e417544841b811b9c53914165060506c278352d96cc1e2cfeecdc133abc9819a899047864fbb6cdf6a0401867a65aab5a6c89df6582c0fd80612404729c18cf1090144f0f02752ba081603336e2b4e1b8f4598f810bd8b5e504a2dd03c958af28e7788fe0714f17fe417cd6a5a1e332a5a5fa099a0d5ebda0757d38c3ee0f07155b6b298bf4e5f4d966b68c0914493ae0902c0e71b64b22966adc6ff8b391f583b3c5765bcdf20253b21de589940756a62e04c3b94584bbeac67fa84bbb242bf9c3eb43c4fb4a807970379cf11a99d420a11aa8a4480331cad004f64fb532e764c67e63648cfcc539b2f77cdf741682ed6a4bbbfc3dc294ec86b883edc4e619697e5cbfdf9434d9e63bac10fb5818b1a96ac8bc716ff38b146620c2770de92cf52d4ccc31a8138899a906daa186fdf6d7c0cec33bedc866e37e3982a0f0283301ca6401a137854492f5fe17ce8bc00213371549fedc65a663dfde5f143df476ad42efe2767084e3f156e28da034464d6480ce4625b250a124dab8ff2ad17b2105eb6c5f00216ec10703f7c8d0cc0e061867f4c16a2d8f68b0ae4a15b4d41d6c8bc52cc46ed651f53671e8c4704ba9a1d0aaa225ba4a429fbbe2700a6a94783ad67da6693e40cdaee2bf2f52d7fa440ccde1aee56fb0c2af2f720038d3472cb37cca7d4832e0306b0c48ad80015dd92729a203038b1a4d744bde4b8d1f94e45f470ecf535018f9880d486565ec0387e58451a3cce00fa9fc7b9e27c12880291671a7d3b364cae051a4844a0c87bd33ff87c3fe85d3ab1a4e38c128c82294d66fcc449cc8d6d861bf6eaeb945ad47fbf6124e084d0015f091b0f3c222f1060cdbe042435b1d94cd1b5ce7fa0e55991370129b36ea0acb7f4d669eb72a0d50e1801bfdd12856dce4ad9d7c57a115409f28425b679c066a4e0ca5a1ef22256f9e9dc523273981f26463a5c69627f914f717c34e909c995f72066e07425d6723589306eed720c152f1c8c0578d0d3c885557689316107065450ea528b364c28ff2f13db4f83dad382918f546a1e5642a727b02c83306881dfbf36aecded00ed2d47bb849b8449a02fc250bfc2c61b5234107958ab27bafd24fc2c1a3d4766111dd43a350b0e2de691ed2c2b904f9c76e77f50dfb34a9d5f45c4a9c114e43b6149ac42893a10766257ddc17df7e7b4c8827a0c27d56dcbd74898fc7ed1b709a844ee385daddc5c0d57851a1b96fa030d0c65b375292186a503e297b3e65634983ac182b0803eb1842f775e6b1d3711da0f152da169efe973d32aa58e86b2f4f7276fbd88014f50637b4a216d5631c3880baaa840c0c50f0ed173cb4a62f7903022bc342f8d8a42e012c0a7a7bded774827c24ac9bd4d68108daf5e97ab700efe78aeb168f7713bf872265ff279608a1a974331b2ce5b5836cd6eb361e7f5f775cbe18a5125c55c52e876ad91d3990613bb2a2e00a69831453939738ac27f1029cc841139e016ebc958523ba6b6cb713be656a7d2ca378e06dc023a9adc211c5285b060c5d28473204f64fe5d8913b98839abb7c294b692992d2eb7659473a484735d8a5265d2a85a5f0da5d6a64778dec42877748476adc7d1dd251cd5cb694af67342e2705a5488aa5488a4b490ab9947316e2581cf5baa4725be45a5bc451038969bb8bdd645359d964d97eb00fde32f02a4628d8b473f243fc31fb9c9b29395d5f0d75c5064e18c5b282a80d6a27210caecea3ce1932fc3dac31000e3b63d41fe20561cba168b79cec1d791724c6aa6e6ef2f5594d446f42858ac47a3cc42a0143a36d0088fa083a7123768a5eb7b8658620378c1a6f3215b6af957e0a65e3e771385e4eef8ac165bf486cd45d08e125283be0bde0c8a6656538dda2ce2068c17963c03e7f85c11d22305a710a48720c6edee521843ee345fff92df00e0894559e80d1ecc9616e1f2fcf4fb6f784d84b7d97589a998caea7994d0f2d71a7a1d5e8ce95294318bbdbcd905210ffad638c1ef829689338d2dced7daf334a5d452f149fa06d3565bfd318522222e162dbbee40bf2b0f2affb5b76aea4973862342240cf359890a513770533489d2da194af5880bc5b963aa364c365f4a286da50dc4393430ce40282eea3f52af3e674751fb4891df3480f489c958cd262839772431d1c045164901131c4c1d077a4c0c3eaa629fb45a459580b79bc095c47a31713f1c080479151b35a70a5fa3a9896945d24b1469d59e6525cfa2440f03b3c6b2322be61da7398d4e010fa354593114f99bd01b9bdbe103ccbf6d776692696197666e5a3e7f58f413041eabcd261138ef9c6ceecca1631f207612a5671d5c266c4d48b5f20d82650456322fc6172b03f948af6de2921bc96eabd10a09e2b74538da9011aecddd0a4bd81dafdbd625e08738e1243adf359ebe36135398efc2a7c53bde50e1260e9d11c6bd4e09315657ae7b6d70e1501ae29a5cb797416eecdc92b6f72d9d05b6fd789511e37c89c2e2efb0a2264bcbaa007062af642b780a17923fc88af82bb694faea5beb5ebbe051dc665d6244f2755b209576a0d334cf214ab024390e2acc2517235e2ffca3d0f1ae2155aa408142a12e51991dbba05f9593d592907578caf2f452806b6b5c3b6ff9dea98207e6646b9030f60506f2ee59535c410044a098b965520dad9a60d0d1567878fb1295b17570d1fe1b55585c5f3fdd18fcfcbf71a89ffb71b20ea16c8ed1de16b39424ca23404c040ba68774cd8b705d349756378fa8cd5fe67af245b3f5adb264d2dcd68a5d1165ab9b96529095b0984093daea7f4aa7d9b99f2ed33af5fbf37279c30e6a03e66efd56bc17d8e5221b91b7cf20432f976278da4a114a1f45e14ca1d3e2bdfbeadbeb0297856a46001af808638d56e146badf521a8476d549712c7b44cfb10d40c9482a8fad5457c14f27462fa7074ab67450b462b5a10bec738d1e1fba61e2975ab4bc75dd1ad2eaf0fdb019fbabb8a0061e75617bde89c2fdef8c4fb38677777b7b5e9586f79adb4bbbbbbbb7b3cca8aca4cb7f563c6b6f3f107fc9883bc871dc47a98719eb7ef582f3231f3f1ef7b160a94767b8ed48ee8b46b4429a504e13a5db3dc947408d5c57484f11df1901ea155e08f18326f0e4a91ad8715df923962745028bd1785825087b6019da3f2ef75e09f370f96037e7cff620fab8bad7acafacedb5cdd4d51c843f889aab4229cba11b9759bad4bb7145baf22bc7d2a87c361b91588bc086f3adce3e4b4d30fb44b491d66c1176127311c1604e1fe7a590c3fc5d61f96c3656a31750b570761f81806f21c0dcb75e27721f251a4418c77ef2ec82dcf38a09cbae9e82ac4406e41fea0e9f52b8c165fb81fc3afcbc7ae61f366c4099e3ac541311cdde2eaee86ce7b1cd59fd7cbd1b898a3810a3deb0b453e7a95de5876c794f58395698e37a91dab55af6074e2a333f9e8db8e942a1cf1eedde7701203d199ee1c8d20963b47e3876d27487ce768a4e82a45b34ffc633efd31dff248ed587df49b2324439d52ca398b7033c5d2a17c3f442fddba1b98a3f11c8d171d9def8da177785efa8e13607420352f7de7716474067cf498610d5ec3b7d7b0430c7d3b901bfaf6d00ce99b73ceb936e2048f532edfecfcb6f3edf5b9e760c845e80ed7c088e3523b18f0ed405ccfbb9cba61c0b733e03b77f91a6d7777bbf9449592ca0ba6ba5c0a41331ad3316306e8c7fb79fae35d79fa9e02fa04bd514b8664bc21dffd20dd4138827848a7788777d80eb8c3d3fc63070b1fa8610c11d11fc47aea3eb3cb3b9e7585ee55ab7a1563ef45e975512f6428e142e7daf57d5d9001697c5c00f955653c891ea9f4b75d5ef3f4ea20afe90aaa2050ad547ea182500d636262dc43357137abd94be317a84f776f04af8b3aa517a5d7452ddd224249a7591e7d9b4ea77777378d5bb89c7aa4eea253fb1400025dee1e745d20d065e9cb322508473c747a414dd336fa51cbcea95fd9baa17e5d6fa399e6d8ab18131b4b95210a88beb9ee77ef7180a4dc3b17bdcbdbce234182769df848f040bf3d606e6744f7f3a9dda2d91810d3ad6c446a0233bad84c7f3644accd7422d166887306d8a24b9b99579002918de57dbb06ec1c18cb119d959d33c006db636e1f4c5152e42da77e79ad6d1fdc5e97a75dfeca180fcb9f532cfa99a9e863fe21fa9df9de12acb0c28a2c64210b5ef08217280f14a000852aaaa8a2a58cef302bfef0ac9b9875fa65df44b792300959b264e9a28b2e9e78e2092db4d0820a2aa888228a28aa50852a381b43a68432090b5a5336f4020c30c080020a28c21086304c1eb66cd9c2850b172738c109511a469131c68ea219e506e928632371c51557b411b13dbe9e506e908ecda476472773f68cd0896451e5a75bbd5912aaaeb8d1712c9f72667a4da7f3f19879eb188906d411487af51abed70d618c526239ae5f5d6b5a189f6b592b10287aad387c1ab3bd7231ca88592c35841bbd8aaebb16456fc96ba3ede1658e3d8e86732ddc7bed2074103ae71e92ea5c77bbeb0d54d70f02da5b796b28d3f65355ee750f07154da96a9e4ff5edf1862c889005ea618433f10cf539edb9db7357e551511bf6fb51dd4ca578a379bafa87c3bf477a37307feea81a0370abf6ae8e7a4efdb1ec1c0dcbe655af2c9f7134ac9545afe9345322650634d20ad555d1f42dde909e2f47c37acf6541842c8860af07a1505d0c8489fed284bbc13871943038e2c943495f940a31abc27c8bb97c438136bac5878ee11855cb3c0751970ec230f547a9766f68e256a71657af99be07a12cebbd97295e2cb182545417b35d4d1e7a05a207f59a46d2387036baa77df0b087f0b09578d857a81b798a254f4f1ce2a547305c08251547c3818036340b31c246f24019e81fb7402437b85bdcf210beb8a56b741ce36a02459512bae52d5d4a08a584b07bca08e5cb86134eaf828a122b9cf8f8b854b6e8c420563e4a72564f60c2111d3b24c742790216603c7528297479834e75d005dce838d0a96f10bb814ee946f3d6303720e176de1ee4fd9e639ab776e83053173b085534ad77475910a017044d36ba395bd9725544b4936059e6a35e8d62afb09aee247d739290cb24489ac764994a57b42ba42b21240f097520102882240882502050d3f6d95048f97669c5b7c3a729713757250848a94b32ea7a84a7af2caf74096574acbfbc7aadf84a71df5fbe598bba79f3467d08d53c34bfa1505366eace55e9291de555697f4f667ed9ca4f902041726db4793ec25ed55ba5d69920781eda6890ab72a15a06338d832adfeeca76eb494fafba795025d77ca3242753a59fd7c0a6528d54ead01d0e6c401390f4e8a16b38294f60f9761f82c091733e9a7f33ff1e8d348a4298ada078c59c9c97dbfb3967c4db4b00e69b01eccb5ba3b72e1bd49d73341c0d46572e25cd3087ebd1e1dceaa784590ae8e6acce72ce9f4fe7dc67388748e80e7c74d45ddeae9381d43997d9e5a0def50974b4279f4b0973ec3f9751caf8605be190c739155ce750ca487d5ae767de9c5b6ef9ed15746ef966b3ed157439f69d93d68b9f63dfc1e642ca465714c26c055d745af2c922cfca8c1a64a852302fa893a9a491b2d11585305b41d79b543a8a1385305b41179d968cb01f8e49524bd8aa5de15694bbe182d0f3f4e1a4dfd88b16f607ef4dbc8ad1e24660ce9e5b113ff3261d36e1be7f3a627a65bd97f821790bc79f27f1276e69316e43873ddf556ad8d02a366c688657a0cc133776116dfe244f0caa8ba1d1c7ea2f1e30547d44a86f42fee5ed72297ac27d0f5f1350de2ec843d9fd35890d7a387ca463fc34cf1844b4ab0354bdb4d1836c9257bdead73ffe0f0b13948bc5b7bf225d636bfa8e6c9e83cbb4918a82481369228d8f944cf8b8abb4bbf201d57a434c58d14c96f9f86419133e3e3e2412133e3e3e9ae6e3e3532a31e1e3e3e33e30304cf8f8a82c8c8f6a890c1935d4e0e3e353800c062affc6a87d29adcb4e7a6192499307558b85307904c95f94eaaff623cd92b0958c2493d54eb68445fa7309ee3e255c7429259caa218cd29a4dd05caea7569826b050e8de8c4432994c2617d38d00d954bae3fa99b71ecea63e8cc7e8adb97393bd15311d03a816b5a8e52e221164e9bbd2ef4bb7d28a96cb0cf27ae5437f43845c2c47bf8ef426aef39113b7fd39ac45601d92d39e029aa7344df5a1d7939558e584ebdc95e0713740284c16a1d32b58f1b3b0c7e368407f48b8a017a272726bbc1b345eba4ed7b80fbd0859d335fdd2830f2eca16a6f439b580e6098c53164e30343ad06ce185cb4b9617e93684e190abcb575aa439b5c3f20972101817c82da7d8e2122f4bf194d0561bda2a60618d3d0c621287b69d20fcccd12df98c8c94e071b76bbc081f9469b8d2b5d8f0457fd80f0dfbf50e5ed4a4ca896bb966b9c4d3c2b05f5a96d5af6bdc71d5c0c67844acead57bd80f965769311ed167deb22e6550554c50af0ed3a189544ddce8969338965b59db91ef3981a06aa96087e7892580bc6b3601c89d4d4d835650063a850958c101405fc205b905c242a21b01b201b9f664041c0604c8651316c82dfbae287210e0af1e7f65bcf578eaf0aa5753f81c449ba8d699b8ee9d4d7b7c4f5ad1254eb5ef402b8a1c0478cbe3b4667c5a79e331435150554d885449b8d11fb51ecd5a7c2468ab7ae25ade035a5512eeeca14accc539a7db49a5bc264e11d143f4e90e70448460c6cf8c43adea39a0a75e27b6637ef589e9705e2c8183293e382f96c0c114354f1ea0a7d93eea6db97e16e371cdacbd7731323251294c40aa3d0713ba88819d1f1cccfc03ea33c668493b3110a803763e0e6073533059f23af02d1e796b2740d8c9c2142d2af84294243c4cef895dc0f1309b89b7328ee544583904333e7af4254021bdd30d8fc05cc4bcd6ea8de1a8d5a9db5e3dea53f5e4e2548faa245c1ceab0ade6ed7d159282d7c52609cc8d00d940d7b6ead80e19011cd8bc4cf162091c4cf16209243650d5842a09ed2f87c5a91e6b75d08772de7abcad7ee150b7dcde99b51879c43906af26af73516eb43598e10ecae3a78bd0235629711f76039af939e10583946e9de5d269bd72337abff820c663e6fba4ab9070a594deedf5613a5cff8b0e863609bc95edcb578584fbe0950733c5dd834107edcfff981f24c344f0b12096bdf2416cc8dd00f278fec70e3c220fcfe5f9e7f6f1fcab42b5c47d3edd161e88632e56e8d0217c98a66e9c4b7779936e490823c6437a741512eef41dd7f7a06e797c530701a2f8b9859fb90b35deaef19e74d18231f67327d3391a97839e06d62cb738b5235f4d4985f1db61f7b743242caf7189eba46861e5bd2b715d143f507e9b011320bd1f0ee009793b10f613aa3bc0bd3746cb618451dcf731c6b7e8fde94f5a98823ae46eae47a3f28b0e3c290cd1812fbea0314ff117d3554fc2e82266c87d8e04c851458a94b2dc0d3a413939f2db2b52448afbda639314c4f8de8414c2bcc5f7e81756f20bf2e5c82552d798578cc2dea5830bca570689716b204efbf369fdf0523bdcf7145bd802162cae237bba3fec9f3d517a68661c952acead666868f56bb3b965cd347c6cd93b2411b22042cfc39c192ae30d2aa5c1dd56770a9e076d949cbab13c5a1988dc430e1d7b445b3ef8d08ec529afdc038859447b8b73bc6db55aad56bdea6d45e3b5b50a618ef6513a7c9c9871c82b983da3c17d4e4a7275a899567b4ccc4afe40f30a875ebd292ee542a5ff2ecaffe30813c11e91bcf9f0f1da382495a33fba067a059b10e92550c65a17dc2a418f086dda94c8ecd9f38b3ea265aabf2a354daacc8d64733d4820185ff1e6b0ac943c30e6051dbc5638b40dcc2fbfdc416ae5547c2bdfc48d879d778e79c8d1eead2928243da04c7bf486413b6999185413d7a3facdf5f010851b73217137ac6f18dc976ba2b82e74a4cee4cc2c4982c488bbe2be703f4e8c19a7e989b2435b5d822e5dba507918fd033352786e7b8ed1ec367a7dbb226773bcdffab7e7986fef5337d4a7ff7337ba22b865cb962d4f8b96d6a2458b16a8458b162d518b162d5a64962c59b25859b264c932b364c99285da0b0b185bc6004199828595aa84891327acad20d1b6f3db73e9a1dfde6fcf435e035fc51bdd4438a16c1fc3387833e2615790b539153d3eb43cba9571a8871e82ae1928b365c896225b8e6c39e2610163cb180d650a1656a012264e9c885a866829a2e5889623241630b68c614199828595a984891327689621598a643992e5880b0b185bc6004199828595aa848913276c1843c22812c691308ec0ecc3dbbdf9c1e2641c186e8b9371373f6338197743ffbc3e5108b31d0ac9d0d3bccd9ce97d998f112ad641788b7950bcf2067a0fe18c3513672c5cdbcacb5d7ceb9a13aede52d553eda9b5718dbcbda018217eba194f058a8c31c65825c64813a765599635a36559966559303715d9552ccb82588ee735d02d48218d3fadbce5bcf439e79c73ee3479e973eab026c572c089659edd63777eb364f7f40ae6867285ea0e70af0a21467d435551146e8aface5378e5ed4a09eff4bfa04b2c490f1d04ba26963af2291c59d986102d036de4d032d0b73cf40d07e8b0780eb061df51cc39bc7d330635a4fb86305ad1b22ccbb2ac68f56837a10e6d438796796e394d8d11568f6e0220ac31aa8bb1eaf3cbabab0eb3f62a41697e52b7bed91c735c4ea37f80e6b71e1e0773e90ffa86e548e3adbb906f39dcb025926f32defa1680b7bedd922d612d00ff9c24e39f6725510eff3c74b37334308f5e034578f34d00df3e65752f67972e5d7e2caf383e08bbb73a3e40f332e3c4ec621cc34f152a55aa54a1824547e99eeee99e86f263ed8c56c426e4a7f5de09b61d5096ba9418871c5afbb93b3c0edf308a1e4a410e631115da27eea3a8967993a21e74b58dea172acb9594529aa773d8e53d794f5e93e76449922449940cd141f8d0ab966fba9c1e3dc7cc3df4e8556feebd6a7a7b4b05d1ab1682875e750f116befa357ed2ed2b636c61c0d7ef42d15b34ad17c90f8383a4c5ca3030f99a657d092d2a7f369651c275d8c93db17c6c97dce538fb057c6563f676c34913969ac43cfb084b4e7beb7bec5efc73386dbf288603976a004b95b99beaf9848d30c2ddfdc144b07cb2dcbb2acfe9132e6a5dc72a4ffb8c4dbd3117fbab3f2269dcac7f7539b7aa1ba982d26296225c62277bbd6c73f9d9f2f6f169d99d8bdbcbd677fc6f77c3a069afeb03763744a13e37b8fe6d1e42d0787eab0199f5c7dc554b67b5d1edb46c82fbfae2b4639736ac7cb6c89708cbeb9d8cf9238468bb9b92b4888bca623cee9558b7a401b226f0f4288ee76896ff498b71c1cdef254f42dfeecd8d3abcd5901a265ba47941b93d8ae2e8033a8efb9506e4afacef7d8acadb9e772f8a4e11390251ff5ed59899bbd027fe3abd7586bde362276befac322cd966f4418f1d59f5f1e63b6346cdeb0fa633ec7705e0ef2aab4db7b50f5c8e3c6d7ec1c0d48a568e1ca4707c106597e65295af8c11c84ab63f8e6c683bce60fdcd8680eea1107a831c75c877edda8573cddb91b3839371e943f70e3410eca4040aee3d0a9c37644a7ee96ec42c5b9cf53632032861e1703a17428fb75ab5010c2ee28a01b11df9b0f78313a5075564acb771e341fde86f8e715832e4ca55f78e75fb4c12b80f459032d4ca2e88344b0bcb6ca09f7b9f5fa50ce451a3178e7ef054e01345c012c774e84dbdc19dec1978277fe5cf0cee386c3cbe4d0354a20932808176288aea172e71ae840d7a8c19de7b81b2377ee01a700cd9d83c0d520790f28739d52302fa8cc9dd7401b2b77f4bd1e82c75d7157789e90f9c94f58e52715a64e6bf00bbbdaad97b5b36bbad68ad5f485e1a88ef90cb4f18cf4b0dc88112a15aba0b88f7237b61772980dfd208751af6901b8aadac0256431dc6f6d3f66b32c7ebd2a8655c744490871e16243c6c5861236c345ef6bc99ab0196e037e2f00ecbec62e942e7715abc2a832ac1846a0e5625431c285cbbb301c3623eb10400e63868e97bd93c2c5e767207143185ca2781b44efa47049c2bb8a55615ceb9d142e4dde556c5d0098623afa316b0652a98e8155ac4a4300f0fbd28ff701f00ab22a5ce3548d11eb698b18f374597bc2f5aa3ca8f94e8a18a2505125cc66b88ae16eb9e58ea78af1327a27850b96920db58aec08f7101c8d5765066ac600f20c4c061598f83660f7a31bba09948132d82fd64bb278b9b01cef91b8c16bb536d810aea2aab2382a5671b9f46da81e83dfab30944a856139dec778bd560000bf9721c27ebc0780d791ad38e3f7356035ab7a2e751bb07b15f6e37df66e81a361b90d45885c556e37d99a487ff5262ed57a459dd2ec443300d8c560f700c0ee33768ff176dfd93806127f065ef217c618f72f9171c4dd1e152a586018ae6e31f51ab025021f0f0a752f912b3c436e9059f90adce8766833e22be86919aead015f4f97a8d020037793ccaae8d747721dda7a890a379333a460703bc1fd0485db09923dcdcfaedc4b4eb8997431e186d23dd6bb523321957267583506dc447805b09c849b49af32f9f5613afa47b949cb586e751414cab23ccaa07203ee268d00cb65b06b046cedc45bbec2eda4652c6fac9764f1fae26ed22bf82117e55ed22bcb43b8996061b1ed95e532ae881a66b30c2b24546db94eca1598bc93c265c9bbc7707f35d987e1b05986cfa54ebd579663b887e06858582f71342c5791e16eafca927fda09c34131cadda879db7994a301632d5635e13a296160f25b57f196bf33fc160ff24b1dc7f4ca9a17ad356f2278d0b472cb8891a457ed1efb8b5e35e4690bcbb793c24593ef2fba8674afd654430d98f64ae52a97e1ab98c630ee301c29afe9cba753deae81ae01e32f28ef2847f43c49f2868c61e3dd918df785ad585c1d92401b9d04ca34952fda4dbead4af748d75079abbc6bf0d3d7903711bc0caf813270176923b98f604805c3178cc2d04aafda4f58da4d795bc521df5eca9b8ff1ed5ade505bbe9d94241b61f8a4574e300c6158045a0c8f40245460c329df0ea202e5dbafbe4abebff886503585dbee90a789341277a38bb8ef28e3592deecadde253b5a5b7dd69f9c1c2867752c40086efa16bbc2d90d2bcb99452d2f7e3ddacd9e821cb75517ec4b8bd7a3fd2faf7b3440984101ed12b4885bbb9288f0907c6132f8a378587fe943834dc780f5142686006ca401bae0344a00d216d2306fe0c79969436487ccb71a4ebd0ab9a2ec42ec42b4801bec7857d5c58314ce1be1785f9a2301f161313eef67eb65c0fcbfb4252501644c822bebf8214d1565b9d644e3221bc1a1d74e0e1c805e54dc7fa78f985851e60524acba5e538d20a59d421929792fab5540cf6da74e28b9eb852d6e810df87906b833d2c2d03fdbe944ebda61370371705cb43eff1036d841c08288a50e5a1fbf0ac40fa704812dbb3f2b08bc7f09024a816dec23af1a50ebd823cf4aae6aa96dc4d473ee5c14a2c1479f932d758b93af0d0355cf3641d7051648b13efa45831f2057827c54a927f3fa22184406996d2e400440fd514f745f8e4fb5141b9eff17c8d5625c5dd5e93a744a5e46eefa7899225fdefc745f9f7de7b4d7eb29fd1cffd096388e8278c22a19f308e603f369bbcbe26a0bccd9832d5b2959bc07c43136e7d27658b120fb17752b62879e835baf860680bc1d32746afda413f5b7af57ef25585fb9a748dedbd2d3cefa84094103436ed39ee867bdb036dbc6e729b5856b4228c524a19a584393e79ef3df982445803a7b4a8e594526a5974b3160e9dc7a4ba187f503b9099effc43f4a99b97e45b3a9099973948fcae81efb90a09778b3fdc0f0fcfeb643f1d421dfbd3c737901f3b7c3b1acf3110cb75ac1d16f89ef163877f8e46b70f3b383c6fb7d2c6b38149a8f0e19075513681c8ad09407e416e1de426b05eddd98070ea39f6b23f1789dce6d413827928a75ec89f87406eb39090835c88f5eab092a321240920461b12219fc00b03135284421eca4698a066468453cf1dca40dfb1c170ea652040847c4451117036221f6521425c68c786da7c1b01d4432ef26de608a1dc03ca2801c33b3616efd8447f38c80ef88f47880f28037d8822a00c74201170361d8032d07f44c0d9843c02ce067b3c3fd88ffef7923c23499024b9e9905b2cf25a237e39c8fbe817fed1af07030284c8435988909087f26b9223e06c86bc27bebd9e507bc4a2907d597334a83553d19fcb9c8a79471ce2d19dd97408b599bc142f9c78097f73ce3907a382c2f5ebbaaeeb7af29d9d04793bd12b59037fcca74ea13bf788fbc92b403bc4b20974479d366e171d84b8e5457538875141e1361a7e836d9de1b7d85c747737173e1eceb8f9789df8d0237e9d5550ee73b945c7ee86b1716211b2c0b2e563843756781c8d9b2dc6705246ac86d585125e0843f7aa7384d21ac1ca6f3bbf0518bf7a27c50a4ebebaa782eba46c310573da359cf74b8f2a78ebe109a0f08215ebd97ae9f16d9068e47f885ef015bae06b6cc157c982af963dc183301d59c60a6bad5debabb55a1326b4ed28f9ea4e8a95143c08ef7cc8b7cb3b14daa663ceb7cb2d1d973fcc013b7eb9298890a4bef428a5acbe498f32e2c8ed721f769cf0cee1b05d3f5c0eaf1c57755f03dbaf0d12b1f337728c31d76771f1aec6c55bfe23f2f0d0e075a4bf87d88ed3cbbc89203a949609bac805ed482d95464a2855344517d110a1bcda85564c6813c1cbfcacb0dac5d7167ad46074313a7780d026827fdd8577a14cbb83827c1ee33d93839411d7f00035d0012987f0c097f8f1bc212d032960a7db7bc3112c8adf0b492220bcaad7b431d7948b728c39ea2d94cad63014f9ad0033202e1e7563c81897a82ea646f77a3ea1af76a7755dd78583e2cb2986e3721514395487e170ecf2cd5fd5e4c2775292e8f9cb6b18de7ab52c4d5587dd8dd1aceab917971b7a27858b9fafb82d6cba4acb7defa4e0a4c8dbd03b292c81869fa1b799a657d64caf72b81c94b799cbaffe47afa0e4dd4f292dc8f6ea7289f198d7f52e2bbfed405f3542b5d40c17c58143396a9b01d9e82279f5ea6a3fa1e2ab5a28555fefeb4462ce39e7f48751076138e69471448561d468251694f0d7299e215f81db2421f207c370181223af69581d44e783bc525bf1b3b9188991679888db644ec29988db8c320d94e1019d5684697a5571c87390e25287e5a858a50508482aac5e2d172c307938f28799e1ab77941f80e1ab6f443c96b11cfdab5e819c0d69733dce260767330373e815c5d32ba63ec24392a8be02b719597e618d0a48314daf2c27611d5ac6f2cc41791e18009b19619a4680e575da8a859f774fb18e991c64487157bd32c0ccea2d5fe100ba6c05c394df9c8cd951a92d82a224a1f51ea5f35d4b4a08b18b96a53d1d8e0b2bbb9d62cb2ba5791301ec2d34a712e33eed391add64b8312dc5173ced2ea6510f6766ac170f3d0665e5fba1db683e6ee1a3d374932c171563e4c2918ff159519d37b9913a881d8c09b8afbd8a2acb2a06b76b38295e58f976cd3b03d53ded1aae1b4775af42c2ad211c6ce1c815572b95f0c954ca25534e994ca44c4ba5cc0699f1242f994c265ad2d129954e39b6e4242f69575c512a5952e9de177b4529cb4a25189852a99479c96b56c29bfd5286393c79096a8e5372936b2537697eba579a4a6e2a95ae759d944d011658608145162792674e2261181f6592c55bccdb69a79d76da6961606084781ea6ace4d968341a99f215e23327e54d881ff944c5c49cb2c88979521624d2e944d2b417544e4c16593652a93e4399306a74da6ee962534a88df72626262626262627a15e3a20861a74c9932c50a995b9c69d64b6e2dbe33cf925bd2c9ced1f5eb0eb35ed337270711ef493ebd844b77a3a79c22e5548e9393fce6946f4a79cbf9d2cf936f25bfa59ca3bdf554c992fce4243f6956389d4656d0ac96b793cd3213eae545b342e9742a95eecdb2d28b974a3ef29797cc5fb2d2e964f2a169a91c25d7fca6e4da8de6d78729a77290bce437a452cc8ffc46a3c979792175cdc214f75ecd2dd6fc4ed2f5e9a3895fdce4d65e9f25cf7cde649747d7f2b03ebde4f7c6faf4bbc3fa749b85fcf50c756f4e5d1fb9bda429a620d9526635cd39aab529ee75ffebd875eab76a9ae6a60c3b6979ebd71ce5176719a9e4a452c86f86b986dd672193c96b5ac3ee4f26af2113361a91aed7ca6364c2ef43d86894b797e152bcc9bca4790dd446b83f622312e6598ebdc246f8deabd354080c480c3e3e3e3e55aa54a952852a54a10a565861851554b8a7172dfe487b29b9c94d5ef2919b7076329d847cf6e2a5f84164bce6a697698508f909533af59bda54f2cc4b970aa51215b20ce76ea5cc9248303aad528d5ca3c268e4fe23bf23a73e22ed8c9c342af90d95f2d65f72948f30096f453ce9e525bb8675fa372260c94f8e39c94dd8f58a74bd24e431479d4e5ed325ec3e842b867174fa4f39c8fbcc379dfecc6f966923aff5b4c5bfeefab3bc3d123679096ff14ba65c034b1834f24ccba41c7b7547a351532175075a68a18516545041051554a850a152c5c92d3e99dcbac984359ff99a486eca46594e91aec949261f6926acb9c332cdad8f1cd98ffb41524f726b4a65d9013b4fca9c9439c9b36cddf5ca9a26de7cfc44999ca4e9c4cf3293db39aba8c264b224933d9d5e5026d7aa309df2b6f3271f6113de7cbc092633997ce430a6d3093b95b0b6dd3f39c44a8e63f2eb25935f37f9c94bae9d4e3db84f9d4c7e32b9495b8dbc945dafb4bcdd8fa95cb0727d987cf8d0895674a2159da813ad5c1fa6542a855dcbf8f84d27e2e8c4d7e2bdf7ea44f73104295ea68f304cc9e768e4166ff6ad2955ca3737c3786d3bc230eeb091d7348cebfcc81bcb61f2ac94671e6930f647338f34bfaebd482185a6bd8c5ef276b59155a9a448b9c95dc754fc5136d2d1b61cfb385a0e42f2518519deec8f321b6daf2c4a8727952a3588c916d8bc75867c9b13631836f9ac58cd4e356b5e319f23f7cfb22c1b79f5e9d9e974726c08cdab6378fbe11f04e6478e9148a411e6d5b16d88c7bce26d88af2fa30df353bea1898b651dcfbc662cdf907ce4241fcd5ab718e9c887695b643ad2569fa33a43d874e72f789b58a8bab59ec4663d88688b2912653366d450830c192a552a0503f3f282429d4e2653a9a4692452968d46f78a44a11086595b2b08745da5d2689437aaf9366fa05ce49bfc929f4479b33ef350286fd64fd7f0cf53d6b4bcc5cfdcb91a23cf86f84d47e2e8c8df74e4937c8e460e31d368fa4febcf475947be75977758ff1076ef438cb89857a490dc6252e6b68ef0f46ce6693dcb32d7b26b738c7c661ed7359f6e4d6e3dc32953beb9d3b56cdd5a9f7923c1cf4fcfbc0666996b5ed3d65ff20d4d5c9b7352d6473ef3cd299b6f32b75234cdb71c2999e7482356cb2c89248dc0b89094942df311ce7cb435954cf3ccab968d308e268dc0868df1b826bf789346fe3ac46e343765d7ab7be5cdc83510126944b484043f30f38a105ec57634aa236c77bc37b9c5261f9570a6673e473ef18ba37c94351f653eaf4f6bedd5a68fec8bbfbc78e6136f39aff9c8b32033fe7a4622dd9cf9c8339f23bce5fcc86be0e441f2bb65fe92b59f9eca722acb3777a4f3ec75df72b427fd208201aff39e0340de93486e6dc92b90e9a34cca41de8f4c7e83f9d63040e16de4ce4f781b5d5e318cc40854e049e004793fbdba112917029393a89ec4563dc8fb90d7ccb529643975e39fb97fe6239df73aef49a6bcedbcc947382be5eb1a76f7b36c3472d736605cd3aec360d7a31c936b5e351c530ef29ea4bd2795bcb11bea24af249c79ad8d3960a74df83d09f793f2f69e741d6226bf79d3dee4359084fb4d78bb0efacc49a5dc03fd916b79d37ee4996759c6a9fef076738ef6d55dde51fd3df753db723e75e37a7ebaebf99971827890f75b90e9cf75660ef21e870340de3fbff113fce38171213b74e9d2a50b4c4ee23515ad415f54550e15b8cf9d942b43de8b587b85cf7528da57832e9c7827a50b9edf7690f3fd173fefa47c71e5b795942e8cfcaa6bc8c900ca00226e43330e2dd338de0deecc31beea2eaef420efb7b7a9543131c7bec27ba804a52ba8444d9774490b15a21900000000b314000020100e878442916034a2ca82de3b14800a959244724e18cbb228c9519442c618431421860000088888ccd036001128334a3e0159c946b8fc5544204b63232aa5c7706a6c379e132704f1f9234e6e74ecf27aac9a0dfbeea5fec77626da7568ce0f5f2ff99102706072f7ea9327bb1a83e928a55c3dc10d0ec3b3c5caf47012eecca2b26d725192523d977cc2dbc0f0d390f9f1c6891fe12d18893068ebf652bb867889c04f8e3ab265b4de84e104e4b29aca6cb8ea6531088d27694e44d53287d5aa5d2e23c8383db52fd3707d1ea9312097b38252c67a9598b75f621b50f62e8e235a1f08b2676f23f5b1f27fecb28c02f2620bb6bf9ce864013b08841f88c402875cc753f1e29a1899124470fba52f9408071eab6c9ae42c47dce112142be6ea4f316de2cac852a4a7a25e73f8c6eaeeab840cbe99f25ea037a188ca492e11ffee72cc43c160c85b23cfb71ee0c1b778f4503a4a437546118f43eafb4c6df3f1af7189c1bee91b21693c0db140b7ce02a5740bc4ff0db41d607de176b8f3460f7fc2a2115075e391311a768832d927dbbf719268c3712b863aef2c920ceaf1e5b66725d3b5236e1af7d30d05d2a3cb91e83734b3dcba0e565c424b5341f857c2fbdf3db7412eac530c8119f28382668a68942d234c51c5d7e51bef3272d9f16429f36a9e659a62700f1f3c083f729faf346c4911b450591a3ea157605346790998f65a922fb44649142dc222f3a40350eb5b06db098f5d15c0f3ce4fdaed21636fd3ee7b71034a6e4ccfb08a37373e899924468ff8615decbee35a0f69c5b96bc8c0d5f60ac6c63df2519ce96ca2a8ee365fa27834d94540039b84e1a5857c4b1dc84701b82470e03e1116e16cb4f6ab842c35f5e7d156e3f5c8e0033bd25c75860053937c1ed65f3704ffd16ef6772668cb04603301d3af111c663221b5e4ca9005a65ac030bb0cc958950bcd9f8c0d173aad7edd22236327b2f3c10f57e0ca13be48c44fd6b5de9437fdc551f82bb691bc4a0d8af21d774210208af240cc60675164134fe3b386332c71060a12d855af8fcb5d38236b34163390024029b26f09814f1d97813cfcf75380624b98bac6b078c1ee247058c6d208cecc772e7f9e62f5716c1ea9b26a812b91ab7b48533d35d85e0e6661a1b8868e330454a6af5d70e1c62e961fba6492425217d09560c65405b35b131e3898bfeda014cc8366cc740daee602f3093834a9261b084c43607cec7a6ab141171070680486f991027321e69c51e8aea20ea6c81b46910f07b36420bc2d7d4691ef96dd82da9f1aba354adf9854dd243190570b693a80eaa3654e43c42dc9c16663ab4646e325424c6a916d5200942720b582890813e3203340aa5cf15d8eca3551263789889bdad879fcf72fb2b1075bb99de1fd690f0ee9587136968da8564b4775d502e25f7c72d5373145a611330cedbd292676a43412dcc348d6c2a70d4070173487b0ff19a0990ace1fe71d1ef1ecec6848dcc28fca9d0a7603ecbe7fd704c13e80ef93b3f524d4c182d4990111eadc4b0f5cfcd547366be7de288f1efe15391415b025a7d483965e8281c316873750a784405afaf38aae318f264296fd5572bcdcbb04c237237f881fcd01d83ee1dba97e6827f1f8dfd5c72c3d49eddac0f9807e14c9cb4cc384752b5fcd2a97a99eab65c294c35c1623ecc736ef2de24a18a1a7c462c12944d543e61c89e9cebc38504c346d50ad6f2edd4771769cb5b2d7ad65b45d8a7f78ca12c1437347da179018a283105a2a15c4347fad1b2b34061a44cd3622e953665814936601d6ea2d90adf060c8b3fa609aee6094fbe1793ce9044bc8b4f36352426fa371db842d0373374fa727c5994b270b1364f7e263e9f984116413cabba701761b65382acf6beaa53f06d38fe120b386fc9f173dfba6abe2cb72bc1a4d91a02bb3f645e8ff47b2548dedc21557a1bf98168a2e22a032b620fb44124319db1d884384d8e331d4f99be6fc0ef70c107b762795cbfee32b5bcd3241951b8da0484b8862619f4762528444e584dae17a8cd40272d79967db2c8385ebc9224007daa8634a8fb65d6d532c33e842d62841b222aa153603c9eaf13763fcb3d57f5718134b766349243af8d632e86f0edb8593332600e1f1606106ee69c32186e6a5941f84cd3e9031d0289249a0cb383a471254f1ae19398d9afba9f0d04c2fa773348a2006693f0bf9a2c0260f99095249e9fb4f7a02e80b0ed13381ffeed4c21a2b0ceb2296d298d2b21afdad71d37a4962c9e5fbd7ecae1ff1470bcfec89d4ae929a7cee7e22452891998b4d820191a42f4cb62574505fc454795f4bff4749690e5ae2c372f85e2de79e28a50c351577482c482d61a4e390c58e8bc8b2823cb3b4cae85150219e635ccebda188e6ce160a48e56cc71e91719c900e431fccc9e11d5d2a3b12f808359eaa80a5b89b7c08b610b16669ce4164289142cc68507a901a8c2833ca63f22ceadeafc3f08904f2b328a066c9b5f685be9ab1e2aa844c22653aa1f520517d1e0c87f81c2be6512056d97f25c79b8873a003ce8b955c8eb5bc358da1b756e21307eee2497fc713f397464d7a098c7c40a12535917b60b2d03cd59c8b812883037d27718522be155bcd018f76662de116e65465b1bfd6b05edad742382e10fc549ab4907994078a3a92db4536c23d8e71e32d4d0729ff3975f0986f3825db9bf0415f1050bb48431e82170d075d4f855802b91792141c4950b0803d878e1785f03583408ad4f17e823d49d034918adb94aa119404b2d6d5a174ce284fc43c37b8301254c1518ef038144403f72b2079bfdab10c27a5e8a2b96355e9ccfa2f670ea7f4f7d8a8de5d6a11309b16a849b5428d4950faf320e0a37b811fd9585be0a18406563433b4f7e44f1a75424e604bb740682b1e44d7a28bf223ad72ca8b64ca39210427afad00d597ae9b6d9f406052d0027bd0afe0c6a49e6edacf1d34811f65c7fa734cf019e669438592038ffbc0bac695005b245d81a6ee48c6fbea5bc2deaa440ce2eef4384ed4266fa1f6ade09525ba9010b1b7e646e12e680da79e1c27218ac53c7e86506594926ff4badf6f09526679099b48eb6307ee4a8ab7f5a01e5f150377c5fb06ac7843e339ca56eb39eea6bb53d433b5f328ca92a88abb60b59cd76c0a7bee22a0ce21d99fbd1524189930806a443cb82bfadee70d9a04f293c45d7c4eee5e4b9efbe0b7963315ec64a2e3601ef7bb7c765126b4ce7d24b5b69a6db14effdae028f43f7453e97b50e4f23300ad8376114a656668599dfedab889bd24b10de6fde784fca65133f7aa167b64ff3d51fed2a8297a55ab7965febb5afddf58337b65126e547e9ab36097def6801f6c06687cdf8c331312c404f45cddbe3f1b5aeafd5dd63002a4cdbf4fead9ac5f1467c3cbdd171aac663b76519ee0c10da2cae0a66c6e3302e287d15db5034ea31568fc14c35002167086ff97f83274b0bac6f2024e3b302fff787e3568eecb109e2f89164ff4a78b4665519ba6929d07646a06837f9e443c6192184c08d169fd0a8d1ea70186bc971284aaf2f5cb5182c7c22ac10ba19c024e22e25f434ade9380562ac5d91b42e8be6f44571f6e79c1434d29607f175043d358831dfb9822f3b4d5e814340467c2dfcd2da303410a986a232bb981aa2354308b233e51587662814dbb3e659a6d77a4a2b9dbd137969249ea43a805e83ab0d477b167b574e2fc6eb4792fc0c8bf5afdd7a8897b950420ccfffb44f9a791a6e8b5167b302ed62d282a7a99b492a4522da631656e44b2cb2d6ec8c2030cbda621691cc125de5004159031c551f562111f5e6c4dd916681fb51ac84749c17096e007b4ff5cc878309b7c737ed2001d297eb9243c4bc24ad382e5ebbf10c4bd8c51eb180d5aa6c6901194367528705c0362c00b2ab50924593f6c3272e0292787f363534807f18c6956e6afd76d1ac7e19a7be3c628f77e19700035f55e5481160e7795da8d66673e6ea46b66349f82ac8016a483d50876b600bbe2a45679afe2f4b9262a26445900e689cd638bee9bb1e2f44a0658f40537073c90c58bab1b6abf0f0155c5e92d7ecdedb0cdc7d100ff5f628228b9d49a4f5bcf11979e1f2e41e505f10082d501693eb601e5fe7ce70e0ccb07fb1ea00fa04baa864b276be17f9129cf423470c6d347975ac2b005a00fb7bf1b036e43dc16615718d3cc8aa00fc25c2d1216a21e1638d3090e3876a6db4bb2c141a001678cce5e987bad70ff1c0d968effc3c14c2db093ae1e4682693c09c10d82b129931848566303e71ac370d6b51e7af67ca9f5a3d3e1008ed31aadd60d05af344016f7d3178bf0bfd6e89d020943950eab05c13e689c28c2d725184d338e61ad55612c2fa402759e728c0b43c012a9aaa3e7c78e19dfd4679fd689842501030f925078a80e4a9a214c6fc264a6da11e02d798268c666f410fe6aa2eadb5f781ade6245ba41521f2bfe8fb359c4ac387ad86a24052349e29f3071a7cf36e94b102bf1b684d0cc43c18843c4792bbb318a4390a8ee66248e58a4e27586c35828a531eb5720000a0269270755bfc29d80860f44479ae2561fea3d2d061e76da43904172ea75949a49bff4b5867058b0e0a817f00b50b684780243c4fefe2b7a6079ec533087abde388c17b1a5468554c796c8f2e2b744bd2e5fdd56ece34765a22266971b8d114ff218f5a29aa44617e0de512d674a96f81744a1699458f02f3801a62c63e197e0849c56c4921f82099a4aa1657e8222689a258bfc0882d054252cfd259830d3150d5bd43bbc638bb934cce7e5445a42bbfeb87b1fc6cb42d0bda475ff14482acd376ceba953d21432807026e2e71abc5a51778256744ae839e0f6925a754c6afb514ae37cb441bf51a52e1b1e1550906b5e2ceb3c67f4b49d5329295c4c2c507816031f8e3a27ac90cfa7bf29f1a5922dc797b25229828a09954adca4956043e16db3920287408c279df415efc3f070f6b3b4e78cda7c7a63ada13250083e231d14f82248bcbb41f237ddf637e488f53e0f7e56ed07904938def6107ad93dae1280d17a40c6ab302f0cd8ec28cdb5caa221d3af43085e5933451281c17be4f0c6672037a0b7aaf611115a76b1b036aaf8d7a27fc1549c1e392403a88aecb1fad6b96e00e1630d25c5fdf771c138f6151e10622b2e7e452499c1b5a21f0bc4e1f6d6b1de0501918c42319b6fc3ae7b548de0ee614cbf2fc31804975ae96f719c5b762196d2e3da526b9d5778ff2c7ad5fbc4026d8dc3f5e346e692e7ce5feb14fc49fed9f66dbc38857fb691ad16b8f72ba744613ad2ab79e2d9236123a209dcb64680f5eb43ce5791ddf3efa08c13ca9690b03c09c296254950680a750c337781682dc4e107c23f9242062554dd22cacf2bfdd873a63f15d3827e1fc2d65d9b3fea98a0b5c859b433e1fad2609dde455db56838268b7fd1c82f27de05da8db5fdbd54d889eec80e6ab71973aece83add816356abbe8de03b4a6292deb8cc2251b8f0a0b14c42b4597762957a16d2283c316c6220e545c00d4b106fb648f26592ba82da7b4b2412cf1b2038bd41802c656446f6c6e559d5f0b0c1b32c1667a0c2d7074ef992cd206b52c7f5d86e48373f533fcf59b461f24d7e99734d549d04189e917d34223166743076819accbb19215c907f5d41df1fb9227b3e648e888ce384bf790a4060e0b74a9d82eb17e797f873a473c1f00adabbaa84e26eb0e4dace1c141529855a4fc29309738b052f402136bb65c44f985b4d12621184ae8f5433cc30adcdc3aee2a902fe69900cef7652f2d9c186085c13ece118908c66f182d99cb9a8ab5caed7789ad793f957e50243720c2c6c1495dcd0d3017ffa1c21f8ab2a3481fae296a8820e83a2bd5cc9e4aa2794d3baa1668718ca1a015541b9dc1cdc1f340bfdbf9b94b7149dcdf93a429a380120966410f907385555c9a1749c80cf4ac421dd416308a309f33833c704358ea71ebb376f14e708827780a5169c01477cb41149259d58a31a4105620a85470a03a5c61588371d0f7fd200443d101228d3ea974b7e58d53a7af10389c49b8a1b53fb5bc848d2d440d382531f68ac10327efb9edb170ee76e38c5a7568e7d2d1c33b7eabf8c8b761760075e17dbeb8e4dbd5569343d1e82cbb7984dc2204cca782452e096ba87b6e7225c26e97dadca335e192c29d4e1530c2d8c0100871efca01f80c7549c0157685e4da7cb8fab845e6030d2bfff7b946f78e2596574f35a8851dcae576cd3072fcc136afd4a90f5dc966d94c4364f91d3212d2a18ca15471435693ef4a4c346e7cea3e2e86f6559af45bdb46a3f720fc9234c1b4f28efc87c2f48d1a3e6da91c2c56a753fb881fb771eb227cb29fd32452637435b1bb328174303d314dd2c37748bf1286e3bbe6f6093135ab95fd156dbfcddf1fcf231477ca13298d1f84b84ed1547c8e80b150d45339801da745a7a4a9faf4b0a2b9b914a37d502122853a15c601232aef463ac29e16459a21ea6312234ebe9383a0f46718752099715af4b2275c9a80a4da3e32450c245c4d0640c9c25b15618e5f3809d655275939d69a66226b4010776f08c5c4b9ed9029703864111396ce8ace923ca15e854b8bd79263cb62074712bc89c26d2616a9515209a4be8650affce41411a1e2bccb0931cc5525ec63c21cc283434a8d1ea61c3b4dcb440ee1250fa2f4a0ef51f09543e0306580cb70bdbef1bd980199061fdf8fc51ff70c0f3295594a57e97664f55991adf47e649c93ac168c29d4ea68f044e7853c04c710ea958b8f30474768425aadb7632544c3fd75fbd355f1a36f34cede928354c5b2d73db5ec104adb765d96b886ad636d40951ff9f6e5c9cf58dfa176b8ea53bdcddb2ad5a7f3fd290a87d51062b12e034a7ba74e61b28f4a222965780144648c4d3678051bea0da782e1a6bd211d18c5d9a646f1564e3520ba18073c3ca2347a3d59b2c6dcce49d7ff7a8d8ffbebbc61eb08827190cc930c91735ce82c278d2c21226c82b1cd4f7b2d1da2d8684595a9d7bedf7ad7f90293df5e58e998df42d35c95f4215b32bbe5f1270533380d5d900a77fdbfe0b83795515c773912534d3260f309cc35d5633ea1f3145594cf2245bc482183ee3c78993c4055c5ce0b75465367de66f64d867bfa4e796b3ca0b905a8c120ce681d3495daefa680ee2ba829d02655ffc412072a72e5fb8f6e1995aa1bfb0f2a35677bd60c75a91ea5bac680d1a1c0464b0dfd530c4aa3800a6784c2a54f3c818c3cb22be1359204c245c2c30609787ec02c2d9acd91d87afb61478ef1b990542d72d226c15ce5ad3e614f1b6855cc1598df813ff2b5aa1df58c4767116ed51512e88412bdaaa0d44b5c1b169050d0fc6fdf2af8e865ee1a6c7e8ea921eb46105d256a8fcc7419f92cf65db8665f8296de24f2b23307fdcde8fb4c4319f441d0519624208e1ff18a1e89d992807759602f41d3bdceb6c6a1bfb6c85c58f85a9735e6190745dd3f781186366070e9aee603cc9c9163b548e41c1132291a6e542105b669ac802781a509d694bddb95d5e06863a4a056c74db7857a2a418abe105417c72031eb07db88c58bf4fc0007de062ab82f5d848dec145fa368d126c1ebade7a304858ce6191e56ea147041d84e3565d4199bd3b8f0f07a7b1817d869bc7ae3e6dd79790db29707b79756d9c8647640f402d6c87856a776703c1281b5d250a4ca160188384813a4d218dfe6a242bbe972b2a8dc8df6d8c44d89f68bbb4028dbfd6b153815b9913e4ba92056e4e68c4fffc9725aa2a1889583e5f351508262cb00253d47e8b2bbd665b00639628d6cf336062b6d6bce60e6191454e93eafe61070dffd593790216c00b68e778d59ca7e9e601ab96c519b207ec1eca309877eef7e448be982048571bff4f09b568515ef16fa12dea0ef15136564bd342af3c458fcbf8a0cc5a3ed0f024556597f292923d3152a028d22a0a74ad36647d61c38cd868eb76702ab521dca92ed0d95641b4a7533117dd8718710131521d4c008cba48cef0941d3b73fbcdbce3a4e2e8181627751972e1e44ed86add72c33dad1c12570e3313e9ef09e5c687c200b528da78d18003e2049cc4b238ff3394ef061d3f02110f19cc9d7a137e697cd44f5a92d3be95a86b4cfac5c7bb5bc4b9e596ee090123e3a108f8854979903e18ec417e35e125eb93c593cb66fd93034c052cd5d1529fe28ea685df9670a92064f53577ef759c67bd5349b4f4395398230a50b2deefc75d49243f31e55184d3313c380c8ca9b6a6ce9b0a4fd3a09228704a9819bb10ad28aa02ec50783b5e18c520413b4bc6e3f9932398575d421c87b22d092b9bc652ae98c71d068e0bcd9591b4c12f68ae903abee4ad98a0b26af1f59a9a0e1717beb9f57b6efb077371ba86c2e6a7181c1ef7031a715452539a46b8ee08ebdda453a1bd1f07798cc4e365377d95576936990a760d34817a9f8bd7860f939f1481c247f3f618b9d0cd51b9d641ef477d634b2365dacbcc9ae6617e7027e5ba0717cca0aae1b93bfaf66d8f9c3c7606f20af8649fcce7906aa670575bd978b9b023bffd6374860832011a2c25dac2db9be17d16354732d85df2811fb7e6c5daed85f21f2b68a070be6e4748df377875173b690a4657c394417f99574a4a34970008122d8ccc955229e5e78bbe7d6221d52f686f9180aeb829f864704c26323f57565d9d41472cd36930b4dacaeb83d82d3da2369999ad0b20a48725f138205cce7b5d968dc2e2f5fe8d4b2fc8128a289ca66431b92439cb4d44dfbe72543f46e84a46831c2ec51ba831251a5f108b9d0aa48e531841700511126db13602ac9e1af64c0947d043bf91d89aad631e8f2e4ec578ce520aef2c2e2896495ceb02aa78cc5f9fbba30c7ade38bbdc5b6eded20954b1dbdba635149699f97e1092a0bd7128760524304124a516377d5352d279734d162201e440200d240687f8dc4df446cd0df1cd83669ef2c737158533af6726abd2260243d801b8c201bea819617716b96b1ea5d2d86eaf4e850d186256247e48b35805203ed03f1af4017827908234934eca205e27f43240153af6dcd2f246f8e836482e0f770e065596b0333f9148d2c14ea2653ec0365c919f1b29627910f9aeaff1ca44338130c2d6780269ecb322154db27925a235a0c1ff785213da1e31de955323b97f33fb113c714eda8312df6f5c75a41ea987f2602e4c34f7e2f74e304a0025aa695b0e97fada5d9afa3d6cf2b5732cf7d7450c50aeb04162364be13027245039dcf1b055071039c466d8b5f8564e66670386580c8bb13457bfed8101e622834bf3041bfb0711278d86bfb2c8e2844ed02dc1ddd8b103df32f32bf40321a98e5f52acedf908733c66bab21e5b1eae04d801b3ad5f88fee1be9de36ca51b5044d8dfcf21440206aa13fe34a4ff0a79791c707129df683c740801ecf0566d49cb960e5ed82dfb33de6588a602a2e03b5cd84d3648bc270367ad700773f242902efd8f8d3c05f6f4631832739e16b6f48b14752eaff42c9ca33e11bd005800c7c0f6ccecb8a98c52d52d01171b6c6c938c40b67f38faaaf09677f62b0beac8540d0838de315684ad560a7fb533fd87f4c20538e99dbf231eb38d5669631eef3d8539161c8e5eda107a833353b4cc7183e123bbd1bc2ec175bcb9b44e57c3affaad9ea694c90e3f25afc78a3aad5929f6a26244032e45c0f62e858908973552329f59559ebcdbd85ad2c2c88ea3eab025fd899ca332a28da96c1b518c5282a2bcc49907e694058f5526f74552dab60a1eebbcfd53dbca75f6390492ded46a74ab18adee8ce593996103768f1de114fc673cb929db144ef1ba7dd721aaf73bad656aaefd41a4a9c3b517025c80753f1b4da71eff7214622ae33fcf371dcfdd7cfdc7beea5fec01523902b1605d0a89f2fd799933a9f1df435d937592ef9f5ea60e4b504932e982a9e6d90041e8945b63b4d1c3add668843bf00224c35074d8cd181f360c408ae8ba971b72ba66dd95974fb23c5362a4e40d267cd4edb5e0471d004553e595b9c40757cedb8878115aa587ef04e5d013adb2c1bfa157fa16b0d4dda4158510318cade3b5c9a48942e8331bcd23b84e2f39163a7150390b2b3ae9f05264e5fefa2d6181f67063e732e6353042949dd69743a2840eef657dd2a625fd52e08dc86b448aea058eb531df857142d996576c12177ef2f6efab0326b403e829ae8f347b56cbad407fd044744c399dbc8ef0ea0b44515e282db2a9d503385be62d35fbe84e3c28dabf3df04fea8bf6e21fc5537c601aad100d8110905b61255a561c4999c129b1cf1d61bd616c77a782fd16b146e8e4e1104b41017b0023b52db304301a8bd5d04ef92774732cc11ccfcf01b304a7409a6a4d078f6ea1f77ca738e861ee874c2d20ca36163fba0e5c20d413cb99d0cf805952955f2d16c91cbc9f9daab70ace3d3d0e7877cc82c95242a0e3dd067e9b24865c281420e459a0eb6597011a96e9b9f841028f841c9a98d4134b62a085497f6d888df262c89ac2288f88768fd33c451a723114718437c7599e5a3a54fc062d873c5701381dcad18e872d385f5f290d44e502188bdaf30ed81206f0f80829ffe6bd3a35a4628c418b29a873d555f3a1856ae40e2c8009f5af5a9d8d1eda6c122a75edabad799c62097638c32986c8a1321af53bbf8b23847f1cc1768659390bd7f49e5fba5edfcfb62d5fa88c9b669a0c53095b889cfc9ff056068e3720171757bbc701d46fe6b332e9c50860640c880c2160c6be642e910593a06087e4c0cf85442b5764a7d43fe736d468b70946f29921803458fb67a791e801a27f80ea2d2da42c4bc3fa9008db1035c3a4836b7c615b858809c0ce086fcdc2754563e6cd164dff74720916a25b8b4150ab4e0ddf7dd38ace8064b2222409493391b17c2a04237539a47103967b0647383a5e0ef2867d068561239b604b938073b0d79172a52009c05d73ee54d95c418b13972ae45c175fd7767b183b735ce9bf952a4370fcd67bd29f077ecef82422d8644d6dd81ce49a993f6cd9b08ef08c23a353d39b40c01a7ee2a0601665953bbc60c905b167a0f4387cfb0cad4109416a3e92873118688153b0c49fcf9b142793da0431f6742486987ad66248e0ef0b3ecae76ab11644a39dbaa29dc4df4ce9acb878110dbd08611bd1c8e76178d4376e57833a169455c78c8c27154f68952c64addb1601834b04e4db1bcd3c2f0a449bab1a863874f220fff2fe4407fa1ed7dd160dc89a9900bd999e800650a35f5a490d75fbf6d04b4be065c6ebbf3e281b4a348836e72b058b0d347f7fc5620d477d26fc55cf943a24fd7e5f62e17e8b171302468dd91a2ef335b3acaa6dbfa782403b0050a501ed1cb7d8c80406ee1905263fd9ec7b90497c5d7da755ce2a61976aac01544411747ff0e3dce9884c1b93c2bd00c2b3d84eb00fe687f550dbb09d3f46c196f567e91a4bed1fc5df6a3f587ce2a62e382bb8e0440740187c81db2bd85716034b8d31937b2886f4ea07fb3dbe1231d9b409fd706768ebcda9efe44dffa5c860b89405e73bcf23ec6dc8e5636766728dfd66c36c9ff050a370fee1ead12b2c576b151e3985311fa7579e36ed754a6b7b05de60d8b818e12241882edf2700efd0778a800cd6f7edc89926c97c249ec2190c9f2c179e0ee0d875f1b17069f27c0943a849188cb90e2a83fc76b3fcb1eddea22699c5551c90423847837624a5327fbcff00e4e951f2223d7c51750115ec7ca314b5086361548151e0978e26d37fcfa1d74545ffdea9b29d742817f4356e2cf679f6ee9f823398e7a5e8e3126c94696aa8cc5f640d10a727fb54610ad33387f037d3ec43919816789e619de65d02d78065494834c78197fa80d3944e9eff6f1b338c8467dfa408681f2f80678fe32ab12d299001248e43ef195a8baa14e0c4465521b58d5413043ca69bf056fc359099935df5b5cb6cbc71895398e4e3817f7174c48b12a250097a8fefbbed73981cef808a142984a469c71b1cc3e7ef2be6b33dade28b92b788a9264c1aed1b3cf0f286cc09f21126b0b92264edad2f573c1dcee562d6edb98113c801328d3d77090294fecf7c7cfc6d87d5470423b2dec3e719c9449c29298fdb40d7f95eba349b11e3cc2a637f28640f217ba0a0a13bb382981616a9b0cbbc81a2ef66bd8363c7026a34f6722176ab9c4f03caaf920c0fc7f9d5ce0f166b1a7770a0c971ec643fe5e8b07a7e1c596d14c11df2baad805515865b86da31844380419611693770d23cbb2df11391a3a182a82f31c5c42a3ec90de2c3ee14de6c7e22099f7194926439858c74ac922f023d1a6dad4f658c93120f4cf9e197915edf7e266dc0d1476a4607b14e61da73b12f5a499bb28649559218703fc62d83245ef84b805e8b6b4ddf98e5862d4b92f29d7e5c8d1ea331829782c708af70abd8a8f5bb2dbc8f79f135018ba51d9c67b577ff9f0c01f0fbe747dbeec6059d8fd31ff3dccf34bc381b46c804e7feb03f416b2f5dd7e29a5ced4a6839eea5127c84ea14b5e698a13a546375c6634765bd89aeaa0b4870031ad52e31d03d4eadf28e2c9279e74e0df55be26e5f6ac0e0b5561735b153990d842703ccef43e21f568552e04bd7e3d448b2fc9dd8b2ec4d37ca3187a520854d4b5fa8b3b6d973f13055d29d8d130b70a7dd13ef84c5bf0e986fb22d79765a38474c874df2b7936f40985e195243b3799db5174c207ca4dfe52c6589106d15887fc7295aebd6c59e0c1e3b4f370719ec5a8732f5340cc19b403db4ef20e2dfb2bb6179f2dec8b519e0b93668fc0b5a0d2c4dde9656d3e03d0499e6f675d8eecd697bdb6d8b8f6bb8dcc0431841ba4cb96cd65c2e3bc94499cad64cb140c27a78bde9606cfac8f89c694adfb1255a20d34d6c258774117ecae920ed2b0fcb4ca589fdd964518784e92bc39efe1fa6cc532df2be4919fee2e2c3bc7eb2fb5594345b3aee26f827477de6d4b533a499fbae1be6da30b5dd0e15d6a58b9f5df24a1b6367a46f6c3e0df1053cb64eab462046b3120076130276a5da93626988f31a1ad20577f2f8763ab82aa985e01a1c8c3a802985a5d8dc3827850842609eec196c478a7de469a718370f8e1b513da9f0e144c738aecb6c58dafebaa2d0ec6ad71862bea47c393524d1020750e8aed461354be2bd919afd146059fcf67cdaa4365384ac42af7f4cc12788567f2cfaa709f812311a52d413b20250a03936226a13d26649ccf449a53d45fa95435189359304da6151da00b80d1b48af4421d927eaa9280604cb6bd939c0da27516986594c036a8332bcd29064870bb7a3a9be026fe6c3044dda42b7fd069b536b696998f07009d0474a5d2d6b48eb8d682a5c51ed66dbc450064c003aa6cc5d1a4b37ecf538f2018dd2c2423d763083f4447d73867c71e411f7bd91fdf5ede561c5e0265fdd2bbaef9dce40914591036430749d8dc321f8bd8a5870a43a41f6aa875dfae08eafa07a873198a3e5cc48f18c4733b0d0f1e1e2dcb22eb6370e1e0276088189210ca9f1f691a3a96b2839cbcae115355424bac8d801fc79f51ebc7b0fc22e0f549575b4176733c0aa684c1b72ee0c14f852790ba98aab04efeb921d091d22865127057b257376febe645330f165c7f9d58fb3a221a26b00fa0ae1212f73af61b477436411cb3d1d7daf406348ad7d651c80903fbf6e5888588065d50d908902846c56ad155def12536e6de336b55bbd142260a4cc25553a2e27b2bf82bf419a55d140568a158d6204a225d20bc099345922df1e1c6fcd8f983bb6ac29c9a0f22a2956f5f118c6d60064a80bd2da6377701938a1db3bf10a221fa56552a272e6b5214d65c24eed2de69022ce2542738914bff63b18e7120aa4048617f9b710dc6ee10d842ccfb1b0f19a4fe265575b31e8f4ed1cc83a18188f5778b08f18134f5bd50faa0da12f4fb7e3c125a02d0115dd227709d0e93a2f8b0fd82c3f0f0e0e48acd700c6493a347c53d60aca77ba47de78643a5ef13f5e2808e0d7d32f8f085c17676d7e34c480f862779b9965b51fa3030015ca647fc89e1c040eac68e95e24434a541b446c412cb55ec87ea0b3370f7b9bf433b46200531e16b5f99fdb8771861ee21a5bbd12cec3031200e794653bb86ce1e98463869c9012604dc3d253575ce40319038bf783de46dc19c74307eb50ed41c7946e5cfa9002c8dbdd4b3148957c41f0064a03d4515dd8a44be604e7236df60f574105a99b97229e32d19758825c24251ec31e845f9be96ff644b7411d020ee452f897be7ac72716bdfce08bd2849a7f5c342f85d2eb8ad19101937bb8cf46300e065cca19a3383e7bb54557875f1f116dc50268a27b9e1917dc2bb2881304675ee590765fd374e552e0b766f67940343e88cdc9cd5dcf76bf080854c7a8e161723fea8866d380dd059bb28d36d86856017e300fd90b80640782058049057ff3b04b41fc14dd1ea5c6e0007e8df9e18538de44f0df21bebb71149090a495c04ae0939f92e60d499fdda416032556973d58eddd31bdbd3a9093284f4d343bd69fee1306772c10bd5bcf768ad1059f63bd9dc77ff107c8434bc3ee8e60b0c3ac1dba3e2b4d895b4ce7bde3374fc3090f02ab53b1f9245d9f7419ea5c694a712ec3d13e0e329b69d2f52f8d2c6097d922385abb04909a34ac8d5360eb04a03599de0e9f157800079e213000d1eaba1ce24f788c74ef8147de9084304a097e1865177f27df75c44fd873dde5b5ddc981d90bac7d023f67bde23f0cd650310a7b1e2b7a2a4dc20def50ae1b58e3931befec6768c7f1b09757f6ac2dd68fcf43bd741d0235358538e7f4e764cc72ef47b3b2d95f578c06ffc1fdc0bdf471b8522c59769005eba64d2da29ef4b904921fed32a293c1511d76ea95b037f61e852346483e726f7374477e1d16fb05a6e5fb82218fa88da989f5a6e65a631567caa0e1debfb39617441724091a83ccc92ea0297ebfa9100c574fb6d44a493bb9123079f3f266a49a5abcca9f62618f876ba2b555770afecb31e1ca924273b73c26f88919e03207c0783cb106206e158438c79c4075eb9659f6a22eecab0c49dcfed2e3f4cd51f9fd0b46025360bffc3b6159be490b52df73dad360616715f18c9974448742a7e1aecfc0e97fd9b8da81cd57bcda2f31e8168a0dff0a43de78a1e0783797985f5999c9c553cb90e34cdcfe119848b36a24296f9972e18d1a042ec9c90ad1da7dee787f7d861f41f499b148ab257522399d86286e87d04b7aaaf190a1aa3834a145cc3ee6e03ff79868c39fd48cdb83713e4a5ec814964ac1dde2e732950043c55fec3ddffc4129b0f9382dca5fec5c5ede1b7933763bf1153ad15f28789747a8482c6f11ace574402d0d75a569f11298a13f630f155c0a0ce52267da16025f452383b738397c3dd4b2024c5d1297aa835ed1199753f8c7b55d16aa02117e4ba01600e38766d7ddd23ee7105d9c3626db889eb59ff40998e5a3560880928f033568d64e99231953e3cba2b1d8310a462238330f0e936d995a7693efb9815d809ac49a3e88f018b7a0a0faec27e150c0918a2cd6021d2d3eb450cde4bf8282fcaab82031a364ed70478c0687779318bb67871b596791fe92bb9e3afb0259235baaf7c9e2698ea7c0d34e92d15a5716ee1b4d7f44886c02ec548e03779ada65d5ea9353e16124b8c2736401ded200e1572802787c683152f492b1899438e806286da4821f48e454a3495e26bc476c51b60a0503fdc6495f1eb6a0e911b977d08eecf0b408dcfae3fc38a25acad5b8b27b81d35ffe46b63399f739245e4cba7cc3fee44781d0980f75cf74725d94a4034838d2c2a817c2f556d7a1b8550805d0e11159316343c1d33d0157198471104e01dea34c5025d7e0e105b2a9366da5c890bfa58339327d442e7b628e74102a6e22b242c8defb51c0a79f359a4ddb03ea72b70f8707f2d0d1199f1fe10e371ed057c93ca4e40ac8a4dccb85bdbf2d47403618338863552eba67cdef99f56962bf07c8b80df3f39a1269c24455302f30e28fb5e562bd5286052f1343fe5900fc78014984771c0af01337c32bc8f49b70ccda80c2151b4fca30df96e24f916ceb33d06be9e3844c893ff02fb116640ada69e19ec3743d45901d63d0c9dfbececfc57ce1f7c6d26a6b1f8724dd7def33ef452cced87668f6f19fa3349b1439c244ddaa00edeef5f723b6d4b77bc7f2c2508ac7ffe22b0d23bff0b3f147a2d43f6d5cf1dd085b07e524b24723572f1e5dbdaa22da2bee175312b16706d7203fe47f1224df3f6d6335f77eae0d515fa906222f424efc35287e5f16e35cb6dd63ee913089c606dd62d7d13ff6fbfe1657df8f720789bbd9bd46602b5e7313279873c101f28ca696cd6491e069f0385d8c4bbfd5825796f03c1dcb14f3effded85f897d21285df6c7e2cf31ffe52cf19cd4b4f304711faf97b10dddc649420847991c93b20326b3e1f06235e83298b6196bb21bcc0e64980e85b108d48f80809d41a84cbd172ae6b22a18ff3964d915954973cc50681358c545bb1875954d510d0229fe56fcb72d466c98af950752646600b5bf9ac79817ff5de480e010bfc1a23e3b17912c182ca6e3edba247edeb011acc6ad16b80e63ccb943a9051d88e8cb2495322ce25e97c489d27643c21f72dc2ed3072652d335958db51170b039a75a4a9eff50b017346054b590b7b459302570d98adbcc5f5ed75646a4cbfad7db16d103a2956102509b96ab05d9184f69ac53e33a9f3a59cb207b938ec913fb0f073ed0d54965f06c40a445d2bdb5f45e29a6706145ff02b83586d37e9d04fce2b53d1b0ce8f25a75e95e52b7702ffaf0ad0010d39f09b2ce653703aa0572b19d3a2819aecfd3f1829aa2b884e94e11af6446028b2576d17060dea940f5fd16366f513348bbec4ca9f22093948834c16ed54c8647d7d243100f82061355bd0785e896b9a925d77af528ba126e01fe826e56007ff1ea7a0f945e2cd04bb754b141f562b59e629830f699a77db343a19badc1e074f364dfbc1954b4ad3ce38be149c0c13064494e5d058cc3a2f38754a03014bdd78f771a6cad41e1397628c65f31a32fdbad6cea5ad463e3c274083ea8cc93a5aac9d45141fc500f25139483b7b02c66986c37e8ed35a8d5fc34a15dae1b7e4122903af2203892abc8cf4d5b749b57395d53a13483cd2d41949e3418fbad98fb520d29980277318e7ac8be2ad1e4fb4f570dbb84cc69353bb1ca2e2530737f68f5ae964dcf0efab39a1c44a759dc16b429e812459e4141f54bb555aa7d8ea5c0f64b3769825a507d1385d3b9c4a6be6a560b1c1806a8fbb5d0a15590e2399860257734ff5390fc1c7de2dd8de0a1e7afa3f6ab62e22200816887df1a3dcc29d59c9a34ba96ea6f9a7d831f6aeba22179c905fcd8f4fc25c4956cf5817451f369f09769d08b559a3b6abaa78338c28f791df4384494bca16affafbcf78da0cd68b5d2b0a05c5f323296ba748b2383d96bf80c6a88ee434dd8eab81608876ab692f1135ecdf68df0dcd2830293da01ebc688a85ff09440d1969236a672510d95584ee91a1250d7c5a38ee1fd0be7d549adaae5524e1ed110cdc43ac3666b67803f578bc45771808ee340836e7f669a71774205c18c8c149b6b919649ca923e24d57b5b7fa2b2ca6503e33acd643610a130eca4b08005b0aee50c6206e7a89ad250c30c3cb521289fedeb853633fd63312cef12a063ebcddb23666bf7a9d0735302c8e3ce091cb2e4c00385fab9f8699e404a05426ed849450a05d240863b5cba6480cd5c2a97290349e7c64cdb74367e81e59a1c7b725173bd5718eb0691fbdfedf49cc315f8c0096b3cfb530176ed3e4615e3894c269bc4e61ba80d857d371a56a160c5db9ac108c9e2e9e584bb8466be28428d1ed2edee9b0d5851dcad9d9699745bfb092ea22f2a5e15e7f4802d465ee018ef07327f5dbc95896f7eb93058a05d97bf1f10145833a3f1956d06fa157f715a6a5676e589ed1bebadb426a45d71bdcfd684c73ed98ee031a1470f6c8dca9f8744162b58952f86532aa0034ed777259fe5d0203877c3ecec72893030d9fcea6ae5482964f5f7e787b8863f058c2faf72d604e07f4af452de1b67feadb406a55b2b5507f1b2819894a5869094947d50379f2e70be562ab3e9ec35927f08fe16885f2c5b9c66d5754e98f2917e2aa8430a898a2644e497277e5be9e1f345bcac8ccae8af13b822fbc02681410e6bf100897760debe170a24e9ef4be0a6467f3fc73957cea0085fb22446db048ec5a8d8a089eb09e6e992c46ce86d669ee13686eb785f81c250fa1b152079a7484a8baf4fb1f6ea019fb8ddd9f55baf90e1dcd99e40ad42e77128174a619f72fa2319993f5e20ebc625c487005a7a3207724beeb03ef5af62d2641cfb529b26026a52ce2db378fc50ac0e74330899cbafffe43cafd4bb89578e441123f463aa6701d90cb066b2baa40551ed889ee0d49342869052492e67a0a69cd9817e159b02bbf50cb0f21c1de9ba85e1b660ba3a194f29c97ac72fc0cf7cc060624b1b002b97be690ee9f6562a9291544daf6281dea9f0ad7a87e3b21192436c554701185bc6e8860091fc201bd150a9d0cd8f0e771f8d48c41956746a2dffbcc462106c5638c31cbcfd9bad700d3b5d57d0241bcf9ac1128a8a537644284ea8acd1106610b6ab5acf2c416939e84e82e7214f449284c6b392136b262660d49f40c521517541269e0e68ae6515496d7edb0d6ce16438728d987eafb1f875f62ae8043fc0db5741f129a6b0427d3ae86719dcbb108ebed116cbe3f3ad227df34b5ae1f5efec8f30e7ad757917e00db932235964bede144977e847bd3a1ba45e50866c6869c92025bb5d332840b848101e55ef9815d7632369e3710ea0c8e0e9b86d09622ffb10a79dca0a2272180f5ca28a98d18120b200a1891e6e010125e54c9eb1d88941a624af20b1193730c8aec69151701269d50cd50ca512e64e759747a686a6458dca8d201114d76295a93989646d8a0a1e4d8d75e2e308470181f683f8dc8b625d77fce616f5c3a8c0d2f46d089089f78dedc64cbbddd59176adf6ec8fab70a687cb0402dbaf84add99f77e6042927fa9e8c6c7d23ada447a08e737cc857b493dbf26da1729e103eac71da7e221a45a0e6b618c3b2acffffab1aa7ba22e6f167a59c474cb5ce5d8f61f08a2c4d88f90f66dcc99daa828f65b7c5b97f7af07dee8fa7dee82142375379c5782fdd8cfb16200782df597caf1d841de71f6e72f16a9ce6eb1e083a6af2de106422a7a524241e42699aff512e9dad939518c0d7a1fbafb9bfa11a19bfa3ea7c075087b9ebbeeeccdd30f3881877a3bd86da126891e859f5e2a0fcb38324ac199945a9e803142e408d03128f53f8d73e19829be7036c905166a7acb3615960331359f0cf86d565213e0892055aa50bdd01addccde84146178b068c59a0e55af6b06a2307fcb564c67b914072fa10ca5b12009452086ac94d84db059cf1d8de5520d1b01a066e7416157479311417446b4b7e7b000100042001808005b6024f3a929b587b700c3ef3d70fd3cfb912f090a610d8f5533792c774f00b4324e0a421c1ccb3bc71444a4263461843cd561e40323d7c06e56fb51e0fd8d401a025918530dab848c432b320ac5daddfa288603fb5e064cdbe8bad8ac18287d997ce28d1d5350b7a399f858ccd266ad4901592317d25b603266039cb0a3121458df3ab23b158f847158e0624ab18487c327d660a47825e5c9639a623c93c530d0249dc2507a083a1b02da32fa5005de1ddbbee510bb5d5aecc925a722bcc3d6d70b9cdb6f579a5b44ef374f616951c48d5cd0961502fed3365d099b0ef5df20e37812877825ff1f0da1b7581cde9bda32b2cc70afe6681085a0658364598c650614df1bfd8060144d299eb40783a19d11f4eabda0729116878660261e8f23fa64013100b87a14922420571b7817cf16cba70afb58830cf23bdcc2828855f382dc6c715a2b3fc24a22baf120e75dc19ac504493b9f88d9393f364be37338bf71a65f664035090292fcbe6f02c4cf97a1cace81ccc19bd88a45ea449294878b7e04211bcc1d10ae4c57ef6853eb6fed921a2be80767e6fb913c3708a3cb738b54f9773c900debb19e5dd1cbe970055549250aad9805d4d4cc4e393de17f963f9873d908cdb3577f36ea9ad7b73bc8017df3f5b0c0fa2702bb7a495202fd0389c29616901702f8f42c6814f73e5dbb12c5358df546d5e942f16269d40b373b8536e75b84d4a91e63bebe88a5b71b52be8cc4302a2d0fbab70d9ec1506e72a1e899a853e7903dd099936f334d2685f8a1f0f00324573830c3062e62f9705f667028771c3de3cda4e41ab347e17d767858f5ed807c1976b421105ffc559c1313a3886e6acbfa9bebac02920b26c3e41ff044e91504a17bd6d9ea94acf8e7bcd5ad5afd8e4c710ad1f33b173d1e5fbda08a0de75137e0021c15c217121316a1cac70862834df4c398678c5f1219c6eb5d30468a25010bfe89110712422ec1982a5ed3aaf9ee4c92d998078dd34ad07e89e4269923346aa03726c06c71bd333ab76d5dffa2da7210266e9949505f3d4e57c37a882744dc2c0244f0d989ecbcc80cb54c1c39a4470380dfb2ece80160edf2fbe12868a9529eb6672a85727ea03148246aaa50eaeb0a2a704f444c1c94a5890a454b64d0b729b625024a328bda2a4694d7a4c7bb829407875fd08c082847de0fc9b11e8ed0018b2c6a9f0067e6f1186ac89f609492e423708490e71db5348f85a341ab41e01f113a9a69d07aa5beadfbd48ba9388bc66ebb6056637613e6c53442f528916848582f3c8443cc48d117aa0065c77c359be82449c7e7462626299e7889d4885e822abb926a14010ed3397d013aabba1b81929b1f50205fc49c7800d593809b40b2384d83620e2396345d3f2c13bac395c2b5aeef7bcd67a5eb281114a94b15e6ab3ed48635b74f6aef22939a79fbaea90716c51438f303c54218dd18e4094ffaac5bfe2fe157ce9f9f74248e17e2a771d17a5b8ea802305c9ea2f80162d4737222f7fa3cc6fe0b1ec0deb7524ac98ca866bfcca994365c0391820e249d257f42aa0774141d25b7f1bb33648c8b76801c4cca9ee69fcdd9efb925d753d96bae39f254d4f318b70d28a9b6a0831c1a0ceea90d2d28f97abf2fae65392175c8dad14319e38cfbc212c276fa14eefbe5503448b9f3c6f40f63cc6bde07c64d369bb15549c183b7192f862d1f594a2086275644c7fc6de7a8471a01b2f6e1cdade332df446cd770b21c87039b43adbd3ee958f441bcbe47db8b8a5245a80ae87b08c77b00248adf57e9a6f78f1a175fbd6f0315dc90a6d23193ed1f5ca16c4c603552f0fd591a8c43c7c2239a73b0bd7b672dbf35d558a00658001fa7cf8708235784c0d14bfc8a5fe41b47b5033815a29a5a8245b07998df9c091c643051e3e417aebdf222d121de64b01255cde4e340e2f30a75e370bb67c02ed7ce2421debd497d56242efadb14d0d1e0121946c9a36906b9356d72a64ae203201ac1848be3d70bb9d4cdc8ef10623726720aef422f5abd3bbe577d0603dbd77d0e7fb43be8396b9fa5a9cd00ea91af79b50c98d751c4d53bc9c28ca572948f5adaf0bf655c2ce8e072cfa8fcb2b99392b15795843fde39d4ef77e62bcefcc05d54581dba4f041e16aa83c3147585790b1e465373377ab1a9c11be2843216787d017ff9b29e30389dad6360ee47de51b57c6ec4df7e489f78637bfe9d4c4817827db8830f4b98e19e92d96b9332eb4cd937e709044f8ab7b94f2649d3c42453511c4657dfa67ab6e0d38303026828903e0e80ad55d164ef9173605a4b40e4b5c703019cd53af204b9d8dde23b08d5610444555cd348fbe7ab63f716148be3422aea9bd48154070463680c5606a0fbef81d0e09150c5fc1c62e1440cad6367abdab40f0a4fef1d4d45bb4e779b31aef1045138380846394e5d739f840de93487bc531753810370c8fc7a63ad418abddbcd48071287388136fd4d7c4ba54dd2433d592bc3766b0e48dfea0ed31cab42c8dcc494b057f39432cb627462835017d2000e06977098c98d79055da00fe0ba57089b75a70cfda62025a1332b280548efb224e91d9e904ebbdaabc3baa3861a759ee7956306acf4dc3256a5243ba66266e60cfe275c94f82c6f6775f7a86561b9eb52185d9aaaa6750b5009b2d1cbb82198c973c3c71d780571e99887b87c52414ccb13b642ff3fa6eeede53b1e64edd40d1c7dbd984f1c099d63394f9f021289d519279014b64f485f09f29ba38ed8c0654f4572bac94a6f4a448107880d4c84ac309e74a99cd3410c13ea42ed3ac1faac98425affa8dc9a4cb4d86ea8db77af6271830ab8c67c956f6d5772fc4d175197678c9593ecf885a561349c41d05e9b5e922964109210b34b02b39207a7ce59e3ac046312ea939b1775314d6dd23541fb6e3f03fc241e44a492d60294290539670c7c2e5be8f36bb42f503c67fbde56851e2c98dc06b0241509a4d091dd96149c1bffb9124e95697f35d01f5497dfd2edc6451f6fe89f2a5218c84eafe76a6e6293c36f4f2b1cc5e1a67af46f0f31f39812515eaeafb095447902e7a05be0e0a80bf6294e8def3f3490142435cc9f6d8edf71434c89163bd6aa14b2728a515b6e96de9c09df3dafd1c8c5b08ec24396fcd0212a225e5ad74cd05530b3079c94005a6af309c62013787ac21bcff0a97d2387f935f9effc1dd0e3a1608157806f2880075c0f277c2bb80b97d0469857e06dba8bc7e4bb0b9554cb3a53763cdcffa17c9440750166829660efed15cf119b76a659021011d7d8bebaa78428096c9e339c6ae726f97864419266a5c429a3a00d82a20f04b5f07416ef7c1282ddd7d07add39b14a095d76e47cc152e445c53337ae9e795c549247bc2339cf6ee2e3a837f9f2619cf819532fddcda46e445e32fb451c3a38583efa692217011a2bc59674cdc5096ad1950a302c320086bc0d2298a7ae0b8c45e653665058edcb56420f402663d141fd956375a9086c371b582e447c4ee1d15891c3a428e29e97ab6054850f5331ec081d93530157aefc767e6d4d4163ef9ba9881121968650bbb44c3cb0a472466c5a66e7c0692a7784cdb0ee7421b6c1ab1e959891f661faf6ecb27b98d6c597bc8735807a816b808d5b5a389da88549a4300f305af6db5b24ba6e779a633aec60eb6d1f1f17ca85add8b0f0439910d2c471440cfd59863621e29ae06ee1d0325cf9e481d372637b855c31af5cedda36384e46fa143ac5d68eea55703af363d351058a397ac0d643d32b383935d74ba6c45124df4cf3f8502fb344d811ea0d64a2758ee412549ee1ef7241a9902fc64f818d1aaa930443b00af9adfabe1a94d184207930edaa0a5970ac990d457ee3ebcd0d889fbb4d28a162d6ba195b474d0f67692248184e4b73efa1aa676ba4b463509c16cd1c6cdc18ad5cfc70e4eae5b871b1144b0b133d95fa7bbad99c44d206ccfbbd338d5aa7c30471b190b2952cd82a267136d6e28436b9e39930e9456ff6c2cafac277faecd4bec121d7f14fa69cf9103e373043e86331e6737bfc05f2869b7671519c5cb9eb703eeb1755a9c06cd210e953a8c4c38ff59f926ca58e183e1111f9398f39267a685a6be16dbb1b94bf83f8605b3b6ea6f377016c5df8e65c7d43f2e3f802716981726d7fddca75f653dfe9de5d0cbfaf31d01e0dae1e64f0d34302d4020a1dd0a3b5b88599e49e4cb8923f39668d1146d60c733cd7f13341a092e1f642598ca1b5b3759da86d6d3cefd73e518b90725c7ac67a87b3253653a086cbfbc673136bdd34c9346e8e224811932618f9774f5f7ac5934d358ed9a145b283c2943709b6ab451aa7193c9cb9c20459abab72636949c2d6633726993f1e1e41e3fd8c40fc8938113bc339bf2eef7ceeba0c397c604d78af2f57a7cd890935f4c662cfee8e7ea660ff872a396dcbc20e6680c93286115956805326ce8b8d743d4a9c14c5471ca4cab47a208e8f57f60b4b989b1539e75989650bddd9400ce0c15724d3146aa54ffea5787be751f049a59225b1410c0982dd7ada92cb9e2cc9aa0bfc3f3e4cc1e45bb71d794d6868462f8be5b8f5e0defce9a0c9f2ae99130a5aa407ee350027a27e74542d92c7621f53f6d2adf6161228e41d288d524bb768e402567653be080802898ee18bb018fa88a9d06e42023a8d56f1b7932a618f26bfec8333824ba2a77c97ea10460d397cc3476726f392430b5139a7add8135d2b91aa998639f3055b1a4cc091902e437124dcad0e19ab0af4b55f0a05cc9ab25808d8f245616dedc6d194361ad1d2f3e77a6dcf53f3994f0cea4a0c56781dd91da134749976ee580aab2f7a0df02bd5f18911bdaadd0bd73b2f674debbcbf185903c0bec9cec85c384248fb20e184a8ffd59a0f7ae610af0edcbaa4fc800f95063fc1e0ea9057ca297fa12fd7c507d60b01e5ef9c4d82a3002633d761b5403d5faffe8948aff72a937e7199b3e8f63010816a609c27d9faa9d9a191a03dbd8082c0658e1ab7637e153663020c005a0bc01a86b5a91366b1034873601c568da30ddbea679b4a950c9f09e277262debe410473953e9807ad58e526cc163e9a34974e8770da7e5fbb5307694b97055fccc5f839166cf5448b118c5ebebcb8c7a7bb5e83f4ada04921aab59835685478c9d0ee84f90d65fb07c7b0afa41531aedaf23a9972df3c67b3a76002773bc1f30b0e74f73a5f5655898836dbc5d390d1e93b7d173a7a9dce9ab7d067695efd9201c5ea2a12c718787a2f8fdbac33bc83850a2aac83f982540c9169b99a651dfaf0d2b7f76a6f391b92bb33b06bd76c056025d837b16379b20ae28d348402860850a430619f6288a2087e1640a7d78db388f5420230bd6b5912d1665b0ccff5225110f02158b65ae2d92ceb5083d6dd36927b6cb34a499d797159aa1868dd3e245c268817b82e17b84389ff65310bcd0be9da3efe55b5016f2fc96f077223acd9f1bf2eb87a817d9fb69af4d878590401687ac36ca96405e5b832d5d6620e2a505b4ec5f0cd956ea758d0f1c66afa4a47db90dedc99b5cb0988bdb7267ba54f9c23c03e414ed27f76effe7c542840687794ee0cf88e92b2ac9b212da9b1ff90ea8a8a6b98425e7a565a7bcb493d6ae48485182c0456576bae506c2df6ddbdee2be11fe56cf8774daa85d1893fcd1406157ac0b011ef0128b6832d68a383e632a42b123014f6631f71dbca04477e2b3f1eb4a9522a0e7a66f36dae73a559fc693c0b83c529bcd23394fc676bfeaf85efe7683918c8a3f1dd906a9973dec2d763786071c724662f7492037dc896d149671949a5ae16781783bd75f4446a3d28ecde7c91a648b0ac0ce945bc78bb06432fe5e08e1a4ba62b20a31b6b48f9739d1ba63075fa4caf73dc4ead53a9b68c2eff6a2840b303ace4735fa15c784131bedcc61126cb14c2974a833950c4089726b0f05354997274e9677352c82248e4da813153736c56c6708f9bd93cdb8c1bc849536cfe2c83c90663561a58278f45d8cbcd2d6aac467b68fb39f3641fc21614e21b6d2f27a95ecac55edbce5bd6f427b3cad0abfa388c1f878c759c5819b367decbc6eab3e382a464525325f23832ed7201411b6ace78f33cb0bf831f246c18f121f112b2d3cea1d423087ae1ada66291a78f6cf0909c4e4003c89f896772d4601cd33c03571b31e96573b1415b15f571a1863714580dbd207f4a45c38434b24a9fd02f0546b25e06693f533bc552e26a0e4866dc107eee521052f89edd7968446b82bd6eb656f5a7a012fa789e8cce185d45e8c99cd38500fc9be7a16d73184399e1039ae097a2674263e440dc76c55670876fe82a155cddaff30c0c57b5f4e90cd4d77821730dc7deaca9f563b5e7c62353a75dec2a8b2018df3d2c1e7e2283cd5f13f7e4256f1d3c9177ff2b3eed8c5ca8ccce6afa29994d950d76b33e19e6d62d976cb67481b92a590d15ae2e3c6f583c29520c4c86ff982ea65cebf77763618c908542a97ddf8769b349c662829e652416b95894fa04f11cf4d3309e3b7113d3cb1c8b30b44817118656fa555902466dff0a25047685904d31fa69818ee6a306e5be922d01bfed88effd1502e1223404d5811998207d34dd42c0beb7e55a9c69878453a3dcfa9b2343ce4dfa3430613215f514dd098905ce36a71e8856ed2680f4c091ab4654985e3e5a859936db0328dbf747c7b43087d903fd0e898bd0f655124b2fdce4fcced54cced000e2b16e4cab170d8cb3d089e61e4bef77eecd393718c81100042fd00c68dd10f591db782af95bc87d446f37f3e7f4f80bb58ef4e532239cfc132900ba2ff4de16d0190f94fefa26c5747f1042ef1b7a048cdcc683913c32986ede9bc31b60e5c7745e62a136062cfdf1c66434bc34aa75f9f2a8ead002d9d1e00d1c011ca0ad1bee61e4fbd0ff321b575decd92dd069db38a0561a4dcdb221ce4e3ca57a1d925b81fca2fa476fda07950fd5136daaa40074bff41d7760e5871ecf1d5cf1233d1aacdbc120ba0fbdce1d58e947df750b567e4c8f4c99db0320ba1fe9dfac895af74ef80effde65fb0ac326f1056f3f24751fb7f0fb1f9100900ac6b630865edd406fe2e389e128bd1418dd4fdf770facfcd2e3dc81953ea407c3bddb1bc18e42c8bfe5baf476259cbb867795240158ea7793eb0e56fcd077dc032bbfa6fb2c51df584bb4570378903a8d11e9baf63c3957725a20d4c25cf62be33aced02297b85b9dd6de7303e1e59ccc1383d3e5c97cc7332be3249e02292705a0ff357d8b89fbbec93daefca02917299ce5d42e7bffb14a3fc797c17014b1bf4ebb9d7bcd419be204afec6d26adddbc26e3b9fce6e7102c0a3836be66c25b011c72a9201d3b410d7b313b080ee6e27233475daf9bb9dcf1dd5de210ea724db20ec71a91b4a7c372aa71e2325f19ce2a638c852f5c3743ebe4f66eb43b2f5098371392868738bd34f760d3812d16a5b30493461a7eb05e25c788fd422d63ad95e270308dd30ebb494df8b618c940fc27ef1e4797e207eec221c2c52029e5e5a56fe0e67878124320ee995b1c34e78d12bc3e5de16544232d2da04291755bd49889a9317fdadf6fd7410fea9c757d1fe610165f4eb6888dacf4199b17414660ac5d64bf09ceddc5a998247e59c20866ddad6b34f065dd3fecec2f79aae44d25404db626d7647310dbb86bd068c21ba49a84d2de7f2d6d875a0963eea1ff6c099529297bf77c9f481a58d820d1209bca966e2bb9e48c4426118e00b910b3df707cd38c499a3f7038bb72e0fab735b6a9fce9bbe56482192d136fddb48a478f33ccbf500ab0f2c7b5a0c1ec42736738ac0b2af60b382e26ce77d1d823523fd3eb809a2c43619693d48e2b9ebe30d8be3eb7da90cb895737b72e4717190b7cc10bc6400081b5f20fb6202bf4be24a374837ee2355ad45a6dd874105b4e3cb647e01ad1b997dab10d5f417aeac52fa5dc1c240d4f3b49ee59a21695f1926c167395112c50db3c067feae67e8ef6da843cc1904bfc17489da2b39c5e5d4aef15bcacd4ac1055d991c066a81ebae3787346729f504cc76af47c2612f6457e736f656cf817a71e82d3b12f542806495072c0841a8ea8f3613070599446b4195652db11378a645ce26677cc2908f4ba9cd91d28046090e369c23caf1ea801b6e00b35d0ac8fb6193eecabef671db00be3ad269f37b48abe4259798d01aeffa82d0d4008c2100d6cbda395b75804ce2ba02b67d40df26e0514a3dd31c38991900ca8767db003034404cb535b823bab4d75a8235e46498df43b43caf35fc4c103a7d5b5a9ffeeb43e67a183a1ed5ecb4c269238a18c53995e0bb4b58c2257754f471e2e94a708b8b1d3832bc0eeb79cf132cd524c13a66f68145f713dd3a1e80d006cad8c1cefafe97b75382bff08ef4d62a5006a7e8161d4cbc48eba529f0110a727696ca1a7080d388694a5f5e303777694d047e5b225dc86902e2291f3df1115ca69cba79f793bd138edfe861848d0026759f852f958e872472f191b5706ad6011582c5d04eca2eccd3ad5c76c49085f22b4365126fa97a25339f72d2649dbaeff29ff6c4af86ba5b82754d83f33b220ff48462d476680e1e2e5f36ae879742c1808dcb50ec2db4a5ebb815b431b598416491629783193a5b07981e007d1d100cc65a353677f30308f0af70fea1013c5809c6d65cae73c1b20ebaf4e926d18753169f6590655ec1d6539dc9c3eddd1daa405fccae987aba148b0e10c2e4a8dac2716a31ba02e786036079d20c9fbcef14657624140f86d417c06714d70c474764a4a714c7f3b58d7eb527fc68240bdc5562eb48b90d825d6886a01ad392f7b51b23176014c982daab948ce2c0d001b3d86f3a2f08eb76b46f214b28b2a19185e2c6592aa6a49b29210265baa0a415c793269094cc001d94848a20f636f856a9537412c6db85e08dae1d4948c70b7c97a9b7203242e181f5858b154af9f8d3e661f4d17ece2eb2cea3d7bd3802782e6305182686aae5e844b1283ea9317a8d61883091d78f94860ea402ecdf5a8dfb8f5bb1569ef12c17e1d60e140f7d2b1149e09595ca98cad1a848ccd886c6c0482d30c9040a588fba26fe02a4a8037aff096ba81ce6c1b57247f3fba99c265d9e24406be8fc06d75043300b654496c3e4691de9f07c9e801c826ff41b943a0ff38df665c156ec4b1362613892ca4cd58428b73b0687b0249519cb955ac398c630c24873b41c4451541e39294e431d711ab6faf0091f291a316e109d66b4553041d31b5b31ca5a0975fcee686fbd782539ab95a485dbd64f1dd648fe606e3f344fce0b8698c32abd64c20d284da39002855d57a4594819ea7c64da160ebac1bd063b7d011713a6ba6184086a2e7b9e0ba80fc0bb80c079e0e680b53d0184fad3c9a3627d5fbc4b427a317e8b1b2d8cf1ac0bbb250660838dc350c595d708a862abdc02dc86e179b64b9caad0ad6a08ec58618dbdbdfc47e703e1fdd48fd33abc6dfcb855cae8e72b7fe9be09343dda34d5f3d771a9b5704f44ba0cc6a9c15205fed3e4625aad22db201b77aabb070fce34e1323d374cd962ccc30ec95ce8f89a906a2b374106e84372fadda59786e17cdd42b48470581b4a4ff8e76152acd1c8b8a5a49ac341af12bed809f1462991d5b0b4344899deece518ed82fc550b7db64600315532f7c974fb16315574a1cba9a292b55d0fb0bcaf9cfbe1d7cee3c123b43eedf09e0eb7378c3ff68eddca732a03bb6a4c3682967c735c8ee477e7367090c65c7e5da5095aeffe019f3e59fd6c9fbf89ca296bdfac82ddd1aff2d74efb1e0345fb5a90d47edcc1947ea2f754ea05190b292820738d0574a8ff3364da2474b99a7af182389c6ac5caf469a1518eb196520df8a224a3f7e607b4a256b69e39f89d629b70d2280f84452f466afb642683788ad60e57cc956110e6a96103444862ec5f450feea4ff850e1414d97d71413688c30cec02c639034029a4526052ddd318a64d0343c121ec67d3638c705a54b2d03b96dd0599a99b7b72f4e00f0833a6fb6d80651e394c50768a007b2f5fc5dc67f95c16b93219bce32bdceeb1828ca796e7807c015ea020ec8802bbaa4c63085d85081b1489d644124c0ce4f406276054cc352bcd392aff281f302efb7b7d5d3b465215a997b64601a6c8de5c74fd5ab7e3fad28374da09e68ab81734f142d1416a222b29a556efdcf579b5d8e635649279256a05eda9c63993fc6933bd4515c8e7204e6ddf95c8b7670b3d1b41b0aeda2a3883d909d7ddada0fcff865a85167e4a12981e474c6eeb9862c6c09b8b2469216602bfd724b993b30add347f08ab2067a32fb8460378f05a12164d32e9ab7fec12b6283433d8f2dc25275cc13bed8050c232e28ecbc6af539a3b929de255aa0fc2e480a5653786860d0e3c68125332fa883b307d45a520c8d0cbad3018bbb42b03b8d133831cc87c44e7d0a70a050c5b8e7d20b6a4cc7b4a1ab404666b79d8beb4def1c00d106e124082a91d4a0b568daa3e4bd98378fb05008f61a934d8c03b9e07339c15162d2b929c27e16c4d1b8c1825e490a63a8e8b4630dc9df25d3c01bb3898a74b07d3da4a84477ad05028a5e4c1186faa7d6863d4ab4332c9823b672e92030582b95a9950e626ecee4d72d19d9aac09b59114521f5146e8f648b35668f79f631dca8e47f695cd0b677e4a40720a949dced483ad7a2009c17c30fc96a21df628ba1b9e110a210964a2531c42f493d2e6a1919a1d5139525709e97b57a338d5cba35114cdc3b5521a24ea620ce4bece638659726c916e37532c45b1ebd1d5d04f3ab739399954199406e7aaf853c2a1e6e2fde368dc7d74aad5e71dc37acb74789c52c2755738a677b29f7a1d364b68c015c8bdb3bb0e2ff2266eb329979bab22034eba0718feca4afe4ffd7a76d7fed9e16e87d77dc89dda848743508debc903efd898818046c652e8191b6dd9758099378751d2b43090973d3d9b111b206f742c37a8f7d943d50e050487723524391eaaf680a0e16eda81b581130f9326adffe102a28b80db3dda9b097d826a2230b60226bc5ac460bcda24c91800671044878f9d996922e26ed45b6d22e18664ca8cf5f2641375c07f6c2e054f9583630b2d2070922e4211a5868703512b4052af38ec32ea4eda6005ef40185e89ef041fb44597a45400f2250888829116de6f12b2cb336ded0ddf4f683b7344de268d2f9202e4c8727d8d19e9a631d70715ac909baa0d3683295e36c65e66ffa22747e5ebe845d824fc0651702c76d4083f742a10cccc0877ad3bc369ba6aa33828b6050e5960754bcf033554d4baa2b7c53d54d7c65f40e5c8dc477fc3e1d12569ecebf31fc7f46b3d41ce67605042ec8c2f458d2e046484703df42dc82407d1cb06aa9e185163e53dd94573879e1a455b051f420d00f1d2a527c99179fab5baa7abcd5840ab02db31d91539cf5812312de6badc3097bab7ae9ba74f1d5cead43165b0fc073fabf8042af74b1cae245ab68b0f260dfacb9837e5b90e1a94db5afa9dd98ca45303c03bc3052ae7379f8a3d44da1b9c0c0669d4fa5091893a8dd060ed7d9d236911e958793327c28abbf221ab512b9d46600badddb5cfcdcc172de097d2cd8ab0352b6915bac6c8b1c4869c23ae7d25e1133822cee900882684d197807e99eeea43e5d85b20991c3d0c24c3d90e5aa494777cc45d678eeb16c6e31d174ad84a070e7d8f7772e6db3210022c56db471bffb8df7be7bcf1b6f79f7a677de7d07df1da1e0f8220d61b2f4206c2698542faa9b59c7d6515f1d75abb79e7a5c98a37459597078c33befbcf59e37df79ef9d37f93d0b704961c63949507dd00432bf4489fa31f77d70ccd84996e0f03aeaadaf8e7ad4514f7df5a9b71e7542dd89b48a0df9118b475c1308f01d2ba9146dfb13d9cbd273172859f65828b7abdd1a69480a327f66ad1cb0289a13633a084f1f6c8eb44f4476ce73973e9c8134f2a52e1b40be4cf34fc0cc9be2d08b9260c29da0cd7879901c5c7f5c8f2c7502556b83da3640ee1672974336b9b6a15425d805da121bacf16fb2acf481c12221d5bad5708557b610af35d51395da03283c653236eff6ab62328091e58e9d83d91db6e99835923c4dbc421f2ed612dd041a100d7837b04577ba7ac111186ed317cf29e08c11202cca13803344659bfe8d32c9d17c4812132356cd0dfe1628768bf2139151cd689aa96a8772aacaceda6674d640e180cc078436864dc44d963b59d836076ecded00540e16d1fc028268cf15c87bb32f2b2d25b02b78b598472de960695c209d8ef7d0c8e0d7f3dc2aab939e1ebb24d902206697d6d2c27eace661fadf663848b276584b9cebaa1c8b54f04c45273ab75155a8529e916174fb19f34a66ab7ffd5b7a094a04b507f16e682f25a1bfaa9e554c83fd685d73a8652348e95dba5e88eeeb63ec36df0de3762be96c04c4486193fc043256d37876603ddf922bdbac2164bdbd16da88d9454340127ba4497b0c02bed82520c367b88275a18dfa05607e7d278122579cd618611d79e9778247a235278129dfc93fc9003be07a58c8bcf844982f950704ee2e619309f383e36128108aa2dce22beed5a41b8ba9864e32653349b89b243264e120190cb6f88a09a3033a1898a9be7b6a1ed3e8b410e01542fc8d0528e90f5cc9b410bd0cd17b1d48666fc7688bfc3e3f6b5e2eeaf660af9a448d6f009f2fe925fd449b1eb1d116200e767191050521742cfef6959cc2b9ab08659af9b3284ec9f99b08d092393d53acaf84e65acd0be500a2dc2a185a278806753d2ea017a551d5722b807b68f720af7de538f64c0f187801ea707c08a6134efbfc90c505ad04ad787ee5095de8585e76561285c728e0936a9629e02012cd984e116ff9c7ef9b9b003e23def5853fddc14db494e79fd4c278723aadb0e2b6d65d3ee7c561020a2328d96b9bd39629d56621428cf0525cef22e9571c2762a0fb23474da56d6c9ca86104fdf33a77346d1de326b079dccff12d69004e72c75860a90362a3ebbd00263d4fd49c79b591892a61c8023f01f2515cbc9bb009c838a3252ee9d8234766704d0c005fb32221e9aeea2e0bc469de0cea9f86ec6075a6eebf24593688186df8ff3721360080be12e6f5fd01f5ca6d040a8dd865a28e7753c9b83126ab605dc40d37c504956b424a3862a51d57d2bf8a5ccb0d0136e7bf8bbf5180b1c15d4427fe70c01410601ecc2cd288ad76cfc1a8879564d68461eb1419957114190d2c39f4fb226619ff1e321a7930042fa7bfc62ed4858144ff7118ff6dfcc092bd8800f2fab2c23201500ea87d19a6738a12b3eef71ff2004bfc36bd46b80acaba7b9ae02ce96d01cf24b6d93390c7076d3407fc82cb2e22351c6012bc083246bd9e5c82d9cf8e6e7c14648bc4a12720232a64a16c830e0bdb30b8e166b8c1dcfe2bb76954e3f2a82921e1f780bcdcb92cdd9cd08f12001d9062e6844e073949150b374f022d574243f03da711b340c5c9dc50ed0754eed2f15023443397436aa2aa92d88be638cd67000f47fb0c7fcf82f0b75733d32548a2442f84e325b7986b515b4ce5aa1d7c7e62b19b18c62affce79441b92c9118bbb5ffdb18ae11d4fa766fe1b14e5422ca3c54b88f0033a41607003c849d53c6233d9d663077b567c0e2e083e199daaa1ff58623df26086b9493725b6afd72b2822b254ed9aafecd8875c5232d091d960ffc89b8b9b1f1b86a73c0a0ba4bc031a8abe805930d2324a07aa637065117c93fc93208c8b96e133161b0f3417504e8b8668f50b005a0db6d747f1545a6179f8c664452d42442087a61e4be03671a93fa8bfe52b17daf47c90c6f78168e8020d5e3749c8de7b6f29a59432a514cc07bd0722083428b01984705392315dfdea19a78a854f3b6666e6232d32937bbc71c8a52f392dde4f86a123253cf7d800fc2ce205a1c31af7a2d61d89d7abbbbb7b0ed1bdf8975d01f04f3f64ab4c85c513fbfd8dff6997bfc67316e53cdac4ff216c3ca1f8e337cff7cd8739e7d83d16fbb0ea3216349a5c0815005f4c8642cde91e94243c8c175e8ce38c531fbe08938b910257d2592b77e10e7d83b791652c609c5c8c56edb7a39b6537ae237ffcbd6210efa6c6f002cc563e7e828608c9a0c4c84911548eb47cb105b9debbafbbf3b513d6dd5cb8d9e8d77b7cf383cc7093b5f7868207b93a4f731ae716a4ffea26e71f8de08682e79e6a76117503fbd5f816ffc6bb2f86be485c0886894432a34cc15fbaa448e606c4de17face9b4b63d8d05996e8bfd0571a214182a48616375c310ecc3900e3c0578dc0d67837aec31de61a7374e7dbd3c537fbfef257df78349f36a2a2a7b0cef553524a29a57f5d9f71f2c701483ec4852ed0c891281b897bd8d3bfd80873f966320ec6417254fa897a0fa163f7bae3fdc9913f7972342ba6f126c3f54629f7355faf09ae727a9543e6ca38ef46c7dcf1b1037a8f0994f2518d9021ac39b61f3c780bc2833e185abce3e86530ace4d02cfdeb9bd2998fb7c0dbd90c37f327aeaf0eee6572e49871e3858ee3eaf86c6af3f499d46638fa5ac46e00afee9a129cc13e075765664c47d8ebeb679f619b4fed2ad5304ec769bb3a150c6ca603a3425a20a27ff50604c31ec8d515a95d8d9bcfd5bd13d6fec97550b38be0adef50870f7480d181031d3c64184aa2db09ac83107e10420f42b0d79c5756efcce7c8d17dcd633b01eb5241e8637586878661226895a339fee43ad9677452489d463aaf3a6bd7aeb51b9be9befad7739f17a1cff7cabe7617a7b9f7d87675aa239ba97ee09bcf0bfdc367072bd77525d8184792bded3eed6675be61aa379b55aa58712417d753d89b5ffb0dee31b5aee3606ffe4cc238aec49da0503270d8eee25ebfd16318677e9731777e77692fa5cbcafd5c8ada9cc6f5f5231907c00a32aef95c4a775dc2d5ea12b2e73781f24079641ef9fc92755a862ef60b5ecd244efa61d60e1442699f8075ba4fc09e7f935a91ae7f86c90059cc8342599b7477b4ee4bf49b499a27f6d9afea3a38942e40f870fddb5d04dc6304ec3a308e1b15e145b88a0d67e29e8f17f513f79e1441d9e57eaec4b427eb4caa26022fda60e0996f3e5fd3651c967516da78f6d8a3d861e98340e4ae6f9f140bdb631fa47da67ded82b6c7b66e078620ec35ac7b00b72ae63d77a1f0b723571f6e40badb44b7afcf98625eea4ec83d27125bb584ad7faa1adc7e3822423cab4ba40beb026725212d106931020549f9d9f711451880d2c7815b4170e9e745640a3256c776a834cba0d625c4e7c7fa6601d77f398ddad1235cfe0470f94bcdb2d0c6fb29d75458d8970dbe43956b24ae00bbb30377687ee800e348ee03ecf9ef401611dbb40f5087eb70c70e76e55e07562bff934a0a0bff065beed7405c8a7218c61d72cf9f65cb9731d2d9b60332300e273db961a503ce967196815fbfbbf4abbb8c5acd7ef0e8f16b0bc2e30de8dd78d48a2a5fc0f852c4e52f5d7c41ea01333f33b38ecb58be18dd4f7599999999bd5b890cc20823e41622eb9762fd19ba75de7c2ca05ab98ef3f5dfb9719dbffe3e2c12df9c73ce9f73c2676e3e84134e9ea8c529498c6ec4ddb7bafc704e082184fc73329c13ce09e75c419a2496ff82eeeede92b651eaf21cdfc9d586f2c398cc92bb7c42cedd5585950fe55f9773ce392b84107acbab8c37b8c6e84fe4383219a11d418bc8c720f749fe8f9965f78100927133e7203dbc98228e3b74e5f7fb77a701f6524488b530bab0d300ff377467f745ecb2164617bb19a49452082184d0590e81032e2e63e9e2e4b22e63e982c465aeb709d8cfdef8c569c6d18d7f8a31b691353163c59021f788bcce324e4087f1e30e23fbf5852c6736259d199d744e29a397130ed5bd48fc01dc22920814ec57f7e3339e080443f52bd65d9d922538adeb8b665a7575bfd38ddc69e6522597fc8f02c1ae941f93716507859fddb8f057e8e1c51471b4fa23f70ddd584488bd7c321f632143cb3dd297761a602ff70508650610b016462dee907cff673a5dde58602d8c5add0df1a3a0cde9fec04913403421a54b6d550e493c220716d01004142d254b705afd45332d56b2c45bf58b16d0eabfb8a205b4ea5094a0fe2121d0067eff50941f381425a87e77435eed9090f80b6ec3062c50d200c3081cd0c023328116287ef8618a2f96f0418b5bfcdf4ed02341d232aa5cf8d224b1f42b841042ca416f32d02832cddcc04629b92a99a3ff37f3f8cdfda694b2631c2c19d522735f1836258661980f867d6fda57ac628d693e28b272b5cffa6650bb369fd257150fb4a99532ad62effcdae5615916f6321631b4b8bc9d60e232efb2708c4b3f7e8aa0b0c797d9cb3ebbb4b697918a8eb8d9671863bd95bea15bb91a55ad71af72d86f9ca7755f9385453d47b531945cbe75c36a166ecad200539af21a0d922be8734da6f185287be2fa1897a25f3bd514969f524ee503cb375e4ffe3761e97f9aac1dca3df935ee718c239fb7671cf92815aba8b0fc2714f35ccfef58f711ddab43a961e57e3dae7cbeb0a7943287d1afff15c3ba8f03f7ea98712ad5e4348116bd53c1e157ad4832aec1b2f7ed47d6bef9d45aa35655536450f3cd07d65cdb7f8d96dfe6258695dbdb0971cb3ecbb82b8303c9d5fe7a838d7b7d965dadf3fee26afe55307e753d5f73c105972b2db0c151085cc06f4051058e2a7290e48d1f5a1e4712115c49e2c3f5abe54e299d705c7f941af70a0d6336da51118610715881c51137d44024450f494924f124044c18d1f2b659a1bae6a2b2e01007eabd045cce8283965b2f67c1e1cafd56f449900b4772f0507396d8eb3fbee2f9cbe0314613a267fa1e37e1c701405ee97776bc8ccccb7c2639dfb0971f5fbba69937bde5669e377bfaecd4a57ef03d75a787a7877f825f7aa21bb919c97d47a224ba3287e9a3c9f43dd09a4cd63d94ca3dedb2568506bf05d78912763ff8c6f728a18eae3a6459741d95145ba9d65a2b154aa264a6df4cbfe45bdb45adf58ba369620b70198b2a906ecd32d3973e687bfb57176432752b68333718824abf75430f6de697ba0770cb8336f3bb64a1dd8152fa8d2bfad6f6264ec9926f6d5ff4add20f4509daaedfba5ab72f5ddd07d403378e31ee437134eb3101f3d9ea53ae06f120c32e631923e9fa46bf2405fd510108cb308ea6890d726f0a8136f337eeea7e8b15522bed5447f4c2eed509716f02056df9518171269003322ec8bd8b03d2ddec2ba55927a481b82019d81ff75400629cf9e3802c5383cbfd788d30252cb0b0620c2e54f019230bdbf065b6a958804155adb66fd7679b0f1554abb5469feb5553541e64dc1825b8336ad8fafe156ef52b573dfb4ab31b584f3e16c3d4d0723f06709c573dbab28c716202da2598300cc3b08c8a2bbbe84356cb806de64f3376c6682feee4cca752ec2987bd6a0a2d4528a4be26aeb1708ff925aeb1606f7e17c18942cd46dac15eb456ae56db48946a9453fdc0c2cb58c48841cde815367c71b5cf320d494b23f5f8ddb8af65709118673e1ff598402384b0238285716623f1172242958eb95fafd161c68852fae99bb07dab4c0389fb9db613789c20960c6e3e35de77c2b60606971b554aa89cb07cfbedc9e1164eaea4c1848aa6636ea4d047ca875b9451c69b185757beb785bdd1eaf3a642b76ab782362ab0abe8d34616c180b95f8feb3af4294755eea9a8a8c2f6ed0ca27c62c748638c2d3d3e3c452f4646f2dd218431fe778a5deddcfd2b94da9de2e94629a57c08070d218003c3fe62797dc42e293918e4fb57ff50ed955dc753e0663b094fd4aef2434865adb162cf58e5cf5aecab53da5d73765f945e286ca2ec09a5ba28bd6a95745e3c50b246bfa67d5621f364af6d41fab5ec33e85f03831fe94bdf92706508579aaee93fd629bd8fee315f3e6516eaef7c1f6fea4a5c640fee7409cd3d200a62dd49f9cd61ba48cb3ffdbc6abf8f2e61877bcc9f9f5f14649dba1e685d6019b1b3d4fdd611714f0ab9375b57f755d495a9c146f7e094d2275bfaf3fdb5b9a540bf88f695d3e817f1ce7e31e23a3ab4fe5ba7d6756837b3d7dc9bf5cecebad77d21cb88db4c9591ed5731b13df3e71b39e23ce945e955aba4465ae0675a6ef6181729eb4318040699e1629fcdeeab81a1fef5cd1de9dbd73308e9c397395782b992a6c05d6aadb51f0917daff42b8f02d641dfa5046e6e153d87d97bcba87165ed8698c235f2689f5effbe55cf9d0851ee6c900f3a466def4a9992e8877677ea6bb71aff4395ebe8fd2cbd2976a8d245cfa1f0937426d7231d4fb028403c9ad737e0fac30f495e160e86bb9784b9db6695dac2e3d7b2e0332bf88fff5c5ca9fd167c5b08f32eb86b747a583ed5731b11f1f695d8287e0cab73d26cc3a298d3786be0e07182b31c688c61888061b17bee96a02eb9e2f0386450deb2b56f844f7e226f70d21401496c6afb48b3d4cee732e324691358069a5b4e7ec620fddc3b958f7b4bebc7161422db1deb77321d04e43870dbbbbdb5b39cb88a3dcebec9aec5936af2bc3fce288aed7f8cdf540aab9c73441c81764bc636313ae30872208219cb3e80b84f3355b147b8846b1092cc2e2f7354dafb1563f9065ea76a08d3499223d027ffac2ce3d0b212c11ddedba5aabb5c37cab1bfcf810abd7457b0b36022b10b5c47e2820c8d2e24e74b71eb479a24ba0e22c8dddad78b0af3de950fee9db9d96bdbb64d79ccedf3a024461e7d727ba5c73df9188115dacf327ba077de7bea652234d10734e31321939d23861bf9afe9a1bd799edad506cd3f33f8e0051d8f895004dcc18a31422ebae5b03f8b6ec6e772b18841012619c6b98135de784b47b75cd7d4de5ca9755739a202c7c8f51ca0c17252c7cecf2153d2079bdb106b593d39d9c0e5dfe4ece8df674bd651ee7b47a719edbd8c49acbc325d4708f186b6afd5aeb8d7bd1abb9f1ed75ffc93114cad9711d7777773f924ebdc821457c0b00126e7c8e4ba0e11e115ed86d4f8c033f93f18b66c412163ebc7c850d5b4cc8ccae0077284417148aaf7826300f04ea47712aded9e9ebdd7c3c06a7c55afdbb53a1f88a03804c60e62a42201939e9286ffc9a9beeeeee22cf59ad6e4c75b996b09de336f1b79fdc4e7f32c852ce0aeb719bf826d47825409e1800c093f9e87c70fd313ee462e8963f254e88dbc4cf38797deb8ee608bb75f1b2153b58b93fae133473fb4dd0e967cb3cf0f664613f1f37fa90f2b3979fc10ff63797f0c33de2771709d04406b32c0372c0ed1ec72eeef3f82b86527a2abb23f1f6cb554e97baeecebab09671e2af3e013d6efc7800e681529edc8bf16be2bbbbaffe8addf12e47c68931324e987a4b9db9c27e3b3762613fed84e9a90ac03c31581b2ec13fbec73c92553f3e8c36717e3beec5f761c28fbc4658f8dfce8daa7756edeeeede02e6940311763e851006dffc187e4296fd51c36ab5d3f3b3454cc63796b16c8174b5df3e631ccb596e38f223dc608d8c9a3c29c27e8fdbaea272bcaf2e10dcd2b53763647bed51d85efb1eefed35d31956fbad4b61af7d2a48e9fb9baf96854998cb599850b95f1b656172e5ce7cad6de40cbfc78d40168799d31985fa50190cd1beccaf605f66851c3f43647a2e07906f115a7da80d0593e9c21037fa196ff135eeebd4f6da9b9e72a9ed7b95c2ba940a326f9f05998e857f0ddb38140c717e6b59a7d55be3eac246e2462894ff7baba3926b2ef03cef47b8c10e21624406893af241b9071f428f7198c774df75a27c58c49faa5028ef0e81ac1e9801ddd95b8a85fa59c7c2f53d507a9035a48bd7c51b22c5ce102508fbeb67c8befe0cd85f4bd819a2f8cf503ffb1964eee7dd4f01995fca9e9ee8da17d230a2073438830b299cb45060acbd9f9792bfc2f5584a762ad4cf3ea87ef6de055d8f5129acffb7cab01e58ab8c3f80ae771fd0f50fe85e55a220ab07c543881891417a1645c608b05aff5f661d285fb356631c7f141ab6efa75d0f125f720a7ebda8d4ea5e4cde5bed70acb962ff064218e2155a5defaa76ea57b51a998f1fe106fbedf8431bf850aece6e3f4c374381b1d8fd9e794c173e0a85bde4322e5bddf867c414d8cb1d939aaf32ba9fcae8d2e75cf8c17e3b95b9b4e08210fc23f021e487ce5c988be9b6c03852ecf518970bc15878bfa7efef2cb4ba27c8da81f55ac86a24281becdf16e0bb005f08fe917a94c118e37bec609057d6f192deddc120afcea4ecadbb18e5ad5a4b59627f8062eceeda1fbb945f49acff0f909021232cb15f1ba5e0f373cb53b04ba9d02f7f85f8dccdc0334409e28f5d0ace1051dc6a23ff6ec2ac1e492091c4153450a28b262dff7ea224760c6d36a0950f1f8731025940d8c6df9d71bab01f23f5f3bb471cb267c1f7fff7563bed7d83acbfafc8cd383c204d1c9edc2c3f1c65a12107f7e45ebb018426ac080423bf9204e65a719d0f4ab9ed46b00a642989424370db5b8c2589cbedf72aaee3174bd293cb5dc622668c1830170097b188f9e19a6185e08f932e1914d6f88ec856e29e9760cbed22f7a854f112b891ebf80c15f6133a5d947bddc9bd7e13dcee2e435cee7ecd240c1350b415b79b09e3a4acb09f5361c23c8c04b29a4923814cfa9d4abf5bf12bfdae8559344b7260031578d9c1181a5a1d061ebd208e4a1347a589a372a00ba53333a71c95068ed38c8e991d32a71d368419940935c3a3e4fd3f8f191136116652dae9e452a41c216df18252199b2a72a326fec4a1648ec42289553543d3e54b1831d72fed76e0d8a99b3633c235c24c971931944a292537651779d5f859bd200ce38beeee29a59729219cf2247492335a11a7ec2267b4a2bb3b4ed92d67b4e2a443c78e6dc796427777da3193e9c0c18d66ae2827cd916577c615dda078dececece0e462f4c46e81886d16b723337dbac19f502ccc2de9fbfbb486134a4b41429522e29524ad634634d7646a6243393e339140f2b532f661827e5354e42a74a6794333b9075bd67f31c8a07d6cc1ac21860c81c078bb9e88c127e1b0d81aceed2455c77ef51b6bb54fbefbf49d162fb4f3aa48e1d3b769c76cc64a731d855a1b364c952ced9344a49c213164e19638cb43481406fa052c36626b5ad64aad654da345b65b44ca65e954e795d57869d4036d3ae76ec463f8b56da75fdd873a56f04b2b04eb20dfc6bab90e642c6814fd386fdda28a349b2402e0c39011a8136d004f720ca0864451f393937465175bf368ad0c5d88fbb0ce9026b60f4d9048d1b16420869dcb0fd0c218410420891bc1e800b2000f7831042186b24006647b9fa1709d808198da6da5225114c3c2c4a26841c3b58f1590784a7938e991d39429041591e26114aa94da5d1642360245c5f39da4d00c89a180028001f407b9a37000104c0a6a4e307d37c22f9412fe5cf6e6888a8dd75d09ff58a79ec6d9edb0f57ac143f7dc84a71975221fefc15e277334409f297f267801fbb1473cbae563b3d3fcc224242c410672c41050a2bbcb494c0ce3b8636f2c8cb605d4f9364dff318b2b04a1b0559481637f9f036c7455d24af6b41d8af8b4a53581f0dc9fabd59d742c3ca38de41b9de7dfbd1d7bfc8759848226e03e1c5aeef995d1ab6c54fb298111367c2c2ad99b807bbe8b281dd544a523424db4c2efc2e62d282d32bc475843cc02621218c50cbcbae730941f9d70aa5fb75ddbfe17ba441dc81f02bedb80855832ed2908eb2b8104688c9a0a086959fb98c74f4c4852d6851f7e61215713f28d70bf117d7a1147deb625d175fdf6cc8b826326b3eff009835bb9fae87d632cff03366a29b8a3d49138eecc749321445ff711dc89a4aa2c4a79fe97029d7615a1373c3a51c6f612c62427033284bbd039636922ee5da0805f661563781acd9f513bec1ed8276451c5da31c71d88fbf10812c28f359b363b7e9ba4596d159308c9fc920282692402921cc018d1c50281a8951361bd2ccd4ed90ae01c7172a6ac02e83f0e892e4fac7287d427718603932c38b16278f5284862f6b8df1ba7c46e8cedcc6a97bef4e414c82d0bf74f1c4910d28a5d0045cbc6c3e10cee81bfccc7bf39f1b100f725d1b90a96112c33cc6408c13162d6194904af84564b7d281c6c0c20fc247f38fd4db1b94d2a8b5e8348c8da72825e4346a8038a24b155ce830c549cb8baa8d83082c4c801193e48c2c5aee55505715b3031eca18030b09c478c08c19803185863290804091d42005bd82560c9b4d68ad754aa95e5ba608238d098da8700308afa2862c64b80c996b0412403001060f9a1c89e1ff58b4936ac7652c4070c1cd2e63014218976b7a6e51a9ca3da228671c2d71410a4ea0830f3a50410cbca8a0091fa2e820cae009aa0a00a05c1f3ed99565d8dcb239e7755d99c3e81eb3ebb8779f77dfe996de3ffb8b2be25d2a88f6750a88f6d8672768dd27e4e35edde71f43dfebeb63ef1bf758d488b48f886f6ca38bfdbcb0cfb82eea1e4e371fac1382cd1bba87fffc89759f0cce35136ccaf817e71f371f694f3ff884fe9e1f202152b62519d7441bbb543f518bbe77a9ee8094c49f43b8eb7f284a50fcf9435186a204d5973f142588fed50d7941f369378482430c6537543b8636dce5abcf2d8a149fc86597825d8a05f9f483e2d76e284a907cfab41b226a3510b4f121448cf8cbe02f39768f5dd48cd2f9dce3dd4cdae8842e9aa291e822a01f88d4514b01953ef5006e953eb582ccc3d2cb70428094fc401b58c2d2439437a608e1d04dd0dfbb2b8900b4e0410c236858630a164528b8c245cc194c84d0a11504fb7ee75e48bb74c6d057fe3bf7adbeefeb16aebfbf3fbf30f4dd5cad5118ba45e8577823f6ecce58ed17fa568d13d26e7dcc0096b3f08003954bbfc60bb776e7e6e275ae484d4931a416f57ce7b2de80645d0a88b37bddd5efc815d24e54fb1490ef0a83842069257caf87f0c7bd54ff7cbe1897eaaf636c91513781df4fe03b856fd81f2021ccaaf1128418619640f1c50d2df843984524854b113eb8b2839330cc68c197526c988c2b0f2c67c961cb8542841097a7b89c25872e37334db1dfcef5dc2b42cae715f1058ae94d5d75589de3ef817c02bf7ce734b0738b7067a7d8f9d9dc7e60ffc27682dfec6b1a0af360dfdf44304fd6d5605dab80717a603ffe32c43ca5078236fd910033ebdd5277621c1d0c74f1d7ade3eeab5847c2fdbc8fdf80bfc412312243ac25a76eccf8f6c8afcfe667d90f7609ab7de9bfa776dcbdc647329dadf535eebe7ed85827b42a127fce940ada979e05ed4b5d7d16b667d87d2cb4ba13e5de8fa39e1f20211a87f9cc62b4170706e0a339759fd0cead96997b40202143f889b010bffc3d2ef7e067f13380ff473c3abadd55bad6e186fd011232840864b5b5a82f560a0721e4617464031809af6f17d252202beb3a6eeda4b0feab43419bce828688761cd925471cfbfaababcd50863f5529d26deb1e11678520c5c8112c5836adfe469274fb5b89b360d0ea2c35b8e036ea366751f245f757ca525ce7a7a5005121d7f70f997ff5d7246b2f674972eb0a99edb728fb28d816d53b6d51ef7192fba87a6987cc77202b05041f7643ddf22ed5442dce960085e27fe631e5072b30838b9515d0d0e221c2d802c6961baa788388567bf91901073bb38576621919b7092c67d121cce5b99c45072a3733392a526aa7d45b47a8c252151036be17a10fe9737c77eedc7c74f8dcda7df3e34337c1cabf27c949d74d74b8618f0cb5926652ad45a13e3e7cd8d515e40cf165c72df7fe1e777777777777777f00578b7a48d42e5390713b8c7252ae259a2437da4913ee76a749f2543449231c599a249a2499d140dbec89268926892609751a4333f10eb37519999691f0fdc2ee5be15a8d268926e9c27ae1a7a307db7d55c2289fb8c77fd9c0c6aecb02840e5c3045103a08e3460b9044c00b172a8a8e5822022b9a16f839eedc4e7ceebef62e67c73d1f5fceed1cf75ad2c0e6dc7ed8f9f0f7d1eee3734e97530ab8bd59d4472538cb0f61d45052736b90b02bd791df8ccec11bb9ea3eb5130e1131a0c187241f82c0c61a3d4cb7bf07f3f0f79b42208159ef86982d6c60c1184658d020881c60e1c1065594d8f042fff0eb0f24e640c3fe457dbfd7cf7d736f9c280a2a3854411aa2ca156498b003134fe080c416227068f9cb804e284f8d14738e9490207777777b181e638ea350dededede39eedeedee417033733db2c1194cdcc0410d602c118131b860d145156318c1822d4e49f8b7b70868acc1c30e923829428a562ba992e58ba2295c84c160c020690435d0818826525640051b5f5049030a0f6838418e225b4aa25feb57f5c0d5a262c822f477f7da6fcf80f05fa71f3ed4a0562d0a082874ba9e79a893472fec5ffb3118e38b244f80510497567b092caaff8db48c2fbbd8ddfc2eec17ed91fda206a1bc54a75615bbd7644cb12344e957ad7cf89385cfa923dbdd4f207cfebe0e258514761f6397e35e1755f1324aa3eb5dd54e3ca46441c4132140e24a960a3461c10dbac832258724dcb036b00c1dbab60384d02b91122e80e861082d9e7041124e630a2d57f460a485105ebc39e7a684d43e3c27c23fa96a9895020f5990000c2c5394b481ba8253458a2446c004076a24b5ba0da4354ed003143280b08206e9d06f330fff4cc5ee04e76a51ed39c058fe7ed56a8ae7ca61286145142a40011b4cec8085ff757777800cdac022a9871f14c120cb8db3738e346eeecdedf1d8aff59ffa552b2db1e565e6619dfec85d2a485ffebef2568bea31e4f49b1cf7d9addccbb98174427952d56469357795c0feb5a8f78ed4e03a355a1e41771391714d85c3fead965916891e2079028a304eb668f5a324d02da0a1872f5450b08328bc9054244111a68c33b0e0f0efad768c32ae49f9730b7eaa65a10d1109c63c19a2488a14a0c00904960c1107196b080183205aad4396eaafe1346bf951fccfefc5908d70838df2a7ab542a4781b1515341082195c27e40b788bfd0eafaebf2ee5b5d97422b661cef6abec66eb5dae9f9b1c2b0069501b79f01ccd3b73f6bc6e947a161fd7ef536f3989c8401f46031ab005e3861058e2bd068e2847f91590b4faa2b9fe75fcfcdf47a9022b44bd1af5daa013f3ffa31f49d1fe55329230b2121ec6ce49889d5de0a09610a7fa1cc97d66dac6452bb1972a3a3f865b7722fc7ffbbb937ee75ceea86bb59dd78446a27557f0d54421b4107fbfdea7ae6315da747e42d323f3ea53107967dfea7a130732333f3166416995d6a3eed520d60f9f3f2563b02446103a24da8746e766439ea10d14800000a4314000020100a870422d160281ed4b5bd7c14000d879a44704817caa36114a328ca1863882180104200000600cc18cd8c036a965f38f2483ceeb1128169714c65777c0921396260e020b2277d374bd366f10605354220b106aef91c940ad7717a9bc2035dc3c4d2e85b27a292e6da03a60770d4e6b67c08bff9a3658a7ede8f591035942667ad790bb2e088686f1e1c965942ffb91bc9b7abb51233d3f90869a9b9a58fcccf11d2b42f551d55933d32964f22a4790ca2244ea0cd6f5ebd296eb5fd534ae2fdca7dacb53246489b206241abed0334a2903c4a4823a61bbfe302de975501e9891eef81b3595b83fff33c95681ff07f1c62ffe3393c890ddbcf519b6241a043b3f5db0f8e929016b69b5ffa29035a754dd6703f8c50a2f7962867b4a340c4ed6cf44614f13b663e75bd481f423784585470dd5b288e8ff56833bec9be96cc0f05a139c0b1b40f1fe3d97273c4262a8f0df772a68bd4192b1d42f9df355ac193cf1fb71ad2f0a21ee3f31fee478c458e907adb31e8b4f6e7ee9fbc05b070c8fe9d70dfc2c8dfc3de9952be06bc38b2c1bff67f0ca0b51b5d185069c9b915c7f96d8c238d16898a285ebc35957cf687e5c49482e57ffbc8db4edfaaef6a35285cdfbdcad9d0a6742ad261eb78acb8072edc3d4d99811d07a34f5901886eb1d8093246ffbdbe0c2e894d4967efd06fae14deb6a784df29b163bfddfa468ef6d281a5636971823ca400140b58cd50a7393e859fe763d2f496225d657b8c6945af46d1b3eae7068d1e2aa34037f408baa18aedb80915cc4f8df51fe6379015741bc4260e9e782da116cc50a8fb43e0fd3e57830721ab65c20a17efc9fdc580dcc9c58d65b8847036b3ef812c385243c7f20dcab5fffb68661bd4d21e7cd11a4b98ac22a24c59d05fad620a0a7b90bce363deb15f38231cbe04ec26029a91ba25d4b8b9031a88559f5ca94234bad722adb280e02c57a1a05ccb2d10f38eaba1c991f17ec1decc15de3567483ea9ab6879e83335c83b3477ebf2f799f36bcedadd1a4fe5568742193f98ad2a1b6c168ae4e129e91e2f5afd9632d9c9830ceba2855fe32a32e74932d833b652fc98bf2bdd6a200a4030bc53421b9e619eac0d5ad9992a5ce7662b7923ba645beba40ae7f9cf621a6a699e2b9fea030a94a081208a52bd1571d73d0cb7ab2b2f7cba45d93840497b41868d3f7f14cfcac71884391d63d0575ef6fd929c96dc2768f7e96ac2415d2d9b64953b07c327ac40e3e1511f74eca1e545eb9402a58ba97ce6c6816c8aa4e34399bc125bd8bc7c215ae898f50984745cdeca8d90d26db73ef0f3a062563d4e3c6fa0e4d5cb9a10372bc2b534c615ade8e9dc2efd2e338ea1efc7b4e43e2ee511956e1cbd013a6a25c630587115657f9402eff37fea9308524e50f62b8be08f57c82cbabb9021c53aa6a65c84fdb439ff56d1b830bc6af3fd29591656dfd2ae3a2b9668ef6980c18fb36770ec9dc9235b2368cd871a0c9cd43c5caeb87922e3848af7aa1f9ae0426e49a23bd3b3964b90686191ac66cac8f3d672af501560c523c9c9ed4d984b18b23d04d44a828977b66a2ded0a201691397869a2e980075a096f912efee55ef82141fd8cedd457e0e35e4ab320ed8abc906ce0db95b871c898d4ee43164bfc15bdc4b5bcd634e0043d2e9fc5cc0ad15076fd34db25698dff0355e3986daf7f064cb5d7f7d322e8c32216fa4104b28809feec059511ae97fc318aacf6228f4a054b30eed20b4a0b526c3d93869fce8e0d3a57e0789fcc9aaed7963f833afeccd4686f34b1ac171923353784d2409b4a4cc9e6880d149846ec0c43acbb495167ce1893a67f805230d0686373ae62a70e89ae61c15d6ceb8446b65429d14805b7a0698e3119a3e53fd2cb822fbdd4928807307f2b0e194014b82a4cedbda51fdbe6f766b31284441c3be30e4120148d8763e421f00832fc1e2f2ba060c023f3a1783b74260d5963136404b96d166243b67d50c56b5a556c5bc242f64baa74d180b7b7cf99303775e7eac4d416ac80c080eac93a7fc35b1c356fa053670031da4894f3aa50aea8c8de6c2c0cdaccee791d80730eac11d1c0fc9924ce060f8bc8b161ebd72d2905040020cc8146bbe15e8c2be5a8da8be89b189b67f1f37581adf2f5e478be93f4d02d3a5abf838b006db2e3714138b7ec13cba4759eea8a2de1386e3d13b62f1ea8dee4d9c215f22ae98e177d5d7da3426a27c52ed7141cc6cf5eb8058a23ac3a9bd094c7750c480f516ed018de2031229056ae945a1520e220ae9a1ae6f84704bff7496dc7cf5b46ed4aaa98aaa81c6ee239823f74269682b821502dcbf44bf1f50009b5649904f58faa3c3780508f1f5fb2b23341374d117568b498d07913e21fcc0e5a9f7fae99bb63187adb05c88ca37c6486cae6a2c61a49281983c36c45707d9f05c7183afdb0d963da340cdd374d77f944b655f67ca6b77bd16cf003d5b990b6b0bf83fed8170a60eedaff64854b74e86b49a29caf769481b6e635d32ce3f7cef627db18d6a3a00a53af05ce757b5f820e14603adb62be0d06775eb628e01952460b752d54da3ade659681cad682d8127f7268593167416c8a51a02431440f36fe4321674c3999bbbb5b33040cdd9189914095b0b7cd42a24b212465fd738717bb4a301946c4c042429018edce439b199b2690e60192dc407aeb2a97d4284c98a6df580e6520a8a26cf0c21bba78b9227e9762fb9b63c27e214020a8bc8ac3a2494595b0c45852f012f74109edd656de8d7d9a1f58f131befd46d060a91cc09929580c4b28fce6d7e6a0362a582fb11643a2a527217eab76c3801e2c2c894cf90cad6f3b6f0ae13b45aa9adbc41471e74781a909e6c55abdc66b97345c87a315952af957ea27444256ab162883ec0bf608d1581ccf5b168ca6d62090352b972b2ec146a5bfa0d8d81bfe7a252c1963354777afb5aa274e61a08bf19f02e4b3da787e6b2e6e65dd67b02688ce70bac998052c69eac571f487ad99c51fa10550ed88286a0a53789afb3854b54a48b9547a1326b6fbd10f59412af8d1b84ebdfa2b191014a594ec1343f7fcc4cc485b4f0f6d25b1c91694e15d43fe5edc8ee1bac07725bd20ba7b96512f8704066411beee8a7db380d37ab338d70c4271b82bdb950a14a39687b091d523a1759a496ee7006c832a7c14e78a3810046b3f8a31304071f1da4d80348bba80e6c69483c9e63128e12e215f77212db509c80748978bc40be2ede1e903cbbdecd28dd6607556a17336d759c06361c4c86e1270bceeee8ea629e172ca2d547d242ce2de7407e816d2521e3bf84e89540c4bc7400408de4637204d1f2584ac6def9cf67a567050a0dbd6e95d19b8c33c95116301163c560e7941067d904a6e01e5e76558738e671226040c6e6505d776aedce2472dbdb793bfbbc66fb0e7cb1fa02be43d98aea0bde68c9f948b415b4e1e7a1edb01a68affbfa9b3e90babb57f882621263f3ef4a324bae80918c8525df048170ff05f38a1a4323882acba0003985351f3dfe1c9aa884f8b6191b909225c5a925abc7435d311661398dd216f9bcbe5412b4df9474455f835d70c4c496bdac3eb59c7448c24bdb58ae63f3e82f008e65240b930976c54fae54a39c775d12ca60c62c82d685ec18a231f0bcc21210b6d5544594a19a3aa6920b8f637eb7cb0a1bb06db1a1cd7e23eaec71ddc1b6e41ab591e53a5e55aeb1c0553048f4c8b1f36efd23b72ba8743a903a39703ce1aedb38f37e3841880948944b7461a45230f124afdb444c8edfd1df0ad5cc9e58591db6b5eb3104a4ac1ec696dd985e4dc1bcca4bd4d605263117de866c3726f0d18dfde66061ff22a4171ef8052a41166dcfbb9f952dbd53ccbc53fc74f23a83e809ca5bc124f27898d17842ef2dc93d0dc22aab905be84536e9f945363bcb972dff8962f20f227744bb07e85266992dbfecb3099ebedde0b632f93b15f32175d39059db1a68b05083d4bce038390b0560451d88f36059801afaab0132d9aa4bad077a79d5996d541a9c61fd0ca6ce312166a078dff4f372b34c0e1ac939e91530b20e2c8e7e3529778e61888b128bd81754503b27c372832b33e24507954cf0028597e1b96c7060ac8fc39333aac2b09399e070ba0cf333f26bf041f4b28a76d13474104e103c0c11f411b3e34d2ef1cc3c2c82d6f11d77e203894aa820c16f262b4bc762c06f770d37dd586695b181e428755d9305323e8900601d17c4c54ff7dfeed648601fab036d18282a391223c309e14c323dbf655b69eaf17f943e48bc3e83b5db74a80a32f18004fbe60f6df78b2ee7c62e8ca2bccfe0dbbb58da22dd54528a4ece6454e053755207033760447622df437a755e62405938bcbeced7cf1287d25fea0c0b1ac071bcbb44c71aa00cebe1aecf5bbabf9c65839ef7f5bce470cc26f9af07301e80b5c266921e77478c2d2d4c77b93ae3d728fec7e7e49cd27bdfba4c7814c8268cb7295bcd740880963b412a6f8b6abca83ee90039f469eb0526338e27a39d49acaae444b5af1071279284e44a8087aea4245f81018f84a70fdbbac509690f8fa9d97af8295e197d87676729eed46e7c6a97c938a299f9a2e0c0989754bb3f6e82bc01b7c256864b9dc40c72097bb86d965f5b0e65f923e19b15d9e53ba4ca7a41a17c1936f1785c11495d817e5591b0b70cdd7433e4253d2934b195b1612010f709334109e7ad49285a054c340f75072c46c062801720c3d918f86a525e98accc67addb41921bd6d8abdba2a9f1166d4ec884501c425794edc050c53241719f00bf0a505c3a7fe662a3b246088639446ede8d4ef808df226d5bde617670ac82654ed3878c6fea05418702102a2e8d7ae1cfae0c2119c9dc4bd4048994d1eb66b39b8b1291cad6804332f4371eee6755e6942eb9ce8cd595f06b7f0f37624b9cff2f2bf13ed76c2134502cc305980005c032eae7d0c2863c4c321145c3ff57acff49cd2fc76b3bf489e86026d2ebe321a5c6af5d7668e877150def3b3787948bd195cffdabf094fcf04edafe2dd0346ebf27c286f73e9b8fe1623fc8d0be558644eab93b89999181bdde9520b0ed56c3b88f8e79a9e60dce725be211ca5e698df78c3e5804e93425e1ce2a650d14d3a678b21913a3a0a5c84ca150997180e23f2cc2a02ba2b17ce2e14c6f6957fe2bacf15ef92c89382bdd3086d2ae390e5cfe9dc60304307365950462017b2340419314d975bf906c695d7ae734ecfa4b61d9b2ffb322dd85ca49f574bf6584516aaab6c79144d08e6275e2dfa768475eaa129ab5e24b5ef87fd350df0f468ae0a466a84b85338b9cdd194c3894b1d624eec14cc9a4c0fcd64ed1dbbea90e3301bd088b46666d6d55d820dbea6c1f52f2c4307812f110b9541818d8390ade70dc97da524a54c60fc7a4df685222f19bece8e293ac50758d3115463baa194a0598b0935bf3a299e29d2e0102f7d52c8a660625915a71acd64611acbc09342d3d0d97c8552f63d8458aa7dd4aa63092a3588cd2021163d8aa043f547d2784ed5f01ac4e35e1f904ad85ea6dddf63e2fdbd8be64a71e27d32321142ffffc5362be84c68eff58e9a3159810734212dc830cfdda1ace217a9aeb40e313e092af763717aa8d4faca299e44f8789efe96c413deab6e9c754cda144593c06e4c83ab4f153c4392ffe5d3f1d8ad60dcb4c2c830c7c30ab05d8f4adfc07ac761aae0ca9628789db0eac5904f556c32e82aa0cfeae16689ddbb422c325760b55022fe83b4233a4fa0ffcce99974512b8411b2bf15ac4c7ba31975eb73c09ca043dbc0bdb61164f7544d261d0159b8db6d3bd50f8510b4511ea16df60dba7394bb4f397b3006dbab31ae390dda9dde23b2b3ff2dc93569a3bb59d68a92197c7efa395b21db3f1e3bfd33ee018895c8f8eb9d859ba9ed2414fe5cbc724d69e9976a5329201e7e216c9d02d7f8ba460e20a3bed728d425a8e7d56fa854c71ba9541f60335f99bcff6eccfb30f95c02455b4854845ca748ee0805be5615e02b2140d3abfa6e5cfec2b124eace52cd140663a57f17c6a717f89c6349abac08c56d2aa80bb74e538d2ee84b8e9b9c038b1153cec11de02eb7de5ba9b7a4a4b7eaca1adb3080830a8810cf71dd3bbb09bc965eaa24ed7132f0b079766094a2fe928eda90230256500c2614876bb2361c8b3f62b227e8e60b9966ed4ef7ca9baf4ce2e34b3683fdc181d3e82c7cf30fbe0f08b8123ffbd296f310cdf26f48e807123328e940778d1c3856a77b399b7b21caeabf619eaa3e089b36b053ee785e3727f12e8a4890f7c29f6bf6c4a07a372a8f62eb40ffc4b7cf5e44d570990cab1490bf9ecf17fe36066dc918d3d686869860fc0203dd82bca320492a458daf23901fe8f815152f4fff549c5f3365b2082e32996e282a06d1ca628e4240f5438d4d4dc60423ebbad1e685ab4b13ba9f81276395fbf7eee53b2ac3ba412cdcbbca63b3cba8e226a0ad91a0f7947a3cc0f1870524bc1af4f732a47d9159f36912e52a14ae561631d5c94c02c8039a58933ba88f9afa4b63132680ed51ff831725096d128559ad0857b7663f73042519a02212f281eb17b6b1ae31641fe753b2a5a75fefe2bbd93c177c4982f659ba0f3c9638b6e855f4e21d53f5565408a195a52f094c3eb64ac6025dcba0299099700a096c66579e02c8325867f4c1b80f3e398decd7f15ad09e750b09673eca183d4974fe9b2e10e1bba2d55236112b02dc26c5b254780cd30cb918d09ec2d442f923729de4482dee90b2818f59ea55eddcf517bef49d67d0ab180534a9197db671d7e49a0a672a0b172568856100db1dbecd55e8ac10c350b4cfb9f1c1041e5a42a331a285de674861e0f209fc87b3625aa209ed390135019016b9e4f208a15248a332569a8d1a05a43c96ec00ad8dbee07c25310d61aa6dc2bc0b631f4ca8b544735230f10fbd56899e0e33cd55404a7dfd469ef02a03f29db119d39c936e223f53842f47b37176442058894ff6301fa2a8b55042ec9aafa86d403491c832535c867ee3831704488a34667acba3d0cf85301513e0d46c94f8e31d2fcf1cb2a492f11978b7ded11bd1be295feed6aef046ee9379ca9bb32da4417421ba80066b4dc0db90197cff4246ee8d7c5490df46cf39a663148806aacc910c2570f43942f375199fcc47afb9770b41c974394f2535e7cf704841c4b40a58a15d432a13c3b991eebf46437f6e2bac2d46bbe2ccb3447098c74e1ba718061f046d3317e726fa0037f5c815b419314f12b58cee0b3fc98aff0269dcd1e47da7134e223e3776af514f2a06af5374d484d39bc848f2b2d501f8221139ad56d25a8e2ec09940f1f94bf52f8310a7c6328812ddc63f5a4a33f4b8099a567974b577ac07a53ce47ce5ec2a9a1437ecd127d3adb086331e9be50bb9fa1d31a7671b2698b481210b3b09a15302a44f1db895b9d82f7fa557878924471ca5a682e826407f7cdebb5b4dbb5bb0c64e06a87101fe8ec8a2255e44afcd25b4c0a5896e2211c2b960948ec10371b9a5d5400febe19628d7476d8f2707f0b345305538a826a300162ed14b0266862d09703f6f4b062314ac426517ec0249a3937703df43399771166038c51ab9672092a81c08d117654acd533df1213f3bb8765268ef2018b51e2c41af7593b5307aa7731d318204058d5c6560bbb633da6b3f4dca363c852118ac1a3b230da93328e2494305e39e3a20901fb139a97eca2762e9936b0ead253a04268293c23abb0c96e9eb8589f08c3f9c5d0f2ddb98796bb7f2559c13616c0c637b04e7e128b7d4e897b74958cd2db86881344fe18ae2356adc8b1b35864a2240bfb2970513825a24d1dd4d242c9facbbb875f8222517af1292be41b822729e59a3e4da75c2b27f1cda036ff92e50cf733becbfd7d4852de2a024f3b06f7a94da6d9bd40bc0589a21217cbc89b5600a0645b46e7f4dc10376a0b858245287e8d2dcf97f962f31d63874b67948456cbf6332ab26f1f45f81527b856cb3f5e43f5110302481bd8a24b044b69da51f4fb684debb7640d4d949c3042ba78273e432926fb7c19318fd3c2ad5ae8ef576bef6b18b8b3b49ae655e4df7bee8b11133c5a4ce2535ba52a8f28c07d945b1d7da35877cec8173ac6a33cde39ba8b6c3cf526e3dd7ef32353fbf7bc1820e116183ec1e21ad7cc5abfa7c03c2435733e699bc8082d6beb487f1256c516847ab7ba327dc0e30616b16a50f92b0be18d7694bab111ea49bcef502a0a1b8e3e97e9df19149aedd1d8b647256a13fed0d21e72e997949631bed2a0138f2d63a8af9134fab88f99fa3ec867f83ac674bc1d6943dbbdcf73ed5c3fa7ea2b6355cd8f434a0a55351eff13f40827c29dcc3485c7c3813b19bf13459eef83dd49e61009cf3549a65988dc46a78c1a1071bd41902adf5d9579053c6ded7ce171bbb1eaeb0722e51f553f259a6cabdf2e79c4963ba1fbd0d2c229ed2a4ebb8eb6071dac008face329e935670c6f198f78c31915a0391471826efb8240e72a90d935855b4ac6ccd55c324b5939da804faa7002eac82a0fb6c0d860a3f5166ad151fdaddd5da549d0fa9aac1557015694f98ae293961bb87e052e824c5bba401509e62bdfb990bf7264417b46ca5cab9a1afa9e60a660459d4a9722bf95943130e4c9778062c44094396af48591ace5ba2ce32d540dd63c11eb67f495494f2965ba127de009008ce8029f68a0f84a6be0a7076d4fb5745465fb6dcddfd6a964aac9106fb19154e67d94591d43651accb5fc5de44aa96e9f4d6efe80cf86381b42d3bee22333818c3141499371b6db8180ea0830bea5e21971e23a65295f136b0ba3fa851626d49b46715d33dd0c5c3b17ef4b1c8fcb34ba998a4276dae4cc5913d783dc9283f5ada8b1abd75a9cb8ee9fd05f4c78b4b8e96d9b12e521620feef9b7257b58c60e4aa4305bfaad032497290db6249dc0e8ec4334420a325e007f9a9242b6c92efa4ffd555b7fbfd44aded8a9752051db1ece24d1dcc4e9f5d6fc7e187f3086db1dd274a47945d5f8fc6c1892131b2808856b2c19ebb29b562a0fcf474a0042f391ea6eb66b1c265e55d53e9ba255fd52cad95babca92cd6260b55d3de45463ce3ff783be9e826bbbcb73a5268519a79370fb1103c726fd521c36225ffadbd3590430c09aafe7757420dbd1cfd622f2583bcdbdbee86098a3e99081432adc690bb9f532b66031f3a4349084bec5853f7a2fc154a6ee30226ebe108544a2481ac53aa7095535c1cc497a07eb4700b5e65cda1cbe28e5a6fdf972a50e71f73fb3498022b6b1536b0a98ccf63b7fb7e22c9e4d73f854ec66ed6457b218d319bd92db7d094e171124e39ea4f2af035073a722800fc1a468bae0133b86073b3261254d976cb7769095f725f4f9a20dc2516da3ad2290021492f999920404e0a18b33dcb3e9f570d927309d699f6caaea96b325c410cb7b5274aa0b4f4ed969a029cfc3e43df00b4f7e0bcc36e35c62235a1c8ef412f3becb38fc0ae19dfcf606b533934fc40ba5d35554cdda518fdad2bea4013993433825322b25b29cb7c91cad6b578adb23ee7484c2c8d2fc4bd06ce857974194564644abde0a3ff45e91574e03bbc67ac49c089760b0fc2766390720c8fe7a042cfc90b4961d1906c8eb4e313d3088933d884ca1c8b26f8dcb16241fc2486f2d3379b9fee37b3ffc60ac0e67fc8d1f5eac7945d520963b45d7501e2474d59286334790521240acfeed53f73c90f2e9328b69235be292a02477ab31e12019bbd373889dd4f6f9d9e8cbb449dac8d97cedc61abf6c7915bcc1779ab37f7429d7201e070af46ee1a0a645dd2977629acb55bba2466df35d2d3be2a8c575d40fa051eb529762093787b28d44802ad8e1d414a41bccc62d304cd988b9c72fc838beaf1179ab9d262fcfe64ccca6cd411c0507d7f12eb2f06ce4533e40b3304b03a844a281eeb4823c2f7e4e27b265e138f3d6bda26f8a367c9196fbff4928ea157f25cdf9f20c27e6409496d74301dfdd91b4a46a2636cf4bc54d8f98f322db16d45f7b918b968fcb4cd4db6b609703bebb846e79491e2799a681aa8f215073f75e0e80d258a38261f79956035e3d5e30681608e3db7fcc1da4f5a734155ba1d5eb53e49065b42215bd60572bd695ede14421d88b925956bc1f49dc3f75aecfe22b49502cac05f9342ac7981e2af31799890703f815286bb4c031752d432e84047582978e823d8e71678cae38df0ce30212342047ac70ea35c7170f0b61a657dc70a2e8186e0b78bf200dbdb50879824f89d6c62e23044a36816d7630a4d75d79e4d52bb79fd9b83914f27dc1a89c142e8b87286a574bff71ac306d588175def378376186c5d4b97d09cea4bf319824f0c591b034570b4d22e9fb397ec85fe1124209a5eb21fe24f5ccc312324bc982121b32aab6445ee0e3c222ae02d01c62dfb830ff7b49b8ece0cea4c38d412fc00bddc3efad72cf2b1bc28348ed1e4a65e57c258859ed8ecd21c00abc9609766887bce27c16d31b87d89b8790aee98402b0097ee06fb10e1d3d678a46ba65b5ef15115202a916333cf6bf3ba5d9594179cd95a9cad8213e9861a7f0b67bb71a5649113e12c48bedc014ec74468d70da70e95c0b288cee8cdc843d803762a863fe0163e12ec888b698f5b88d84ff62b1cc79b33169570b022ea10b11f1dc5ce103abb34a2e078d373c84de8e00f1477ebadaffd6ce721e8267e448ae5dcfe287573cb51fa65379ca2573e1914118b041ade13a6be34deb8b428a1eb6d6e9aefe2421a42b2ba784861f23183d80fb444be03898e3bd01bc0ca0910fca82defc6883c2f47881de27aa80ee77f9016c02d606da3ff80d8c21c8429c82117b97461e7e27e3184852db28b3d3df5b71b08193e2a5098d4babc83495a50bcb4e21f0d275b6ab3e7726b459eed3c9a766aff87447b5f6df596ec0ed0f14a009e279f981386dd21ff7b464e322724c4e033d77157226db41dce7172eb52b6b9a75130832a7ea9bf94ddc18809da8bcdfa34021e9fcfefba837354d7f7c83037225eec0337aa26295f12dc4f37dc93334c62cefe1e4d9b8109b1a486a52d2a714f269272484386c51ad305ec8aa729ff60b025a96781762214d215072c75c84a73de9aecb2a4b20790c18603582f5260950a94a68700a4ca46e61262e5057e1daa754bb1a1510490f21ba31dc3725f143698b5432a1f8eb8a297d4234b3daee3a7a03b86c497acff4f0014413790dca7a637af116ce563bd28ea7131d37c96a698f1c64032ace18aa538fe38d21ec36780f6869a6c1b8ec1de99541fa25eebd8b3fa1c8e3c48633775a03d08f13089555e85c5698a0673bcff2c6cd172c4d6cb4aec3b048a18e164d3491c149053a375bddd4fa4d40a88b76167c1107f0ed02629f1d3e360f8f09e7d787d0564e568c181d4b680f19107bd92c0a33faf76ddaa5a70b855c398e5e628892070b8f556642eaa48d18f337cdf080cb850152864583cd02bcb5ee186a973a2b6dc2ebe0eec35dc120f0213bb5ae1aa6fcda870046b9b171b4d078c1028fe044f13dd8f84ab84535f67702c378b575ba8293fb8e1c8e41407ae86c3e92c4a4a81821df2ad7f349f97e43c5d5eb488e19ca4edb6746c5a53869411cb6e56a2b060c145615d24647b1fbca5ff3ff704174f5e8f6f36e2c2a737d7c29c789adf182d26a89be42bc1e9a67283ff3fa8821694411f51ba14a45dedf750c1ec89318ec1bbabdbc840f64f789081848ebbe7061e27201ed6da08ecf634034218ae6de38c7f390b8d61508c81d6cf80aefdddb046fc17a84013d4071da203127203d75d002d54179f95f92df9e483eea13971b272983a590e0ebbdaa343beb8795b2555d31781fbf9a58089046327568ea93715546804dd9e50c457cd2774166430b02b80382baf2b43e69d9ac54df84900499d6da60792fe1f1f2893478a8b5a7432ff0f6a20efb030025097c3c7f784d196812cf098efc950e1a21e8a107d24c002d0d290ac00f99e60645824f5fbf7718c8d647fbd16ae2221532c6b39052194e3abe9c8859eec270145770d12e728f0dc651567a0c76e6087aaaf44bd630dbe7c2a481b2ba791aa87d380403d3fd1a813250e5f89bebf2b9c15b1ef08db309b6cb4eb54ac9e09b67f9f1056bed1118cc30603fb3c9ef636fca270360271a86a82bc2890ca69fedeb6b614bf37439813be8b61f216978bcd831bbee188ba8a5cc9f6713d52ce919c4b3d75a31ce9f775b8aab7df6f8c35a799199a8383ffb3ff31a792ca9f05570b8332155e1f4221172f81feef3b78d130d1f80f1ffcf7a1ef5b6a5bd002ae82292043d89e03bf456d6c987bd5afb1c1589c7b567e4519eca84a9d6b3339f1bdf8a63c9aa3ce748364cc67ac7f64986b091eccc7d8f4cfaf0a2abfc5ccc05ee32d45e686794a69dd952220c005ce4d82a155928f2a831d59351e7823061c4fac7b8ae5619a2eece0c3e8dfc8446cb10b6297afb78e868ae861881fc8e017718fb680f23a1d19bd6c01b7c1eddfe0ae92009fe87e151905b6910e26f25923d09a5028d2b064623ec3b47436aedcfb0782f49edbcc2d9423f165c5d78a96b3ded1829f535708608e0fc7d7373face13d5ccb2cc700769ed0c26102695337ae177229b4db0a8be2f50fe1ef38f35c14239f6f09111ecbcdb28c8764d89c975270d71da09c6bd5b41f9a8a6c3cb49a3145bccb35fd972a215ba72fb377b29bef609821c7efc4b9f907e26b7eef298b546367fbb7aaa3c89b1f5da51a4ecee1164b1796d81c0f0cb9064b6f883af622bad23f11456909f0756cb08f33e4545a8738c978334e5f02f9a0ea19327ae37ec46cf598a110bb1563864aecaac44ee5d8ad043b1563b702cc54c7988a185b197ba8626ce56060ad2750c42d0e3bd2454a5bf17edd9381eb30d6344fb309c31fafd6881be674c2cf27e4c9863e1bb0271bfa6cc03d2fc4f3847dda30ce13f67943394df8d3847bda90cf03e679433e0f98a785a9cfdd2fa41dfc79e45ad88ac4ac6fe9ea997c8690ea904cd0876467fc26cdc42f4c8692bdc88e544cea7e336f8c4ad9574b95fde4d2e12c6aacf69f846b1e9572a8f913541247992fbd6fdfb416ec12d4e97d7966efbe9ea9cff79ba975e91429fb2b3a857465bf954b6c96b8e7a7b6a3c27211f7feb8d1af156fd66c02c6f72f3128a1bd63f52cb48e4481065e98c91ff61284ef3c7a09a9244fd82154b44e28230f4cdc27dd26a47b1e3805721a91b6547983d61b68b125dbb67ba56048f5781858939fe310b83d130e851175d8a068e7df8d37350d545a390d9181ce4dbc7770de49654a0656de4bca8c1798c9b64b8e98a43eb19dc851523273c43f90f870cbc35916d3f5cb8af07b58b0fcba34a031880ad86474b15c9728159fc1e5b5a50ce4bbe6904c6301fe3093c1513ecdb5256ab7e22018bd8398b4e8932d361d4bbbbdd66c3534eb42fb59187d959133c5727340bcaa63158436197173b35326d3330a73d563599b98251b3532d4038fa09792737650d9d2e552839698611cf6fdaade11913cc5798bdeda2b29585093fc2b7e281bfc0f82cda112f2284f02f002ad46c4132892c207d4ddd4b82b22a5faf364693e9d2d9cd1cf07b0d5d4bc1011974802226ceaee8001e4ffaa7978601a93d47c783180e8790cc4270b5c9e93c2351d6c2c6321cf60ac77ab147695eef062fc45b078de22e46fdccb895c061680eee260b194ef1e129a4c39163b6361f029ea13c05cd506cf0ac5bb6a8302c516f51be6c92f408e94dc0b0c2fe63c51f13fe85bc6cce0808ffc14941e8ecd25b77c201563336345b17808a76d9e40c7293fc2235084f4fbd379c93fa2dfc820a861509195b1e368deb4aaba2ce26563085d2c26c023287022310fad433fb0ca23e35fa26f4aad4e7638a734b95f02e61183474e939bcfde84620b270ca36e2e4700d941c7101c769434bbd0d148a863ef8b22b1da22f39bc1cec846a0d05f1e4eceb15efaeed63e5fb58a7eede181245071138fb79e51ad56c7d7d39b9acb65754e19ba0be861a2938d4037d62acd0e106f14c87abf2e6a97773712b524903781fdeab90e69c2c7fa33c59df2af9884f811bd3bae89e9cc6bb0c00882a14cfc0893815af44d0defeb779ccbf23dd0d27990b122dc6e740e309fdf4ffd5069c3d6f2bc4cb575fa1ca60203bcf7a8dcd17b6c259c08a86052f218cd30649307493808ea766360d46bde9881d9bc3722eec531b0b5a49b32104cb2a3a5223de52484e1bbd79c44ddcd2502cd4906971f623b7debcd3e8ed66b325a49d2d771de00bfa970ef8bc037dfb1fed649185c3790c379012ca55276f3725533a875269fb2a6a4595eb77c34f61a0abb7e50c37bb45b113d1e05281e0837a292b0fb95f016b98607885ab7027dad507f3e378125f2fbe5736ea4b7e9ca41d7d68db26ed93fdd40ff009a8a2f04832b15f2053dd8debef40c6f8b1793b3e490d38e886b8a19c1c789c205a9c42c53fc7036061e764bf2fcb936c52ec45ff17d7551c2670bfa47e6393b9e624999a7e03e6c834007a42ef5f6ae309b7aca6ddee48bbbd94996829e985568d65f501384e7ced5b100b0e0c5d83ee9547b035e9e6320969aebb603c4148a01f3497f5ec036740bb18fd96fce3807d0852aba16458a7ceb354f815221191b95ea05d1ab323668a05ebe1522b886266d6bd4e6cc8371744590b5bc51e2ad627ca471f0fd50549707a145562886e6af7eb1da760685e6b14c1d87bc541f9d83a6b6fdaa773a1009f6f881b0f9dcc3e3fcaab29affd901b3cd450230259b495487ccfba70c82a6c6f4473596d2245362008d0020e44c468ae55de3ee0a9c575abb340ba805122e4b39a46ab00f9c36f7020ba8b6b3f7d5cd1258a503a54dd6ab95b9c1723d21f4cb5a412674a72447c65da797006232d28583b6a9732829bbca40411890b63a5d6ae596d30295c2cae468dae8711b12dfe01f9c54a5b453c10e6cb3d01590a8eac74a7cc83508a6930c52f20157c25165dd33b7adb0701df00ed484d849bfbda901259d0b90d6116609575eaac04f7a94ab5395557e37bfe4cab9c1e7cc17b8156cd714105812285cda444d9ee58894756aa93f202d9a6f79639968757329f7c0ced04367234abe86324bd72b7568b36e10c3a05841f2297087be6e16463ac3f79dc1a83b3cb33126927f0ebad7f5551da90a2e04a9dc4a9e3434dc2f3f81607f821f6ba92f79102a63b7413ce8d98c736d7aeba259ae76884a357399b442807d2519362591a5753033f9b275734d820ddee99c6b7c2664c3487ed96616a8d35dd33b35c4821c263acc2b1678b8cdbb9bbb0db6c600af9bc5dbfbf0617d7f6df22890e00f8d16271d54c34870af5e8ce389d51cd335cd1f7d5d991d67ee0c19242b6c72a6419809c3241b108c0346847dcebb635386832dd70645025f27842a837f24e93515d62e880521927ffd9b173f132cba887157d30a1ce3e4758a2ca86a30bb27452030c190be787ed3ba40bc3ff2711537eee516ada9f2251af50b1e786494ec49f733efb1e5cb472d6c01e0e2d16b86d688050e6bac1f1d30324c92103a17a9fffd164516003187f77d74ab85f9fda574ac371e10bcabc1bf075f7308e5d8cf1f78b91e9d2a63a406420f897d4f8ff43080d4a024006fcf65d6babd59e6e31515746fb78f8cbab77503cee1754c7ff93a327e138b6e6ce9ea67490c447f6e6d0bcb03f3158b5713b30babec826069f776760cdaf6c1924b4977918ac933a7e014e74af50b67d988acfdb65585ff52006165b1414e195922c04db1a6ef5048f26c959935db3e1149bf4a302240a2a40178fe3db871a0b4c1bf880dd63cfc9167503043c48ad98f6ff7a3b9bb223fa0ab9f2470e3b8071e3e4f6656f0dacf9227b4f5bbb7ee838b0cd41e3ad416ba6c89b2dec4083d75088f39c8b1257c58790567c90bebed3ce6cd106d247964108c2d551fc9d396338118f29e98b076e4f997d0436408829d09620c4b3771d243278beb901f8da5a9ebadc7e19d93186a432c7ff5b81ab742813c05d87e09cf854aa39e81cc2f8c5d0664ea9fcb562e53b274194f4729cb42a815b8b03708269d59ad56e0f53ff771f537cc78ab90aeb252a4c9c648db0f4a5283185c883b703925133db8dcedda71936f55f0bf83d9fee5b8f48e162b415871808a8d78413913467499830ccc32ad7168ab3a26af7531e312cf51cbd0e3f05928c58569f9e34f6c526e566cd987e7301d040e0c1139e9e12db70a6b309bd1e06ac897ffd249de8185078a2122d7ddee082be5b68478d7e2583ed068f44f9fe17a4bab2eaeff17d729512ca68e3a66fe2e358a8b051c2d115e0b1919bc2133706115533d50fccc394cd911f7ccc5e4b9aa7800bb7b57615bb1d13596da1d7c0df053a8fda706a4009d45ce1e0bf9d89c018f151c4f21a72f6df6d4d40000d93f2d258776c7a36705808b3d1f98fe23f831acbc72c0c273731130e9178a3e45bfd3c19f4b4942b0df3e2b0975dd6069f6b27d12753857ad5c825267a16ecd4d5d8fece3d5ea7fb4834725865fb91d28e516dec7bb0df231b969a4214263eeb191a6b26949c9985957967c900cbb288d0cce330414c01a3ef772ba9d23a8301ba99725043d4757e01640dd43d9a864731cd86f2e5a41c61dce5f91407f8713d160e7bbb5adbccd4c643c2e26602d360a53fe1fc3761b37ae5418ce02b92bc8549ef11464dcc502f3bf3b7c27e93c16b22a64ac4029590ecd41956166286da5cba3e3e0120152d88eff65c18222f8c8990db906b8ef8991fbc6a715f9ed439dae7bef3e38a50f8ec6060645c0cebbc6d195c31ede92f3ee7209da9f74d18bcea45441664e4b0180a3e1b048b538404c85b649068ad672efbe74ab2ef137ff70eff9dca605dedb3faf6d910eff204eb5e7caae9f2b04ddb151104b4d20f0435811af64f9e8eb1dc1324457860c12633e2eb2b130d2f7cfa270e19bbb35950e63a499bcb2c242df440bc2817a3b72fbd04407fb6cc5dd4828591da1a2160373340b6ee13b6ffc9a1457175f5d25ea1d5cebdbf7b98d249929bdb151a178e2c6ca9971fcc57ba7c9f1a54faeea9674cbcb6f4a4d688c53fd88b430967eda0fac228d2dce92285ef7cdc8c1a05258d99540864b29bdaf722e2e59641ef141787aff182d53b79fba4ebf4ef4c7588b46135b8f3a379a76353482b7b8cd012cef75d288b2fe672deb488f61d7347681d78f7d6f45aa5cba71aa82d4a9dd24cd32dec0eb7bf0aee3dbf507689dcf1f724c7d5908bc9a2ccbeb9362e14260ca915d3f08f42acbd0844e971d49c86fa05a4f007cd31abff144e1f7ca8df7c6fbb3490981b6b8d9b2c309cdbf701826e6f59ee7e9174decb9c50648ebddba660501d7cbe32991bd347f063bfcbf77a2fa2dc46f157faf3e8c01bf316698d93e067e0e58f898afcac3a111c21248495b5a32317fe5bdc076b7b269d740cd0da85df39aaec364883abc742de7b00a73b6ed53242e094098baf02ca3fdbd2e4b3bb0dfd4d8a5c414ee6c3e0875645ef58571a5cc565740babbe1a57f292b649573e6e7ab092bffcac0e7a670c7ed4434ebbd6dc728c5af8cbe87a5d5ef15901b7421511ada0f71c333a3849ce5ca85f9341448f107004701a635bab26b2a741ada79b7609b3e1ea1a0439c86defea6a519b8155aee7a48380ba9e0e1bf51b83f8446ccd0ac4e19ba6bcc230637bf2b1a4a898eff30f5150de620afdcca9dd9c91eaed072c7f0dac0960891d3145a532a375788adc46e653d683c6ca763b8dcefda3ca1694edc5b4fbcfcadf804830c763caf034f9470eeb436a071c4ce2040caafe7849f192f215416acb7c8818317ca4de8cec210ac4623c57080e80532e8e82e88e3ae8e400ebb6ca2587cdd1f1bd3efb4979998fb4fece70bae4e43df302a78d103a3d1070f02b33914d1f6a2329b64f9ae5e9060c186fdbfe183808660ca4262affbd58040204c79a13fdfe3638578954ee6d98ee4fc824ecc170b97b73158795e99a171f998bfd9a1fd82ceaa4fd2c1a6ab9881b6ce5e34cdbe484d36f8c384075d00c9346c90c805048222cf3e1cd5a98e5043c8fbb5acf85c07cf7aac6449aadaee70239af5e68aa6f69d4e278b31a5f7e9dc7d637b263ca712860746090b8548a0dc6e68d2b105d3ac3fdd681ab104c38b5e8d849c397aec82d741f72736b1716cfa3ba675a313342e038cc52047667e8554e388c1a7cba29075c3b45c04694111deab6e40a38eccd7f8ef301f4f07ba37612f5cc0d4663c08540b24109a3d17d802bf772ca0fa4c57772b3de191c3c8816691f92efb2c908a5d03229b52437bf6329943f1cdc8165bc03436ac00f19f8b01109c0dc93ebb0a7070bcfe70e6ec62e7623cb569c28041daa5d79070d49308eeb62e5efdce3d978432bb0a2cc13917d1e182686c2e4677e2d3d0130c847b859ffc82dac9ec39f97143e66abb75d0b770c6fe8e0448c6ba4b32660115888d2dcf37da5ba5084d2eb784d52bfbc531febde2c99437cf001cff24c9d1f0720be606f08cae6049793706cab7565a6ba8b90776c714b6ebed6048b72a10d6bdd9ef1b06c036331629c8a7c2274e0bdd9359a534a5d340a50f6b131b9130e677352e44e7a39b45e84f40df7335635054b97287a3fb4190f271a541f7c8c6a60cc00d6b0fa0aaed6847ee9e0f8bc494d426ffb95f66021257b8033c401c57fdeecba4807ac80c150c4e7b6fb4cc8767e2cf1dc2b8c259740813956ac1a52d50c59818d6f00d2c8da59e9def663ecd2d4e96d3e31b82f4436e41bfae23940155de252de44494e163fe91991bd49f6bb070aee14a0c27a36a11d8c7ecaad459f25d87800eac7177c161f093e13cbc0f0ec65f2d9e3abf884d8880ddcf669623d508c284d4201069170aec14208b755e74b268898b13113078d93662de4647546cac6c65aeb07177c09b0ca60d582a2677d11e65b3acc51b0aa3a9714c0f7ad419eb09d40a0c0d0d385f74aae504b6ad19a3c09c3c104864aa9e7db69ff838f68ab375621685dda07b1108d5306262b772999b6abd8b8f457bcf2853cb23a2417b2c0cabda6648abf48e6bceb55b864faf758cba843b5a1886fa2d0da409690d283c681cfd4c4a6a3d0844fb6cb8c2759c7ae5887cc97b93dc113bf263bea388be1c1138c394b1d135696ba305177c404631a3db7c697e270275fd2df1fd9dfe9ee64e6101d27cdf269f9f264dca52401589ffe71d2efdce85277245e99f0d0fc7c5176aea516dd85381174154bf9cac8df6858373946d1c27ea5f67197956d9029fed74e5b87a8ce14ce4f160eb0268f7dab74bdafd28e24c625abad4afb33e8057e653d4c0d05cf1f8336decdd10bddecaaabdc3d51b3507023428a9de07c9f39d6f72679082fef0e31a201850b2476e931fa57d652632ae8919e6f208b9688d3618b54e6cf1de4858b18722e411ec2b71069b32a3c525377736c62cf0fd3ebfdab64c8cf09f630311bff215fc1d03706f447394958afb075742f7b2ccfd09c4077a243255a4c0fb13537a903830db14e55ac0137e92321b0ef4a723875ad9c781a4d278bc017cdcb6462834ca658598b3ffacad44fa5864795f77f039bddf19ad8d6785e797ef77de2e3213130d45f40dc20abf2685488f4e8699490383fde068b5455c97e8942f9671125be29fdba8b0da3c9bec43d30c822bfd072fdefc8eb11edd029eae864ca5b426ed4efdc6fe2f96b74ba3c7bbe706d4d98f6c87541b2e0813def0a98c69c5e58cdd83d8fbef7fdb189093dd7797053f200f8c6845e1c52bd4480ab1011b087f8288d05886a090f2465b46521af4586d5483a3949289e067cc6cc63820234f91a5d5cc63c37729b2a1dc6ffb068a7cfcf18b614679d753fa670d75a71fb6ea1d4c55a8918b9b5b75a147564a7783b29cc48719a31293f0770ab4c57f76305168bd6f0f2403dd570a069152e197bf502f459262c412a9fc4df88166d31e7c91dc0083d4a6b8e92f4d3417ee3d3576dc837029d5de3915b4d4a05f3db94b262fa5f713f12bda1073ebe8f11678ef1ba80743ce3e00cdf9c56d0ef8f503df0d29feeb829d302d6f29c2aa55fee58c5d87e4cab9dc1b57c9f589c564298a7a682f1af090bee8c97002b021c4b29ed42490e3667b796fadbfa448c75d73bec57feb86c7bd71efff980540539ece1ed2f4b633ee5cbb14b277e125f0b8e071203d09d2d1d099c874fad4fc63d4ebe64866d3f06fed4a2a136ad7a53ed964e10549d695e6da8747f879fcef0346f6550b43ab1c68831ca29b653159fc572e91152aab2735b64079f0133f8e3b884fe6b173dc2a8101b4d9428d93e8ba85a4c7445ba9ced5bb7086e445e379e721f27e515553111634c2a42d80f0ca4db402c2d9c06f895e91b6a91de682c61139eade61f54d56cdb6c1fbef3eec5b154ff53c9df8e0b408f4a1e0743f27e8beb04eea67ba9abc30badc30ba4f2aeb0b1a09c198f0bc48c9ee2115d9a566e91aeb9a638f06fa677f435ca6dca07cf2cf7ff0352d3c68b29cbe2f36bada31b490de3226028a0b6095084e10bf18f3252f637aac411bdcfb3c0c98f27173b862c61a962e4e2084cb63c15c4d2b3d402c6b5ed47e9f9611b9f5d6022068e50ed70fdb2a69a8e85444403b1bd7a1f7d65bd08c1eeef18afd8ddb15977a51a049731c966e37dac585e34454d7fb670cbc197c34ed9c3762b03505cea68111cd3ee640108772c440ead6cfb0f55827f7c19685601b777f284d86f4433258e0ec69aadfc72c39b7e622473983f1406b87a6f779987cdb1bbdbd3de1e692582ade1e3f19ae2efb165aaa9f8e64e3d79ff63f7247bdb584b4167812b8223b77d4855664ae3ada51bdf35d236bac04b100e81ea706b8360ce7af989e32dc227a21cfb9c3d245d58b1eea047144f42b6b23d0f3873b6b7040a2049c0e6caa140800d6c4166850f6ad6d180c76ed2102800eb70fe844d3fda55004a090be5a7cd654a154aa2a3ff1d51a60e99aaaabf4889993221ce666690cee6b73ff3c61693ec4d3d09f7d3eaa103c30b1598c357613eec36dbb2ad4f5f36fab4c844a5fad8c3a0ca530d29a04205182cff03a089938356407a668917b0db670663585e3789eeb286659ae91bc286b43376dd90ae77e104932f0455c720d32c0e1a0286d4b0812035c92193e0b48e8b78e00a5e465c3eae13ffa19b36ab48e5c1698d841d1916fd480f40413d9f9fbafcfbc7017011ce1744e1c687c6e761480c60449dd1ccf418698bbe5ccfd1946e0d9f0d58e4e4da41c371348cc2c022a5087e57920e9e822b1dce11c25e46f421140e2b88b0063c86090661dd031be894ab419da5777b10a877d980bf0c6ef9a8dc2303252c759b605167da21a9d3fabf06a9281cea501c9dcf44e37281bc9fcaf1010329821b037d375f0a02ec9e0b39df9c0e407cc81292d2a4309a0038ce050fd26894c4692224d1602afbba86ddc45b97ba508404b4f9c46b0774dbe55b7788eb225831463af6e1c97be40681fa37aca466d7f850d082c845427d1fbcfd0fb4f25f3bc18ae373e734846fc5b158691108d172c92e449dc2b0a3ccc48c718bf357b033678e15c7575ac780bd4f54e863fb75678b8f5f51141c2064cc0e6cfc04587a037f6556b4a898f5d1f0eba57fa258cdb1d3c236843b9be43e158d98d207f9e4c1b9ba9b64e867a5c64cafe16f4b6729850653f9a0c2ccda618330f80742bd3cec790df31d499a39a138c74a05b638f434e5f4f02bb8627e69b0d6b8093e2dd226ba79526e4d6572aa8d4dad71c794cf87c0466cc12b7eb08c4027f1201d482a6bd6bbfb648f9382c042941ea64a3c1006c0d3d5d538327c54dd9a02b9b33fc81c7060bfb417bb67cb80d33acf9c0328717e0a06581a6d1b74f32cebc5448c4f6ea83096e2ab208921ebb98e27f6bbfe0c443a551f40890893e7b7ac10ff568479a10779afe6f547175b5f744d3e6e4eb59ab60deb6d3bd4120484fb4fcade3ed8f94acad94780cc7531e114444bdbd001bee2e41b6615294304d0c9dd05b6397be951ca9d7c88d6672afc9a224a2c48c19e9ef1fec22dd95724f295a00ad1101936ffdd3209f4437533bb66d0c0bd621b38cd59e4c781cd0128c20bc898a161f67f6d57ab3160130a7901d9cc6f0c8ef7174ff522db95a5352ec47fab17a6b5a063e962a4c6dd3702927901cd4165cf4f823791759ca74f4a1005ce430663648dcb464da47fc0cc1b03e80535e6cff07477fed062c47d8fc152fef5879dbc2fa9a198cf425da43a54f7989580d31d8e5f449b425cf9499db2a0bb06eff953a0e2afce238d893c638fcfb11b3a663bc31a553934f29d0db01a477a4017bdafc88254628ae57190e39a292bb04f0476838942c3ae79a2fb37036a31f52992de53c55b9c77e172adf5134f885dc3d64bb95fb1292f38c5b5462c045f14da38a9ae7f94988197de10c4f5ee316249299910412c018461982b3246e55c4ffc7771ec22390c4a37f347551731a5b40946dfb9124b651fdf24b4b07d86c8c37c653526bc7c8c8b578b8b66a8b9e82f17a4fbfaaf5583ca956a814d758163271c1d26cb57a8ba292d2499b1f2a34a5898b2dba78faedfa727593505ca3eba883cd1e8b3cc506965e440242819f345426b89de36c2a22606f1c59f536ae625482ee4b40456e958c734a7d86c88fe3be16249ccfaf242c6fe05c52194978722590a5b4e831b05a9c7d8509469016eded593026e83c89b91f30079647902946162894c0b689a137043b1341052cd60a0af1ae20762a512cb1a85f6f23f78e25ef92a1a1897c34cf928143f3750fedd64c85cbfa0526595ed8f75598a4880c8598392cc5fb000c220735e971b40510514271c21ac57fa9eff151edeac414d02e4245ab7a1e3e1711da555c2bc5bf76cde06c0b29054b4954fbea97e0a4ac29d944ef87a378964de6f7a6f7a4f06ce0518ac29740f9d3429cc0ad0812a38c0793dc8d6c28ac90c96bc2e6482b798f7876cd20da5d58340c4701889b351f13e3e2969dd3f60914a30c9dcf905b47abfca9c79d31b0473f92fa44edb654da36da93e8ca2fd7f81f21c89f58e8652be8dea65733056adb09eb5df417a97001b658014ba0e3d0d3f94ed32eb54c2f7743983e23227f624032fdff5a520449943ea960ae38ac64f52207480205048063db37883d1395526a786af7749ebf766eb9a34c6073c1e583360ec4d2c2b0a002f0a1736d103e1b55e92f6216a35c18d057fc14ecc2195e96be5f8bfda9067361b70b6870edb4cb2b023398ba1c43fe430c26fc9db3fd4ad4a5bdd57ffe0c7efedd1b552929cf3eb41caf3a082b12c391bf3dff2b0ad5aa4346d7fb52ebf29c7af7f59b72b036a7aa5c5170d72d39cad688c283c113b3f5b5c1f9f3a5a71721c234cfcabdc292bd28e8f9422a683f5f105f193637dc65fbbd1fb9144348b438a5276c12222edfce9c923bd8c42fff1335643ca50ff433fd327d0078eb3baf4238357ff6035224713e3cf2125f990c07ee839412ad2842a9abce3251b234ccb38325158db3e17abd4eac50bbc35ede8da121d383ab8f2d480e3cce079c33dc87b86f7c29cd499546dfcf1f2cc0494d2279476cb58b3e4a053e04f65ba196944f0ec79762a91aa7a923e0d18e44a475eb10403617d8aae1c8dc329d970d2659a7626800339bb48212fd2235028f0915a74e1e8582a6a49d5f0780b3d25a2dbc9924db6e41112680c1734fd06aea73ceb4c0b5bc7a15677fdc88024a904cf937f99894f5a7c859800ee2ab67721a4922244a43893cc747f4797c58827a452c23cad9d9ba649e89fe4a74a9ae1cb7a03754a817eda9a2a7e00d6e996a566b8bbf98adee89c443f5ca845f7e60b76f88414bacda75aed21d8d518c5218c121ae802a1de6e0340843e4ed59f79a08298148d0c827ba413e753a13b6bb3a98a88b2148e658eae871fb8bef8188ae0e37a3f1746bb780f18265cc58e49b363b72a60d3501da76c14a2696926d8d5891ad00bb719455f7b294e6a5689f9816a9591efba38d3029d5d4701595d5da1f60ff0e8c635f9686c018b2f8f07f1d4bbaa241400fac2d55465d104059a41b3d142c630cd103ddd0e02729750b1ae6dc46940c175224f5e28a13aee2e1792b33806e0750bc78810ced65f522f549b50fbf56852bacf129831d4fb11c19e1f02a910c76439b74e79549d68a04ab022b50cd9b98a7c6e4bd535d0b42c771672e25da2193e8d179dc2d9b25d1a4f51d57be05e65e7c1096c3d9ffa5e77a0bb4351dfd78c965f3c334a2728883997b5a805f292a0e054327c4352af5304dd760f33d025e15fe7e356e756373b4b4729ee7469305e921f69b39bce7a5d029ee83420496843bcbee875bcd86597b58cadd39df05f78fdf5a47e5efe0c501e342204779eac7b50ebb82b5aa10ef6acf2b1d84090a7863662d1ec3a98eb8113bfaa08f43ac5b24f35fe90bb3d944321891302cc585df3b1ad44075b7e46ba2ceea602bd24614f740946840790e78a87e3ae88e77ee57bb9af13230053dd035b5fddbb5cba26d82ef1f715e2d63bea2156498ef5cec853f631d96a5da198e80f95bf51f6153f6c2504422a6ff647949a075349f25f64ec618ae847290e738c9ddafdd631e38e6a0235043bebe93b9448a6f1e4f5e9844422bc849ddc3fa015849dcd23434c39ae250764dc5e3e1db3ae4b8b2e17cc4198daf768c6314f526c7953e1603d1a01fa3054d8400c8c1a0235a2c0cd8906465d82ae1cc23ff8d416c21e6ad73f8963a47ee17d3632c0eff7bdcecf4819da058125df14cd2e86db8f022e1e1a2a60af5f9f32ea3892e8eb2946b0e008d5158668a44f8ae5bd4dc7defd8bdaf37c103baa14da67c8d21c7671e9961ad8f32784992f9968ac6784cadc11882dc5d81cd4569686898f30b26f501fe1d661cf15f3c21e40679b50a40d11bb7505d19c73c5af4ebc72aadc52e3d5630f124e010cafb69bed3c8e361de836104e0b02d84dd949cdbe82575c5e4f40bfe0730983f24a7005748389f9261b61b22b65e6d98a5881a36733a12ab76883eaf38b8ea2881aab3d95907f6e2eac45493de49eb0af796b61b6c7e5eaea541570c0756e78e1916f0e1e09c2d47dbbd9281ae2b0b8e779e577ddb17f71efd6f8ce2aeb76b0728efea6d24bfe5147ed359db6f6d1e01b142f71a4f0a319fb16cc236d51ece6eabf0ec388ee1bd4fce16fe21613d9127b564d4438a143aaaac304ddb4d3974e93b0df4aaf63270aac042aac3093cada7b96919251bf81a03407f2ef5ddf7b182fa493090390c86f81ed2ca7aca97067f8a32b45647ae8922e0800f6b7b2989d264039a3c9c21f170468c2fcb76d0868972de75550917d58ca124a6e197eb79e1dd5ce04459216d82327baab4e649156109551ca116624a46250fadce6eab4ae10032ebd0a22e68c51815d100ee2015629a69e210888cac318258e75329995b8fbb3740c01862a86cddc03641c33c32450ad81d22a2bcb90967b43054016c77a65bc02362104c374c3280dc232f3f38967573618f6023e67bd0ddc568238a0036696fe184297ab73bbe39d4ae887b389496e1e04883e494633540bfbb599247efbea54032a905e6a0ebdf2e55a57d72ff3ae7e6978a83f7b101e1dc2bc08051af9127f9f08f054e7d2da8762268bc2098fc2d097a9297ce52d81ebdfd3577d6a0fcbfbee9d91caa837076116358abffed7c747e7e2c1827e1a9d4b34f77c1f19c2fb6bec086fa5b1790a51e0328bb6af49612a95074f6420dd3792f8388e552dbe12c72d2e71d222799ef92965f54f912242a105d508404cdbc67aad91dd1facbb49bc4c67b2d00310739980cdb3bdfe3c92b8e9b0ec322534b36899701c0edd06de271d7b68ed3cef50e90142187857404bec294cc080b28a406b4c1f2de9ffbbcc5f5057e6b40051f63a36f1faf70e257d5c540f862b492e708738163f62732311a8d2137b1bbfb3fcf2a8bbcfbddabc707fc3133ba98ca074faa6ae350a5de1988baa5840b1eee8694284c5bf4e33b2fba197ccf4a4062f2e9259938e276e295f0b0a4d37af0823538353b22c446f4d7f6315239d123c4b5617852a9b5b59d02662030d0780ce829f47b925958eaea99c6366888487acf26fe683d9e6e14f45edfe4a380410e463bc6cc87cb6e3505c7f8cd9f60000026ee9070886d48e2d40559678a34bda20b3d53e6adae044dd4473e0929601183e001900162d2cd65ed5c95698652a3bf5d3e23d8c3ceba945d044c19b1dcf71e3b5f06acef9e7bcd9bf6c5dfada3c130a2111b91dff89ed353738b36e61031118f70fc9df89e96241ed979d598cd45553627804c0dc470868beefa54eedf5c227d2c43e080ecc71c61940fc69e706f6af28cbd2cf30dc7e57522b35052635da8d78984ec70e45a453a85161525af3b5fb904b8ce59884856b8123e6ad5dac23b5e10322934ef8b1db740e26a50d351feb5f3b7d35d146a469f91e3c34ed82b1e62e305031d21b148a9de631d776e3a5df1d4da673b68ef2e887264316ee5a9ae539d934661df329a22b8f34f92707830825cff95d8f10493198004a0309eea8d27d59a5033aa21cf33f030552371b8dff30fda0d228abe2e89c94518791f9463c8e5fb2e01ce4c2ba5e55563c6c4596d6f8ecdbc2aa5793fc114a4f002cac0aa94b48cd3e3ed211344567b80b8acd29c059bc72e9188e0b6471d2fdb37818350a77393d6e97eace38c312339725f09da5ddf93743a9b75fa248a3415e1910decd6ec500a6c3753323d32889596476b257476a03f866b91128f45a09a0c2fc8dd8c0f091754e438794b1041510bd426cc42d9addfd46253a551dff99ee9f2e17b698a40e79a4b7d11b3b590cf43bf2c438a35f7b21d966127387b3038dc1199a0a34f38df382156cb609670103926ccaa66b31ada3f8dced58a63ec7579db3c98d896ef460ffbdf54c5b12dfc7f4345535c51a9b0d20fe524db3fc807fbb895182f2db4cba24e31963830b505bbc8633e81b910ab5d4bafd2e6be9a17a578596323534fd51e05ba834dd1450a359d6373addb1cd5db370cfc6785689a9ba0137ad8babdef8809b2ed23c5ce651bc0f203075331312403f261bf9e0af4ed5b6381b18a1e965457234888580b206ed18cf44b72066b66f28d8bc3cfcf02e352bb0502d9e074295cd180e77caa0ff87dbf741f26bd3298b430950ea80e74766bdadf77a20d0e0a7f6714a4f22eeb5dce21d2a9cf7b4d72560f639ee521438118442cca9380bb6672844d12c00041053e478a23014824e46239dd4dcad28387779b4e42ad7c977b4fa3ddf18284f1a747f3c438d796182476cad712f84c881aef55e4d1be0bb447100e7b47bf7d8d01395ab373ec668b8e137a89ffaa95191efe0b4aa9a10f516bb5d3f1fd2a59051f37c0c70fffb5470f528b1e4dbf030adc83cfc72c85a454f11b4d427c77931b27d91385500dbb522a9e234a771709284b59f0cf2a8d9a69d3f83fdfa90fc9ce56d604078290b8ed1a1761ca649a3269004adf8b7410b35e5118850f97173750bf805d6a764398db2553a33b5890791b93308886c0bf374052d123ee8b2a18bbe3b2ec9952c4bd5d8ad99f38c726eb0d67d3a4c2ac70356fe1511585183bb4d03437ad310abaa3c69acde6773fb327607a0b3ec0bebc736486457290ab8ae82b45bf3a0df8d5ab082fd37c41e06967f0e4e3e402291536ec7aba6841e61bfae049ed394a1533159507929edfb86e2b1ed6e8302cb5d9e87462ee060eea9588c8774d914532d391ff451661bf0f24639ed4593b9ebc6d38a2920a7d944ad798d0e144532ada718e294b737c85083cf591f8608bf33413aa9d63e204dee9cbdb3a0a844a80f0b77a391fb861c57cbe76974a19378666b2ac8808ba75b4a65abacfe00133e149587510c74de553a4e71aa4de7385c96771da6e56da7bb1be06151d40dc19259eaa29f7e708a089d5a44c24853b0dae7cc46b2274f76952b9e6b315f7470caa64fc31cae44b21cc402b0704b7ce85c62621ae08427987083b3f376b433d203bb3b2943e1ba3e00c7bf0f0c300d4c3924b6858dd8827c1d0aa95e317231f49d22dd24dfb8b4d50586bfdd0a984431d1fb17f50fbfa17353abce79a7e81dbab6288e08820e22eb482689f945dc5ac2a85c3758a526615013b4108aa944a575e62ab39b7dcdc6c55f71c17f03776a50c89647a612387852169bc040f887106f398495df1a51853b91f62a581f3367001d0b94ce46b1045f2a4f25a8e8255e5fb637ad7d2d2313bb5c66283ea43f7dc8476315c8a6f32089e01a3a190e6d835cb86122744be24ebe9c52db211796d9c8116533744567bd93fff80e8637e79574e56f759f132ccd0a28c4d8946842efd0821332e6ff42161e881d45b5158567a2fcc2e436205f90ed8683d8881b09a62b62c108db03a3e1a06d3253c8a053036155b633b852006d3025a30855fc5799889f417cd555697201828edc098e2aaab8b63dcd54ddf7c270e919a8bbbda65a44f5701dbae4868168c8918f2d07300cd27921ff7d0156bf8b4b6a3c51ac57de36899fea4f3f82ba80b413e18d79948807a10bd1b5c46cdb88b0527330b1110070c0fac7ca2081a041a9fa7078992c109789c8f8ef693df26b7f84f9d10b52fa30940d2ee0b86fe2a4ba0859612082c81933c7a821e6aae48684f1ca67d98b2263231fc6361afa0ce2a5af268aa9cb6f3b8bb5c3cac1cb6761fa810174151c515af527f7fcad69af81bb22a5f0cf6ac8378d65f283884421855ab53554be4e9db4bd7b04a5dd7aabc637e3b3bcf1bb23e43fc3721a60e56e6e494ebf55431dd1a7f5f47cba3b531ba1a1a71debd542527cace2c981fb7fc7ae6458012b29db1eb81aa82c8a8f45f508d10b406c675fbe2332cb58baea57a2bab90d45e24e7b5ed71291f62c036c9f1c4d0e7179a941b9e8b8fed11bd7ebb3a1b30008b86d05a004d800f002240e5bde3e47f40a8430eed963167012159ef99f677710b1f51ee62da1941cf4697c2ecd25e39819a13abf3e285c46399ef5089ffab144ceee9688e1df52c38b78ec9798dfb04ba6ab57f29c3e779cfc678680deba3a7b4d733df2f849c6d5da35a9d0d2ca04aa6b3159c6123ce85617e58bcd25d09d75b236ffe6dc174cfb5c7bc6a39bba7e33c4185418c9b5da0007bf56cfb5a0dadff693ad357f3b0c91878c28f77c69493fa01473aef5698faec50367907a796f21e41a948ea54c5d30fe6dabad7ef40b69e255d270133a7b7d8fc33dcd2a109a4e1081e6bb4a63b3a44d865478ffb043e94ab8f312c072d0dd605d9573c76616f24219c01cb77a5b31b6d9a10f871c9ef0103dfca6d54044e7e0c3bf74e9f7a1ee3d29662f35ea618b38c41d37c9be0c4a3d368f3c8d819c0f818867b9f9b9f1b15ddff5cdc867fb7308cf82bc51cc84ba6dde98e75b3cba71c8b94cf03bff56b1159dd5ee723c5b88c04262d95af31a28945df5ff91e366a48218a79e1a2516bb1cacb55abe7409c10732f35ad1a03d8c938a9204ff5148fa3bc3e00f289f2b8dd9f2d00697058938f22416ed6ce988f02aa6e33a0c42567f19d39a5cb09341ba4de2845dbfdd67e1a3982e95395288e0d3a67d1269051127f2f3387a09e107c35f9666467c58381ad087e354b494be333e5723f6badb3822571a214fb06b6adcdfad804b3cf286ed018cfbcd232557b46c196348b3f5958629ec070e5a6009cedf574bb21ad294dac82c521d992f3bd35fcc5cc703379fe3dcdbb9d75395f5033734476e93b63fe96fbc8e9fb80682b78007022d32edff25cb04fb0e6ca87fd14766a9af7f1e23988ee3d0c860f7b5c6158f0af927fa345681783ef5d921a836262e01c0205ec75773a7e0b840dc2da655759abcdbea4d9ac55b2fb00ad28ce6f89f4e9d06947a9030e7e48c48bee0fe504d7b5f78a417f055a8389ccfb8a5c70982b6a5208811fd15b5f6d81667a7b6cf36b3df56ac57c6e68208090762dc8297a0b703553786c3211b14f3a9623a81f803281917848885026aaac0e34e5232eea0e2d37ae068fd0114b6dbb44120197cba9bbb6f819ba5240270cd6c8196b3bb6a801d141b4bc09f2362ee2d6b57cb2af1372e0ffabc0f7d1ce3fe0bff3af8863870b1bcf639f484141ecea620a7d64c4b6a7d00db817d68055948ce86241335616fb2e2aca0f0b9c06f129bcf087cdae918ebd03a13c6659d1133e0cb093b57b0736dcd46f7fded62d7294866a825a4a1b52b11b72905e1032aee0b6dbcdebfc7c94f81e2ce6a12d0d19b306fe1bd6caa8f2d980b90fe5bd9dc9f96dc6d61ebd924a3b715fbe9a0a4f1d2f416f766108b6aa0ee2ffab1a5d2fe71b2255e40e555f12726d4efd5b447dda40bdfa9107b979784286a238bfde9777b7bf6e577c4689b236941903c4c30e45e461289346f626a21e65ca81408b73d03759743105addd5709cbb3055133400d7026283da2f3d970bbd038e4d9f577e06cecc022902d68dae55c33568a5d7935599a638b9959c92bcd3d69b4b23413813d56ac93155f9e30a57379832ac176cdf2f5e0dff7885756e21c6bdca0a7c45e28b9cf943ffb656ffffad002aa4bd68f732df7ae2eee2391da2b2cd6aae26c2eb01e7f1ee5941e7fd2bdeec461f59d8461cb3a46658fb30962140914bfc6e9a591c22eeaa1332c0dd0618372c8289ff8de44e8bb6e0ae32c7f7b97009b7868c711e9fe3731a246c4587d471e31673a9d83b1b235befe08c6ff3fa796e4f08eedc7bd47156cc067871354e33d84e99ff87df6a080ae8d0f46d496edcd77bf01fc3fccf1d4399cb5ec6c436ffdcbb24c35c3ea0d55d606838f72eedcf9de0ecc7d1a297b591b819c635d48e070ef31bffabb9e766c989eb904407482707cef7fb3c31198b3f6d820eaf9403885141b77350455988292e7b4563099d85f7781df46e2de52ce6d257c9cf2e64a85efa8167d38975c36bfe6fbbe658c855a8f4be158d9eb9ae6c25028a91a40110be966a08d8a379d9422349005ee150040b7d5b3a427c14208ab5fceb7b8c35ab26f0e673203d394d4273e8fccf65a2a7217ced334e6f057f848c830eb400be86ceebac862f177edc4b5435b7accd0bcf3e997965cf0337925185983dc33a5e5aa68411b0889b1b4d347e20ff9e61befb210796c38c9be5a565d584b6f654803f0c4710a53618df1b020a97c3b75c01a7c0bdaeb02bc2ec15753540b54644db159682e96ca34a3b92451412e1665d181caf41bef621e969a46ce80df81f4f9ea3ad594bb6699c7da710f36f056b169bb5940f8d8268c0115d3ede5b506fed6c97927734aba15b6f571f98f2fd5a7a341252c5835862f390595c6567103e22fcadf19c1a01f8a9f371bf2ea967746acadfe7afb9c8bb03c91132f0cffe342ba90b6c914ff4db4269df798bfba02498c9d4a41e0a0eedd62833c314973191693a609f0655c36618e0c266c75045a63fba8593054324a3a0969399e57a4f5d759c4ab3878bed11e963ed9fc90ea33a6b47c9eb1289aab19274601c1f7598cc717357000e2e703c4dfedbadd3e9d1ceee2cb82c7d0527d78ab36b34ef2d588e27fd208219b90bdf7de7bef1d140b140a540a79bbb2cfd3ee6c38ba01f6b588a58cde8ffd7b7cb79c56e4be0336d2bafce3aaedba6ebef7be829797b92bd2414254b762ee804d1754e7a595b44837c67af9528c4884fae50ed8f47d5753e79fa4972f08f5adf9b4314293b9d36c8cd0e2a7125a91a0a915099a3b458a1091550d458ab47dd02c22a6e88b172e6fcbebe28d13347788d4104def45a2e9c9dc3d2d387d53cf47d33b4e34054dad085ebe82afd699e79cb57aef514a6ff5483f9f3b60d3f69256f905a1be79bb975a91529be7cc1db0e9ea175ba77f8ff551be20d4d53475d7d6fc8250d3bc511ae36f13457bd9268a162b68cbf8b9acd9f2534ab959d37aad24f91ee57bacb3defadc866c3edb4f27e96b9bd9c7bedeb29fa36d887ccd3ef6133eebb4ab79fdf5aeb455d9077bf66b9bc7dfac6fa73857d9ca3e5b35ff7eacd36eabb24fa9c69a574f9a7df0b15bbf1ebfd11fbba855e57cf50d8a417162b03a81f14ba26188f5fc58afe4bbcc3e56ae36eb510ac4ca3ed5bda725ebeff02e54d9e7e6975fb7fd54bf14ff7550db43d266410b3e062b6f38fb54306f2fcfee8181f34673de3a1df926957f37e300a1d9c785687a8f219a9eb4b9bbe9b8d5cf923dfe65b38ff5ebd571dce256bfdd5b8bbbf45b953ff3b9db6af629519f99df3d035ff1bfaf6ee183b7dc164f3fabbcc5f772cd48842eb96e93bffaf626f3ed4be84248047c53aaf276efaddfacb5f2375dfa8e9a52df90c2609dedbefe5d422d6e0fff342f63deba5b6df6db7ddbbd77b3363f7924422df3367f928ff595ebb67a9cfe56b30bafeb693d7d7f734216946370e0baf709187874c3ecd3dbbed57a79eb1709cc5bf593dcecad1637bffc6c9c68f230a87607f48a20ac2024e3874343435eec9321861802b201879ad0cf298510428824e0651041042180229296a17bed93274fc25c990f041040440883f8e1871f6a82e612245b0ffdbccf7d15b2f9683e37dfe6ad675ede676659f3f3c1071fe093b705c6006fdfde4f59052d66f51a89ccdbfd6993af59052db42a3f15b4909f363f274e9cc0337ae8a10730807036243e7543e2537d22bc0d0f9a7c4733d0bb99836acb7c83ec2a4bb4f0d2654b13d39d4c864e7a3f1e7da8a2434a044f5222d02145650910a231042f3545858bbdf6a6a87c91a222458a0a0c5254b0b0d6de4a56a9c2c196f505292952a4a44c494929228544514a0a2a85449714123778c1152924a24821b1440a092929248a4821d1247544135247082175440e66903a224bea0828524734913ac289135246182165449794115b5246cc206544162923aca48c282265044a8d54142fbaa4a26c918a52452a8a95549422525178484139420aca0e209782d2054c41d1a221e14587a7658b9452caf79e7c571528881a8a3aa4a04891a242841261a80461468a4a9394125c524a809152024b4a892f524a7031e7b437a5841425a0b8b5d67aab24c474bcde0f259c340956566585b9be58d8c24744e9789a83eea692e0a24a2a0918a492184a25e182541255a48650a92478f080693289539ef4e330a0cd8e9027a6a480d1b12282182a292844a4a038691297541148e8eef64c158146aa889ac7e3c16bd72523f6abd16aa587163ed39c21984d2bddf14b3230e9fd8828d0480a783f240f1f03b8f17c6c3db01f0f0a63bfbff78e8399cdefd57952f4d142cd0e15da7ca68bc39c40b8c70010a3ddaadfbdd577a2167558c8737fdd4f2b18cf2bf0e131d6035fd56b339fdec4f8327740d795e3adcc5147953ba0ebe62771eef02bcc75f1f6e71d4b0be334e9e37540d78ec45c503f20797a56ee2e4e61be5b5c8733d0f5fe3eb1203fef319fdeebeb2b4aaf1d8d38a0a07e87a7d79433221be76d080492fd5e3ec5bffb8bb989e7ac1dce4127eb49daea34ec2fe65c7d5d9cfd5dc738fc8b7bd184e12dee75df0c14d4f164a4f336a4690692b3b7997de25feaee3d48c4a2e5458e16a0940084a21d88a032040dba48030740fc400729d822aaa0071f2a7a50028b1ea2e88012822789212bb0c0f2c48a2a220a78108a420c0f28d8a10a154704114400be1042aa8d309aa4cc885138a961844045137ec08316a4c03e3142133628fa8187293a7012b1848089225420032f5570c0840542880923c408821726c24803156b158f0861208107580841041dacf84133e205291ae8a0c3154fe2143cc0229e19430c910512b014a521458c944429d5e275087b82e8a3fd6ea28c82efab8fd29915a069d160cefc9d320a41d26a6f35b158f8c2b29126334343ba81234709856b83857653d8681ad7cc3406804ce300d88c1a3f6bcc618df3d55800b8f1cd00aac604b88d0b60d2a1820176f0d071001dd9cc3d424d7b6895d142b9559d5d11a007d0d6ad5230b91b7d2b4a0fe5a13dc651718f674e7a43d0f23dd38495054daa33b1b4adebc61c1da6c6b2008d27011ad38b720368cc6913a0b1e2a6b11540e39b1b575c63eb8db14de32b008d310034ce6a1a8fb6c65a0a8d6550683c536a4c93a3310947e31b17e56e74c53129c26365fa74604870901ae7a0695c9a698c824ce314b4c6dba8710d00b0c601b0c18dcf558df36d2c809b01ccc604908d0b101b9b2e8b7b3c2a9864844f878e8ce68a7b3c37d7572dac8a336557269e3811a30d94184d9ae0117aa28a941194400735aea480a7fad018942b3b18821a44c842258c28986cb9411a6128c1c53ae99a5a6283ae37ba525a1a43eb34ab8af9f87e4469850922076052002589252690627b5001119ac0658934c6482d11eb0a7a805420ecc1090d5c66f005132f50a004a2c80585116c318112614e00c616314e1cfa8a9440a50563a8c0071d3cf10422b8f0e1055466c084053c88a06e41d03aad6b644d4b93a9e63546cc144a074b478d3174a003164d8620c50e37d070410dc6c0c10f40582366a9ae0062c4014213d2f801cb183f10e10327d808a38c30b8b021064c821859115e65fd5242a04620448c294df8e045125220036b460a2a0eda00230618420801a6054f3a7ec67a82149320de2fb5044c531e26ad4fa804650421860e6c80c1833378ca9802073f108114561c01856732e9c9061b3d27a5af674f3acff883e69cd3c7bc0bd1c7f3f101540f62800414292770e1991e20c196315986f8b2841093073678d0f32d581689e69b4b3677b7e64ea39967929ec2104767ce22fc20066d9c41c50dbc5c414295c11056fca00661d43063c91035c2da2991a05e27aebdf75e99168aee707acb85021f89a8a6456ddd0bbdea31bc52724d5287f524d89554cf5ebe75a16358dd6397e1da639afdcd1bb6ae65f386f37d0cd5ef7d95955c89a1babd929b37fcfa1c83922ad7cfc7402da704fbfde4945c8901fbbd926c83a17af60bdc633b54cfbec33d663541c3415aafe1fdb06d9d4a793f7e34f68bb2fea4a535c6d9207510a1c1de7085fd5ec93d762c2b91bf7f5e883469a27df426339779bc02c0835683f4b1e15f1ba48f0de70d066babdface4bebaaff286ff7876a40eeb4aaadbbce1ac134dd61fcf93d61f8fd5e9b4f51aacdb60bd88751cac472c6244ec1f171f0fc48210c98e718f07d72075589fe16c903aaccb7038481dd635ee49ab128146433459c7798668b2ac5b61c0d86098af2e83fc7dc63d4b8b6ed113da0bea13b57085aeb6fd832b74b3e6128939317e51e5c6b4a387ce4e0b2e4020e6c85ba3175a95bbbb592231c7da77032d37a61d3d74765a7021061bdffd6128febdcfd710ca963535373b7a5422e00d867819833c8c314a0c4984624822f460009ed06e3441b803bf59e7a4c106a8a2599f9896a9ef7650adfa785e68d199010a3c30e5244ac3bb004f04cb4cc3d40f541a9e061e783aab96f2214ba949dbc39702424bdb1c44e70e68e485f6c67492e6baeb5d2d8f679417eed8d103480b115a2c9a48110121c4baa7a58926f0098c02a9f818e57b35ad54fa4d261c1c20179021f174a3c9b22acbc202b01465aca2adac94336a53d66e1ebf938d27b5f3f7246fc1da440a33e265f3031d48af73af8e0d0eb4fa77c8c5f84e2796e2a591a2f0d29d8e8fc9f3522700a363bc0d0f641b3660dcd8e4c04607365d5ed2e63581c49b4f3c94dc4f7cfc9187194b7989c9476ef3a11d949fa7792d9aa0e44ef2379ae0fcc34ad1042f39b8e506d67ad394c6f6a9c9a2861933881e8542055109b74c392fe784403a4df3366436fcacc11648a58cb476f6b5e24453ad5044e95aeb4dc3ee81134d9004c5105d5ff3f7a3fedec808850e130dace582d7460ceda6ede1a539a1ed6bfe7ebcb67fd666074f4b25695729661d9c997e292b5fe4112c1356965c2b3f5cd63e21c67aa27a620ced091c644fc8e0a2d9410506b6296df75101946e6a4af14da09840a31f26c4c607d65abba3085cec00049735c2e082c70e01053df84207483883893278ec1224203d6145db191e6badb54ff450c241d89c60036f919d50e2c68926394a69dc68e169d929f50047a5c18cbd3661dc6b5f6a415bdb04136dadb5b73661545aba81b7e981d63d177672f44b5939a251e897b28205c6c6a5a7aff9fb019be669b303ed4693a4f4a59b68b60943bb9963de9b4c002163bf14133e60c14b60c9d6902dbc43807257342dd144cbf44b2d01a56d099608d3f337d3e605b2a696205a2206dddd745c81f5a8801e52235d30d0eee72f29e6cc2a49f5f329ccfa799af041f8a276d9ea1d3d3cecae8a1439d8b33b9a371ffa1bda4057cb4c12ea9d18a1a502bfcf09659eefe5cc296307c57804f61b33a65f7c6c07b4e401d6481fa94d0c628c57c2221b2d3a334dd4008a4e90797858022886465276933e1eef92fe747f3c2109a39354952a5da5e563e489291260e91863ef6887ddfc69d29bf390097d269d3187562c02704ccf6cc26cb4d0627726c8c283f651cb16951e6bf375fac49f3a6cd38207bc2b9a6c58509a35f437d83412a1dbf0b761de86c4e720d8343ff803b1a8b75f90f9ab7085564fa50b4d61e30c3561c992254b962c598242a15028140ab564c992254b9af8218b25320b140a8542a15028140a1500d492254b962c5912794013001d6802a0c3dcd22a686161785247246179557bcd09549666f65e7a7b2e0b13726f846f5e9a5598100bb11efbf74c9d4cd3ac13ba9890a69890f778588c48931d76c0d7c57adee341edcd2a5855d7652f0cbb6e57353dceae0a8361a45dec1ecbac6b7138bb2cd81606edbab0201a36812775400c7b9166151362bd3ec37a6e5b191372bbfec27a26c5ee85c02e439a3b2944ceaa054f484d292a424f0942cb2961d8404eb1d64229616aca16555555a929504cc922a7346165540758a4a03811143b0515b6d00e9f4f3fc108c87ca2a778781a71d1de212c425eb38af7172733137185b99c6d344373fc779a286f3593f4485ef8344966df8b657eb9d1754f4393edec8cbbf6decada24ee4dc2da18a175f2b1f6809faca4655955d5dd57a74b72b7aebcf560bfee83e50bcb7869e5ccb3dcc1ce48403d22e163d886195a1eeb2b63ddab6940d78874f17d80ace0f84ebc38328d4cbdc6c9909a068834532beb0677b240a47befbda38bb4f5c85cbb0fe934c77e658f277134d868247da4ace42fce5b0fc69aacf2d653fddee7fa98da3493bdbab4ac7b31c9ca5b8ff6d17d662ef3eb38e60debb41735eeca2e475cdeb20dcb5bcf757c9f2be3ecb3dd7769e5adc7babc8f95656f3df7f23e57e7dde73d993822033f9c7baf86dd9bc66e9ab39e7dfb195de63ac6759bf6d7343476f3b4ec33ca32dd9fd6d1d017d00de63a1a1a1fd7907064d8af509696cf7eb98e86bec73a1ab08bc3386aace4f84ebc57f39123cf581fc970a3dcc91ba09bae685eabdfeae2806ebada974a37a5d20f14860dd8247341e21d3d80dc689f39be4cdeba8fb2df63d72c9f4c62bf97999412cb2fdf008561e3d993eaeed07ab8806f64deb06fd6759c7dc35efdfee05b7fcf1e988ec732d04dfbe0fcf24d34bd6ebb405df403d3ef71c84c9411e2ba1edff578697f55d765bc716334aaf2ad5e45539da6fa8d6a5ef818cbc87c3422e5d0fcba617fc35e669a4babbe136517745dd7edf1956932edeaccdecb5ad5bd52a46857ceb689a2c9dbdb44b1366b5704da755df37ae4b05bd51fd7cd247dffb2ec83dd3a96af241d64d90ae72b494b39afea613f183fbbaf46b7b8882f998ff02f1c5b9e6a50ecd188bb6d8384d6e1c723a18d3efbe2edcaaa5f7ff2a3cb2c1bddae7ebdb2599e52ca1c87e4ed3a9657f6b92ebfdd57d9a73af6ca67b3bfe4af4adeebea1b8576b5b52cebd7a3f60cb330d70535d672965d5d505b5c655c17d4d9f541b236580e0bd26e71d949d20ae93b9194ed652e73673336738dc37277b50cd7d9bc55cb5e78e608ad7b5eaccd40d71140d70c07dbce70b36f64757c5bb2baf775c97c74edd9a57cbc0cb7dd6fd5ed88dbaaed5e57b759f6ca172e4593c432d0d5d67a7589adbce12a6fd7e24bfbaa9b2f1be9285a4524201210e95e1209088804448a176b1768446a8c6686945515cdccd5b28cd4da0888448a77868ad495059461b9036aa96940a451a701a9a4c4124b292fb57b319036991ac8250191803c2ebc30451f2fbea8dd7b81b4e9991ac825656b609ff171a32291f2bddc008d5544fbc38458a422d5e81527f31bdc8d3a64459369df91335a865557252f6ca38476db3aac6e0345b37207514a68f1b7e5a16836506ca068f2f73650b4f8fbab511a608809a91e7f6ef6a9e8107647df7eba0241515b5b967daecc651f6a55b99339088a1a880e8d4e33861d636be692f7567589dd72f833d6953b79ed9dcc59963b193ad495363ac5cfb28e297d1273b03b692c1f41f0a5ed2b3ad4324afa249a240e385f8b03a2434d0f4738854ebec7d9d756bf8ed92afb8cae1dfb65337ef6eada9537fa0ddf8eb07565ee362c7fa594b768bef240d4be1b556155e96a93d9e77ad6d1a1a6438dd1cb8bc379a395bcb9dbac8faa68ab4abf7b6be51114fd9ea5dfad2a3daaa21f1d6a9f9b3b4b4abf579a412b55c9c7573ff7de4a5adcc5b9033655e4a294f205a1c6b2c4f88af1e64ea67a95a1f69a31ec068906dfb2b8ae86b67e47afaaaac2b78efd72d70d124d764b33a351ee64de335c27f31a44f19555dfa9b8ae86aea29435b4c47ff2cab92b95ee0da23ff6640f8f65f517bb864a56d7ad5f8925965856b292d50eced9e5efc26b4d4a99b75a8ad7b8ecfa7ddeaead625cfd5a5cb759d29252052d463988d670bf14d5ee8929e9e0ec5cc71eff2c76a5bce16ff5d6e556377c6f2111aa5e71dd5669e9573d2dfdbcdc0061cb40991ae09744bb42f0e3718c0f3f3ec91ddb7e643ebac75cb765f88fbbb8ae485f59f691c9a3eb2f8e30cc5d8f52d8d688eb8af48824bb0baba1e58bf4cd6457a4afdfcbc91c73a36bf25ab9f19d782377f73397b9bb3968b66565b9935acf70f747cbdbc534c75c506c9969a4e14ef5f0171734fbb46da6681d44d94cd1e4ed722773d0ec8a13f3661d8f2e233189cd5cbbcc4718bfbb8f9fd9aa6fd6eff1b151deacadc2aa631baef2cbda95bb0dcb2c074d9cead8afc4b72eaf4be6edfa56fd5e6ed5ddaecbdfed1ea7312c30883684f75d59bbe00759983bf8c04682c85723193002c7fcbd4f8ee60ec24ea8df1629744224e8f9fac620428bfd887ea8a2e11fa62487391ffdccdd5f97a47bdee74fef331b49d29ddc891133824393253aa0fa3dd3bbb402c2aa8b1042086105ab9b0a461860ac5d3477f616f54be242bf16fa71f702dddcf429fe3d7233462eac5b8ed4955aca7a799bbbb74d91d6ede8ba03a747ccc15eaf53ebad3dc6bd3f1ef6a38c2bc579b323ab7c4511e0ec23fb6d002633eec2b97b67c2a11147c9e45e58e24b2b4593fc84a29b5d3f45eb6e4729bf23f3bc2c84416042d0ec19ff60c0049d2927138264904c31a1dedcdcbb439a6ebaaa6635e79c735292d6e1dc9876d48a43234ecca11827866882943e225a247c57a24331ca28a79c0d650c8274afa6954af289914f0c0e5147d4e9cb09da752986c0a27bae53ac07870ea73ba01e6260ad45b562d5b2261a505a979c4589c41c0821b43e8b663662db9a61068e86687a613ac6184bf75ba8b5160727eafc086d763787a2c10b5db0c232aaa94143bb8fbfa9b22bcea6095a4dc7c328300a8c1225e6c02611a2a2a966c775b3e94bc39ac3aaaa68bdec6b8aa68eb629e6546c8a39a69823e4350d55c86b1ae80cb34853f9a9a1a175b772316ba96b4dbd5a26451df123523d9661d985bdc62e8d54aae9b1a73da994524ed1cd49ead2cb5d55e10a6df4f96c948d72906c2c3bf69dd8197669a468aa9934cac905d1b8067e82d6ddb423bed90fa34ac68c19d345d320da75e68b0731758793bfde0f24b0710a3e35b4a53df23344d38b219a28918e9ee823cd3b2dec4493cffbe1f388698fdc6b1f8ada7a1c6af96d48ac9f39d10fa82efe94bdf280f7348fd88f7c175f83688f1cf69aebf510fb918fa7d88ff5983ba1eb6606eb11ead16b628ebccd6eb94dab664ec37543accfdcfa4cdeb46f43649e5de6d9b39853ad38790f019d0c37bac6f508f528df70a72b977e63ee7006529349a21faab4fcf57e20818df54818b9cd7eb6ec87f968a8d8704c7559fdd623f741f5a786b636461cb063a7dfdbd1c5cf0bcb1e0f78e91367e2e581f5184d3be24ddc69e1deca9dcf21f6736fdd5ab756e5c2488b763a7fb497f75dad396242ee4f3a321ec764faada8635f65349008b5cd97eec4fc842c58a9d02a3d695ec35a6b7d4563e2588f97afefdf2c8a39f7b54ae7190e5e47271b591cec124df4914a34d15bfacac12dd1447f9f75e3f25af9547f2f1a900599cb1cef70dcdeb8ddd1f6c65a6b2de9d6e6c841b149f9543b1c246badb5d65abfdd0dd2b4419a4c7fa914afacf25588f52aeb254509b1918398967f134ccddd3d7c5d8dd8316cf495778689393387f7dd0c23a6e1a1bc3928b67c5d22185e6010be074ecd90cb11b4ee36951aedcbd1969c4edfda927b5aba72dd07bafeca98a48261a33bf920cc47769c60200bf036cf30dd0545d9a75a5987ef66517741f172f49b15b5fc8e9b1a12d5b9b9c2fa51f6d2850b18f4fedd7bdfad2c63d59a60e00a758683d738cd7a3a526746a7a4d3e95c348736481df4f76a5ac9ca5de937354d8c31c688e58ee6f9541dbb55cd589886abbfb85b5938265355559555c16befae9577e228c366b85186cd70b4b5518659b4bbf8a584a5ca12bd442e714b171608ae89065ce1035d5d1b65d84c95545ef3054cd4512fa3989e74fd4836e9fa8c8daec7c274fdcc076ca01f619f5d9f820a57d439db46d4cfd5d6a1145a2f933b53135dafe5ee54ba46e9fa2c77da93aec7b295bb10f4af7b4db707eed156f5fb0ea847e3dfdce15cebfe1ee70e5a55f537d1a0b3a8ba130db842a5813a9e0c15b5a91f110f5db47d046015f53383a1f9547f2c7d3cb5f7e2aed44185d89f6eee60d79fabed73be908c9d18f38b4255942fe0214be3f423e2c14994d2b7899a8869a21eb8b402fa11f550a56b06edaf8458209898037f7f7dbeaeac3dfc22a5f3c317075f4fe268381ca9a31e976a7e270eb3c34c58d939adb5765afb79ce72257879d9396d90ead8729d129cbb29e4baf6578c50981c8a94b43dce60fac2d91767c4561cb487300783c1ef2098a2092184d9e60cadbb3ed0f79776afcb98048730ddcd07451fd917f4025980bf108c11994ff7f607e72e5eddc43e8fdfc9bc9a7995b55ce56e2607c596c941b1610a2a6ca1559fb8c2e29d5dd979e805ae602f678655e0d1087b17146fc6f2c93af6abe50ec26b88752c5fdf8941135717510fa8c65f7ac4d967b1b2cf27ebf7d5abec75f0de432cd63b98a5efecebdeefc4ab7bd89ecb75e50e0416471b920159a06d7f71b4bdf0d205112e201871d83348065c01bbc5751f68eb750b968bbca841eab0fff20132200bb3edbbc0d22a5ca171c1ed481df617379b86bbfadedc85a00fa58ff8ead52716817b5ce598752168eb10920159780d6fb120f04bdf68ba19920157b0590b8e2cb03c8c2fbc326084ddfdbc791e897d834dd7cf77b06fb079a16af902be9ce44ef4f7b85ef59dd5e5d0db0bb12f3dd1c77e8f85d7a58aa657610b8dbeca7b99a23b598fd3dd7c272540ff45ebe4bb7a998fc41f9d10ba9b4260d3ee666b5d7327e4824cd3d76f32543cd661c75786ec87c96089c0635dcaf0be1882c7ba0c350812f058973230a0c9163cd6a50c930c29ac87c920bb9c01c5ea87c930802a54d0008789811837b8fa613200200827a290008b4c3449426a8bac1f26030e20a080c997513f4c06580615467862ca98315a3f4c06992000e1892730497285de7a57ae93dc912173eba16d048ed98830813cd669cf360203bef52313783cd771357a36c21c96599915ec3b323b59c118b63ee230e5b057ce48905768a4e76465f4d74037b4159cb5dfef48ecd8e219ddca280b1ea18741bb754cb921f386b6621d3f4c869ca12c1e2ccf906594c5a31d6794c583b9d8941b8280eeac600712a2d2562c0c3be6b26bdce852066d749c51cf0c9e0cb6ec645b71e2001eebeffd18c204098fc7eb2e0e6152f456780d2d2e89f51d8478324ec9950c634ce4fab197276a6514e5b1ac5842fd3021272bd65f0385a0ada0288ff583a3ed71b263cfb2f5b668511bc9e42bf46fa3bb1b681ae8a59eb841db272992ff78b0a2c6a929be743d0d912d7fc34c03cd2f2f686984c8a5e47a764caf890521c26407211e1eaed40bf1589fd88f033a6643508bd491653909dc82bd68b1b6788155305825c90e423c57e8afc20a3499913c2d6de48d39dd40d340362ae8010a46bff1581cfae3c9b6741751586e1279ac3f1e9c9fd471b22721422d7f03a98128b0c40e2dff523de8a2bb38f482bc07bce8ce03442d99401e7c8be321f2e0bcc9e337b92814ea943be2135b7e028f07e31b194d1e16783cd6ad38e9c1631d734cac95b378bf91c405c37739bc1f47843a363c84ddec38e14f4091cedcc10309a1a0a84b02b3159b0308cc3801bd2f863a4943a225507410ec9a39f0cc28f2be18ea99396025c97b0ea32d506881e2b119b5020f6080e279877fd805b42df81dd5dde341cf1fc1917009bc01219821049b32b4ee9ac28b2b5cdefc9dd6899e333545979e938cd4952c7af64b5d01a30b0cff6ee0f4f1dbf41d6c72a589c61093575cd0f330c8189ae7039ddbc4135d6bee6c7d8fdac450d75a6bad6140948ca92b465c6152021b316c8852528c59430a27a4a408137326a52f526a2fbeb86feee6ac660492d453bcd1142cb4ecf17c3c1f9a7c0e7813e326b4eed742eb7ab4ec010402ef470ebc0b336e89501e012f28f37c3c20f4713d52c3d7c5bdf7be78e33ca571524aefbc97c28c4188e1cd1a8c30c61861d085b1efe3b547846a090e83c34083bf177e275ed855b7a715187d0f8bf5dd9df34a48df5f70edbbcbc291a0dbd708d402b768bb832e81df0da9a6dffb052a7ccd4fea90124e7a2994e40a7c0e160f85340af9cc74a492c61ab3986ba2a9144d33e64c68923ade3d5f4b8d56f3846cba51d287987b16f3f31a2665be466a78a35d557bcc913de2634edc793c229098337f2d1a90f91e3a5a4f2a658c718b1e1e34db1de007e30e58124951a5658c47812617303928e10ef0fb23e5102d816af9097fc178988f3c7800f978c01f79f000f1f1800fbadd01ad83b43ef2c45007c147b4c45007d53c35086f9433df49c484f78a5701b7239a4c7005f8b7407bd72cd55433c893d15a6bed8d1aa4452d4f93c0eff4c97742ddd1aedc919dbea17657ee480d7dc3d5f2b09bf38b97af9539aa6987c9ca514b35b86a24edc29e499c4b8b3990d40f0bf2e28da6c70191997f37cc34fdcbb0a59c2854d77c643ec8da826a79aa05d57356693121af8bcdf587e6e923c5a0bad705aa476641b5112906d532ffc074e00af12dd0de6fa210f8ddd018e435e59e96a6502345cdbed33c8d5b884ae0e5bae6a806d38114b358902adfcac2b0755dd8cd56b8afcb8d3933ce6041687e162d5ebfa187d1460b5610c20b2d84b0c209299431641d0662d357bfb7bb0db4f9eaf164e56e9eaa7c334ddecb567f55a6b793fe55d1f5f434cfab4083ade57201f6998733420a604c71ec8110c29e286d2c41951edd98d37da0bbf81b645b014145a4ca4c84505aaa0c3969b24407140f9326afcb144800210332d4c8d285a77a9324194883096bb4f184315b78e1a99e2487315b8c910613d668630ccf0e9908275c2c4184132e96e0a9be83104f955fc7af407bbff554b3111cfa34b3cf9b46e0464c1e666cc8cd5d926eb69124940c35b27469f8fbc84d6e12e1848b251afe662442a669af2ecfd3cc1302f4c21e8af5e4a633d224f08c39a7396709a68c5dcf8fec4ea829ede84f3ec1680f5fef9108f5cd38dcdcd5db35dbefc8aa4f466ee46d8122ca0f6d8fe43d1e5804e0eb10d9120e9959b07c045f9a144dbfa2e3af37022e1d3f7f332f7b93dc363391337a93b902423cf58f480b11fa116931a36d8aa075261898bc31626ace82e62b0df03e02ed99de8334eb94340df0fba9d07eb9829637a9208e20ca2b428a174dffdace6fd5d6b3460ef696969f24286af90a9b4ac1428a0ffdaaf59b3bebbed67b6fadb7de57aeaacf82d650cffc7a8457cc6a3e5d9f899fb920705dbf3ee373b3951cb713b3e2e43c39720e16cf15bb236d04b226f7535955506dd95b5c65b505afea6131b3f209dfb2c7af988f0ce6235f3fc3d53a536b7dbcb2854de05728314518656431832a3cd6e32dcce7f6c9bac4845898655d7e1e468909b92e88bd301f25cec78e49dc44020c99d35b2e3afb654667af488469fb8971a71bc7eacc2bfea4bf5e613ddb1099cfcb7ce60dc749cf8e2366311c1877aa3fc9dcb2b85bb90efebcae5ef38623f708b5f6cd87741919a1d68e71f493cb70eee847cf21737037b89289eb847a26eb648dbbe170a2299fb20c24879e42f6148380d016b47a9256924325d411b3f0836a1f31e280be1e54db88edf7788c2e2546700e7dad5cd1dc035e2c7b3ce009b4aa1f9108865ae69b218e2823e5082dfd888c70d23efa111921a5f1e8e174c4c2050748471c29341a624e07c4052244518a3a7e0333d800a48598f3de0a30a0954a5674949ae489442268a263c75f0a0d5e5370494c4d179a1a642eb8e9474405891d67f4b8824809261a2084148868b29c20a3720d25b1240b13049a2f2e18a0608214fa114d1962eb4724250c91942e299c4883ef260a19c5e236f81738b893484a93d9d577ef4be5e8b427fa0274019a16a000b40ad50a6bb602bf1393c448218c303e422ec69c04c608638c31c60861aca64c4ae93ca927fda4f613ce4aa77d3751568152be9b3da496dc099ed4f8bd1f3cba831510e2b987305bb1ef64ee726839b3157b4525f53bd4dc830378ee2f845ccda121111427e8b4f5f55d92eeeebcec05de17430d2bac3645d0ba6b00f89d688b801f95d1deeb3940c3bfbf1d875352992912a1a687f1f492d23967adb9a39872af65a55346ac0793191b125f29769a27f819e516f815e14507a1712c5111c3f49b87d2f4241c3a48f6c480d69568cc4dae74a42978888ae8221f94b1f98aa69a89a03c6999cd2c7152b8064b8c084a95eb5d8122a5310d6cc1e48d198303c7401d446c067274c58c8288082b444434414404954b44841044442c19124334c42545118c3e66c4ce25bb0b30d928b0b8d200ef78644fa08da16282864443a899245008461b053ee98890d7f3f178b407e623d40812a19eef3cb0461b631a4954234b97297ae211d1105ef47bcf01ba564ae7943246f878c0997b0ed0436aee70c80768d8f4140f61c4a4c4f4ba28944fd08e84d633efd5b452e939f6e808632cd5f8b8a28e7808ab80d13a58e511421175c4c32eaa585bc9bc0d91af17225fb35043aeebd1d35a41912bf58358e926fa90124d4db48725ea88d6a60dad83559e162e624e1741b4b34488a563cd41f3096c127de0808aa627a345132c42db12067e37afdc3b571f668f84d6e1f4abd62c9a76da7af95e7d726fce579c9c97b3e83d5aab90b67202a6f1bdef3dab72cfbedbd76a6f6de5e097673dce562d87d184bc1d3187e647139cb954134d38960e307ed8deea7e72af8dc0792dd572f3680476c4d9114d73c247b39bc7e3fdbd0a4e20e09700adfaf567e1f85e8cb38aaf6cacefc7001e0f895d94526e36c599f3b85e53da2041eb4c6a689de9e6f190bff457eee2ad8ea069ab47ed72009e8d4955ac627c7cdcd46a14973ea444a1b81b381ff9e4635440ec014e7734da3aa58c8ff1f297d2c759246f345d9f56cc3f9b35b4be2eebca3746d0ba9b6bcadafc457f1d9201abb8620362039c9ca701b2e626e64cfaa52b0665901f7d02f2a32cd459de64881f7d02f1a30b35ec8af4edebc2f5386876557feb77a4ad752e4108296d2fe98bf7614690c03602c7649c104eda7e1b326f9fe3564cdb20beb475492c080e1af9f77854da25f673717c72372eb96bf4792d0b13624ffae5489fe14ed667ae719d75ed19b7e1f8ebac9279c56d389efd7529c74b396e3d47de70c8e0f8e448f2f528fc1e07d723d4d96f701dfe3d89ebacdfd370a7995b14b87bba97c95a34e944137dc6dd44133de15c02124d5c4402bb3a4ec1077a991d287550a927f71e8f3bf37b3bee2bf6636f04768fbd1e0f9ae723963d1e94d2c313d0bf1b4a8a750c0c21eca785066c68a1411b443f60e96e123d01d3ddfd237a22469320d1132f5e443dc1424afb444b49f4448ac637f0b10b9237d964a304adbb6f461dd8b8a28f1a6b4b6da50e5b636fbea20e7b9bb568b2a468b249d0ba52d37afb1aec066c28f2201c2c2a0d2220c0f423faa18b4e403fa21fb8f48c3e48efb7494234e9a3e61975d0d3fb255ad012b44e2312c30c30e7045cf47c04027e374fcb8e4d1ada7deffdf0c92eebd60759a039973e4a8903d928614168ccf994eba0754f4b0eca668c060ff389441f31c079211073e22950cce9807464df07a828a8d4adb4d44d2d15229901000020005314002028140c084522b170389c68c2ae1e14800a9e9e4a6c4a95889228c8519442c6108208218400009801181a0d2b00ce90f8ad7e1a2bd18457d3e19fd795fd876847beb63c20025d7ddbd75ad6d60475349f0c0eff4226f05155aeb54dfd9b977a60b133cd684c30aec87f0b9905a5066ad6045120a08cd7ca71c297ad2bcfb83076b276913808ff0b0f5e379dcf52cce49a8e98a7a428e8fd508df4c54ba219d8692e8848642f05506cbfad1a7734059543dcf6262a30ac8d9b34ee97934722abb5945d6a7f3d47984082a48ea219b289f9d4a5784f3d1b7091ff594c65eb598ad6da1f75d4c52a34b92ea27db81ff4f1950db6cac3b0edd3354a679f3f7df570cb82d86716fb740dd0ff256751352a4d261f4dcf3f9030072160276fa2969324fa04cd9461d591791d7a85d234867c32f4ea833bbd858523af14ec97459cbf52b42e10c9643ab789519fa75759a191fadc29d7e650b2f202ca9b82ece4ff02abcb5bc8a5bfc8072faa9b87c6e23589cb1157252e3a74767eb176105347271e345afa45c23eeda82815c57e3e24673476129120567eb7c6b40f4db4b3c6d5aad6b4fb00515828fba455c9294785c05caf14031c5f5b11cae412c13e6134e1ef010cccaf14360809ec33a9cd27adf385c5ad6ad342eb1bbca650da3abd266ab4148745013da92ac8f9633208a00bb6ab9bd4fbf59e421d9306e75c80d529131a497a526723eb13efb8528fd26df43c320529e5402a1cacc6a43ab974d19d4858df218f9e4795a9989e504095d1e2f2a33e3c397a60eeafb89852a601ef9ba8d4d01176c21f66d6ebe01b051efc80e661a22160f785f4b81260e33fefef1bdc35ef6c0023195078cc14e587bf1928b3f3abc86ccbd45e43d89b738ec3ec0d329fc2f338b1f2e2de3f7983c810867d4db0f5dfab37660d3a4cca441a258772511e607561eb67f3b54d1bbd12002cd9a6de1939bd1bf8920910cf29deb3309fc544106ab144e94e59300205aa35f1bb8162ee053750334e950a00dc80a8f16574c429dc809fca6e54aaf72541e8527fe70223b2b2ed0d6e4b2c7f37fc4e810f6ef118da5863ff9d744111a75290d99c7f1efb5565e9028c7e9cd25606a28582650002a657931ca33cfffffd85eb2654093de072ed6431d50af2e5c4460b8f64bc4a88cc2836d2ddebd2f78bc8d2d1e84dc07fd49b5b9cbef257409a02303bfd0120e541f5c6c3c3dda6f0a7b20eb95578bcd6f7e644d89cc0bb29e6fa4fa5b5e728b64326c9f1824066676a99d0d0ea60889471e1fd55d6efd23b50c7aaa3fb66b9c514715d3793b5493956f54ee62ea80f90cd1db389dbd601684d26f3d185a89838aaf610a43e64263459bfed2b435e45bc10b71754c2d1fc3f94a498968de9680fd45348e16f9353f81cf375d93201e570b89fe8643304cee498887a2e7012bdd6ca4d0b1f05f1dc66b851cbc1ab9dcb53969fbad7263759da71445dab6a3cda415e229c993249529a907b05bc07f42996bfba7c10b8e1673bcc85291998f09f0947f804344c11e7e777f561d451e459a42958b41097962dbae66c345ecbbc66e4667c6f129e2a9cf1a9d72e4bd0dccdbfdc506d28ee0a96b00d4f8aeca650e3667ff5182f8068635a795f1d9a9280247512cf8afae56d9061812467139b3ae2045c880b187636ace0bfb0d203c7608cdb4de09999318fe286fa733e9bea9e0338705fd0ec4a97d8134e6c8caa203c67f27e645bff2e8f0dc473d7e8d08d0227c0a5f7f5735088da2ee48ce4c8ec7d6155dabc3bbf61c60b591dd9f1554b5442f892f22e62f2a3a65e9162e81c967c08d967d899665053cb93974c36956408793cf21b588b468122afd36e2cc9bdc10e13454449a7b11d2051fbd925c74a61b2208c950de28a8e8067fe5838de6a86a8c7ce23097b5993b006a7d5b224962bf0a09dd1631cd96db2a44173c08290ee634f4ac8172479c719acee06b338b3e5a66a5a22538653f590a8e14b76d0bcb97a4ab0d3aa385191ea95b904a41b33600de1c20706b861eaaa67cb02ffd495526879b7772e8009a10c73db4ab8e2a7d32d07dadf4a40d051d01a43d23d3e2e154247fc5e23abef044db2b09311f6bf9f805e8fba3cf700a73b765967af99f819a300c2491a88426c673de44dcdbb6b07fa6220c612853b7e2ac17d2a05707c091d57ba68e08687f342b6714517819d94aef22d9affb0fdff363897f54e88cab31b65114bcb8ef07f4c57814366c6b1841303ccf386d8567bd4cf3777a9b8d7e43af2d3c839f0ea27b212e7801fd8313f5ed59ca611db64bfcda11bc8e954e4f3dd04f5d2673937e24ffe200fad42bbc748b840f2f555b08cfd601b56a1032d2a87743fa31f6a30b512e309de520ebc8592d470b1a3ccfdd72b956377c067093f7aae2c237d1a46e3c41f9539b5007c0209ea06fccb8385310ec41c81c4d1d23a26611f2520ebb4078eb36f56960bd3d1f0d237ea9645fea895268891a0b0f261efc6a494a9f878afbe54d0a109833f67a5b8806e8cf8c860f93e0a0e13903f0d5d46d06682c802fa53e81283217959b0b52ad20900e2c2de93aba2c7d5318942f3e3b272ecde430d3a262a1de80a4fc6f16ffcfc8c93af710ccf238d83c4abf74c9a9e1d5cf31e59640eb6778c87e6cb775f28312b5fa9c1de5ffa429d7ef7d5ac07e3167403921969feb8529708cc245009d089162d7afb816123dee6cbcc029d25f798dead1072cd5580bcaa71445ab6c0254ad3c19983ce902aeda71e317c1b7a119692b60726aed78158ce616c3ce205c5535b69bb31c4248369d21d35ae73f93db62903d05c01b7f075d01eef1094583735e61c0fdb5d00ef521d6fd538bc70c4c3d39b9c36f37973b8db00ab9ed681d112969dfc48adc15ca32b17d1fe087eb2a6328d4108c40bf6142ff41c5ed99723174cd09c72cfb84a0e6d3082f608405dc2f222b4134e358b86cda9140bb586b00fe8eca6ef20d14beccf1277442c288ccac25a267efa54ac59c9faed551e3f495c1e07e636f93cce3ff44d84f14f65690719161bb0f07c12e275deb78d969e754b5005c96a5c8cc5f0ea2c31bbf5b2a1b58485ae076e90c629888a9a792d3ecf0060cab9a13f4745bb73f34ffce5eae59e74dd1ed1beafa2151d5e421b74617269296a6efa487b8b56753e2981bf291d6ee7d1672d0a7a69413fc7ea07712cf878e75a13a4a4591db662f9fc8bdf11edb0b390d152a7a27d7a8113655d8d691ae1ba065fcf03b7f5605710471379c737c373274b6dfe9f325c48b34568167b9ccb24d1ed46161abd1709042dcc9d23aa27ff9964fcab5f9af34827afb436a13703a1db2d4be15a96ee9435d3889019877db1bc1828303cde09f4970d4f75d3985792c9ecdb29112ee289975c9cec2c4ae157bf301644dfd914e3b9c58d895f2281740363df9c305af1356af499813210248023489f8ae5fd67c4b4705aca15b1d3a5edeaefb4f1a147c2592e55f5b6ce2fe081c27f7f6c2855f0f7613aac6d5dedece4f77c15b0bda6ee13e254a1c4479053c1b5330439004edffc891aa35057f5b8263abf0593a9b7fb47c809a75a43ba3dc0b0f8e4261cd808e5e0c3f00be95565cdea46e1cb6887ea0a45fcdd85f6866cfa73bc4739ce33f4ece68af4de4f93501d5c2a0b889f4b8d688fca4ce1db5ccb17677944f1fa934b5afbd7560b1882df4855c20f1bfa6e3d858f42397e7cd09a0be6bc423fcf148154d09a0036ecc0101c978e0b123b6f9134be27c3969e14b57e4d494b798dd57ba8e7b808f46dc18863e31bab8bf76629c355ef0fc81fca265b94634ae92cd6cab7337d8753793fc505cef9e5804d231a43bac6386ccecf8c91786987a8f6972bb2666b554dcaf9d3559af64cd954df61e0073698038ca8025a997741f46652779334e066bf39a25da9a484d42132556c6ade14f2180e64fd8a80d48261bf9e9e018dd4cd8579e33ed15df531f843d940528a208b8912391f417e745b3bcaf4b35474e4793216e0c0dd4299fd33baf3815faebdc1e45c6e55eb742b18c7fc6ac2c905ac251187e35853bebc1c97ada0130b3f375af5148ebdf0be983756141c35969695e5169e92696bb57425d6b9b620ac7efcf73ee9e1a788f061dc1759ae185b38838b60b340e7e0ba6e5968ccc2b9166d67a871701dd30871e41b4de6607e59a3a8f65380bb214ded3d5c03dd11c8ed7dad6be69c127f9d2b688ad7a2625ed628be536200f6798944b907b8d226b38063b45f02397255c73232f363ba372312ee37904006554401aa07e7a89218c339d7e86b753932f4356112fe1c4a96dc9fc201c40a8125448dca11c1160b804891ce60a63b848db043f1dbbdd482084df5375121c4ee0b551b2740818ba8373e21e0b8c6a870827454903f5ca7ae62a4669f35d0b78f991ec31b022f76cac70ecea558683288be2398f36cbd54abedac6871b5e547d8a909b8a955e5d4c778715f566660f0b752dccf69d5bf1ccdf5196c57f18df557c910ebd44a3a5df2d0fe2b7881a3d7a29a08d54abddb0f99f58781c3e16152478154f51678fef77f1eb1b4a17a8cdfc38081ef3d564f82122c8731da5c5fcacfde09bb89d58643beed51a48285de9bef5370c70a82e82fa06f2d3fbb356eaa1ed3d7dcaa3518961d03b75b32a6950059c20f649f3ec9cb31a69ee522d4e21c24ed4ae1f125305938fd54b0bb07680763107858772773ed13619fa452d6290b95ccd8dce9900be3406c19f3be3054dc6447d222656f23196f6eee3d5d0cab330f221eed652d82c9970c1ba4aeee88e9f17258fd792aeb2b6072f394d214e56959b90e0b0fcfd13c73334cbeb60ff24160a002b3c31a379303f61e462ccc960abf6526709084458362cd6f3a4df5898734dfb535298d4a20fcfca989479a7f268d8cdcd2303727981f75d1f28e5711e17669b0a6e2a7c41119204772af7ff69512b8bc6b0b9e38a963dab5a45d98fc357cd7bc7b33801d00b3ed3def08471c6c453a7cb8df31eaa26c3858fc7f44bda73a09b57002e69ae26365e4638db85a945d3c3bf1300a0d0ab7cc1663e87ce0be7851f68fd1e9531baeac6d78a8238ca86b9e0a4fbf932d8b3b86e96af3a72e23e757a713a3fb3c0f7e6a111d48a1b7ae329b05510dca46d74bbbaeebac3b6edb7db59c9f980d9c3125aaa8dbd76262da7a6564322081fac9e6e6dec84d1d20c3930e33046763a41a0c6d5f371c8660928fdbd7731c183ca4cb33aab52f4453168bbb60550ca4f976ff5b83106df31708e02c00c5ed09e6de2cf69940eb1e043c8ac38bb5b7f8e10d13f859ab226e722201cb49acefe6abb2411fba17014dfb057501f3cc1392a90376c207f6dbcfb3896378669eb5431b7f747398e98d3ee2b805c6b3458cc4e2c00f7ba05ef1f7718174873b8cf53e94228fed12407106263ca77dc37a67de3ff09aaf71720c6bd17cb7303721a9a469c143a6ef4d9a17683746923203b0728521ec4636939b22b6298d8867ddb232213d54e48c3ddf822d0d4b3e07183d142230715fb6673360708c4f53f3882af8c1238c75ceba8e898290855f223f4d6e0eccbad1aca1cebb54aae80e80e7f40cc70bd8784d22b3bf4ed9085d49614fc6c936af0b0d546afa955e1ef968e316e220195d93e90bf251986d4e376898b4209f0bac52bf1dabec8e6c4ad3fd5a4eca7afe40881c8ea9bf4899373e90a54f4602176d7a88b895994826cf853872c3546723dc1034e58565635711f5312b848240386d77e2ba29e297d74caa9caa152c8d8f815951cfaf223f22a8b5cfa5f3c050ee99a73a16ac6b1edd8a2595184d05eec3200c8ff28a0710ea37e497215b0a4ae4d0561f32b45c97012228256e2420c23c163db269df2682ae10eecf808221205c9caec9f71b7f2f077bfbdae81eec72174c5f5fb303034b3e03872e96c2c839fda479003e8eff96685622c8abfc42cd954e571f610fd84fbc1f9947b4f87b575bba7cb40b21680e2284715952ebbf201149abe52a4c6a8d607d09581412ea4bd3939f580abf085b11153449f39d3ccdc78901bb637bd6dd411171a1e6a8baa29f1d8332ac5e23ddbdb2a4501a767a6bac85caa8e20d139bf4cbd41b8bee62cb8e6ccb3d5cefff81a1bccbe4584a21c11dd2aad0e8ece61797f190b68db7cf01442077ccdf41fe60b26dcbc0d6d9709a563c6491a4dd7ad53a37f605bfd0ac5f5d2c43486d38467a853a9e0b186ff8b6e3060c2af78c1f798c77daea7f08ea26963080531e1e80d36d1ae50071bc67fbc3b32da2787abfcbe9de3da5eaf781f51879ab082c2a0823a81286755eeb052ac804ace6522591b4a7283ed2a0ba5f6ba046aacba2727465f4c1af2641f796223236c7c5d1ee9a27a2b6ae22fb1cb25df64843827229eaa441a795cf30b44933afd91442c1ccb7692ac0f0f7df8a3f9fb4d729c8aa80bef28898fe09d40ec187711eacf6d4046dc2a84882b08866cf767270da60de2406d4cf15c13788602348f59e961a49e6f9cf4bcc31184167178cc6463719c0b2962a9c9f4475d73be3e2010d8744b130d13b359d9a6884621d3fbf8c06782192a43569a1682d4281f10ccda8d40acc943ca376a2b40cd75264b774465626ea91a986eb39d044df9f7d2c8536a32c4b1b57dd5dfbcc8dfb6625857918dd60aa07a4ae15fd6cfee50dfa16eac51f83c7dc35bd9637f353c05766ead975976430c5f4435b0cd7ce8ff33ff7dfd58a8d0e1a98bf01a075dde91d8f84d0951fb2b2b422659fda8d6f886b0b1b6b61301d125697fa8910265d84ed4eba0c851f6f36946192aad4c92ed85e3bf25ee315bb6b6b8f835738956a005cdae96bbe85ec3af5d435cfc3e4baa589f44445c8aa6378f01d365a63ac645c1d46a54eebf7d6cf67808b822b02d68fc5f18606245c04e1a60571f234e898825a1c1103cabef634d5b616e42236a91ea8b509f020ebe2c63e818b08748b2568dec25f3ca47401c98d0b357150469d5361ef96828bc0a09c0d6d41ee3759c340c7eb5a77730490ae4edc8cac9cc28f5571d42392ee496b359cd84040d99906383c0933fda0f6b1f6bd1e9788a5479d6de4456f6cfdf2041041a51ea11d557d0752ef351f1372bdf00cea35dd3cfd55e5c9848ec2dec58dbc420ed0fd5db2b353bb726f963817f41f0e63e22e748ed17c486d33c967ea9ed646d8f478c886539bd8d7f998265afa49227da3ffd77a3ba1164c0c4cd79e5137abb7bff2f6d62f442ef917c678eaa61239ce8bd7ab85bc7375ee825e0f13b08bad90d3c19e053771065ec7fe35110eafa4242aec559058d664a3e2fa318892bb698e73e0aca1dbc3bc7dffed8fd07466064f9ae293a3e02c5b586b8942df3cc8a453347a1a3345753b1dbfac958e1e347d356c2ee04712e3cab98be3b6a2181d52aa726fb98f6c949ae990428e53438efffd37a2b3fd6d490b5b6d9ac54c0179882befe533637646c44278a5fb15e9d4a7fac162ee0dc8220097886804c5491a5a2c560988655d2ed9b0053438e34cb509707cd2a273994baa93c2ed21c36b5c4c63a4eaeda03399b3ea6b553a72af44c487fed6a0df65ef02a1cbd43707dd6abc963a2c9bf817b480495d9bbc831446db82c8a0fec28b5038f90f2a62365493992c27fd070c7e3fb10984c9b15b587b12c2c8a6cd77a46de25448a26f4f22a0cf6d1f75226d4212c5286650c440a2cf0275111cc5721aa7cfc38620242f732bed15182d5e64e89e5ed656a26322d350d6f85c5afe6a53d49edee1cdd0854710f61da48ce068aefbf264db81e9061dddadf0e034388da993e1940d7cfc26f89053dd9a8738ae33aa810cda138e40f90406a170d1f130bf137e41405687d4ed9133e981549d562d7498573a76894ccb30b1395ca5145e33730ad1094c782a07e2c1801319d501a9ad82cf626d814010dcc368d7f7cb297ee5951d7c6811bad13dbd84c9f5bdf867df6b1a559fc8fb5e8011bf9c9384aa7b757955050b1c8ab302659bfd10e0e931a7ab784d4e8639d69682c6293ebd66cacf2696a7be50b019de47afbddd452e678e333705a4165ea7032393816755a5298ac3e08ce8ed83477a78373805a5e293a6e82b71d6c5ea80e2570f19049b29bc7c6ff5e5538168bc2c4c8af33fc02dcb2783e73764e7ad0c2f336d17c326d78d56eeab5909eb69c4e66b5c05c1c8017f07a1874bc3d79e1871466f70a90ec857b346b1139aec549b79ac75f84c3e5aa44f491406b0ec6508c27b2f42a11b1935f870462d203d16bce04be7e8651684fe6e0846bcdbc1be87faf95f1cca28e975ba40ea8b0386e3650e4c2304436a0ba94af2384e4af7a0aac769038b55093f178ed31c57484944da66d002c036e877a05c154716e7593c603ae3cb34d4c691e13118bdcd8d8ff2e78497b1217aa4be94e58f3805400a5a84a3f005701b47d42db799343eb0de2697269456181a6b0d6a772501f0bc883d6d586e0d3084b893e8e4f1b66e8745ff5bd247f7ce40b8461e15663f28449587e09e3373ab8d9d753eb0e25107042b8e78b65541c2cd5dd6b9fedc85acc73b11e420e7fc0558d9f6f3ccc4646a9deaa293824da9a302de9cff9111c00b54f6e2ef6ce040cb8a09b15884a67dc96dc46ffc4a8653872e48ded63b78ca134bb72997212d98b98f856ea75f60bb1b7f4c4c623fb83c150cdc17333ee9f63f309ef366802e9d834ce5dd0f8a4c46508204a9399e0d299484604c6ce9e7df584a27ea515802d17fa76f717fcab74fe22b7419897622c646cd042b8fc55b33a83d64b0359d4a7717f74ec08d66717521759377502ca835f6bf0b6751340cbebc933f69ee65458f23c002b6c7554fcf9061d7961d9a227bcc19852d420e18c1370e120dde31395ba7e866719e0a74bbdb42595f64989794c576cb28eb3944b562894bca9d2e1c6f549cd89c88767f040fefa4455fa2033f016d5919073cccd651415124f408900a6d81669810c80f1e9f77f5fcfcd679f2d87e268b4065355c6200bcc6f0f04d04a5a00babe34214e2e57c122a282a0718accb6f30315d60b3f30aa9335434f7ae8555424d7b8a18b90e4915867519319f7c0a60d5a86b14b6576f64111e26d44b9b9b523d170ab4dc842cbdcc6e450af8dfa45fb9a906df159dc1c624212c62fd6f1b09060278ecdc467d549bb63208c9a41fdf1268c4a7c060a79a2de9380f36eb28481dd16da8c67f0edf82c09ed3604ca0c97d089d0abb7b7e93b4bfe6566fe349cfb7e6397853ae91a6e9d8810056bcc8acc067a4074d2e3853cff801c9dea341477640fb8150ac65f3e90dabd3a9570cff95ea6d7d8bece20caf652e0034107d3dada9ffbd4e569d7cebd7a62742c8b5d6f454e546b58ae0623a4d795a695a4abd1ef0856e3fffc81c250030d7c83631e9c86ffd35a5a3832f84657144d401cde78bee8f173942c6cd6c5c8f3173a80792dd9105aa8692686735225aaa66b739d7cac8fe24dcd845b3fa23d0fd9664b88f9032a70e503d0ae119587827801c81ce48057ca87a1de040e5287350203fbd64aa97b5978f8bd0f843b18dd23d2a67b72911e0320c947b2ea392fc13978c388ce6adeeba46cbc1f8a714b23fd9c0e912b1157fb481da3cf59930615e3d016e81c485e5d533971b07132091b6a09dbfb2eca5dacd8024e4b023499c952490f4d6f844ad48ff3344d2d7d4d71c39a43bfa014012e9a83a277120cc8a2c6b907c75bc14a6a5b02f3f46408042628440c725cd8c431a3e9025a27c3949d001f65ee6f5783e7c05e737ffded33adbf327bd15fcee4cf63dab8badc613f561c15ca3498396e09f73e0a011acc958a89a8cd970b10444f4de915fabda9e2206e3e6428d621404dd5f74b00e334d1b2489d0c6d0582d2c078ceb0000aeba41828789e73640ac90ed09bb7682e9f613684fbe86dff3bb2925fd6d135a908d3d47c02bff182f55fea9f5058158fe0b2704c754e95923f612349d79265f02fff1b0edd9b5d7e4091fe7bb2eaa22134b3fee6059a823f9bb9ae731642a1ba151cda79c3206794ef3c41cb5e109dd175216a84344b2427687e964bf308b292208c14e86fc3ce4ab8a2feeacc6ac9c204b3378bc00a970e9390da72f2161a5a0888d4e3398273d9d8582d7c7131586f7ab068a3a8c748ac22cdfa8625ad04a75150d24826890459db4269ed5d1a37561ba184e65e20232a3b9c7f566908bfc85d7d0e7a5b94618cf8b8153f89f075e9088926cf22e8ea71a24de8870dbfbe6fb70e7d83d058b660f44d328c751e94d3308b2fb2734b58fab9f742c8ae5bb809337fbc2dc274a0b515d18dd2356abc53e89b42babaada628bd9f63ea639de52c29bc2076c837e85e03f0a375ee5daec15f9f2273db3619d3390f0560238155f1687aa89882fd33e7c8a1ef6a6cb8ea68dda8822be3c7f35f97fd44b5de09f262da6bb2e36af7803f753d93dc27306704081653d942596c2e5849b86c39c50a0fd39e3d82fe812251e4f28a25c4d5ad09da0f1c692a15de2cbc33deacf4c04cee9c0d2d6394c6356f25b0197545103a4a10b4fccbaa39da66c6b01cf4261d3e88b6101c632ca257bfee72b8ddf748953b3c96c4cb43c5752bb13a1402c0e991546b610c8cd49062a60f75e1d82326bcc3fd3af50bff57145195f9731be9672d2092d0d5b4dff91c5a4e74e74b31bab792f5054f21971d99d86b431c71e9b945362bfcb0226c946211ca355415589e4ad9ff2573239bba338323871f9e2d9d18c46e1aeec9d35c330e7a1e1f798b4659572e9358db23925bb84377193036df051d328f801511c1f8692f8e26722ee9095bf2f75c92c38230c1bb220ab26ca84ca879553f93073eca577efe08589a312c30368c2b628e7f4bd499e9eee2c4578987bb374fb0bf95f46cf9199ba0e1a60089136f981e1b1c0278f599388ae78569a255755472953b3c15e5edd2a8ec44d29b09b4a8d99ac0d2f271ef308142bdd3df0e6a4d9af11cec930efd815885bfdf251220ad51e304b35fde3c8b0443c345fd0b3782f61805cd8726250949eff1a1d4e43e055f25f6eef4b233909d033463c3cce1deba76adf4325b5861c8eb920ee7500e7c7e619ee85a959c97b219d87d1e38d3befe4c5df3ce912485751dc28456b9c17f6849a0f35324444c398d6b62b58645ba33e602710a38155614ae813c437192b5d23ad7216944a6f892a0f1b9ff561015a421064b397c2eb20086a7e3ac8db7f88d7af3aee4ca12a4bf8f6374da1b8455edff1595c2c9f349658802b2c8dfa22727c8ee0393cfdf25940b464c4e684973b4bf57df01e4bee73e9475293f0e7f0e1fe19557548a08460c85cea1c6d756b82b56bf2bb9a373a38600a9b3047be949905a24054f8e2ef52d0058d78c4426039742d2042e5828a6ae108af051c558b215381e14c312574de3c4182c48a72cafa20c409a2e10cdda7a8781b7b632e4e5d87b5b7300bf3f753f19a4fa2c9b035ff2f797676ef82947c540b1cc539f2575d7cf29f272c449ac3eab74ee4e0144d907da1b7935290ce763713700337929545d9601bafc2eb9b8f423549efaf03ac904efce8333f368766f0cb8a01d3fc36bba712e363ca362019f7a66680765f9f21a165553ac589bd4c61e5cebf7e675bcc5ea6c70f3139cfa474b8cccd6f0d1893f2d9483498ced95c89c68badee96b730562f5d1720f8242e1995e588c324ef6ce878d13df70bb804e758f55553622912dc904bd8a7603512e9c0e024a4c6bb5a6af105c20f700a373abdfca53c806ee6bf716143884421e168251add1d3fd3f8f2f11052f9e1f57a1943c745848fc6514f79582a779beb9f62887eb38f6b3a7799d7f10b3628ebba01f858e31c9e14d953b675a830cb79c953b75d9044866c809a661cb06ce4103acbe14f729153cb51677b5e410d118204271a364e7e841c35bbf17da904110d114e7e449d76918e3641aa589143be4d73ce174849d7c32f4ca415e2bad7ff206ac73984cb3a5edc9e8068f1549f885c8adcca79ac3bb621838138fecfa8df5138123d28281c81c99ec6edd3bebfd022093279fd7b30db4b13f336a2f5a402f4b16c73da0e5ee78c1b41867cbc894d69ba5740ab64e0bbc771c3e7170a6c2608889a2d6c8c74b5bc0038dccedc9282a1c2e793263d19acc11465ad92593eb6e471e0f8ca08d587e8d92e49c4da36503ec38fa777edd128ee3e927e1aa4d474ecaf3481196685722ae20c33e796d32852ea721c4023c496d4c92365241922b027bd0867e5d9fcd702361f1c95e864d3d9a5694637a7720f9aeb582b66751275a0a9eae965f6aabc6066fdc19b78ea388400801ea25cfc63cd90fa77f438fa279614db927fa925b0131748a97ef095549c93d49e9aa35ef3fcc55b729a060450e05c945b1a9cfd48416e07b1319c05459d1cc821a70b9dbf331b707bd349a0885dcf2059934ce6292c91ac5db09a34077b2737859f2c250c18badbb368f087ccb985a764b88d09a002aaf587430c54d2f4f42199ed858707b5e04da59afd48a35a635e5fa2188f2efa3c3e69be29f8d8f6bf532b8e38b1241565ec84481513da227b5c70f4fa22bdc820dd7cd533ac960de92065cb35ca7eb9b51dbbb8813e12c2ec1de10af29dca8bc8ace92009a3bd654076569cb2ea6bf52f7f58ec89e24a943d4c098d235a05a3b847c016ef1ebdab0a2dc3dded8ed6fa140abc8d1a4d70585d9d43c1592ac5d3bd6d4086208061cb1b6fdbb2e59a5e9c383b8beae6ca0bdad93ac62617d503ec67845a7f1c94804e59a22318020e16dbd7e7928d94d6dd697b68aad6b8628b79853451bf376bf60e5dda106296afb760c0f2573792d24d2d0b17a46008b06b83b7375bb54ff98a90a9c566543c9bfe83bdfd3d00163dd63cfc41fdf6b96bbd969819d72435e2bcde0f45988d77265cfc84e518c19b377ae3d100d3f871ff6331c8d63caa003f61b1ad04ae8cef8838022086a9089a5cc288ec8077d9634c642456220ec3d4978068386472f8c45a027c89838e7c663d177b236fa2577f2f85d867558208962096fa0d37959b4d678a2c06482e21b1554847fc7479e08ecee96c791c89b508e6415bd7c1930cbce0c530a012f2313e1b9eb570efbb7bb72fd21f777cc8b96c45487d22731a9b7fc7407fb646fc3aa63de28d8fc3bdfdd1c012013eeb9e97b4feba7f7da58d47fff75cab4043785faf0740ded84e82baf4eccc7e6dede286600a30b7de329ce372cb38f5654299ee90d269b279096005dfe5822117b94751d2e92f27e722da7a0ebf40b74da0c12edd753886b24b952b82511ab57c83114c87058eea14a30b7acc46b88efcae9fb5b59c44a3259fcaabde6173de3e3dd4fc93243fe6da56a5f03e72edae64eb7ac2602bf46bd90bc27453f7e11fd44013c9e088c2519e699cc605e627abd6a434d82b5e551818ace1c1c92925758c298c3272c8cbf620f36627f1370aba8701060b5ba80609d52a772eebb370347924b451597eec6a2b0c2138e1e2b2729aacad6dab28ac199feda94a8d3b0054a6ef97d57e8a0867f5ec770b220819909e7024e4670afc5e10fcae227066526f4aa5ff5dc877d7d2c1466dd75eb493ab701ee38962486d5130895fc9ee63f16bf584b01309a588820e35911f34858986e5374cc20bbf0c9b84a5f1ec40d84d961f7152df56f826f82075694defe53e1e806394f29425aefb52fb7272434d28f8144c38f22efc8a1192f053ca0eb549794368ec2c85ff9d3534427619086051a9eb9a1ad8c0c4440c7e5a6430faf33b837e14e4a83b6a2a1ad8d5981be1ac566877af970c271c1042137b1e2c43bb3b6d38e20a5ec0fee5fac078ad2559d93e11fe34784acf431eeac9365aa9851a1acea25aa5e19b0dae77d7f0b599867d631803dbdbddf04ec2fdb2dd6f83cf45191253dc1312d154c3a18f60fcea3d5a355c3424d88aa4ecc31e4a51820d387a19274b1ba8e9fc1fc594e349880df0487a5ebab1236c8c209efa51b1ea51ef098764c43d81f98d11b1845ca19ca993669948a7b872424f35b63582aac49cf0448c74b7496d53e0c41ca02001df0fd5c23d6918ec1442522533d01474c8aac16416cecd89957880148f3ce7c5457748c2ff767e487e1974672d373c5f65a446dec4c140898441fca83d0ee2a456d61b73ac455abd13f2a8f2afdae514005efb6805e22efcd75c32ed5066b0a1571cc3be60a6d90d43227ebc2696ce607b33987a0505ebf83ca2f12d873a70e5fd095c4d1f2fa1f95cfe33dba4c621c6a920366c9e15097452a6d963424686bccc3e39ed47e45fdc4c3144233389c88f9f28a69868f43547ffa018a18ad962a21477849fd1b50b3f63ccb1f04cbf108d84592d6eb2ab4f9a38e91afb8f5313eec537ac53fcceb3d03cb3da9b64dc8e329e55305465e62f5ab574e4dac841596ccec986a467493a19def6d4268e0a9e26d7fa2cbb5ce84d488e0cdd47dd053708e58a083e1948d735a0fb5654b2f2dfd74aa938c9be36fc7f9a5d65039083d5ba57622b74f006b711cb55b25b2830857093a89fb4655009becef2cd22a461c6e8879719959eed24b1bf321eae685b4c6e2393cf4548b8e4d385a76427b91294c5b824eda30ad8e622a913725a7d46278821f4ad549ab7be7e0d48d3f13a2f2a321b5516100fc2057943a67421dafa97cca446a82dfdaaaae875ce2f835dd484c8432c9da440880d1e341f111c6111138969a70a7b6a0a5cb5b343b1386a437c7bd3cae381483031e611b570fde1b415d1b0efba697eb9ccf51182cf399934851444aaf9a5f689007b7985ddd0617c26888986b496b4dbd1a1bb6257b250b7269523de42b71c6ff91ef982ff74d244874c3e47d4b5bac8d47a9c5f08bfb297d439a91beda7372784f67d9884c0693ff92168537df8441aef74bd2540f2ffd161532bea078a143c1724a80af2f073d0b83c61b843120f86e3bcc046c263097f971c3524e256dd5c7979082a7ef1f998b51fc8a5850cf6f53ab63acccff87b69d23b9c7c3c099765649a9d9fb27fcb51e19af7300b5aa0be7d24ebe6c1a20c460b72569657c87fd55a976bd53b92004e3d1777478a0d94d6e79618aeab5a6aae8de95a8f4c72acbef4030021a0b215b0462fed7b638df50a87030bdcdf0932529c2d31b680f931933ad403117e1dab55d7aa32560f4b2256e8fb38ebb97651b4cfa032aa3a295cd33fe16810e4d0d7005a44a707e89a8d4d3cb201e5eebd1f479aebf91e75f48b00a206a912b2f89ebab75f59cbd4cf63b2866fe48b310cc779865da9e5cd715cff0b28a2c2a4c31d77e01d90a7aeaa877cbbee8a880f89c6ec4328c2d2f8bbcb5e6904ac00af54e8b7424d1262499284e819fc7d13b5639b7beb4621f48b4a619ac88ee1d84a13554bee17d0e58d1710e661a8cc2c79a1c5858ce1cb3629217410b66576cd3e5b66a2b9091ab12384c1408cc94bb18a1c4e017ae87fdefdd9d0d99b33db917ac8cefc59ec8f1c4492a7867f8b8a5f16758b891b93d7ad43113ad213563679c3f6be604f54b768729e3897364597c8ee7806098e51e8b401fae82baad3c34cde9121a221c6d94cfaf5985a8ed8d2ac61abf864e51701ab13b71c8224c1c06b933a28f9640a1bfe1658f327d5b83e5daaf28a2c262dd324f020eb5f3e4fef0c2ca882375786cc1021a82a86f1f3f3a1e04a620da0ccfc9bfd4fa41c17644566af28c0f3f6ba6a25d7f7811c2f1ce325a221bc2f75c7ca67f2c893bb0d05e1466daaba23970dea782a9e6f1abab89d57a4ecdcf738120f38e597c650f79517f4a5ca5ca49dead28011973866227f4f69ff185d9e766725bc6cda403b533df2ec08e0b023cbbeb0db6f720052c9c7d78a9ae42bc95356e19646301ed7af525bb4fe1934366e0e21aee02770050213187ae64f53422b79003cd2de1210e0513b5cfbffa2fe9f35eabc748c5d90846bd245fc6e30d21b506c2c195eae37b785b0f2d6739bbb146c36b06fbef5f102c25be0ad4126de9337fa4f571bf74a61706f1b3161f318d005410a5c73fab27f5e9430dce0915670876779030ece9c366d9df0ac0dd240f53d2b3ef9ef49298fc3aa93b56cc1ee3f25c3be05e2a8a656b2cbcba9f1b26ca939e1bf0f5e0532325177ba9199d5b4f07cc471cb590f873ec34aaa3f1da9af14739ccab0fea7a661a4474de067ba93eae9f0afb1feebab4839b1bccab5d6729f53776037f8b62a96c1faf8c7c5cf3a5c9aa8c10bc87212205a5d6702bd0b0833d82d6f03b27c5ccbe97bbede27569d32de528fa7ac8b75794e11170544c60cb124f95a46b1d8054cbc97ede4eaeb94c9165198049a15842d8d543a619866ef5e12c5826ef1396eb1dfaccb70ad5301c06b8bf0aa9ad7ee825e8f838a32075df119f8738b8a6a23d8904cb6058b1f3303ffa8f5a1064e995f5066aa3be73e8d04e2c4667e4946eff38ddf44f6e5887fe3f20d285ee2802c87e69f72ec89419e5395180c9d08d10e751ed26bbc0128a1ecf51818aeda890d7c1f5b07a46d2088654e0d4e1008045cdf7be6ca1a823538ac289c36b5ad5489e1edfa91a41332156bef160646382b787220914e92b1b7f7e1fb39bcef74d72aeb3a696a2c11648989a323a55af9a723392e76d5b695dcf08bc6b7089ac3ae70c483aea879ae8948118c1fe00b964ebfb600dbff6e97fda0acd737f52d8ebf27ffc8abcc35fb98140141ccb9a98611bcbaf3b874be0b479b44aca43862cef1be8eb0c8683b1598eb0712cd6c681b7ef2c1f7443a4bd878839b8900696912d21da651061fe5b6a11ec94503953889e934cdefdd57ee9a0669c7fefa6c1d22cd1e31454390d6e016e75ddaed5d904c4b7e036de1350a5cc24157f6dd4ca4c13bfb9ed3d87171f9de45b08769fb16045e60e4fced7e54dd2a421957a2574dc416bc578f948d1f6678e6f5c1579ce584100363788e4a44a5bb488cde8b5dcf7f33191de99c7c97f6769af304e40973cb9ad91dcbee171d2b5b1e29eb6640f2cac27407a904dcba349b9a945ba10a6a2b3652414b7191b88f117ff7b69ad782d31f504ef6fce54f74d5d030896b3c68f7ca24aadfa4ec5b594806f590a314c537d19af31f6ae688bc21f12647d7f8f54a44c1bf41809f873d859ceba52542b3d4f840ee2a31df48bfd8dc85aa13fcdde54d7aa66c23042780600172d40ebc7dcf71c73c6f22deb58e26d12bc2a66e1352924c71840ec8e00a2a49b7c2733f9562a1a8e1df69d19e14b8be143061d84c2418e4ad5656503167aef29092893e4142b265ef53a7e6bfef4b726f7a6becebd6634e90adf2d7a5792846b96bebf61a3f6d30090ec1fc381d344bc57b0fbb927394d7fbb741226042da6958411601f13313ac731a3722c3e373040a9b023c6d759f55d6c050ebb8493d0d44fb9bd5b57964e3c7da2d8b031a06ac11365e0baa75bccd5b632f01aa577b967d7d821a7b10d910043047644580cceab9430fce760742a512db9094aba085a6cda197a5ba492b7d52e5d7d7261ec9fb1a4b8b94e034388c8c3e322245147b6a5f6c8b5e975fe58079060bc4272c4b81522c9a032e800171e83bb5305f4fab2fcb8cd9934b2e4162aafb11c8e15a78c1e54798c930c8d51b89791adaedb23049232b2a204833e68084d43d8f836d6a5ed43ea476db6223901393ea92368f7d6b533fad8d374e92c009248a37d3fdeb18052816c82b0f0ac7671f67aa0e4d8cf7a30ee4541b1db9c017db8d801a54d4676fad64ed01593989bdf851266016294821712e5127d3ae07e156c407b9fecc4cbc06aa82f64b51c52d8c12e0dea1a3d3b2421ca5fa65ae69e62f83c5e6d53e87ef7c810833580baa480d30120a333fe1fcfb382357dd18651edb27372d4a33af19b77278bbc1502fbc03896e552ef698d8c89fe80dbdc115de15fc75dba2140b29b6404752e1cd9b7af2086d98011b532aca752943ca688acb59d9517c8e86784dcef3e836e4623a0250f56b0c97794506e7a7d0204a842da1f25390f89ca409cd7515f59c079396e9b667f356650658312c92b19f9a2382ec779b32cf483afa79407036e010299cd3b2566961ee24cb38dda96ad0f0e12905a6e79a7850f779c35c567f997f64242896410fc895db6dc522214801c3ff892d6a04f046430af9a0ae589041de48d1c97cab50139af459b0bdb10add184b6f60cbf5d5c7d94d6217d87e55ac8299eb30ead684d26ae54a05432349869c651c379308a4b1f2268c3531fb9570f144ae41171e3a5953a9c9bdfab031cf70b6e3488f01f97b8465bc2a0fa45deb9a2abed3487599027149a3183dd281ad62448338803178212425ad31eb5205b6f9b8c59b4f00034872077e6c768483622952ed51771f19251620a1ea7eac88e03b4d838be66ef6f68933bea6ef8e0dcd1ece5c700cbeb69565863f46f1e76703c0da615731e33d891f283d0c7860233bf576f57ad5f728665132f6fd22cc0eeffcdfae81c7d54e67de09cce171f5a6b30ca7853960e68cd38c49c71ab179de295a2e27727c992d0f10d34473f4366558facefecec21601703be221bf064e7e698d282266c93c5f7211ee1e0985ef297337ba32d59e091f319c79ca074208ec3e219cbfeeca882e174a29c9c46a45d5faca2f2d3ccbec789f02a2192c3a02b42d69c5af393156e14a1ceb7556c6c45e6f6dbf9d94678403d9da538e5c72286ef635d7b2d1da6760f065266b71f3e487061bc4b2a749e6c4a5bdd21f990ff2b7c57cf106331cbf0c2612d14dba48121ab480a287f80cbd07dcc681387139a190539a0eae39acb72eb7b8b94a6ed7ca8e4fe8ec051c42250312efd8f243e852e1313189d2d846b123a535367eea818798b262f113d1bf2ad092f7c2d4fd14a986b6388f828f33f53ea6dd9b79425d804d994d45d9b8f7f94c3e11018cdb7de42614897f38b945e9049c68c2e9f733e0616afaa5026c90b029a6600a9f3bc65b55cf1416dd7048153575c28c27c60255021105505c9ee89e136b14348f8d29f886e37bbce8996f3a3d9e17f3cc4a233501bdd3b76df968d0292333818a307b6d1d08c6091a3243c73b6008ae44929077aee257a514e96bdff73f7f7e9dc70539efcff0e3284c2deaeaa2edff004a6a84761e2f884cbad75482630d8d35a5ee1d9f9eac4a425bf0f0e0f152c2c0683de3a596379cb32e04449f5ec2feacc989c1cb2bf9008d068e0de9db7cf31b19cfaaf89279e526a96158347b8c5339ad903dc1e7c6ff28627fee02ffd2107ddb05bdce56d585ace340af48cef1daa81541095e846aef2c7d4febfcf0def68fa0c2b7cfc12387710ff981dd9a8a30b3b568aa619f518fa811ede2284074515b33db121861b96089eb93539105792e98fa0b4fc3d7df4599165a226e0657078b169a835200bb579d42ed1e4eaaeafc632447b2fa9df356baa55a30b10e1d8702eb259a39d030043efd5aaf815daf28ad124dddefd9d0ab336a0b4361c1f75a276173ce8c856fae59c94f932020dd3562d9344f677cf2dd77ee020644355bed1ea5188014fd66870ce3f9f165c224878818226d8051c6b74aa56fe0a275fa166115f96f2aedc28ec3e77c9209d89b322b299e1a5f41e3b8ceb805befab3160a94d35591091c5097bc36b611bb69b14662a1d434f2336902e8170819ab9f0e80c1038a19761388dcf798fe77791c788bfca9ddf2c4de1caa00923f23575d7cc34104ef3f794c45cc36ef13e07a942a06c21d2ea83546a0995ef1c9bf3526698cc1d9a12548a6e7ee313fb889a344c21640eb884756dd6b633be767bf3aaad79fd4471249ad6fc3ba4755d4c54edfff33f4a33a9581faf6ac0ab8b24db7a23e0f398a41793b525237a637fb97d6884515db1eba5767df919068d1ccbe6b611e649cc91fc732e8ef464202f53d2156896d4504402d5231d6644ac2ef2b6a0c26512b0eb0f7cf6da00447d6bae2a182a29737cf2c5e975fd1699736ea2fed12b75311f75f64ac982500e912b4c628f2d74fca9168e46f9583b27e542b4d0999010e48dad98435cb31d4e16f96402da2fed0daac46a318575440683b2575e18dc2044e9f26ce372ec9ffec27774e4132176ea3426b5c3645622256b973267a494d6ee1360ba80487a3cd0136ebbcf81baa119c5f3b39c2652dfc8a29d5e8910cc817a042bca5855cdc1ced46a35b4e396abdcb7c74ac3eea8717d86074f75fc67b1c2b9df9463eec18c0c221134e236c0fa563dd0b7fd4d1ef185485a600ee57aac1a7db2a6e55d1ab82b43aee59b6e80c454e07d0f4ed679c5ccac44a3cc9713137b7cede51c9d22665f9e6528324bd1ff671f48ed80951201e6b144e8dd12257bf324d4bd4f38109ce51719c17fe7671410a9d2904150d193d2003b33b5dbe74a5aaa826c7d632afcbe03376789f6174510cc5f034d3ada72dcf956c56af0d0a60a88a4a96f329a20b4dab95fab497938b702d382963ae86ec810f566440a9e1c9370e8054a29babccda89e96973bbbc0fb3611c0e2c944b14b07ae45c9a39f1c89bc2fc12b0055958ceb78be505da66ec4d6aa35a33b124c16b96494b3357d43a9eef67b0ebf021a40270299a4ecab97c8684fa9c1a0e284121ba2da07d59b3a3f031b38873829404bcd2596551eb15b0b7f8db4738d1854ede96200cb0f18a78894fde1fbb8ce76ca5beb255a009b9db3ff2340567c12e28d3eefd6a88bced3103af007bce223757690f1d5e92fb929bcb9b1680e8dbe39e3ab19c6de96fc36ba14d8e99f13aa36893a5b0fe3cc00725b72bf5cabb35df2f66abf60b810b5ddc663236333f1c2c44e8e7c23224b25b88fab76059c40dcbed30b31b2791befa77d8481cc7a56535fedb2a3ab125f6b4ac3cb22d85cbe7740c5b8d0906e9ca05679a30000edb38a556a35da707399ceec397c8815a6cbfcaa6ffda00e7a6b5af935b20839a97b834d530a71c2c46af3e2bc0fe3b6397f6a2e4461ce4a137f4feaf2a2dd09e952554dee6028871c6f4b1665c02e15765c490101d7a641971fca5ae7f493153e274511b7344a42eec2afbf0a2352b0ec84329df1559c3c649df2ca97988bdc0681c9b78fe3fa6947d9e87d8038c4f6482409087c9a78180ab6835efd8e66869d3dbf1804807a714e45e225850b415ca3ed0c5c0171793c403b94126473876bc59948afc1c82d28b51362b63378564f1c1662d8c1ee3c5424f2adbb90920deedd3dac5b23991000d1c0d68812a3ffdbb7f36923289bf0a8fa223b1f7c07b0cc20b373ba2b6d737629bff931781ec419663c6064b4ae5f3831f3f85e7e0978e80715dcf4def7fa81a044e0d2bfddaef3e890b0b54e331031888101a65f436a2f1187c70af62068835714e8a1bfd82f882ecae252a98f5cd1643978751cb0e0ef48218042b22561be8b25dc942a861b63150e96f0a4b24cee000bd3c6ba1eabd35d42f3c6a57c338110505577e153a3266571acd28ea27f3e1e0c7d30b84d373fcae14d5e4afb0b04edb0148b028f241e03b751ebaf648b0cbc3edc8cd736fa6ff79b89739167b95ef3e1cd766b69f9e6b3287104b87529a629a3ee17d2ed7cd148abda8cd2ced71843f616d1f9b72d2822c8210b1f207f031623a8f26f6b128259a03fce91ef18b56eed373b94cf7216ae9595f87d2e21fbf1134f4ac4ca45b1ff7cbfc3e979b6eb98de9a0a13472bfdc9bd97e7e6ea0342c6f485ca311e353ea257c7abf13a34bc4ae2e6dc48c069fb5c3368103ede0c6e8567c29beba7e36e58e89d4f41812d9af98f7c824dc7fe13430697c3ea827fbdfc037a11cc04029ee25a332db4bd1c3e7b71552f042c5b00ccb87b6db126abadbfc7814fcb6bde4857a0c1979955ce0980c1f3dd4e91178b3c7fe8d7a7c3acd50093bc616824967d3d6e32eb640f26d80fbbca4369e87190e0c2ea8f606ebf060abb4fccd26556394e48dfdaa0c423f5b5586fd5d9e882507f1a18794cf9179da7be310a5336cb14cebc6014942eb8221ab25454717b183cff907b810fcb1bd1c491911083a78971f2a62724994c8c548e5cd2d4826d643d1ed16ca32458ecd25276d12ca95cf22b1e365a388e0ac61090d76eddaebe3cdcd247627f7e7c078d6f04ab9ff11c11032021389e96a66bffade32dd4cd69e7bf2a06684893208279910d7024b92f9eb8f167aba2f0280c7b350104d92fd87496319f64e942dd42262cff0dd53b417882d7573d45b8eee0770219f9f4d72efd709ef1f1b317505ff4331b235dcafd774ffe2972fc0e20d60212df70e3da42378618a041c64f57dbac06ff72dd6c06134466124a2c54544e8bbdf59665da148af25ab9b2ed61cf159ce8b078a082d7a5cdecc14bb38df7eae1f141927deb0d5bb9b49b501caf4e503f55924a94811c629b824224c52355605d4a47cecef8f1b930b3979efcf5f8c0e37656bc2d9e9c25998557129b61c75914bc9683c9fb2ea05476fe093f1fbf330d1a9a8276389aa911c21ea7b8a40fe436425791b1a1cf3badd9aca6c2a3973810498d6d859c5c06b1e5bfbb1e2d321f515822d18912e227f9a10d07c053373720e4f62fc3333a532fa66bfb1075af5fb92d5f3eeea862541848044489683186c2c2e086bcd3e75762266f4b8b5e9dc220912cc188fd0215df177ac428de051a6b707af79c723b208e565277bc10b5249e8b02880e3f614c8168cb6cd09d41bb4bfad46e7afe746a0e4f44600675caee37f36581a987658c746d87756f24b46bb72292e26fa71efd7fa7cf695ee4892eb14ee9967d191da75cbc26118fe8c8da6adee1ac0a14cf900b1cd33bb8c34936a9626436ef497f8fd6280f0779b4d006edac9848c394a86439dff6076a791f4c82246c5c662d19c2c5984765af1480316c8e9d394d5c97eaf0cab9a0c58cdfb4f745490393731832665078c290f318304a452b5689cedff46d390e4e625366ce336ccc453e3a5cc37598637d796caa0aefee195a1c7e3b564047c57febc06a844ae1afe39ce411ccdff6e8f5e744b3bbad4b34477ad49456ef08187843af6fb5ee894c53155841b31018945b63765cbd03d332265307d01633c4e43d20961a3a254e81dec4ea4616105d65e41c1df3acceb0a6a74c94142c2ec4bb49ac9857263fb85663533c6a785153a8492bb3387fe89802baf2a33a70d9473a34b08300f58de362400decec0ea125f86b1fb7e52c3b9ae4e71d5259a762da3d79b6d37b810320ea1708474c382b7ecc9dd4b9656c925334e13aaec50bf00afec2fdda01368e72eb274e69a74c1c157a735a89a69730e3af22ff625c6e2d3cb92e4b08e7a1a208056289c0a7f82f8e27912a91f906854709cc9b361398c88b7e3c2a6626d208c84fdf2123821393dc28f306a344b741944c9dedb3d1a926157ba83fc71071ef4d73d1f15d5b342b943fe4de6ed8dd75f603513ffdd6d6754d87eb2d589cec2d538ef07fb04427dce11a22b8383d3bb6b8e42f9a0f3a984204922b85ad45967711374260e47a169ecf3f84354a3e880412cbff765d24c01d7a9798555b01aff9e9591a5500e735c071f8ac4f4438a94b423eb6c045e744b2e37852addd60f88cb3e56f7bb48350c6dbfcc3c4abca65da88841d786b34ee271ccd613caacc12d37aad650d5c06fef17deb96bde6c47be7250b0cef6e55d2badb7efd79366c5c6c8610f94889b6cca49251bf2031575b3057fe436d95031d71b03bc8acd8a8036c5f9e651d203af1c6c86e0d5c1696450b1ac10ebaa3e894f2783b19117b5ca128960290a860b4d2a3122860c6103c56f172a599f15a200c9b004643df6b9c94cc12dfb0320d11fc92bf9dc3188139d0f6e43f95d585f3676894eefb64247b344842dc9385746866f400cb9bd0dd8778e19a20015f7dc12b1d31a9109a619696cb6e44899308911224be39cf8217c13a1db9b87644adf7e5c3c49d52fd7071ffee7c511f3d60e5897ef9f5a8941e5ee0ab0e01fbb59d0c3ad98bd1ce511f863d0e5248ab16574735113e36486b8cba4d39f7b7c04a0db72fc29e8bc6bfdbc32690361f13012da293c9224c08dd5968d8d608cf5912fb60f4fb06ddae21c0d136fe6f39e405709418405a22dcb9edad080e23a94688e54045f4c248a7c951815ef6d47ec571a207ceadc235ca2b95dcca5a2a656952f9ec41cecd7f949e7d4bd492d30350163cf2cd52f01cb27b91cee88cb560999fd8a405a07de518b84833b6a31269042a968e7f22b879faa9ad672b266927e568477d4dc5af67239cc3fee80f00100a919b9d280f65f121e143902d4a7a68e86086257e8692247d7c71d755e36b2d12cdb1d3d491ebc2cb5f271f440b2e28e920b1a963043691f808e0e23204772bec358510f4e479f027dcb7a6ad1a942b361fd88d064c8e7b979d9a880012e8cff1fcea74c1ce2174ddea1d97772f054c0815d8e6699dc7c6c7a689441b2924111c91662aee4117d3472809cdacc2f919d1117853e010ee42d61c787920d3bad4fa535ae74811bb1d3e23640d7b8aab1ef26ca34b21698df4e60da3b18e58838a0809b87d7527587b9c12ff7ea9514282a662603cdbf5048511b42051f3aba6fb4334c380da598fdf6c29b054143e9e032ec7f21cda356a11ec39a6ec95956e0c5b0866319c6b2de10081c30e2a058a20d8479cb219983aedccf89f2b0aac1161de737077fa6cfde1051b48429a99609b867142056c388a1e05c716fa13193e18df80d599008f89074358a12103eb6b2450d2a8385f51eafa1acf1cd4f24f657acacf1b4c90edf8a7b8c25feda766a4e8f0a1db946e2782f681f14dac403a1add8046bf0d66da2c6857f7ac50f797e514e805c6cc0cb89857fbab3ed990bca521b308d9ee1494e8d136768f893e228af1193434fd0eaa65816608e97a51bb58659bbff81fa9a04ea4529a7b29f240ca4e20b0779dcd4a97701271cc9e72346d3873c7f7b2d14dd88a70fce82390a20dde72b7c3de946326b96d1c872e8a6234af73089856a061c212e3825eabae087c226e609c51b4e30e3535849f829d3dc37d8f6893f250ec1618a3b0cdb929daadcfe73fca9c07b28468ac73849e8b0f9f20b571e5566d8e8249b8f6be67c8bbae733735e2a613a4acf50b0245a3e451bfd4ee70ba32b9d9ea71ea75c21d6efee5890a8f49b5f758b9b5ebb5dfa96c339cbffbedc28d1f1581598b1f4fd0ae0c8e26d5189aa329cfdbf300abb784f0e5505694f8a2a9a417d73d2db67905b4f2a8b631697b53eb2cd33cc74e13f3f8e8ab2f68dec0f6a0c2a717c1c47af717c1b2594e8b510cf71b433e1496ed3b8f1d6e338efeec1b76f4222bf07ed713b02770ee444775968431f16907a9a097be4fc6915a1e6cb4db25f2f9ba4fd25540ad573ba7d3326ddc89c467cbe2581f26373af9b7830f061e57b0bcb92746426da73f7bca87f35502cfdb683178f8103083e7f529d1bd43ea850e4dddfe85a198ce9214e351834fe6d342bbeecc53b53d0210d0223c4146dcf29fe7cfb8a612d7ce4fbe14c80ae7709fb94bfae38c565fbffa5b758ff11529deb62ebbafed11d261ea72a550b61ef1d008607703b10e8648cc7d717e61fa63dbc9a008a52924c41159a4b3dfd2800002aef48abeb61dea5d473675ec437a29fc8cdf94903e62be53bd586dce9bcb05d6ccafe3f5e8037b76c32994827d8233f10950ce55436a7af0d3c4ed87dcd4f70b9a3e58965c120a4bdd6bbe838e0a523a8acf6977641837037d0ac87328aa0a30fe54774513f9e0400039a4e0e1b53dee249c05b09f54f46660fc9732fb35dbb3d1368d541c273eab8385215c67d78352b7baa78cc31c9c917ada031e2293e352706e3d85d82665066a7a3fb54f166d0b7baf481e4f745dd303000eef484a9f653febd1bb0f50d059d0f073de1fdd64135e37007f6ac4ab97806464c87835b41d730682484df2004bb058df60b01ce03b986c1f04e26ad1fbb345eb6f12d09bc87292f7e7b1362582e78e107a093fb1670b431b39b15192350b10e41794259d4f0783602cd5dd7b0b4927b446827f63f7e4e4fedb4ed31cbe355ec0f96965d79a4f63dbc957863b73db6217a0eb354630033496861763e1b4cae0d19d88697d783b16b1087a434def33613a5c6535f18933ac393ecd0d6bc0adac1d07503889521d42e8bc592bb1555287e019c7cf1c4efda09795931e39c124a16ce942802c8f92c662c8a8149b9c00b61f12e5a8fb1f45cfcc644f2091c7fbdb674743e66d80cb823e684c8765e7742798d711c25e08fa756624e226741e8bd3aae25c7e75b80f6cc925b679ec3769f1df97ce9a6a3e268935d7884ff8f15b3d0e3b32c4993c7800acfee32bd7c97f6aa39ccfc38924bdeba66ebf0ac45f44abac53d1da7a92f3006cf166728cbd02acab83bc54526a8b63224e44449cdb57a50a68e68ad2d95de8858c702a870efee4ca04c9660e8a1395d31bd61b7807b3b8e9b6680f02ae08f49610f0b45c9db1fb55070eeca72061d40be1f5c28380c675f5ef7c7735ecf45da035da713dc4dc07866e96907d71d59ce01ac542e7237959a1c5d57e0f0285ac8afa8269f4dfb87060cf5fb60aa58608586f4eb689b65d382db989dd815bd482ed32b4b51e57fca74c97e4609b6c4f193a2b889dc4de6d52c8da5b94f83d42986055dd605faf5c80f6d9c71462c5b8a19fc8a44f3fee4f96dd38760b47eb678b5b4af71c58650577754c5e5d632c2b79529bba35fcbddc704134d7092db5765ac28aba610240f28afa988f1635c0c525dabfe584cdaf05232baad7d382d6b6414c9f522c0a0249238a0cc09a312a78e8a7e70c1344bb114ef5b7a73d92c001b73ed5ec9f96da31c75f651deef64f951541c804093c51af4967f7883211440fabb856d027b6bc7ebff674f12b096b4f7c3f6c964f8e1ba473ed1f56060c7c2b7f8256689dd39c63cca4d65ef83fb1675e875923932e0743e3f05691b62f6ddd28711dddec647467f0aeeb914cc2892aafa493b8c433c06b54675fb3fb4acb306677bff782c6d5b431f91059f078e5e065e92663fefbe84ccd372787011e73b986c246d88759ebbee2e32f3a73944ef413c411b61b6bb8cabbe90c07ecd62ae3a3057d282b9489bc2b1066476ff4bf4e75d1ddeb3b3295442e2d54151bbe8764cc9faacfb81835078c8f9bfdb1a63b6479ce722a965deecef651ed6a430e6f1a540ffb7b2c491fb575347b3719742773779931c3a5d16bc561340e102f63161939ac915f4e08cd5836e37a179579da0ff7a15575b7464dd53f3e9cd9132ae9478d108866522e3e9da4fc39b02ea297b439055745e8e0be4e979b7fa50a8034260152bd04a4e29db5b67724ceb5ced48a6be427dbe3281552ffe7689b3eee55140e18c09fe5e82d9b07560b41b24b21ce1d54a13304321025f9f177852baa5f7b5a91a12415ae1772c63c2bfd825e5df4f51913fabff7818b07179c86af43c314b3af6a4e72cf3626647363bc4b153eb97856356964c9ef6ff2a8b3536c2ce94c16b6a04a3e1ac58f6155006f0525ae63268263dafd55bdb325fcf9f666819a883e07a46cec51e3642c285caf7a1b0e92250c7db0332dde366f2108ff8b24ae09cf81a8b7d1f99bc24c16a2f999e4cef406f788d996d4914c5821bd15478d997ddb10dd34085e3370579f4bd9c0503e8ac6126f74ede289dc82adfe116a35a71c91d990f039562c1bf0de644989e369f91b1897e2ace6c272258a29a075b64f0da25763286c0c830da57f8f672eac7ed5d089c46d5931b982b5660d6c6bb8857c850e43cb2d08b5bac37dd00132e5104d2108814358d5545e2c5247b07895cdc91bb260beae525fc9c076cbb93a5f6b547b5ae530ed23e0a0f78ef2a0eb182bc29754cedfce298bf3a2d590818004b8bae5533d73bda7d1d3445a46f7aca3cfe71d522ac15c5d09c285ac85cff3538f892ef6167b36c9ae6b7a3d7a26b0f5a29965a586fc66f545bd0152c2b15eb8da0fa550bf5e93e2adb0554d3c6dcea9d9d30f8da42963dc8a131aaabe8c74014d8f63344715223251213edcfab31cd832ecdfeadd05b6227d62e45feddebfc0be30204016c3921c1ead77a54ee61179f228e69c621c8c788e56e8615b3a4e50aafc06d461fc8bf3cb31c0b1828ded931f2b19441efecdc7fc3b6fd190a87e8be7ca2a442e48dc48107852730622016a129ead50c225bda268e0cb49a4cf6e9f0bd31066614066bfc21b097599732a01d6015b9414bbb0b6742d83990d400d43752a3f45fe84cfd3f21564d9665773a25b2127be3ae397d832175c039b5303b615eaea0f32d8cae7bb31c518b7d119d429dc6ba5329accd5cfe2503126aa7fadc17101a99f8fcf52279caff58f7f4e46e0a9a5c600017bef2d3a95055feb082070b2f28dd261f868d855f8506d156ef8b6a506d2081e943b396d3e8f8e5f0b5f7259f07c0176499eaf802c45c2e4a1763bd442c90980b06b88c621d14e819cc1a5c54374a5a06bae1237417d60fe9e51a269eb5a83dd0524881b3a140c903c5eee7fc066524c08d29431d4d33cdb68bb8e7e6b11311e00ca7968347a355fdba051d0747fc4e961471e5b2b4ac18705ae1227291b0c6477b41b90d0584f08ed93b9b9dcb069be5ed1870a22587790a388bb8d35fc000b7ecdf6c3802ad5029bf598ebb9ade1ce4169516f4d6371b7a1512f30d25420be513b81c5a07f38d2b3347ba998261b608227b227cab74b70288a7aeee61f0041e79d41bdf44fee1405fb1c7369c0ee7a0b5fc124c56492e9d5cb4c396fdf69fe0b7477f0669fdd6a2fd5de59491ab4086406db65ae6e1780b096f09272d71237179629ee69d9b5bb67cffc056950305ef9f7cf4d3fa85b74f450cee6e8282938fb1d8fb438c1c99303948f846bbccced8b0a01657252c1456d1dbda2286fe1891db139f1fe7f2ea92c38a5b18f67f57ab828fe58188af50d9981aac09b4144c33d60b142ba4217f03a32f1e634075a883b076a186c5ec4354e34aa171b0061b52d5ca8b3c7edaa8111b90e2cbab2983182288441181802ec2cd3557a991ecc2ebce944e2576888d0b80c41915538ac040a4dbf0c822828210174c29f280f3de00b68dc97bc1379bc3f849dd1ff116e6f7375063dde4fab887312ebf52b4bc4051a010fce27f8777945eaef5edbbb8d84247602a64f8243ad3d83dea55e9be92d322d0d9f3e86686175f785c916860d43612a9fc9e9f3eafca5c0fa11af0c70d44921d725c37aa1908000f970fa09d90ea9dda8934f98f7872931dc7c9d307081acaa4ecf6b77b7e868a72bffd07a446dff5a9bcbb2b75087015d56e17916f1ad3326ce1082b1dc7ed7a50962d8ca928506be6d96a9e444e7e054e16c42d3808dfdf53384485c5ca1f5ffe14b45b4eabf0ceeb53f9210e4aa8c145a150360256c64663d9ea5f9c695e6699cb393eafabfa2c3a52237479c11b4b1055079f19d386d0e0b222fec1d0870827424c2a88831c48f5d1b619eb118c22353f9b355b453993c073e68cbaabdf4859a7c5529237b45ce27dab0e794053b1ab60db7a5198f13006a87807233406dabbb455d8e00436d9752ca48d72f6c73ff12d680a27d06c085988ffda3c3dee9087884cb70674c7c87c25ebdb279e0d8d588bdde39fc9cd1b5bd92c13e5f84e24a739797a348f5113e2d96e537be05957b061674f191222a8447a97286400551e3d2a0a88100038b20f5d4540baf70fdede1014267a0f79bfac9f20b3519e23530a28c3b6cf1a1c7cf03ddc569dec93b225a03ec07d01c249de07a6bdca1a1b2ef233043c52ac15229b427ce25125dd71d98071c2f0b71f86b24772b94f30f611dd526518a291272cf79767445cdfece785c0b8237388fa8337746a560985d72e213b1a47037cf3e56d7db7420495962d31bcfeedab2ffa821c28c955df4acc9cf2a7e05e10c62d0d72c8f3d37ca3bdcc90712c04f115a3f9bd1308e1537a285a6f3e68b18207fbf037b6fab5d8d7aacfa1381f045cc6a66c0af330c5fea95df3e54d5b343019d6de79ad49a98f6d5c4a73b66dcbe6721b2da4a89535822eb41451cab80cce226a285e20d9b9f39c0f6123b282232c99dc7544b9399547f036ca610f77e11f53091a29e55c06f0f9d9808d051eefb549dcc06bd44a9915742a90d4b14b00ade6198031977674de7afefff062cb158146d35509bc94ca6d9eb63e1d521ba02413472e15275dc2eec9aab8654c1e827c4270ee980780a3fbab14014b7a66b0cfe4696fea18803b84cdf5ec53aae493487b05b5217e237936e837ef0381b2109a707409d3580a292cba24e63e259cf86b024767b6ba91e09fe75eabfd47dc18912f304316910060d66167dae3cdb673c22f0f60b789ca5a0a8a0df493cf77503e0c450ffc74266c5e6f1a9bca2f4b5c872b90e721d13649926778ba915ed041e3e4b8ef9a615fd743a68df9ee0751bbed757ac0a69b8db26601118ef57173b63475e74bbeb4091de898164b159137f08d8ee796b98f08292bcd27af91cdfff57374ec1e13df9c10861ac74704b3cf3b1a137d19db707166222c2b9f57ec8b7aba28caaa09f5d4892604de2cb7838e9201976df82af375c4bb1703eaa49dd1125f7b2d67734df7a4604541745dacfcb60bfca2baa8a97180ccc8b71d6c62fcf786af96469495ebb76ec1326be3afc54c6ac8c659a6dff7cf316cc3d3c14a84cccc2acab1deeb1302aac6b1401855233f6f1b6887d40207876f0fb1115345ca6fcd6eedeb53d22455a6cc211a6a35fbc0bb0e09b5396c09aba5d6ce618815948eec10d866b452cc2562e0a621959cac1149d98f8ae83fe8c36949203656a849bc6c7a31164dc50d385e68e547b3f8d3a2559bbad42378f13d7d73ed6dd2f716f7dd74a96c2a3bc75e2b1128a1a344fb34631f7271fb16d0935d49a449536d6fd7f69261b31d1943c12520d323e441a7f4fec0ccce7dfb10e58e61951039372a70be2f58e82bd94778fa2bef9ba30abee53c488b6c9567b7bcb6cb317bb0fcd88ff1f8957f722c17aa071ff80af808c4e9bd1c00313caf9c367e9812a55bb328e4fc662742313914e94a79052fb0a8b4cf899f551408737f11971ad666edc2a1eae31b8ffbf34d95e5d16a2a997a5885e6ad5b4b68a68329317ab991ab3d8dfb39429fdc670af012f9aeaf936bb326adda3628ea18532fb95794bf36ebd3615c71411e6fc97dd922c16f9000a69c7cf0ad038b0e85a7870ab7bd1e7d94a0b5d5139683359b5f9d9f80022d9cc28bfcd01a3fa80fa1d2c75f1f092210dd1419a8eeb4f465add88de50b6fab7df4bb746c32523d9579eb6619a2ae64f24a7c2958e2ef679dd830cbd56eac044552dc47e1f553c65d676436c90f8cfbf81356d0657d1513051fe26862837e7fc88e8de41daabb64d8d7f7ed5085a41cddf7a5f89a537dc293897ae4cac6754f167198575b698aafed1e894da816b1e4f535169e7c9d1d745a188580dee3b96e374ff7716a44d0ed0d8fe6bb784eddd8d65d6315843299a50926ed74cdb4fb246f7c977a1dd227a02bdf12da9f81aee4694eecc5464c27d6cabed7757ab2138db17b3efcb7ff83bd63ebc10a704025f94790ce282508e5b015bbcc6a6c70d3eb680ee67b69bd4af2e18727b5db0b5b842d1cd8e81a4a0d5ea25a01540029ddd88179662dddf595de28eee4fe007e3fb26804ee766ce7081c5a28062e1a89e06e3fb49480378749fc7eccddddd1aabfa87e0b625ef8942680e0cdad5578a9b2cb8c0b10ed4c69e3994ac06344ab201b79e768f69803196d0df6b48ec72d951ce0c2f4acd922289ff2a844acf843a2748100c68d2cc25f5a86cfd65315bb5f10258cf9afa8cf3748523270d5454e29ff4321fb6457f79885ddd91f3b82a0280420c828bfb4788f1a5b92270282d1d89db32b9e04c91531052bce7562129941ff9bd53b7f0c355b9d0874990cbe7bed5255f0471faf59a6b1248c4451e58e672883acedd083c9713a217893d2f1aea172fbe949b064c14a080de85e66cf84da96da86b13eb52680161c99e300a96404637bd483d779d0cf6218435a31a60ff738207577218e071711be99e93e3e880dee8e71190f59355125832e0ea52c20a6326e72383b344ca6ddeeb8ee2567da4175af6ae1b699d02587c8dd5e6fd13aa3760f16ed741600f8ad49fce5382c6cde8f662758281f880432ab43ca280e9b44b307d7b6067a1cf3dbbce243491957a61749288081c69bd8fc8cb70db1b99564fc48b0482d04e8f027a1428976f18ed5f8246275455ed3f2335764b8be5e2771f510c6b191eac66caf73a2b284d410f06df60033f988d72166f90bfc16d7bd2f4c220aeb1e47d187134fc1806933814c55972faf8900440b025e2eaa099181bd81565828b6d44c8eafc1360faf81cb408d2b2c303716f9f6516c6b69fb90be99c6349df26555cc931169776c336364daef7240a3b95ffc01c480006fadf3186aad907832777986ff51c50dd3c3eb4af89e6268a2365e35bc75b620664e60b33fbc9bdbbb1281c37f3f6a635eba09c9b94f10df3edbcaac4231a3f40044b1333c3f5fa347ba3904b2989c015a14c0f46e06948fa3dd28744a9237792c9b03cc457fdbdfd25bf2b6b4719f00a53f75e035f6b8ba1cb2ca6bf8d08b733c371eb4077c422b0305f38662a06a4c155e73170c8fca1d0adecdc3aec8b69bad2de514477ee78ba3e59bef0963470de202d9443fc87c7828b235e7bcd8e81024e729a555d76999f5bf698a4ad542a754d3789c3074c4e87359b7cce63ecdf249781304a0a6485559b06debf856b0d1c98600482c95aa71f8cc511e7ff885bafdd90ebb140cf59749ebdf9e2fa14a8586d922f57331907f147d89a6d7cb3af494ddedd7fd9a1abb563a54bf41c78ccc3f8e8b334948d779d393cc2822e714080217b5703ee88cde0824b424560d3f26248c6a08acb3c0a47eeb9e7dc7294027d0b5acf749ced6bd72e740d2224257362fd101b0b9a7b5f975c01eb4f710f3d41ed19cc9c9a372c470cf15b4daa2d735b698e8392206b107795772dc7c1823a0d5bc2de646898d0c31e907f3692da2cb730f4dad9137308297386b2e3b769ef7fd9bc89c034dcd2b86f01e563d6b92365c9f1186aec64d4cef0e9b07b146d1b1eaf33c30efba9ca1a97dd970a5436b67b214fb8e39ecaacdac4fbe732c743588f8623ccd2032d2daf6101df9d3bcff27ebec2bed6c28858f1285b59d6d5112c139271c12b4ff4961fd3be7cc07168edb3f039cb15f878eab12f0184b8c20d239f1675b212096ce8248d1cf103437b108f7c5690e344ef3dcba6a5a4f59322be1c6480359c01480552dd666b8b4f3d639861c1fc81534486f52e329665bc6fa401a59bb3a223405b3e8b4dc8d5b1bfc8b90e16dcedc6fd7478614c38e8850ea5991d1259614a63056947167d6770bd29c219471ce12209900288fa05545d862629a7146aeb1e84c8b30f081b13ba21896a37352fb67e7626acc9c6d8127ddf64a8af06b7c8f57be9776c799f6ac49a8bad915bef5ee0e264b2dc25e02a86339fcc1cfbbdf6365293c76fdbb278d412354da3b3da8201d66908e58501f324f67be0ec3a78601ef11a9d8b9f3523a9e8f83c73b3034c21182fceb5daa0b0d58c3bd5b1aa1cbb74540d965d6c82a16e4a91a4534528e03fb91843adf60d5e9a78bf39961b15219667a63225b457ca643ed0b962ec6f66173ca1d9928f549afa6f790da2a8d8eba991da960880650c367122c037fe7732fa59e9948ceac8cbf3283c63f92bd1a06cd0e00bcd0eace5cc44a3d43f526bd953b7cbea046686d581d982ae4616f46078e0e127a4f7438b4eec9f17b15d5b1975b468bb27acf886fbfc9b46cfadf356d60a5e58e3764003a238ba5c4303f24b345fbeebcd39e96e33a108e4060f9571754264b66da7c97c6bf36ff92e1d2fcebf227392e9b9b6bb12fff0c5d360e1377a7c149e4d9f3b9108efba754ae92d4cabf84b8e68c2e4cc81840da812e54fb3a22df300ba33bcccf1af32b80d8d9f4da74d649f461a2dbe6fa8d234997bea06f14dc905952c9171dd845d1e2c1f6c8483d3215ae6c31030227ac0cac9e470b58a68e1f757e50bf5ea69b240851abc51f60c7cc4f30461389514384f34cec6a59627c579c639783593e9681efa6d2d6285f784fbeee6eeec258315ff81a5f10a5422365325ce25a1618f7193131825345ed606c82960237ead9df1c35d06b8cbec7f058e0fd5b602c36be51bdc5b4ff3ddf47ddd27e7182f5e96337c7eb0b2ede47e6e9b1803c239b7e8056e39ab18b9aa05fa3b9776c47680d0f3671d5446aa3b98d5562829faf9483af093231817d5f6677b1532e0c9b8f54c70969fc52133332d4e78a02ad04505c2d05d02cb2db17eeff21cc8a86c9718ea98c6af296c63d1cb879516a18c458eff4d3c0bef8ee790e980f5b9140abb1c8b58cd8f97d7a446d45b126b12e884dbb33cad9d759c20934fdb1ac5d6eb007034155861c68663595ea74e119b7bf738985f0befa3bc39bc9047409d25662c6cd09aa9977b75edcc43b595971ac02f7ae106609fa672738bfde69115249473dbf5537a73af3b51306df17a291da0941dbc0c7fa4dc4c6c129e8ade4e8fb0f0ca88b85e38eee12cb8dea3ed66c1fa0d2294ae55fb56d6fd72524576fef528d555d32498512a951b96b2f69c1a68312e20fe1eae5662111798a120bfb241c590c9b7f947edac2b2492c96446a6f90d5bf74fdee655aa457818913f1c366d4682d5e19bfdaf9411a34fe6561151e5d29c900ba470849476ddfa1d06f49f133e1b4f0b784342f02f3098488d3016591f33fb4d18a2f56cf35988097c31a696f980d1c03ea13699af5b29fa4fda95610559e63af52f15d41ea702dcfbb1bd7f86496177a0e24238ea583f4a355721a827e88bb0d712074cde425baed6c63898a141b7603816a2f06e30c2c2fc1ff029107eb16c3c021dce802f17f73ade457656994f5f8a243860bd540dc03e68092c99107f7bda57357e0cf217990c899b992cbfa31dc8e91219e1e97f6a8b354d5770fce923af334036735c09ee8aca57b946d92a239ee1d65faf2c0444e4eb488bc67bc5d5c7040cfe948d6cacf9020db9a4cad44091a45fb965804859247c225e37c5c4de2d8c3e29213705a819d8c9efddfba8fb3ce2531a1f0dc5c17691682f99c0b8f9803c837da8034b4982f158047df542d59005533dc9c7bdd670ec7cd44dd8e83a856c186c43623f6dc56a1177af7e62e083714ec41f8e594d04e80ee72da3d733fc25e9e401d4a3e8bba64d3dad4ebed4fa9f9baa5bbc065030cb15801a5560b41e6505a4128bcb7f53b1f717e85c24922c97bd243be085a9a34950c2fe62756e1f905cf4ae0b71e7173430bcc6a152a8f7aa2eea7a175e33ef65b56066c5826baabba4fca0e50d23fe3841ffe0ddeb0380587113fd86239a8cbc3ce55b0993828cb112c32b850ab0d64702cb2deff1751a75af3d87b806d2553ce0c6ad0f5442e9f465a76eecd40b91f23c313ffd6269ff2aaf5ee71f12951471396015faf78553f8f2b3978f0de2b2d224532fce2fb54c7aa524fc95e52f0d3cb4d5eeffe4e8a1b8262fc93a6fe15f36f01ef40a6976c677313c0e4819a72872630bd37d4566405d6714330f079186c92b5124d624df9889aee1663c6587c7458ae571c2dfe1b0da63d056ba2b1871eb8f17d5032aedd440a3e2fd330aefeff0301d0d13817ec6d24f85119036e7c3003687ac5772ee2c66f1ee45a2208d7252b1aadc02bc9e87211b2fddb1c7c3410a7b0fe0ce89462f8b5ae410b9ab1a170e64cd90e1c99cfac72bc0df77692a428f524ce4b5cab98013734632dfd244b73fb0852b96113fe3e080fe11eb9c2b25910067f6d29932bcbf083aaeabf476c89534d89fa346886fc59150d19edfc4798fbfd99e1ca4181a610f48addfb2c70b997e5db018eb4be975b2400b44988775e9480124627999a8c46024fdb9eaaad13b8e8c34e544f66eb46d948bd8b6cf8684f94431429b80d3dd686996c0efbdf04ceed69bc7b830c632f4f02054c4b169b62c27337b37b2898e372954a51a56a4ea00705d3804c77cab6cb94ce6b0897ba36e7af0fb8508dec910eb958fe484f29530e559c437dd368c2d1a4c66c0c8001be1afdec39bf6b8a00d5633a4e3dbb663b132743f51a54b8023b4df6fe4b4ec6f1d78cd1b9d5c1206eed0f290e0b932be297cec4f15ff832a6a82a925c2a2f6f6cb268dc53e260993ddc1e49652ebbd1e9ef9b2435023a99e1535d404ec24a07bb1ef12840c9cbe94ec0fec770f73e862fc40978bd8525d2f315e9a8ec27d136489932ec808f1bc459d73fb2e1cd099c0b232ce0bfe85278278072095e212c4586a6386d3648d514af699c1f1111bbf7de5bcabda59429054905cd04e804da0a9e26a456094597fda5a1463fb1a225cfcf99d9c4383249951b460589ac896eca76b14086245060058b217c6613793e88c3a4b283005a0b2f6082c0620556b28cc067d2406585232a6c8a134c4a1839230970d88187226010025812a562812f498aac50240b93213ef2d2301d7497472aaf1c3d606b6ed894a71ce65c7167f0ac65793afc3c2587ab083a9592c406af2a5894c00727478ce021030c5b884811c30b2b3441a3d4b630295dbe8bff52059896f6590fb9b2bc617bca8bab2cca98ecae1c61525cb45faa4022a4d07b92fbbb5159c813cf255dbf77bb67008bd2e5294a981b7e5e17a2fc905264045b1a025b4222c415589420b0284cc0a22c592205cc891030275c604ea83c21ca0d73f225873839733226e37cf639d9afb297f15127cbe2877383a559768ba2953cf18894dddd07d032c4912c4f9000510311174ca01c511e4266683ac2b285891a7c7042a44c6bb5cba1b7048a51b5e2ba00739243ee72c39cf870c19872cdb02844b9614b4a396c25d8d295ac752b51245a392a89b0a71ceb1742ad2d43fbfe1c7f957ddaad626cc396d02187f2031166eb46ed33dc8aabec79893c314fcc6a2f3f5ce5d8dd5de3bfd8a711e74b29a5d642d739e164723b79ba5d1e499664e2d170b87a1c824c92d56af1ac561e933a12046d6879f8c40fdda83d7057ee1cfab386901c4ba3de911f7d2ccdcfc0e5f22177f9d4903af2a509346bf1dd85834088784797c9f14fa039e290c70b994686dde523c564ffde22471bb10707ac4aaf3acb1e1c302cbdea1c368c8314d816933b138971520688163628f9482282a862e98a0f449e10e143e348122b4e10664aec071fd983902e84f4f014060c163e32ca0b3fa06c71050e2d44f191bf922c1e39ac90a5062558c45c19810f008e88810a8cc8e2c21655f8482db12078a821872725627ce4d790ac9e4cd4892ea9c39bec302845b0273f6e0baa963d02064588ca0a6bc1950c9a1bd6c44a9326395c65d9838a8c0a2a925021a557c49431fb89ace9d34d7e28f1905b7e956f614d86c8524a5ca7b061d481351992676e589320399c7906293cfb27fa092522257c404193947ea0a2788200b96149b8206bb9614944d105538c52fe942f33a667923a5a42ada61697f8ddb9e46e0a3705a41f0e378a3af7f37fb89ff1c77b89877cc65aa943ff51ed4314530fe3ed8b8ffeddfff877ddfb6ff8077c141e423d8873481dea788847ead04f3d0c3c84f453c3978628fd1798febcdcccfde673ff339fc34348db0fd99fbfe11ff91ec7453ce42eece3fb888766bea54e7d9a6f1faff9f6b151d9dc21a41c2a3ce43ef6535f73534801e9a7c3ce7d0add737002ab7d66b11b913a943e072cb05a0a434813689fecb56f1f0ea7907dfb846e24d3f9a82b9f234a62499e11729343fb09b48ff614709fec398ae2212305a2ab434839340d0fb9cffced224d80870f0bf2e7c7d730056e24a0e1ec8bdc47fbf83f191e62c17db2e7b0fb74d17bc639e3336f8e4b82d4a92f6f4b9d58fdcb4154c309fb3c52a7be7ce77e7efd8935e2a1779ea15a9fc3317e68fddb477ec8a37df69d537150c7a4979ff254be89f99df32567b02531108284fcebd70cd6ea9e4be072c53afce56f37381d5ceeed12218532dc73c39680c9599090a418b0210f98b5624f58eb4603b6a44b9658c76c4272e5c61dc5b8f65272fab1b4282c09a249df0a0c892cf2961bb6e4884c475f291720f76faffd4fc53fdb6b1bfe0dfc685f35ec1abd43483f5287c6e93e71dec0d90b905bbaf41967e2cb1bbc823938e0c398848131617a2d547b4aa9867168d69ad4af015392b47d27d5cff2e586290922a372c39404a131639af33319b339bf9a90e49d2451f2e388a2dc34b0a4984acbf3c339e4c9605c71146754c78c1c943d9df78995445c28a09c2d5bf33577285a83eca75e3e88e369d3d6bc7d8eabf19ebb35f7878395bd1a2cd4cadf7bcf5d6bbfe370d84bad9cfa70464efd2af6d47cea5fb26c3e05e6d457c98af1a96fc97af1a997c17dea63dc963ae06da903e3b6545dee5fd8dc973adcd7dc555c71f653d73ecd0d857aa93f10388bc1b8e21e75655c9eaba395bd0eabe28afb2a591f75b8df7ecec81ca6efa83859e83cd6146b8a35c59ab25853966559ac3a0ebd299627fdee9bdc999a629e35c532a6a6d8515396d13c0d2661a32f25873d239a99e2e89263e213c51e26ae26c51e8babf94f334ca7d3f4541ca2bebd9f79fa95d6a751714ff3dfa76ef7ce17724fbfcfe2504895eb7f8f3fec6d9d2a149a59973c7f0665effd9679db36370cfa77ddbd7f64d17ca54f73eb9d1bd849ef0b812341b2686ecfb8aa1a04b8d7fcbbbb3dbdda677772381d63ac29d6146b8a35c59a50119545d7f497e4cf0dbb12a453a9542af56d5f9b344a467966258ac3f9f4e70782c5daa545747e20d0f9816071c823a7344d33ea66ecd18a9aba12ed2ea76eea469729d630fd2b63ae50c12204961e577ee418b96155b8c89df4b22273880c128f3c4fea45ce80ce937e06339cd98438832755fd0c69defbd0fef78dba5ecdeba0f094a8afbd13c3eda74e447db4a98fde7f978323157f1fbf1082b44fe1d033eaadf7a1874323acf6df87333d3c0667000a07cdbcfd191ca47d96adcf6459c31e8377e013f7676305eada534a29ed3265ca90396166c741d96fd9b45eb65748d1fbd47c77f5ebf4ed1552636bb69fb936844dcb66be46acb944489f3773e7ad43d82806ef606a7887cc19b631a50301ba566b1d0fc0122165b8c69517157085d8f84a6ab596e3b2e45a47469633e24a3ecf0562b92c5f06cf8c160d01d8f8fe407410f09c63e5db04005f212d03bbe767599681e0db78a3e19a6d0eebfd809d4d1d006ccf6e83e9598847ac81759d0ec7f6d4a6d4665c696fc196775e229aa6691aa5f8c666af391929a594524a695520cecd6ef42c032daa94b3f35b30477e11d3938e77788ebf0197120bd15ec6b0523a087806531d0cdb535e7f616389901ccf1a15107baac420f7ec3af6c48e5d06aa84a9c9a995660608a67eba582bb69608c967c4ec8b807c4ab50f877c3abdbf10220e5d774d45c08a2e71d5d9a3f2943d5f13d8d066dac5c6b2bfea51affbc3be321cc1f802ac7fbb0a3a0781f5a8576f46bf1bf3c674b94e885a77071eb3e36d73ce338e21eebb212340e3e7d51362d6426f8945e7dfaedcb0265c3944e56ec247eeef868651b646ec9c7e4037b3c3cbe4bedde7d9fed6ba8eb33333931249939a4b2d6e9608a93ef79bf695ab9493bfddc8f79c79e9aa25429ad9e36ec69e9bf89e85f4c4d4ca27427215dc13d0b36af55571555fcbee256243cfb3b5665ab661cdd22742cabe166559d69dc595eafe60432f672ad0ef063ccf89854cfbaf0e029ee50dadd22537ac4a906c80dcb02a42641b5721d8f522594adfaa409c2259de0d78ce3ec342ba4e9221298355f191fb9b1d8e960889f3b8cfbb477bee3ddb2f7befc6ef2e97511cf4318bcead7cff0667e49133f7ec548b3102f1e56b1f0e0ce08bfa6e7c204c8b6bb83d588f455de7cc88af0fd7072965aceb9c560e5bd9ab8cb493bd8406189238112186892584f8810a268eb4e0062658885ca8082ee357a76e5e4d8bf2032152ea5a36a78c3d9ea726a5c4937a7645d7d76bc839b36c3a0ad93bfbaeeb761054c8334c0e4d70f718bd3bc6efd9d46528d5e4bc0ce6c40f5816250fe34cde41fc383de631ed8806dfef57246b83d8b32476bf23893d31be93628f0e9e1cd4bdeafbbd4b7b90c1f70fc5ee4721f6bcf87e1224ab3a81dfa86f1dbd9483505f438547b27c0927b26a8e0c9aefe7ae336d705d83eb4b52c7dfe6fbdd87ff30721dfc28f6c0f87e0fa2c279d50d6fbee36ec8d3bdccb67b9cbbe3e60af1648b3d29aebcc39ee47a97def18f711dc98b96ea6dab354482f720ca7981c376c26163145e5c2d83d78bc495d7a0be06858a4396befe8be08fc396b5115736b148cea5c2722f59383932380c5e2dd35c2d7730ae8e7eca41f5370e48b22e103914f726b407307a6c9efb0f62cf8be7de866469f0dce790ac0d9efb0ac41e1aee5baa648d04650cc9490e87e6ba91abc1f523a943f32d1c1cb6785619e070f531f0922fd5e0d056554d4d0d0e6b3e8c3edc49efc47f715d87eb46d7fb9abbca34d773fc115917883ac41e0d58dbb9a7ba335f736ba6a9405cc54fd54f5d1bf60dbb4b68419e1c461fd58f86b891fbf01f3731d574abdc2ad78a13b9567e00fd07551120d95f5504c8e572b95c2ebd73992ed3651ad24c72e6ad6e1bae97832376fbba6d31e6d83493737e907fe4513d0ad1dd29acff9da205e62cbc514077103f8c91939d6addd26cd5f8c0c61521aebc1bb2720c2d38bbaeebba6e66ff3a7bc18bf3546bd5f5d24369491224dd59c69443842c495ccda43128cc227135bbbbdb8810edc4465c4d296c8893271857731a7d7162caf3c88ddc29889be56eb1aab7a119c1f3bcef6b6c50dff7d6f3666666beafb1f9cf7236a8bbc3bbf7a0ae0b81b217515dc7715cf7379f053db0e3de3bef61bc98f9ed7637cc91bb6de3800ad97b9adfb6df60bc37c3792a8e5361306f4033df73df75dc0365efe30c0e8540efd260ae7b475214d6016e9789c9875801cad2f170b15c2db02daeded9be7b07a62506d3e2d45d2eb7cac5f279df77432130de38c2cca7baf780b2070265f95d67700a7b287c83db70fc0f87df8743a02c04aa2c07026594eac5871b58ffcb91e56fffdd10287fa0ad5f238640f5bf0ff5a064796fd9ab806c3430db0881f90b2bfd9ad7be4f754302e4ecebadaaeffbb01068e3935fc71359adfc7d0fcaeffb7ab36d55ca1b15ec1dd046e56949f91de87df51ba12a659edc422068a377fa5b014503a3772c8cf7d6912374ef8425a8ac55d1dc2bf74aad5fc395ada84ff5ce0de7fdcd76b38df07df77293f246efc8afdfbdd3bdd33b40badf34faddc67567b9f91a7dd7f08d89bbb9be92e3f438a5ede7f777bb77384dd3fe0609bdd35f42eff4d4708ddee9cf70ef4c9edee9ffcc337bc7f64ee73576b750307b9e6729a54060ea6d9ea6a6a60628d37c0a87402008e36e0173cddd52530303468d4a150304ef169a1b0201e550080473900a044110ec9dd4833c4038315c2da9abe56ab95a46b0f6c5ab70f78efd17b7777a07053da3936ac5e18c5c371a1b8d8dc64663a3b15d17d8ac769665af853504311dedd4755d4fd161a1767a6a27a71c7ad1b5c276b43ec7b5d35396b8c678c49d72d0fc2cbbee9401f7b78bdd38ec94c3ecb5e950b4a755d3bed63abbae622abb8f3222cb3e7dc9423d7d7b65449d8a6b45e15f75b56ad8c3737a512a05cd3f21cb3ee7f4a71c87779090fdfd7635e7394addbd843d2e4f12c5eb734da9c2c5b88d2a8808d2848b939718ea0c5e68bed80c494d5650c2045d7817615e5c025c4e1c487321e2e5398cc00616bc5c3d851233b80e557415f7800f658870e1f2241c9737692596f00f2cc0e522e8245a78f26ae27582282f5a009723d14c4cd16338701d79bde095840745cbe54f806926545eee8286628baba05de0247841e30222069699262e394051c30c30323d9a943625b46012822846b6ecd0a3cb2b621153f4812ab8c0e28a18199a58b0405c6078f8a1072bc2fcd0e525c58ecb8557149a1399d7133cc8702d7981794549b9cabc3a70657102c10d8e14c409ecaa32948092830f263ea6a82086252830517e446105882546ca7c91f242f2da781c19e14296a7228074e101189a5640e38029882841c68b101717ae1faf23925e59903982e1d5e5bc0b8bbea20a0baf03482f4ebc2811f112868b22b5e8041d4f529628483e20439025a521af4d7371b1f26a80070610806b4844d570c58b01ae1f1c05312c7c8766d2f2721fadc45f0070b916ae76e20d832da0f80b57665c7d840c38b8104df4861988d8904390275ac8bc3004b70193252d428240310190255e345c50689922e425e6d50413232aaf07b810d0457475931514597a85e0a581ce062ea97231f1b2e2e50e7011c170c5e0e53d9e5e0170b1944c2a6004e89807400b323dbc64d182284e2e70c18207d2141f6c680190277420410ffd820cbc085f725dbd1483178faba904435c712c2ea59ff4160fc305122fa6573f39316385577b5182f2025d317c966db8d6eb655fb3169860a038146a7971453522289e5238cb30e89a2af9022b0a70791059fa8b111314bd52f0b27169f16a588d112ba76100d1e5567420892a4b98f00084952b7c321698a6e4ec73228bc6277b191e6802db610544f860e464a4e82d725ec8aae4b8105fdd90f654c2089ee8f2440f297db0a13c42d23a92436c489d25c902bf79a3d819441a8eae311bb76972883c7215893dd9101924f66cdfb3b5722b7b20c98a2ea7560c1d49d8ca492b89c3d623f970c8237f2f7f7e3635efe7bdc14125ea359c610ddfdca0afdde0a0a9140e3dca399433efe77777f0f0b06fb9e25de2cac9bf30f909f4049ae77c2a7f070f57d65e137227158cc28c8323d65df96fcc868ea48524c7a7da938791463818ba18635be84f6401451b917581a8e3af7d9c1ec87095b3952c2b67acf1810917065fe25b1752ba519a446a20b26ac4123ea8b19241a555fdc41cde03c51eef49d080f7e0c1613b95e06114e2caaf09526c68b355c59e0dbb91223c22689c8cff356c7c20856d5a37555ce588ab9fa813a9586a43041a24eaf86bfe5edc7670624c1e406e981341ead72c977c8cdcb66dabb5d65a37bacdd722515c7974ad9248236f64658fa8133fdaedab558a3d9296493233838ad265a5118eeb9e93478dc3da43ca9427511c34f35e5c2199690f2e1396fe16ae66cca066fc48b2a277a45dace11d5e264c3dbd3738b659f31553dcbda3fa8a6f70d87cc53bbc8c8daa068732d71d4399aa90eba75ede90470afb962b5d9cbe38938e7ee2b4a8587b158ce80a8cad54c89db23f79988985dc294b2c55de7d9136d87076313a0abd2e5f624f2719994652fe5276a5d6aa358db2944f734813369c4563b20c8566e4386616cd1e4f31cc75b2a15fc9f2ef141b4a1f597e2889624cfa903f624fc4f18b34f217ae30c1852cff7eb1ee8aab3171e51aa535628c31c61869744522a72e5bae3861438f652fcadd3235137a8ca62026714095131be26429278e0a74f709766dbd59dfbd7fe73804caf33bed3bad831cbfc986aaacaa19b64fd8f0a57ffb8c2e7167577438f123eba30a94df4b9967185469ee4672d0cf381b223853d636daa22fb3a4bfc9ec52ec45714514aebeda903a8e42bd39130766c31c19d17f76d03af1bd59552b81be86b3cb7d07ab8ae94f7aa3b0210e759cd81336941c3bba5d151433d43e54e54cfb9eaf952953e629cb97339371a8544db68541e998aa19010000b314000028100c86444281502c1e1827513d1480098f9e3e603a1608439120063110843000c2300082000002200c020006c20ce246170be3d2362a2f9341970dc22e25725701bac466056c32f833cd9a10ca55a3fd7864a66b70b7dbf20a87ccd266cad6205a950a2c51fd05aa12b3f5cbefa31fbabfc144a95471f977fea6f9479494589a51575c694be5fa2ffe47f9f9ff78fd90fb897f913ff9a604382cf9afca14a177a3e9d79c7f816689049a81148d82eee6f6a4488ae0f88534291ba5965f68e26431fd86f52f5c97e05595fc547f31fe06054ae73487f225382be8ff025fa5ec9265453a6be2169411b3d7c2d512418a6abf6a6a627cf9c5e75fa82b1d82198894e2d920c24bf04dd4eff9c7861f41567a7af5fd7df563f74f902c2d57ddfd5e044a634d483fafce95e1f4c7f167c564108eba9a940b07fe02a1125783f87599263ecd747e611179406f0e10c6763e7d853d507776e174e9e03285986f9027f36a4b69e72085bb95130aead9425b69a6e1e047105afab70134556a33a06fe05e135d76fb76c23fa221a56e43fef9f62f242d55b4b5404d6933b6705c1a6ab9f7239097ced428fc95d8bf0a6225f926e2cff933ff70fd7998121868973f16a605e65405fc95fc4b1e3cfc18fcb4c27e9f8f9001306cfb7f849925c218c2616968d5214d1fc6088b90b5d525b452f00129872fc3acf41d56615942d78afb6f04a31463c47fd03f1dff8aeba5a815c03f9ebf805b82b490fac53125d0b2b3fd17284bc45795df5280606381608f51e1c6deed68ff0e1e943e17e6fe027c09a515f77f724c297611fc1a6e3af0ae4ff90fb24b35cdfe1c1dca2660ee62bddf11c47a3085ffcfe6bf3057fa5315fc2bf9997c7eec8ffe07dfcfb35f7bff044969c9d59ddf3b3f76ff84d3255394f0ff2bd694625b057fa87fcebff62fcd2f9b4037d8f811c44a4ea9ffffefbfc38f89c920999ddb64e04be1f48f17c37f0733029fee0e4498fb53678e31826114f22560ad025709a971ff1dffd4ff1245966aca36a253cab2f67ff1e7913f06f8a5e8ac03092575b5fb0fd68f205b7aaafafffd7fecff0d9012931a845da52e15f18fdd7f602e8d5eb9fb1b882d918925f895fed736005e825b19f117384bd85616bf03cb528c35834b69e1e2f1cf2020a578d781e8927b0dfcff40c3ee25f1aad8dbd014d4e8fd72fccc7e81e6089d2fbb2272d837dfda3abf21bf931f2b3fe4fc0b1b2503ae57ff1aa02ac9a90afe947e06f78ffdd5fe30fc7cfb71f921f213f417fdc9f34f802b55b239105a72aa3a332d84b390fe7afc1b8429c9bc843f87fe15c229c56615a04b06d8ab260751d613abfa654c0130498957169dd1fb9605e2a74cda57401ba13f6ad161e1f545a2451de05b3f488c5f18d3e767c756d0d735b9348c1f7ea2bec54d172b6131b46856afc2ed73d1cfcf966ba0943c1c8e1b10a744f8644d625bcf8dbb9f809b3a6aa4d4855a11a814c53b94925b6595b7d47940120fe29bd9fd088ad27155e56f99dffc13f8c3fb37c02e79d62a1e96184435502b55a619144b435a9a1ff6cff4b3c084e013a5dcb4c0e4144289a832d0d8085b55ff3780add91d804a0bda1a9a0b02128efa0be62df388b8b0878d0d653240b01a24b7a8a526485e12eeb391c36ca409658cc9011c9740721cacaa12b6dd4a5a1e584d74bf43dbefe638e78c431b0ebe828e30676e6fa671b01805904591d1d99e9ee88ff4853fab9ce78cebb1c695e3f2a2cbd3aae36707fa7018a167de0e653396ea410ed040790913be2de086d501639f0de3fd1291066522f88ca82e96ae17d775e26b49f81275e1be5b21e4e8b062a0d2fc007ced2f8ec0ed52871a0faa790f5b62051a85da6c5aeda2c8737b2faa80b695a74065d812c7124fb486a71744ff6c43ea5c5ea79157c1ab66ed3e05c75612bc64be1d30de30fd39c4df3e4068d7384b6ed4ea5836201add0cfdd5d71da5d56d16dbbe37133e94caa5189c7d26688ecd28120ac9c45383a0131ac08fbd3e56025d4048e422b5e603933f61695c7685fa025a19359cc836b4358feb71b78ef466390e7119b26f87ef84c537a2f6fae80661b02e2259f204e86ce98f49617859b259ed3c1948d6ae54e7d30160924a0d32e9d1048ca19bd28721ea5721b6039572cc37b932744096888cb33b9a36e06338e7e56dfaacd2f074679457ed6bba57180c30f9420342ee4b24749b3169a3348627cec372e0e427e260de13beb3e549919b8f44ef6ed77edaa841d484c93a4ce5219645b804c24536f9e990d5d622f5d4d09a3bc3a77834b7442443f0adcc0a634f482f44d67287e057b890d2d2781391a6382856496f0abf23176d028cc69b3c5344e32b9d81458421e30e4d16a853d9d36467887691a257b1f907cc437bd1cf5d79e32c7b8aec2ca809542bdf1f4f4082917f60efbafb3622e3810e7e2b6322c20862c401fd664eb9b6a563ad3ee37375e404342bca084c9ea4ce6eefe21224d2e36e6f6504931ff581c2c871124e89261710d648e104e416ee59eb0fa3ab48c076bc8cc2d033821caf09258e85ab338849beda1e4a50aa5d480dd052525d542333e48955c61b9e94002a614d9e423c17610c38d8af66238dc8814a5ddfef993fd27172b601e06aae10048a993d1d5d17955b3e40cec70351658e2205d8b80684619affdc0a70c789ff086fcb7fe4dec1cbcdb122ff3afd4f09fd6e5a2c595a0a1d3b00fd6fb77ce222e9fcb9b614096bf76cfc9a97a1707b177ab871af43203afde4c3c8e3680d00dd76bb6f6efa6e696f0bdcf153d9dc41e2b7087e78f638796201b225055a8801672b295840f101c5f0c1ac0da72947746dc158412f9324056131b439bf735f6275aa312faf23023eb7602e00896c2ce408c9fa87e54ae0f9fe5ff6c6c32a5f58748e407e05552c8d41559d4a0d579fba9cd601d53f87ef3d507d4642733f237374ec5a1b28c0ab1332b6cce679793fc2d8c1608b2f8501df910c72d018df8ee60fbc26ff60f42d646619c58cb932fcc892164a481705a20fe7e0153b79e02cf08493a414013c01d6ddeb6accc4c02feadcc8de020dc214f629eab4327518c9ab43e1e8c9b019802cc4c0ae992c4668015b822e3dd45beb9c352d2322bc2667d03b2c0ba0f4a383ef9f5fec691bf970c8674033ad0f0d36bfe51bbb701e23e62ff2c26a69eb169f295197fea29876bee5c68a6331a013a4a75931209368dfcb94ccb9820acbd33a4060c27129ce95338da1c76fca122edf49449851c027c8c3c8196b8e9eda5cf7409281e7f378b663321217d85959a6664ad496ec664a9c9545d38ab3c301063bde98b09a2a59386bff8eca12b541006b031da65c43cfea120c266b0a051a2b407206fbd3236b2bffed3894055c2a806591aa8d8153b061336235661999f5b71b4e5690496c61141541f738437472d3c6a48fc129ff461d75897aafb92d7cb086e195c5f4a8a4afe09bbe47a9b7c30ba1e7c8c10c4950dbb2ddb02e16df2ebcd745b44373c085798620b8da1d6e665cea3c1d7faeae3ba654b54cd8796099fc871064489bce28bbb2742a26eaf861719e83e2c8b62773af386a0b9c635682b4a64cbdc80bdc838d0745f18ee48fa9c98f96da23c4c88c2e659c1170a6c400c927655294434234192a34d5998357f9c158d4d3d04dfd5074d7babac811999843f3a38d365de01dd394847d3bfe96e8829ece9300bf0f2354bc50c5557d338223aed176c57b2d0f94b0970b8bef37aba4654ad587ccbb415e1af6162ff17b39208a95951ffd9e8b5c2206654d9488b51144311b21d222bcc17e5432911f45dcc03947da1207a57e7b8df9d436ba92afeaa1464ccf3e3f583dda79c03d19afaf939779c17761bfba1d3e87d7638128522b6ee1252cbb6fafae7097a01977dd8c986bc540741c14842265846a5b29d0e922a02673f9325169ca8764aba87861e70f994f015d423cd73694feb4c69b8532b40411088b00991f2d5d252f40e6d51eb78191cb34ebfc22103814f48a8c7ad645a936d524ba8471c2c9a8b2168b01d88ddd973c48ab093fe3715c0f9a5b2bf817c70deba2baf3ab0d35013687f83b9e3e080354ddaf1f77bca877557b6c8706a9584e53a48ad409592d529c7762078549dc9446786c9ff5cca4d250988695437952eb50c2499e7f6b1aa11f2556d7a50cf201afd7c78c82401fa224dc32031bed2692f8247723b05a61ddba1aba835c411c7427c247f53878ab8acd719018d3f47837de167026ed1d143121e771b60676901e91b55240785f5ac730eb61b483952d3b0e0869528bb57f33e1e3b1f32dda1346983b1baa105284754780b825db3c758d792aacf85987aa704f232186e8e4bda47ce47635ff2382ae1aa370f72b00cf3fcf49747cba857649c2b02a84a6488a08cdeac6b4b2fb602a4639f604dc2e1feb29f33e8fe131df37ea4d695802ec31f18ec438c9624672b1c309a6b3801f938670156e7a749025a34761a0cc5142b1ead8ae7532a393a8beb2c92b9ba71ddc18c0b6a9b1faabe1f7b6143666c6c7d844299a63efacde2d5fa244f0c0755197efa1c0b21dfb19a2bb1755a69e646fb208636dd16efea42a084cfec313e19fba21d0734d17e503daac97566d2cc4d59b552085d6e934d24eb9b1e3b37dc5b0731b6a7686f1987d443db2bcc03bf06c441ebb73b4c65ad48daccceb60dda6a37ce0599fcfa6335e7e617640ee749415c7842d530b7fc046608f796f7516a80dbdcd44fbea938ae98efbb1cad1d617ee43243cf08899f292ee0133ccd9bda43fb21b6baeb71fbec2a28132a6d40c506a4790d333e038bc8b2826ad1e0b5b75042d5437bcaa772eb7e4670bce9e4fd4d6394caafc4447722f0e2b28cafcb29360695db89d6d5b2c25c79ea58d6989c0684633e16c7915edcf7c0af04916e41c031848e7004ef70ea44c7984094d0acae114d273e1eac62e8495e3caacd43276901ad30722294a5dc656f84732fe1a7424de72f380b54f38ed68419ca3cbb1ab5a303de3df3f2c5a82387170ac2d5a83b49cbf1b03af1c5eacd34bcee70d47a1b866d668f2548fd0b1374240274b1b7651a8776a58d6b9218e48cbcbaaaa8151b78d64b8329c4cb3f3dad30f368f72db2982c2daba0bcc8a1ef56d8cb842bacfb88a56ab33fb4eb9022035383b2f0e0ad65179eb8973e200d76111189ed79b8b4897dfe9000ec06ab5b9c4f4d4a97d291292b9b97c19a0a24e2f8497495d15f919c4593825337e30979ff915560900db4602a2c3f994b106c317316fcb63ee6b5e3478fd971bc180d6a39db6c59d83dd5929a8ebf4b8a97b8e90d1f8c3cc33e6ee63d80a302c9120b3bf1fc1a3786b0dade79e21cf953305e362221aa2216f01111471a45611196e78b313390036e48510316b270ff5678753f32c5df2ff864e7ea2c5fe7d018404afc75bcf77b1443303482910124f97768386b361f3a1ef0e2a00d28678a9f8be73739110126d56cfca9cd7e7704316638f714040d5e2b8810ccc2e74305f43a7f3438790787c54b2f73b9a82710cb5889380d8a1a69e22327ff4acdb7db91bc55963cf628a493f303696e6fd06874a41099dc6f16d351d70709c03348b69dc4f49527aa14fbf765cdb3bc783a4dd6999c8fe1492bce710e0b196e9ed98cd050dd16ba2cd76c6b122930c096f97479e7de41634567e684ee8542b7edc4d96ae9e09fc1da691b59669416a3612c96205948d98a8d271310aaf6916c7a77267aad4b511fe5e9b0a10d9d5bf0136d7358592c9c2f68c5e877f4300519bfaf2f0e702d0429b814467e89821a9adc79c9a8ff192a6bb6a310d01f3dc23700ea711aebfdf31d0f18b39519311c433ec8f8d5a88936889db94b371303e7e1407d78d3fc5a4d4f191016f6ea7ab927205a3bd3ce5bdb4abaa85b2dcf4a8a926d5d080bd16efa09ffd24837b47a075c041e60a247454df8bd87ce808397ec20a38db1e7bead7c12a558b8f42d021e669ec3c24e8ecd34c9c8a0378473a80ae46fe29d005116c004eb3a0de3692652a126ab9e51b2105e1276a23a0a019576659cf29bc752b4062adecc7daef1b29695fc073151a80cb0081e0868162f41b77dfb05fe290451182f63c25498e32cf476f24c271aeb711b239f10bf98229af4505790f33346f585e13a37b45cfebe6a8b816a3d64400bbe0a5f6280e7d83b011e6678d4153977a7d0fabb58bacd25fe7c62ea51ff3a1e121dc051a9b7766ab68ca10843b5e72320b57288a6824521578f98c76f1e0c00cdb7cee4c70e7b1b659dcafded535ea79f7ba795ae1d5925baf42621e747d22b780a04282394a9b0d54eefc06cf5e60e9c40cdbfec006aeb05c123759b00240c2a81ed956b327f4547395b80e54d7429b2d51aebed2639e35b8960714e7d84271f1af28ca84d5bc81e1a91bfc1ac5b28a58ec9bdfb58e5dfa29476c68767875561bf910f02383de53e29aa7940acc8cc6df502c98e0b85986e226aaa9837ab093bf025d80af79ad287cca60ecf73844c1cb38532e4d047de6791760600fe486f1b542fc42be94e609d8c457a940e2717a9670bfd5e8d9aeded8fff1a0ae56031f18acb7d5a9ace147ed23400ca400093c4d90897f4ab2fafce4bbb709eb358e81972d83c1296d2e2882347ed1d085c8ee54e0d311111d952db364c63aa850057b5b11e90a11d351094265b0660f6dfee1c31813319bda38116b150ccdc1c3556ac556700c5ea6c536ca965a0b8bb44095f9f06db5e49124b53880c6c317252f859cf1707920c4d2860ae59bebbf24e33259248d72534f826a6b6308d86338b5d375bc871c9f0f3156c06d9dc30fd65cc23b927de4e8f1ef137e941c6b5d5fe82aef7233e908cc97c3bb07beb49859eb896c581f8c8f46cf766b39663fdcf3fadb6bdc2817c6a06b63cdf6c2b61a76e8d77693e7b563af95c4980c44bde420a2746ce388b54762f7c4a9dcc4b67960cde3cbd59131f5277449e5582d1c781f9f0987aea216f8dace17c4d0c91c1dbac624c149a13686e69e5ccac166b268414a46980a3be73ebf9722ea00501da076b620c1d27e81718d72d7c620320758419d0e5c1e8d9695abc09368bb3579a2b7e7a3a3fb4668be657911314a8cbd22274eb4b0dbfcd9f70b193d2ac056e460e0a52944088d280f001522f2262a293320d0034fc1a5554ec294f8c37614b7806efa720463e184387a21124af97639dac412ce879ca30b103ea50846f00ca786af1d2c8b3e969f4bf3949b6c995c5b24f26d8e582902442e01d2dd99be666b712d46e0e701856fd614b143f8b5c47b10ae6abbc59b7dd87daab913ccd6853722411cfa66b28db714ea4b5ca0cf0337e56f59768d4de98b4dad08efecbfa69259c6dc7fa252213ff3a6289c141285a5f87b1cb793c7825f6d5d819b9ad6833aee81140c26ab293482bbf059a5614eb99a22e48aab41342514f7069cc834d68b5e28e64c551f0aec3cf6637b65f8d013304136e66063969828789247eb21e282c503f10e12d3cd0f2a69cb7d65caf2505cc02c1fa6b7e25ae16f773a8720f09cb4806024a3a3c6cf5311c36050e9298579a87f0f4c54310c50838691d7568c5f8f974945a8dc31f324a29fe2749c2055184ed7610ef01d5ee3fe0af1299216d25be2ea68c615cf19944feae231c5735cec1a3168a44ad030de791a15e2274a875a47c16196a16e9c0745e78b187a72d33f228a0bbd61eff1752e3715c10870ef4b1c466641b6cb2f6f59f312d160424c41ddbbedab3a7b5a38055ef901c18f4de19d01279d92dbd50cecf6b2cab64e2e227ea186ee0e9054439a9a6464084e4c4885d348c2f8ac4deda4e4360aafba52bbb7224b28a9e026569a95a71428d2d1ce4dc0f94f276dcdfe42c09ed8a3c3b83202591e81f04a6bbfd7bd46dbcd9b65cbd62a0bcbae97a0cd3d387fdb448c5feb5bc0d89e8abf67a75aa49561ff82ee25e1557f548539fdbaf32027884ae1b4b3320e672a0e17eba9864c448e81f299123b3e4065747753ec74ab1a5fda394f9bafbb21e28cdeab4e6635465f3af536d03e1f4bbf9741361645d32a336b1629df6d108eabeb371708043a049bd50c765532bc773f3ff9386d86523d51f31b6625a4414832411a4cad266be6c39f3ca923749d0a73679ff22757e7e56eeb0ae7b37518d173bc9adc60c7c2ac5ca0293dc05a0aaa1fbb7fd561fa84615cd2a3c9530ff42537800d08ddeef0af96dbfbea94b5c519c1eeab97f4006525a06dd5bbb0c673d3aec29c30dbf69726125b43add05a547e8e52ad8eda8f329efc0240ea19a9595bd90f3333fcbfa4e9ff8b1cbd26467346d1c9b2893642d77a56b555910bec2cb9b469ef794b6c424a8d3d185e67e91b584b1e0b6d8ad815b22e734eefaadfe3a08a390f15481e7bad38acf962983ad3ecaaa2245db3f529661f5f3275abb289b07a3cc5795bfd9ab5845991266734155cc5160ca0aa8660bfe9b6e43a8a85fd79b2c6151b25db2dd9a1d5c825759e38933dba4e9f8af5bfa45479f8c4fa0f88853e732aa759c5fa710f0460edaf4efe3d972aa67659395e58fcc5fac1acf37729a796092487592e76895741d0b37e045eee496fa2541d2940883f96310297f5d3f4a7cdfdfc6ec01221eaf7e29b6db4789447c8d572df0271a11738543f3f718a54d68ff725b92e13241c46c8035ed7974e5b69153bff8d46cc7dd4c7dbd34b004965ca195ff822c7946b172451cf4bf9bba615f82589bdc856ce3750286990cbfa6d5e890010e8f561c1e72711c06a1cfc4838a1894400baa0768440e5eb29c014ba398b7468869b448025df21f49181fa65dd2100e54e095198db62afdb564fd9301201f4f791818fefc40f2262de00d8ef969fd1bd6cc5fa28226abfd92a8ded0351903ef177ef7992e5049a747126644acd070a2402684c93f612360403b0c50136944a7beb98e584b66030c2ea0398216b9f432c01ffde03166c6fa71d8668da090ea4f9855a02c8a4e9d7f268a0d62b1681143f22a1e63f2ac56a614b176cd568ffed547c2483ff5d1201bc8a5ebfdbf664d2cd1e110f042b33c5bfa10a121edd32be98f2946b05a9f902a918347b4df3a40a2ec717e4b7d0d85b28295fac9fed050c1f410c5a729b9c2f982ab967d3999a796571d572befc3f7ecf05ce123726b825da1c918173f4870b94ff6ac1aa8670542a40cc62fb42f6711fc43cc7eca3216d73cc0294bc9947a1f1019b00cef666a182d33e0467137b9d25405d4baef55fb7af409673fa417049326500935de926104d26fa94af405a96fb9093651bd997de0a6d2ef1ad10f6ebcb168d7e1024dd146396de2acda5fe0c795e447dac6a2b10070b9ee6c60a63d6714c2236dc341a040af5eeffc21062dbc245f99ea0639e5b253eb4af68c0f4baea0685b7f17f0a403bb988ee68f3b899e35bcb01812c4c90dba0c8b122efb556f87acd752c310cc458774d1a8090832380def981558a479cfcb4b63a63990cde8117bc1715dcc53053441036e90614791d6209dd4fe620511761973db5d8df92d46d24ad4aa0d3e6e07482b766eb98c8c81b7b4086c54d93d1a0a2e03a14e256fea54d20e3ae11291fb171017e5d8613020e1e8968862cea686460ccabb1219d1001628f889bcc72b051ca9400acf1e2bd32d1534571299700d97aab4cbf217f250fe35923f457af40315f716ff525932734ee2fc879625456a8bdd4ac49910662e5ad952896b99b2ecf60945ee85e4862b1d949ceea5e2be160b03dfe4391c9223bc244824b4b29263529d0cb4dc06fc322cda439efd422cdf028589ef701f77a1d3976f8b5ee016761ebe7e9ebe81620dc6f44485e38bfcb6ec0f536bf0758ed61b2a833f1509fb35257e66b67ad7202112708a1f39f4f9aaa9b8bd02bd3430262cb8dc83ca16e67cdbbf40c1d51f02da3779de9c33d80d8684979a66af9b3b732f8684a12c0d44ec8e4d858b33408841abb9712b87c3f3ee86c631a7cc06842f2eb3e254c084820c245d0340b406c04f7a10560a6163f1df4ecea860cccc718f34a18050e6e68823dbdf2ef47832c7954e93de65ef21f5ee5232d011010407a4edd68dc55bdb14f8298b5df2f17cb32222becd657df2bfe10eedf8a9731dfee0c86aa5885e9aaa3a451f775c3b25045a41df315cee5be35b5a2e3b2e93d420dacabf1e3f5f7be4219dffddf58752d9152ba92f31a9d915e4413acc6b3e4dbc46100d10299310b8d3a96f717267c904defb3903d484480e1636f69ad16e995f6aa60d1c3053efac889a302412a8dda60048363121a0ff105812d80d4a8a9ef7cf7e9874209ca5d1f754fdcd6d9fca08ca0bbc7481469cb00c709ee8f0603867d477930263539a9f09dcbf18058a801888bce09fdc1a403e4a1aaf25d8670a2401c2b4a4e6df979367a78db3ec3600e01a81bb0350a8ba54c856619fa07f3905f9c64417e2f7c83592be3df2454070612e2895a63a145aa68698ecf66cd57b7daca199edea72a561999cb9a818526e4753559685cf3d89a66a12b6e8efb602eb7a2e911ef212a39b0d423ecd9fb50b21e1cda0957a1092829fd994715f9d517da1c51eac3b1f3877923e7906e22384d038aab7f600a6f91681025044251dccc2aac337294a01c28523a68649ae004f008291406a2a9429abeaee6de1345f8e0df7622ec785d123593ee0a70ee75ec3cd0de3e9876decafe7a5171662f98437358fe7522a03488047fc4a07d3d9613d67c367dce170602cad39ad36842f97aa8ca68f60161301c124d5ea64d3306914e168ffe258f42d26d4dc1282f5ecb7c06067c3ab852058a33c606ae6c837d14fb764ee684d0cd694aef416779f3842f377b331bc5d54a78de366ae6c718082569e02fedf8b3a55971df296f8aa71a08dbd974d70bd00b6c142e6dd77a62c3fa859aa1a5f41e09bf21c87eb63aa6f1c4f4cbc7483e3f78551088a83137f65914e2f830ad9976ef57be384fcf76bf5a5ccad01f15e474a9abd1190bb808378d7dbe514816b07976a26890cb13957f582ddbd3129f1d5d9c24ebafd81376f62a97ea172960ef971e81d073473166becfbe4db6177819ad8368fee3a6f7d650a7ffe054a52d68cf656c41c32129eee835abff0e583c6b8500d70d33d22b3b3e2686dc8c3200b1123cf9a524aacbb2267ceb528f582bb767a22b8c20b61619c0c23b367d41dbc360b5e18c7a94dc4fa3442b880a6a0316a566f954166c3f20cf482ed5c50743f91845128e228ab550762ffe8f02b15f10d3e02e6b4444bdec7cd2beadb6c80155c661bfdcbb6c54f881e446938d58f23032fda0d9cc492ed0be8ba377c0107fb42b820f9dd80f9209bb320f86c5f17461f3b7488446ff244b915819c1cf118e0befb700d7e263ce1a4b77eb9dd9345fc5dd4597243c9091cb457dce5ee2800689e1aaadd15671fea80181fa750658f1698a0a8cd052bc04f87bf9939ab2bc110bb7ca6edc533b724fcbb5e62022e734f55e79fa513b314100c31a32744a31e0af142efdd62a072640a02c03a4177b4430eb916117725a923816e79501838f0ed44f1a3264f08ce5e218f923719b1e8ec016686059e1b45d154f2d8c779bca763b11ca96a565b1ae1c165f5a1c4fac7a5d1c8e616b5db3b44b5b0890c137602f72e24f36bcb28f09887ebdc73b98246cfc1886dd1d3dbb6a508f3fd6547dafac87dca0feb42827ff64eef7eafb3ee392489630fb61bbbe321bb81aa00d85e4a72d4d6964dcec8edd5b88edd5b046c8a400bd6221ea5c026fe1eb46cfff23afeb4582dca65791e386b46190fdbfd2faee1fade71b0635e463bfd1ede16fd352d272138cd648261faec072c53a8739d38f83d3abd054335cb3175db27d72a28e6cd94a1e647182b9df0b52513294e8498ba403d8a0c4c9463c6bd76d28a36d7a292dc8743a4dfa0e9e4d2a2dfb92f5c68f438c3bc141a4f5be67e6937614ef03a36183aebab1c528d983e7a60df76b3e68ec989889f5921549bbad368e5439e0cb2cd540fe4e7d2cab38070aad26a841b98d8e2b8110b375f832d5cc6a3c13a159f559d44a20642ecece6e4cad809be51bb00487a3e6feb0b77ca72c107bbb9f4685d2537cf34fc47d656b8615680850788f4ead88b1ced7006753143b36719aedfdd682a045a4c86edac6d8a6ad675f97f989d7af373d04f07179af82cbd627567d0e08180c420c1a95d3fe2f84d9899d0d4b1dcbcf0c845c83fe4f1b9fa4751549c5fab5c31185d8d43bdfa3a2281008f0a4f2e5bfd4155ffb51ea59cb7018f00335dc1562bce9fd13411ae63522dd346485b64a1dad9e2de663feae5cd04d1d6f28367cf6bd54656e2da62aa2b6d146778be77bdeadc0fff602aef6cbee06e5c561926fa7f70522a35b68b88442447f62193675aaa8158f38abef35a3f1548e384aaf253331a6a6e848876d633964948e55ed903bbb2bd76a36703b9c32cec38ec941175a449758b82e23ab97c06b0a861123aa4c1506ae86fb26a3a6852e78846ebdc5a8ab2ab1e1fbd1b4636429a0e026d2663e833503131829882c86109d7613dff7d3b47a16af8a32ad84963fcf4174bb7c1f6f514f10c5263b7beaa44213df652d357361c648bb1aecc0a603dacafc15b324a28a12e20ee3196378757b8b51459a13a5bd6b3124c9636238f70e2eee1f593e894517a3b84de6772e8a5f6ef81867361fbdb4f6da0efe9d3bf8c70926c2b91f84ba09718c02ae78384b91ec46f625359e155799ab4c882f2488cdf85ae0b94ecd186702e0d70c4ba84b40091b585305c4bfbac976d21a9121ceb411f6cad665308205e6d02e30479356b4a9a4c13cc9e28bf9a32c1ca4f05e8ee58c83ceb62c22b84514a160bbdffed3c1576e6e0ec89ca2737ecd5f4f7357b2ba1ed8d8993b65d4c2bac752652f3498c64a61e4c2c37d2af922b1c9b6d0eec0d72dbc1785688689ec0cccae7831183892d571b4f2106a6b202d21395ceaa062920db6297865c5bf751a53195579a67c40990acb9f830344ad1561b0398b1b99309866639c8d4725c2d4283e572cfb5e41c515497f829df28d74527194adf9d609db2d837df0ea543c4bd4981328070c4b1cf383312e494d25699d9a1f4c991b1a4fa07cda9d56702c253102827f2d779a006767fa3e9f14a5067bcd8255c8e34f209fff88ab986c72f28844d186360ecef9761e25ac2e090f91623ec5da98677551215705242a8e5bfaf2b853d1d40abe9e583bc2e3fb7dbcbc14876b4dfc9d0c242ff7e977cc935808ff0e4164b0e3057735afe36bdba0160b647b57eb0b38af35969f6dadcbc6ece70bad3b494a0295a96df5be61f11def94ff8c901dc271e2aab32eeed3e72cce5e858f4a5b7612f801e6c213ceb8d2e2c0ac9d19d8f7be512f5b9f11c89a2da4bf1c203bdf066ff10e05671581630042e43ca4a8b740559b46ad2ad227eb6e0c74c3806f25ef1f88bdd11a348cef7b7f4b3f15e1d35e0d434638243fe510b65349a7b4f6874c5394cf46903ecd460aed50cd4aa22e2ae201bfd8e31e635937ac012b4702ed8212a5bfcb0e801db77a88a2e2d76586ecb144f7a6ade84d101ac34285ef8316addafb398c67e46eb03cff19e55b85e95fa1f7dc7d36d6b9a0f995c6ee2629981b2e00d739a784821a1549436f23b72479c18450f437bd5057460b0839b44cb6299b7c1f5473cc298294fd2702092f27b8240da1683028d996ee05af7fc62754be90d91836758e5ad61709f656ec72eba53aa8af0556246297262d7aad465df865c0eaad94dd5857e5b6376a48427d1519c1afaad4fa7f333e3652e8966d8139ec4ceeb8ca8832bbe36810ed33c4e339311cd3b5ede839b80f28077858140df183dd7894181b7a1fffdb720f0850b2d58a3f69cad387e9365631ebe0c79e8cd5b86473ba97dce4514d788e94ef78f436197fde8424d6744a2bb66aad15e62faa3bcf1c4ad1a70592e17e670e19d05ab08bff0be95240258814badf5ca933a7d53e8b0a192c5af23fe79eb23b6c18a38e3f0ca77986225f39cb5d8d2ff48e402db6e120f36ccdd297d492e0d07b0b776e522c77ee7acc770840a797223086dc903620740014dc1e052d0b4b1810635b0b310231fe56d425a78733e21ec2c9381384e2e2afc425c356d68a6e6f662ca71fa582d350a34c0303f605402e400911fb93b1a9bf20dde80918bf4a8d8a378ddadd51437880e865ff44a6b9b7b631237c0f8a3c197e3f13e3b34c038fe80bc03c1c99c6059f1ee8ef4566c5504f1e47843c7f2521ac4424e11543fc2a7908b4700f9f8cbe8e8a05281e2566d22346333312d08d71c0d0e8b059bc50f9e4456fe599e05da874906cc9811453d94cbb21667582bb6b7414f41eee03b0dec2bcbb7b0d305492ce84eaaa3507a20f8341068e929d99d0618edaf87447912956b79763e3334ddcd10315f7c61e68d1f7ca98b8a60fc84f852a49b38a3975b2cb4a3729040a76a73efc7143c82243dc1324dde5ac0bce41dcacb606f208dc13d050f408181830147fa5b10d39da99e9295ffad3c586738ae320d19e2dac42b98833fe096e30a32e4e36d91e8527263e1cb7bafb0c4c658aacf46dba9430214132037c5a49e09a144a9ba911cc84852a9ac6008bae7ed4f02f473605f19086167ee1ca101a4280abe8eb54544f0d1869708d650774a89e028f7dd1c0740e715a70c0b820e9ea84df063ac02ac86739494e1d6150e0eef611fa46b2f64f5e7e8123d75451f63b1097dc172079d51a29a3e903e5d2417bbc0070e44c5c786010067a4ace1684a0b52254e775a2a018e9ea4edaf5ef53b98b201672a4fcd500acb8c24622a357a711374ff722a5ccf685c378a10721491e3b46e2911566e83da14c946070c65e9836ba7d43dc730107228fd187b3710b5969f4c695e95cb7dafd110e09795c5c80d64e9dabdd73f2c524710549faa44d6dccbc86db612372a929c92380c89b979bffaa9941576ecfb4ae175b5b87b3cef76d03c04e322fb71ec0a69e8a0285dc7c131b7910bbaadf2e3bb419a278d496be592b18ad43ca67cb157389599395c92cd8b5c8878102679c2450abd0a37dd4958ee302501728b155c877df565c6044f95590a49cc2d9f391be9ed28c1a26cf7e8dc2480a0500f5703341fd76613490144f1d36c2810a02e29462dd4517f0d5bca5e1d4a155817dd656a0169c92e85aa25fc809577e77b8523026c68f5368afbca44aebe1770623d41c81c0e22dc93b405afaf90a0cdb2aee338cfd0911b265e3d808f29660cfd5540b3c2634411faca9bd1815c01514994f17aa252fb6c85ea51966a5b0f185aa434cb4c7b6de7ab3dc4e705d00d0b91c83222e06c50e899efe60f52df4fa67c9496c9d21d1d7cf8620fab5a0ef36926d841e0b5e18abee77c6c1b1da5b18747ce34cbc7fd0763e4fbc9239bd46cd5948852108d9879428b58c463e1b3911f75439b9d90b323ada91497b72408a2f50a664d543b13384a22c47a952ba81795e98b351b82530fe827044de070fb4caf53f987a28c6858d8bd96b334629dc1237a478432f8adbdb25e5097a1d1399bd9c2a161e1794ea6f2e79a48bc5630e604d17fb5da5e4a839193edc0a7d14ce2918efdf63c4b991278cca2d40b5812ac3a074fb338966944d9a69a857bd01602df573c686b234929690b8a2d7a4d84d5d87bdc7eacbbd347e6e116a17828b8e3eecca36a59c404cfae86a328d4e4a931be0ab640f12b2ccb4edbb73753b4fb19f00aef1865a9d753344ce3950980e24b1b258749c405fb3bdd6e2618e38b90878bd0f80f990d5b0fc9ef05ecee038771d6187c0d0fae2be2d43d128ef74a4cfbf5ca830a668cc21bc881c86c65305feb109a21c070c80fcd34790ec32dab4b7d86f02d444e1434641d60f4c2f55ae0d67263b76a6519805444df6ea2e4619e3e651416b4b0e31029c037a18f247ef412adc08f67b376c4a13e31f50905ee59626df0f520109f86f9fb8fe9cc340e3b9c67ac44b19d04d813520318e02c306dbcd7a00d093b5de34578050d38e25a7e1598d02d1d6608d61a109e710ab6d3b524d408524f448f78faac1708f23f460fdd1745bcc2de40210e821673028392e0bdc58f5b699681c8059ebc8b826be551f60924d3ac18cbac69a371910858103ccd253705f6f8ec42d8741c0d42a377bc23853a376e3a3f9c12e2b1a3314e7845b860e3c2ce78bfff7f28b7fc3daf114866947e1c39d6cd03e56c9683c252f407674216d2a61267f50788c786b479c71602a96543277d7afd3b8f6fe65b1a2e70d8daa526ab80ddba9989f0ed196c5984a93421bf458a46d68669346a311108dd621348b2583e4ec68944753485825f848c3671ce483ca20be8328e9e1de48ee69f1d3cd0ccc4f8df9207b2fdc95fa069ec744139a8f222e5488b459f81577199c9c1e793f6d6c869f750a946c2600cd1ec6aac75805302d6cac660abe7b906985b10f989a43b69cf21974d01d68532359833658b2680ea0047cc0d446558242fdfd82e2401f3e7a0930ac393b1eb96c07dc254c041a70f8bec63b8d857798580db07ec12f9f0a28b759901e95ead19df8d8f3a9d9e6447959e7bf2abc5588c11cd0bea52d19bb99f9bcd33f90abb23551168329094e4a898cddbdf2db566df990840213e957081c0f7bdc729752dd343072c88740af9b8edaf9c763cc53a27fb52136596736be6811e040e3003094e7515323dfda52f97c86052177e621a423cb51a4629261983f7f938ddfa5934face44408be8240cea5b3290baeda019ae99a7892124f9610c47437fd713f86d17a11162ddd3a2f1c30554eb0aca5ae31a9a738c40334bfa7c99366dfc518e53f48d07af5e54394d0f030db72c8e9dc9b04fc53bf3d368c1ba628cdb9e914cc69350d22a48d153538404e063f6e3ad807935d0bf4d2d732c14c74b5944253bcae22dfb932401cb51801d4baa0e65bc38e1ed381b7165a55dab9bf17b06301d30501b129ce54181ef6ec4ceddfb6062e92dfeb9b7b2d140112138ee61b4fa1d24667438c431eeb2418417838baf70edcbad8016aa3697b149ebee403efb2b6cca34b7de421cb6d33315b86f331f4fb21ea0d0c7a59d3169ba240fd0c84c4c522b035b532bd51336da06be2b6ef2d2a726c13c61b4f7e4feb4fa0a539919eb9280269f7bfa9adbbd6e98c10c0157c4ecf713ab2eaef2edab836b74125d4d68bb3e453e1dad8d63f2f62b48ea00f485d37250b80b46946e749c5694f31c3d2045280b61272710a8170f3905401ced2e8d62250078ed1a888ce05fb96a99dc811bc44fad7ea025c9dcfe03c8603b338d03906c7af20a926e0a7fb1d931f1b7de242a72898ecdfa4dbc729dd1f539d7bf1e86fbe9188c64fc64dc0d5ddeadb7d2429b6dd917769a220646f110aa386d95ba767334b944b184fd7cec80cf99d4696d47ee032e3a2685d68ef72f0a2134c56909e24138d676c22723e2a1c2c2a49a9b7ceaac886e00c907cce797d694767663dfc83ced5f4cc25a77e7ae5183e37b04f9c906cee4b7b9c567d9ac88b8dde619e9df73e6a53aad436e4855a7ebe49935db4ebb666ada7abd213ef04ea265c26fa6166e9d8bed9e6cb0b7edfc4804841915237e124b6c3e8a575a72b9a35b9c304bc9a477a0f6dee42dad45f06959dce632eb146adf014b0ddc2daffd0a9633225a29e69a81fda243b2021627e30b672f510b34b0e53372bda3211f4bbf22910733fdccc26ef2fac69a0f9bac7be51c7b1a878abad60ceffcd3e65451d0c9a107b6d2f0da918b36a8c578382f83863aaabdfd4c8d2852cb15807409726585407900830a9f331f48fbefd572c76b52537d63b9940b1195716072c8cb0494733f339120b08578710e6d0528d40443ee95da628b32c92282145620c76de9509fc1603ea45027d5a300eb1930e444b6eceaee363e0535f20b37b23f6acc994a532f1e2e1e161f1c3e0e7d7e7e0c72da7f3125e7668a5ccf7ebfc7463a40a16a31cceb82c0ee00fe190931938e42b84ec650109a11b58bd09d97b6f22a594524a2903140800075007b63e6f812dcdbdef7e5bc2ed3b776579a8ccdc9556df65b5f573a272a62c87d1da5a6e5e0e3799bb26eb59d75d9335ad5bd25441b78fbf6b4d4881cd7575735f109caa1bd4b71bfcb1af79aeeacb55f5fde08365b5b7076b98dffe1e5fbfad765bb06bfdfcebdb706bb392895fc16b04149f00b7b75f8a3fb6bffd7e33dcfb567e2f3e289e47691dec3b5725923647553728f7deb630e7aaef076b682b3399588e2ae74e80cfeb8c8b9189ad4024f78739aa9ac1d6e193dae7857696a3ca51467ac6b85731b16ee688755d1f9b3b282ac78acda01f2473a450d50d768e8a4aacdf2ad63f134415ebfcf557fe98200f73a2729e7254b9aa1cd57e22c00c1e8aa2aa25144bbcf068eae1b399fa164bea5b4c09475700bb61038d55d8d699cfd69d9e2754380162335f9277cc438b6bc54fd34a736b32ec60d6395f9a6ff7f97efd753fbda0aee2076bce133cba153ff05069af7ea53dfa20bd654e450f6362148c040a500e1d68e8165316c0304002073d14ac021b200103962787616783aed3f3848c7e3b3d4fc0e828acc8cd38716297e644d0ae4d9a72c5982b5d76686139a26851e18a4819d655b2d0b11bb2b11b12634da9342342519e118d20d3d434a1219565c9a2660a8fd34591223ea476b099da4d39eda25cc03071549e31215426bdb00352c6c7172431ad2a6793ddd29125204c5965092a3633762a301d194a517e0d0db960b38391560609b3c2d8d70a3cc0c22429a784ec9e6c45a92ad540edb6bcd80929a1a24c5282e869ed089a1287981d111915164835286b4bca195ebb335c66394a146a9035ed8c587809150d1112b1cb522ba19021e1856483a5fc22a50cfa5a92c22e478f1d8bab5288540946c66e8516e41a09a3528d0409155c60a18532ca6cb7428b1395f205931d4676423fbc60955e6e3479da6959da958d408a32a24409644d89c48c1298165bc05839e164078664d725482946abe482238c068906859d9888241d224c9452da9426ceec489c916db89456aeec6a3cd99d4ab086ace1511a59955ba2aac0d85d5951e24499c21914b44b21cdceca18b2852ee592b01451c817542863102959e8da8dd1b15b63032a4699928d3514659bb5115b6a7aa089286b214b79630ad6b9a3516437e6832cb3a34431daa67654a3cd296de9499b14bb2f6d4c945dd64a2669764f64762aca94b49464554638d9552529ab0c49e3c2a2a35422c5e554666977c426c54e091b1332d6ca13695a90d91929c3a28574a18a1327a55792b26808142e263a765d52bb184e2a96cab13529a6ac31b133b166244d161933654db49461aa94659cec9c24f931a48a6b874647292475e4b43b5b2af2428af285174c34ad31a5295b2063a34c86961d922a3b224e7c244933448a8b3ca3034d4a8493d552920b299c5c30b10b5a134b53f62053fe28db4dd1e242151a4e7656494aa221e50eaedd958e3552259713a96649879a14bb176a4cec5e582bc7a429a99031531644cb6ec4c8ab8819b993344a765c8894597c9468b27645553bb51a9d8275c9e84496baf9a5965a5237f97fa911f15af2be621055a9069cd770acbc8e9c6b9e7b2d6dbe987763d420c527a8bd3867afb54840f0708c26885ccfad701f638c794fa319eab4d59fbb196e7a9e007b9c71356dfd5abbc661a7019403af34c36e316f839d9e9d0c96c7ebd9f095e6c600f8ddb0b363cb1e29638e2a8746fc54a659ea266f072acd51efb13cf48f469d4ebfd0944163d64d5d993322c4b43431009d7ea565c16174955785d7f7de2a88dcc7b875805ad741977643675d5a8c1063d0e974692ac2abbb6925420cba8fabd9fbcbde5ba7fb9e966eee806230e46ede7b6fbec331e24ebfccb0c0e426de14c0d04cd7079ad161fb762846b1d32f3334528c5fa75f66984a30332ccc9018f538eebdcba8f5bd37ef30dab21b3c8ebcd4952e94d9328e65acd87223ff7899b3efb14c92be1fc3debb77ada825fc72e4f555462b0ccbd0d09a6a8cc54e4375fd18ace63b610e439925a851db34e79cefdd8d3177fa556645590345df6d9511ea1a74b911038f67349ad40b840e23f6f93afd6ac18516cebad95303aaa1b91ac52c75f08a21d1411ca2c939e3a2733f83ced32da973cbfa15a64da75f67561ddc626737e9eb4c57bf6508fba0cd48d88563a3836178c9dc8a0e8af63323eae02db417477137b15b927e95bdd0816cbd4114450f6d028505dcd3f110c65bb6b608e938e7837b86ac30e82e66b2aa32240b95155c3c69cb0abee5450e6fa8b2104bc3c5d672f0500d1a3bfd2ac3b257c8b76c0ab7573fd6b80c2c972dd9b9acab9b3b180c582e2700caa1039ba13ebfba001763a81cecc8ece8a4997177fa5546d5cd5c86fafc96adc89d01ab5667ec1b7a4a321dbcace58c5ecc84b99e97972d37acd661f022450c751ddecb1000805e78d83e2f5039b66cbd2c855c57fe2ec9a83bfdf2a242c7a1f41274d385cc4d9732de652c7709d35da6ec2e4ff057971d4edddc09b7805e78e4a1d08db7cb519711ddecb500abd55ab448e11de1fc64694a10bc8eb19230611886039d889f0f00d8a004c0c994709b6ce8a65dc864408c41b7b984b703eac561973a27399b1fc3febcf3c5187309d2314ec738c45cac3ac618639e095d759d7e7169c1c5a89bba6eeab2bd6581949739ef510d4af040bb07da7b1483fa94c741fa3c9eed793cdedb1e865e75ba11e37efdb8fcd1a52e47d24bc9f13e81d48e2101ccb05b6b6ae895a7e02234f2006ba79808f5fac09814da298f1736d62ae2c4da43aa01ae210e9003a32e07f348f078423c11373d4cac294b922676e682338cd10a632c9c431c89d31f79899aabadb556dc6b035038cb40e428115e0822e4055d2122d688e86246c418a40326a8b2c110268fd7d36079f8e3ef704510aa12907eb2b8eabdf2172340bbcefd5b2240c888abbdb7fd20ce15dfdba6f7f6957ca43cac6d80f602799073cef977b6619e7f72a9ebd502620e0fe22ce68f697d0fb4d752c7f63f6ffb6d86b69a81fa262f99c42691f55b43f0ed83b78f242f7923c12b018cfbedf1d7cfb752c703edb75207bcbdfe5bc96def817a075bc50f3ef043a0fe81d0dd4c8e4060b551c033cb83968671d67a631d5a8db1ed26616c7169565b49b25612e3d20a95d6c8f62c91ed5d9bee49a7bbcffb315e134c30f43e39aada806a42996b0937ebfc63b47fb90ed244676923dc6ea5797b7d7dbbe97d95b3fda6389bed76bbfdfea93feaf0cd9b879f9b1f5f8b20ffdcdbf4df6eb79b8de3dc6e361b7f9cedf61f9cdce7be83c5e1bcadc4d13cbcf98dd36268d56e86fb36bd71395bce5bb51c9cd2e4233992a6f8b534793dc2785de38ff8b99bd6abdfaf37e5edbd94589e1b0826c5f28496a78a24ee6f48f1c3f0ab38019c16dfa2591ecdc32c0f07ad99e539b33cb5da74999baa1984dfc1d6b5b0e41d2ea873aa7a45eaf454d114f20d21685e8f0dba96b92bdbab1d3a10c1f27cff9198e4f9184d5dde9f9fd735d66eea7ad8cddacd5a9670b7e5a90f84e1ded348ad9b56addfbf4196c7c4615dc872a03ef7345a63799e8abccc4ded9c10b0c65a972605b39526c766ce1509f76a5c8a15661d019d9e2b808839db6c2d9877ce172c73540f86e3388e206e1c6f37f043d87e1b7f6e1fdad20c51aff822596dc589b83a96e60980cc9c2b7a39e470b1e47f3f044b3a9626de552a8cb07e6bd724494932d219d0e9999464cbb1039950e63cd9a71daea54ffbd58f6d09b7d32c7e00d42b2ecd2eab75554b93feadd83e367357b8848b7bfa549b389ccef6868c1af7b82700eecdd0da30188260f8e0beddbcf839240e79539a6389bb8921589626502ee44d494129acec07b199ab6a41afafa41fa0d333c5534f91d41bd0e98914d64d73043281df9f124ce8fcbb3edc9e3fe7aaa859cbfb7a875af4803f1547dba33adba3743f0fdf767bb3a2f58d73338ee38324589a39557d2c6f25dd2502b846b1dc1ac49b403fdfbd5fff7e8eaafeaebf430ca675e427a5ae542d5198f7831b0ccd6cf9c1cc6daf65f9672b11c0751fdc5ae15838168e0d11956e9628140bcd725d6b5dbbd696281c0bc944a5bbb15993ea5f2bb6f7dd93bc60b6f7e9102d140bcdee1dab1bf0d77bebbdf7de7b6fadf70d3fdc7f0f92b6cbf6be502c24ab6968e159b76f8942b1d02cac4f4333cb238666bdbe199ef55ad67a536fc22bc5f6bee7718da67deadfdbefedd4a7d4edd3d7649f3e3044e3df178a85661f0743b28bf3856438ff955ffd2f09878ffba97f6fe6be37c35e936c86efb90942178e8562f56f48731c4933d73ffddd6ad24db4b6bc7dfb6f0fdebe906c6fdb47b475babd77387613bf5d7edf771345db4e4bd26a2958814d0cc76a06e2a79fa389659dda69354905fabf9ad4737edb7539d8a79b217f7e73b45d3d7f38b6dfd46a5d6f33afe980ede9b74f637583706cd4e198698dba0982aea16b3a0741d774d0a86bfe811f6889c2b1902c1cbbd54ff1715e3ffdd5a46ad4f3c773d137de10d7a25af4582a10fcd59c6dae45d5084858410d654e67333caf67d39573ce39d7daf270acd588bfcd16d24ad404c4748292ad05612daa1be4e780fbfa35c9f28415d7a2ded668721e8e61e7396ebbd6045b99425a896a92cd48bc699c78ab45958867300cc3b0dfca500cc1b01ad5b083ad4567071d6a1148d6ff6a51cda06a1244282aaa457503dcc3c7913d36437e1bb901775c5437088bf27fb58887365a8b8c72a0d8bceef32c8f8e579bb659256eb3657eedb6e96e6982d059248b6489ee0e1268bd70c82dcfa66f89ac51ce5f7bf6409370595f7739ec8000a103a4076c0f5310a4487503dcdb711443c8cf71a459ab26f10ece5adba4cc7398c9b034b107f0b325b23cd6e809a8e9044b14be35ca1f96b6c41dd45c5ba4ba016895987435d76baea508b2469c73b7a4aeeb6ec9ef993455d0f3d3f05a13cc9a026ba49bba6989ac51dda02a7533840eab4d42b2a2d689e54f7e5e3f1772d0ba7ee0f6ad51cd20bffefa22c949dab925ba8516bc7ddf77fb1e2ccddbc7c9fc3652df48fe368b64915690fbb773ee04484ac1977fafe0467e6fb348350315f4fd22a8eb544daa4a626deaf94115ece0c7f9b9ae8764076c2f3f0601c448cf39e7bafeb37579efb74416c9266d6d916a06194d870692500f3b3f22ebbd4ecf23209d7f9ae5f97ed77ad6ed199ae5a9237ca8e204f2b777eeeae3f979feac99415431b4fb39aacf9ff3744b8a51307aea3b9d9e28d07a58c7fd2c12eb39e7692a775552e544e54c61d183cd79ca4de53ce5a87255b9b0bb77dd4d9adc49b6979f935bc9f6f289747607747a2229d9bdf7a67beb0741bdb5d65a6ffdfbdbbac481648e22f394cd904170ef325705f207a9589ad8c37dfddbeab74080ba4ed0ea01995349f18b4d641b9c4dcd62b3d81b4836130818c99154228795214e3d2c87121f3e9c646132a262ce84785415c918b364ea8a0b47e7c98b8f1b50408c14d16bf01a8c46a3d9e7d8016632568031d3a6853252a70e21178c7515e9684a718328ab5ef083f39cb98d18df10cfaf43ea3957bde8395f40041fb15193d5e4f00cbef80bd371615e21cee70db273aa859d0ee5bb1b72b953832a49bf279593a7a5064420419a42254408460ab0d0d4ceb25a7cf9e173796dc21ecff600d0e9498545551504346dadf6da5b3b09903eff4b025d59a4c197db4ccdcbb4c5bc515364ba79a7ac8f79abae56bf7f8930b93b2eb7b562c59a74d39ae574d39ea1a5ade9f72d98d618a4c266f4689124caccc7b44f51ddb45345ba69af7037ad56926edaaeed635a2046fac5e0acca151f238c9c35c9d90c8dab9b556d8d0db242d6480ce37ad585c3ac46949c52fafd3a75a95abf3568a99b95e8a69bd5686aaa9b35e9fa9855c904760aa99b14ccd64d6a522bfdc6c7a461fdfa9894acdfef297b36d85e7d0e4460f13ad1a30615984fae89d1121b4352f4d0e173a1fa7d5d5abfa7149b9b9e0d1cd0d38697d38695ef05fd62e9971b526436d0aec67953fc1cff8831c63c345567a38a05c0803105a1d1c28ae263958c8c6419bb71540645e3767ada9072a53cc0003ec3089054138c175e3590a2581c518124889912289840e34fbc2ea70d2362671418cf0baf2c8618de981e19444e4f2b88ae30d5f0a1695643ca94908246c895cf76eafb75565c7def3131678daa1e767ad6486104a8ffa7daf8de79ff033a58c3ccebc2db3252cc2333661dd5207f0927f411e31fc0f340fda3dffed64fd778f8aa213ba1944ea811b55e2e8337764255d91f63f4132a6a8a4786317efd5688d3404133d06ee2449881cbccd3320ae0c3083a0e7328805e4b1e849785c70290f0cddbdfd296b7ef60b969b9e5fc71bec325cdfaf4be599f96703bff0ed604ea561c9fc764e44fbb6e9aeafbf3e5fc869af7a9153fb83da56fde4af3f6f76ff771f77170fe06e74bf05edba979ffe63bdc5b82091dc4f9b10fded04bcb1ffae0587e601f7cf203b0923fe2ffd437b3ede99292627803794c46dcefd73fe283ffb3c507ff46e6dc3f4c35c018e34b35b8b75baa81b5d67e1f90a2fede9637327fa207fcc510e8fec93f867cf352184d0cd866fca5819fc9bd497a35ce38eb9cf3d659e79cb1ad6a2622ac6d52adb55a4d9f52a36a916c12ce18e7a9aa4e767a1e11c188a2b5873457657b1604cdd092a018dc0722ebe64513ba67d70cc8685a1296445a55abffd19cf37f363224f953d166e339511b679ac59ef0b4f091fb3b58a3b9434fdbc15a6bdfac4496e7664cadb595dc40076c4e93b4f284dc3e520971a7e78cb3d34593515211519111c6f8864646b92a1ed888ff9ea0761e95d1eced5e15c56d9f2c53a9bb1a4d5dcf366a6a5f1e0c449be4513953395575e481a8623792fe47dafe411c0e87c3fdfe11f7fb71e5fe5024cd10382e8a614ed4f7edd7cf37dffbb7d53d40fce79395d329a88395b7c208525e92b1a7ca686a307b2cf660c507efad624cea7cc00ffee09e765cf983ff0b310ee31e7fb8a41c6ad1839bc6e12eeee6bf5239f775f7a62fb6d96cd8cd1efab463c3e1de0c617bbcc9ed61cc79bdbdd9734fe81db4e2070bfebdafbb57e7c3f7e387ef6dff233eed62a9f3c1f6dfffd8fec3f9f1291e49123af8beef4624839274984510db48dbf1fdbed7b9f4bb250822507bffe6de94e3a7ebf782e0bdf77b1e5a9edb7fb9f1f68cfd7ee3f73d7e101a3c18c11a928feb3ddd5b0e692b4d9dfb98732a63886eea5c10978306ab617f1e68d7a50eae3eaed607c57a887e136f2450c72488b08bf77b4e9a273cf50f9352395fafe8418ead7e0fc993329a5cebfccd79408200523c7ddff77d5c04ddd4a936db07d60fa82dcd8a958c9f0eedd1df6fa39f75e8474b1d9bed6d246f4a8f67cb53607ba0b6f16a04082f9f87b9a30448cfbc4aa9566a58bdca99287c6c792c8ff8e187e1e72ccfedc30f432e7efe9c2ee7e7f5c0fc22d973233bb0bdfce18321c9bf8a1e763670607b1d72cedcf6f287414673a7e7d0f61c74e8202411c0357ecf80f1dbdff73972dcd0f3e9e71c7cf1840329ae94625acd10bd96a64ed525d5f9a0ffc6ffc7ea7ce0afff13bf3ea845f175958176cab8ea37bca207a1cee70f3528c8f62b7ea05e1e84506b075b4fe83ab50c21f7fa75775e9a2774dec19b2529e630869aeb104068e8f6eff738080ddd6623396fc9485f873fffdcf5db38d5b16508996f126f4a6f07faeafeaad6d65a2baef55a1cc26215ce103684e59de78b24de93713fed268f3eafa7c192566b6dfbbd5f87e139c7dc66b3d93ca83b2968d0a04183069d7b73b76fcbd8cb14d5e9b964d613d0e9b9a4168613d8572a0596c7fe8902d1c96466e5e67c32293999b476befce613743e565afc40073f0a42801fa8d372c4d26b3e72762e0951cad79c2e5818d93059af224225809c2ea89c2eaa4e17504e17a7e5a938631e7c6d0f6143b839ac9d761387907388acf3b9e3fb817a755cf64bd582a845d0adb47731d4d15ec330fcfe86b6a7b7d7787fbf8dcc8d43c6bc71d43deff4e4e1047f01cddaeb83f55ab37e08d6a4fc8e60f1cdb7c4956edb372d31cfb77a7580307c6f2c374e65ef265c6f1501b96ecb878deb3a6cedb07103c4180d00646c832ff8a16d5b663eac6ac39c5b58a89c5b4dcead223ecead1db7565dd1766915523ab5da3c3971a09d38b21ce028c7f8edad0505dab498406e80122319adbfe43bb6b14debb00eb4add4d90cf5bdce2c295c5ff0e3d746036c8da68e1f00a983ee7bb5d353ebec03e8f4d4ead26d9d9e5a61dddcf9b48014c4d14b955c5033d8377b9d8e3b3d6525a9067bbc978b7f03c0de7be3ba48721ff1435288a8c747fc21ee73a33511f7095f24857c6e3fb4e383fbf1877a7cc2070f407932d05e4540d7bbdae36f5f6bfcf49617540eb496b5df1b686ff7b7380a090dbdcfd08e0fa50b0047f066d45df2a6df722128da7200f0d9c2db7d4d9a65af0fde92530c46d2461d501f4752ba0013471b608abd3eb86d01e03bbce96cdd3ca1520cea73d286e448520cea83e40e18004de2ad059031bfb6d2eff6b30392a9b4661161a5f5154f6ba5b5d25a69ad75848b3968daa0fbde4e4fad15e70d2f3668d07d6fa747ecde3bc20541772002c540030f438e7bad10ea26ee60041044e8c1c4ed87390cbc51f73d82bace9246405d582f1128a15132042b7a7d4c82e0ec11d4698fa44ec16c365bad5d67dda446ec9ab7663d67bbe970b3e32b8a012dede804567b29bdf8c4523bbb9e74693139bba28c9c5d4c679752dad985747609618c37b7727299517a727529e9b96afb6c1f975638f5811f985798fce4123ab94674138b75cae3857b79a0f59ced812008de50843a7954199b40039839b572409d5a4c38e3acd66dd2a94544e6cb993516766649b16c28353ab35438b3a6ee992584d233abebd2ef03c333cbe8cc6a8a01d64a2b4e906aabbda9b2d75ea96ef2938751373b3d7944ddcba3e986f8e2ad261e25cead36f6dc52d373b647f78ecc85562a4e3dbaa8097a526307151104486678a1904a31c4a4cde61c06d75c8bcd39e70c83e73179cc11962462844829aa5d584da19a2758c6a458894dc1fa516d64b143dc46163b6473a581d523b4974a2cbfb853f214abacb5d6573f29a295c03af004c585a0b116616a8c70a5c889d550141f64a6589d5b9ff0c4440d14d05c89bd0046d75e282a0a42d2444b4cc53c196666d66898107125c58a8a3751418465ac091b3067da64d9d8d02f2e3c013384450b5b65a25c3062c6110c35395cf858e2e9fa802a2259b2b2942115a9590c688a32634ad49ab020ab21a26e9389ec5003a680cb2910d9b98ec09abac10e4031e64989255f90d0b6b092c29bae235c61482bae88d9dd28eaf1c5e4c4102b2836a01851608c1c41e165c68cfa24ca45c3a16de5c8c27236e5c90c9f1ca5040a2e4868425e54f0c94f7ace39e7bc556828303acea65c60c1340466e4cb89284e3b46e0f071a1c8281206830418b3179ce0989d55110273468668c51a26c282658a96276d582c35b9e00421271c54b4702525dd11b8b08031e40893530583251492e1b8c102e9852e2caa581f2e35ae8a96ce9a5c61b2e4031a263e902ca12172044845d3971a34aeb0d2289d3868c0aca8126222e5426aade6c89668c7578eace90e16ae5cf03a82302654462dccd808f9e2ca204a97142c5e441449635373cef95a225af51a0b4a356ec4e061e44a988a0296d8176546884c0c102c1d57d67265958dad383e608cc1bab2ea1246851c4537335d50c0c0889a827cb5b19ba8098a2f276276342dc9bd60b3a482d6131a4aa0ea556205b5732ed2f154c40c0fb4182692e0acf062cd0a423fb474ac5d0bd475c60bedc68e23426a48ae96e430aa7206478bb01c3ed64a80e4480c12132f86a41c1143aa295c2fc26a14a9a0d5b25821858cd3c58f3142467434e5785c464092a0a1a234a58da512cb2f2e95587e716be606b80568022663ed2c8bec4b911d5c40115b86a825c97202479b8c05e6bb5a8f5aeb1e5deb9b1be904c2181183034c9429133efac88e2a55575d52a78f4ea16bab285da3e18874adcf5d96adc042cb21428b1713208ca15a7302244d05222b760e6acbc84c8fb0314e5e38db8a10a5302f156a0cc9495847c2cb001d57b735788b48e671b788641e57c8e57153582e44c75ed3bd203bbe80c4384f8009a2428d568a3224c43c29996983cfa8896d11723271e40588181c1f5a9a6411459110b265cd866a6a438aac6c392df11f6a7619d8b10585020a2e1969a8d59a25c14a0c5692202a5288526d8081221c0da13aa2a2b176860db75dc168f66275b966585dae998f1ba0115c4c46a0a892d6c54995965a98d7909123a124ba2389e53c4d8d2b3184b07052048a4f0623e5460e17a2a098b2d4c36a085b15f1ae2cd62a48a7716a4185132c535f6a3e5985d6181b3454a8a0b2a272ae3da66cc914264cb0b81cf1804a23c3c55409245554ccb03100cb4c918c33c91524f587d50a174ec07ca1806542ad05cd7cab671ef95e60ca1985448d96134beb8a051c1e28506cda4cd1f2a3057e25460a499ee090716496b5630b27298bcb894a90205666d6f0182502c54684931a4723b0aad4d5942e410c8f34337e08d510d2e66275b9664055652c924cac21e39a9a408f1f28700d59096303abcb35d34971b65013c5a64c0abee32a87ad8c16186d720c21695329971c57fa62294d6ca0a521d1d25491b09ee07c9ebab0b6d6848152ab21541d19cbb2e2884c912b2b2c910542655f628caebe5ab05462f9c5cd5462f9c5d5f105cd101c2c60b15106cc272739e38a5a634afb418444798eaa3218536d890d3951d3f44214ab1d63400cd2b8343d19e3a44a098db5d65aeb4c6d58ea1b29a663c739bce9cd90f57e7c01911197e398ae4f2b181cd32a2298bc8298f922b201346190a03491aa42e3a3cbdc387e169c002269949272d8903ae35557627030c0e24a0f20609cc88098a00b8c9a116a5d5836ea900eb29b027e6c413ba2844cc70d051c09615242c50a5f6038aed670706c9238ed186bf2c40900f2dd019db9a8a4ac9846ad64687000000283160000200c0c880342b1240c6238e4e4031480096ec4405044938885a224875110043108c33010630030c620628081d010920d20ca510a7d0b9529160b3696980b65c71b089ae00c5a07e59316469478d8e0c21a0b8641927b33a281f4dcffeb16e2d5b0d8ab100e50d15a61492f362fd4bb8aaec867e0be9e78da1b92dadb71a4677114ce773b536636abc0ee6e1f1b6f48025ffcbf93be40b5626c5abdc9f5831f36e676206043f5a16ef9f9a0dc8aa480500a51d49912dd83a0f691f7a010c8550da7480e3c792b7237738c22466758f61f362eb06d68061e57599af88c7047a405acc410a4398b81d82e82edb71f4ff05540e7dc117322dff238fd2ab256369a8fcf6107c75c7a8a80c6b5c5bb00adbf7e4bdcda55103640ce7c75193768fa7d29b0cf5bdba97f460e4818a25f7c518957dfb656482093cef68b824c847c6e548b5fc59a4450690a7b7b2fec9e76b6162e58dafbc8313689e4f572748826626d1f2302ff02f42b65f1b21aa565d7ede559200fd6ee40f069d4af6d120de4440972f603fb01fc47a59d5b0259b43d7e5d5b5683dda4ddf2a7a291b4d2df140ece544eb5ee7b1723b2975111774815129e517922d05308d20c1281b5736a9829e3e06197659365edba74d67f7d969e76c40ed56cdfdaf0f27278aeee12190e7f53f686b6bc5c886bafc528561393138d63640ff0cd3172ad91efba6ade0df3b31a8547d8e93b63b2754441fd07e73113587d5f607f8c2d7812c941e0eb771513130ea481d95647fb0775ab1f141481742b0d09cbd24186c6310f7659d91e465a3e81b6fe94bd7c7241f7cf3bbbaac52706ee294517473390b50f84ff578ab77941835a0280a0366ed1bf4a6e1ad8bfa8aeb04f41498a4ad04f606f538569864589f73e9ec163e82039cddfd50f000472bb62fb9c4c1d7cbd75f8bf95faa9c3fd64427f5c995bbc87437a551b74e9004418e3db58b67c028234dc986f6f1a3b8790ea5b4c2068270183d61adc9ec9e7e119f4847770bbcfef2d46e082d3b9649516f76b541cbfd0989353ec670ff961103f03cae93463b1ca4934ab9312ebc44f69b5bc84436c01fac8e1f9593a53043a9f5e7252f567e680bef730156c65ec5cb7dc66466745c504f677a55db72f707676b6eb6556d5f087a0598c95438f522a16e8bf275d07fd7f6f2a33b176744a56a9b54e7953fa7b9aab8e5d74dc0a0aa3367478c8fe0bfcec8d387d0c0511e83f1c5fa91de6aa120671af510c19bd57d5ae1da99672af56edeea1604de292ef23226ac9f7a4a184f908e692fa5f94004bdb45eae32547960918001d0301533156e16553ddb4441bb7b45677b8db2f0891ca4a489b48e19dbc796ca8b8518001c017c612f9770990dd27e8c0bddfd3f1456b4ad87804c372f824a5dc42d365ecd38c0a57596c9c1f41c62ced9373ce456ffba5e2ec6a4eda0cae6f2ad40614d2dbc1f1d36f93a7c497e94bf611a38ffdc64a64daaa06130e182a7a2f6032be6ffffc6d4c8d00032643c6055ebe8e4f5a1abf01e617ce432b7fb02b14bcfa106c1d71a2fa6acd75ce412fb8fb171e0d5855f5e98575ac082cab15ff2ce379fef6f3d1244909677fba148dcce5c1f72aa519b473037a268a6404418f9f3e69da4b97d07d968a34a826680ae6e6c9c3a22b2fab007bbb8fc7d8f33ed005edfe570b4baf693e2e1555e61fc32876b850085602084d3c6f5c32e0542fccf14531976a24fb123ec5ed0c80e0250d267f747d7c80ac38ce40640bf25454ff77bd1659fa9e6d43fd16e9cd8100e0b520186b207f5366daef8dd8da52d5c0c2887a7877e1b2ab78cb0725a5100b8c1ef63a97a65dd2efdcb8f01ea26a56eeddf603f03ab67d7d58727c5909166b2f2350776252a6d49fc4e6f6e8b8fd5caea9bbf18178f269f5bb43899009bd6ab209ab4954ebaa5c0e8ed36ad97fe1c5be21011785e11bc790a9b7fbcdf1d0a584fc8206267715e1b625595414715263f40ae263fb333b47cc9791f8531a333991bcaf51832243381dff493bfee819fd6947efdc316d14fe565c0129a9fc5cf674055e058b7fd643ccdd5fc8df26b8ace0e5d2d67354a8216437eea3397f46c5872ee9265fff00efa7006a5b7c2e634697064ec9a7cc43b315a35fd2537ad28c8a74aadc104fd03ed72a650f99bcbd06f841ff5c4632dfbd779aa5c7718f183dfc8735b90dc8977dad4e80a7db7e6be7c5a3d5ef0844760b90059e6fe119a4641bcc006f0fcdde10f3e93b972f0b91e8d56711ba0863362450081a850cbc4827787d00d8adadcf9b5695bb14a0d10d8a4eb8bbf2d2408a94df27d5190434c1e5c1c84f1e2f2077e646a7c5454da63188873a852570338373291d78037041309a25349542b9adac25d591221efbe33d1ea90380d74297e7c01b3518f0abdd87a2a36150fad5180c8834f2e7dd47c36afaae3552404b701b1b34fe54bb1f69e1ae498dd1e1e48b2df30ca7ba9a76a0124b4132d00ab2ec9061f3a0564500306a0ba8d774336b97c731eef4206b5dee5985b4bd96a898b998a3b860123d1193e22f5b031ecaa4974b6016704160d35f279423641c5a922813a695b377c7410472270438bb18120e6b3225165a21d37b3db0c8c092a04e9d9ea42897e82394950348a09730af52ef8733ade890e14b46cefc5a55d4baca16b047ff5f421eef1148db82c6cbe3ce0a53acce2c78dd5677a457be8f6277d6c803d13df160b545eca95308143b1f66d8978f84010fa289be79156ba959246678573601dabeb89b2b6f4c4d59a1c0bc1739a536559f59ca26184fcc088675f9a4bcd080fed294ce8cece5abd649096141d894ab4981dd8a0d3b0a57a0064311efa514d6b63326fbc69a28d0817b55625c08f2347dac9be33af9828770dfe69afaddbf854a893c7a34caeb5024f13774a57d022ff2dc73656ad1f1bb5ccaeba2612466bb5ef1a36b7697eab5fc128164b7e07aa1abea232e0f45bc2e6f37c7411723113f1529a5d6f85c719797b59a12e351b42155ff08e87f48ff055ccd72d74549840a903e527b19fb120b1489e139322f14c9f3d949a962508b05ac31c36e6e048f609c01dd58ea3c4fd08207ccce700061292b723c299aae4682a6aefdb5a8199c25a04b7d6f73497e48b92d640517b2c3fa10b8e8dcf8a4ddd7695e0c3772112ab6d755017042f813187a84a524d145af7bca69aca4cd580e9d5ad136b48c6f055b4f15859e55b63bc07689c8e751681b0db7664412a5d9a9036edb4f29d540910c5cabfc2ca201e1b33c568ded477486d7c9c048f10919e2a2dc985c527c54802af9acc89df6abe46fcc64f1dcdc4c057594c6be9bd2c4607ea85308048eae7ce387c48ccfbec5f25d18a798c9c47583a2c82417967cfd2be79cbdf3e03599b67c4babcf2dab05f431254b6c530bd017d07bcb0ccd009c62e694eb929f38027217ba62e04ffac55828c550aa73a3c4b9d025c054ac07c509a7523ab59ae200a7086957c82ca1f997acc891f213ce8a91e0d63adcda3ea1d5ce30255d0cd3d8a12b50f9a9660d29da57e5523365dca8ef2b259abe3f15a829cc4c8e3250f6a14765f2fedd9cc095fd73b7f95bb986288792115d91fc82600735a6185a4ef686ad62e9efb6d2b22520f5d772666d370b17a8d4b3035681a34213d3643a6585ce8ccb5a465b6584ad67b21ba8b205768cb9b64adbba6216ee2bef36dab72d3a95dd7c2cc5c92e32bc5890b0b49968e23ceb19185f7e85d86890f75c427778f3d8fe67b8aba7ee8d13cbe3df426cd0fe7107d209887c647a3d500b7865a64090b1f27f41202f26d39bc08a32431276c2243985522ae21147ecd8a99bc7793985d8a1854f732de232bf195876a1da318b08d91e3502905f4c6d6eee4447c5279108701f8822213facab5a17213b5b2351ce2143cadb430cdda9ef2dea7e033d09fb75b2fa63d5afc2472e61b1b1035c09bcaec4e08b55705c6d4498506a84db5b29f22223b29ac299e30450c90abf1e84a0a75bb2e9f1a148ad5f1636dad132f1137ef7dd0183011daa443b1a17687753c075f5efbe86e60c72db548485672cc168670959f4ef7f27c2bd2bc0a6278171bc5003baa7feab575aca5c9b88a0bef6ef53a3f94214ed95e98a0764df94c815e07bc066b1656761d4a5a107dc7c308abd06ccf5b36720ac176f4c1752ef27a50ebd06410b01426d95cce7864a2bf11e7f0aac54e5312f6112aa170eb6ff93ae7dae3d12f6cfd3a7fab4eac12cff66c7547507136e95af013982e2b4962ebf9018c880ba66e994fcbb281340b1dc84caea4ea421748028852b4d3e90da5b1c83ba6faf1daca66cfd817e471727ce5ca0b9290ab7adacf1e5fbe70c96c6f21c79132a7d97fd2ed39500b1b3c0cb6da1ffbf5de86f9a016e49501dad1231d5b0224adcc393b89551573781628190bc4ff1d680798482e8fd5c7921da1317edbdb415015a3742884532189543bd1e20dd4d0162a5b336760f3b2e30508c9fc37e85833aa9029a16cbd9e974887dbfba11ec96e65b8285cd740eaba0474f83aac8da86b0e8b50303158ac821875cb2775f25e5c093fe8bddadabef7ad5f471b1e026292fb5628bd4709b6b4b6ebe9f945d4d9dacfaf6b418c13f2fc5bb6c247f80c7a1c322f5d5bbe4e915c7626a5760382b3b890207a2f86ccf4d526db418b6d5926cc8bb4de70c5a2c539b189a1873097083ab1e8502ebec6adca8ca1f6f1dff4a195302537bb0ebbc93fc50f755a0577927b9639a004aa0fd1cd819fd20286f271155d271c0d73959ff2c36641c14e7d5232cda45d2b1b8e2ea1ae2ac91aa01b3fc4968bee1553e061624cd4df00275bd104eb3cf317c1b2f5acf3c1616c2858eebf944a936c3d1d5f2b54158f608c15ed677602b603c303ac414d569312e345ea7076b664f085337e42e38459405a200b53ae70c549375e89cfb9584cc14fd5e26a9706e7e4c506671f1d22dbc5a7b85da6f54611f887cb47f1720148defb718481b42b07aed38c608b4768cca72831331f10881e1fcd0b78b3d5e89f2b47bf485284ef31f89737418ea8f4ada756a1731e64ab7e6122afb1ac001e0b2b0ac5a586237b061864c42bf464a845b2fba244a3da853d7b8caa20491209df8554b04eab2c71bc5c07728614e350acd6d0b4d62709337f7ef56776802e1b0614671b7990a4407aa58deb6dfe9386114872cad3d545bc3281c2b1f06eb563dc2868e1defead13d1461b7d784bff120a2ba120b894fc48b211313e9779c0620f289d1dc49586c379123d2597447019e3b6d27c418518f9270c23ef78ffe715af5aa2b685b34288467a31a8caf456e6a27a3d1ca1bb45475b3736731c9cfe7a4b01f0823e72d045c3eb0809f59463ac02d6b98e28c7b1a589a2515e0d168fd30606b08efca8ba1771528972bedcf93e314e9230f81ffc0e8519b0f1b0277e8db11f01e8b373ce77705e953c9c25f6fff8e919234f09d9284fac6e4d6791ceb677cfaafc9e15c00230330fc2d7cf0ca1a1e8d93b394ab516d5dd8570bf628eb08b68cf0ac051cba19deb1deffd3cf0f07325648f45369115aa5cc8ffb1b9ccdf4b745db38027150f769d6ae004139e4e122ab25bcf111cef1185bcd875cfee64261a184ef55075108cfff54597e1dae36e5e1a506ac07b718330712294833f38f0a6afd0cf1c53b5e785ff71e2218bff61d139a325833f0feb38cd4aa86973479f0c836eaab8a7f920b71821375551fc96d79872087488da9372385b41a35df92abcc8d568f22488a104069f6c48b8778cdd1724fe5ba9541d6e894f842d6b892a9142e2abf3f3852d64d4fcc0c4124cbf4248d5ee40a99ee919c08cdae53c983eb656153fae31bfba80a70320d5b8c06c049c31b74869f3b8d551206808a63bc648b9326bf6d797bd40178be0de9fd894809579de50e59bf350568ed3634c9ff7aa82ece5ab12f2c5e3884bb10470930247e79f3d95e80b6ef96a747488fbce228c037abaa13feee0542813667bc2ef5f00f3e8b69930d3824b2f83b58636adcbcf09748ae34bc154a3d5a1463d0e09a3980cd7a8263d464d964d4847889a02f88a265d4c8b4a8772acdedf8b5ab92ed276a4a3da2eb8e0b31c3d71d8bd110f5f0f2860cee4572f8e9b37d836631e825e8fb1c09a9b3c55933f9c5d347675ffe0fd1ba760b35dcf3d11f5f45f106849a15bd96fdbefd3d46264c755ef35cff97b501e1e13ad7a76dcd097c2583d0f2955f25aa50008ee244d572195fface99e8894ec06ad4cc6dc3e8ffb7e17033572a3f8db75c75ee4284f5195db7e539ac2490b06517ea960a681e9fb12248788a495cc7aa6d492379509d440c85c2ec9cce6b5577989181a5e342f6d4d8d22ca380ed807d77489fd0e66e0cb8df8d36db531572742843a05cb997b7f54647992a802f23a6b5aa62851bc7d226482881c89ddfe78dec2bbd40cd23a7d523fae9ba9fb1568bc66298048ded03120dceef07d69a2a0ae1f675760736d1d6fa81810bd97949e11ac71b93df76136c2a28ff2f6edd682adcf3176c02ec0051d5a0a9e2a160c421a44526ac076e1035b6df9f4541d4c0d625194cd4c626b5cedaec20faadc62dac16f761527e4b3d81f0a8c763859f385a2032b8191b62af72c34c684143708341896b3c26655547cb4fe2068b0a697fc6c6915534c1100beab8d12fb097da1fa13fcc9621f35baf0b820b2256e09b43fe6266ee81753711cfbb688573790bddd973c5f5268b7c1cc6f3e7971f8f2419cc1fda7eb19a40ac271990738b485a42513d130a894747bfd1ffb88fa6c3555b83790ecb8dc7774706e618465ba00b2ca8da64f9bea8e36e91e2ea45d94133217b8877763ece6314323a25ca94235e4f7026c6a650e40d84a62c1505161e88d4feade21bb98efa77228f3116b892a3bae8c33268a511024d78f14ad727c65e75f061f0734980d2f2c1ddea564991c7d3315d1d35b27da3c2c55ee6c525092007ec8da08d8c32045512d87de276aa3444f2e670941564869b90944c0f2c1c6d7f4db4c3bc7174ff2a9c07410a26bf31f7d021f671c0261db46c0e02a36ec67e1eb20669b0fb8d7b36827b6a7e776707a1a4aaac32c6c1e9dea23c1101602e2eb3e4345d491c5bef352b7c1cbd8e83e0b0334d6b458437b72004fc020a9c0f28016628a78bad5b0715ea366a0d4a767c0b6a1b0d38138b1b257fcc5a76e0cf263a3234cd68bcea0f5f8a178e32fa494388679bf496e29219fbd31938600ea5f514349970ddbff1d1ce19c2e9210715116dc18efc7d6d6349ce96e474e35907ccb9121f00469b5d5ca925570903bc43a2f837cc222674c2f8afb2d4c100b7a4af3d404022f9669420269c891fd77f00b275019a040686db65722e74490434705e934ccc2f3cf6670423a296c4ce995f504ac5c13ce9df419a48423fa1fb33c14fbf3d783f57a2661cb413151fc0939f0732d34d1f7d4184c304697b9f155637fa22e0d7f36cadac98c1888e5f123f70fe2fef58a32fd8eb41b927987de6e2b74ec0e404caddc24a455fe07f65e2559e17f445af2cba2b825ab061f6a2b96eefb6dee3bd122afb72a29505620c5d0df6b32571e3195fdf6a0b2d275ca65616ca81f31b8f37f807f66be0b125a2fe0f7d499fb724a72ffb5f6f1a800106a17ad78b95b97cf127ab6f8a1adaf8dcf5825e3e654111e8fa0794aa6b210d6406e8062e7a8f1ead1c739ba5a6d74b58872dd682a149ff2a0ba63aa4a3c565f575e676320111d272ab7a9f1e0405e5ca49ff159d61fae82c92ffc2f344e842151a5ab4864e9ffc9fa5f9757901c2390a1dffb208e8a2c51d9107ef94ae5b8ac0a4f61dce208efcdbd139713612907fe1c3829b445744c92e956e2c6b236f21cae927e67b94b9ed11a6a04bc24cc7565047f6f77f513a783a1e8e7205251d2973dba8538d090c3277f355118e156cfa6f18b86b78deb22462d0503f60d51e51ba5987e0a3a5f79da5a5b07eec0cae04488908e014eeb9a48592406371f5d9def953427d3c545e141c6a0aee840a0c46c421b172e83c95a079376c7f20ec69d3c560d9341a6aa81109c2b544f393f273245f2986d93cbedb7e4a8228b49beab4a0e57bc578829fc4017d7385c3fb2f6b64f2dd251f4f0a3bdb8b4c074d27172e902ab1e21e7dc7d0d3057144c20d7cc07b463c34be100e703072842d6aa69ee1b56443c739183649510a8d2c4b098d7357974e210b988ef3b80558f54e1e0cb43c267108c90083a232ff78a56525d9579bf3d2cc41d2920d46e3c4f4190a3fe5821d5cf0a98ea364b48b5f67d4507de8474880a322acfdbb3a2c451d2837fd28513857b31aa3b58e19d8d5a96d4c64b4568cf782c4f73b9dbaf18992a8977a4baf477d67b6c76355911245b7510b25d4d13dc899217f21b7cea8702ec6424b579b8196341809fc55656320e84599b969d9d2be042ec4d38bbcc4035889282c59009054c239e3687a68620505c8186bf2415a27f5111d9d250b5766034ca4c88f9c3b9b5d9b235f98c41fa60be5e0b68b27d247c5231e96d92750517d8460c5f0b5d8595eee19198caee9da39c84795d778e2129fc520735b6faa516eb57bb937353f6ad74a9b05eeadc1aeaf080fbe9f6ab82ae7a0564f8136c686af052903f195a58dd200255ab34fe793f530c29f27de156da9b865244b5a6ccdc07fb7ec92847d6cec0d0fdd9534930070e33d0b25660bea0a570564beb9b97a832359a0ed2ded038e48e2e523979a049c0c4e7d644364288195a14b987778bad48e4725adffddb89e73be11ee9a5fe20833612b8e3364784cc88014dc2f304ebf2ed83b0f96e40ddbb68abe3617bc1e921605a3feb29d96ee4de4a8d00f72f538073548eee6bd80ee05041d03a8a282f22051791f101130a4e8d0da4f49692b5cc78a0726485a1e8586b47f6e558343109950fd701978f53378b619caf64d8bd5c83a02a355b7c6bdb1b13616822cc6555608b319f1b04df8996d02577f806c247e21c5e13742914cc59a95355601abc1346d4602fbec9a2ced1aa30b6a08d4f2ec45d5f3cd8bd0331413ff455521e03ced9d2fa409bdf2dff54605a77c048d24e5bf51ca143cae166a185c54ae521c980fa854d53f3ac0358024232471ee2f67d9eb97b868ae28e9e2365eb5dba45c7b3ba880541f1b60e4dc02d0eac483b4576bd3ac86e70c2d73f079795f60ba98227990c75c255feb00b118324bd051b6e621a6f853c5d84bbd13be2d5dd3026b7010bfd1dba2b3117e4074270502a0f7437af9d393579a92605d6e274d32694520c571bd48707518bd6e5b8c724d905c01ca461389504205402a2ec78345134cd9ed9e821229676bd20a64e90bb201e6b53873313d4fd4b9e177bd14d81b425105ba0d308f8de89d1c2a6218404ca4a9dcdecae5f66450b14dc995a43529d7a8f206d511bc98085dc9ae00a0abde14828d6244abbcf04f5afbe2bac8de825ceae7d950866e0c5d1f982f6e3be8b2c28cab04b44429b31f3b586c7d06e8d72dfab7fd96da453f25e11f01091c6a175343c6a5c0ff7daadbbe27334c6aaa0e6e770e835ca5a123ac9739ac149883e3d4f518459d6962fbc0d72247b16a133c7d5c76400332c83fcd5ba02502916d3aafccb085efbc2af60ed025315fda4c97f126ca656f1723132513c747e1e2f7557817a3310c564f5f0b6de8d7c01cd035fbbab8fbb5759ee048f40a4c5c64ad2baf1f17525b47f2a9d263ed681443d11c599f0fdfe2c57f3c4d23b07a905583ed847ae9b3661615ef9aa7f1626269c8ed3eb921a11d30c4bb49440a913e067573018b0376169bddfa35ada430191ede6764a549ef460e902de468186bdddbec0dae1267a8d07ca7d993469fdaed562ad4973f93e4994843fefb4053050e773fe4d4a8ce2223394577f2faed9485582819aaf69d44e609fd1f9de88c843201fcba7332c5a758f529da524755291c2f77b198c18f7cea7c8addf6be802dcd056f4d5d0b30d7f20a5fb0350d8c1c4b478896efea3e22eceef4d1212271d99e55591af624c6739baa11bfb9d0ff243ec1a3edcd91890638bda8f542fc0a56ba0847eb28f6c1f94ed6d82f4f2edc1a562609eb1c536e93d301b4d257d6d30b10e8888e64f18ac4ac6c89705ea477080ba90957dcce6cc8edefbccdceb0f7cf9db41650492819cf796d1f9d31c9b9af26340a383c77b09fc2c9499bd7be1b0c018f29b1f86a680245eaf8c3082854edd88c9f8aa3617a34e51fd0f6552d0aa941c128e11369e40dcd4d5af920508227dc19cdb2ea0ad03f9272dfa600a51b4f888af26b9d970bba02e3be061b841ac241b2613799c2c7bd774edafba545e3f5706577cec7ebae2e36a0c8078f0110db9c92494f11a8d5f93c8a07c555a64dd16c1e84b874623c0f4dfe4b9936ffeba4f11e6829c1155a54a1ca525cd9415ede62d5939ae756fd06ddfd71c0444afba7ba3140f4550f14574881b88f8e6aba6d1393185a8dc42b53d6b6ec6c342f89c95c496aaebd543b437f42bed929043ee83bb2dd5cd38caa7f135124fe220cfd5ae0b454edea42ddc636f02e82052b3e050a5be7202dd98d45296c943118876291f0e3f12635fc61a904a04e8a118c37cb5673ebe0a51a142153fae11110a8be8c6e5a7622a61776da691d55541760287e9c5692a667d31680fa4ace20b8bf37b6fa1f088adb9e7b675bec0dd5355e1b65399040d36391c530122ca7ed268f7519675babb42e393753588302c11f037c84bcad6b29698a351a06b2cb0a370abb72af970f947dd7676365ccdcb703b356c5f5481c69cc5283a2b4d6e21431931934462f2632db8f2656d3270d740446c1aafa4f91e62f8865783156ba5adc91f89519143db0b72f0826309b220aaafcb042fd2350da406280bbf544ac02805dbf1047fcc3780d40613a4311b3a3bef106c325cfef2f0a11b80c79947a80d6260488a95e3f23fd00c7e9a0469e338037dce08de9e473a3c55757dca81895b3b4a3480004cd27560eccae2fa59195e161353d0eef4e3064c98e1551d6325020684241942b443c543865c1fb087dc3270a62cfb1d8ca7b07bd88a6a073c17e3ce118d03a6d4392a06e044b56e09f862861cb18420ebede939e701185bc3f6f8848c08cd5fea4dc1aa690bcce40e8504863773e85c59245cb81f1e134136feed56f360972e9402acdd29c08c86984405877238f4e6f4c1319d685b6e1d1d83d88940ba87a51e90a93104451b2cb4954e7e4d3806733e1748c9d113e9a50c6ecf19f6e9459fad492547a3ce548efede6226b8ae563e3b90a396afbd8b8fa1a1989a8c67815d423f878b6b57de154e773ccd7e7beb19232898ca77bc386bbedc03e9c2f3da617312427198d608f467a670fc248074e643b6a49b5b9536288df9bf0aaeeb43b5e88c3654493eaa6d91b5c85bbce148c4d0dd0e1f3e47c64a68f8047c61748f65fc843afb9519dee3a23c100b458239be883f58ca79721df972cce332ee8c6b6aa42b05ee7072d6b7bdf29dde97fb359ae69f755ab42f0aa5ce928a3e7006dd95a05a11fb223c6d020f3aeda38ea520139552a35a550236b70b34dcabfa729503076f2dcdf06c295feea6050ed631db191502553eea587906b6f507500c5627884354bb46f8cf47fc1d9efbd1474381f20d17396b5d4886bcf5a94c8a47f01cf1b564581323a7a640777a6012f74092fe0eacde0b7b53180593af5cea4a5ca8727a079b8917bc9e13f07f738e4c607d2b187eab110739489e3113c02714d6643b7ff155c7090a553a25f9b05e23dd45a195c0b11894fca70441bf207a05427414252a78218096feaa00906d3f702907b413588a403989d19a796dfcb4c260bacb7969b09910a8f2d301eae55b5107f2f18714d87ba1f7e5a0bd567d3776c0dd46817fc4f88833e1d9cc1950462771e49cd09b4091bfad0abd29ccbfdb34f8f0b7221d58e4a27ff80326db7ce1fac611fe1d424d6dd6c95fb532b7604bbe20b7e46f6856500bbff22f5f44e38b2adccc71d7d6e7303b0bb64cece03f19aaf5221c0e962cbcd2216b7cdd537383fdf5617a3f9fff4225dfeecc4121a65b8741997cb7f000b4f71355b21190c9f842c5ab703b4fd7329b73473595646c153ef42e0f9a2b5aa124f6757554ba716544e0e666808c149ffef38c484c5c607a7dca4eb984b93e1a214eb4a0517cdb86ce18175612193d2a6b3f720ec661dbd10682bd943f7f033438b01ef5b766787bf82cb96fb4617be4cc2ffeacbb026494bd9d34596a474e0b377b512fa15539bb3750f9a926769d0a7b306f670316fc95fcc113da5f346f28a5d1641046758108b16804767ba23c0a8735d95aa7b9ab157982e3679d75c644405543e9c7fa26e8915a1a4dcb26d21d39f7422b0647ade0109b7524069599330fb217679e6fdb200b6c5b04e753c135cc2952f771f0430abacb30e0039c1d60188e22da728e044ccae1aaeffe2c1c5c301698ed160600e2ed51500e11b0e411d29666cc12ad22d6e9a932386bf9b05af153701389c5e07054669ec0c1ad977136c7c6af6dbd1f39a7db70457032ca8b2aa6095a02c35c6ee8162bb7e708e410c848c59996f6ac23aacd38d40af7782cab6174eb3326f25241fa3058c009a0628c65ea301aaac57db66f3c7ccdbf0611058641be61fcf53a8b51f0b2214bc63b73b635eb47a850a900745387cb8217ba3f1245254dee01b40b51506ff2ca03604903e2f6a4ef9e27c86b07341bcd6315ac269a503ed516013acb7b65a966a3dcd6885b969371eda1f3db2fcd3835282e39b8364a55b3eb86438768620ec434f53954c3a2db169a0f77baf381ea4bbd76da7d666094f7af95429cf9df6944ca019c66b0f1d79459c931932a614be944f8a58b6a196921f60150d4fe40d0a7da44c15862f1875e42d9df0eda1cb28e2ca09e2b8cf3efaba211d4a0fa2ad598fd826316fc2fd79858a345dca05c91e4189013f0ed8341dfed259a41b4eca67f197ac50ef222489e576ff3200e07b9d0ee7ff97b3b44494fa9d38eeb32e1bad6d5998e731e76b86004203e742f41015861adc6034c4be5328c237adc45ba9ba33fbd999367fee357885ecfd11b855271235cb2b59101068978047f1f1b81fb0c9f6d6c536a6d6e3200e2b002f7586f7acc85504020b3e2e9db3b9b8e38645bb6c75cabb65c903c2101fe6ec2d8f0373dad094003a20219f7762a18d75cb435101062f0c8aab32dea1f0e186a0c1210f2ff30418fd537910d993a83fdbe27f737d1482d83b21ea7f9dda251a87e85855834d3a002d602007d8f6b1171b0ef33ad2a51f7bf58d70925ed8b14fed20d83515076b219537a725a612ec017e30afdedadddb8555f1da29b567f012583c892fdf4a1b0b5866d21049ac4623b390c6867a670d921890bcd938165c21ac18b1e5d356c3206e77047efc3c7856402115583beca34f98f2f69348a13e78da6f84d24080878588dfb6bc94278efc26be60347d5554db618163cab57c31eda00a4f105ae4a64924c06a5175102141f06198fb7484858fc7cb3aca2ded809835ef0aae9610405d369c26a8ca89a281506a4a4446db47958e3b66cc40f406758a2e653c0f056ff71f789439b5312f2d420b176c30849abd12d2ec5d56462141715e7636e7bf4b51dbb162283d5a583306737e9754fc1b50ffc125f393eb113e5a61682fb2886cc203e02ff97109b82c36104762b6311b9478f02571a5763195c7916056aa69ae60cf8ef2f17d08fbe833bb07f7084dd971daa11ab358485028405d3d0558af8dd6e05e4c9d2bfbf6dffea6aa42e39767fe391bb595c838ad96998c0d4eb6a10fb058f4380025599d9f9fdfd87b7f7fc08a2dbeb0614a1d30db42cd8c7d923c40e1beafccc49402829d0133acb4d5705d0c4ff990d99e82fcb44d61a218e6d9a9d9e642fda6859edb2b8aa855782d7d16b96076cc520c4401492fcb221fd62e83c3c9bf0a1a76ff5bbef7b70134b608e4f68e8edd9f492923ede2ce64b0c5d1b383aaa55aac5e6ea4f43e7e19284bd08b4ca17c972327358f4e823701ec77bad8cb928d9c8c21e27b8e2453984fcaf8b533ada8ef16f5d27fc3d71635e26c6b90de877e7038c802f0eb47ddd615d4c4a71e82ae175233f1a64542cbde2fbe5c0ce5fccd8e5eed4a09e651987938f1ae66468a71ff2402901bf4bb343ff64be3b402f0c1abc6007941c8e10815a47e42866ecd2e86f58803268b3a020a8e9cf4642a75d0dcf20d2eeb93a7847d3becfc64f50191b4f6feba52b31a0b2265e93e793662086f8226df52da075efebf4c56c7b225e278e73c411d33a085f092d069307a7e102837531c26243ad2e621e726e6edfaaaf54f7e21ecf95d7342f1ec10f7b66b0396bc02c8cfa2706b1eff62cea4a45f680ae657e82043082d3aef272f9a59bee468ec3b50d70898a664ca425422c49a69b83e8650129640ee8c8f3184cda886177ed4cd201d96ed713bd40c83d4da34486ea03f9e15c73dd1338bbab187f087a3aef1204a487cef19fdec1838ab93712211ce34c65de0f0a31c8d48f53ddabaf58d19bf48ba997b469c8f9c260c2719f8de4892659c0a18c21bdd64b2367d406b78b306a3472e54b53b208b696c666a446ab192a6f7df665d24f3518172f9dfe96211b9ff990653fc232d96a2a77f90f09e80e8a2323c8d7312f50251374d33d9d73103644a485826c357f10b9fb57f951f4306217ca2c1422e85e0b42bbcdddeb43b840f6e54d78d109a1367ef91e5085fd691d597529f01ab92f4f9cd7bdd10759dbb0baca107b9f7a5d959bf4a7f5a9c0937336f70d84333c789944f6808a9f78834591259d704e5484ec5e219e5c61e7ce55f5d868d5b8864b7a7f8dec8e0117aafced08b5d7ca11b943fda8865bf3c8221a16c01d0b5a5b816e790450bdb7fa30eb86001b518e18b402d8c3ce0f61acaec018dcff3b0d89b3ebb3d65eda1e6330299cb10aa20ff9bdf37e055b34869f1f2beaeb87b3e0ccf4fb5da320fba3a1d2d201e80ac74ca12485d437cdb1aefe59c02f9f9237f8e558645ed04abf9c03b7e2b6a0be12bd8727bc0b1cfb5ec027229215f856e47e062c818bd30cfdb8480bd7cbf8297aa4078db87c802b6c2c48a8341e1031387f4c7d6c9eaf8dc3a38cdbd41370802fcd3aea702a882a6f4f01bdd9ebbf150a52eef5e12640be3cdeb2b207aea68fa46eef97091aecf4f34251680de4e805597a9b60f011afe2d2004f83427af8308d2eee0a5a8ed627623a2688ac557f4b98ce12111bb7d05c7b49a8f74fb4b06ebb9ea02b56573956a1087f0411d4b3453adedc89c8d34733cebb2aa663f7a258a10a25c8573aed45a42bd6d83ebd90a9f93b1dc2513264a8369a4cffd1fc1d398dd1debb87760bbd57c423344188b19e4ed54cbd83914afc5d5c4ac73ae7f1e42a410231c5653783dc7ef2c56ad252e2cf188cb42833a73b85d4c82f40fba2ce2cf968ccc621432fc735484914afb72186b98fb81c1484570af2410f93ce2d1ccd336770a62bfb53e0a65a96da531ae36174fef919027f2f3b1da15d2fc3d114dba01b27a4cd51dd2253f5ee8f80784314877f10771339f4a84a5790fe6142b8836ecc878c3bd12e10cc333c484122dd89336fd6f8061a8b0ede2c60ce2042327682fbb06007f67b6127ba9e1b7a5d8d0885be897ac81afb3898f80d5595ed24f61167ce2ce897efb164b0382c74e61abd3bb85c57330620844bd12a0c85c3807e37476ee2a87125d2b65e89e3b73bd965e8c24688face71b7f8602463d610e8544f3da0a4b7058233d0625d3d6f5686e617420c12c495f08ef9770a210e75da709934d150ff1c1ad459e383225183e843dd9a0c933d674512a90fc46cda96f9cbec43ff811193e4c017702d780d0de8ebd907c24858b8c317ce32d193321e1ae4ae2ce5b3b1de1d5b8367e691688184ba66c301908cae01221ae2b4770a02d73984b765fa959b1d1b14e63403616d2ce268280454cfb75f483ad1cde7fcceaa6bf73f0b8853966b12279a1964d5e52494c8114f7233de02128862db081e691efc041b652274d5fd5d8c27b7f3b6077f966701e056200bd8fb049318403518668a0c64123503f7cbb52ac5e8915178afdad3c4d1baa7bc69ce643e471d5f9ac9c60c67aa4fd9212e01b9925b3d8c130bbceea447ed34ebe043b54354439abb17dc5e414162109a76f9f1da1a7f55720588030271d53b748a27ad11b7336c0ab33ae599fdc47bc07239fd2d15a12afe6c0c186abe656c4656505dc5282a88c68ac3685fec34ae7fe212e82e2502245bcf73036dc28f428be44ed065cd4c8e3030150353c53b0a9f6f325981d79f9e211fe3755c97961f3274fd80e5a152e76b53bb7756339c2ef490f252e633cee786b6fe35567d40e0530ea6bd990f30f3c6461f2d70decf61a29d8ba770dd0c225ab580411905d3676a644c84eea2aba4532c5857fe6acc113ec7d4de6a34464a31b7038293400966d392562f758b3f8ec8686ab35c4d2fdaade368843e71890ad7eecfeca946023ea8b30a91d4846445886e65766cda52e281b1e61419b50ec5ec20d06aa610953770c0b6a2f72a2c412260041d9cebc8ef864555d30c35c3b2ecef2696bae57df77d9822a8a2b5b55b3141329f8d8af216b8545b4be6e361ea0ab8980e17c13a16b33a505fbaa7c814293265328c6f554eeb20b1ce3910b8b08221b56ca14d27317dd8950356b8b3e6be4c9cb896c9fb5d1a5ac12757a3f552cfd2952803a20a9a3d33b4963846528bc8b2b79262cdd0155b0311c717cd4677b52f1d530d24a8fdd2e5277c092ae9216eca932cfb1286ad1c6e1fceba9a9cb49ac77f907eb7d872acf7862c14ae03daf51fdacfe1554657daf423de0396a7820a9165a48d93a2c1f829de5567333d2e9a2c8f268b3762bad58ba9d261f2224ec9885b8cea072cbda5c68e7485b656feed1de7b4989886d74ab7a51528ea6968400ae716cd0ec001792635d0b1093d640db67a69765c8f8635346d758880cbe6c52c885cc51d40f35ea0035189ff57566a25a406dddd35346f6bc76b6eacb834b790fd7772097e1942f361a690d7e4b047ce825d1d3b38147a86a78affe595cec1eb428b6422ffb43bf59cb274e826a6f803ddfb3b10d5765dae10509fcaf0f0202da2c767b4434c0b75c152061bd2ae1f321f71c11ea91ec5ab8fb0a1beb5e7060df9b3e60f5d43baa8adced7baa6c291ee3964626564ed8a4fa173ca79d9ed4a5a4f8d34e0a6ef30dd98d03d91ee09d7dd2cdbee57792d98b97f353e394883f6b5b4b45d5d8fca18c7d369dfdeaf5a059b7fc142e04ee023bfeba983f5e355407c8941e364fdd54519e3064bf75c4f2405c77cd4cc9aef6b8ac7cf0c7a30d6b20632b69bf9840587b918cb61013e8b174f4302661414c131fddfacdb23e3b89a28c7d741bca9bb25a205388680f2ce7b00ba18e0d5170510b5da549735da2e7864d7380fd4076556b159800687b543a9b1480e0cfc2c4794c58ec949ec1667c542472a2261bcf0230d41e98f15970c5c09781dbd95a6fec3013e50a32be51814dbbf1e7a046f712918ad4a7fed164ef821752edaebaa31102fad4f21e797093fa446052491bd1e41135ef674b8a51878f20f4f7ea0e1ee474206efd17f6930dac66336d682910b55f876053b5b410a9ed2282a5cd089efd405b4e928952f9e02861e544200fa13748aa759be68d828621bc94cffc150e64de7cb87ce518818a254394d6d5348b1a693f70fcbda49a696c07ad8bf37de29514ca5d6caeb30e6698ddd4758405cd03a890d886064ea7ae74c9ebc5555f97a32f94c60e070a4a3053878f277d134e0f9dbcec49191c5ef60e9b7c268a0175957d0b965b6f0c5735ef130b17df5a5ed6652ce0eeb467cdd7d4860e35ed3fd6451ebe0265323983400c883c6ec7f13058f1e6b767dca09519a9b1861ce1fbcb313ca9cb54d993fae538227f70caba4e7494c2b7824dd793f53ec0dfed59cf49c0b0d1f18d3b5283b973e39ca7f1884dcfd2f5c06f34be536b20160523706aad6a934f4d15280e87f76e00aae87f11869d6ab857f0b162df560fb2aea56cf9659ef649ae54264b8b9d0e8c67f48bf896a1225105cc046bd1fd38abc0911b11cf3ea1abd45eb016a64899f608672e7f0df432a3049360080751832edb624da25722a693df9cd326a044aa11c733915c4f5b7e03ac9f0694aceae5aec2f2a91386c24acec6fd3dca56b6bbf40fba55be7c92ee21aa53458057aef66bcb2355d629b4b959a256dcd14f0735a2b26720118004610c00986a3660490919cd7a601db378c49cc9effe3ce4621612f2eebb457807335dec3c650aa08ab94d0950af54af34f1ab56cf01ef876112adfbb8d24c29646602e0280ff63ee2f8a84ca06d18ed316dd40b6d22b8c4e97579e77163864aa46111f8e875fa138f0ba5c7748b825040c3cb81429ddcbb6da60ebd76e8cd47b88f70cf984b6b44cfbf124de5d32fbf318f2d4d7bc55c69b9b1b3db8340f43b9e5d6b28b823a4409fe7e4b7856797ea81d2206b2fca9b02425f2c485b5646902bc9cd6a3ed59c7f97db1f283558a2920d681be1d542ec9f121aa759fd97da78cad5d2e4c7773885cc161a8ab3d0273fcf5c458ba6675efa4466b471379bc87a5e7934723b55cebe31aaef7d2864c862b1b4d845084e9d1410270d1f08b19c85882602c4b0931914eeb321393363391792d17996faa79d69950c88412bab23b24f5fd8245a480022e89d94b130882a1ec6a67543f2b4d09afe8208e2c9067a41f44e83a5679aaa41a836cc25646c741f838627de47a469065c01f2d3457fb24f6ea7fa4d985a61a593a15b0c905598100a4775512102f37c77394d68b302913f06249cce96eda6fb5e210ac95bc69b48bd5d21c25157cbb8e0f926cb5a7709c36b65e8e674d3873542f60d015c1c600d1ea0c652c37c8ee8953c53f01f3d4c3db9c0b610794f2128d20652a675a876e90e35331a5c8e7107ef1805689f18ee9f942a703f2502540cf19f236ca1da24079cd76fa8535fbc4c9d07ed3e66209af7bb810303e88d400faae8ded5050e58a27a10bb9e464f397cf02a4f8ad9404e96f39ee0aa40102545e32291d61abc1cc7b79cca33d1d7c4cbfa42cdd2b7fec7ecbe4469b1d1d27d2bb21dee5424ca1d3fadf041ef6274855bb7628846d2f52a12ed9fedb6cd053d92626e2e5a3abdf102d69e1903ec68401d213481a205e0a4ad489cb0642592ba55f8708684abbda1bbf2bc3f59122a2b312c599b654b8fbb032e6f86509b16a26032409d1d655aae6a4e3d7264f658f531a7f202067ea9648ec45689a37f945e555508da2191cff7303df1447ca26c2d0091958bc86395a8ef40f48d07378b9a4322fe10c9fa1acd9dfeb16cf82410ba6c765193c10ffd6833289973d345c77301caf9dc61cf237c610f46f79f403188a3628c73ebe80e2596dfe226be6228e044b87727089c2a383162455e4d14b833223f9587c24f88c40376f6c6a9a63b19888a3273d184b5ae24d922a969643a504fcd8ae39ffaf008c878627c02e7417643a6753bb6042c0a8092df5eca0bc55dd42a69dd14c4dde5decf5f61b7926ef680d859fc28a28f6004f7379c4f4a9799844972ad712f9d7b5aa18f224b4b16107ac5405bbb4ecc7f1bc06c2c330e91221218579206398d2a4871761a76881b9c941d83244e196ef15a6a0416d7f42d2324112956312c0efce31cb577d2243c4b216e94bc302fa486c59c2fc1c05eebd4ea76f85cf0e3e01764b4458984bad2221c3e10ccd484af5d5eb33f16465b175e077cdf107346a3e092914ca22231b3bbb5de158065c3c48ba00f405663d7704ac4d35c01127fb176f4867ac3c928d2a29da790750163bac766514ba548c7d6e3c1cae3514238ddf1bcf6b57761db0d86e68b1a23f34548f9703dbce58b43d8f456e50942654283419b03eb687f6974a600d4238aea7463f3efad6305baca0cdbf64574c38108b53530cad10a02526042bd6be7c0da326fb43cd716966ede2db2e731d91f59639fadfc5c89e5558b27716269a3251068193d1eb4c9b4c2adc6b91974e93daf04afd372dae9c42d8ce7bdaff893353f8031ce7152ed2f1f099c54eece12b06b9d0c7844c957c32ca73bbb981fa9f10799f108d66b7531bc1835ccc8562ddb36f54ece1c6daf05a7f5401e14aabe8097b87c7be2808f78569f020a5acd9e0f6fe3c03ae52a5922fe9ce6eb7e021d8bd236d761939d99ddcb0db61d1b6f8750cc84f84e19f5b90460a5b6fcf9749c3f6ffb09e9dae35c40f32cc8392ade4888b8b53e5ecfa4e17f4b603a514c31a96378669835cd002dcf136b386f19e3081d40f4225ecff15d02ad712941493baaefe504d1ab2acdcbafdb9e3280d412e322d700eb1e5687abb25bde646f32168d17ce9eb84963a828b8c643db9ccd468b2658b13fb1e258dba7f31501583a25e7c1d0a8bdb4d640ec882eae498d016147ee2eb9106356e144776212db2ea04089b1f5b2ce93a355bbac0e56634087bd0a9432c84332d34161b4a266cf1ca3cf6bfa740d4ee55f7a49d1006d44b9b12c241beae2de8665d078b91b6d400cbd2a6810235aa8c48f315c2ad124b56040d1e15a7daa0016b6bdbebc051eda83e9532330a6c7fbc608e18a9b888cce2437e79d7d2603cc2659e11a0751ba2aacf983b7ea7bca2bed6430d257e97b6672f14c7d16cb83e58d2a57ecc85b6fd962f491eb3814310644aaafd5663ac2eaca95e381b018b042fa53cc2467f29d3f588ccecd5bc1350f516edb154820f005ff57a0983492b21f2c46e7e6ad205264411de2a3cfdcea3ea64f0c0283574d2517b29a8f5ec1ca19885be485587afacae8a37cc45096fd99f6b8e4c209c57498b4084fae11c7cc6a2f351c628f502f69406bea24113b59acca47e14579d1cb3e80d870b3cf89637220735dbdce338d03719d79538cc986759328a949184aac5d5816a41de50e705ab86181864babb3c192997c26e63756144c9de82efcb42f1c1f081fc2190fe069d0a5a5721d7a8079e19179c750175a4836e4d8489469cbc0e1830045dd2166eab81b0078ca6a9c9802834dfdeedafa5d213b9a2e345e3e223921a220f43d6195ebfb7211d5be762b54468075b53e894112e1ad8a8898ef3e7c68f3d2d9e4921481d896d7db38f8ce79a0c9087ff7eef94441f9b967e33190fabcb98c3bfe373c8ffc85cc39260cf5667330396833338c7f8d4fdfac98c4d8a668f06296504c05ecfbe03699b6060355772c829a3b59b0f8533b436500af59e30fc6cae3bd05d32ba9ba74d786402ad69b57625be9d126e846c9c7b61685e31032cb28ad41ba4573a1d0524b0ec1177802445019d5b14308cee299913920ef371fc5a17b363d682f6d9c59a30ba562a155e8e0bfe245f2e12fd72d9c0e3e90971911a9fc78c05d67838b6841e881607367037688718c5289f0d69f56677d580a6fb2b098b2bc25e31d89e24a29a74f9054e5d4ab916f011a6c513388fae51f36bf36ac618576ae71903da9e9003f8ace60d53b271d264552674e8e4626f3628e5e9939ec83b72759f2f3ca3eacb54800cc52b7a19165e023c8c67b9fb595871a8179c6936c015f3729dbdd56acd865cb322b68295272503f3214a3a77e569a582a40f5f988235702517f40790eed45bd4e72a15d44bb8926a589eb95ccf1f3c4bd109e0696339ff0d106540c7516c9e1a32830051ba3be9b73be0f2e67c26ac0ff1d602e2c4860010973746344b36bbe4d6e887172f18b069e91f25f66161711a041aed4ff826d1b5332b8180a10c2dc357721889c0acb93dba7551cc32fe5ab484381075cc805842b8ab1e16b43c2b251ee0ca231a489c0333a6b9c4a1fccb347f4b11880acca838dc8728a487c72a1e1516ea28e17b38266574792516fe140d24d326984ddcc70f6e2963fb0c3ffc0e0f2f43e955a832248418f599d7845104e3e8327107d700a52398c933c8a14a2e1336eda9bd669f604c92bd38015f53598c5d5914a0b95b679e270328c5d79583c569f985e5a44ad82aa7aac9c84370554c6b0912a5cf987631e79abe62ab03188c227bf4a80fc7e43c5cb0b2029d84c8ca295efb0a8b05a6dcae5c68c77cd625d2f19c634439b0f9de1784c50530c236e5d5d26981ba776386e2daa8c5f14f1e310c196e1d402c6d556a011bf05dc3d4d4c813856b0dc0e41388a617c674890d7f45849a5a2a0adb1fe4a602847034bd0a04ad7327689b6c64028adce337ad44a3de5c45edac0e9367bba0c83e432952e94a629fe94a7aed9c02be8021a23d78487a9397581405818b66c20309aa0a1505ca85b6a93663bb281114503e472f28030b164175eb2241aff3e5d63b3f7476fcd26ef6ce0b9e369cc619856f734c9acd732267c918dd0669b263a6434719557b44034fbd801e7c503558a63952f24653f3eb553a9eab8fdc20fecb15271f82ba93a1e16dc045f5a747e41326e563e078f0ae0ad65c84e0119ddc04437be226711902949cb61ab001954618274d19c3b53e5467925ff8e6b79b7f786fc29ec823550f7f288da01cf02ca1b09147891ef2fb8d0b0a4421e221fcf0474c6b3fa11c2defe71237cce03a44f31c7d62fd47d8b81fbd30a1350017fa941f66a34d75136169e9fa65e05fffadeb2e77680e4282c5ba9672911d021d0ec497f0ebc7d1ba5dfbe4846588aa1df2de75305fbc65e130f6a3f2e7a4a6c990bf801d443a02126e670b9136ceea50544f4cf348867929ec0e4e87f841dfa0af2ee6c5cab6516cab80baff3fbe2f7043ad31c232ea5d1315d6215885d84e06447d84304c67de1a416fd30122534d43b3fdb185bb738dfa174599547ef1802cefec0748643e7f4bb15a3b3a9dbdbe7e85d54e03157c9600a84a8746992baaed54388fcb45c5d1d534c7893356e814bc5ca182b68443a5aa22a9ebccfd6b03600e28a83378bce2285e10449a435206382858c43898258437f372cbb2bce9ef94402d61bf38f5a7213fa07d14e9c556b30ff02790973ea1a2eb91eec79f97773b32cf1967f38d7c0c7f9245c9a4b750df490f03fff1d191b28d445bf395ca5162559adea0a8bc701e64d65bd6eb42b1ee47e6628aa13c071c51ae1849964f64dc42b6ad5a427a11308d6ad65943d6d1f70fa659bf300e7c035172364690858cb288a40b5a90587261fea8d425393ceafa864442355c6c9c8151966f17087b00f2ba9854024aa309a541717dfc994ef62f03531793204214246fe3001cafd3ae0c94dc4617cc23c9d423cfb4a9e505d7ff4711da2405f49d70f36204e69c87569b8ef21d0f7be8dbfe5aadbf5dc496781d6d5fccaffe08bf65d4cb8b39fe6cb84d2e18e61f3963e0d085b38375b89a3b2f11e6d56cf0017330c17f32eb85be17ff985550a54bb9c96870009c5556d70ae3662a61838178ddd228c6b4435b276ccb3f04092047d46ac5eb910b714325400a402be2f55cd191265ed390d029ef1636f7f0a7b0667e7f4ae2f25c4c217c10432cb9c3a9d864e7b85c267e5632acf27c6b99b99348dadfb7fc1c7e5a459a14040d5b97a3c861c3f547d4c540a25d7ab18ea711ad28b7719183631dac56ba74b74164b832036de3c095bc9a2629282eb9b9ff2592680a4baacecaa23c1266222bfeb4407109d2c82c617f9e1c382e8ce8a83a73508fab81093978600430b8cc9376a1223a5ea241ba2b4e112858a720586eda375eaaf60252b6c04a20d68a656aa3f1cd10900c25c1ed92b2f3d59c811d9e7592cc0f619dfdcf9a2e2bb92bcc69bbb94b77c4b688a6211a3db94f509a5ff7ad1914b0bfc41f6e9ecc3aea95248c4c5510c16a6158b4219f6192d95623358dae7125bc240ea46b8fcdfc32e364d7820f884d7f6e31ec0fbe94d5a445b74ad7c9ec27ff7d8c5853a8455a8fa9b89fa03155bdf6d027116419b8271585715ffe62e21e75c8aad20e042096a01e1b34b71d77221425d721d936b938ab5a62fdbb02d380db1bc3a576ba8cc5540f85b81952967689924b73911ed6aa4a48bd44f2b05269b79e461dd2f8d45f91cde83fd4aace122b60c4c2f559323f0329157c85daa35fe383dde25ed61d779a8dd942fb16e4b01da0782f1c038dfb02e45a4b3bf95301d6f3322460aaa40001175cb71da5054228bdff4028802537478cc521d81e8c541db8448ef4f06d84c372e6703eb1b4e306d2281089a09363d7f5c1418c4e9770e19fbc8b47bb3fdb97c89fcd771e8775adad1f5ef9e1273fd2cfeeea18d17713356d2d5dde00c7d8c86856fd74b69b6f870bd9a3936f0de99819612c558c57e95ac0d64fe2f906e69dceb00cbd2b1e5f3ffab4c0d6c3799805be28de8f225f4ea49ba9b97e801cc83026ad2ed615e408e654d1be9547cd4437d2ed3720de7d566f27d6dbdf6f7adfc6affda69addf46b8b3d917ff8cd03e9170ddbfced0b2d1017230983be1ee0405eab5056a06c049ae08290fa55931b5ea9b66434c1d607781d6d6db2c114924b2bbbb7b07fd051c06ba05b60e66308319cc600633985faf0cfb3c1b79fa6da6e9fc5f0f8b3dcc955dd9955dd9955d3feab8c49b181761b391a7c3fe351b7946bc60b311f62260220c1b0cf6a29db1f517edcceb657b81278cb091f8143ceb378b76c6c562bd76f504fed7cd089988d8cb9a814bf7d70380f7d7dfc8faeb47c86afdf5225e66208e3afdf5b1a73b008838e2a8450cc4918b2c7194c9c4f1bb4b1c6b634da7ebf491ee3ae6e9230c9bcd76b3d93c17475bb7b1b1d96c3c24663e2448d4a01134d481964332d172e8124c3882d864939eaad1e7eba91a451ed09d99a68f60a6e934207c3eed06ed06ed4605a242c1841a39427cfade393a6a24f019025b07c1f79a6a077cf97de268ebdf63157dd6f45cdaf17a288a2008bf3fcaa55bef05c12baf46d147b9c49345a92d921c1b65ea181c998c85acc80e95cb7e1b525d9264043347432fc0b2d422c7d002083048e9310408635ae4a22245b60893a721e59cb3ce9986c442f6355d518ea21a53bbfecceb956a1422d850d55a6badb5d65a6b78e4479b2ed42271e9eab57c605961cd088fd89a4b84f0bdd22e481e8803efa4afcc17c4bb6273a356815d3c1007de495f992f8877e55a8c53a32647036b243983c929e02835485548f468608d2483c929e028f8de1810b7922b281a1ff912b1768b2d2146862e5d34f912b15b6c0919cf2c4883f7c8594a86ba10f341ab58739692a12ee85c2d6d06aff46b955e6bfde9b5ce8741d13934abdcf9e3754dfb0e71b45629027a9d945604cc39435caffa85cbb1e2f55f216e6f8f355f044e942102d7e95b1522f0f185b3a1922be60dcd9bb47cc02d202c2bac195f178bfb5c373a7de178934e5fb67ce8f4650bb358333a7d191ee9f465ee768622043ebe70364172c5acf24014f06eface7c441e0e36373afd49457fbe70200a9dfedc4d9dfefcce74faf323eaf4a78743a73f71b7b312356a722460cd2467abec43d72075fab406698f844e9fbe709a49a74fb355a74fb38f4e9f622a9d3eb576860324b30221df1a2c0bc874fa95e67ec42074fa358b2f1cfd6a59d0e9d76abb9dc568f0f1858bf58052e9158ef9e8f42db696bed0e95b5bc1197c66f9a0d387f9c815f74561509dfecdd2e9df5be95f5b67748ac4d2ed5f40ae986f39f0e6d08f2eaa6945124c0e766c7a155fb869ed7b2103acdcb95ee18e4d7751c914f473c6f0f9a38bca63555104ce268cbe3e17e802658bb3489060aaaaaa5b83aa5015b2344797e892950ce955044ee0293c09314ac283f01dfc8898183c0992fd948c51d28223515e724f444642434a7e7458e994f84c89d12559dae992208990327469e554172ab962c7506f74f93548aef8a61014a9cba739b9c2933fade40a5d857c74f9b3875c915fd62f502bf04b7b4397dfa506472c09560616e475e545c35a24355d6a70c492606560417afdf9bad2ebcf178d5e7fd60a00970cbdf21c788e9612eb07961039b56b954b069e03cfd15262fdc012622d8e54080426d93b6c15660c5f150aa5422030c9de61ab70efcda7ebd51bc22ba2b94c213cbaf4e9bc21bc229a0bc6330c745046c23d50263daf745046c23de48c413b1b817ff41a558532fe11a5f54c365e195fb009337c94bb295e1938b922832e45f49db3e3e38b04b38f36b81b6cfa156d7026ccd8e07afd97cfdb74113e236af0c15584492104211e4e30410712df81cbc09154590cbad42519928485ab645bdca4a235a8d29cb5ba1b9b48c3a7d863220d97bfb5c79ae28d8bbf41c8ab6ba517e3293eca1c0f8e9108658c3fe04aaa7af091b7c0bdf091e7c6f06d25c447ce659522a1ea63ed89876d62088efbd5c003129d62d0291561a380be5b5e97d2564fc817fbee94d25d05d0441c400f693d2cd1c330006253df1c74492ee709ec7ffd15defe0a6b7f85bc9e9827faaea9b5d239e79cb266d780b1b001324d9ada6b6edf22a6145159f34317b98f2ae853e432859cf5dd1a67bfe940b6b89f02996b7102fb27b8efb23e3ef665b735e18d142570657f21732daca8eb891a9b582c067bbd5eaf97ab0664e9594ddfdcd56ab5582007595aa6905f41d9defbf33ccff33408b2e8cfd9acef11744a4d45da536906a238c7f34c0c711348b8ab4bdd0d442698f0913b7901e73698b95a8cf16f16382cc2a033c658bbac8250c5288ec55e16c3917d7692a58ff2c9e6e4644a1f634f4e96ba89253c493791e4848658ced989919e73c6d7d6a71790400ca784932a325e8c6a8075ae3301f46f26673456c54d65180189d471077a8b20b4680166e518637905e39a44d13d6f26417ae25e8cb1c51863f0048da26d36897471f30889c9c54529616ba594be20871846a831c48c8c1d2d7a703809891a72da39d5f07c08c06307912838002195b0a3090a3cf420c499343d3308c131e5053654a1d182078e748901c22eca51102f904d5baa6ef09121cc8fa49e1d416889c104881860a8a187f8802a49861f212c01c28a182be4c786231d6278d2021a152821ad7043939d0c272d3210c4c20d1e3b7e5868419fb1696ed61101ca3818b34129d54274221fac7362824ee9871ec647057de487aaa23258260d3e70a1cfe713cd09d58d04e889f93a7b24629f33021f634f29072c6250fae200aa83c893bd05d00d64ce01f4a7520e4c6238254a509aea230a2a5b465e7dc371625c161be9532f83f153a13c5391c55b3506632a3b4e9c0d3be08b5f628c7f0ed57ac5056f8c5f043d25f4302385226388acf0a3851ce553c7f8317e5bdda016e3c6111e7eaab628b5c0a20d636c53c13997bd931e4e4ee863edc97b9a73da79b37dda6a9f7eab9a90137571d7896ae04d5a71645582538c79341b3c835e6b1587b4807326252a9c05a5268f870597bd52129f759710828fda7b99ddd6029d562afeddaa3d39b5ce2e3091a18f7c89c90fee02262ac0bad4310102820a939f1c47688ec8e4ec113962c10a475aac1c5d39aa528f9aa6202571a18810ba291e9e68322c7c1c880b983139b2cc589263437f2097581df9ec30efd2a615618cc1255be40ddc06f7aab0e087abc0816731457c08af811bd5f0c22e754b4440f3bdfc21927f7abf41af5b51cf76b7f45a5a9b5ca6b872d26b43067cadb5040999823e0a0ea80adb93007bed62934f1bc247db69d59a7399ecbf565be7bb45698e0e2e6dbd863bb986b72c8eb6adb7d67be65cc35ba618dbf086ebbd37bc5d6b2d0e6ff6d69ac35bc594eaf0467bae09e1b37f794b8fa5459ca9c49b9b16a54c31ea8c3c7dc7d2e94d3bc63ed5a9c92e0fcfb51afc72dbc42d176b34e80fce5a732e93fdd76abce32cab96d5ae4f3d71c591a7f3c73ac797644fbfab51f55b55af38f2e733efa74f4b0cc551068ae3d774469e9e3f6b8cafa4e1b5b9d6f480586b6b4ba6a03956f888d3290a5b9b646a5403af67d065bd3201381ae897def73dcea4b456ccc2b3f1f6a96758a492960347a12353ecd8f677ebc6feecf36778bb9c8637ab6b78ab37d9ad2880236ebb0d6ff486b78959f56f56acdfd377ebe66602fbbf7fe1bd96b1c54f8627ea7a6276f1867bddba8fa3bee999330d6f99e2de478debbdd6da3eea9bbdb5f651df2aa6bd8ffa463baeadc0671fb5be7fc3624decb9b95d5147d4e93d4d97e10d631af6ced9e5ec683fb87c99c3696f99253f73ce79f15416536bad382b45dd7b6bd6464446465ae629e734026254037b37f69c74d22548962cc93927ad463a9614a95684fa688f8cf4511f011d110942ca50cea31a8e945cb0a7ac9e6079b9cb8afb6e857d53494fb0ee920c27e98cab9673152c872675c9345cd2942423588924236100927cb0c4a41ddf2c4904f0936278249a5c7d1934e90103a08917164e132cfba6899318064d66808d6822049435116289903501e236b2373183db2e754d7e64b22b30b6032c0b7fe958ae1cc86ff85a443c16d20e9b1029460d68f5da5d5a9faec6d39a09ae35c4c351a3cbcd70c1082bb2a550c724a7957874115dea94888c7e4c0cc921a16643da5c204385168c6ae832e648ccfde0f0a149095c8cb4c0561dd72b1b29eb2542fd76a93b1aea63899c0b5a672845571ee7b2b75dea8acae8a46743e78cb38173ce30181942e36464233de8fc5a26fb5a910e9806fc69a022297d94399fa2262f1415d1c089a48a8414b96039977d91963498cb703625c0f739286cf03d0d877ce2d713f6f36cc87eef27fc7e7fd65a13b3127de39a1efeceac0ebeb51f4eaa814e9703e8dfac043df1b412445e80ae67a0ccd239734d8a8f5a8f306a7d66b942c6529ce836bcd5ba7d3c931f66ddb8c016e856005d0787d91b8c5d4a6b6d59e9c20074f6403b039ef09a8970c1fe05cec673a8cb076734205cfe68d32ffd2efdfa6967b88b19d7e96ae8d3c5fa9a90c582c16030180cf62ceab7bd7fe91df027f8938b737690862008debe67bdd69f4b1c6355a703d2f36b1d8f9edf4640ff7cb97b3a8c7e417d83df077ee07f1eaca0c39eef6f7ffbdbdfa8d37a4f6b0dc3d6f5c7f35a6bce65b2ff5a6def1b98b8c363b3753959af35e799f5e3b39e158aa3ce37bbd1fb65e8c17ef9a0b8a35ffe3793b41bfcfbfd7a36c2a840fdbb202d079dc927d71c000a3e623c0182c94c1298192db2550a32cce0a487ae052c645fd34de9c4e89470a0058174da189f2758392b4b9e398a22c28f2820782cb8ec7997ba261f7268fad2840354d3aec94a65c165df0485ca96b80906213d68b021ca88932702912e2d1e2152689ec458a10540e880319c9f893d1d5c8bd979c965b418262fd589898849870e18c3616a42460cf7988e544161f23e165cf64b626e6ef4259db4522adeb8a895ce39fb0e96954e7c5992ded856daaab5152db57fa3a2adf459504a02a5a2a57405b25acbf90bfcf73ba827acfdad59f465bfd526fb36abd5cd325915399f9d7a1efd7de9bdf7de2935eb3e0b1ad6fb372a6a99c2057d2ac64a40e226fb7575f974875a392faf73da7b717efc2ff2e38c7178bb32d739b5bedebdbae6c7df41c69e77bf7bbd3ae7f7853bbbdfafd27ee16d7ef3e5dfa6d4e18e37a50eaf68a93e61efd6e3afb48a6771bc3817372e74dfaffbfe9ca6efef47bb9372cf3c241668ce39e7aeaa48aaa8d0d55ca25b0b55d55a6d5eb2d6deab4eefc5231eba12f7d3c470fdde6e40ee3bbfee79ff0d14779d8e77a8d37ac70fa8df5da7e238bb3b99c28e2878ea302eaed39e3d3da62d4b8c5f4eca15f9c349f91d7d46b3003bb9a25a8ba536f419dd0fd7730b3d0361a669a1ff33bae36764b1ce57672b4a0fe90665d0abe8e57278a0f43fa1bac1a4ffe5aad5aebf5bb4313cf05abf8ab3994ca13fff8bef3d9dbf3bc52953588c737da13f7f075ab489b67a2267abc3dbee33cdc6e18db66e6eb257b1a76f1d8a3417f070c7dbd4ead0b37faba27845b902db73e9058343707fe183ff227c3004f7876f73ee765dfe9c73ce3969f8000b648128a5b45624f26badb5daaa3ee55b6bed9d7fefc573a94ff918d3c0f0515e651a18aedf7b7d13bdfd7d1eebfe8d0b2d8e63cfd60bed0d8b77e80ec914f8f52cd47f83bfa76bd1032bee64d0b36877e24e06fd8a762753c8d77407b154c392d2128f25a2a59f251d50c4404103c50b1426261e4c4450a298743411351935d9681a6242d32484274b083318f93153820e83245cbe80a1225594c60b2149078ce12c4df59b2e754b669a7800e9423515f5b850bc85661ca89da2b0da0b93c26a2fd4264ec49fd0d0a8aa11358547e14ff8930fa01881226402bc03ade7a4b54e0fe4dcd1b7fa3d74ce14b6dcad160d8a4f1df24000b438675ba9eea9931659cd89a9aaaaaad095b5d5eaea6a0ec95dd6f4f78e8e4e9d4fb1c54f6fad09d86cb6de9001b42c9cee84376d5b3b53023f32b49ec914342c33f6853797accd77c95a4fa75ce4ad0974c0190082535e960d6f5acbf9e9ef764513ccdeaa34285e7f6fd66c944f9df59bcf5abae3e73312c0fa0856e0d339de3428fbf205add9d7d6a8e3b748c45451edc79547c75f85aa2e7eba64d15c59d549443568f5b8a272aadb79012aac81a91bc1ce5b8f3f0c1980bd1717a01ad0b70fb83bf7823d9c495aab547b36d6a09c959e8db3471f25d5a54171fb74d6010e5491a29629ea4bdb5a625f3f79200d8af7d85cae5698aaf6b841d46756adae66b4ef9c5da5da52edba41e00e5a662bdd634e4c55959f28bdd9a7567b551fe3acad565797ea5e3a7be02aa6d7c7554cb6b95e3f4b296d4ecb15b02ee76b2b168d2d77abfebe509c06c547de6bed3c8a8fb2ea7e20f3ae290d8a8f7a26e9f5b74c519f568583b456bd3e522012cc7ab54c41e70c216033a8431ed9afa557f66e653067f70445071420507ca0043d11f304cd93dd93aa274a4fa29ee09ee49ef07842f404e889d0930fe690943ad8f802735a6bf395c9acf5bcfd93628c31ae3dd7e29a411053aded5b1bcedf96d6a6f7a3ab5ab107df708a37b7590e1f7c19ad17ad7cbb37fba29429ac17eb9e062b286db6cfe37ccb649c6fcfdbf6eddee196328517dee8f6be83f045a6de146f3a08c59e4e776ba6e99efd6da9b5d6da29354bffd47fb3af5bd38ae16d7fdbebfb3798c31dddf7ef6fca306396fd29deb8d86fffb63dd18ab3ebdcd3a7fd7a63c59b6b8716202d447dd4d7474b109d16a12b1c4098ba9a72c1140ba67098929a829a6a9a529a329a9a612a684ac8548fab2919525559a6b2fc904527264b2e8b51161e596eb0d666adcbe2833196734a89856a7a9ff7e53afd3a053e7dfbdb587874fa57c9a877575852d01285250718ea76423e34b71f43218b992c62b2e4b2106509c262a5c3f2340216a22f588076632a65c165af458c162f7dac3d85d913013a4250bbf7de10d43a08ee5b99bb2328ea123de3b87e04377869c07a892e91b64449e705a27014b446771abcf7febdb11d1814522b0409248c3005eb6bebbd35ffcd19c69522bc605fb43ac458cf4260ab8540a7f0a8010992d3821d78f0e02189ea0e22a2aa1f2ed47ce480e1c123e70b7f11f51104ed4c4695303876396c988c238cc681c35c1c739773ec688e301847188a637773ec72f4a8f0248a9221b62c1dcd1073688188921b391e4c612a8e29a3aaaa0c898189cbce872a5762a4f911534369ee724c5b254b5be386149592d44e4a0a951492d4111da4669032222584e20a4481618521456092c4e099c0c21784c4c4e443250812950b31aee8ae4c5d017225844a75c018ce15253c90c0af1889fa62e50b48abd698671b1728836e91588d73b6d6846a2b0a1d15361292d6692fcbeb5a4cc80992132627cc8e09d6f65037c8350e6601e8ae71f05ab6e080034f7bfa0498930297fdc8c9e18a8e2b40ae8470a58c940857bc48d5b0f26485ca0a122b4b567a58c15901b222146565c527ea8b5490548f1a94a8165c3951366034198b2819d9040b9e2c557ce8a3ecab50c1aa50a9b254c5a80a0d555ea8f2a38a095542a00204151da8485161a292848a0d545ea0a2021511a086802203f5056a0bd412140d503ea0703c0df164f5d4e569f794f474e48985a71953cc4c2113958b7a9a22a684910e741c2911f9e164cb5510289ca09c9c219a62c18612f3080682f012e6c7ca071ab81e1ba0a6d4e0040c0a379a3c1d1b672b65a47e5ca03a437001c185304d5640ca51404793992070f860cb6e1a1569411719e2595b8b08f55aeb97a8b6beaed8c987de65cb1c1dfcde3a02af7f35ae974ea9c912f5e23008591fb8312b04f7a8c1ac77f0dffa7a961feb3c8f908e44edd1fa74005e93b3f136bd4b9e27829cc60482b79bc653f3213ec487f8101fe2437c880f7510d4df98a7dbda47e4cf35b9f133336b24f0fa2caa9e85c026a97abdba7b46ccece4ee899cb6031f2f95d69ccb64f52e8d77a9de254ed52908aa4d50e6c18694299b14143c5d9c6e8ad1c5c9e8b1d97cd285e9a3763272f2e2344583425ba23d394006e4730984e432c38a8c9394500f695ad4283a13240a4d2e860d39c40604b0039703102c637c8c702aa50f9c944081d9314bd080d4697c94c1fd908402931154505165c30727b460c30730cc1d52ca337384124e496a8922279ce7d4833ba5809d829e829c6aa870f2f93897fd0f45b2d6317a20126699ec6b52bc1c91e2d4479953a1454a521f794e8a11974224c7f110293fac94a9184f1c08a7b1454a0cdd992cc50426e0cbf06603a59452ca4d61b3f106f62c9ece7a70e6b2f1842cd6de7b8b3b334d7789200b36f27418b6ae5dcf67a3adf39fb391a7cf980d45d860d683b0d968ebb0fa2cd683fc61b3c9df06d7c46c086bb9f8ac8aa367ebac190c9bf7d2f52ca837836163cd465b67d99adc1334f274d0f6149cb15e96b938caa6387e8d38ea7cdfe6e2144171d4e9d4f5ac190c5b77fd0c23f07ad7df19085eaf7f8f7b6bcdb94c46abacf26ddfdffef6b7bffdedefe71028027d357d7e064d21d6f30fbfbdf7dedb76c38a7df8fc3ff1c63586e28deb6fe6c37e3febb97833610ffe8d8bf5fb612c18381b6fc01bf0fbbe87cd6840f8278e56943abbda936ce626cef5fbc1a72f027dc5a7cf04fa193485603f9fbfac925472279f6ce20d4c0c9fff7c4a3ffad18f7ef4a39f78c37ad87fe18c068473d783ff3deca778e302613b334d877d0fc2c097bff7cfa12eadbadca3b4ea01987da804adb6f83484684802000400e316000020140c89c4811c86a24050b37b14800b63943a584436944644a138208cc3180ea218866220046000848120e41c430a3a1e79e30cbb677d710151ac376e50fa3de87a5215227c7ece40cf3078ca2dba562b5dd9002999506c8d4deccfdcb5be7847fd4559f4f4cc6ff43d35139837a005ed00e53ac7fe1d35218d782bee5442349a72455d921d27296e198c9e47b223e1b03c961aecaa6620fba7a1621381bd421b079a5f4632713dac180e84c70cc8e36d959fae16d14c94b860fcb43016e85e7b8f9e088230738f6140fd7c26fa7e4bf2ef2d7158d012c018ac04f0730fffc6b411452c714d0bc6bf38cfe8f507291abce7babc6575a6d1a3e52cbceaaa88ab6d501243dcda2cf170052fa3da848ebd97a3229675790e9d7b0e3244c2880b8a72d1b9e5af527e5634d05395d7ac4ac6870018ab49e0b120697879189ceab41bca8c9cb044e93e098f696a9c4bde174e4c327ef3932e956d787c04ee158f44f3b0949aa4b89e38f459a9ae1790a5af46c0aea1d25aba7d822ccfcfef6d33244ce3d026f4c62ed8b9166244b0b4ed5132bc0817365117a122cf12a19482761080fe39a10bcd1bc938b330a3696d4d03f3217e3698990b9649ee8f6340f009228bd6874680b43d38255cfafb72057511b45a33d9e3756a45781435184427797784853b3d75f3201ea1e8c71d2c0fb95ec241999f6139ebd1a59aacb91dd972e476db8d495151dea51a02503d34f216d61566a6072575033d8fd037e6db2eae5dda6fcae1582e54a785e859445b3226e1588ee0080eb4a607f4281c927ec25364a140bc9825fd81b077ed46d9f5c20c11217350bf9d136231695b7ed471e003f23d18e7ba8fbbf1a15a0a47d08c7db2c21406fc5d956e2cb279b28cdb359d71eee873dcabeedf0d86dd08e3de606f2aa0959e9872448403829019e22c92a496016f292b55e1be57733b7bad6f3b1a444db56927f20d19ad6a297c988e4d9301881c0efe7585e4ede279884bb96618fafc2170b2049642f84e81f487584192fb2eeb6c8990d87a9c7718ea0488573c1229f335039134d1105a1a692fd10fbd41435d7ecf24eaa46110dc2ea751a1e35c1954b13e29e644dbd6f38862ce6153a728a1b6f9876a8fa42e5bd03dbd02feb0db6488329724651e99f06f533167f48588c17926ab1735bd4d71afa211d5dc6c5c84339d5373131603985b05b630956c3d6bdaffb8dd047601d210df6e6f9a4cee40b11a0aa1f1e752883f95719f19a77cf557fb6ad5b8bae85bbe33fc21a55a4b896eac9f98f48f370c3fea23885016177e58e3043d13881e880bb02c04dfaa6919381a0bc91c6df82096da0154bf94d664e5de0f1e9ebd132a552be371945e02d30651400102a2414d45d6311a92a3ccd1bde97d7b838a58afa59692fc1f6796d57924eca782c464ad01c79571424505bc4edd76aeeed8e254434cdd2d31fce6537458a8cc36ab3d32fd392adf82974ac80ac72e63d68288c27872a055e4dba68807a9f5c3a22ca8e438c4c3aeaa0bbfe818878b1bcf759ba099aacfc811b5229c10c89967282ad9823350d3b65c2afbfdfa7c69c658a50032deba4a8249a5e360169a1f6e3b4fe2e270cfd93114d22a61a1b02fa040534de3a8b8628853b2a213815360c7bb3ac8938cee52c123288f8363f7ee55034f49754861f30e9a5ce75151a3ba6cc4b26a7236d67abfd0f1bcdbd27946c408145eada9068339c09eb85ef4b2a70cfec29ec167827f0ecb9303341962e23b9b36ecfc4c55ef766e81e8d6be732d88254197a640c4d677f5db20ad2a62078462e1a000efc075af25817c433fd8450b2e145ec7380196b6f98c3d1c4508ac5c195d4becb41f60468ec8260f011f5cb775222e2ce299a523828d56b71d6298b671deea211603ce1e7028af774159d2318c631d01c93fde854b92c7782b7aa902562529359a4e4d532d3c69fb91714488d273f85599340210fa87db108f1accabeb535423cadfc4d8044c2cfdf66fd621f05bf61bba1ec4aa5faff1232191274c6ba34d9fe2c7f55b62179a923740654afb056a422e2b6d402605da03e478e59902e28e682d9f81e154078ddfbef1887662897520b07d0dc581f7eddc20f0ab674afa5fdc805e2dfa83f35fc3a7cb7927433c386c46bf2f229d29659ff8a79a9250c1be3d6eee57bab99bda31148d7a0f9af2e957ff4fd35e05c858c040160a042a9d8420253d903ab7196e9c101286b3d2c5d471cab112e7e4e4c454e9dceb4cd991045d32e729f8db1726d79b4ca952ef8e809da2f2cd29624110de7adcc1c9a351d5857dbec16541884cb1ef825242e9029f654a21edae118573c578bd80516f354a90852558b2ed2cfd46e13c6560323c830a7c5ac0076d4adb922afab1d3183b6a3b2a8b28b68e01da7b7fa01744a921f6ea95eefce9fe5896baaea7101a7016c206c50105305a0da7e2ee2c5e4acef16a8f857340291d7915cad1874ebf19daab78796f911836c58be7129113a1d820a5338a50e3ec85ecd86048b3970c262061303843dc78b9887503dded8fc6340e79370e513b0d0f907697c582991e7a30da1b543bdd54ef2e7e037218c266e75144c861630605b74a30165ad60568003b57e142f24307dabea96f204a36763e3cb6a39afc367ca3203a6814c203ad810b6a742fe145b96d4270fbce7ee7f2ad10afa6076eb54b1fd9493a938866ca1838b97f063d605f2235e4c0ff06b8e0bf2117fc52e086bf8c150b623936f306be5445bcbcb930255e7829064d770d116150b1cfcdafb039cc9cd95a9374daf1f37c52a7ef7ed41a2ff25230de1705100c6b8db6995c9099249baf99248dd6cc124d964cd2864b8649c36586a4219149315db24f66bc344832591aa5992d9b24992c0d930cd786c90c4bc324c3a5799af9b27192c1d264c8e89f415d14b2945441f1c567bbb161710e26849b1326443984631910b11eeee30e778f6e30be5419c9c597b2ea4f1f2ecce0d22db8ab1ae1b473d3f54538f3f8583078cc57c58ceaf7b4ae9083dc3d6ba7128609c1fef4e4a5a582ce471ffc95e7ee50f92c1e94969989ed019f7e4274c53066a05a0af323c99ed591bbf18c8dc79d0be1d24e02b48234b59e82351e2a8f7e2e867a8661b285d0471b65df2c44478966a512d9b868d41649e4e627dae1df07c3001aaf3f1a1eaba9404f0e6a97576fc7d4d5cc3885f7943ae5ab12dea8852ae703e15b43bd1387328c6e6a9495b55ff50e677d05240aac185deccb36606875f06ebf0714c0b500b0ade0ed5250aaac2f15b6d66304f3e8d7119c358bb39592d1b3700a0ae78719a467fefd34a1289b61182025810ad1aba9fface65392fadf60ba9a5fcf9d78c7330a074e5590e912c9f0a82c766f3674e38b4266fa147a4508a0f6449763731c1e35d511d306b4b36cbe21031e71432bee1a565ba4489462a6e7d1d2eba769294004d482a480043b1a69bb036d518f491eb7af58a0e4bba2ac4e61c2144dad40e29dfb5b53939473d54dcd9bc043a538709d84e2cdf247d04e377fd88949f1e4af8200af82e7978f6c2dc62318a0fbb56a3fa3a39bf75f02c4f0e3f231db32741b34f4e80b33657b6e753a845c08806821847aa7f3ba1a7e379646a212284f2a962968f40cf652b5abc21f0e4c8fad218c5c57d0f44270d082629cb30aa663675ba4094585f5b9319317e98f05ab3bd3091f757955934ea4fa002a3ce3b325fecaff67dd8b977b06ee4d1eb5bff0e57bb2a934c35ce5c7b8253431e25189be944bfd3a6ed344b1a711ad749328cd7777dc364530c74b8ddf1136a55acc6bd22003239e78485a4f134180e89932d4a40b1e57bad9b30383261d5e05a552a2afe28b0da454b713c420720e30bd3f4c210a095cac0955de0e1762821bdcbba35e5619c3b00392d507334f237b61aa4161290a8d724327fd6242862f818e4861828ca6cb74e7cbd9239b778b4068ffd84cbeccfe159e8d65d6fd5805ee7c5db0f3b6c834f9aa2eb9b554e0d68ae63c9b1d5133394b2dd083a20defd1a287f6ecdfebfc55bebedb2f52ab25129770c2365a8f40f4b1a856e040c4211a2ef73138370faa2411098b87341d914f4f96c3c58e331b02775b005a2e20a32054d0daec964f6be496513451fb37e40d17a35b73b1625acd61ab3521edf363d9fbc4e4408bd562de3f16717172abe931f324d0c60c1323f3eb0b16519f0408e5feda13ea81540df699ca3f84eeb0a53e8a5cbe36151afc2a6a2c4b625a5963fe8a88143f50a9ccf3c2e7ee87945cdcee7045fc74b1dbe45cbf2d28585cee98cd219cdd313796ae830acb1009f85822be596374c419125e6fcc72e58e41c98395f1b21326310dfc688589cb51fb6d0c46167e8c070c0224ed15c786ed8a51c787c142f0d294ba0639b17888ffd5ac3b6000001e80662ee85a26b65ce6b459f5cf6141ba7c47f800f9265930d3180a9596fb72d043cd007ca930c1d16a750b44604aafbb620b7e15a3b82bb00a28f4ba435919611b06b6552d7fcff3be09ac5dae05e647948cfdbfd1185582d79504d284e722ebed58287e8e7264d94f713c0618d166b5ef28616b29e6782e9b617d27220a6ab5b3315bd7a06068c58695ef0fed89d88c70f54d9b903a6d3d878a0443a3c9fead8a93534529ebc8e5e6d0a3d69cb16d963dbdd9f9fc1278184643368fa3975a272f0be8d4f877251a45b51c9d51aa8a6966c0568c876c47b4566ba3a083826b1ae36bcb8204898c71133ee6c2b1a733d6783d74d4ae57172427abac0e4af751602ee696cd0d1e753c48046329f797131d9bf293d202f06144042c1223d2d4c98039435b01dbae833d82e58063473ecc82cc3979ccb84dc693cd205bb8b18bb811f5d86561831ddba4311f51a220c5a17287b988a2dc7d00298c4bba044a5c30c7568d61ad81e603e49a8b127601fc69fba36e5a9fbf8765c02d2e96a1e168575fef951889cc5af9e2e523ef487916aebb315f85bdf68a2ea3c5b0c9be4c341060dd468740620f62fea3c9327655a53151c545bc240117588fb40fe0618b9fab58a163d57690d24b8c103992de0d11c16f6c3a641431fb7753a0f03aade1aca9a388701d7d40b6e0ec5e99164b6442c610f401494dd306ae111ade5a1ba08c4769ff399fee483ee01325ff759bf0ecd79c3d86af17a2959ebbc37101ee28a0c94ea3c04906f563715b31c036de0b9d40b67f420d6180a94acd98adbcfae163c55d4d9639a437365d176155acbf527e990c48ffadc210b0130b668803b02032b0546f1ea7cb8964282327eee1cdd8f4505921c141acc8657288d299d0eb6368bafa7d826caf0c623903f1292ad434355d5d167e4aeea565cdd9d5509e2461572d2e785cdea731305d60323b784ca2b5ff5d19786c50d69503641cb81cd9cf8665c844686c9e29bd1b2e220154c637211430bcf329d0dd613731d4597eb06a271f78282f190e3372f838738495be94144b8887f16c2fea36a1ffcf0436459313642f908018aafb82aab2a42155bfa576b2674072dc9d0485242613c3bb9fe419e694a91d23360b1870f0bb4cec27e7c36c2384c008158fe012b4a1777af42226e9a8e699de3b37e07ce1e2c09194e8f95ea96d687376f6ad7985e25824c762ab9488a4c1fff773c614f5819515e98883336e98aaeb540f5dfa450bedaf8f3900433532c6a89103c8d6dcdea0673442b57072b10c162828cec3dd5ae8eafb2c288d86b61a5b272a0d25fc357aa856443c4333463ef93b0ca879325df495c81082a27aea57baca9007585622e2bd595880281948f7e3b1ad6e080ddcb61b29946d05d7daa6e02c16220ed4586ca5285a6add8c27976c1872c2b056119c6a4955295b2f4d1e412bc2b23247ac3186f008ea66e0de631253edd2a34276c81f003011b4a005a900eb35f5bc0138910abaff192b38e1e9a39ab2a3c246e443a15d16fd166f45a7dee18b7c03c48a816809cc98b35662a08fa71a39f99f32904405702fb415f4677e43c0f39896e98b1760a64d65e830fd7091329f946c14e5e721ff3fd19600a5421a7c215a7e6a97f9affa31dd47b34333795c239b0ebb23275cf8395a0c01d18e45c7b1a19e2bbfa48a965eac2e0eb6a05f258d2a71b71320c9bf84f2a2f63777b2047e9a45ac494515d416ede06a0606b0ebe37bb9fb10fa2a8ae6ba41aac8ea3e1847f1e194cfb7bfab90a6c447a09ad6ce009f1328eaacf804803a409c72b721f9006588857f00611c3cb50b04104f86d480398e11fc87718cd42b80f71a12719f5e532cf67c4764b9a9e872a1a1fe51f0ce686f43dcafcb36f5d610c0cb0d006002b62960a4c056515ec4174d2024e75b92d7413bcb6a13a8d735a0ecae96cfdb80d9d4d57a7c2befd218320669739d890ae623730b4f43a4039bca446ec3718508f0e2cdcd507776397b7d3e60969f7622895f004f9602cb50e6a03493b108aaeccd83501723a444480d53aeec726cac2cf9a105520736a0c33ab3f24897f31df278fdd1e7e11b7521efad34bde19782c4ba524d5c5c28e0bed9686b62370c08214a8a647467e2947ab59b10229c962fbb98c215837b04e135681ebde7a5cd1d588cd558ee3260082e455ba2a64b36002916f192ec72c366dcfa81a2369d4094cbc2432dd124b9f29af62c9166c66bb7b16809f1eb15a08273892621d5cdc8176cb781ab8bdc8409c0efe18abdd04904bdcb478e60ebf2ec6331bab58e54f4adb37c3de938d4c6b2c289e97e21e143ccf1a1b2d10c0dca57c1b01d84981b552826d00c6391a167810dcf7c2932fc2c1acb9557349c003321ec961cda84871a325d69f8215541942ec39339fcbf6cbb77d6febd36944e15b49e0a6aab84bdc71ac1b9c7e8f082a1fbb6cb11590fdaefc0fd6372755fdec689c200e80489e206934c4361d09d9aa4065621ddacf12aae01c938ce56288b1b04ec4c69bef3312531ae7c2fe8a0663df1bae1753abc202ea5f3fa578c041b8f759ee98b9823b29e66a586f4888bf664e9df2470fc499e918ae0b19bd850f404b813ec27d197623f54ee6472985b5286ed30e76888d99f63914d96b33c7cac46f8cd34fef5812208f08341f5789cf072ffd18151d458ee92137ce4b1c36240f615dd252eac59aa570e7ec9ecba5bc46503efa6749e01e9cbe8a9a84941832c5871f1cda0fd08537d26b54dbefef25ec5ca0d97e376eeb9e668a39261b1f764e5262702135cbfec2772f2a16a9605d06df352db1d4240a2c25251bba42ae294875c8215b0255ad13f6df60b217495a03eade339d9e07230b3ae1466935da623a32a5280eb5bec531a3b6e3c66c3932b778f64bf64c6f8d4d65cd972e40dd16b139af7c6c4c93050249288a278af928061cf0610d47b09ecf9c68d9f0735f6a80e9f40d58507459932e393b833a8cb34b61c11ec2bc1487a48104352ff7845336cf32eccd7476c328898e80650e82d2d4bd0626d44c6191213da7612fea79c91132ae08fdfc0da05e5eb3f719ec3d8b80b019a2e0ae044ccaf76781b2d5202fc5903ee3e74b2b9aef32c97fd5708969357a97a2ec2ea945d0ff89bc910ce7a45d9c939175406b01ac9ec8a0b4bf261cbc4597ee3ba27e04df40afa3c01b49c0b4e0bfa30be65fca873a4579f2d874678d2e950fbbdea35da8aa9f1f47fd5fa85f27aabca968c63b06ee78fc7aa90e66214aaff97bcd40659cf44cecfae690cfa3163361a70cb2cc8aec9d7ef87859f14144ff7da24eb8a41c6f9519a9c6297555100228f2d4c1b40faa2331f103578a9e982a027c3cba022e2a7164b435a6c3db9514ebacd8afa8df1f0221bdc19489e5428cc9ad39dc882a94fab81b5ba495d61492b00a8680c8eca7345bb4978d824f055cc6ef2dc6e920f63192a88e9a920ed3a3fa92debab5df81247822d7992a1dcbb8e46d469f30478b3ed1168bf7bcc450688265c2f92dc66a6b7c1fe0dbf3b307255cca424b06b4892dc26da9c1ed626135061d4673f919993cb6503077fe3973406b2400546a58d447e56338427c86519d9f60b8a31d16a8a0ef2c5d0bb343c2c48f645e523b068b3aefcb8334d0660f4fc965e94eb25316616701b8e982525bf16510a1d2185158a546e24c5cb5d1e20b66a2c1f50f6659308a5f519e30c89f3551e8efb2a956a3d3aeb1f3c8aaf3f6488a471a05e7032bc129c0c57d1cdc08a70f66fb2a02bfe4d55f12bc1696025cca9b89111c5f20a1cda9023562331009010bd8bf595934568054560d4ee1e388cd5e770b5e7b517664f431f38b36611193f4e288d132484523e9a4c55d901dd45fa5241ca603822ad0b84a69ce6c8cb75ffb1f327d1d9cfce0147f2e92f903add2d42280a935b4e5ef989bd3f55dd9d740f39a7af7f8a14a5f32fc2cb2334e537474ed41521c087f7a4d8cbc04cd339a55cfd87bd7f7a5df15d079c18f4a84248dbf659da0b8b6b10d61c88d5979c81d03e2f9522d43fa69c14c522779886b666142f9b1a5ad581d61cc2c54a3a510f1d6874ae4c7036efbc9ae3b92f180da0779a40040877178b73b3a21db61f83a4356c73212d29931fb77a5fb5eb393e6fc9a2a66aed51e20ee1e5dc7f1570ef4acc72abd90cac2c0d12f649b2639d546730590f151fd32a3d7acb0183ef70876c1148b8d2e34b55d9c2a058cd4720feb9a3d6d7d15b9c4e8cbff4bcaeb18c64da6f9acccbbadce2c64c1841025ac3db6380a467ac19b3bf3b9c9a794d99ef6764a6cc5740e62f648fb97d541ba12d5e1896b124302e2e025bdedf51299d5e0cd0e58e85f463ee5114f3d504de37136a53ad325e4f7a0c7ef2990f80cfb370d8280ec8bdb86a412aa4b90dffcc3483f968dd1c9169abb389aba30c907bdd87ed58dd2440bee10ef32117cb6208d084a0ce1f59d89a32fbcd7c8047208cfd05e41e18b82e5ad920a784c5bd3a7073ff4355a1f1b238b9873f233a02dc068b92ca7773d18246034eeed1d292957953bc56418a2526c5c1800ea902be89741bb1985e83bdb12a6c1663204e82ca412c04666800cec49d42a0a59b3bdc8da669c267a53322fa0113217b74c301190a639e87bc0d3db2e9fc3375f9410827b8e886df8f2d997d416111a6d99ed4eeeda07121224de70987d6f7cee8692a9cdc744e443ebec1c92620eff643b022206a20df919e1adc8b2a1cc32bc047f6aa73f89679ceb655a39eab5ffd8abd5dcb5ccf015766e6e4cc2ad2af56bbb77489b91a26dc4b9e4dc8d061a9071b44ebf71329e06a235fe662e8dbc8bb1120f3e7ba45ddf9a2135091fe43eeeacefa60e8eb83c26f6f12966bda576b55bbcca9e2bf1d1a9ace22f38f44849d54b8efd256e09b023b4084753bf0e5a034fcea22d6c268873d812404cb4565d95c0f6b9fb9efa56100446daca7f510e668511a53cf06e40b079fe44c7ac28d0882850f760d60018bb91b182e163889f4014d23dd48d04a124273784137d468ae875e1769d17d6fe6328746093c55e0d5fc8351c50ebb601669cdbf59784b7eddca0d033e9fda9bd5627dd4db098ff519f935c2b7677cbd279e140345b2f61ccf67f87d7610d7e6e17b86b6b7c5fd261a6c47a049eee9d43b7bf8583325eab20df55c182053492d373b02bd9484b8bc1eced812364c16071cb65e2c2a55eadb567a7a064395683c6692d11e270c86e562462c37b10311378f13e87ec46207dd1d4d6a3045fc4179d5795db40c6c12a9c9772192ddcf6ea01edc13608fd19bd3fbc6712eb5ab651f52a64444a90243da94c0910b31201b1ea8b0a0c47dfb3bafb3b42d2756a51e9fb9058600834c133e0b528ee7c7205d1097e3eb6979d79f44e5c5ecfd9ee660813eb56dea455ab59553fdda0e077625644f988b63608ec3642f5347867e72f47ff1fa022889a87d6c62e71f59e2c11b03a4d8ddd03494cf9d397ca03bbe3da6c904bfdace0400dfd1fb59e5b9f8a2df525f135fb0e8a01be3a60a803a6947b645ec1c880531323384bfa81d69213388cc3da1dbaccccfad4e7dec77bd3425bb10f2bb8c0725a1c8b7f86962cd118dc3e3cceefaf1bc9e74485676922c155886ada32e8c1d337c8e37bcc84d2676352096697b890afa3a1f9a40d97653091f49a8fb114c42d73e327c36b14bafe7539c3f1ed5f6527548bdee31f0ab5fc2696852d16d812e6ba42d04b61a06220eeb63200279e58755b7dfcf506b6ed88652935cdf01bcab902140d4cd1a4cb12e40bd2274e82a1b54b04f0e4550469c42a6b14556513adf6345ccb29c40bd104273dd2beb5d87bfe30b5ddb1bedb0dbacc8566171721a4da329aae88c200f6d203a4a151e2b65756f26eb895d482d66d1dfc0844427f232bd06945d25f2e018e3156103c197ee55fe2c3c687475332217b06fe000de27c83af9ac0441a5ff6dfe5710f24b08b46cb4d96552153de0bd48717f9e95d8806e3549975e547dfcb48e96ad8db492ba24636148b4987893a113e823680ed130165220868dda2c0dcd14ad2a856ac54fafba9ffb7d3cbccb4f97a1b94bbe75a569f7f06a531c448e075084210cfde2ccc243cb6bc018dac5e625d6a1391cfd2f7698ed9c02776e0302bf5dfbfcea57a83189e7307a5ae4513aaef3894fea66033865035bc421a2b80941d4120021cdf586e60396ffeb083285aabade4eddd40f3ee126500d413bccf0e2ea9fc3f347f882cdef27cd437f72187c5d5bf710a12147fc37e08ebf0d38e08f0127fe33c8057f0c38e2a72147fc2556502c1ef6d5f7668d6114c30411f748a61aebe79834e70c5577077e8a6acd0b88d674d20850995f95a987ab3b7272385262580d5fae19ed019f337ca179fa6be8d977abb10b1f7c2a113f3a643bd9320ee6bc10ee0e2c288a2da4653771a2b684b43878ac7b47a54aed6db61ec5655447a4b3fb86bb08438e516aa7d28a71b8a5f31a57bd310990e04d85a84c53237e1d03c7dc0ad63e0c521887dd87cc3fa7c880500f46fb7cbd0c95c1a4f7c5d64bd48cc803e468241be2cae4ed5fb0c5011c220f6147a39b3699c687893e0294e47b0cff1556303b24056fa98dd5bdacd5e801d1547632017e01817455d326066ff1c8ae8dfcfb5c781955f3b69c068957e1339e1b590e1090932d95e22367b86ff3636dfbc8741e8dddc5babc65bedcdb366e480ec34386049434d30c103e64c5679d4532963d6314505f6140f0e8fde3c2d6085bf477899347aaf8f403c7637e94198c9be2e12ac398f2d9d7bef8aca0885adcd7f19498e362a83f8eeb85cad00133ba0ad78b3e97f5820cb9a0b33307b8afdcbf9508515a3c78a776f38d2db28ac02d99a7da89bf5bb8afccf808c4ba6b61b6b934e8c22ab8977d097bd7ca6f6ed76af5d637c278276ca1dfc801688223479898ead1c52f126bc87a5dac50165d4b9d0bbd54cd864f09774d2988f4d2bdefafc4a00ed4ec223f7393046525c6084a3770bec1c6570a646a271508ac1b7edf7ada910950a114a1014580d414bd98e5ae64ee664d93eab0f8b22b3777d842e59332b40b252d838f863f2ba08297bac71b3778525b575304281fb889cec681f9822e3e01b9a75c76fb94c6bfaf2162f72c669fc0db07cfb3ffbabef970548f64c91e20b07a703cf76f37100a431ea6522127309b6e890bd0de9968786f0a17021d60d1a380b0118ee800649a91e451a57b17d54b80ac8474b44c96c5c2863c217f6411412b56a7eaf3ae58678d96e893e6b73323e29d4aaee271aee5b59da60656e00f7b4aca80802334924ad009d710855c92b095d42ff8dfa1b7b88666001955c1500f1dee1728cc33429e92a46cf65c8c6d79219529d35076db0c8572b72764494b616a5faa510dc7a94e6d1caec8c09dbfc690c623d97e0f098e8942aff821b98dd1cb0a24139f0c8859a5891e55ebc8df2da9f0414800bc8a5dbc5258f7a9825458a04ac4dd2860d68103763a1d5ef8b045396afc7e1af769a829a40bed803c53743bc096784ed4044fa39adb1a44cf47da020f938532eca91dbb2ac07987b04b32a98011c6b5f6335aeb7687d779285324c6ae16d2d4a77aeba3cd50c908248fee70e76a0428627487af0da1a2a4813be193817c8f7fb93d5b1b1277b80741e84e83b84345b69002e744bbd4b35b5831d1904085d2e1840a4940c792c12c03dca122fd15700077a89607e0017796a1ae55d5a5c526ee324fb5e84a906939734b8341ebd0a498338e0d8eae3f86f34f54b1ddd3d0432f72b40ab8da4bbbc88b15022d7e05be28809b17c5e9e291d22707fb0d3c397c0011138e8db64e7ab4609aab3dd2752e119231301ffd41f070029b3ca132429e89f523191aef3569646481b61444d4750959e1c0b3d39806e78b53a49cedeb60e25044927f1f5deaa7c4a8d502059364e3d1a96d4955626d55fd9cc47ee91997e27ca81aa84de6dd9135539ad69ffec4fd1d6d0b4831543ec3a89e6f051a92026d8ef6e49b927bf0c4c7c005cdb983f1853abf3177c41c8e4040c5aa7421e0744680f77d8401237d0af83c0ebb0f226afe2382f7a48010ff79f5ad44848e7adc01d5c960ed101af1a221058a106769608283974a83ea2129911d8949ab56724272bec2dc61279b860cc12dde586c8e7d7d5caa9be72ac4922e19d5ce8f7c7770361807cb71563f8967cb3bb78ea1152d2d5f91b321a023c83ce521307828da1526a747ed2581974e4ee9f234ceaf8ac1ea091d54084b90530dc2dcc3423b80f142b0a5229ac00a925f738e02c7a0cff95670af3924309b6a037f3afa1a7d7016f5b8b93765ac2ea823692cacab39f022ee703c54cb34578370e25e0b4d2b22960152f0e2d5159140f0d3bd8cad4e1176eb25231485d431d4955d67218b5e2148b7ce12adce0ca67f0ee1e40a692ce2beff7494fff5ec8403b0896243e28c8b7252cea8ee236244165f317aabf0a1bae0cb5d31a823777388682cced999380e5323d985d46d612418086fdc96080962296025aea759a79b9c67f615b7a510af53093b195ec8f8f8b1fec36ccee842d029bebba96d158dc5be54fb8ac6a248d357761004daa4c7ab95a65290612f4869249a421430fe98e39660406e69d9cce9fa2960191a8b422b60d338f6a010be90af2843583514b05d8958d01f2678c7330e1c19c9df0b8951a8c2b1e3a04c1ea132d52b9c7fbbc0ef0f853145000b2437ccbe4338a5598327f82310d443f2e767da716bb1ac9778c827f2b1b1dcc1539f4033484d874a39d629c3fadeae5156678b4421c5dfe8ab3f966647656bb45062d7fc9e729c42ceccc129b6c94bbe9b4294b723f6017b38a7ec8ebb827a0469874b3cd6d195eb89dcc493f4b175e063f9b347e9e3512eb23a88ae6c1a01bf4a3d92dee280d04bd346474e5bda16073f155c2120621ca5370bad8e3fb26bff872a2e9ef4e603ca9bac4d7d9e0c9bdcd8ee3fe146e1f6ed79919d610b76bbb9920c5b810d6b4bd852e7e7beb0c65a08d208dd2e5aa0f01238d0be2736b0790249926f274bdf790b80d0d7e73088d869d9c0b05e25abf8701f761e0919a38123934994b4591fca6ba6aaa1d54008fbdd4575af5cb4fbca45f9575e948baf000cd54dbdf2564324a450dcef894d8c561b55dae3c8aaf9c0489f7b75a4caab6e64b962efa4c69280ebcd5bcfadcbca8f983ecddb417c5fab57afb8a1903462be63dbc9600797ccd399e704f1ee848d386df22eff51b6b1161a75eada6244d6319c9e20b9f58b5e14a607d665b6174dfa3552d0c22f5c0e39cda0ea3905b26a06986117d42926812c4904dba692d34a635d334015ec8f7ebeaeb4d6d09ac564a6c06b17700aeaef60c4e1c4c11b6ff3489f352cb7d0837d7bd9105caf0a5f97460c2260c03140a16975509e2934ce49bb9ca611b81c5b152368682160a7c69a927d49e1ad836d3122b1565609ba157bba4a9feae501b2d5204c241227a330fd447a9dbcfb479fc0dde8049f00f5749fe268862dfe26cd6c090d2ad6e2318ce093800d3997c6a8831fa06686f815450f8d1e01d12a44aa9cd693d2c4c954c8634ad11a6378f35c157f82ece733b4ed698daa70a0e20bb833f7ee7e7a2a4beee99379e400c0ba10931953cb3a4dcae74d82d45b687f9ac903c4f89f22a9982521b8f16b888c4ed704a7c7fe425d9deb18a4c08fb4ec0e446c0b7d5e2f6e5ef2865978ae56775e88a88d4beed66c021c27b0ae1191a05236d6ea683bfa5d6f1ed893dbe40e4c1c1a6279360e1b651b41dd820d81ef727650786e2c09be893bd04dc60f697aabf335bc617c3e11cbf6fcb565a3ec621047d974e895e9cfc47c47e537c5778a392b9856a4cebd7aad749e930f0e673dce275eca2644ecf7758fec9e410c5a119dce19f715aaa84e4c680d9cd4da5221ca52acef1032853ba78609c5b63fd16f4fa46684cf03c9b5d19e9b074a667e78f2ed68415ea7a9a8164c75746d8cbc547a3e2c3b4dcec03e60a047b5778c34fe80675f629543a362bb5d24f646c159b969430cbdd95f9ccae7111ae9280328a09ea7adf9b2dbe4866d9dbe6551c92ce2a88c9d0f660bc483ed0cdb43129d6cec186c20ae04315929942bb7528046201fad15c3fbeac5dca70c2c03744ff4e31523a1b787e9b5ba1fe41143a7f717ca81a1911e1ec74c1594232e41784dc0efe2b32d7758c354b3aa090668f4e7909c0496d9dee129f2a3fc85f4b5ae923df0ff8d5afe779930191bb89ae239b07bd24709a0acc53f12abc6fa8441c6a3da4e9035ee882ab28d21c84769964f3853a30c3a76dd33b6f4a8362bd17fe7ca522e36813b514dd5ef3f4314cb0359236763ecd650ddf3fe1ef034cce84dbfa490824e4b29b2c49d951cc2f7e70f261854db518e3718c2b6b4e168be0db30dcf363c3bb8d981b301a9d910a0b81ec81b141fdfb9d294647ab74f9f3594c3e9c7aae85e0fdc2af6f650df80259c853101dfd78c7f5293a862925f0aaeb38f2432681813aca4839f2e1e3b551f7d150a2dc8738c34b8f721b53f16c8981362c1c80186f57b1d0da58ca7596a2416f7c9ead70bc430076724406a14ad2e445e9d01278810fec8539a3d5175aaf52548bbd682c1b37c41f14dd5ed4d5a267298b82030dee7dcd6f39a57b4e6e31710eed64c6d488340835590208ed55f081bdd451de7b78fefc12f1e6555e1f43d8d1fc4096c674bf46f9bc0b857749609db8fb262acf200abe588cb4e7a69030facc5932cc5bc5c2835c2a7a48b37b8cb05237fa560ea00d771cc120ff23c9aed6eff82654b8c4618fe904af2b662ff275ee9143398e9470d801abec1a62a209e7cb7c1e7f09dfbb72b995fcb6a4c465445ca19396581c7b120f5060d019825ddab7492f8f974543a65cf7c410dafb7a516c89dba30b1c1fcb0daf54c3b71cc9a283e8d535f2e73643a32b15f1bafda76f7ecfae2d81631fe9b48e86d1bd8fef3d5d93447407f22b672fb48cb2f4432e98ad921c01543c6c090c50da2c28bf3c48d38624d049fc6892fe68cbc073316dba856871f5b234a8a621e30377c9ec9b719629d68f859c61eb13b2fca302afcace69811f19c97dcba846b07b7304718216c8dc52c6265815d409f82551b9eb056bf8ca9a2792ef815d3ebb41e43d086b480c9c2e0809ef7f02482e490621064362f9daa95ce9ca4fd53aab928bc9b874ae0bf0c2a453c628ef106358874972a611e63ef1d4e55e8a91de2ad51c998b8f9145abcdde80b36d9844b0abd61c4041bd3c59284bf8bc606ec3aa9d3ddff93794c0a41a12dafa02cc231fbabb7c5b652700dd673bbeca6f02cb78c73834c19581c8a769664c3c8023d128b7cbc3e7aeef919ae2bd7d9910ea5a2dda4b3dfaeba6d1bb02b7f104b2b0b238dcd969da9e25c881473e2ced3b641f9b744c4a0b50ffe72b51029b5af6250e649e1536e844896a37b157971b23334ab29b256659afa97837ec8b4c5d1d04ff3d2760eb807e51b946c466ace2d3b23d749bdf6e93aaa991ea40d1033ae92ac4fc54c6062be5acc68a7db40611d003ecd6898415527371ba3738b4bd0b9eeb8b71c329be65631bb46c8f7a0391a4dd1a9094eeb0215a1d60a40ebe290d22432ed39088715dcb34aebdd865d1a30e462bd00851801edc9de25ac1321f7b834ca6b8e805c4b60dd928b19fec956e58409e8ccecc506861253d41c01dd4f9f578048bd5b8f02a245261f984485443509b7ef04825d35451a748418218130ef160be64cbaf34d2bbed58a417d4af3359338a2e35daf763b828a4606bd2c6b3b5cdc9b403db60a4ba568bd7c515b57e214ca04b2ec6450b4c58ef55f271b817656d999d48fdeaf34794014de2c6f25f3797f3c599d9dd81d9d40dc2b1a310559e5cca159c7cbf265b4680e32a39ea6f76e2148dc8d54352c5311d0a61aabc7d48a43f61490c9605e66d64b5a1909289596923118a4b574256a0b199b5188fb7c5ae859c92cff701ec23555723ead3dafe5ad54db236fc7feb5f70c71e567825e7f888e542593342853a60d1166cd391babc5c7ba862ff68d036c20a2aa316ae0006c7580bc072f86e09c598d94110dc086d5b33508fd50ddeed384f03f0d21a95b23d3e2eea40239cb38b2a7fb72e07d764675c6f1a7ee3fff7335cac62258e8d043c43e6e6bdd6dccb68def2d0a53a61ea225df8a85236733ca467b222e3c0c6805bf96c14439b90329e31746d022588cdda2613c4e6f9e47d4d1aedd65cbf6adc64ee82d78c8a902a21f8948be24a64d041764704a2745949ae6a5f610b245bc624ee36855232ece008ae48ecd59592d96ffe86d5b1c579969476eb50735fe578bcdb041f4ab7db2c499fe56e07034b826c29f0b1b2e56309cb32c6cb8cd30c34b0e513bac74d99086dd139653ca2d9cc24634ffc248b31a15412685c6c7f1333fc61953f9ccb2fd310e978e1c8b59d2b42ecc6add4a5abad8e8902edf104ec1c26ecef6a2b8361249dcccb777a9fac5cf3d0251d83d7ed847cad460190f7ee7a4c551894a17eedad497bb96b1baa987f89db4ebb8eb6b84056a25469e41d4e088cd92f732acae0857f5989b5109b9a7ee0ee5edc3d7dbf5a907e177bd3b2b8aa8f1ed09fff4ca0fdb232c6a19aeda46a319b01ba2c4b474907e8a27da158f7119c58635e25aabd7637159addd437971364e1b67f9c0bfc457ad1db536ca442ed9e4364ad355b0d4fa42fbd5faeed919859caf9138141a3be1b03b7339b75cf6a72514d02e126ff2669d823710db23a6e703d2538b9db77b2588d3d4d4d67c01719fb7c3137024e89dd2c83ffd5e428e40f57a980d645b4380ac85363f8da667e2fc0c0309086041402cf09f3a935c7515251bf408b7cb445526a89087f18fc85ec9a6b939eb2ec910dabb416362a351019e6e7dd46f911e7d3ec8ffc42457ac2171ff3cbecf895318ecdb9440b0a152966ff62eefc61fe0c682222940553a918ed0ecb30441512a4c720077b0b0329b7a9048383e06d58c42ca09e41487cc23dc308ecc42782b780b1e9465ffaa880f9869f80115a149610d549eef7748251250a14f10f5f21ac44db24810c577c22cde7eeafe74e873f3b28ff8be14fdac0d7342995edc405c28b64c4bb19c95e1312c293855ef4ebb11e2510391966892600d105ceb4115510fff4c4a8f7b456ebef6524b3f6c1d0a2448a6841458a6e58e8928272bc1142e4d13857d1d462dd21c268b2c02844b808d388a9f8e904098216fad46afaef389235a2bce462c411bfdde2268137461a2c83a40b892231b150b31c5cd8b1ad95d45fe3bd18b8ad80dd191a09745e23a22d7766299f5f5b03eb0670afb5510a5489366ab846665ab81002c86d8215fe279a4ec7353640c12c9b7bf49b40cbdd42c254450423534a1d9a88d8c892ea30c20b52cfbc103992a1b697d68c4b30da0102321ad820e8613f823ee409cb068ee70343481e1bf980802910ad10df4aaf489287bdda41d2f5013758230c4f891410b1a1c628cb9e10ab223fb106358e4b0cd55645842bcdf5c34572a140efffd2c33fd51d13615f94e13e36b4399356a8ccb6482e28309779b20e0176fc357d2f57d250f584aa40c3f7a76f191f10b83836415678b29bd83c3b0e0138c881a6afeca2ecf4acfbedeaa74f5f625a57048f4a0ebb909ae83e6b9b3737358882e0821e05def6aee0a6666b7a2e6de5558fc57737d2fc2970aba59fe6e5c0df7ae5e154661e489b7d649fda48fbf337b2e5b9ee5b7855afe29ee81d58be1404518ef76aab67fc38e9f8ccdbcf52159b7cf1917d2e28f6e0e9cae3c30fdbc9fc7842a91d703c6c59e754b45a030b7152776cc74c25353a79f9a3a2e623882269197c92ab39ca515045f0898f8f80559e89d00a51b16cd985b9e252e26190d641d8e597c55d460cd5551996664e3d83e570f85955d9d7860148af31af5c9bd662a1a94f01fe06b91fc31e32ab0f582067bff256cb7d9c9878c55028f3649e64144356fafc50930b72021cd4508bc1459ded63fe9d2a7c8a4156dcf1c35b41528fa7ac2cca125bdf5f32d5ab04a5c998ebec1089d0d3d36e5caf98240dcb9a273ec83b41ac6ce3c0dcd5b9badbcf080df0e4412cf74149ddc857ab55455a4a75944cd121bfd9b1bcbd14215fdc252adf87b3a43e3636920ccadfe7d1d99f24cebb902833f8c252c2d3dbdfcb951d6525d4e1c0403649974a49ca054c9dc4525cdb37f6c518595581f1f5e4317bf44c03a5b7268692141fdec605808f92f72efb0f34585964055db843a7ac1c474926851f86768d82d20f2a9682886e7684d72bc51111b5a193aa7ddee7423a7edf3ccd20f34bb89fa648c4dd77a5f5f9b4fd2081961e5ac2ff8552a951ea627320527790ed2cb5cb2138398ff1132f769fdbaa9d7aaec672180ae57f7bb10a121aafd2cc4d011957e174468158ffd14c457cade6acf4ccc5caaf4bbbc053748e0aa3d3fc9a4947ac794f342868a4438fb8a23f6f7dde2281614ea79dcbb35437685409453f1f5668e6c49e9d36262bb36ce5e58814bb22c598c50432ca974226b5be0d89ad1034699f2d2f8a24122f5e1b7cfb312423b4b7769a83bd1a38742be04dcbe427358cf8e15e81ab070e102a2f70a0afd1eb25087c28842b0bc865b504921997093f9acdac481ffc89e5fe31b1832fed8d24f5083e7d28983045eb4de8b5e4ac6ed31f8dbdc0e265f3156f5df100732e5a636e21ad3243b800a017821a3b55355783a1af2d174c946a2febf87fc042235403fbb1730006c1a00ed8204443b9dc22e6b801a996f3867794e7f513590e559fda0406285f33f29cc43568169e2501543c84ee9e9571c157201806291a6b31625792807aa82d34db1b50b091926d7f90b5b4357b590a301551318d12e7df04f050267368f10001d2cae302ed4a4664e8d08507366929c1d0d6a492d734596d3edaa8de5ab761ac08bc5a644c804b3cda5aca232943924c30955a42d8281d6b63d52f1fe7fdd380a812e98c2355be6637ff7d7530061e249713bd3e059f6e68c6be8449792ae5d0f1c1c254ac75588cde68463d00ec2517143c9c1dfd1ae6bfae9426e229afbda1574f84480f8379c3a2d8a3a69c5157004d0147aedb32bb6ff63fb9421d74ba5309d0850d2013da2e6fb96e50c8d3f998b64fc5dcb92be062073ca5fafd3993b1ad4fb0d27aa51aa8d81ce32423a30b14c323db51e2c95f7f95a1c776fbed2db0a4e23437133ff8e33354136a745435ed1e0bf479c73e2f859e628e75c0466ba5709b7623c006b2cb57d2cb6c67fbf90b69c8bf258844cf06d4e143759b24b27479774b699af7a42559d3d0b467ec166c317a2207e79a8ae2e48e61c75645f5b87c34f599b0be7c3b65cebba0c5fa98ee198c284720032601097b1a146324a54664c284c360de512cf344da81cb87849a7c17219bca215a098b966974d56885a99e99a484be0370efa2705772fa455f194279e206e4018d1d67e866e5445d05a4b78d3168bae613df75039074b53c274549c21e568951ad5d22535bddc4c5aba72cef68ec00c0508761160c549eb5ec53771de3385c25eafee19f485ff9935b027cf5235ad91f6db928aa53268457d133321d6e9c2babdd0c2f7c3fb63acf7ad1d68f4925b296dfec6b9e95f67fba4333671fd33111a4c7115411b956b3931a32238c3f9104ae5d352e123922dfee1a2287ee19ab7faec1364d7a454b61f4f03fb963cea3e387db499174557403e3397dbffdcce9f5663ea904c76542e03ba36e66f5d405a19a50d4d8293028cbee0b89d9d9dd976765b22a4bc22ae68ee12b259e82e2f91c0ad2c1b3b2f2751a430142081ebe81fc9777067f40eb83bc3d5d0e9e89d31fc2943ba254a2d0259bc68459fb4d648eb261b2122894476f7e696011f070e07b10639f2f5ed07de8d77e3dd78345862c8123fb893624befbd98b5ac652d6bd17b6f66adccb296b5ac652d7aefeda18712d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d06c808686868686868686868686a66752ebd27befa4f7de49efbd3df4f001253efb9b4fecef2a095a3d610921497824190289900f242b24499008412205243648668eac90f0c0862e7c617ae4bb35478270a4c7111e18b9e9c143410b2a52aca4545238d4c9b45d1f25d228531c919661f8e2b178a6102541700d2476c1a4930ad94e84097894f4e05132c37384274916789210e149c2044f921f6bedb5788e2451828ead744ae791ceb332928405497c20592119820407c98f111c19b2c191213f1cb9391244129c24373df7baf7e2cfdb978b7d99aed3855d31b2bf1ec216ed5fba9cba17a98a32481d0d306cfaf845bae4178bc7e2a121ca58b178b287f19177178b232ef6f5d70bedca74a6bf723d9671775d73ba015aa38b720cede5732fed964fa18ceda5fc085b487f7ad3531de9c275d4870e8932b68efea028d529753f98fa508bfa6c139c713a3dfdb99efe6c3a1a418fbe3669983315b11022638a035fd08f2bf882fec5b0eff1a66fe9eff777af0d7a82a943f736dc197417d37f2e36f63d4a06e9218c911f7b085b44ffc5ed3de982ea6e788932481d8e0883fe0bcc0dd2856affdd5249c319dbf638e85ff0c76a79d4c5e01e853bf4b11ad7efbe5943f802f3360d25477fc54737f658a5617ffdf496acfb5e36d67d9f68d3bfa8ddd456cd6990796fce1cf7ef79de4518f3ada7b80f614b0bd09bbff210b6acb86b9527e9d3bba059fc0adde23b9d7a0068ef6794b122c298a5c94d19d043bd7cf8c1b04dbf4dffcd6d7a019ade932e0078d3b3c018f84dcf4997873006e94d1f4d6f42b13002caeb40bed0bb8938ff65198b77e18168f12b5ef4a36f8005a06afe86aa49c302d09bbf61d55eff3f84baabdbeb405ea153a6f75e66860b3a65ea5236b8f02bde860bbfe24dbfa2d37184a9b3b1e2bd4e478b5ff13a58bc0b2f33e34a17efe7671803ff54f19f6f4cc5633fa7379351dfc29b3ee5a3ca9f9ea45fe07d8a7e01001ee5bd0900af427f2f6c538b9f5f43baacf8f9507bf89fdca2875094ad1f3d5ce93eb959b8f05f4e6918dc33fd8a28e30508c3f4b300dbf4a59fde62faf89ef6a4caf42a9a05be30bd677ad3735d0bd3b33041cff4f365a0baf9274d1f466efa8b33a41f691e62d61f37a1d8f3b54c7fd6c6f467014063cd025ff8afd02e68ee7b61fb633a2d3816c20c821082256e96a8914bf4904bec9040e48d0e23b4c195be41414b90c544f830124ed76413315032e4470a449468c1c6a2165780566801458e4f77ec802fe04e2520ca6e87086cdf32025b66c6f8834968b9e4a98931ca196493d292935e1e2918b1a1356f10306c217a0d157b01325bc002b2052ce04ee83ea160b8e213bdb645601b5222284d84211f7e9a8646c5b68f5ba04374a84a97d7af9d216a852d69583b7679657247f2c5b3e35e74186354b1294ec4a13451ea8eaa81fa031d724f3eada1351227a78342e4efdaa15ab4035fb8ef582610b1e5571c1ce972edb8eac2d9f2ad4c066bb74841fe2e237060c40d904b07578362cb6bc896aeaf9d8be7ea812f2e254018f277b6d486e00bf9353b308057cd55c46573d5ec59831d8bd87468cbbf68a20c0861cc97178db5577fd66e21945b340163061fee4f021b76fea21eac84264c40c2e8078b21b22aba9c943a20c6d4e61efc592bd66205d98b1fddee4139350f73c5564d76297f3984136a3ea00411345082c6526248899514a27836f9fbb85a4d9c287facdc777c47fe181a1a1af29d196da2cd10853ed1a6669e4d863edbca20ef861f33196075016e0125df0bad8db53aa572ce292595a2165072dcdfb5f0e3438fa2164392869c92ce4929a5516d8f1eb5d65a6d46c3b6189a37312d86640fd9c3755c272828d6c49a9816430ab096fc6f86ed75052d9cc018b7700263dcc2098c718b27b09b23c70ff25640891f880c1181422409224a20620422428804e1b8f7942001111f6e20859c1a2456d044076654244094004814238cfccca825d8b556abd65a2b0b4f14806c1f263e4ff059828f117c7ee093039f237c6c7c76f8f44822ca25c2aec5126329a594d6de2badbd97c71115ff859f0377c6f5d0efbdf75ecba2d4b228b57f891e63cf019619d96bcf819719a2c7d65a6bad8ebbd6aa8249b71abe7891aa6b5bfb6eb9e3aed6ba90d2df828a14dd4aea6ad702005f2caec5570bacef49c54a4a25854b813a59daadd4b25a26da22ef94459666455a8689b46b2d6dbed4787f1c16808b1eae67338058d442498e2af6d4f366e388f943163f78adb5d65a6badb5d65a2ba53b68f427d86b5d8c31be18e3ec628c73e4204212434924899ac60c3bc42862145bd3883fa54f199fda196b941e29d5e60e71da6bed8dd069d4156861d21bab6543a4d12b4e8cad66653424662dec5acb5ad6b296c8ba321a9e5999b5ac652d6b5dd7d3987fcd9befd42c964dedb2582021943d5ca7a6c76b72a8ff6650ad9cc6f172fafa263b6d8ac7507f7d0ad4afa03e857a158d423da74f1d8e976dba3673db726f59f6869ddc2ac150106b5dc75a3b64c4108f27849ed0b1d65aeb38d63a8fb5717691c77784b88e8d372e0463d7b118e38bed67b6f473cba0ed3eeded679b371d81641aeb7060db2a69d87d579763e66cd8f580eb48e7719d5b12ddab7d26d231c81695487aa4a1ecaaedea5b578e193dc7733cc773b2c8eb104fdc3c31e3d27e0edf12035bbe13434ef438a1e38410f5a1fef296d1315772d7f87043fdcd28b3b3524a4e24afb7f24f2a4ffafcda43ec57744adf57d117a5aba92b75a22ebb12771fcbbe3a1c2f2ff24ea0096472fb821be498e7025f2ec475e8957372cc2dab7d293dc773e2cd64810755f8f7c65b70ec4317e22d39f6238ecc68f87bcb85dbbe074df76ebc71cf3ed6f5bdcbeed5dff4e62cf7fae5c18e23e4bab5d65e7bedd5b3a45f43a91a4d1c1d6b596beddf1bed7b0f0f8683b990eddd772fbe7e3d0a811148c47197cd76de915c6f7dacbad5bfadf6ee5f98bb3bcffeae85effb3b8ebb906b61d1df6ce9ecfb39def0b738fa636fcd60df7367df1a69f70b1f6b9ae8bf1ca3d76e5c9fffdeebb3487b6cddcc73f08d1777340a6619e073814bd3ed395106a55ba54814a12006784ee63911c6ad3ee33ba2e0b0caf11cbbe9b872957d4cc724250d633d27cab899e7bc7718ff97635afc5801f17a0e759dcafda009204df810fad997457af22527b3bcd7ba9665d12bdf7a6161f1bcff155d4d124f157478aad08387899e7de10fa594529d29f4328172fd91ff5cc109644776aee2bf709926fa3aa34d2ce25e12f76613519a10b271c4a2e3cbda6c4bef1ba59452ba7594524a692e7513284e1da4bb7badd1cea09a33bd9efed01d325132b0ca3f7f7267ed65ab9d40f1a790b75c2f585c3181228cd8c5955724dad04df5cb8675d3999fda9f3f764619593b57d5ed88667af9e81ddb1ab4fd717703c7cb0a86222ee67f2bdbda6fbef51fe92f5aea34be487bfb7b791e2684d8c4f3bf178b6ad2c7f9178fbaeff70cfabc3d6110a9d65a6bad24d2d711c93651c4c6bbde8d6dadb5d6fa13a8fef71233ea603cc3f0756986e1eb16116d26d0d5746b1986af4b69cf0f1ae711120408fd7b0192ae232dc3f0359233dee34334fa99d0a68ff9cfa68fe95fa309a4837489362cde070541a0d18f4d6311faf445ddc7e2398f4db9382bb5acb5d65adbe11bf7c576f77e9cab1974ed040af27080b10a091512f2ef85d1dae8b5baa454ce3927959256f7ea2111617c38df05f458e00ba8a17b8c2eb2a7099700ccf087c188edff1264863fcc6afb2380670a34dbdf068c96f583ed4d78c2e3890fec11c123c2127e7efcd8fcf4f811f283647ff707c80f919f219e10a0088132048a11508240f101a5889f285084f8d9f123f4f304e807280a900fd00a4808d010a01b201d201a20238066807e401102e201a50894202851f08438593989c2898f132c38197a02e5099127493c09f264c8139e27354f74f0e4e6890abc200cc10302131e303982890b9810c184044c3cc044034ba22c19b224c9929e253c4b702e15c2c6c6c686088c3f8a883fb167499026486872d3048926354d749af868d26315d4c4034d76ac86ac922cb15909615584ce0ac88a88efae7a30c902131e264f30f959f2c344083dcc38d971d203273658ed701203272d7042e3c487130f345935c942932126449a1099382bf9c3778674628c3c4b7ee40864c6083d4a8003f8c218704307d4bf4a60c1b6152551ec6f850e0baa9220f9aa55490db055af1217e05b616a28e9715d4a662cceda9047094f078c4461840a469c18396224092349303204233a467e18b131e263624a5d724964f99ec55009e30c3a6eaf2673d28b419e1fa79cd3dfa1c0f41dbeeb152e090e7a36f9c6f4ba6b98ddc5dc8b41960ffdbb1c8b25e4f84158e461ae5bd95830c1217c21e30663cc8f35ab616f56c3ca6ad4c0590d6b4a2718aef06af2c76d6984c7863c45745ed890a748cfe621ee442f6dfb97f51f8462cbbf74e461a1841c23cfede2ce841e8c61bfb49d5a1b677ffe38565ea666d82146b16d07b753d731eef4c017924a2c8afd65f842bedddf4d2580be7ddbdda870e30ebacaa7d7502264f65a5866a44c4a89adf62d31932948cbaa15d76a690f334bd75a6badb5d67a2fa595d21426b5f5daf5387b0ebcccb81e636c695b37dd785a1a6e17600bdc97cd344c420d5cf0840f867a2429e2a388088208816575c4c2be32edc960c70d8a8044104666b8005b685842087a70d3b00415a007b73f9329c0171666d57a6f56ebbdaa0ce3a96da533aa86fbce8030860c32b3af864976d51658e203e8898daf22212872828b595a2611c2be3a1ad95553206e3880b3f195a50b0d4b4c01aae0a6618927e00bb83fce5ed67a84e182499ded1b800210e00516180378e1a2450001c0151b00937a84af1898d4a26be14b0676fd0b950bfd98562d2ab718deb4619a7555bb659156ba16bb07c0a4b6600201b001865894278bbc30e62021ecf23571dcf65ff2bcaefb5eb2940da527bd8d524712bd0d1cb11cdc933117c984dfb49534a98a3c16fa9a666516b383fe308f0991fef216e58f597bd7dac56a1673b13b35ede6f5010cb148de1d0343857c39fb17cbb5dd751ff0802b5576ea4c8f897c9f5e10c2abc38c97d321b0bde202cb2d5fd7b55528c81fb631d611e36ebaa71241c6ba0f53f5b84290286cb02cd337b291beb183dda387fb663bd84deaecce6ac0accff4d5348bb1d68ff47d1879e3fe22d6a5ac873b7f8ce831cc1280b597d918f66560633662f6a5e3545c83c575c28dd54b665bef09dbd1c03adb5df8025bcc76291b4a25fb3a6c67a3f4db6f9d8e522733437322e3ec2f7c81656e862deb86fdfbd1b27ed4ed60f7cd1a6e0bcbd86aec62d867577f3106ebe2cdc5b0b7d6b2321bb332cb7a2bea52f625d65f04b23f1c9886bd7c7a658f33edaf2bcbb04b63d96bfc98cc9e6631b88b3719dc3e6c2ca5761df764ad95c2918f7680e18a7bf355e1c8f0d30435d812a1173fc3e66fb34bd970faed6d9c7eeb741c315fc7f6a7d9d938bde94d9d0ed39fb00a04f97bcf48be7e7b530e1f74dc6039b6894633e68771172da3f7f7b20bea3df75c8f44dd8f344a6712892412794ce4ef270d993dd238b05dca987b2f38fd3c3dccb2b788fef4fef064dabc4eeeacc3f40775e0ce876deb319a57487e858050458267f9fa3233a2ece4eba85787fddbc9cc88dd9723ce18c45b688c3a54b3b8173d1715aef026c0a7e1af5a626dad94ce29a56a0910aa26aa269e50febcad4242a5c4b5b6564ae99c52aa9498ae73c50b636c69ec1f65f679f1533cf367357b1bb983f2526b5e12fbc61e63188d1b7b9c558d3e7d1a18f598c8d6a5eba5adfbb2aba45ad39c54c9a9bfd1969f0558445e116e7fcc755c07bf9c7fe9f958bd10cb711c981a0860912ac9800be4394017cbd5e6de9c39eedf7b89e1a5eae5c55e9b1d29cfd7ae1c669c40f3a7bb010708b8273f07e9f237efff3c968d9b409b07255f2cc79a8b037cc2283b7a91a8b342fec27c5d2291e86fee6e44d28fde6a98f5189ba4eb108944d7c3c8ab24125d97f5395f22fba687f192e84da6d25fa5aefb6276738ae6b4ecfa92e84db1c75692a3bf8f2f3dd29ff691a424dfd0a050927c43833e24ed27e02dd7b244dd2703811c70e8a0699f655ae7423e870f3a44ba258b3d7e1841d3c3c7653bc78930b2acb33e6aec6124665913c6b0e52bc9717f31780e035c480c3568b081794cf2f7f7c62439eeefa3e7b8f096d8bd27b30286176fc15f3422eec8e13817c71bc796a24ed473aec44a2ef21d5b4e78a1052f085960603cfdf6a552b66d5ba9542a6ddbf659c9d4ddb07f1afdb775397cdb0cf5275216a5f8524954129544db567a18591271db367749f4265377237e8a87716efb140f3305c44fa1c2f425d387e305aec454548929918b3153d1a528459d54f76595eee352baefb9eef35e74faec71499ff447423dae98b7a4882d1f5bcc43926f909eee88ca9ef4d187b7643342d0d4981ea5493fd2f1333d3a759fa98b38a297f2a34d3462d3404af327c2183d8ceb51277aabf3c3c81c452228880cf16cec98a6b1fb63bd253fb68b3bbc257653c83d1c3ae4f04187b7f0d8bc55fe60cf9725c07ef590e4b8a1ed62ec4499a609d9c4289127ea08d56cf951c804127599a6d92feb2820b3eb7f2f9cfdbf9dec2e945989190a3146183c17bf825bc95b9c815d8cb1ca9a4d29a591524a2975d58ffcc1249bbeea47be3a16cffbe7385f4915fdccb1fccd637e833b29bb8fdb9fdf48bf91d56feccd1e5ad1bf37678efbf7bc8fe55538c8f48b5136a57146d6886210a3940fe35285936b052e1c29a594c69861588b31c370d56a8cd46950bc99010657c9cf348c772cee49d50cf2c7a23a22cbff58e843cbb26f3dd5d062b6935b5a1679f3be58c315178d325c9bca87919287b9e55fcdc3dcd0f2ac888824c618638c71027d3d6cf80223a3c2c953063b5fca3983eecd99e3fe3d6f0249977b5996cd8279f82f4ded6a02cd201939e10cf2091461aca4ca881c7726d00eea18063d4c148931c65c570d31b65699c5d01a6f1cc773de75bcc5c64c68ff7656f65fc4c15e922ca98013b4828d1dc3aeec3c29649f01e32a1464150af2e76d5903de11884e55e1e4e8c3553552157bbc00e34e0645f92a20597ed08eef39f03202a614538cb1a6691aa68fb519348326d0b66d3f7fb61965dbe66afe4ca16d9b4233a8d2973192e228e6288a59fc4aa42c7a994013687b295a28acf0630760431e287e34aa514dd334fa9aeb788ee76839e70c2335fcdfdc1afe4c732139cbe8cbf84f1a451c81442051270289413cc773446e6f5075aee60faad6283e24649aabf953a750ad0133c0d05bac95c328708c9873feac92b8d8caaef670e3b0afb73029a5967517c618eb64f0b7ae6bda4af7debfaeeb667f69ccd46d25edba2e29a5df9bf395f2de9cb9ebba7fe17b73e6b8990b5f3bc8bf7ff597ba573593ef755dfe7fdd9b33777133d7a5d79dc9aa991c05431d995eb8060821bceee31bd8ebfa5d2f35b6fcbf2f1f420821f4bfeee86567a2ac71a853e96b88f2a34e2451d6aea954eabeedfd31fdb2b136e96f1b6d1ac70bce52ca7ba982c89f4c8f97ff4b639abb52f779a4ee6389510591bf1a7e73f76569bdbcec91bec1c2e279ff1c672d69494b5ad29296bc74b84371c7ccc71ef14797ca9f3dcbca2bf23e5c8f25bb9464b97f7daa62551052aa82c852769fc84a29a594524a2925f69994524a99d23e7bec5dba74e9d2a5b3a882c8aa20f2f5d6a770a70a22dfeec3f1a20a421504dd620a0a54436c26d4c96216b398c52c66316b32992810f7e974ea54447027d3e9497f520d713aa986d84e39a338d510da675aa6659c86ca543fbca87e50fd908320950a4ca8df188661f6476fad25e1402346b1b727712516e4d1931e873d42e5a039a84a0539e82ea954502ae5a0a59983aa5440d21f0e9ba4e2495b469948bf71de235d5474294cdd77ef4a97e9bf0a88cf411f8794dea1a2e2419be32378062cf2cff3ca0ab4b05dd4551b9c544519e8de214601ab96e57b145bd2f097934eea7a52d5924927d593c279eff429852cf0c80216843c62aa11e49457b11ee5a4d5628c893c9bc16432994c288bb52c136559ceb2519691b2ac94655b9699acd72ecbe466371a30e6d46dd88af046fdc9c4446bdbb29853072d24ecf0d9a52d8bd9366871db5ad129ed8faaa5acc6864513e6274ca2b0c9613405ab2aa687de523fcef433c8145131a250287f3be72a7e8db546548cf164b57327523e691a598c7759cc56c3fd0fc63da91a4156f994ffe24ddd4c271497a2928a417655d1291a4a55949e2845b60fdb9fd330d2390e95818de2385ddf42fdbbae6f634cadd65b1a6eb7ac5ba8c83d4a73a6e734dc9649d38027d50fd7a99a06f736dc6e5bbcab81801e950972c6b06dc3aa13e4183f25e0fe77fa0d4638b5fa56f7bd6c4b07c6f097aff272347a51bc711eabc8c3675c88d58aaa78f98e55dcc81eef7050d1394895ac9fa2f22929af523b1ba95779954e87caa7503188918c3fd8c3c9971864051271b88a7a188f41bc45728f7a97142f1f7328dbc59b9708c43df9292a0af531466b2de68ffa13c79d5031b887d270733ae2a0ba9754fdd4af7c8a4ed52e6543caabbc8dd4afd8880363e82b5516b61c7739cee2b88be330c7611c97719cc67118e609e52b8b89370fe30cb071038c515ffedeb6bcb7efee6dfb150da52aa555748ac639ab11bd844913464f5845a11e76d0b2acafd6bb75d2d0bd4f669b5e94c598388efbaa39ed3191ebc747696ba79da7afa747c1c893cedea4b18f3acb62b24d9746fb833d9fa7bfd187e178b92f2fae9213db58c2da55ec85861e2f7c00434f287f9cca47e6e4cd3f1f3f10d88b1e88ecb5c7c011fe18c85efbcd3b4a7dbabb534a319f4cbc4829a594527a58c80f84e18a4feed8f3f184ee0f239d44fafc94f4393fc971620af7296f83fb9414cf712bcfc914bff25fec91d127ee9829e8d3165a701cc761008f14bff25855829ca27f03f7a9bf21e5553070848d9557790ca4e852f253cf7dea31b0d2a56497a29d0da9e7de46ea6d7ccaab7436cb7ea43d8b19ddeb3191e9cb6ccf9e6a3bddc309e22e2a3fdd27c5ff931bf527d43ff73233fcbb940d29cfbd8d94e73a1d47d0d7c17d4ad7d950f9944fe974a4bc4a273303ab40407d88916c7ac7893b70b61c70703108a53fb318ea3ec45ba674eba2e2e75fedfed0773eb8721e153acb3229e5c865f6d538b03d92d98eec6291141d5752359fd32a1f70bef6b1c75b86b88b4acacf23404fb6c41dd1c72378dc919d9d50e59fa2391dbdef657b47c22fd2df8f50fa1b9df4e76d93feb0ad843f6bd50f77aac3a54bef7cc8e19eef48ad837bd3da9983d4ee0961d50872910857d166fa8e4d08f20ef463ae17ca3e936997d403638828a5a399d51079903efff53b9fa4dd71469fe364511767e08bf9ee3bd67734ed3baa0fe41917c27bb80fffe1345ee345b88d8d3be4dc543f7898990c9ea948902b8fbba4f8f9cf7139c71ee9b2f2f3bffbe58ffbef5368e771d54a07afe7a5fe55382e25e70efa90cc76bef381fcc51d3b96cf9ef26768a449f9c38db9a74241c60f5523c8dca77c6a86eb3f1ddc9b1fff832b9f912eb1677e8a8e45a46aba4a883ca90f4fcf10b8e58d5ccac3bf299c86ae8adef6797763be18a5aa176418f76080e1de9c3917f21da9f2a028793ece6af43835f918db32ee2b03b66df761f373d041c7943b360f9fdca8c4ad56563564686600000082026316000028100a8603229124c8b2340fb70f14000e81984e6042188803d220885114c32863882108190008308480514868c8061132cfbaea44f25b48624d686caf1caad041f80eb379382481a9948a31300348947126c7eb4752b00b9744e9f4f81404f2c3c70c139b756e085d604c722be4f2cfe88d1f153d264f1950d5105e09591a84fcbd1432792941abbc38242914ba631c30c551bdad980bb518a453cb043c3ba5fdbe3fc91954ba7820c02f85f37d029c02ef5d5d4aa1e2a05ad7a8487d4b302e651aab2a9d5399920e96a8f01569bcefd167bd945b29ac6ddd9a4d94c5e8ce9f9570ec244aaf3066eea6794d2f05251a7fe206e65a11229b7d63a0ec2b6b0c60166be19eca742a09bf07bd94ce782db76f9640f219ac101bc0a0d21aae1fe3ddf571941f13db78a06c5e4a9963989ad8a41312682d29515e8a0d164fea17cfbc12aa837c39e0211c4df9337100e0e6d1f570a799322877097e35d4858e98483846cf0985608063118b57e1dd949762dd6b1817463ff8112a19123f0e8b8921081be033de459a2a340003a7f6bada9bab38a5fef22e45bcc8767bee52acdab33b777729446e2d6df7497966ab94359e600f8afa4f01c01f43331e7b6877772973ea49175ed377875e92af16243b5d7aecb71a22376fd86d3c1eca80a9deb9235caa8972ac0ecbf197a5aab2129262e2c83204ccb42e2552294fd2663f08a82e255a33b02e25cebb35d2618af5fbfb8e629d4a0ff7aaef21add73598221569d3f443afabd2abde68333603e48ebd752d893745ca75875fe7b0f391a325533405f27e9279dfdd8d2eba27e55b0bb72e1ecd3f8ec3da3de17bc32a953c58395e30748bb84fb72c15dc14baa462cfe10b39fb2c8396c928f5222c28c94b2814558c9413ed6687f8f7fedd0825472643996e080b032a0a42fec072dd453fe72de72208a5a35f460d731e1f311b4afd0cb28c5cb63010d233803ed0c161946584c5adb8ffb4be9cdae137ed32282ca7547803a6b2d51fe16b187a3941895b82edbe6554755bb8a0d8d533748636db4384878bc9c99e87e3f6bf5d2005ea0aa94a9810ff4fb2841a20bee3f5cf3ed9f486f046e04875001313045aed0bc74852a16504e30a9386771f080babdcfb9a91bcc86211933fe0b31e37adf2a93ff4c1f82cea713e6a7b0727df89f8d82e660640b0f064df468cf15652982ccebb25161ff9a882c5f48b9b37a6d6981328376633643021ffef965f745eb9e33579e809c5fbfa2c1eb46da260c59caf682490370dc6ddfe561acdf235bc302395d446b87de3a995bfa902b9ed1b51a3d8113aba766c9c0f9ed6042d38478ddd059ac67a0997a94d4fcf8689c73643e17673915c6b4688a6a7a3fec42849aa3fc9dc3166f8939704a6a1f212ab2eca42798986f064701116b6220c0e14d7d6c4fbc224fe643475ab75713ecaf1839f9a982fcaac8cf5fb605495afd3776a7a35fefb80070f6619033cce2bfb29df27827d01c46f81f67d82928b4b4e3e278f8688d1372efca4f45c61c3e4991805e0123f29214547928fe29f197efca74b41ef2e41d0d6a6f13e62ec4f1ec2fab7adde719dea20db3b9efc2cd23f7da95f91e6e9091ef73c0dd2a7f8013c46965749018f8b5113618ae47a22b74521832ee0c7455c7fbd08499b1088ea8972e1f12e6ddfd13bcf1fd07a3af85db97f5efc9a0b8f63a5024ca3dca28be340f6ac5c6ecb980fa4c6637dd315798ce9ffac5d1b8b783e63305aea4f995606fd01f4c72ad05a0c4602a058201c129892f255bdbf72b8d621610b407fce8f4526c871f2cf2e168c43e700ea467aebb04a7535ae157c1346c8ba0a47b6ea939ed7ff6ccee1cffe21067b752a58a278ca466c99f9c58ffc855b87d5e215adb2f76a917037f416c4b3181e9670a30a73c1e89b686da96a412cac2140d25ed172dd3b9ef1d65ce030536e65a611b8b90837c7c6107df2281eaee2689ee8c106071541918dceec4421859a468c30b24d587c16ffe13deccdece62752f563d8b05279babdf11b64d4a2a306b1e7c47e2a3e09fe34b2229a7e4aaabd465e29a7c71834a7c24a3ae9459d4b83793c040a0ea9e495b343d3bc1efb605541e130e6c092ec487a70d8d04039d958584d8651a5695e494d8b3a119e4dd314f28e4ecc1796ab749d935d03d38dd418358d185a74c3265ca7ff74da571339595553372cf91427f613a6a2cca78413dbee4c58ed1fac4e3a7d1e6ca9e9291ec04795679394c443f88042f7a518e1a64b2c8210fe3e5232a9e9f386bff28b6563848cd4f9fadb72cd646bc5a22454bba5fb4dbef1cd677cf2ae5267a84d006593286e52f71224cc97dd820e1af7a0d902dc0238e569e2c8a1665a87fb390e74b0a53ec46fac98c71456917d3ab28f4e8e1ffb60dbfe54005f2cbd8166121937de5ac6493a59ed361fcfa847030e8e0d9ab57970573b279015f28383eb62ca935a10106d523040c95b3761e84bd6b3ad20408e7e27341b70abbd239f5c14d654c4b9d15ffb1a4eaf87059b2de09b33ab2f81d961218aa64372e87d3805051539ea1e9e3139bc7a154c551342d5275d1d1148a087ef387874a764f4ff0e5d13cc5870561a541ade174d8df8ccc9cd8f04623055066723c920c4289a2d13a637a4abf9cc37d3660ccee4b7fda3f30dd9a321bb0999fdfdf658f7f38f7718b994b0f023009f6e2448f727b05641b4c501e2fd4e2d0b5dafa6244cc775ed255b1c480f56d300f9e45b6b1f027c8b03c4cfb480c5b2b235148c4669c7ef0a446ca0c0f584c51a438dc09e8e7382c2505523835a004e90f65d24ed14d8aba5e74e5b31fac097d30a36cc557426314794001dde7d8ddeeec151c856ac353b6d785aefb213a8797cdd50a2cd2fa78b7436a2a11d45033710f56a29df7ce71bdff96cbeaba6f0ab6a674d13601f75312f8e192349639955fc1aa8ddc0f0adfa011e6cd351bace2c337ab9f4ffdecb4876b09aa3c75f2e31889e35c9c3da0bbc278771be608c3f2ba2cac84a33caa4c3157cd2343348d9dbef6afe6e34f0c7d289d19a5cd043dc67e9fd639140a98578b641a774e8bcfe585efa58b7ecf89c7020c285ca663890291c59818a2b291d45cd43e1dd7caae51b90f498fef6a4d1937d1c8ccf051aab9395a8fa0676e77c5e29efb7b60d769903ed45ed64f5580ae0856f0be45bb0fda4c7f33f03a9ecee1307704fdd3b2775f2ef2b727831e3e08ee369eb9529aa6ee5728cdd62e9c30197a4cf6eab70a2a1cde8808285db8eae478cd61ae06de5dc80f7bdb56ac2b435a3293aec2b72580ba181483c0b98acd20f5f6fc45b776ae1529e0695b9442db60dd2ee6fe0cef66980b3e53b700227348248b611f8e585ca2535d90a5c69bd36d069288c203add1268f844f157c6fff705f0e5fa6fd52ed743d9db72be795d208013372660844ef23c22ae3b387005768b6099a9f7cb01c00599d01b69b96c96fafa0ce183fd301f88b0376142791033013641d4b4c25eac5a1d501bd843a1e8c43990249c050d9b269b2d4e66c8cf4120a5b0e43f55170d7b05b671d7c980c988ce2675cb050ba0deab514e152cd5e829af5c5e9b2332aee8b06e82c494077d9d5df0398321a74075dc8eaeb6b6fa8ea388d3ba99738dc1f8fee884c91e2ece7ba3e2cbb98806c601368e71a9903806be5420183f3c5d4e08cd82120131629d1484ee2d930265e7b740773772d7e4a19cd435f94827754d3e121cad545f93e386083e84b0f3711d79051f0f2890a65dbc9d1dcc6492019817b7a5999b2857b7fc1f87c05dab0fb6eecd79479339a94c5a7e0d938d5a8fa66f4a7cd047cdb3b26445f1cead9efb2cd7d15fcf93fd2b921469e18558d0c15b3f678e828e2ee1444141a5406b7af1de0b1423e39fa3a5f8ec5805db7fd3d37b74894c2bd4f31ca5de68e4fe079ae5d0b4082de1dc17b20c6b878ea39609faf0836e17a40ad2c65e084f1f92b6e24ff4cc14f0ecc52c92346471387142d830b5a46685730d3fc27e5fc060056ae44c7481c71b4ca4af600701e609ee4bb4176719ab4490910407a5060c02e792f6f6c8e061efcb194d39a2803c6595b066b70b86b8aec21c26a2750f7920687a904e713de67a1d5d822bf7affe00dc30c09d09069245e813b9519ce51af3825a8edfd1f236e866b0f81f45d4e6f493ff6fa503fd43cb45b7e3097f8d7b66929df66a00586ca4828c22d55389d8f4939f058353de4413097defbf058a80225a3979e18295fcb51dbe5d38defe202a8bfba9e253f010c7eae6fc2f0a94b771c5aed091e2e3f59eefe12bf81030790bc30ebecd9732d9eebf2bb398de91dcec1540902110196b750e65715417f044ebd9127999b44411e051d5bbb629ecd10c614b66648816348ec304f94f46820a132f38cdcb7dc9e99719090bb3cf3126eaec9cfd7c0fb4fc944e21fcd00c21d2c14b6d728d095c986fc3219c7ba70f379161a3bd131916f40a2daeb9e42f4636eb40315b9628e226617f57a148280716ac717e01931db0935b2f03e9bc47f354eeba07d4c287838a2783598f9f5664b900ae3788c56383d37f65d131bc6af92afc171d91ce4ff053c4c277d90ec57ffd444df5b6888450dfaa7df3a8491d95de426f8cfa9dc758220aeabcf3a2c944214e71516ed16810b444d25605383008bc200dc19860aefad61a0fea63a1225425286c0bde556da16811f6d01d9b11ca19755440eaff5e831a81ce1843d992818da148585e1fa6615d556913b4413b1b56b1ef873ee74feae8a197271beeb37d76e77526a668300468d460202936a73c577a6c84156ab20dd8b671b3cb530939e368d6a937e0692b320dfa9514735c95beec7951ad9ce02cb58877d8dc8dfa0c6f7a9854907fca8d40484a00dc5500c1222d204e098f7e90daa6c955ce1ea39862fab4f148c3cffe9d6f2639180791eb160fd76d7094e2047de780fffd07b5bc19e6105997be5dcbec3d0b03f1b49ec3ff684d1e1ad6bee467cf28e3799b174b72a970581c20637d8ac9cd7ffe684d852de625497fb3a1281cc8b8cc59e18806cbc25cc76decb3fa27f46d5bfd3bef956eb3fcf33ac76fdb8ac35848561c5e5cb7af2c74108b81ccd23c9108502a3c4b08c24e2abc32e5608229110d531866779c592332e6bf8390e14b4ff314a2da20e4f1950b138b16d20d715c655800aab863cca80f8314a301d6df7a2fd400afcc5feb06c468ffcaa552f1d44f0f68fecb00d2cbd6c35d6c5a6969d535dd852a6afccd80d629d4cdfee210c8ecd0bc0d1592f342226493da0c2ed92ecdadb6e675162abe87e1c8f10fc28d63320709cb3982c27e1f35e04eca5bad57a5939b78dabb5083d8e46c113ac6a4f998f7336c9d867413b9bb4ad60ae075e25220e1960a394fcd1d58952f72e11a4cbfbd6479627e1c5248ac019a4e3cfdb41dddf450ff48df0b12a90b2b18a83ab9055d51df698083675b60adc75c1b359fccced38a900247d1ecc28e185183e376f792f50296dcecc154b5826d37a4b84784396348e0141166bc42fa794944c725482c46acd30215b9f923a0d6f7daf45d4948eead03a8b9c0762bad277574e641329658cbb359e1a5955bfbfa0494f1608520c0ad7480b103f851b394c709293c63d98e201639361b969a1c35f9f7d188e51a6caa5a768909749b1771b58b7738a6050e07a3045a11f1937229e421affc98ce90795e50241f0df1df86e7b7bb14d79661ed589d2d3156cafb2779f31a34c2f956f44e7871102bcf333c2401d32d96c59544108c5543bc92360548a5afca51f90f8dd23a2a6802e73f8580dbe5fb370b7accb8b0599060955eadaa20b14bec00374ab5656bea580d52ff7d5ace21de76e13c96b1f2f1dfb0c239a442654bc10b13bac9538d4856549c432600a2aa3104e0ef3263a9ca4d59d4d8ecc9a6ca5c2008a131451287dbc5fc20ae7306709f13b3a267527c81e80764daef476b212d22128f33fd98593a10f550afaa9a601e2c0f8eaf419fd62c2751da582cada0f55c8e1e55101ac2dfe1c69f07d077db85f20fc8489d379db843d936b242d83372d8554733c88ea4d9bfee559bf7e8e9e8705bd5f49bc1635732d59bf1f6652e3a88e6ba20a87e75f09de968e9eeddf0f46959093e73e0be3f2ca590c3045ad23adbcf47b486925136a6e936a74aabde40d6e3f54531d9327b5707dc412e38d9910da1a6e5dd0e4db23397235851158ffde104031f0808130f8230a3d92eee9373d05080ae964a9d6fb65f829e1d4cbb1183df25eace3d0bb04a007873958b7bdc2afee9394945a1cd481c2d0e5a5903f3b446544e678c8d7ef3c429a8d6e2ded9ae7a0638666a3a2a5b9f8312ebc77cf220bcd84f0a8c61a61e51c7c799610fc3869959db58a353adb61318115491be78ac331d6111477b868db482ed665b24e85bd4aa7acafef847939528930479af24d1845dd27250e09044a8839102bd9669767f4768d2b394116034e039b91d9cb8d7481ef9ed7590b302a30a2be9b05831fecd9c3ee06842d55f6e15ade772962ac6fcfa3aa8200b03daa21c283c0f89cd00b8d52468c9eaf458165acbfa3f6bfc2067b78862aeae2af1f74d5eb961bc807afbb5d63927ce34e268f144670bdbb654c8dc3590deb7f7575da7618e837aca37c85b8dc023b53a54044ef07b4aa4244b095548740c7faaa7e80d6c597ac149cfbf7070282200b5b114477e113df3cd1f4b1599fd680deaf6ec366384be4a89d77274aaee1a41fcf4c06cdb784774bac3c6172aa0bbab3c3cd084f9ceb99603ee64fd8df8cafe5e8c1b0c7d93db5d1e2f7e253a8a2372657c3d15f6a82996ce5d3ea1f2a18ed5e2abcec04734fae9a9fd621554e0e91259fc76d6f6f84463c7b046222595a8a98ece45b2ee290432c0e8020f5430e94558f9d096a85abfc21c3a76b6fc95c52921002f339e1d267b04fae819c9126163bdb7965927de3a2ecf9a69bc282d59c57fca24dcd72023240b51f76ba06b512b269d3ad995362119fb428d337a5b146e63780c5760bd138835301b12aa9a3290ea4e68c142430b1c37d6dd383dd118c96a17898b03933dcd93075b2bf46fd35d1c2f50721be84f350cf365d138517676645e5dee0d4737bd8bbe9443f225aae74b9ec9c4fef0ff5a95ee88d4741667a358e4fb10a20d5baf1b0e637e41382a3fb812995f953598d86af476c2f14a736188390b542c2956ff4d79040b9ae3fc8c9b3f42e12d2194355ef97068f2e7bac555d4f8fa8bc3af2cbf90d4fb21c0f1bc467c493f1287b5612483c010a251faf13b7b80a17f8d21db279a4b113dad20a631bcf95d5527c05313312314e602a6d05c8ec91126aa818dc86e5af6a3f3425ac3bcef2e37f4adcf07ed8310ff80050a38e2a8797d33fbe41af2665c17da85a7952c0bf397c1b85242f404af070c1e94436ccfdb72701176027630213a097a8c5460e50ca61aad6a6e079f6f1988eded45a3e502e1a56f03453edef5500f4268f3924302633b39a8776229ca06b8bb23669450b73038411cb68755e7a18da07cc06b0110cb6cf710fe12a50f804d7a36ca77209b5624f56d9241303b64d2749639f327622eaa1f8583fdac0567471257b1832860a0787af9311dd92802ba8853f2a7bb7d6c71030854f6a39c0c384ef99382828738a6abdf4ec5908c3786321d5b305849c686ec28281fbcd6ac8506435f0c1502bce8e22e360993cf75e54e65a85391d89dd2716ce0a3bdaeac00f75358f005bac27b62eeb31b44533c8bf8f90199c6606edb52d5d688d9b88c3bf613385f1d3092e60c308b074ee953dc07c8cd97d94807230583356fd03e39c2e5f5442c05a0fadcf274762d49059e39786361971a36e561df57d51000be08476e1761428d72969d8bd80d858fe7d48ce52b7f8314cf814c270f5c727f299b3c4bfd962e5f403df7edb3da7b99118a38648ffb0619c4934358187d838572425eed5104612caf8ef3e818ec2066f844b028e3dc17f968fc7f225d77d1ff119c2b7f8b922dafa888a948973c7561063c91806ea977357fdcd7654622aeaf10840c21a134211116595de232a451d238a6dbc33a4fd711abf22328c30e93c62cded4da22db5a280dc8977a5fdb5aabef546616afbb3be20a6bf2858a94c22746d670cdd2437f27dd28d1accc4006878afaedbe4f9b372cd89625831a72eca441055dfb570a64fe7670835f82aec7a22747d87ba1befbefc3e59b1b078b2a1ee42bb2f8b321795f65abfe7bee4e5117b92a915a59e560f5fae629b36fd7eac76290747ac7188aa00d00c741acd1f807190208e086aabf53fd44203ad414ccfc0156d35f92a59e06ba2ae75c66a4f12869bb77d017d8ff349ceed5d1baa30ef2806057be7551369c02c8ac01bf9767447c6ce58ce22b48f389ff7763906fdbdcc8314f27bb276881d77b6526fa0c3ae974c97a9eeebf8f37279f39e352337ce94e0d42a871b3e2c85dea937815aa4b43df258fbc9689b1124a6a2221e0ea7180ab7077692cdaf53d4fd3f9ea410ea781945323ad276e9e78b836ba9f4b4ca3bb0d57daf584bb3fd0b473b47b62fd880c03770668e2be310f9da5c3de193a9a5ec67996ff0c3b0d928d970ec9ad44aaa6f4f6513f8c92e5c38fa413a33fcf3408f6da66029068740fb19f814456389126381f5ff01cb26b403d790a70e86a63c262027f00c958173602d21d96fc31360f1c387c6f8dc880d55dfdc25b5ee7f2b4065e3b5804e660fb810c7e4802739f5e77545e799b97f4f32f5f247a2097b94aa8d95c4409e2fc10a7ae0125a0141403ce043523f15c4e7f2ff403973840318cb518dc084f4135de7d19bcf7938ca7a06a86a758158782a3d34f5064e63a97ffadcf138ad54397244268cefd080d4e3661435430e3d10e84c03fe6d3d3357be9cec6ca063d0c7a0ef530eb3d7a7aa193e889976dccc4f02413ffa2238c6c8a7d1d3b2ac7125c0b7f5d94b869fb9d13c618a2c06458554b3e4cc9f104b8595eeee79a1e7fa1b7263deb4b0bc1d8e351e04d912146c6de4848b20690e720ab6a80ccc101a66c2047d04d32c41d4f3c376101728938ea8f7ce1ae0b37d314801872474650ccfce3b98fadd2a8cffe6086c0533097239bed9921cab26131b05bf985de7a1f6459ac0f3b58e7c5e17db2a4a8d9bc1e22012bf638b171bee77009131f84df8ff3697ae0508ae72724ae81a2541917eb5c14bfb4bb82533e5d1a8a1d2049132c79d5961d58fa7b034a2b3ae61c04e8554f2c8baebd4c7bb5177f2a5de6d68e1ca19233e1bdc775b74ce240fb4255cad2945d476a14f9a304613d1d4ffdbcf7ee2f3d208c3dfc390f7d001238994488083575cbf20ef0762dc39ffae91261c1237823e70d6103bb5a2421364919d897de2daf54215471642d97f8083b152436fb8e1fe4c79dad9b8b2bb4ff8fb2ea2ce98875c4b05792d84ec4e1488d740c3492751dd4a67db620187054391e41307e89d63ab461dca7f04f68dfe1fc4344245e849fcdd74c5676be2f99cf1686afa52483045bd6f0faedceaec6ec89b0d7845993c069ffa7d8632f90f42852a68418ffa95ce3d66146126ab6dc807d77debd15122cb959a76c90e827d6048f541244b79bb8afbc0e2d4eb5cc0d020e680a504d785b979191dc85935ecc76af1db5a4b132bb89d7239ad0d3cb92a2017c5308619f5eec877e7a592e4fb4f5591d8289a02881dc5efb79162c92604c6890161f094c088aa440027b1a23701e2158cdf34edee4bf8dd7b34910d505e4c5d680d3a63431fac34151619a141dbba5a617d9119f7c13b866938c8c02a0e09003afa6606d2ed7983ea27971585e7c440a87cebc58eb64d3be723dc318c28b7368be3014c053bd92ac27c2f322e9d6004a1ca00295634be5d9b41602da020f9818176c967b6e0b0590a5f8522e9e8378dc7fc53b618ed2540fc9f6817101451979faa0c0b4212ac119c198765e741857a9706576cdf7798501a523387be25c195fc070ee9047cacf4234b833cdb77f3b556c568fb00a19d5f289d7d560a7fc2523556fdfbe28cadd59df1d6572bece8752c9a2ebc0fc3335d7e05765b9e525db610f2e683ebb234b753d4dd89b4aa54f6ba665e04c26fa24f14e9a02be270540c085fc17a56aed5d98c34119a73340a818433bcedc8ea9b3773e1b4198e020373f9561463188d987063aa0814f61fd48f72dbc3e5a398556ecaea40b3d2b99289fe59513c46c057adbadc73e507a59bb54c7a7e68ba80647f57a92a2dcac0fe6e5e511786d4e1a3b06f332ac1c03012e1187e2c71eaf23d9cf8a5bb346b673bc40d607f471a17052b7a8b5798d7ac8224ae03fa006750ece829c2e84b5134a699995c041c04ce4b6269c0add205b36a05a949434de002f1422bcd372901d6b04c4cd9140814d9994975bebf1c7ad124cb1fce29925172279ed3369c95a9b0780137c6f9c8281dc510027a9b8f3cd38cc0dd721b9f4f9a398b8749150bc58a70f0adc27988909b2d512645e34a482b9179df97a920d065e6febd25e84d70abc3627765ea6a56d7a090d63ee9ea636d2383a19758367adfea8a2acd62cdba103c7add802802e952c5bd16e937b22045c8748325cc3d0850c488c267a60da1c7151d2d0293a8b4d319f3db02b8d5363d30a636c56bbb3e61aec0e25027dbabd59c2d3f2be42e621588303e766f298a29f27df98a9f27a6a3a624a6369c225ca4049584f025cc99f6f3334cbda9373c7c3964f785a9e58ec2538189c373c2dd123cf86a4df66312d6663edce96108b25c638a4d743c0f2e8c9934277d4e7a304d052467dc47316f14f4dacce72e2849e3bc46ec58b9448614c191b421ffdd43670bff983bf78d6713be30bfa39ef55b11984505050771b7e004f273e29ad70098e19a4905870c8a51fdfda214b40328c4bc1c1156195e098c435a504aac376a3044f23302a9f1ae04e08eada26bc0acf0f5057fe5284facd2e2408bb04f4a115035695d04770377d4de3bf546edf900811be10e630dc9c6e1cc1a7e236ea2a8c7e1521e7a73d310fb04953e871ed006dc8cdc1f242099914e3347550d4a379f8b30e2b007e0795964152376a0b3bb160011e1ca1c5ad8386053f81e10ad35068d453c0b415719704856c49cbe7a382b3535aaab206bfd3dc40ae29cf08d53401bd95b2a720d957d8a0718c3fcbc2808920453029e81db465fba14549e12bbb1b2a05d6ec9def9f36d9ea68942c8e0bad0d2ceece7a2e6ba6441274dce4daf3beda3e1df51394687bcaa88ded1317a5059da98881df561db2643fdaf14569b10d6b4ace3ea5da3c14a565e09c16de6025874045042dd02ca25685dd342ec446a098c48602e63f7771b409f22eee2d3e01ac3c34a92c45655604f8e096ed6c568e1f2b3977e3e9b22ff1c532ec2a106638dd5f21df907d26327c1381d1b0fd2617ac64f29ad7ef846176b5f8f1102c965f71524e26a344eba7e184011480b91c1ae31aa173cc3ac08c880fed64d94a3863c707e23f1efb4b21517e91d6949528352e8c303f6671dfe7fda544509a3a36ed6be0b2043575c2e7cf21d198003d7085cf91a1e0b9fb24764de0d246d6b50f6ed08b85c3f4e7b222e70ee20fd00a047697e0e26196b1435a0c0af82697090810a63db6a60e6481964d96fabc0408081655170a156160cb860ffd591179fe877dc974236c26449371abb6660aaddab0f805e40311ab764c865f83abda8b982aeed0d0bde3fbc9a6175a404d8a6b3bead71df7b258aa4114bd27b5b11b9aff0ce0a271581a39ee80e2b7e949716e55453f1e4c22fca0976eb7dd078bf954a57a43ccdc7cdc41e9f0a228073c2f81a18e61c5e0519c40c6034419000cdeac98acc506ed5df9bfbf933e85ab01ed89a6f88f17af74724c60abc37cba6d4602c2f846379ee4fc2ed0adc0f8aec53c21ac3339dd6e89d32d7a52e0c56f48a4fee08d74b03a222966f7883ed305be0ca40e5781aa15b1b55b092d330cb8e1136fe4ccd002c5a76900a6db02f2906a3844a513de892ef777b459cd4ab2bf743ea49276a7231a4dae9d8c1c8fbc680d54e4a57f0f3f44c7c075b47d2871b1ecc3a213022b7279625ce2ef2f8be51906e1108e3829211165941c05d8a963a888585a3814d6206c4640cf026feb1f454f77050b8da09f018ffe0e02c26f3b1f22e2b296eeb2e8bce0ee78790cfd7a5bd9aac4b37fc9a8ceb4d489c8fa6a8c4ca2d486a20be5d0ef449f3a77dfbf9110f0075783cec1df50856662d7ed2fbabdb94900cc1dced0c25d6c66824231fe41add6c7275c64a627319b874eac7247bb3c4c3f56f5c653eeaa5807ee72f75c400ba044136576a6e9c8ea87492d51312b0589f1e51520bbb8150901a0a0c0963d5ad5e3b35d33f106f9126f971f853d6fda2bc246a5fc6036ff600a2fdbcb9db7a8393e5157197aecb158af8064656969787ad7c0fd01f1fdee7275ff43bc6acb2adb0fe5a68394a5dd9eabf01a6d55f83fea7e8d7295a00fd663bbed89e3cda2fb5d95fb3761bb35a6cc10152c0d4730627869af346fa0606bbf3b106407d76e8705c7255c26221e748278d6da2ddf4e5b4bed05cd36c20a87af87eede237ce565576fc5639863c86c2722355320af3e65f04dc657a89b238e4c1ad7c674b7aa07517a01736799e9e27f690a72ef5e67b8ac364a8191c6471b135e5410663c7e9878c547d2e9af4dafc200dc45d996e2303a2fdce53892cfaafd4ea6f09375322b797b09d2e640ef6fa71e1e208fb605dc5643b2fdddb04f9848e08eb656ca586fe2ae0227b98eb605422ba536596e8b3311738cb630f262933f6e0787aba69221a0ae916d835ef95851b6c702073c163399b432c9a25735619f646233f554b4d6f9959123a77ca9cde399a55f021e56b75e3bc776ab791f9bd4ada8bc666ca7d2c8833d9a0475e0176e83a77d32f61ca8e22c5508d2cc3a479b2d1f0978ed89643f6a6d659df5a2e40ef5b818869e2ce3673cfce901b9a02b5987bc3b68b14651da4763fabd379e3648f4590a11f4e96c1597fa676f0f5a61d86d5fd92eb9f2d463fc8125412387a4f96173a0371b5148b7b9867ca1e127a91b5e2c803868b365d8ff0c178903f9c1841b707006433e3473a1b69a1fee3b43031e8ed76a0b335146bc3f1782c6a072e424c90ea38c86052781d101a8b9d32ded239da5aa0b95420f97623effc0844dba239b18587860da703e61bda493f381451811aa927ba24ef533288de9823a587007ad7758e7c502e74e21230c5775c0e315aefb9d78546fef7910b9bd58ccbf59f6bc112544ad78217f402ba7e08ef3fe3a06c443b74fd3aeeeec4f7f930112100becd9c89b9a5af43d6f0107bdb122fd6b1d00be040d3cb664eb24e0a95cc0ad2d3bf5fdf2975b521eefd790b718cbd34b26e0c2b3bbbb4db6268fc184a73825b6b5265e6480d050ad9f3f25bf6de1f2332125e01ddc8aba944bc03faace27663a8344644456e070664525ab94549ef381c04aa4d5396e1dc779ff944b1c10e5b238aa88274c6f70d676fdc2400404e61073ad4959a78eb8170f987426ad52ec4a8752e2646bb706cab4720a41ca256bd7cf58e5031929971cee1c99d346cd29280f71aa839cf04b845b93197a74f93ffc4d9a2eb727f73c70f754f979ceef0f70f7fd4d2597ac710265587ef46a6f4c786bf2a8c5c3b2231a70012c1e390bff09e6516ffbd41f3299881200f4607c6856f12ed4f7df37100e0a01641279dde265234a908971592ec2d0565c4ed6b7cc44c1a46b5f110de0041434dcc1b1372e956492e3e5bd56d33669f83e2ac91d471cc8e4819278f1b411202f86926aa4cf8c5c4f88daf00c71b83f441063e50d89e85634174f51cd54a89a26e64cc31c631eaa26be5c55707fdd4d137b07a5ac4672dc47021c3ab06a091d357fc278d2fd32a478811d8f5567b75e0a3445c889e8e87ae1226859f7c22b903b04306d110e814965bdc21ee0da6b790ccf072c0996a70cfcb2107394848aed5f86ee2276d0d1acb76209fb156bbb53f6ff08926486391d39a0a3244a85782f7cecd8f65098dc843dab67734cbc1e275095ee5242c61319eb984f359022ee3dcbe162368e9258584b0284a296bb1c25b18331a090924c2a12e545d88692f728d76a9a197d1854d8ded72d5212516458fcfc143258ce20bbb66e801eb0d7ddf5c2c77c44d9f40d1bff9b58468443c80d8662d86b6a0c08efd765e7486e83b2690f590cf1ee2a01229e52ad5c00ecafa18fbc5e101f4e499cf59484de8a9077d952a8306e0e267216bfec38aa7de002a25c328cdcd82cb01399d44b3e5712f0095e6635668ee03bcd4a5049f057571b919e488b71d297c1bc173737956468f2344573d8bc707afea06cf0ee6bec2a6046f1afcc79008a716f6e4a92278dff897f115a3d4f9b16ff9c44491a504331dc5a3a1c4b1f4f5649de9fd267105749324491b8c4c11705201e14ac248b82f89b5dd04ec838ae56aa0e1b630865590b2455be78cc9de27e5a211cb009f8487d58809479da02f2cf846ac051d3d5001fc92db6081293c50dd311715e96ba0bcc05f6dac135e0234b14ed2e283e7852a4bbaadd065004f808e78fae04a2539dda0161e475182e464e7f09366fddaacbc5f33042c2f82b31d39ec6eb0afc2f2846066edf9db3410c8b6c20fc7ad9f9cfcc812d0a4de79746cf621767fa8dea27e1b091171b850fddeb31388090fe44ce9fe6bae4e801a0d89f009fa86f0f931b291215a4e92056ca483073218d93d48a81d8e09cf1054a88a19440bb9f0e3aa991f8e5fa0bc5d1eabc0a4ded30e7d0b18475a56029061c0cda421160b01375c2d8c5d00f7dd90377adc9efdd3de43b3b92aa578f7ad8813d1860569f3ba2b07c01bfdd91872058e9f3dbb2b69f5a3bfd93d342481f4578a46925de7544d72fc7376f1ac4358b521c5a5c39f331b2d321d21021ed2ed748a6d31f389a10fe0f572bb56b71bedf7d895046c14feaf11ba2b2e45c4b7969b903211e29949b090db7e81fad30f05c5eb21a97f65d5e7e77e31bc172cde16fcc852f4830a079746884727ccae5659e177ddd2d5cec48407d0d16e9e4915117cd3eb121af9c95be6c7c3862a7a6bbfa7d790fa49594403701c4425688ea505974af1ac148f510a122784d651374881847c05d8d7f14d8612657ec7c104bfcccd36dfc8e2f69c9c03c9ea68a1c71cc991b31fae4d12de07bee27799de2fe240864bfa256a1b780a369e252625b254f2c1223f1fb17b012f9ad3ae400467b32b10d5b8d0d498e637ebae0f1b4438528f260a9f1dde1c4392435beca23af402f3fb2b6116e5e2bb61d3c958027114a62537f3a9e7ed9fd9ddebc4cc0005baea42c3106300db9400ec8336eb4c60bdf91d574096664e1271f7247f1eff0de93c21021395b75a9afde72b301fcbcc1ee662bce54008312c08ac32bbee13c423cb9b106cda78d53132ee1d638197f2208ddf3ef2b2ce4831f797f8c4f171bb1c77fce8bb79f17b1b9ffb96ce3492e637da0b410c42cbd14c81b3ed4a2a28a4de5e881cc3d8e952af8fda8c7971906d6bfd89080f679fbb98db4cf978340b2666da9c88c3d8b7095d0addb7b5cf42eb34043af8f87e401640887f1273cc3746ea9242dc18b6de591c3f846b0f3cc039d9856f078e393da22338b464cdb56da53e10fb2b2fec2856e78f1a3b32655d903fb43b7a0b031fe84788bde59708eaa1b7b3f0e92f95d2af20fe3e36d04ea1e0ae43053fe022cf412efc1f860d07a6f4f7c826001f397dd8b56dbca1520227f687ef1edf7a5abd8e05aaba5892810fd98bd172c56e71b05389d8cd03a3a0070a552173f4be0ab0c4b73cbabde22e53cc2d4ae4d9afb41308a96ee56236b031b25445ef48fd514a5027f2b1b8a3b87a92b0b23e2df5a4b2b0ef1af040da520352004e7f32c40a5a3955448dea1b5f2e56fbe69f8ad92a47748af76cc4c9fd36fcd5b1101ff41b34e87ecb90be5159067b4cd0b9d3b53e33a63ce0d9b6e4bf1674f5f8781b329fe00c90a50b7d77b0d8cff45898b26f9d1627c93353b86cc28a9c59e2157c4b9d2690cf1c9f2c547e0c2f8986d0dcbd8078da3aec458a57be83dfdde953ee47992a23c19152c9994bdcb75868efca6f4c0efcdb098b9e26a3056a75114fc80ac235f77f0a91191587f5dc62e7660dec0ff47b70fba3fad4b6a693658af26014df5c2613d1e84ae9734d95db5e17e13d5ab4b7cb8e27dbecbcd2333a8ec963026a67ad2e211ba5c987bbf598c2262a8fe3bf2af32fddf93de86b5bdcfd7fce2c07fe60d70052e5ff5734404f0025ca9cf827645b85b5a344fb238eff31836b8ddde5f206b9a4dd703479cf7b63e92044ceb30371ea5e200c1d9be48340da1e0e135278d8f70a33a664698dd58e7803851ef5d92fd3fb0527a501cdea14eef9d658f041db91c34ade457d5f7bd4d0827a0dcbb15bf41fd041596507eb6d995c72f6e281679cc49c300965214aefe0a2e2dea49e662b0205827b19392b09903b6ba57c887a89172ef2083cabdbf25a5c8c44eb4b7dd07bb8b9aa8d88f7c60bc7b8b02c990a967a04978a389e6d174cd20cd62c6ce555f3e40ef7491b57d16bb9e2a685969c49dbce847956f8a2c4ef38724e7ba8ad42009bc33530b64c40dd751b4f742062b882ec4122d03630c8442ca1686e2cbd073e8cb7cedfacdc8e80814033b28dd28cf92aa997cf5ab0284637ba7c4e62b6cf7003f2cfbc28ddb572e2b22f8217b1cc2d1766b43c5b5dac08e0f4e9526dadc5e9374943d646d52b464d4ad869268cc81f53502a2056ba93875432392cd823617da0b21337e2fb999cea0551606018715c094336ec3498dff32647ed876bec5f4f8233d581b5e0be302a3fcdf5d795c0512e791591ee93a8b041ffc9c16ce23c3ee249ce63f8f10aaf69e499139355765029c0c4ec7885268f516946831ee43d78da6d24f76293026ba2bbe9274a5b0f965514c9a0363438d1988b0a6107404ec78b6a579130c7be8c8fb6ba18600baf7848cc9a34456219ddb737b5ec0fb20fce3601e09962eb6c022a06185e3e748dbe3ed145f5eafc0125823f2260e6bb9773d23dd465bbc78fc40c5c917979a7c01d3e567dd7a8d805981be1a876d6fcb24480a5008b2162814a717cb6cdc8a41dca3fad4087c183d77ee9dc554247b932100a28b35e2a837f5b361d0c7d34c7ce20fd13f065aa42a5a9b7c25a6db58efbd3aaa7f4fa10d91a777aebc834321032391e2d029f4fad0de9297021f376ec98449b2737dbdb2f668229bd9bf29ef881d5c2f7803de7b25036338789b70fbda6030c9446d8840de4312130990fb1501729066038fbc001306968d937088424a4bc812db9128de0d63dabd5f3ce063047d2fd77dec016bef835c013b3ea809ae6601932943c562ae49943241489c43cbc78982890c4dbdc91a920791c8bd214da63107a4839702c1b6ae8d3526758fda5381212cccf1d3cb8e4ee6c5a4c3659e02133825efdb67f31cc800a9cbd11647c1f59aaf7a74400bad708b935600527529a6de832009cbc46fc0e5d7c6fb5ede17526ab56964ff6633638650c63a2ae025af37f65ae9a8ab83ef167891ff105079abf17d717d847a610a0e24cbaf3de17bb4c1cd04fd7ee47b348f217e13719a9f7ee80fc0ea3844c901fb9a8c88a49aba31a9cc36a53d47f7e4c269ecf26c445cb565c73e97a2259f2042322c605f826fe5c6d2d79760e4bdc74c92c7d425b413ac18880044a3a90c66bd54f213fb571324eca2b353bef7a70842c95b11e7c6de47ced7c5f916326e5ca60584ca768077be4b601934200f3ebdefbdfaf32107129d549e8d909a38da62cd3515ca74811371be61ac892e817e87ae94e79239c21a195c57329ce56f571d6f423e76612772a2370a0fb13d9d6559432fc7f413268567d67b5e8f514f233d264f2f3a3e88b5439be4e9e18ee962da3e5d5f33e39a8d4d0cc5289e74264b421164b0dbc647fa45813381ec1ded3973aafd143694e49f28d0a104e462b4691afdc16037ace1f991524d72246c9fd9dee2a2536f64c87621ec7fd3be102f8348918d23022a51498b46b994a5b8bf800e94ca3883ca594f89418efab5eb9f5af3e10a862f89f8126022ec3ecc2f3dbc36812dcfcef68adc3ef018162cd32d19dd247d221e9e835e463dcff598f536d16bf2f4a0e383b09825e9996928d6def28d02f3a992059621c1a626a01ecedc083e9788e77487ac8fb0da691c80d711a0584f7c2ef600a27a9292510e67ac9111dbcd5ba2fdd87f8972c53e06d21ff734bc89be1b5a8dfb18de9c7d29acc27dbcbf37082cd0dba26fdacbe086f4676825ecdfe036f063b07aec69e016f86db012ecd1e036f8507eaf9b21b6156d6103784b06d900690f8a88f7167cd2e3a6a7e28f733d9908dc64e9cd8cc24d70a962184ca3b2fb53cf2086c34f1d032b4927a99220039817c6d8702e950740bf911b8ae8a2298245ac488a3a2026583b921fc115639f5efefd5a04dd54a5ce13df44e091b0da392cf975e4050fb1c175cb679d8ba573ce8524190705928c830299dca10859e6a08024e3502021732a22c939299064381448640e0ac872074564198702129943010996b9634621f0ed1f90c91a68980e12d9630169e651912ce3a4482273504092392820c93915c8f8dc3b0202e16f3e2b995961c1ea90c8381549e49c0a4858c61d3102816fdf5e4c56a0c07491654f8b48b3870a2419870212b9530199dc418144c6a140e233de11503e7f97f532c2d28475093c20057fd9f8b4085c3e4d09568be283f462719cc02fc65ef669e98ca882e986de55f3f636b695b1fe80bd14065a4d4a8694d520867c5fe4ae5308e6a141dbb99dfccdbf02935397d6df9efe1036d59709db996cad6359abfcf881dcc73c11e0ad45381604d8902e221ec2e17806be109db835299b60efd4670bf785203705079776c51a390447e220c531215d441ca844e2667588274c6318c57cce9b2a40d94a04e22002576448a9f3584771c6cae22f4943a1965a00b187075e8dac5a23ccc69bf8e04e1045e276fbbf51746458193963a4c0bf9429798a1105e280bf237bc973abd57c7ce6465db82f2b515c2b89d6fb1e53c53c537b192598d5450fbe21d4e2b70f586db7386ad2bb0e083255c8050303215f93a496fc32499dd9bcb8a781086391decdc1a6ec8d1103d0f779812d10b50ec78721d05453e66e13b64c6c6503297cf79718354776555438aecb73386abcb32eb2b1582af39c1ddd45ccdff249d3a88f5c75e1217ce86d7a0e48310fc58b335d59ad5d071f34a29d6d3a427d3ddd435c49e55394da355c4df7102e8093b8e412b48b32cfd8f129319e64a3ec7ac67927f1ff44968fa764390d31752af7a6f10a8213f77b69696697f186c30f2e92a2a6688aa0882876583ccdef1afe04af827d7ff377eccb41c0176fcdda483225fe75e1f70fb165e3e74f3044c5e56888320c3a33b27edec62ed0b07eac59d023dfefeee8895b8063beae290314882ed8fd402440f2137a3d44976de92c8d541ca97f73d2edbae98d9d1f12c9d0aa75547dd911f4e6b83e56817d2bee2bfe636d5f52b40154bad43c6b67b1c5a2389726b3de8052ae0b3614a2bf06ae93fb55c2a492e4a9a764f66ebd2540c5232e83b9cfb020e5305b16fbf30dc3bfcd1cdef83e5d593e6db3994d69a2a9fe95bad8a9e0663f65b88bbb5d9e0c16cf000b74d8a0d06c05279eeeca4934bccf08221f652399f68e071997fdc318d0727a566e58cfa57c7916c09e42269ef2ba75e835329f8f810eb3bbc19426cf8f65b51514044632696d6d5342c4391f611dda8ff538ceb5be120905b52f2c873a891abce3af0c50830af7d17b8385b964b1c39a18b52882e355e3c44fb521814a44f2c567a61ffabfe61b79387d0aa29980dee6569a6ee5c83b0612efae4f968fddb692249926459997934398c0bf8319ef414e49773c16e860538ae82da22885189d3fd8a7ef469be1360975fd4b1940f6dc3cde60cc4ddc3fb95170121a55109cd4d43561c823c0f7f3165887a00f1df5afa636df6f4981f1146a665f3415c05a40d28c5c49ee26ff5621025a686f47053494e11800330849f10f673cbfd0db7f19aa1ec065f96e2f248948917de4d64712e7909adafaf652a527ef2550d60f2522ae66fc5edd5c4ff6d32a8f856e3f12240cdb189901549dbba812d875711e52e33f71d101757a81b5e3171aa4fe8dbf187598a50f3d9831dc168b56200671470094fed626644c6efcb8bc73a41bca2d70f628818e0d17041062d4a7941c31d65038709b6fb9f5a6cc683acd1a0f588e8742cabeeef8af30d1af3976427b30ecf207a2b6557dd8e0c90842d62846ce5517078aca79e0abc4821b082e011f10d261010c56029062446f015a813f7000bb571f233b400bbfbb120d2e47c6b948da45d9b3f346db159815815391d08738001004624f1588f425b88231629a3c49e636e931eb5c6c87417082abbc8ec91a87a3f4591fe04a2f2dbd51670b462a6e8ce935eacff3e88f52f1a7995085218185f91b82f162ce8a5f8fe11be3a33985a04d3bf85b92e5aedc0c8a43ca1d95e0b9789351d3d518ad1b810fd78bf86663e3314dbdca38ec041bd3ce1d0e1b6981d80f260356a4ac5ec7499a4640c413ada19ed7127224f85e85c2fb1a5be85e83b060c35ea1d512d117f2109c66e889cb127e3f9688e223530aad5a057b8652b289b9035da0f04679ae9392d39bbe457e92c271f19b65280f624b04cc89b7d1a0a7ea4f31f212cf40294f46be36199ccd838128d6003bba89f6969f660705f67003710b0828b06af2e72ca9e42528b09f314b408da693e12c13a78b53d54551c1629826e7628226cee247d8316b46fedf5b661ab832f2bc49328d54b73941c215e4dec44119bb673ef4b37fef32b0cdd59202ef17200ba6b6821db2a014cded45c8c31358a0872933b23c54052178efcf7b4e581aa9427831609ae19b2dc9424a00aab77c26891a9aaabd97671d2726813449bc3130bdc1d920f437a04c96b340e9086876788dbad770571a77218aa51b8d74a311747bcacc99d8762d81bfae6b0d777513fd501a75031a9e88d2ba6b03802455a17d14d5815be3de74a1f1aea1c96439ae36820c452bf0dc97099673e93d560809f7134a16a82cceb1c9f27a47ee2e421e9ec2029d6978623a94a52a09d23398fd0d747dcab24c02690d66cba18ffdac954559e3b6875fbcc69c7e8d43d46d7a828036944a4dd090f7b5fc71a1045df5267a34176807d0ae9460cfb5955424d35b4770c724f1dd8e95a5b59caeb9b49ab97f00fff08f06d868955c13a11be42ac4971aace0a3f7d132c43a792e32f89008ee6e19ea786035be4d172fbe397167e6542753eb1d5cc3ba7ef8a95814eb5a12dc805439b9841a3388fbaaa7740603c35e07288882030a44c1e4d6504e3ba42c9b7250bc7faf81b4a76314a063dea67890a4ef8dae81b0df0194a2061dc0d4b76dd65a9e4a6ad124e30980ef4d8becc7de53f742a5fab2cf15dd21b3fdeab60c35d0a3f5f31cb1932d294850d2c202db94351420fa2e58873c0043d2bd03e7fefb39d47f9fc44da70e6eaa9607c04d1e765f39744cb9a81c8134d8e8abbb43f47ca876c5479d1d0cacfe4c7092b44f39921c0021e9074d2f57b5c94c1136e480d3623171c9bac7dd1780c23e5209997fa08b4d39f027e803f886cd934c60b3f9707236b186f3df15e03cac7bc6965e835e554c2f8f51261876ef8b6da03c07bad75bcc94d5c3989c2367ef65ca8cf18204379d372e12e5329839fc7c96e07f02c68ca683ea53311c7b7096326f74ca5874359a8c8e245a6e03e5d7ce548319b733dbb18bd83e50c66d51c41c17e44c056252f1b402b73b4fe2706c0bd18298a0a731b3154e36b8b0e091713d62e19f50546fecc65e942937b7453c3947055dcad5335680852da2aea188c9d8f1e876ae3d00bee0723955db47bfda627068d019c1e199d6acd626f185c32324de6be9922c20270cf092044a255c6b28c04ba3a7621e5cca9c84efc0068c74cea21ba205743743f15808701fcfecc889b4a763f92a54f200fb9ed35fc727cc1518d002a081b2391871bf7dfd1451fe8c36cbc8930f2ddde3d91b03824bdaa4ec3a4d24e2883bda99a5faff61c08faaca83a32492d92a0d6710f23c80468f8c2a41ae9c34652c84fc642e4560231d3ff3ad92b5ddc00950bc330e42cb6da470e9af02240d1664a3379995f2391e1c6c0fb0baf3c8832fa829c9d710ed765a819867173c180389707b1911388a551b9447af69c4806089dbdda7c9383504ccf86d718debabce53e45ca7e372bd889caa88d5442a8dfd96f22471a3737bb88eb6759bc0c9b99074d458e89a9bde90a1a94bb192cdaa8436376d49f5e03241b85953fedc5de48417c35198aa328d1f05b6036f57104205a1ad40f83ba0277cad280bbed6a02984f196e3309cc9cd3b75fc6edbe14a0b6bff77536d977f38b8816ffe63a0225c1d61b914d6966100788f1bcc3e439db0d60d43e61cb70c9052a1d47227b5e023a281a8eb38bb149f3572f1447207e0314bf34fdce57e4d00dfebb524cae84d37ba3fa502b0dbfbcaa8b6fcbb4a8d6fee9bc62729f16f1e446587041b98b8a08860810e27901da219b95bd9b7292e6a7b9f210cb55ebb9ea24b9d9ddbd8426ff7ca857545e1dbe14c4f5fa02bf1daf36110433dec31098e1733825d1653fe8da7f5e061690078eb0ad351b64ff9f0e7bb45d2094461875d1d0001ec6df0fd31ecc673d6a1ad8249ed2e0608506f2681c224afce55e6a91b06e1a5e4680ba4c8cda82e19db0fb885c81ba3e15713499be021fb2192c98cd60528c664063dae26158ecd96e58a158b3d0f618593fa3be3a399ae8a188f0a754012f885e8acf7079bb3378f38998bd43f5bf7bb24e0994edb2b2af2727dd921eab48e887c2752c20ed1e76f790c90b8e9e709132590f12e7063a7f22ddb7988d6b1251856d8f7960312ba78535ff0d8c78c855949b72f5a04d8bc57cbae9f031cbd03cbdd54af9bd7adb9faa232c75f7a83a5fe6171b789fd4bd697e6f531c5c9f3519a9637fc2a1e33c8b121eacce0f0d88b914d88ce95191c4d783d1aa25d0f93450cd2c809867e8b74c016aed87379e8a08f17e1f18e62fb97e7db90fe1b91d0f135283be5537e3160c58bec013f5acd73d55c41396f4f71813e25cec5c5f9e3b839647d1c6b5f1e0f078e6fa8fe9ab954c3aceb4e7082f1479d83f393ab4a70f5c321de5ff6da8ff980c0de5cdd35f20a8cb0da4294a9c220942040a85a509eceaafe944b1883a744a58371f53950444bf6e8157937695cf95780bc1888ce4bd2b4706ff65a78070c92c4c05956393acf4b0423311b2c7ac3a3ceb12d2a90d0ce70982c71ab07d100eeb0c2436f20bccfc5e3b1f17c28e2372656bc7097befa9ed1527bcee1018cab2273a0924cc33886a6ed7267fa0f3e0dca2fe33a8cbfcadac2681f13c8ab306804744ea33c981e9cfe140becb86ad12d8db117e8421aa97a49e7fc42582dbbda3f4f305cd724ba2c86cb7b58e9d02ac490aa8975585739930884f9f77cdfef34ad205b7700319be7dfeec1c8df8963d9f223e638d4203a43e384da2823a373c19957af0b81818982810916d883afaa0a9267b069bb5270ba7569a82a8106572170f761d58f94e6d55b1fbfd031f5f594d8a1fecdd238307e5aa20eb600cff117ba3b738a4d22f93774208da9b01889ba232c3c6c81150961f93604e7ddb861a83147363eb140c284d3b4222d10e2e1fcea1f409e09c1c616bd83ea45f0d2127200a51c224a66e7682ededb8a3061a25a26b43fe1aca731c2c800791e4f06356b2c36bf5d306ff688e3d822ed68d7d9c232b2d987b36558b78b11b3110463da8e94346b21ee537cfa0dc14415ac3cd0e50f3c9bf18e0debee9e8cd842feef54d49c48d562c4e71a79725fce048f7dfbb94d0ba02523b93f81007a2a39c015c146a5ced0554f7b8a1b416211d2cd6f3866611ca1416ffc76c6332afc59c791dd353cd18ace42e1de24ddb4954075d4b8db805ea3b388cd46330577cde0e09d83c0a2fd1a88d87d546b7a1006827218c248296685edb9f8de42f4a0f74dd165300450dbc121595d2c1a2444a57dd670804431b5a788cdfa10a2d9f4774f2fe38158702424929ab1cb7c75d3d5b49a45530763304509273edfd477de4e06be22ab5982802a5f90620e2c5a17455510455763cdfa9c78300637ada203b870884d07587ee962e91422d181d205325e3e53ac5e7856693cc1f9f684bc3ac443417441a48bc5524fdcee732570310e6497a7b63c2d78e8dc7d6a4af5170fda447904eb3485a177ff42e6c7dd0a43cffab87ad0e353f036b99edf50db584fa8cbd146cc138c167fc67ab13cd9d41779c7302c820271c495a261f0dd422fd8b9413820e35ade60b3c41466944f914dec8b18ba990a6fee27e78e6f6ec7a53e675f9a770fe2c102a122a0b1c2a8dfed2069856cf7abdf04224d92f0d3cf0b74679430cfef6775133a66c7330cf40fd4661e1aa26a5ecf851a1d1d70730064ff5cace10cd9041f209b091fd20cf8945ef856fdf3cb0aca59923576e29f6c34a2aa95ef1273e122d65e1439d3187017d7726d86ae4b7c66aed7cf62023a7077063481688bb4f50d88a042b0e3cfcbc5ea899518a22a808201c72cb02148f5d4f26b441a3abc9149f4b44979908adb2d436fb4899a4b15734d43287e128a8e2c53026c2be01c8bad76d2c3973c11b7f0eb33815c20e25da8bc0d1c8324d1790bf7a02034623135cebb510b9c3b97eddda22eb9042ae069d4aa7daa9dff7b94d039a12186214883007f0800dfb291f0286cd7f20f00da43505f2ce26074ff8e8fa7a3bef04fb43eeafb179d71059985fcc94a13a55c93c84235e65d16fd814acab5c5adc02857f1932eb4fe38a23372fb1fb8b83e870ed962319e9b942f1dd280d951279da8200354e43d5c01ec60b41dd44c5e7b25daf41938b82281c8a781c84ed088b1682e47e00cd47ce956dc513da6b66fdbf4beb0a82a864f11348a4ba738bbfebeb27d0658fe6091b232cd7468382aba0ef0aed8bc0583426bb61f71ab085fb0436c4d2f8b4c2fbce40420bea2caa35bb567b25d9df27c686628a6bbe10ccca155030343bfff54b57a9e2a5beb0da3de2fa52dd0198c2f97d361655a2c4ba95374ad4bec6528de70289441a7563ad2ffb57b1d9ff252f387c386ef2e66e9d05980fb0126fc4fec6cda2e7823d2e8671f179cb80185af5c15d234f607983bcd0d69fc355817b987be2e74340665647f8099d390bb164dd15144455384143b2cbee17787ef8257c2be5cfe3cf045d0e2e0a0c8dac3fee534aae83c0162ea51089a86c4763502cf0fb8309d0762a4fa39677344841cc6d70177a5d27f19b17343994a23ac4f786863ed044730d8c234e62459c0494185c2034c8df998c1d5d5ab45f67d338c68b00186fc5ce68c4624d076a700140803d1d0725e735adee5075c9ca1236a44ac42c9e48311f6971f7d2eb3710e25ba4508cabf24d5985e4db0aa5b3e3c86737c9c6983b7a670034f969114d9007e060ba93e86681ee696469b3e8a7dc6b3ff0cef3311bb8b1ae8ff5748574887d12216e1674469d4303542c985eb7bb179115ca302b1d1509087028bb07ce2ac4abb9e748bc6910659b4aa9f91d6aa81d0d03d69d3cc6b270b68e2905126f2501fa331b8fc251ac6de559f69f7657e84463e9344c0e5fa7b322e501177fff15ac498fdbb8be258974e091d887183ad355027c5c16be56883670b2681a14010841a821a420d42359081bd11352ddd98e285dd8071e8c57287ab2f6fb1bccf932478991816cb0a00ad328125ab62599514a9f08cf40fdd0f2996059eab94e8e0f7227038ed0714b53b096eb868326d5249f96a0b6f73058edf94459a958855f167a005b10b91263019b221a6d3d1eabaee8d14e3cb01ce91a8516e45bafad5bd25ac5d9f1d4663d52cecb888401563e5db8bff62bb92a548648b058d87dfdffb63a8f6d3143ca54feab415d8503d3cd81a4b458c0116380be96d415a116d0bff12be2e70c953f09b92c67e0e05d9c7873aec2e2d2b577ed1b648512bc2c044fbf29188b6983f6c02c3d7a1a2b09858c21229ad9ac75e38acf94272e13601d5dd24887bc9e74f6bff620907356cb93a8a807d77f523449f45c9bb25c9228410ae161b9aa6a40874f99b858883180acfbb3593b1f07797fc9024bf9d2b32deb2a19eb022e7dca3a263df122e1281b6f1153b0127f8f9a4e60409920956809812b18ed020cbcc2a0cfdcd0220af7334c3aefcf3f9dd091725c35833259b6408804022045604b764e954cfbae854f58d340d83ed3a3c71d4d596f8d6844e5a7ff2fdb8df59a35c1448f24c8a6c862b22117a018b6be75a4828825c3ae92063f44028eb87f90157d191888f0a8464f570de872b5ec602962f9df590305a209cd5c3bc8053e14044aa40974e3a48182d10ce4a71395f6419fef9f14db7bf0271ecbb70b053e180840cf2551a7734b77420ad2bae53544293a5f4bdaae430e8ed3edc650a6ca1046e96c4d50f5710300a8b73f599dc873ff045c017af2e7b6efcaaa57f1c76308d646543c6f446832ddccfd0b4f653f5bd185bdceca51247b32ddc26a5b040228d3e890e8b718a1b08c2301cdb5e39a891866d12da9374b56fff776f9902151091105b0f2fc654e0b358b129a5e8be04cc43a7d68725eb6130f72591dd50609050ec89b2533eaddecb572cca94d2f5604f949d7215db61e96a85a52b168b32a5045f755f8c4509bd044126c8e0f4b8120cd60303c310060361a0c356319e115cff7af2e115f381ef832be60b3f84d20a7895a6c067f55c117f2afcfb20d87ab1e7ca0aafbf2d91ca94d2ab074b084ae1bb7aae64b94ae163094149fc106625fcfb383d57703ecb2bbd9ed573652afc1bbb62257cd76379a5576c2a7c57ec0a965782bde790d0336525fc2626a0ab27cb55727dab27cb556afdabe70a9610945c9fe5f6c05e84c1622bbc9ec5e44ab0d8fd155c0f8ee1eaaf520f22c628fea65cc59e7cf081ef834f7c9109f837876704f1c26e0e13f11f04572dc07a1ce180566834650a0be087cfc265fdeba75cc9f24a2a165cdffad8142cafa48a4d61c115cbf24af755bf8ab1b0fa562ccb2b85cf8ab1b0baaf8ab5a013be3f0babab8a20d823cc589b408f3123f84ab7270b9845bc92458c61698546e02b813d58b08857b208112584f2297c2fef631163424409218a9329c3bf25c0544520a9ee4826cb9ad2c96021533ae994619a30a5ff305046e9a423be5882392a31c451894b8c51869f868ad25ff48096d2df4de9ffeb40e8b5f089f084b11e98e7f8df9ed57bcf915bde9de4fe2aa60a552baf6d919593447c7f5ab85ae524b91ffe2b278913159e55ec8813cd491479ee8bcf7357d7bd75e43ef82a1d604e60cfeac39e158b1582319e7025f638d15a4fe0871c88a2045fc50131b70465177c308788180b9dccaca90098df9a277e6bbafcd630b1668c0b301dbfa00b06fb0b82e1b760e0165d2ef15d31a85b8a25785945ae4c29c4c07d1a5814b29ec5e261c54090c502572dd63fa9be070fa39358b69eaee835d78baff25a149d968bf52bd6af7a56aa700a33e5ea55644a2f556abe941a94fe5393a514434b592bfd1786997dcbf0572ed6af58626b85f34eaa558f4bb57abd6b7557eee33e2d189458e62451fd4ab522725fe514feea73f45862e451f1a87a582b70b57a281d95f2fe2ae614fe9595abbf319e56ec884cf65abdc7ee5f15cfeaef4ab5daae53b6120083d16861cad5afde4be9a5971ad812aa60ae0f5d398ff3610e91d7fd2d966218860f0b4331e6741fe7c51c272ae5eab229719cc4e739222b5baf9e28b77402dff52ea8cbd38ac94297eaaf8be7be3fcffdd5abeecbcad6bb6237749e307644260b63342a38577506e65e8b72d994ee3e220f8cc623bec823c68ec84a19287331cc9439e2873d3c377644e6a02c7c1a58d4fa1b63b1407f72523df82d4da504bfc5c3ba31a7552cca2d552118a3852e807ffdef8be0b1552c642d512d01ffb65cdf7ad68b3cadd812ebc51781157b5a3d85545af796017b176bb573bef5ac9cd811992b941569b9e33c4f8eeb5f39ae18d487394bc4307664f51e73bacee37af5f0b8624764652b26a38d2016619029758af4c081e7c110e7614578705ccf032596fe51744a8ff9b7c49c24b0d77b0ceabe2a8c15b997c79f153b222b59b127550ca400185c4af1554ff7c59c283ae5abc0c8528a1f8620d80325defb03cc6f0c6b6268f3059a327cf18bd5b332d0a554fd13eb97c652fbbd600f8c052a4eaadfb71445d887b127f1c387c59e6060e93d4eb0f7255195b31445a7d4ee3e4eb0f725e1835017eab6c01c1160aee789724b7f28b1bc1f45a7bc394b56cff2d0613a3c4bac27f1f72d5731f1092c12e5beae0884ea59624ad6af6e09f33b00d5b7bef802bacc419e23b29256b6ca5088318aaccc21d25abffaf0c656394b54df7a5599233ca27a3006756960ec882acc89724bd6b3563d50b7bc4f1748c5e68b28b70cff1eb913b06143a4b1de490cc5d62b7c31dce2759a9525149995ad6fa9de29ec8180b18787fbc0fee62ccdcad6d3c02259093ecc092c55e5fd30e69453a4f5b23264a3e8945062c9bab077ba2a1cd16b5062098bf1a844914775693b76e8b0d1c300a3130febffc67858a20c9485516e49739f150cee72fd75fd6d022c6a45e17a98cb9564e9beeb5d2cd65fd6dfd60be79ac1b964c47b7fb57461d7d562b17ec5fad5185d8c916600e1b7ba94ad77b5de15feead7859b72f5ac1a39452aa851e377e8b864a84013c66573dd6491c10b18312098065c039661014e05392280e52a26822599b21573c2295d7f4508a7d5834fab673d6ccc3972af6b69f5acd5b3ca1558060971767600211969baa55257b674f9b28303eb220dacc5c200c706f84fac5fe51cb93f2fb4943949c26ffd92eb5b3847c25f7d98b3c48a89b01c21783d872a4708581a10fcd298f9a559f34ba3e697868c1961c09c6025f82e8a45304045e9fedbe24c89e5958478d950fae3fc3090a6dce148607eab84f19b00323ad7fd822018e33922935d1a40d68f0b2da5ffd2b8293f28fd97e64ad962537ee93f2ecc94b010070995b2d52302a3f8ab9067497c188b85b31257adbf394abc6cbd6aa5c4cb554fced24c15bbe38f8b305c7401dd6bae57f540c0a8eae1e9e151bdf83caa97f1a862476425cbdde7160159acd5833945c0bf3cab5fbdac144b7fb18747ec7180d1a9a5523d948e4ae9e5cd59ca59d27a5ac578623cabd81159a97a994b01731084854908a1e4ab282350f9c14c10400401c69b25c228853f82055068604d1048e8b0a50825f0bd07c882ddd245d80ad6f21acfbdfeae03e6333346ff6991a5746a39892db0c87f68b854f11d4e5fdef0bd84dd1c11c03284853de027318196226112136879fff0869ec4045a4059c8530454d5183f7cb084852128f6bcd778c2bf651873edb82fae564958acd58ac5628560112b96abbfd78be7b44a9558aa74882780b9584306cab8947e41588306ca543564a02c4909caf0aba4c3ef95f1974596d27f67daa854ef3b543f6067d694ad3625ebfe44f05de28835704059eb77664bd8ea71f073ca2140a3d5abde066ba7f5ae1daf77bdea83307902c56bf708f4e13efc5b3d40a00f7f560f0df4e1bfeaa11da9546a6e17aa1a2008de0bfabd514ef0f8b77e852942347125168fc7985ce95760ade9b9378cf79d5051127f05d5bba08abd108252389b02356003f6348e1cac11d194e2df078aa274d2d1f19a982f46e1cf025a545ebb594af16dc8ee0dc91c853dbe64e7f6e8f89d41538aa1cc858252144f508a7f59a5ffce9829433026bee72c017d872876997960f41f165ccadf11d25af855410210fcaad0c0af0a0c5cf1ab428a5f154d74f95501868b0cbf2aa67258ae1c17cb25b25c2c17cba56ab55a51ad0484b96a2070d9d0009261e3854d0aaefcace08015548831264d69c38a30ac15cb0a30a593f8b3a2879f155d7e66d8fcac98fafdbc48f333738453eb6706cccf8c0d6b7e6610f03393e557c60b1fda944e220d3f2bb4fcaed8920501b30b94f96191056646081381a8125566ba946007be48f3bbe24c9bdf1553fcaef8f2bb4289df15627e65dafcaec8410d18bf32bf5f993111f895e9f22b43c4af0c985f1544d0f02bb3e557060633a3aa7543b115fea0d852d628fdf7849bd2c167fd3d23270c12ea58dd30628a3042708121482084926843f98b02861ae28c016354e072a9c199f5f083024d6bd6438b56fa0f0a2f254ee93f28a8289d80aab0665fccc4003386cb172c576460f3c33295668c28d010d1c4cf052f3f17c2fc5ce832036396815918bf1f1b0f8d826810011a0320c1002336a0642403101073458929dcb85172b125c2c215e6e54ccce83f13e3376612988d998d998da902ce8e981d313b820bceec875116f2883111a0a8c298ab62a048f3528b343f2b6300ad91a9393a020292c9fe8f1e303a1d1dc110c6d1bf4c0604e46440100441d0c9b0594d01f31d3b47239093acd51ac79d9d1d9cd91063f8b0ab72105412df754590060446a7ff55cc557f9d8a1226f680b252ec01ff89387d09cacacb726a95f73eab870c19f06160cfab46b1b57a15cb85556c27b613ce44d58a46f8e1e83ef7c1d80c02e30591b0e2e5cd9a5f0b6dcca811f36b218a5f0b617e2df840e667e5023f2b617e56b2f85931f36b41cbaf852e3f2b65fc5ab8a22f07ae3bc8c46c88116cb5c67167e75f2603022ae22e0bbf86cb5ce8a1c46a04f759f8357c063e6c055e17c3d58ac6f8228cb1d17dc07be3450b4459298e7fa3470467e35fd033be8d9ef12dd0e1e0ac46cff8aad8e8d201698039200ec852fd910e8d1c180cc4390a5534549f83a37ab990c0a231e674745ff78f604358b14609becaa946a903b66260634347075ea5783b75d8d8a1d1d33a3acac181bd2e8d98939192c63b3d8d0f025e4753de231a347a58ff383baa07d3a3590ea3939381d31a81189dfe5757a829a77070b88c4e3b3a6c9461cc293d32121a498533b01573c5687814592e180538031f04775a6deebdf786611882e0952b6286044102af805bc02b6016700bb8e57e01ef97fb450cc19d12247a1a6874d1b0582c353a3d48b88cd7882846c1fa62049c816d4250e755aed66ab55aad563aa0d3455316b91f4ee580e300660b987199f930ab6156c3cc87190cb31a84c63b55b64a9088c8c938122b8c1df130de72f54e44a50bfcbb65cb2533e66e09535006ee4019f84e77cb96d0a8e7e5cbdd72a7c220e12a0819326440a22324d58fe19d72f5dc26c0590de3fd9996f18697f553c022a71ba6171a5d32341998914c6814444c68c422c3240c678e66a665bc66581a80c174583866a498ed30bb32bbd282c00ccb0c8b1901e030aedef56ac16030182c47dfd282c7c1713d8d071f961ee104394232027bb95eae97ebe57ab95eae97cbe5fad7cbe572b95c2ed7cbf572bd5c2fd7cb05f46ac1603098cbe57ab95eae97ebe57a01c18efe65322020d7cc8691c6ab1c08f692c0f89a496084bdcfc6c0b01811c0b881d90060b31b603a3f2b5bcafbe37bce124dc515bb200707c7020b2cb000e76bbc60393d51eef8f769549523ac67cc19c75104580eaca746cce9fa8f3d5efacfca5409f4abd2a6643ded48e5ea71723241ca97cb821a300b6ac06af43cc1aeeb2d78d8bf5eaf578cc7825811d78f638dcff9b14727e64423b6555dc6560ba78707a65f488cb323ae800318576472a640315ac0c4352108c66605189dd2a372b562ad422716caf07e18de17c57bc196fb07093f8b190d173ce286018fa8007c69018ba2c4999acd002b552af071c080f90e1cd8559a1560cce9b985665a69c6f0305a0f7bc58a2e4e18eefc0085464d60ccc7343f3a7e74fc0ed94a549a5239e0aa71ba5dc0dbc41d7389b860c0cb43d96a8de3cecebf1a914d48e4f6a0ed023551cac0a871ba311919a3937b1185cd6b46a2381ac1f8be82e1f6bc189d9c0d97a9566b1c7776fe65b2cb2506042493fdefec1c1df9fdd068251ac93c4d0982b2db73bb805ec019e8b3025c2eb211807e5f7cdabd66583fa8725ec943a31c1f72c015d892656074da510509552a194b47a962b14496ea592cd6ceca88c6abee1070a662f5b86ab5fa56ab858383f3b784b17e45e5b512737a4470269a4036c6d832828449d31328b77397f6b6d4943b75af64b95ac435220850c081510988c82f932628aadf8145abe7517deb86018fb8aafb3e0b1f1c411969ca10e38296b2d51a5d2dd7bbfc37709ff05f31572b2663333a6de022f37ab970564f05a75c856cc4d800651e185fe210b0a8c8736e1010fc5205bbe3dd81c59c6e475784f5b07e67276cc554aff2323ae18838add997f182231537cce8b453444489038ab96102bddb93a4fa208ba9d56a0326572ed051722114a1ca0b5318b84ac0b18163434a910b7a2dfd2ac114d27689703bc0c8728539aa2751005aaab4441aab0caece50950dece0ea61812bad3634d00061aa2035583fc423f98430562c50c95e455e1eaca0883d583fac48400711ac0011c38d11e84a8e8964004d4cc1dadc0d2de408e95c12748420b03028aaf262fd58bd5a3442a32511c20c5e454a30f5622de530b58cf01029f27219c1613a9124308543a415bd56350b72562f1891580a3d4cbd5a4634c0c130a50a47d55d8db07aa94058f5485282b52e1e2d23ab170b839c21ae1da212170e1b3a3cc871a347eb83234d3ca8f26a91aca19c21a325262c1c919543550b8b5c38228f0fc272b15ee28fea887814e4aa78c21ed50d2057cb0c7f100161a7f5bae132aa2244098ca8251377883b56404cf5a0210e517da0d2c0b543d4a113146660658afcb9612ef41143950ab11410a0c4aa3660083364c23400081e0640650529fd94cbe62d052801784282132626480060a4763424f3a0030e5e7cc28937648cb18110923a88e5c080e5e547850f3d8c4024845a0617e0c0564d8c3942882f3ef0b0001dbce0e044c6e387bcc18626a6376ecac005800c102c615b6b1c2006349704c28bf5c5005b8c5c5aa0f9e14bed47110e1bb0578ba58327024640c9e7122500582a42a4461434aba0068d1c51010510c00b15a8f19144038600824a09241815edd810ad30330212928014b9582b076039e2013bcc80001854c841010a88618992241078e0dc14842800db886c1e04e01a71036006546aee16e005400bb8b2c039035a019601c7800e081b003e894e77c7dda095814a470e06200ef0826ba38605aa0ac01a7714698030d50b74b55a208ba502c51bdebbf297e7183b98f21487b78ab23d2548c61d1cc06b9ab3f9d0f387d7aa400f24e9f14ef399b75e1191b9aa89bb6f1f77b05cb3a4cae6bd71ef87dd1a85135e33da4b265be15778d15e2afcd79ef62afcffbfbbd3dc7b380747d4803ee9d9bcdb2b10d123bddea80e5dd5ab7d8c7de1ee3a14e76208fa7dd57ee33ff39389aab897d555c9edd515ddbd2524d9d3c2d83365d693428f113fd5bcf1eb9e53cde397208cdcfa995d732cdefab34ec5ecf8643cc5e1ddf29e01feb533e3de77e2cca63809bbb22c91a8956557252a00e00e92a1c41d04034cc12270a88bcbc54d0346882037e7be1a300b2a7a24a40f923dd2ff07430401c40f409ff417b8b7e0630640ae59b2d528ab4322ea78608625d423bc88d2624848020560808103100106cf0f48e116678670e0d0458b9892282453005d979f1486bc8981163260899e30828460b8f0d911c23035e4480a527c28e3828eb00746f65ae0402863662374b281f18614e8d00507079c10841b461ec4e080018266570cdfcc06648c3d947085e6265cc1104401c8302248971c65801e40020a09121f7818328e019a49c10d53b4e0c18721486180467cd1c20a26286040608634e00d5f7829220d0d1b00d5ce8055b8dc08c00854e861f2820a3047c55771031650a418d00488248b9b11e01041acfc620c181481344b30357140892a88006f94e0091bd80570c9010a98eb860137304184811f5a3ae0e166a08b2239245183171b185d2e1409e841041414d0e00502325c3051f86072228a195000a2e51e00cb09580e70c20d4e3071e54611d114860e3e2e76a2a65c28227049a940c40a4f2eecabb4036f0135d840461400d2cb638b0b8233478f001d386fce0192a0b41e7836f0c45d044da090c10994a264604393a369018a560172049131e09e44982ef291962884f832c477f880b943d0992652c003c9b3e4b0848707386c881121725e16571cf1100407a6764c31b02054850a1d80d2733f224343152a48c1ba0002ee1a5409e187541b195c57dc4321a43ce004181cce9b356bce30607c534597206ede40c08a08e078f205069956c69b1ab48ca1c2b532c942c5186f58182346eb3c408d1c1e08e38d69c60b4588a0059f82469a374b40ecd85103aa0f80c59937641850c6267a7c00c28a8a37366a08e18b27cca840054c13679ce017a603273928c8883167783180169b364be85cf044c4194d4860480f01317cb9a10c98337e68f383ad4c96bd820c3c9c11430f5e2491831831c4c4d0e58cedc509a276f4662c2421c319019841890b5a0c7084074eb49c11821463dedc882600c474e58c0e5ec0d954e91400c030e58c57114508f10588375362b619648ce08b3608e8820221f02035238b0504917b9263dba204a7194760004cba63051c03ae4031c30b029e72a802890ea498a1c90c179ce062088f1c58fc8e4062c6d39528ce7871c513218030c40c260f38d2a5a0878f071400c90c1f6410bdb8e0e801460c223378bc6029f3a361091acaa099e117d82e54a0032b352876b8d1c096193ab01e19e1b244879b0a3031ca509424052418d970e3002b4815f85030a28a1270dccc3004154b060822c5c47b23ba51a1035d62511c8000860ab871837188022606217a90a28832dc14c1620428b2202736b430861b1a01cc6c20c4162a6e90c270534309120c4046f964019c342928c19b2807b851624c9a256752902688de1938a0b0e18929dcddcfa0220513d0408e3221901c44bc70a7b95f158e2eecd638528dfeca471c40461c3e461c3a230ed16f9f5b7ff6b2ff7ca25a455fa3bfd107c1c76216efccc7e86f1fee061858dc2833de98c0edf63a1535d154cf1e08d2cbfe54f303417a15d5651c0a24499eb065cafd6be38d146e8830de30f2afbdbeb67339fd7fddf64e87b2ff3b97d35b06d2030488bbf37077d12f68335e80c6fd35ba5bdcbf467fe7ee3f7cbc4086dcfaecaaf1ef361af56c3e5514f3329b374e5b33c5e67841cfdd57364b791feef38942a1e7affb778f97f7c6610a36d26896dcecaad314f3f4cf9959bcd33f9b5df50f121f6d6461f3c9ae9fe2bdeec7ef4bb7995976354db5cdbdba6a94c53dde6afe994f146a657b3e6cd6ad8dfd35908a9a5329cc034f0022020494818c080282f440a4cdcc67114400e554540956f2818f13ea43c42713c9452410a98413e9cde25e12ce038807ee931e05a013722d6ecc58230533996ff778d9a65132d7e27ad84cdd9cc563b9e82290eb2c2ff754d5b82cd7b9aa1b97c66d0989461eca206ac0025b2d9518deeb3840d241bf2388215c6781a0729dd5f57baf4a4504c240bff786a0917b5917bc375c49012f7859e08d26f7ae2ecbc19b4386e06a257bfd154150dc72c57bc1d72501785daeba2218e67b9dc07bc196139007bce04a04af07c07b57e01de1e2b837045b1450852c1af7c6bd17b4c28bc305ef0abc170c6d17145f2e2210e726b5ae6a4708e4b240500c0a5f2078673a455705310856246402bae82003ae18ca441578435725e07e711df4711cfdfa152f7841705b608f7b41150886f70edd9fdbba382008822b219707088ae0050a4918062d0f6e4e0539401aae0ac4e08a37bc0e3201ff754507d195ffed1220f41e0a5c0ff20418ded5155937e7865c40b0550bef517841507c10c725bbaa7b594062d00640177cad5ae0bdf7b2eead5d10c440b8ba2e500ca16e0c6cef0d7dae0b06de16eb8241400d400dc00c420ffdeadc1508de1c710c4510031004efea8ab9ebda71612af0aa2e0da37befbd3ce0120be6022fae85c302ef08b7756f085ef11ab919803f401d200c6481aa1b5ef082309025de5015de6b74c32bbbae8b73ef05e16a00be42d66d8917bcb776efea0668e38ae05589f70708bb21a8c10d43101441f1fe00c19ba32582f7dedacd007cb1c22bb6c47bafd1df9d1e2da4f003d50dafeb8a3bd7e5aac14ee0d1e072205e105c81ac7cc70b806046ee052f78eff57196f73c00542e1b1340e06f820e6bcc98d013c61825e02f8670920135ded4b38533e9604566c265bc890e637068d2b32e19410727364c305110243a683002235807d00f1d767e7e601a41186520678751cfeb1665e09a51747ba380a23fc3c95b0a34f0b97d01696a7a58690fd4e3032f343236b83dce068fa302f762010a7e5319c05f6cc13d7428327767e2ee2bf848036f7cdaf2b99aa8dae6d0b8476e6ea104f70bb5c3fd46c181dd9794ec3cb16f26cbbc55db344ace64b856dbcefc64e69d496e15dfeb28988014a6a625119686d8445862b23d29933c90a49fc9d49c92b9161795f6a0dc1de8935eb72aeaadbb5ff111d604f449bf55cc234fcd92eaaac3ecf9e3d3e58dd9ad7fcc7c0aed34f7c36b551593096c69c8a8a20cf50369a6d240610227636422c827200307154e3419f34d130d24252c0290c105453b84782217c683074451b348001a9d3200507b4f5d3079a2820b7e5e40a3450eac1258bc70021772ca55c2810d3c087b18a282c1985f982d9a10d3b4b02843823138d861fce85c706446e8450d2e320149776749c881104e50c6163ed0f001f5e803e7124e1b5c1741e0c32b4f70010c195538c022827502ee513e6470980798f01be4e3aec195290f7382b82b79779619778f0204c186295c45820cb8739961ca759482f8c5e2c59dc7e81eae71f733890b4c0f9b3ae7f269e2f08f23a3221f4443424140ae2977ffc047971357107757d1adf3f93b7363deef557d9dd7f367b2df6d9d9ad3cf269aeacf26dbaeda358307ee2e02b46470f7dcb23beb14e7dc3d76ae3c2070fc11f449cfb2add9e36b1afff871f4c3e847d10f1f3f887e0cfd10fa11f403e8c78fa3a323a3a3a2231f47444743474247414740473f8c8e8c8c8c8a8c7c1811190d19091905190119fd283a2a322a2a2af25144543454245414540454f4c3c7910f231f453e7cf820f231e443c847900f201f3f888e888c888a887c1011110d11091105110111fd183a1a321a2a1af23144343434243414340434f443e848c848a848c8871091d090909050901090d08fa0a320a3a0a2201f41444143414241414140413f808e808c808a807c0011010d01090105010101a1eedef37195c65d0a48b324af557f362f97d9d5bce36a0a292840505649b843d95058282a94580ce7ee2ef7273eae961c87a6ec8ac3eebe81bb6be0ee19b8bb0e77e771f725775106114b0e0c70b85fe06e81bb57e0ee7a33aa4c70f71d1f55fb6bfa54db15f7b9f577bbfeba6dee3ebabb8ee73c10a4d72c29441a91413e3a6f9fbbbffb05e330f74bc6dd03e0a328e5ae59329fab99cf20d20709e473e443e37e724140403e82300e2724741a05e58a7c5694d47963d6247decd6a82eb33fab8ec5a690fb5ddd5df5316ce3aa4fffb8df0fdc3df5315481cd6d8aa1fec6b2446eafe257d7bfcd643dcc46a96dadb6d356e726b73f739a62d3dd5f34dc412aeecef31164c2354b6e5e6eafea9fe8f93c9cd9ad5fa7a2be94cda7da7edeed57f32eead99565f3abf8cfacb2edceec56332fa73d1efa1bc5ec7e9d8aee8d79ec9a7becee2e77bf2d7767ad76bbfa301276655922eeae72770cdc3d8a8f378c7b86b24561b6868f5706778f5ad31c9ba1d6f76dd4c43d6ceee09707775f7dbca26b96fcf1b198c5a1e98fc9ea1f1c9ae2d0f4a755753e52ad8aa63e6ad4aa3ea9ad0f5191ade848ca07880c3a22837eb48ae29d99aeebfee9b55bb7ec0f9bcf146d713f99b7ee7edc3dc74717c38cfb995314e75ee7edc3fde7566cae698adfc7aebcccbbc047f7e21b4d7bfa6f1bdf6ef8e83bee5e818fbedd530cb5f232ebeed74727dd1d878f2ec4dd6df8e83fee40a4f2a9a2afaee866b356d1d96b54f66a4e1fe933eecfb45d77fa67a1bbe8ee38ee7e733cd005e664a6a64a0f734c2006b63322bd9847a247124faa9b23828754c27b04e65142150bfe1e4b30c682e083e2bd4f60ece9f55eaea284a54b1445f1f5ac96c87a89a18bc57ab5624fa1288a62188aa0d3d265a1c2576dd64f29bdc7694aa98a45991265a7bc31f0f5147b623da80aec9abbb1394db190753fbbfe671ed6e4a9e2dfa889a63da9fcdf14e5ba5054b83b797b5a6ed97dfbda4ddfde8764d7bcf1d69f62dc9aeab256db67d7df99096a098a83b1a5a2d5a14582eab597cfcfb8337fdeeda21effa82bba9b7c517817f5be75e7dd26339ba34c2c95b199d5d5a796eacab6279ba1d69dd32d3633eecfdceb61766b1e8a5321e38e611b8d9a0d7e353fe19da634b56d75ce39b2470ff2c4796b15fddb996f9fb593af65b697cfd7688f1eee3e828f5a47b7dc9dc947b608f6069608bb81bb3bf131ca14518a8882c218c5c6d89ba2c784bb93e0632fedd918573253c8acf1ce64db5e12214f683e157f0e7ddafbf8903ae7dcf3f2463ffbe49c7bdf9975fb1add353da966dcaffb7d48a1a173c92c6faf6236ab59f7b9f5bffcdaedf63ee4b3ebd34e35df509891a4ec6927dc3ee37abefd698a96f0e490afddb6cea74e45ff54711466cfdcdac3e69346d9df38e3de87795b4d5334eaf7d3ceac5176e57d6ee9c9dcfab79b0ff77edd6afe757faf7ddaedf6dbcc507fd3b77c62de6bf4f389eaf53fe722e4ba792b9b7bfd99b7ea6f1ad7d6a85e31aa37b8aa45ad91fab7ce1ba73c53eaf63ee49971286bea7cbe4e4533ee355abbddde875451de59a239d4cc3dfdafe2245fbbddde874c22e4c9e7d6dfe9da95dded8d4d546d79bcd56471da9ae86671ef719aae5b8a489296c2499e363b120a6a9fa6e67349c8994fb55d95349b79ad565793cd4fe69a1e0905b55b6d571e66b31ab5b2f94cd71597a1d06d629b46936647edd38e5a1992badb35f3d0dde4cc5a5d7b3ab34948cecc6694e5d932ce87791b27a149d1a463d22cae24dcfdfe2012a2e2d4911ea5a24b02a79af5111199271e9e76ad1f9eba18050911f1e44e54b46870f7cbc3ac6e657bb61f3f8284be38a54e439c46960fa7ec746ea9bcd5d5cc3c53cab6dace13fb20ead2250377bf41425b5024d46db8fb4589b289b23da1a02b3b2fbb54c708e8486aa7430d4b35ee6c18fd487553eede818f3a233a1bab32dcfdeaa0743c9e29057474cb751973507232247234e40690bbe23ae75ceef936cf941a12326aca62c69c2537ad9a72109b5d7b39cddbb69e4245e78edcf2f114dd022823a79ab3ca99c483d3cc39c5b9c489c409c67deb7cdad6d3c84790e9c6dda7a4dc9d85154c13ce74c9a9967a671d8b9bb69a59b615e2d36c4673e85e9564a69c757833e9d0242420e1766e70142a2b3814704770437040ce33a56c79afbccc9ea84de79ccb3ddfb6a979b76da78a6db9e7db361b501011d01adca4fae1eed76868c7cd8dbb67e0e3cdccad8b2ae6ee77bc7dc0c643716a12050dedd06c68461b0fa219ee7ec75b06e34de7e6b29d616be3ee365edea82de79c8fc5a889e6b29a71abde596b6c6373ce4828086788ed8314dab8bb786d41b6cee7a9666d5434a6a0c6ddddfd8e2940a570fa980212b700ca534072f71d1f53b0c0dd877c4441cd91bb4b911a53104ae1039b14e6e5dd9a592adb78a69450d0508d674af9fcf04ca99fdf6a86ca0348c1dd9d7c847202144cbdb2594a4e4f9cd35ff7eb8de21ecde5da1cbad39a25b719441691433e3ebe8db2b82744c256336aaa6510095bcd3c1feed9b2c639cd4da02c2d9dd0663c010827b7ce3a5dab7fef7428ef73cfb771de6d5ade6d35f35a1d8b3fef763ef3ee35defd4765254c5159c97882ceadcf6bd5fcb7dbe75d93d6599f39120a6a0380c4f39025161db2d5ccdbba753a4badc36911cc6e2922695a9a1a4d9273119d7bbe5dab699c56578d73137777611f4db882f6b02d6f0c85b23d9b4d882422817c746b6a025066db8d73ee6f504fb78cb23ebefcb4dd6fad86e412f16487ebff1c9a7b52f935fa79d7f420dc6cec6abb9d79a75115cdbd926649cd5e76eba799e8c65bf3408a5a85641e56b7897bbc5548ada6aea8663394d050ab7d9849ce399bf6e15e129b9968ad716adad8dc93c23c560af39290a4302fdbb2c6b824a1a116490af356b58947429535a72ee513b3ba9e93265b6324e846d52529688a55b488896a1565b714115fdaeb9559e31d9335a72e6d354311613393a6a85588130b90678662b3ce4d72668b981ae5d56a5acd4feb6e37de4cd954718a95320fdd6acbee34dea9ede8a4c749ced76eb7dff8ffcc52403b8d774d52f866d79dfe75ddb5acd90c95d95797344bee34de35d9280f45f264e2cd74a61935218acd504bceac3ad9bd96655721454eac54ab69766559224c7cecbaf1a9e2242966736f498a79ecaa446b9c0af161264c5290ab129d4d60024a71ad56b369964cf1cf8fc9f67aadfe69f2e42449f2d839a56d9c59ddef55c993fa53ac53d1a835cdd5d20c95535b0dc7688433ee0e05c20841f2d659b5b179a33633f7f48f99a146f0a004265d98b490b041f205c98e4ae4cc290e6b7488baa25acd5b2adb761aef6c78e313efccb3651e6673f95c931c39dd1d0a0084596a0380294ba71b1922e1c84e6323494948662621b7ae2c13a65a2da3ecb6f534def8d469de908d6d5bcd5a5d3a71de19b129b1090db5506bcf268579452e707715c53bd486371eb2539794183982aad5f0c643cebc35aad9cce45cdaeb899fd2b5b7e6d4a5345d85e85434c5a14c99cd4632bb9a3615d5a1ec92bcb12eb33a5d99792b535657254d27ce464e9c774a9ecc35557a62318fcd1b47090db56ad47adaceac753657b69794cf55080948ea8adaa2b2dae3b14652bcd5550acaaebba4ec24f36c278ba37a6dd2518b6466126a353343d56a78e3f35c12d2739c1605b5b6ad593497ae2b6e6b542adb748a551daab79ad11e6a6636f35a9dd9deca9e4243ed99ae3b9dd9ddb669a528ee3a45f1ce437a3c536ab74bce7caab9a75ba6a72578e3213a34a7ae4272d2ecccba136571a50cc9cc24689bd699d5a9a82db7b98d623b771a1ba9d54e9c7736330acf1685776629a866939e762e61163341d2a9e8aadbed6aebf1d89dadc7b3f578ad2dc57bdd4243ad4da77960821871f7263e0691d2a399bc9a73d4ceb8cf1a67d654d1f7a529365175ddabbaf2fe012164e14564501029e4b3e654d427a7d8d4675675a8d678b73193bb010a4288b99370a48793e4cc36296de16b92ba1c7177004c71f72511dcfd3a11bf0f30a2002c5819800d4f00b941b3e4a9e62713e36c196537cf67555f1881bc66fdd61891bce0ce8cb2bb0c1242d20f11842f4694d4d880b03fe94f94f764a26d941f14c833ef8df278ab799f904f26c6adec67dcc6bbfd58ab98ddafe6defaab12bf21648dbe4983878f359cbdaeec671d66d7ad5fe30f336cc6f15ab5f7ae1f5ddc9d877ae28c99b8bb8fbb0bfd08a259f2e7c95c539c2d6f7ca229862277d6e846a5f213e9c1a9b8438d4762ced5b4e5735575864237ca43d9de0faed5bb7da251a78a95ceac9de48dcf9e13213d1e6b24e9ccbd1eca6e5de3691c85e46c6ea79a7b3d7c435316e73ff3ab39e35ef3b0c1dd4b1f8d9a7057334f97b5edcc679b9a2aaab63e3ffae767ab5967b6f719976655c53bf37b3c76f76766716ae673a33c34f51d401c451e70a6bcf166d2a9a80e6b9d93ec75a77b58d5b6bcf1d6361f4e335babf1f2d63e6dc32ceed9764e53dcb3e9366d79ad2944a7a22a8a79b9496e733bc2cedcb8a7d1213e9ce6261f60800569b20861070e8821c5172d904eb86045a555c4ab4611650cf0484801d51118a1c30530c474a0e302921a7ab6e4082500072288f011c1ce0c3b5564f1c31016ae681a00902e560e9a197db471ff1a057cf441739f8a5fc5bbbdd5cce65c2e43e1ddaffbf599b3fa3b13dd453dedd7ada2275ed3336fb365b75657f37da9babe4649fcb975a7dbf7e1fecc9f71bf57f5556cba7b908b3a1ce723110f39c5e150d577e6280fc2def8f1c6e76b1f8e4af19f59a3bbfda78a7fa7f1eef12db72926a1563bb31ab5249f3a15dd99d8fccce2cf2538a981a7e0e3100fbdf6695dd31f4224900feef4c928bb651cf4b87bcec7219cfc644bb1b9cedc3d881edcfde6a390162232880cf249d59545a1d40ce40e9a10d446b364de588742615585a3b2aa1a6285178d062409c8cfa44e54b6731949ad09bb64e73292ffff970a2f75a2b6ac3bb3edc9c4ec7e243a563c2b1c2e8024e572aa4a0304e9b7daaee67ff06a4e6db6335d433622191cb88b0d60fd6c7d3e102026ab71aa0f80f8488671f77caeea7f2d9fabf939c5e19d8933abb67f66366f357f2dc52a9bb78a42a00141ef934bb37e20407eef7468de915907d26042d39290924929e4915eea445df0d1878b8fdd6afe29e2cecac1dda9f8e8d372cd923dccbbd9dda77ca4b5aefee4cc6edc8fce69ba9a49afe674057e9086d7aae30734f89f6abbe6dee76f28140d3d4de8841bfa194d7feb96f7b5ada23629f4c4ac8ddddac65bd95c8e4ec8132a312512604a44b2eaf19745822a630f532a150ecb832daa883a048938707808623580291189ebc64bc74e0b532e13c400841ac0f60aa755a475b47a8520ecbc5415986a8999b201a58acb28548008605e4ca9c62a392c3085b3634a4cb97668802c10543f2a19b25c00c380062ba296299d1e9d9ed0a7c64f6ba7c61b2baa0f5a472ca45027446af130152609fa59bd5660a642d82a874883058557c41169af22aa0f5826ac72ac5e2b271c1b38365c3bae1b2c134233acac728461aca8a298eac049e4bdad830cc41a8eceaa8678439f5088c80407c70a6495c0e26129515d40e3ae60a14ac46185add0f5d221862c27ac0a7498bc6ab056ac1bb24422f147bc2d0c429e2aae0d54414cd10053a5c64ae4690db1227a6125667b7f73c606b44063851929a268754c06d8424429042046ab4a06087e788c3a2dd6090158a2e44d1567aed82006202e3404711bbcb8401928968840037cf8213d98a1dd31689604216093268b334e1441858514a004e0c912252214d940031c37c62041a50143b0b0822d031dabba010d2fba88c29b7a7386094a00e5041c376a5c008a2726b044048c70400c0510802d05134a58a264a94c154ffc92e06204035b9481c2892594e88107185c6821046c2e90c5124a8c79c095a97c2e11a991c3468c30bc90c209248e20e2070310209fb614a09c50c21211106ab31a3965aa9002871b0c70259f290060a90811105c8c30ca5431c513481c4184037e384001aea00080252020d48c88826615d410fd8d1861743185144f3881c4110ef8e10006280001ae7c6143014a083fa49b142039a243830b1d5bb4c0f0a404a01d0800834f4a08d79bf00cd00c550a4014b4821081007f007db8426090087449984ff841f8373600755c1e568e1083bb23e2b8375c16545041388a3a3834c69c1c3818c074bc6ab858ad150b5cad54aaf0faeacaab830a4cb15c2c9748a355c4728519e0345551126bc956afd69188a373459c954d8c0a73c21a229a166c985ad100592e919693c1d110532f12704e9146a30a48c4a37222de1512135340900ca9d10228be442433134c69c04a0f3355aeaa5663832055403147e8132bbbd555c8edb5c6bddfedfaa3a24c74e669bcdb408fd44c923431f26ab981d77aa32b9306d6f8fab949035138a94371bfae5b035c5c034feee4910676c458801830b8fb995514b33a9faca2b9cfec9ae4c9dc46c1bdf57d7b6773cdbb263188b89359877b6178200c2e611c01e304605cc0dd49bd33aefc93c5e608497af8d29caeba872f2d018c30a40e8c2977276f453e846c42f909a738309e80c1d3f23e6a357266dddad8b83bf90519ffc28b7f1144f63776bd9df964b1a9d1f333b046c5662ee35ee757cb0c8cc9c00919789f8d37baaa7faab849cd1ab2cd6994083559d408a969b93ba953511fbb33ab33549a352f5d69e75ed59dd932a9ad1762dc492836ebcfad8aaedb97a2db8b1ea9a258e745095ea8dcc9335dd1cc32c93adccb5250a65aad29eb70af8bed9ee2add77f1d173f2ec67091e362479a2cd26449a3d37c1ad09d9cfd07b2255fd3abb933d5ac9471eded7d4814fdacc33d1dee41a12c8bbf093dd5d57c8dcfcce69d0ef756b6c7ae2cfb3e16b36b8f979bc8754b65152ab3bd2c05d54949484ad876c5ed7468566dbe136736eb309353cd1aa7590aca94a5a03a294b41999e96a5a04c3d9c8d20090d3959b7baf4b4a3b687f34e495213d2ce4c5b48608b2cee284079daba851127db2d80b6185180f227fa274021930879b226a9d59aa256558b222d3c4063051a17d09c68c60b8cb9000c178872811e0b8c61011a2c60c5c9dbcedac6e6f454f1eeb34d5d6fff4f78d76a273ae4f6b955d6f282de679671b79d999ad42537d9eb34c5a64fc599d52dde49e537752ade9999c551f8b3d2937a67dd6fd4667b8dfedbb2ba2ab1a1703b5794cd4c782d0f55d124b5daed041fee33aec7c35b678c77b4dc2f3dc9e69e2f3df3d30aa5a29987ee9cd91c6f6573ebd6ad0576c8d9998519775ff7cbfe6cc72cc2b8bb67f1c471f9cc023d336b3eed767b1feea39ace98d92aca3b33e5e44c5dd13346cec8b040b37546652f863c7314de6d2cb260b1c3c2e88a35ee4e6abda6bfdbf5559d8ad288ec1617a5d75dd4156472f8780597752daf6042ced81585c2bb8cd328ef8a1d77128d3b39d3567871bf2da578af4d372ba658316486545733ebdf9a45358e623223c695cc48798a35caee8d9a015acbac71276f4967ba1a6942ca2d39d3755786ccca662965b6b8bb47fd94b9b94f667b4a63999d7c9e2baf8a354ea2bb5fd74d421561ceccc36a1553eee4ad88144e72fb33eb2c256736a7a48a5207f5e589779acf5aad296736678bcaea6fca4fe9d7838a33e4b9ee76e67190f2f68e24bfb6551cf5edeafbcc43f78f1f4f18851330131542692a20800a0c5500360506a6a8c01452a6e0408a0a48d14a41945b76cbbe97f3d3e7ad6216b7ba990c9b4fd378f7698a33cee9bafa902469eefc344dd36cf6a9d887c3bdccf6b2daae3e1afaf4786d7a92cd38da4cf6facc1befb6b6e51487ffb71a05191ff81845134edef6bab2b7d7686ed565360a252840000518284c2876b893b63367b33d33fb37956d71375a364daca24f98977936956d71b6c76a9b539d642834c5b65ece4fb73f55dc6b55b6557a82cd134d38d9b46449936ce2b09327bc3c61c4ddc92d959ff61a6329224e88c0891d9c48e2c4cc099c26d89033956d77e6e752346a4d5315ddab9a7e2e41f66b074041bf5bdcd638a7bf73ce65281447adbb333b69228926586882893b91e780bce19d42a18f7d389c0f47bd4eddadd6e848013314e08102370a58c04417ebed966274ab794d79b0b79cd922b7ddc6f9b4d96e9f731b054d9bb04fe68a246736a766292b9bd33a9ce69878a28409a0264c8ce4f7d09d9e809a214b7f2a99c01677526f8c9aabd69ac7467fd54fcb50689af5fbf291dcbab2bd0908914943e60b194c46e6a4e6ad66c66d5c9237edcb4f4c4be8b0449325882cf14a50a144abc427c12609259c3cb3149a7b8dde66327da2365f7eaad5b42f1fa9d59a6effb9559fe5933af77cb915fddb2c48b6e4f63ebe8d4a09790e3820b796cd58ace2cc93e9cc9e277ecd63c8d2a3e86a665cde52594dd1adb6bfcd9ccb652df3e5279f8a9ba4f85c3a517665d92480dcbd4c6274afddc6b0e9e1a8724c14eeeeea92315fdcc73152eeeeaf96639898af71d498775cab91a0828791f882c4938cc4d1aae408364ef256d3475dd5d778974fd4445ba623b664565d5fa33ee84e5bde97471ce9d8b6b7b2598a8fda2481366a56f251025d2480dd49126e9fd955489a621325426e13b35baac8139be4cb477cf9299fabe9cb47c474408c19d2c766a8fc3e9c10520c978df2f2138b79997d1f55cc7677520c002250823b46208c7b8f997b3ed8545bf6f7aa6af2a6aee7b99a5139024b220033a2082354306256c41927cf1cb5a6294ed392cced9977a6fe75b36b54f6a5bd1e7e5f7abe0ff35af36ff95c85eccca42eb9bde6d1cb43c853e39ddaaa99f664e64c9e331f8b551427fbacc33c34b33d2645e08ab0418419772733665bd38c7aedcb4fb856ebf6d7fdece712be478f1a91a6254a7af420a5d05c667b384d3111b97567a691082879f79ac7fb521c2f6b5de272093aeb70ef732b1304b880c00b1000b2ddb26e9fc75bcdccf2d437734fa73d1e96cabe9d7358ef763571dac33b8d6b22285952d2a42ce1012078c09a076c71f7bdae4d5eb379e3f4cc687abbb5bb2d1f907979a3698a1e69cae76a12c91aef7a494dbd756bcde28d87e08dcf6caaab9acb4d304b249beaaaa4566bb23d991897a638a773cee9ad6294c524f05a255b458d3c6daba891ada2bba4ade253cda6ce678fa8a8cd50a8ce268579b68d3a01204b41b58aa3767e32d19da1d8aca23615e5e5731582f3e1246b4e456d6b4e5d4a9a3d4d6868eb9567dbd876aae89153459f6c289bb7ba9479584d310f2592535cbb0ed13c536aeb245b4edb7395c2bc25254fec1ab5b35a24a33b6d93b0ab13e6d9908688a09078e6935acd07d4998718d314b5b3ceeafa395476a66badd674a6eb10337752a7ad668558530a41c69d3c5b21a69ed675ffd6add3cf6a3526321434b6ad67ab4f10e20979f65ee328217e9cfcbcdb99496a554a536c466535a90929b704c9ba8b028304989d93d8ddc18810041b277df9292aabb35b54566fb23f97ca1d44124174e9d183ccea7afaa82d09b5f2b69a55cc633393208204610308298098e2a48dc996e2acdb5fcf96292a4db179dbebdae4967df9e9d7b3d55bcdbbdd9643b6ce3af4dc6d6fa7b1919dc64946d87ce2bc3b51101a420148e841386a4ff8e1e6698b7b453e848cec34de2da9d5326f3577bb6abd9a3cf309bbb26c4eda696c84673e39515b8adb6d627d2e9da8eda84d536c7e1971940f66a2b2faac0f41dc7df401d683113ddc9cdcedfa67e6a17bb3ebd3d25e7b3edee8d3d80c95559d8a62dcc333600a06443180070f6778987212b7b2bdd7e86395cd50ab2ec99379b775bbb259a7e273b6ee675794ed9d38b3f855f4656a4bfa52ddae2854667b3c2459409bdd46f10278e5026e58c0102757d5969ff0be3de17de2231f7708c10e5f665965579cec37baaae50e4e3bd8d0818c93b7d9d3cea5a7c96e996d3714deb52708b734c5426e8f4fb51d75b8e26ee2b4a7c30d32beb1979c97d1dd49bd9aa78a51754555b42437fa67665775dd45b51aef7208410e5f9cd4bab7ee1c98cc6e3cf3c9a966a65bdeb955a6ea9c8be4404301e85633149b77ab62de9be8c6bc75b758c5388dee9a145000051cfdbab2e76b1c85ffc4bb1287368e9be0e0c549ddb651329b4bc0e109071c5dc8b893eada6bf1ba694f637979b7755e4f225dbab8abb9ec82eb8243fad2b4cd69adf1ee6f107303ee86316da3ecac3b5bdcb359a36786d268943343a1d9cc2c9bdb116c40a362261b78d8299a4b519cdbf1d1862936d0c819db93ca28a923b3ac863535707167732ed7e24cdcd39f5bffc9c4e9e3d73dbc63331352457f466612957d6eb5465fe3a8a61a7a64666b30e24eb26bae067767d9b5e4f2c53d671ebb6e8c6ef444718fe5f2843cf3ce6c2fe36e5c5aedee37fad954514d439875ef4c15fb649606297727358fa6918620a4c6bb19d4cc908554d79d46a3de374b532c7b356afdbc6b9a41a6cf19dcc90488d9282d013776cda94d097832d7f4d314ddfa338bcf9c4db3d5ff64ae69930c1b90618bbbeb5474b72bfbb9f57d38ea35ba691a479d413ecac0c49df4a998f7ebda848036eeeeab8f901101649ccc2602a4b0194a45556cde6e4949698a85d46a49aff1372121f1cc274949e71292d4aab4753e9372cfd78424b52a952034d426e9a4a4990c89673e41925a9578e6932d63b640d98213430c41c4f024861d4ee613f356f509efe7b53c540a6b9dd3d7a8ba66f455f4765b9590be34c5e866718f5ddf97e69dd31e6f356f3acdbbe55dd4ebd453455f2d49dad352fc990024c0810044b4b0d182831625a316a0ffdba96218b280010b0c5030ec7032b3a8cea73e7d6912214fdea76236ebd435fd55cd2cda46c94d04f0460040b8fb111f056013c050162fdcdd878f5980c8c2f4829b17d6bc7080176a2fcc2c80a50577a7f988a50417deb810850b52a30b1f5c31c1152fee4eca64414f6632a1279a5d73ffff6bee9fd46b8f87be5ed5a7e51487d1a8599b9a5259f63fbb69fcebceec6753cd3bd9939a5d69298b5ee99186d4b856e7a8756f34eb54bc334f15b3288b75997dddbe6fddf89f74d982d942911535ee4eaea69ad316e7f2797b1f72d6cba689f156b399d9df7ae5a1e76a6ebc93e5b07efc3b3355694d3d60ea36b52305151aa804c0dd97f84805c6c2122c3cb180646421675c218915f20a49533e30250153427077233eaa60021576a8f9a8c24e051c52442005072944c8f05521e58b8d3e96141152b751aa83a832514544dddc49adb38af2d8bcf3f9ab4ef1d67ffadc74ce3975c9d77cc8db2d899027363df3a53854ebb5f7a78a65e46ae675ffe3d77a654dfcb319493e6d469232dd6a356b75152282ec69425fd33292cdbbcdb6b95733543e9fd0f44d74af527967deaa4dda6d86c20ab4d94c76e6adb6ab90342d65678a96f03515869cd063662819cf7c72fbaf69fcfa356e75fb642f6ffd2956d1dce3d7585d515445f1565793cd3bf3308b7b3675e5ad5bb76656d7f335ea33d5955df7e75d1379bbfdd699cdba7d1fe691b9756b347d3543a1ba33b3adfefc849390b713bda17f66d6d7e2d0bf11f1b54ab7a7e9deba7b1f8bd133f334fe34cd51aff1d396fc3cae35f23b6b5dbe46f35ebfe9555ac9543241adbc5ff78d369335e1e5ddfeff53c59a8746ffd79daa6c8b23ffcfac31ce67ab28efd7fd680edd278ba3d8f573ebab3a34b319aa764bb1b922a9d572cfd7747b9fa131a8e46ecde6991c059d52d00c88080000000033130020301c108d864462b15092a350ea011400067dc258804a97c8b23486611432c4186308010000004004806684a4360028038649595e28086f9636d6408d0352a1225fb74c7a9b42d8a31aa5bc93ed6fc9f0de087c0e1f479919e674c414917ad02b49a247e2b3652b40049029ec0ca8fd78a7b979ec66094b769c310b98e650d1b2e0bdf6787a2c75796f4778d0367b29059a0486bd62a14fd6bf514aad28293adeb4e390c68580df6b0482145a1df929d8ef5ce13bf5668cffb14472db870bc6d1605c913e3c5d2bddf881f386bb83f86e830f6b11adf58c3fd7202c9805c02b7e1ae844097e1ea708badb07c30391bf3274a6e93f19f6e9142ba52f67fbe1d69e6b61b7df3b22c4fbba97a5d4cbeecb21cae70c3f61394dbcb649d8a0be479f9f4c390eba5c0748248cf620fcb0c6c0d87588cf8e504a440f4b957fac4f7e4f15e7a51073e04d8cb1bcb018fc1433e3a1258849bc3c1b30085228876448540fa3de111d888bbfa27436f02c566175acef699b5fe31a4120b2bc39868167d7e198eff165b483926649a591a3a670cfc29826308fbb7a3d5327561419f21ce219f5d830659af8ac8feceb9fed3050fb1223c816f3e2549b625e617464aaea9203bc71b091eb9163350d09af73f137652e1047a852ae109a04e6554543d9d2ad4cb2e94d54de2487e00643ddf6f4faca43973c17e8ec12257e964e80731924af0be99dd6737c884d01e29b9e1f17efbb3f0c072a1c85cef3231c4d60ec63115f99a79508a8b1cb24f45684315e2f2d8fbe740fe2ee59f79efa328cf6720171de9bf2c9263d1cc25674cc7df39ed179bc229972bee361748583070c74aeb0393f7716b10581c879c6d00e6abe1457739d9da5473788c49e6a7ab7e5c9e674c56dc926558b24fb85c0c844f4f7a9536823a4438345721e29d02de74d211d260a68d724fd8001a7b4f78f364f6423af9f6b3bc51406d389862d95dd7bf16722415828ffc6dd5f34484a5015482a0a9eb5cb1f25ce06efb095aece87460296d0749b17457eac0052f0b51d3a7b6337106ef77f2f73c1bd97a2b7bad8b89f70b05aeabf6be1db01ffc020d8e5fc596e28f719b889b6849e189d7028c235d68a5bc9500ab5ed606e23560d86646a1bab86ecfe21f8ff5bd375b5d46e92f37188e85dac5969ea7500b447aa292c6e01935f3b329355c99055d66713db2c7a87b24e8da309743b161f330e149de0430d0223823b15bfdd796bf8ea664eea7fb389753b963cb7ccdeed93efb69dde962d23e0c01f2f6795b1b23c0838e14c18295eb54a5119de1f967398c12aabe5c3889ec3a1a29b36ee5e83e1085d343360d329ef17d1e8e03c6d8e6614d8eea16db96039ed344841bf8711a5c05ab29f1cb36d90403b827ec930807946969d1643abb0bb816b8b46c8f9cfb38315095f50cf8df660327d8c418febccfd02ffc1ac29ea12864b4a14fc1f89bbc35a3131673dd24de7f0faf74dd998e185583668e6a4f8974980fe3539d25bd5791aefeb32f65b762422a38bab4253713706febe698fea2c4fac625170e91404fa1f8daf09a7761fdd34310693f958e382c2b7c9cbc2ea7526a6f07e635119a024e403d49cc01d580f6cabfd4e465abf99d75f81794497f6a09ea1966dde81fc62e5277723af87636f0d08355b971b0717efc0a3439b4ccf3feaec407e0280efa4c68b32755a2efdc40fd53c4b4d9720d00683fd73e79591ad7a3c6aa35b50854b832c6d3482a44e4f6ae381d35a9de30149613bf296d059125067e5c0ce7c2be28d5f431f08443655dae0c44be29e601d06c9eb19dcd4068f6fca0ce029ee5875a1cd5f4cad144e2d02574dd24fa9ef42ec102ce03de6fc65352fd8f1749067e21550bf01fdfe7ce69e7c6cc4790fcde39225ad817a0ee05bda756aa0f315e2920640a539501924d351465cf3627cd29b4b1698134a6302fee15462ceffae80610d963b102cb612761cc75eca7c706d22764079151e1cabd7218d4ab73b54f7edd7c7136302c3b6d370c3cb7ca995c122f0bedc948c9dcf8ee3b031b3122bb6648c8b778aade79e2a255f14c1a141dbd79012f396cef988bb94210f8572cf19a2b12c00b484d964d67cfb35bc4fd66ccea6ec8d67f847240d67d306d0f7ab32826c363b635748206c862ef2ce82794fc95acf4cfa9036a39376e4fafc95da0e9f3f3c09f9b07f492f9c4341b79bfbc841dd0f2ab74877ac7832aa742a215a044a092d5287fcb7aeb626de62f741b620329da56db77138519dad7fa98b2c2471bb0ef854ab4bcb752a94d70b2534db507c995923d0d8377129bb2251add0b5f705b994b134881a6e8375d028218b42e278bc68e73ef4bbbac29435cd244f1ad24a39d929d67082bf3d8a0f121741826075d383c5a55aabce1a951721dcffcfd11e19eb6b549aecd6b7ea2baa82ae3a528be037d3af2301a49482b3d89fd7974f3ca66f51f92749411fa56b28500b91c2b464b891847c61cdb9eb9d927be37e2680f16483a2af08525cfe472011e6861b70d97bef197c14821b52da4ed0d4f8d41b8a9d4c4eb45ab4a463651fb59d0fddfd320068bff3414bd716a58b1594e2d8af9a3123029db67e4e43bedfea2be87b9990152113ebe15a1e3eb3aaa70cd199efcd7573607f58d1508036d1fcad217166c9e1dff98c043e097aac805dae6fcb1a6a246f4516a045882f05214a372f7011440a37e19b5a05391649f3e89240ed05d671593ca636ccbbefd027b6e591918743725fb5bb25baa0ec1cb7338db4b2851fe8589dc9beb482068c1c21d2107da558a4a3396c179f4c528f0dc2044a88993b51051b0ce1b22caa20876316fc568e6f0bf42f6b2dc98a88ec7f867dee44e3c222d7322ae45b4d01df193d9aca96d0d4d720adcb4b757e95376c44afb615d1bd4a9e67e0be4ca995f52aa7504d9d2273a25016044b8b04595f4286c51c96dede5184e0c3f59f7fcd5c21308ea320337dc9668c9ce6effa13937c52b36b829a5f9d7cf065db631075afb8a04ed0e4d358436c58d856a84db55e8911b86596bfe95ec18500321e1cee75fef0fedf046739698470159641cc1209f3ef00d0312b01a90935ba29e0f403c2f9c5d121966e2d7eaedce3de06e65995e14c73ac5c63a033a1a327d55474a31628adba5d3f46213042078715561682852450e321f96c42001b85aef8dfcbb9d8df0c5c60e59f0203b8a48c9e78ae4530e528c7a55b4e4ceeb03db604d96ed6a07b0d80be0e6a6f08625e46032bdea56ab82421b7210e9bd309c69690f53f1906e1aa9e3202c9d39b71a0d77dd488c7808c3c1417cbd89d9df0fe93b94bb6b38c07037e72c86724cf912427235a8b0cba95197239ec10b1831a344b210f0c63661dc2d190f5f17f089d5b1d59c65558b346c2895cf121c8455356a7007c0adc4c96959cd36b04b343aa9af24e6749e9f648edc4169a19b8317fabab6d8856036b66c08911017782ebbd819731849b63530964850824b253c0589921c003f62eb79fac8131286e714ea97cfdcd0e2aaace8e1776778bf98c20fd68ea1b50fe2d335d3e2b99f36c7059f2ee49c4db2c63fd2c11f53636514c18b9734d8ca5a9b64cc57072cb01fc059f341b525de88f787a9d8eac3346a05ec369319b6f9ee6d4c5f84b5ed3f074b5fc384c65658dc3afea51245c8e3b2060f444b5e2ea5a22644919659f3da9aa9cb352a6792895e14c75feebdddb4b42d249788b30c0cab1381ce3e619605224ad37d9a2705099a9f947abddf5657ce79cee12c6154eeca92cf0a27e98eee0915646dbb497b60f5df243418ec90f623cc01309751efaaf691887f67827f223a0e84ef95da575d84fbbc28dc1effe4570368080bd65fbaa13287df977510238f69e9c56fdae71c5a9e6bc21890805d99e25bb9f14e2ed64b4456a77e8976fff2ea3817e9d2293606ebd6b6142b82b194a10bceae8cf4132d935231cee46a16c28f7290153e6da6f4c26cd46b74294bea166008929fa5941b261508e40b8dfff26b3958c7e1281b59aa1abee67a21c308e7dd1cd278204edc21a80e2de3d52a0333c87c81133d897e4a315fb69be0a876cfe5f9b0c6a1c4884685fe6ace80f8c0baf8e38a00aa7a83a4311d405bb1aaee7bc36fae658cd7a7827435c704ea272632845cd6b01e8ed843027d64bc271587374e69a5ec66684ceab1e3f26822b51d8564f30ace9922e9b433702dced6e9dcf6739b3c678c063dcf749c2ca96850b58dd0db78573879b43383f017ec81918f11ab23d9b1f722d33039b28c152d557f3b3faba443565df4f0532f59abf079c5f2716ed289e7294086dfe73a0eefe447bcf44bd7f55822350071628d2d7c36af06edd731c2c1da80f1820ce682a85155441d9640ece33f62ca4451dbeefc871ec4310c801afcdd87989829e778a022a4d7b83877885fd1661c85ef82a769b2fe73d90c226c226b93836f45097ed136ca89c667079bdff08ca668471a858c51288f2a2f9367005f6366dd7476567195a4115465429b2aa927c5343644daa31a0eed10263181659910138635a1a6d2cf84fa670a942f91dca4689427a7b46436855b82f072d134f82b662f48be79101a217f0d341e163e077b40a25f1b4e02dfd680d4eaff8a4830cf363761d152c6a9f3438712c09528ca54f14484ab270fcbd908b9fe97249e38fc2ce89d67f84eb0aebb92171c554d6e49adc2686ed9acf28fbb45fceba9f7240163d00750645a9e11816aa3336428aa11b58e06e8c525ebeda578bfed0ec714ee26a6f1dad7e0e17e8e41918fa7af3158ac2854cc0ed04998d34669bd9a53c15ab38fefc9895cddc4f557a61a14a981fcb2fbddbb761ce1d30e7befd16a33ea46b47b7a30b016695dd73d3ee363c5f5a148d6708540766fffd9593a2ccce8e5ff1d398b071044553924e0f770b8e142236859a386227dbe57c1f470eed005f09fbee5614961585c298b47a49ee3769d5d9032900c7694e3ffeee62d2462d7bd560c30bb461c202169685a6cf3cbf004c277bb309145f349a53076c088c1b87f3649a5e90cf0e3117397b074765d7c521196f40e4cedfa8cfda14bbef0bd51b069e1cf48b42b43ecefb90fbc428d20d812189013ca6389762e2b66de3e24d4803652606ccc0d678115ab9e79b9ef7a1cb2ad2be7b41917c35852dcb7e82fdead69142d00f19967517d7249330f62d8d68ee1cf957d271f22b7f01238f706916eb20038c82b1944149eb5e5757e3fa86aaaa781f052c4bf8b2560d4e85d3c78d1db2dccac722e2d35ca776533d134ccb18dee4d0662169c320ad32e3f14c200b7089fb800a9f4b91c23e2fbcd1fd08abf7b339fb448d40a974a2f32e7b518c88f8b88b634ea6a740d4e9924f1b46af9e8c3c7a159072b9c5431008f3ec2db178a79f893296f86861fe0eeed63d2419871548c97f597718667b581eab928c6075710db7d536eb0522609ebdfb185bc11fefb5f206dc680c1f855a8771c34d104edd2a45bfd3a5d46661fdbb80554335198a9daa404e44f53060be0c35a15e54e7964102531a2d152ee9221b55daa60b396c62e9c0961353aab2555e4a18f1e8e7cc23bbe82b0568dc61d83a5993f7f7552033b8fc101a2e89d1530b160e9f26537946aef17000b63903a970b1ff9832dc4e50cefccdbdbe19fdcb05b7b36af1cbca0aac82fc154d1b095443268fdb23266735e67eb16b2aece5bcef451346e4453554931cee42a8721bb6d7728dd9070c0f4ef013598ca3eb5c052f7a21187c7b36f10be4b5ca7499e2b0cfc0972f8986eb90e798109f09641cdfa91a21fddc45f6de7eefbe09fcb249349a47154a4b37c2af550808f23fff542032ef78bf6960b525797df265211482b518a74c3bf9657d9d39ff65707b1508fbddb0fd5c11a276be008c43f90b4faead6fa314a4326366b5e17462ab0a321b95110225f30e7e3d11c6ce930f9f7043e582d30ebad3e9f6b307353fb557b03caa31d31827637808cd8fbdc667a5b1f69af5666de968584f8e57821f07c7b8cc2672a3d7f2a99727f6eba25631b188cd52c48cc4bc900f527b75190b463141058364acd13f8be7f991733ab9ab4ac9c6a2bbed6b108f642ec31cd92f385019042eea328272b4568d10980267fcf80424037944336281154cc1536619848810569c1de011c52398b63a8047a22833711083417c5dcd4559180a17a5c73b575e11c3983106cca6e277b87f9cb7c089b878d2fc5cc5f97f68732bbe4218c2f58c920f54b6227b7b5893b405e73cc1294c251ad983c477e4075cfbed1231d89336052d2a2fdb63c5d5a1bbbe7322e5f4da553a2db88ea8323843907cad79ff99807bf719de82d7611dece9b44aba19ef8bc23829f9647d865418ca59e96537cd619f7a3b965b394bc4052262af30fbd46657331267c022b1f3ea3b68da3a0137cda96b9498ae27bae047587dcde81e65a480a2ad83277abf0e4f900553013e116a5f359b2688630e2a8649c70f87b03380f89f01df9e9c04d4106e5943560b3f54532cf01d22438239cb2c805ccebb03a76122ef4659722300ca5cb91b0e02735a76fae8991ec2142b2082aa66a631781bd4c3a3b321f065d5f8d395550067b406cb7875913e96b520ff49a5a9ce66e5b34bf136b53dfad94bd320c83aef182852eee9499fec8468ebc9fd07ef8befc1252e5a953b0221b81fb8712ae95f3a33bda653c7040833b08c7799308bdb56f71f6e32a8805507408ef6f8aacc79dc854149bf82860610b105d8c64bde134b7f0007bdaf260cf80e5948a2bc104d3a8ac37062c3d3eb9c3ff08cc85a086f4bfcd389593f91cb52887709e0f99d38d5858de631b6685a2607856830e032b440a0f9047a7508fcd111988d52cda24c5c5eb1ed044493a295232133c4dadb6af85d6ce37a44b7d558459d4559091db70b78655193b2c803f02b8e4d744063e163135b9c88371c0b1ce517eab724bfb946b647f448008651b18134e40f82d37e49556403b20f2cf44a9ffb03efe0776847dfa72dea8eca6d90f34bf4b5e4e45f3158fa75b5ca4c14f6d02fd084dd1ee0870ea8c63651be27b64fa2e004e54513de0965563936da15c8a771797e2dc7fa9a490997cd77c2e1bd8328316e24a0e5dba63770ecbcfed1c17f880ab4649852dc3d341ab9b67d03ad6ff2357bde85a189d0912913ca6fff5fef8de1ca5cd8fccba640995cbbf81f1f0dd8f54a96f405686d4d68428f806fe76871ce8c3c52c3c09595dd2c6559c51e7628eaf8ac2259b4b0e517fbc6d40f4c0829f55a12ac2a5e96d88404b64c367f284a382fc7b504f00873eb3d92b1124611214d76ae95bb5cc9314f8ef6ee1d767f91469c66bdc9fad64b74d562e807f5f79b6d020a10b77c8d97d9d2331187c0dea3f2d7f50259bcc8d012ebcc9df9d6e5b675e671ddff0ecf2314b6c19b9f2aa0d1cbe1e0e859d5448d1e49e420ff0a0fc5d3ae7be80195fd5fc5121525405da664b462ce685cbc50611c026780a45f509e654540994744edc2ba8976d6d49f263e305d597ad2d2111a7bbd1633f3c57f091cc42015ec3b46f3573610feb125c2f68db65ba39ecaeb353ef2b3da67bbd3f3b3a0cb96d8007adc77c5b717adf1d8514ca6ec80aa6874d97b3763bd4f4fbb94d1eeca66b859d3f2be9349137e8875342de0c1a3e204e860f0a6d8c749c568f5390b6c76b05c8e3d69a92fba76f8e329b61ea3c4ae6d8af3b9582e3e0d754f5142ab234c05d7c16a05619925780568fc1e54c8c35030f91bc827491267c76fcd0594d7bb22a6e1b3915dec897af73251517b63d22e3f9a654515f4664be5011c96c6b56ba31562466932bd643efcf9caad321fd3337be446f5ad6c1655ef88faa8420732d924b62f73bd876874e85e92b5d3b5f24ff45b1843fd587dbf7c59f54fbd2914c2f3b2e45e9dfe04db9b9212e88c9b02900d4552481c62c8a7b7d6c4f72ff473c1b4cc2deaa5e1ad4338d398d31312fe2ec761edfff90620b30f543ea2a574bac5a8aff77cfc0e4d145c1f7de17ca1468bb064bc45f98f1062b94e82ab809ec98f22326a2f2cb43159da0ac7ef49d3f1f2f3bbb01cb35aaed7fa555113cc5bf713739793e83cbcbd3d04e22fde28e85b91639a9d3e63a3a9f382fe698a0c4df0f0dcbf0b09aeaa1631f0167c24429228fe1df7773dcbf8ebf4868e3493cbca365ff386ff9a4dc08aa969cf5ce8cba4ac493020fb0467e8ef43f58c2ba42d818b701aea51a66a7eacf89a4903122f4408e01da30a3795eb5c04189d879163636e3a38462075b74be9e9587473f5c99d52d64d194f51c1898f43e11622af766da08aa1f29ad723929af9356a86331a0ca02b0a56ef7b5ac7b6ee494d6709af65cca2f9e603d0fe75b352e6c096ab7bf8666500cb8f1fbcadcf54b8fedb171b9ffe1bd31b600fabe03d2aca1e37148f9b46d2c4186a59053fd884f47030a16272cba7e1c4d96792864dbe1c127e4614feb92e063c8f29487a3fcf6f1e58cf4ea6bc38d5278fa6d5ec1e4a35411d811ea68701c867d7ccecde41d42cbcd0965f2b5ab6200cc1e475e1ee0682b87ddd59feb92f18bc9b93f32f6cff0eab2b2498b564539203f875d60ecabd40c0dca7f00af3285b413334bfa1f5038d57b8df9ef97f141f80dfad623a4def5930ccda5414bfb8ae45ac55a5c91013971d1014fe3876c2cd04dc82a540e30bf0e55b5f8cebd9b3d173a11c7693a734c33e09df16f831342a6956bf66dab07cfc32fb3a0da8d7c25fcf072c1a605c519c5e74d42d3a274058f1d487c3b02f166d51490965f253fe4022341ecfcca959a6b8aab861cf5ea965861b661bcede109db39c57439e08539bb519a00c988104e603bc051ab3d84e5d98b391c9724d908260e1d8e0d4adbb52e014e1efa68c57135d0b4eb1bf009790eb2ea67be88c10404017f5595da50f792044d76cc5f9aab0fc5a4976e9a4386f91f42ceff8be61f35e35a8ae07fd3c09f6ed553b88c743bc42879530b0ad024e783453218180e46c14b9329998d97e8cb747f58f3973da0d7fa68d5e6c8c9b5e1e26fd583cd869298a0ba01f32fa5bb9145c11b0545ce685de86db2f53549430fa2b5dd1256d8609ecd3c87964e6c1fb7937e8b388b57037781d366bdc771d706c3b5d6cade5fe9ad126e41d01b35d3d6c3c3a15603b9e0378df37174c68279fdf49e3c6c6942ef276cff65d4cc987bebc36dc300c0db3bf87c49d65ac7dbeed69bbade29551edbeefb3f55d91269b2df758997046f8a27b6aef96a2a6434ba35c16b3fe5aea5a38400864ae4aa0b598932c6481c68267485165cf9f797bba4f5930d258b12355b71e803d0152d1a1f95b64f82488b8f766e103d78fb301b938d96d31535fc97c478201640041a75258bbe49f7b2d5714a623bae61477899dbc7559e555bc769f851c53052a64f9d596d5d4f0df9de0b18f9e7548aa60a780eeb20ee3a16641d243b2808074e202dd60f054b6ae06b6b46110a70587dd7dca814e3aa3f3884b226b1221b8d253484ba05a48d20a1402e362970dc03936491c726c62daa62ce590c911c83a286fad5bdb12837361d05e1413b67adadcaba9b8dd8e2f142d2fa2630a1196cc7a61ef1cefb2a3b3845c8b8eb47d38e93ca11b41806fa8f00251a1ca2cbe94b70c43dbe995df5c8322e0616c483eb7d14d82577378d8c6185121cd6796fb59f2963b093779e73b7a91d2997bd9dbe5b634957a8dc2c6b86f852e1b4e1d04bf3f42d1910f3bc5ee5ec96e8f353f9570a5a92bfcce783208970956b73a22197c28e16f667357565e141b943f2582181f0c356f551ff5d574db25add3fbb250ba11d0ec829a22faf080b21a493c43fd688d6ae4a5182a8fa7fbd44c2135376014337ba9e90c88d02216cc462307452c0895e9a4d45968fb7b43fd9192b2e1a4c7a388e367a939d73173470a49365d2b325601f9031ec2c88e2a188658546035146edc4319e30b01e72cb3a0a76e0a0d4529d3aba694211c0add387d68afa90fdcea35436de4c824ca1296cf418af2ce21e2fb32e6b006c30e623818ddfb57b81d4df408e5769c6fb8b218baf4b335dfe3b40bb6d4bbc003b0759cf96968dfe021bfa1fa17ff431ae03012cc5a752e55fd6e717b05fff7dfac4fbc3b84432ac78a4290f523eaad9cc5225627a29a6cdf07661d17e36ac35334cecbefd085897d140ff57d39a8cdff2646ac399c360e94d894217f6cf5466ed4fce1c98d887dba50d2a2cf281a0406f6e6e56d82231053566d7c19c9c4f96804eb40bfbbe4b6176c7b1c6e69006acc3f82e4ea86393c2c599d93555a98f0200f7abaf383a63d6b99f5f9438870b23041f712f92c167adae7ee90a9135d1fb5490b15edc2cdc7d0f49a85bb078b7a205ff13e8239790b2c1e5d1c409d925dc2a67d4a2653d1fa72cb9f1da7e3c02e05a5b4d221bc92ed8d0e2a009f344cd3b05daf1fe7eab8136b78f667303208c8b70a18e9a64d5421995ddc7d8a57a12b05f76280eaf04302f27d58f262d0761ea9884e84be0952c5d84e120ee1c1e81fb250e17a2ebe3b220273459f42942f58aa5a15720fc9b5ead6d590b5c970d65a3ed6814d6f3dd5746e10464cbb422892b5326ac30589266dd6f18f32001cccf3b9b7a4acd7caa913bedfee89bee20b3e57ef54625bb18de015f00f506c21633f693b86442502c71d80e2e90172ba418c1670e13c72786b4af7d01879180c3e2b725a2a7ed678ec9d88141e05e6f992f7ba00fa339031e88e0c06b6d350a35bb4d990bb1025730c6714ffc03299cd929ebce9d59a904a9773bd930c553d8c66d2577b37d243630be38e9ed026c193c26a4f62d705eefad51d28ab420d43e58fecfa1ae23a03eca2972f19f48fe9a1dceb4d6b19c35ea59438d00ed2cb89920cc03c42db2389ee74c9c37abf84f8681eaaf07038dfe863521d1f5310c1c5b0eca48cf6fb6ffc9ee95bfe0d8f841df593c90f1fb4bda7b487ccae09da99634d63f9b6de2cfff171851f33f9df05c6cbaa8233f2fd739f115c7f2e20b2b23270d4b0789f8d180103f00b7fa37f133b03a8dccda15022c6b1cfd6eb78701646acbf238956bd48d803011b972b8f543e78a63a8416f9287b4c47f628f30a5118f09207d87e932cb53eefcb9e82470fa2bdc59509e6efb386e2c8aa404d358e8213b9f739ebdbffb5c4af84c9774cbce13b765e2a62dceffab8f16e0fc23e4a9b765b0a36cf85efbc2afa70ee4ea6e1c290a106e8ede3232be808a8142ed2e36bba1382ad426e48217bb4f0988363dcdcfff9a9b9de22cb20d8e71163b5e7cb7d62920b8fdd2dd369842ba52164b46f795841a362d3df9704279671e6df21a26eca8f473dc34de14c7cc6082c0cfdc71499fd3b818ea282a91bad1fa28d1cb0343016cc671b786113dc4b59734bad72ee0f5f7c82ec15628e83fe12fbabc6ef2cff88016f938e391464196d71abe2f83283e413d9007bf1c946e17f9d41bbd5dfcef701e8ee353c2054d5ec3a4c030c4bbddc207b80514d7c2c9439e57e9febf833658eabb3699b5c7d5f03f607c56a53ef23ffe38f9b46c67e2ee7eab620287d65686d9547ac44845d118eab11c7f77d4bdf57117c8c7f5fab7d395d43a4ef7e9d1e72ba91f04a298cd70d05ddacd3cbc35f7864a21f2512b72e83b5634d3cde2dd01ef3c369f27bdaffaf8135cb7335348e781d35bcd9e66ab6992cda8873c1e00ea81681028f1871a3733f1bfddf7d99d92adfda1ff01da0350ebe081d78c62ba08d91de3fdc222bb89f915180fe19e5e3695f6c32c93bafa5dc88bd5387468c85b6ef435efd122cb56dacba4583aa7ac4a57c7d0b277aa0403824ac111c62d3c6c884c20b20ea0f51b1d5909c881f518717e11c529843a6741a36c4c56378be6bd0f9389d594b65fa64c96ababcd12ab54663d902386736b3d82155f163b0267caf127da720c6a04a4c0d9a79422bcffc123567c66ac961f299e6bce57a82eec9a1cbcd71c668d184a744f2ab5e8d6f041df17ace850f62f3c97302fce1aa4665673da7f48efac257ff26f96cfb3ca89cc87b1ea4eb990b50b340b66d9ef6eadf05d515529025a37e207cd3654649164f78043983381a04c284363d06a729df1606a5a4511d66c235db1bafc66a28ceb3f7ee6c65c1257fcb8d9bce8f259000596391aa5c4470a846dda48f0d2b5f5e5abef6404227b5a13ed13ef0cc0f7810eefa9040d4185cb21b935d2ee7400545828b0baab638f30ac384335295cd658a82ee41b75058b12b179f76f8ca450e177c4cdceb24425ab0682d61fa490f7b776d4cc37dc4c41ba8644787ef38e2fb995449beba6976fe61d3f40438f95cf56e5b5b80fcccd5b7310858963b21f37dcd72807c99bba996142286dd4858d2810fe5bab45ff19d314ee9c1f070dd210c57f746b3cb8989134ee00fa7d7a2736060d23cf353418c9cb8dc67da3d127c02755ffdcd9d9ca3fd5f825f8b7b3e32b806878198d5e42351e52348fc6fa7c8bdb8dec9af5786fadb7e48b4f7ff91e123bdac8037ddb1fda6ed3eaa340443876107bb001cc56ec295071edcbfdc861bd8db731eae90dddfb4db6d3877de6fb37a8a11e789afcd274b9f03fdc723fabfc17b6c32978fe52ec3598e97dc7e03d29af97aa89daa36ea26bc13c74d9f27301e0bdf097cd10f7cbb7e7c02eca848802af5e621dfda51b590d0f5c10fc5b23de26e025ae048174ebf5c2aafb7befc77396121784ff7a4d306953631c5f4988090e0bd1adfc8ad511800d2fa34b9be2a82e19e2c892cb8dd547747ac8f28fd6e94bad59939c6182ee4d8cf71d3ba0c392a259ed10566bd1c264a89e3f297326b11da05674a985e8a781b1594da269078605c4961fc09ee0d4955c5fe2d302057c6297fd1fbab81652356150ce75b486fe9a9e8d495a631cf47d710f40049cca7831a756276139d45ca29561b5d7a429d03e208a98cd354a4c216c4628e2a066680dd4ac74aba237eac5d319929478faaadcc79fd0360a9b1c30e2c1d50e02f42be699c7043a02f895fbce62739874da90e52a62514fd4a1910e7b7bb3f71ed4772badfc93e34a72b362d11410e41b993de26794edd907d1dd916a45c94d5e1b2da03b05b3ab84218b646acfc6215590d23bfb300ec2aeb77348a830732a617cdf112174eb3eb00a127f4bab5fd8ad076c28300c5d694acda140bd03168b51959f16a61d5ff8afe53b3d34d93dfb83a35a0092e9c6e3685516a6c747be6839b35f810f60e8115a40370d602037a19926760261424ff55db8ee7796e888733f1a386c469dff43e7a1b6f957ee5b16f13eec97e0f8b07d76d7ba7bfc848e65573cd91aeddf229b68ea74b9839e2f2ad9fa0bfe8b797ce8a559f0e366d0e76d1ff581472197f87fd9bf6ca21b66362ec922ed1c8ca2f703760147fdd693b81c4a6ef551db8c0c9cfc4dc3fce79b930a2b962fac2a05ed0244a11e0da5f6e3e07cff81b561513ac5ae847dcab8a21835ad5033f51c2236f2a78c05b0e1641daf3a1a7e08a2a46b3f9e755db9ffe1d63d5edef2c79f1f7427a6f0ac2082889d6b9a6ed92e2067cb91a4c0077fd31154662a61a8e1fa5cb06c1eb77f9657e0d7052fee897325b31f04e88276dff0ca2785f241614a86ebb283106fe3c9586d13a3c708262c3e65f0b03f1c2ab54ad4c95827ef8567f017076d9f3b56d043b6914813abfe9cffff3cfc7ad257ecf7ff6841056e17f9610fa2b61f51593e59f5983bdba83babf076ab2295b4b52228dd1145e740370bdfd0a264e00ec8fd37c5636486192d8bbb01c2ad29f57501aca25c20eba716e27f6e967eac51670dcb9f3890a18f82a035c5723af49bb857089b638c37797ab56a4549596c6dff813e5cf4093190283dc93f13287990756b2c15399836eb7bf5467d1ef7ca527cc7f600551fa0eb01ae9e18ba09c58769f2e36a582767c8cd859f19e7df3a3d54f3ecfbb9308d276a6e7946c3c3f18d5b9a90e97b6a3c15baa1d1a7f609aa4482f79150e5883f221c9707dc7fd53e19cdd6f468778d57f0361231acf4a3e2ba70fb4d555647badbdd8941cacdc1802cfd7a5fb1886269c5e6b6e408dde7b6d8667f7ccf5dfc41a9bb5f76122909db4731fb7d12a204b1a2b86d5f123bc30c89f731a3773d448ca63746a7500c8277e1c28eef7228c46d1893bff61d48ed23897c12f8063ab2a1f0b9832099279f4c5b593e30f47ff56292dff2fc1e205ab09221c20b4a65b62b0f1e3558af788b8fe1051e6f85203130dc02c059470a8483feb1bb04c4543fdbbf2f0bc116a1eff49baada5d3c557cde0f1ced0681b3a4987b1cb4e629d338254d6513e7498d0cd407d59d7c60639fc17dfe692ff03a5dc57faff645096599b31ba3a6f7d4a804bc53df4ca6c701a54ba731b33b01e9dda76c290612aa4fef7468616e7d17d9541855d8a51fe61403a0efdc343b32a05bd0754184ffa81aed286d7a6e9d904c23cd8d3ea064550ac515e9aef1f9d747cf3c488b85f1533382181af4e0f9163b38077eba9b0348483ccbfffd80ac6eff9258699854c363eb6fe26deb7ebc8877385776d930be33b8212f5ae851c55da96fabec8863aba2bf79741f08b3c7058410a8f83fc90f4921f8c95ffb897bdb610782b3c6964804ef5f7f55d0e9abff3c67b2c41c4917a5aa6ffa8b37bf843dbb93ef41def3e2ea3805b50166ec441af8fac2d7772833be5e2487cf4f8c0ba7272283e7a7c645d393916073d3eb03e4aa7d16b89e9332cbd4ae7a335520e87ab16c4a0a6e84ce25f1980dc89e8608007e2512f02ba7c634db9381217bd3fb0ae5c1c89879e1f5a5f8e1c8b875e1f5a5f0e8ec445ef0fac2907472b36ebcad0b7d62e31dc9a6c0f60f2dd9de38ce6c767029ff4ffed138389d0c857f291562da73ceac406754d8a0fd2c3f99da108ddae6381d80ff062a2df0a64fb75a4b1c121133eea2e8d374b7157d85f22be673a761e4cf0fec194b29f0910cf8998e8cf34b3bde8c46301fb9160cddcc2cbe394482703de0a0538f9305062d4fa83918dc3ef3dfcbe2f9a071f4c2f85afc5ace4e41900f7828489d54a9a9647bfb71de2b46edfeec23ef2f1b82d36d1eab7b5c3b50120e7482acf9db203e97398405efbe883c368b331fd15093e9063959d7175085bfa6756e79080f0facb7996b12c4e33743eea5bc65061ec95542e2e2019aaeaec1556e0fbb55af613985e371cfd755d14fb0ec669f6a55dbd47d6dd7ffa1565e56f9023366f8eaf4ba933c2718577a3342036fc7ba6bc9ef0183a5fb66346c4541b18a76804dbef02d414fbebc93c8b7a85da6e164b4cb69e8ffb95f3297b19ecc3e43fd5a65010d9fc9acaaadf0af24cddcbcd68e04748b0c9bfb34fe0b50794442cdf5ae18621ba910697ecaf082ee4170684fed718fd7944e8b97bf727b489d03385cfd3c646a07fca338e7ab7f7e0b9a9fc1a1c9aaf3a5fefb7ea3723ebcfae53ec920f47e4c9bb9792167552f2bf6bff66d9e9939eb7733cf8bb0a38f1d8789c2b95fe26114b4f6bd1848f0514cb70045acdc06dc00f978e5b29f0bdef74d44b0fb455040650c5529de361e65e9a9f2facec8d8fa6002f2811299e75e829f775ffbee697bf27e518a2889830fe1fc8bfe0d391b364836433243ee12977768b40de847f22c111f8f9ea54fbc4f08f847033dbedb13244edbc640e9f0cd200feb5a002a3f11effd65d96bc72ffbff7b26a06fd31c797d76ff496bb1163b89edb75cb908c05a36f1f01c94fea21264012c8b84e00c0eff5ce3078ddb83128c6c91fbe89c29b50336571115ba0f6f4d16a6aa262109772233ede74d2ae8bdb71adc575be15e9c1b662b332b939f4ceeff98b3184cc43ffe5ec0709194f2b04a49b8203832b2327b6228e71578e3c24ac4b5d6a455149b35a84147d609fca8e3c118eac6acdce4d2393bc07ccca17f8105e058d706e4faf6280128f492cb3e24a88c7ac64f4f1f2493070a00313d6e95578aa52e7bfdce12d732d0f86d8e0c2cb2c9b3da7c7c292a2457ba29159f26b44add24e2436d1fb4aa0edcde829cddd7a4b18550eeb49e9b7447f398ddf787dee85f18ed5538698a4439910b6e08069325ad821ed26c58312c01cbfbfe06e1ef3879c13027d78005bf63b01ac75e9b1a0379525eebdb0b7efff54c324ee659099be36834de81f6544cf0a54f1481ee9ff31c4fb32712ae1de288f8eb72e2394ca863ec192b1fb4c25f2a0a1abe769cbbaa38086b27b38e0669294a4edfb99a8a4ade0e5b3d36968c1037b8438a224fba7cf678239400b2bf61158d6ebb3d1b3f1170c9f6cdacbe74b81d4f6e7738483e62417c42c62c78db4bb98d6c2dc215feebdb5ef8638679e50f9cb2d499dc2a58898f7e76b735c88e7b8fbc8305451ef8107c2a456f4fbf4362b11d3c8ba6d40f8dc3f8bc1620293f64325a1138e5ff00d7179e313f970b7862155132bc1c9003a73092bea3933516d315e2079ddb10baf4db83cda8f2034655f573a5c0d8cb7fb907f484d709b3e6550ca53b782a1f80d71c382dbe1899cfe01ea756b05a7c107ac92fd8097ad3ab7046b85e954ad0afe198a71f4ca3812080f97b1f662c481cc67ab75bab126461f371eff141d73ea8b312e745b44d5ba741a6ee0e673fce4564e5c3aa8192920ae08c67f5b24773892196ffc245ddcd270cbc359469abf15073a8e1a45e9d8a167a8bb9badb9dccabb3250248f8f6d070edf9acebd07fd92e5970bdbda681f7d38d1ef0b529df8edfa4789692c69a5918b3732c5ec278014741012b4680ee65efbeef972407393bc7de35c94a8091532150faea6dc89ca12cf51e5ee14a2659ed30b54b4d3f1f9af015130cace19122be06713e12a4512c6c097b5d5aa8359d425be2395caa4d8fb8e638d87ccd8bc878cca65cac208e7bc723aeedf26d8b70ae37d57954cab891a99d8b73deecc7350d05e0f2802e37797cb64bc8e0c79c7e5e14f029b24c1e452356014d8cbdc6f591b3d0f84c9ea689bdc14b21abb3391229f24e0d9c85776b403ea22de61ab2f96921c1b46f6a07752c6b136d51ebde4353beb83a7bc1c33695530857dde6f15c99cca082ebb2a4139333f005ab9adf2d1d3de3af3ab1fad166652affcff514f538f22ff366e0d0a6be2ee51dc7e8b8fdc9f8f9af15de4c9d49ec55a1a99d4803598b7d241a9169d765d1cf1ce3049a1e8e5d3ec10f2570f56880b079e0e2321e68c82ba92e8bfc902b29619305d7330415f3b75d904770d5c50e6fb1c3d65ba611a4013586fcb64d84a325fc0049a989b731365328b9610d9b26a56c26737b090acc4aef542b28267a6ec7a567b76cb8916e7b2f0458914efc5a48c1293480fae3a83a5bd752432ac952bc732b83f99e85777fbc47b8cfbc6c15624b8d64cbd01388d076ee465cc71588abf70311be2618f39cafd99354171cf0a6aa2103af5adb9796e548ae7a3512c83a1c57cc91970d04cb1a56015f10440ab9d78f3d40859fd2291bad3cffae6e3073d68bb4711de30258514c59067bf97d8a542e183972b2ef94f39ae614f2fd90ec59afd3add877b1c5f42558157602f6c9851673c4e1c950e86c988c9f73755c6f0aca24568d705efff09840d7c0a77c01a2b11ee7f321672513c6166891073a2d34a3e943c4e8bc98b13d0d18763bfcb075ca240e264239260f292f23a7d2a807252ecd45b9e8a03503a83e8e49803147338e2adc8fbe54824066e3b6e0995b0dd5a7a6d927a1e333978e0a6059cde947cbddb1633bf2cfdf1a74c4b1b8eb1054d1fc159334b235230710df0aa90aa024e6158c9029c69e4387c20517e1470cab7aa3a54fc79d9b52da0c24904aeaa5820b36d1bc438d8f4e0216ded131f9a14c8ff596af8609bfb4d17219cd154295c030d9a6c96848482cd868e54a32ea18f459e63660ce1b5c0c2662b4167243db2ae6a32c183a4a20f91f3fec5d2940648532683ee9aadc6a573ad42a48cd8488c1e515c11e294c182feceed766c6c521d141024e99bac2ca29c40a27124f625bbd703825d6ba6865bf484d8a6759e8a379cdad8f4d11506d649888f97d9c5935f836244b60dd5932f0112c435361cf80028beb8179728ec36b1c51684bf885d01d1eefcb944f5fe783cd31af472945cff1b8f955289c7f9c3bbf40299cdc8b82c1009ae4f58a18170d064d870e8e713c2aed79f303590c6c7962b1aa4f280efcb3d1c90c8d2c9aaca8acd6b4473ad338bc87140b3a279da721809ba3ab19b7bce28130e7e0d6fc49a2315e8bc2cc4c04d38e7c1c46e19e0a2f3fa4c074b26da0f1e7fdab6d2b170f9f978fbc573660bb82bf3a189caf899644e66831fc0089cc1a720c74fe41e14a4e6be165f3d0b119c323774f5c471d592f5670e9b4303208c25bcdda94a4702dc5ff6f0099e0a165cfcf853d7f80885b68bf9fd69cb4d0fdfd91a0437ce3807c9c6d5af003c3458e91c7b29e03d00d0e9acc6f506393e19dd5e9dabd7a4158127adbddbfbebe41c7582cb34da91e3935607a7ff0f0cdc6802043304ea7b2a2fd7f1b06e22961107719376ca106b6162925df5c401185978b52cc6d606a2fb061f820f7665a138c72e6a300d018d6ff61f6e8ea35313d849120ef0e26930a8015f9bfeb2131d1bbe63ab44746e51aa34b02e844c303b66c4db2f70ec0768a9478070805cd03132eacee36cf9d1aed35357caa118c28d8e69ed484678963deddcafb8bcb1e6db2ef07a1a10b1ad300994703892cbf6f52e0f2c9ead1a5129399a568f16ba5b160436b87a48ecc3e05b6d7d033249dd9d8e731c014512b941543f6a0c9a7f1a5ab9f10bf1a7374d54a8837b9de1c897760d57cc7594e7e71f78608198db6cae7ea1b4aea7ca246bb32328f980ef310af55809f6117847cc4dfcbed1dadcc61ac339e986288ccf2551985d54972c909456593db028672f8d913d640ef3fd0cc861565a7a3e338b6d6d6bdfef5e1ab045d19eb5b8451e78c57d800e4a06aa9c0da802dac932397f3d090fd73ef0852730dc751c0b3cc52a698c03057a9ec4968396edad6e5f06f99a62210b0131547545581c4429d1c6410a7a3c0f37a28ae63f57183c7b746cd0e62fd75ad2755c468e150bbc7a6602fd4d9ae3236f19d9bbc4acb46214c2f27fc03c01768f78d6891e05d1e77d887b11181c982441e556ce508e91ab7fa57bb02ebb77209c7eba473680b7587b9aafc318a48d7aeb96c0ee8ad82bb573b3691cef88aeb90dcc47bf2e1055d8f066bd6b5a0ea16625bc6564045460d67167368ba07b3d9a529837a18d2b3cee50f1e34151d18f0d0651560672d10da88530a0e7c171b92d9b54294aae3a44dc17fdb52eb375b9bd22774486ac6607ecfda71fada41b2c40ac4ad3ff5bdd21e2657231e0d4e7727474238ecb6ebd09e10461c2717fbcb354b13c308aa5139fe76d189a82c03560aa6601b796af77f060e405f8ed5f1cccf3a62eac6b9941feb9c9f4833f6e678a8ad49bcc9140157d9f8c4f9ca9463f095334a831a7cd378fbf686ca6fef96c68669cb9313818ef2f93c100e55566bb82ffa9824c78f8c8264d750f56602c84031ce9daa21aeceeffc59c184094cf138eddad49cc3482c209c23d12c0007f0028cdf78327c5f903275d645bdf8f39569e929b8c56d7340ad01ecd275ac91c5b5758a10c0f27b7c40d91b875d998666d4b264042afb4f2f0a03b9f0f75473217dd92728eb01345af4295546b6a3dd7d6aecf083121e634e3f41f11dcc56384eb7670b5a2d90c0676a2db19f61e320e0a067f25b9aae63e2303db6411e0481dab6a8b138d5be32d55e8c632dbc66685c716101c23c845555507f4d6f3e50fe102bdfd83c87e6025bfa55c688f22fa4e22420fee5e2f3e0722cb56d05c530a2b3ab0abd82173092005d5fbb076f23b3aa74055194a9cb03d0dbd8eee1dce31243d6ee219f4da1f9b4e968dbbcb68ceab51fa1aa3457b23505cc08aefde0ccc00d2ca36c8c00f6594e58ba93a8287d8de362284e56f3a4c5a0913b7e4cae8d2f9aae839c299dda09d7a378d60f970b98bd6979be15661c2d13293d9200184b116bff9efa15e1e8252d50ac18111b9ed7a2410d5bbd942d99b4457d44bffee9d7287044c37b58f0f5aba1c447b5aea04c17e8c03a3074a9cbfd61f970031a4163703ec333d10608aaddaef9cc3e07fc5b5a289fa800e183bb1546d880d0966c4a69d885643765a2cf79cb522cc366af2d4de2b463c1aefce58ca4da9cb4f6f9001682c1f2c1acef84a2451bb92f56ad655964dd9bd1eb2e6fb1a2ef7d3e50a70dca9237031ad77b4c20f2251c3a37142bd62a2c18af988dfdeb63586c660323dd60883ff6ae341e1d0b7218dc82afe96e1a61ce9fd348922f5299f0b511f42df793fcba79da8fe6c0fe697191d53c75f8d02671c921445d90f49da8bc7970a0ab8bc9b4407d722509ea5730fe51a20ff7a0fa4702815c05f8f473fd379a4b4ff4ea26f0e61fe4ec69bcc6a48f31ad17de409284c0cbe5069b20f16f0714a9c7f5198684b13a89e4a1d6a992387abcf710f10917d603f1c1c85d845211ab6ced90a68f862c30482d8f6ce5bed0731f1080639029df59865986bc67e8c62ef718c94ab87ff298ccf16c0e6dadb4bf7bdce4e5893a8fb61637af20b51e86babfe308112c9df0ca47785aa9552d5c34c445f2b33df8c3b324dbecd953511e5aeca5df3cb3096a2de39347554c168c85d018b75e78286b03ec3bfbfa18440da87e270f145d64a6783ae1280b31b3f228703edefe7299c9033a507ad168dd34a73a9bfa4b254fe3af7de218011b4b6a155147599ff985c4d48bb020d1fd58c2ce30ded06433621a2148f72014a01d6eb6c5c8a9a5376bb6618ec8904377c1ebe095c8c74893284f1a3963851c52eac5d51ef6e526eb06f4723c261e403831b1017e87183977509214f357d57e93bc9aad9e820412c84ea6cfca523fa9c913a5190102cb3e8dec39d310429d2b1bf15079bdd019ae2258846d1cb258659dddf34e0952e61d599f67699cbdd6527c04d45c77b8891d3e880aec59effe27df888143457d571c5f8989cb35ff591b0f2b910c86876b8d5c18acf8c9ea219f6e75b9ac33776ecd7419891e7fec12e533aa12b2c2aec95c75165bf411c4be6e8aa4acd016336a7090ae1fe90797b9db8b89735661081fe6a4aba8855e9822e494173c308a6d3868d66cbce61c6ccfccc23211844fd4a5812eb65d89d6f023afd2fddb2e8a449f1e421cb7b9abf50ed6be0a3f8ae20d9f012344482e03b18c5de4082c18abdf2975d8f7cc03f48ac58eb21ce075b5345240ebdbbc22802e9408b20b3e31102530ee72c12574e91326274f8f964887f6f14f885371aa8c7be4ecf74a0bfae7808f2722d5aec180b263aacad01e9c0d96f00dd591a152fa5de6e60a61d9accd1737a2b19f04e310248a563cfcec1430f430b3a39b131558a5e9ad944419f11eb3792acf62d4d3f0fc053d9a221e24ffe8791f0df8bd2d9e891cb048f426e6aecd4f4bfc31c90918eafed0b09a119fd70cd4568188b5cc5d9ac5b604cd4072218f935b5fb9904738338544adf9c5f33173393d5405b57c1f98f33cf39312690142b08a7069c13b1a2db6ababd9dfd20ceb6f3cd492c2cf94ffd9cff787d473e6f6abe4e902ffc42d3fe70499100cb899c11168112f3ca28f115ff5d3631c92d17bb22613304dc66cecbce3e96181614c83eec884f95421c3a2eeee1203ef115833d0a5dda199928d88c212bd1b91a8db325ae487f28654eec7729653db72b7bc68e29b19576d9a073e9fc68db96da7d702a740c902136350d79d285236b25fc04b72575201e5198c753f1b9f5b10b0143992a2705699c980cd9cdec9708e56acd658dc17d3f0419200d98e228ea06b854a5cdf2f7d17a0737f65f4403b38c66e84fed9b8279dcf7e8772ac9b9190320e2b1d7e8e795c08ca9501f8ec8f4191b8bead6a4c85791f657bccd30b69550b82ad5f1fa73735a440bc04ce776cb3c3ba13557380baac219cac0bac74c9ddb428bc8376bd1c10cca230a281e060ea25ca8b6755f8f091b1e77733f60dc2ff46236149452568ee7b3abc579c3adcd78319000415b3ccb6542c8d805050db6287bc0d0c257810094a465f904a919d39a4fd136b74607907169bba27e22c815ac587c7f4803437c9a32c8575cb331be2597a3cafafe21ab121ae9ad70aa77a9496e1c7401bd314a6e78522f848828e9c651346262f16f130b9d94f3068cd1033da7940aa6ae44b13c138ee8a7bbd1e390657356b1d8a8fb71b6a95214e608a505fcc3f433d8084e2e243e0c8e571c4bf4105b568c56701725af51aa5799981b44b661bfe8628f5d0123357051c34637a9cf1b699a0a5101935c2d5df92006ddca153e7cb92fa2bc49d2ee35f8485253c44883450f36c4cdc1f19171e55b8bcd4aa904e7e6ba6f7b11edf2beac682939ce79ec0fd5b874212f2f60520f48d55c1527092c8d64bfd03079076f36a9e2d5267198c5d72a27fea2a6c287f1eef5ac21c718b3e886c8f8d3156731f63feb14dede6105faedba2c2f9162982b3a7eb81e313f444bb20bdb2393ebdc06f57b28957ac74e261188d7819d1352abfce6c0db1fb15ab0c53632a6d0ebc007aeee8f56b7233f4f933abc09dae66306f36606c6933bdd78f2344704f7f7dfc51c9a27ec6378db08f15a72168b487f0bcba05a7c2e46f3f544ace348ea41626650bdb0b58320ffe79da1fe97d97f164dc6491bc374f88a3b3b1e90b732ba374858fc011c9dfd06e2b650e276131c6e855bffa16a20ec01709545c60e791522603d45c361825f1f5b35febfbed1cf64cfde9fbb0edd87a835d3f56395f875ef15e567eaf4021eba0ed13f3abd706aa0bd0353378c7c2f746535e1593bf3da60b8aa3f52b4d9e8a61fae06b6d76ecc34a2510358b00f944d911fc840a202ce98574f01786003dcea10b3e08d505f2fea86950c83e1baa40e7f7770b37b2a6d8c4d472118aec91eec5fc7dcdd4495f462f1a111f443edb8c63b00f1401d2647fe9e88098c062bb11434410f4afa04cdc846af156c224bb1fbfd6bfb9dde2e5690de84fc9bfa0b36c8d9fdfa3e60fa09fedc4f2f4a10c2a4787f40bbff0af60d441e4241617585320339e8e1d58147270f3d7875e0d0cb4307af0e3c7af2d0c1a9078f4e1e3a78f5c0a393831e5e1d7874f2d08357070ebd3c74f0eac0a3270f1de03cddca7523ab802ef656dd46b0fd257490601f527bd928e001201e6d51c08e237916a2e4986b2967dda09134b74974c0777282b1a68f0b0fdec394a022ecef3c9ad4b8bf6099448f2b90c8a01c517d362f1d40f9e1e05f771b12effe7014c3854720e9d1a2b8003d60de4ce4f1413c1a2fb2e1dec4ad4e775b87cfbaf22f822939c5083824b89fdc03ca0462410cd452658cf8600b1bc6ab8581038efb0098259c870c6050a4584a9b29e6f1c9f9ecb972e3a4315c5ac03b832e8c245e897e81360db7613fd2664bd4cc25174788a643394f7cd70c70d4df40cf4d7c4c833421b646d420fa73793c5ab81ea191f559df5a075f23e6c875791dad689208b6e60771242cce546cc8717fc26970f9c3faa71f3353735308a212feb27be89d2786a8d3f3ed585c45d6d7956fde223f7e9103ea4555957a66a1381f2c2a23816e9d36b1aa2a01da9637f4da08ac9f5002f4c703ef27b400fd731ff6b351c0cacd3b50e7a1d04d2c10fd022542bf0fac1fa0c4e8ef02ef07e82948bf516a6a606d04e13f00c7b31eca4068c8f611743f298c266729360d396e4754521282eb0c01b20d49787d49ec940e908ae4bbb46f42095a0a31679ba827999a7741b9530251450072241d2517e8794648610f6ae9c20b67d7722168f87d993fe684e80c6334b48f059f4232ae0c9ba018ae2871ae7026437c6ed47e2c54a62eff39ed1b3c3e8e9e04c63e5d1e482182f9870ae552b9f1cab25ac416ea7da67e5cc8bbdcc766088a441184d8172557948aa1bab9fa4490d73da68ec02d848e79c9ef25a1548101d9a1a32c9d1b941ee73420830a42eabb86b97c78c15ab92dfaa6be3d6ae90a68e4621658d27160c198f90301cbb8fdfc55ea5c1fac15a5a9ba998850c3fb9c748674410501933364ee2bf56b483983557c6ec8876ff319bab301f4e08202c0bfcebbc5c71acec5f89f008bbcb262f204a9f74201657f243546512d76f18bef626d6e7e1b4991e9c44811d12feb1721c31dc0c5c90caa64f24dc69c6c3b79b7e7db991b534423b51eb6bc3e9b1c2239c8a8ba0bf618968ede62ca327b7b523fcec195d7cacd6a7cccfdb3dace15db0033aeb70e58b8d12934ebdeb622fc99470971c881e88230f83278666a5460c91fd4787d7bcbe27609aa2f245324812e803a10744552ec4b16c2f05ecaea5eb8b0865958e37251b14e7e46be1851ad4021e0318e23e5ea53a064bb51d98ed75ebc32899f965a778de9052262767d5a0b4a099c2ec128bb85a99ca8c28e59398b9f73261e52d77ca61eb3ee96ff06ba06a04fa5fe67975b8fb5fc897909be16c77c643729a6f10541c09686fb90b3faa505a2a2463744e275a78c415cbede78f359bf34885cb93d02e71c88dc0403df0f505ad017861277e8d92558a09f038c097cd76667e2076f4868c7dfd6def009f117b3cc0c62c3e5fd96498f5742c1e8302a9159569f10278127eb7d1fbac7c0d830f0f003c3c68661800c035dbb21dc76f074973f7974cb15467aedbe6eb4bb2f2524cfb6d0780a62308678710bab20a5a21c35c8ad404a85725831820008990509c85a00efe962cab0452e7591f52c1fa09b566cd3f00aa508e7f335cc510b6cebdaee0f1dcb46fa13a0f5b2c101e5dbe838fc4f0dbf18ef0820256cb20516cb7d581af0dea69137ee3cf3eaac8d942f59a95d30d13db48a74992de742c95f87a9d5815eca146091f9752e7d030d09f89291e31a4eb6835e9cbae0f2c9b99c3019a880d8eb4bd240c1fb675b99bdd8c30003b08c77906b05d73fb30b585db18da294f6b8cdc2a936f374c3b8d453c1dfc09c2c0afc36fc004f098b71afae8495414732e311862c4ca995f41509ef556b2aba8662bf1dd9d82957d698a1d4190ae59a37ce8790e6d792b134a12370d4a701e80a13754dca8dff9fc44bfefab6aee82cc633369b870e4287312ed2a19ffe3fe806d32592253960824b78a0a0f2b391c21e764683e9f4044a2723396298d571f0723d5580e08a37df9512d18397505bd75bc9261260d63e606777541a4828f467018456afa087def0eb3e5eae990d7758920adb36a0c9eb4d7a33dd24ad1065f452f4ab4dde3304e232b51bbf26702d8fe0d285869517041297c1146036eab13fd1de0e4fc81b788b777f5dd3b6d48a1d5cd8aa734f104b5ea5135af030b512174f45300ec5c8213d47ef1c1616433a8fdf0b0f4b628aea82efdddcd4e6aeb5a8f5f0e0a0cf74865ab2816605c0196dc56af13c8827e71262a58b6e0194da0580a45a3a6cdbada426bc15e24e4dc949ec4d1b055b71bee448265fef07c512735ddc9834268a3f54b43aacb1a389fc9e9195caeafbb23b238c968475ed05d99f3602ddc1b904a9a357042750dffe6f4740220d07a490849edd4619ede6fc40d4ffaa8557f32491b6dd0089fe38a288874312b9d9ba5408b43b3ef855e76287e01deb3f8ceec02fc89099264da1bd7a11c6a7d804056d4cb8889fd149c2ab84fd788d459e11b925ee84a25acdc58b3dedf0642fe3050cd3ee7d5f41fdacf0e42a362aacd96790745b4c1e1df7b8050cf11efa7066acf6bc1b39cefbc0549d604baed94aed1550aec0d80d8bc9ebed200c916dfae771c86fc15cb71eda74c9cf913589dc299d560324cb09b0d4cb334c7d08f18f1d444a2faf793adb353688dfd146297fb1772480c73708f7d5866a5e0e76d4572e7b863bb3ead47b46e8cb163c62eb6f74b8b77ac0bc4c8e6bd47c4727da468c6015f64dd191cede9cedf57fdb60c07d42117c519c454fcf247f19ae40875418964aee44f34049999a832b4df8b61fb8f5f55c9cd6186fc92d8cd6972fe51f26281eecd58b439ec35321d2cbd35c098766e8a67ccaa66a2be4385fcf6be30c33b224275b3224665277d2ef46a32018a22733c0a8e0318d572400c88f78e29d713e27d69e3416774f1d820045bba7aa2f855e86686b86965586b444c4e399bc540983e3f0cce3f96f5a7f094440d516e093d51fbd939a61c190c4c64c34541ed7f20553e30760caf992bb64f56380f8d03d294838083add8170feb6f0386da677829d9568c0f47d6c7b5109e427b1f1b123051fe9f0b128d6fcf85680de2a1f6de63b923a355a33dfb30fb1a3a4f8fb5dda02983d12ff96a02cbad44bea83f51cea25d573941623f6e120de233076dff8a5da1c84813f47e006dfb863efb0954972876bc4839d04732902922dde2d01982aa813d44954bd0d8a09699a8d076ad6f2d00bc941f8a902911e784ef19b30ccf49ebe2c3022f90156537d01f51005abc48e6f1eb0e9dd95e5d39f271d3257da57f2e732d83ccb012e8fcc44f3e67a8d22033322eaa5601bf5782d5a9759341d6e59f2c0fb533dde7172cdb4a853a7aaf083adb584d8cd3d2d9f7be60383c7301953c4c2e6f4df40aeac5631dde66a086055af9fc9a1adb06b893b6d102cb4dbc6ec69a798e65f2d1474083dc6f641b4381dc2737a5393a1d59c032ed759e5fc7155e0c1303270b3246b92ceef389660aef393662e2fbfe7af5e153986fffdbe79bdb1941da7b57b615c85fc9d6bccf4a84e6d9c1078d0df3f06dc76fd31e096a83b892fa6d6a8b8d6995a11bbc048c8272aef267ae8dcaac67333d092573e50bc3f52c3383d71433625921fc0bef5f9501f485ab72047fde140ff3917ae7f711effe9ed022999766d68d7a08ebaf7b1a9e25d21bec15d7f39d38dadd0e80a691b88eedf6808b82102249c129fe7801edcaa9cdc0b6de14607b8ebefff7bf20bc63e74092f32ad78ba150a3a0a62849aea742f7a61342eecccd7953fabe673f51e2a7fac9ab3952982db35114299642750e796854872e049c02d1936f2719383bb58196240334061c332410400a02a44320f423000ad3730bcc5c203500e0688e1b7701e38d7ecbfc71bc4e101460f206079dbf8ceaf698a8ee02516d6abdc85631757cbf1e2e1b02af5c22590be7058050cf7180f86ab080cc331f589e19e8fa32fe0508ce8cd45a998d9c3d7cd198edf8f65dd83a63255b9db679549c3f5d1b5c6b0fd7885d0d0709ce3079d494bc8da3d2ac218a90fea9a14e2557bf9bdc6e366e034522fa1fcba04615390c7a118f29e2b8789b28f4491d0265d58c8bae2c3a817521f54b8ae14c50c18e416b757b6f606b2922d60becc14b69272fd61a6fd201676747e23f5c164a69838395c16f6c99208cc0a801a111951e6581f5498cd62c35cd910218923f3aeaf06cc5b75c12f3b0a4459606f3f85fa60cf8aff4b2c706cf8b0a837ea1e27c4b0cf015e0ef5415d05c55186991ac77c9b24d4778f4a4c79cff1aea65a4557966a2cec46aa501898f20eb640173c4ab59ced5f973c6c5474245dabd2654be52b572881e00dff2e5eaab9ff1a1389fd8bbe70c8d4b53c330e1ed41dbf21f562e10426454d48b3bd52520bdad307bb59f93139289930d1249f14c77ad0ba948130bf1dfece96202d0fd386c5562e1a7d5579c5cac3272033cb247db00340de139d7d6999bafba33978b60a1ffab2a7107ad91d659eeb6c4fa13b5bc7ea8940ba9819b48f908034c87464a0ff5b8e8008de067586b8427da4fe3360f651707a41f5764c7b2f8d7622c72bfa4b3036b03b48c45f12ddafb67334f12c16114820dff55a936a757a08e7bae5debad9a9891b549a3c41ba0cc2eaac44712d601281a410c9ae53ab1a91a50eb0da6ff486d3b0b74a6d4067b1f5c28972b73541bb9f11db74918f5763b5c1cb6edd80016f9770776b9682ec4b004bc226815e88ab04cb90dd124281b9ebadaf87ccdded215614858134a979c8ccb29ad0cf2d00d544abbe49c1e8740a67e913bf1c9a8cb90967748165080d6407729d42d677313a72639071ca25be77587e2f1a6641e15462dcda5f031319c5909a7af5945a37eac217553abcece1502d1d8870bac222d7d5d0a34f6a930ea610b48ddef7437ca5c3767b30efcf45f6de3c00ebeefd9c81dc410e209dd5646f7af7cf447ee3f6e445d2fc7c7e6eeb9e49f35f567149a004f3f921bb823ef59a0614da4ecc8b21ef7f0ceb543fc72cc5eb9c194d3fa2a94feb5de388aab5afde30ca9f64e758fdaf28c46bd0a17180c055dc87818294880f1d72e0e400cd51ff3e451185526febb3cba6a81ffcc1276ee9fdfb68cb60eee38857ea166b5167dac55fd799f220c11489575c2a150704015998a6b46afe987d42c4902e0e6cb27d97f40e0f3cabdbfe824cb85af706d2b79c47302820bb19016cfdcbe05a21384a995f5f663551ce6759eb916b85d808169f9c3de70240c3482647a7cb46c261095c2647d03b9f781aeb44bde59cbe6ca08e27c8373884f0a5d063728eab4bad4c53dafe710b791e3358ab56460c2d73e970c1b4b0d1049ee0a1e9808c1f1031cb2c2103a773d4e03180055de40772625aa2926c50351888bba5adf4a033562f74cc620f52238528ae42f585ad109e6c6567c1b48e18feefc906fe3681c565bfca6692432aa5ccfb19d8e2b1b137f4c90e4f45bcf1c82332048530b0d9f207628ea79a42e2361638712dfe76597efee2a1ee6ad95133534aa500337ba9eba9ca1ff183d7707692be94bf584a6efacc80d1d514d44d15fe18b712f70fa613bc40a66011665494caa3efdbaf486ac247df37951aa98ed7681974fbcfbbfffcf51bc8ab43fc1ff93d5e698db21557b82db26a55d4a5a6de840071721ce3a60816a2a4e037a991488d88de82c8e6de13bbe270ecc975c28a5b8d00b05afb5363fd898a0b0de489f95ea8a4ee194d46a7fd56b142325cb5732f5756d10abb7bfe69379eb10b085919c69ad0b8b6a88aded99acf43e7ea18d943c361fd97140814f415a285db2e29f167862d410f105016d46772c4105128a38e180a7afb0551ed9b0c2db771eb04debdc460f09efed7cedeb989f82102d7c9888e1c68f5935158407990f7182acd6aeecea7b108592ced538be226f529075ba307f7ec79cf0483daddbb39f5a766efb82f1e1f78f2409ee6d4e2aafa0d7c40eba12d1ca3ca3792878d02f16a733cd0efe0c68fcc6064dec587b6a1a43ec22031668a7fd264971309ca9d6ca51d224a5a2c025db9e9e5e774cd89c3e28ad002d51948678256b4aff6c32d5d971039cdf78ff876b16665a62b8f5bc1457dcd17e8c5f9bebddc883bbbd45b8f875d837a93fc208f0cf386b9483d75050d1d23d21d8a2b70f94bc0b59ca44c79c6d7bc3fc5b64e8b261b1371c7460183809e8b6aa7dcec7fa4342c4d79f3b64b1fd37a6fe2d6e03d6dd6bba88b0956fdc98a3a73b95711be37525c09d1aa7ba6f858c3b65bbc67d4e27000d86eb171d2207ff0cb76e52e9f5cf332deeca91dc93d1454d0bec26936ffdf5fa4614f94e49a5e377486734ffa83f29ede77d898ef47eb336237a071c04f3bfdf486b8084d3ba6a4669a264d8591f0ec909b9ad5d57d95bd1be7b7017f350fbe17835f579077c9278e96c58926c800ebec4561449bcd2c5177564182eb42bcb180fd4b7028f5defb077ab41f6296983977118a59ad1bc231cf187e5993a14325fb67b08c74331f364d50311d085123e0e59f58f17879319f8b09ca68f7c5fd408b097995d7a2d2633034d06d6930ea138042744365888dc551592972cf654ce3891a8a81cd80972f3d599bbcd9d744124075b2e86f1bb2b93da23f6481937f09ae6ccc44ad0eef24c9971f86e697d49d7e4b0005049a0a92204a931587f5842e76f34ae30c7add7ad0226a5169f3bb395d042d860273adcd394bf9c8c1eac0b0c1963ccb6d501522aeac11d7346fcc7024a9e33e0c85680eaa7a3ddcece4a7678006dbec64ddbfc87e1f970079c34ccce87efb4201515ad6789f595bf6e337b415f4a04210623bd96eadc5f2145569cceef9ba1190234bc4de87784dee2ac7d6c0cb8eab11b0a0a3122c7c0e55a1a84ba8a58266ea12c1732e22c262c88160a007051a5d0bbe7c6b53ad4fbaf9d17ea6dde68331766a8812b9f9b789ed01a4c8cbfc99325a9c65c7075902892e8fc7bde9dfd9c72cc95aab6b4036162aa2e382dedc8e402ea2c9b4ff1fabc5e4469191a42c0846c942d6387f6c3f60a0f6eaef01415fcbb25e36e5e8516102a28e886fcadae6275d320f4df9a0c998ec1f4db9d8681d28f77ef176d7e547c53bc9642f2de5ba453467b88d574bf9800b16ab93af5103e64b868c8a2a19e9334011bedb51aa3ccd0730d924885db3120cb9e90487cd67115b5ec6a6106fa1f004f40cf91463ea95ac23d389f026589e80b869ca25e1154d12a70f7e0e61a3a0558e466e9b69802569567b42f7f6c5c10119d6c7d6ecea5e25e8c332e7cf86dc944427a827dc04aded01bbddc28af1b9bacfe6edb549884d8e7b5d3f30b2b70c5d2ff416bf9dc56d69d40d28b8a27f821d4cf705be33c65f173710983a1dc0c0c04083430703a30e1d343898328e25fbceb6bb6cb61b916dbd4276a6232222e224b12119257458918888883c634fbdb514b7146d15df19ffcf9cc66f74a23d2f04fd3f136236d0889ba94b8bfebf69480f2280525528d9fe7f29c14585bf481339acfcff12c5202fb11a809b91ff67f2d549e88382018a47ffcf44c5228da11934acacf97f271b31202ddb3c2dc5fd3f934fa3168f9466ec61fb7f0037a4016a5cc960b8f3ffcb6e604cdd03ce9537ffbf6c3345fb624145863a35d912b3e7cfa612ca3a2525ec38fb369570fdbfd37ec2f02bf7cf5470d3b2e0ffb2ed29aa99848d99e0ff97cf5c43022f9f4c044c30e6d725e97bbff0dd6d69f5bd5ff83f86df7e65f2ff5f3e4ec670fedc9c6c9bdc7a1b29be6963c3376dccdfb421e58a3a7c0071ff0004a070f24d1449be8982c83751487e13c50ca6a6a7ff676a346beac1c4644e612e2f35595aaa446992a42a91203db21d3562b4082dedc199a2b6ff6522b5ed5bd7d099f0a642fbdd6bea4ccffd2f95ccf1dd65592bd76b7a6e8e69ea1dcbc17114abad33a4a934950e84e77edbddf0c5beb676afee53c59ab6db6ee510cdb49f1c1bfe2f0fddf11c573b9da1426c76756d84e6f73ad86bba8fced235e06dbf9def8662e9b9c3587aaeedecba4fb1bdaf5363ba306e90a0ff877bf54f96a1b20cfd5f060204e7fdf1336bab70dc87cf9e8ea5dff4fc2ff3e0f9e5f34e0f3c3dc6f430f2ffdc8d736797e1385a85e317b077bb998ee93b1bc77216fb7646a9147e6941eea46df96a6b238839e011eabedeed7803ee80f75e8d86f1379e8defe8dde50862d9f80ea3ca2f35fac6f9dd653b5e56c6c76e5b5b4ba552eeccc6f7948f8965e33b8c936de5fa95636cacadc9ee99aeab52f9c5d6f64cc77da6b3f1cddfbadfef7b4fa5b12d95da340d968def3a5e04e3fdd666b15229944769b8a923b1a903408e24ff8ff8cd1c850f67f397bf4e9bf7fab5c1b8fdf2fbc5def2f41584bb200e3ef53bd37e38a038a4fe1f8e0c9e5f32a775a66b467ecf9dd18693b1f7d559dff75b868dc22190b1f7351aadb5f4b6ccee70acb596d9541a8e2ac276b4eeb7fd62cb14619b8c8e9d6edc693388f6b90b520f61f895b371f6ff6f7d011bdf3ee7bc48dbb1cdedb8db35cd4806d7ef5696dfbbdb50eeace9aafc421bdf3e677cbf7cfb66efb8932ea401b94c53e99bb6bb75a6c7debb8e13a57475ce7b6998aee1c6b7cf9928cd62a987d20a7c8e79b54a937538c2f655a4cdcccccec2387d9de3ce4e8d6f7abebbd5decab696eef37bcb5a6c6c73ac3dd6c9eabe484f076929a7e19796b76ff6658c6d6a8c7de72d4cd7f73ce555dbee9b3b8ced3b7e196f9a9e6eefb8a6e33a4b76b5c657fe46e833cdef9777adbd96b6b6f6809e39235d5bb98dd51a72a754caa58a7fd2b7d7f34ba519ef2a4d36d373648aff97e597f7089178ff6fb5ff22b75820e2f45d87c50f16d97f67043802dc595cfd7f84158356b4715ae738822c6b57e85831f81f813f57245041c84f859597df99b0a92285764acb724d157c1f41c5d43f7f1272ed33bdf040e39d7357a4eb297746f8668ac6ff92f02e76b907727a96deddda4c11f74fc61de9668aa8ffafa2a0f32fa3e8f25fb63596a3e941da4f96b576282c3c0abcffbac72496217e7981c4fb7f02d109caff998e9f27eebe3bc682d617f0ee76f4f944739a9191354d949920f0ff4ebbd7bc6769aacde75890da17a38a6f677c3eed998ec25df1ed868250c2999eb1245c0050b3ce3196daf87c62a98d652102ff7fc03743e035433cff8b3fb1d4c6b542b5c2b4c26897a75a1a7e3e410c028107049b3ed374369486b582f0b6efc173dbbe07c7b0561044897bee8b7534ac15045148c3da6d57bcbb3d6b69586b7f7023cbef9c2cbf5432ee24b33d407a78ea00e9ffaee9be411aee206d069a7db311cf371bd37cb3710cf37fb9bcf2bfb4f27ff77ff7cb2a74759f19e9ffe51b1f95ffe594a594ff6594ff25747e19fb626cfd5f4279e264d9e47fc964c96d77f87fa9e4adff9749fe97487eb1f9c8e7eddaf9388257a91da6a976ef76d0be2e4f5f6a2c67b3d09bf6a3ab73defff2c8ff3f2d2799258c1481b5f5e0ff92c8b26d08dcff977133215dedd8775e0a5906f95f3e2e81d0d539cffe31d3b10f7bd94396b57eff4b1e923b742c73303ab13296f827dcb78e2f1fa321e3f35f0c4b3d9ce3373d87f1eb1dd3311658a4f3b4cecf450b9516c5fedf1a4f9f644d4deda3b6f2f973421fa76fb8a72d3e9fcfa75d3bc9b2586a63536ba875f0dcb6da583e16f7915dedf74a766339dc1b367e3e1bcde05ed32f9f3dc1def6bdbbb1fbd9b3d60eee74759f19ffb67dafa6ed14695ace16ace99bfadaf56e4767f3b467fe5da92f351a957611533967b904f4bd86e5f84a5d16aa3f0af7ed2bd77d91cef6bbdbf0e0fff246a7ce5d9ba1dbfc45cb6a6ffec29d5ad376837377a46b3bb8c35d119e63aad6b6f3e040b93e5910bedf6c0f846f9be6bb6d2d1ddc196978ae6b66993c5d770aef2d773a3afbed7482d6397ee1392f90cf5a48d7f97f671e3de69f2e8ff7eba0b37eebbe483bf91adf3e677cdbfda6d67df599567c7955ee364dc79f7d8e658ea5ef9b96e371cc6bedddaddb176b57692dbd15a4e9784d63c86af9dcb3f47366dcf2fbe5755fec8c341e77c23dda6b2db5db732ce5f34b1da3ebee18f3065b597e7bdda09ac7bffe02eb28703f28de200fac1b84d605def13a393e2848d8c79d73cfd2695ffcf191c39dbfc1f1c7472e1646d7dd316ac7cedeeb6a1394e28aff77be583aeea4f1147b3d3615dcced0f7b66f2d7dc7b3762bcfb494bf52fabe679a6a9d69592ae576b7ab55bac36f1dcb39a552aea511b9542ae5909cbff74bc7c76a3ee6868daf82d9b7af5bdf37ceb906ce393d4ba552ce9d55ba4a4bcbced620a09d95c9b2362be5bd0b72a7965e7cab7c8e7995a6fdaa2dfe7fef9b55bb773260c09f701c1db3abbde67950380fee817b8cdd0fee303d27d85bfcbf732684ffecbef896b57d70a7eb4e61a6fde6e8662a3d68c3bdfb75eceb47ed5941f84a1396df69bfd745fecebdf74b27469de939acbff74bc7a935dd63a9d4e9149313c35eefb8a6adf27b955ae7f86abf78523fe76bdb4ec0b635414167cc48cf8a2f776eefba83d9b5add93618efd7bb6d4dd01a173aa7744efbbd5f98beed3ad67658a6c3b0bc8f3b6d787ebbfd4ef97301e7ce6b56daebeb4d57672ecbaf95bef9adcdcaafd81cd3f2f5e6f43c53e3acf46eecf09c370637e856d37661d2abddab74afce31d68ee7f74a8debbed8e558a692dd576b2c739b1adf762ccb50ee94c2c10dbab9f577308cf3b78f4b6bfa16acc65c2c8c73a7142d4d2b95f2fb85b5f520afdfec181e97762c2d28e54efbede34ea954908bd90cb061fc9eb9932e6f100bbfbd9e69fba55669326b232dc79d6473cc5fc7d8e6853420afcef1b47327ff00d5669f95eb5b6bfdfe9736fe7f09771876a8758875e86418c95051d33090611bc335866ab234175310c69f655c4c01e7630af8334bf331053c8cf3e798cd132b184231c4f1fffc2916f61a5a783b6ca8f5ff855b9eaa85f9936f9ba5eb7beed1fa9ee367e33d187d3e6dc215fffc97059a8409fedfd9a7597f060609fd3be798c3f19b9d09a97196a6c6597af0ee827717ecf2f4ed37ed27cf848a549aafa6e5d7aefb623c13d65ab9995030db9885de5d10af0b0a0636d6f540bab7cb73f21c3470eed9bf39c6ee97c5d67879b2fccea52d3e9f58ea61a05660d43f96dad89cf0a839e1c384da6933763f0e247207a2e7d57ef19e4f6a9cb51b0715a9fd7ab55fbc426acf0aceb5761ab5a3b2fc766a4d579b7e2ffecfc593fde1fd3faf691138f4cc3dfb47fbe541e1a82cc78ad15b4bdfdd8e66df7e8ddefdebec2b70c219807bee8b7c2f52e05969f289e0bb9b7b527c533e0a4dbe3e3eb8265fdb3f967af8eee1f95b9b9536f7447c3d66b727d7dcc3fab7c6dc8eefb7ca677a1ee3d4ea1cd3b0f6adb56f638fbeb5f6c5521be9ea9c076b05a16fad85de8d756fad85daf6ffb25ebeb5f62d73f6d95229bfdfe8fd72cee139ae72fae689525b505a81c16cc78b79ce5ed3734e4169afe9b9d8d9611acb7be74e679c85caf23be7bcb5d93996f66e470f4ab9367b6b7bafd25567af8e41017baf46e93b1aedbd1aa5ab7397ef466d9c853afb75d029add2bcdfcdd174758ea351e74cc773dc59d3552e8db35041c2b7d642ab747cb5d2b222d51e7b3b150c8cd7f15b6ba17980d0398fe70142673aab8dce2ff5b6692c560b5bbbccefd87edf31e9fdf2b7372ee563dc903bed0ec7f4e26b9de76e6ba9b195966f5ab6c7a664f6ebacfb3d776dd74e98e918ee56eb3be8e4cebe8de139eed89aaed63dfb4aa5f6bcadc6755fe4f48dbdb5f4f4e5309febb7cb349de5331d37de357d3b6b3bae5f7ae6d5bc44a99cb4c21dbdbb954ba5d72ef6a02d8d8d5aadc338dcad75c7be579b3bc6771dbf592ec6b7329d0db3e33a8ba5a96261559a4b39dcaf54adcdabb1148e6f3826bbe5583badf37a0dc752389672388ec272ac85e7773bbf373dbf362cf77806e44e397e393cdbb7d6ef4f50ef4e961d63f98deb6e77b8ae0f1a68c7df3cee9c691b439c85beb516ea7c6bedcbaf77cc980708e54ebbc777f4a665dbf9d65a28af692dcc6f5ab6e16e6b63fb7ebb567ec7e6388fb32f972a5269594e6a2b52fb953a673bb7f5bbe8b4e34eeebcb9fd36d134fcffd437d1acff71166af8e78cb32fafbb624d3346ab55dac915edf7ae560579dde1b75fed6ddf73c6d957903bb9d301677c6b7bdb15a3fdb63ae1de6d9a57f9cbfbcd9dd93a3ee89ce0dcef973ad3541ec89dd639ae6f9b4bdbaab756cafbf48d171b3bdf7645b8771bf29b0eb4bee9c0fda603e6371b5012e0e4e5f4ff5e6d876c870f0ed70ed50ed30ed10eff977dffff7476f8bfe4eb75c7c3521b6b7a59e06a8f8eedffa5de1dcb6fad9501ff9779784f727c9ddbcaf3df7bbff0b6effd2feff2ee3a757e6f7bcefb5fda6dbba219dab6efd5a93d68ecf01ca3a5c1794bbab96dadb5df5e0fce741c773af98da931acd593edd8daf9aed43ed817e3ffe5b3ee8af14cf7fd2fe5ee7826ec30ddb78466b1f4dc4dfb2de3e099d0dece6f1f4d0f7679fad27e709f09636cdd01693f452acd77f7be9bf6b37bb793df4e47fb05d674b6b1eecd2bbc52ef176bc77d1dfbfad9f26b977debc16c631d4d0fdabddb59e7d8cfb0f1edeb304ded7433233d2bce845aba9e09ef9aced275c758b0f1edb3eb6099c6ebf2d4f0a6bef4e052023c6b69bb9bf67be5f26eda4f96b576f53d13c27d26fc5fc22ddffa4cfb61a98775b7b575cf6a0501ffff9a0df0966e119e10d89a6259b1b6b7ce71368275e6cd3029f695da6f2c4d754eb5d73bb696953dedec7d5ff9fdd63ccb23c0bdee566b5cdf73af7698bf6129c2e8f8b3a6abd1e8b3a6abdbfab5ed4eed33873bb67738fb320aea35c3feff01032fb04c5f99630fc0986e4cac3506808302188c3d7f06fe0506d6f1afaf2f26333526036968cd05946fb5b15c50c8d98212cf847cb4206dc1d4f3a73d6648019dff99b67b35963b8ca7a9a98086624d336aeda9d6b6e7ba34654d0505b0734c67393d73fb95c7aed4178b33419aff77fa423696e0fe5e6bb1d4465ef779bedeb1616f026b26c0fba7fd982468aa6c2268f3cf64c54113418abf01f0688080eedaab00d29a6080de2bd5ec01d089f58501186300967d460b9a606a2f6bab4c83147c31fa3c09beacbc2cbf54262f089a5f8fff4c765f78ff36060ecfdde986a5afa667a4e5aaaca50df1f87379483bad398dffb7b572b6d6dadaeab20ed8575393c7f59a99887b79414ad1c585e3ad733cc73c9b7deb41c62d2fc0ff67ae687a15e882f35d3ffe9b5d7fcd2eb5e74f788e15f9187fdab573a915f54dae365259584dae0eff2fb7fce5955513975a1da1e616a46f6ec181b4ed17104b3d84fb6c7d3efb627cbf7e36f4b3be99f5182fab9a596aff9fedb75dc38d893d9f512cb5f1f9b4ddec6ebfb10ce5636e6f5c68f33138b434face6e6beaf399b5ddee6ef71ad0ce0b737be342c1e733fabfa49a5a4a5d35ade8fcbf759efbc4a6d5fcff6256ed14ee7756dbedec321af0b66ba75df7b8aedf69b32acdff733ac27f20559175bf52ef175b46ad6d322cb5b1e9e9ac4945a7e9c268361f9b5183eeb92fde735fbca3769486b536dcf3d9ef57ce62fbe01c967a98c5520fbb20ac15b4e9ea9c07c7c704dd6ada4e96df39f99d7b3e0744a37687a37387e1f81a8db36f346b3f9f55b9dbcf271c3596a75ac06c164a0f3e9f09b8fdf2b7d642ddde5afbc25ded172f03169ee3f73ad70c90e56483cd006dffcea613a2973b45bebce9042867b34da72867138096ac758ea1003afc3be1ad6cc7b45f1300fff84ad7d96613a0669314daafd9746d36e535992039676dcde93b66b4630e68ad5f994f6979cae5381b37997a4c6f9bbebef74bd764b26a32d13499379acc6d9329d6640ae0f67badb3749dcdbeb00d954a6f4627878383df7e85d27751f8edd7374a5ba3301cde5bbfd96914eeb6b6c3745f87696c73a9d67c3befdc4ec77aaff26c7c4bf9951a5fc7a4bd57a5d0deabd1deabbd57a36f97b3f10d07e7d6bb5d5eaf46673ab6a7cd37fb77f29733d235f612994bda7f277f9df4dd65eb1cdbfcbde106dd6adaee5298d3b6e7de6d2def869620fdff627c67e39a6a5b4282d5526998cbda6a56eed7999e36be584b5095aa54b2563a40690a252ca5aa495a26a5981427ddcef1f68ef9dca5b2fc46a550de17a330fc4a79959ef297df771e772ec636975fedadc5f2f8c5da559a47ef6e47f94b67e93a6bbf3c3ac7516e4bef6ca752df08d4184bdf2fd94ccf6383dc79777bd196c76898db76170b9b3425c90a6df33ebd6d2d5692a324b5488922e198ebf234b6c672dd1cf749b772bfb1f14cf3be4817890b5218482c20497804486acf49c9b2f46dc7af33cad8676995267c94e11196232fffceedcb48cf63dd7ea9bc5be1ceef17ca9dbdd65aafd6f86a9de379d6d6764c36d3b3fc763876a4e82841a336ff72fc2e4ae199969b93a762d330b137ccd928462339468618a5f9b7ebd8cae1f95564ecf37bcb72ccc8cac85d6465518845658ae8fc3b7b193cbf6363b1758ea9db299772452414c5253ab475da52692c4be92ba3148646e1999e46ed188ea3897aa2a8ed8bfde34e7bdb151121a284675a0e9124a2ebbf8d280e865f4457d04369667a3b1d72a8c43be13ec7348643d2fffb9099212c2363f733a4e40d3532f6ea1c33763e4378ef347475f76d61214076a1c87f9b015da1b69ba6bed92ecff4946fa98cb43c57e8e9bb2c471192f2f2abcd00132244e8e99def203cef9c65ede29cc7e7414ae0f9a50ee21be41444e75f1af7ebcd6199be05310a620bc4471a53f9f6c5daf27bc7811ac7024d017a04c80da0102f754a3999b36fe79767e91aaefb5d7de31ad0d91f3d7f9cfc3ba57597e56ca8dea4642aeb38de78fbd3284e0b0c1d7ddc564cd6030065c9f1a5c89397431cfa0ac8398465115ca01d49e7ada691e211956b19cb8a0151b73607080d0df67690080f912ec0175b0b800c634b9434c262e37a5b9eb1f125080e4309f3890f97f8a4501ea78a22015087b45991aa01c482a4010c4f38afa80d100f226d69e3f5e6e563a846d20b056350d9a928b3117de8a061e2c01b7559a4b3d3e7edefd20a386264ca14b869c1e0e1085d50a64e209311e715330219ccdc353d8542db16e1aef66b03ca51e7ad0db5b1d639a6ce5bfbddbe7287196958a6696a349ac5d6d728563b37bed80ed37397a3db3a8a96a6bdded90eb758f15a38afc3f49c1bd964ee48a459210b2fff2c8eb048c19fd2db7eb38bf0fbff4ba61d3a9872d0f867c271e39fc9468d7fe61d0daa1dafc061f7cf34e3ff9f986430c5f8ff258c141fdc14307ff67b8c3fc3ba600ac1146f6261b0dcf3699d63199e5f3cbb77bb3666a1b9cb51fa8e66b5d17fa617d6587ebb636c33b96851c28b5b82332f340138f0d66ff60168f1b1ed9dc576c69b041459dd15a96f3d8817859218d1e199a046fcdf73c7469289a08c20228553f1afc3bf223dbfeb61c58bb80ad1e61f1a42cbffdb21e2b22e883120b2807802020808a9938f71fe9cbb1c7dce641da6fd3e3022232bfbb0f721ee03da87023ff7ac3d6b397fc2fc0977feb46ba7e020b52fd6fdbd5f3abbee80505a16a49b69b434423be69b3b9f4d8ded39be1bbb1fbcadfb224db5633ac62e67e37a901edcf63db8cf79596c8d67dff6cbb7ed7b7f592cf5f09eb5f45c5e87e9b9bb0bc2315d87e939c32cb6c6b3ed392cf550b06bdffb55a4d27c8f8fff2fbfdd1ee23eea3d9af1278fff2f1c1ab67cbdabd57b570bf7aeb6ec5d2dd8bb5a03efde8edebdffbc7bc779f736f3eecde5dd1bcabb778f77efc877ef0eff4f808ac05494e3a3edc807df73c76669b92f76f8eef36df73b2abf545a6672847a52acddc8d878d4325040c70667872b3832fe96389eb9034180c3f2d1a03b00cc405f04acdb81489a106a819202dc6f72aed42cf21140d31a347f9dd82162328b466114ca58d71c000cc45461b0864a074e12cf9056a1eee3c2485986d5a2194596448118943b492a5413bcc8b386500282ada6c6c4be2969ee16899e329a3abe9c905d3a33950886ce66648000e7529f87bb00902824122c93823d72000bb3249114908211587f20f1b91ae4e32e2612006bd256e3d863d815c84126f6ca580a00832d4017d4083783400ecb0aa36c520ef991bb0168a7190288a4d411d59bbba6239019baa5e89a2b4f9ada0c206069d0150f0a80e492b8e99036eae0f21718c219654b352711cb2322208aeca430ee99617c9b541d122400e90aee2c49893414b2740304e7c8ae6cf9dd19c443a44d64a0649c19780a447740f598613c3f9aac587a5127c5948710443fbc0c18981b073666caac6ca93e4a9162236bc08326e68a2139ae340d727d7d59cdc02cf51af014023482e04218fa1882834e9add88127eb442081273c716b8912791781814c1b82bb280098e4857cc8b072f89404a0a36f0b0246601d5265145d5946405640f2c903f210a6dec31c327e250c4d887ca1d449aae644dbd5b6c2a518579b1d60681db28f5761db5c6d5d8a636467bf5b5a3747586e328a486eda3e356c3d671dbc8b8b8343065e77fb18f9650ebe4ff9db41f7f3e9fcf670226242cc7781da6fdb67def6abf787d0f4a77bff70b266d5bf526c4d2b5dd3f937df76d686736a1ff97ba361a2dffdcfe616b90ffdfaeb576059b4f2ab5ce6e2de4e5172b48d383726f4cf52fa79459aa4993e533c0c9cd52dfaeb7f22bff3365c8fe33d1ff4c3313f67f4c5a1e7de1df46db7645a6d9d2ff9ff17f2645260ccf68d4ef2f4f30b0ae305a77070dfc1b148c469f338ee7cf4f73e7bfacdcaf51b867e04ec59a66b431fc33c54cd7af5a985ab06001cc42b7e5b7ea64e009c193998b95eda6c8261bae4ce71f2dedcc69e63c239b691b43b4c375b701dd002cfa7ffb5d79ea63ac6299ba975163084d0355ae6202bb7652fb22d3fbcf24658afe335df8674afc67b2f0cf54e19f89c23f1322f451d1c510c574e814f654c38de9ec0c0b975496940899065108064e600264fa5b46f1eb8b5a66c8d24c5fd6285d77c7c1ea939f63f895b3bbf505b46dfbdb4423ab5b2fa46275d9e8f617f4ccc4c7b4c7a4b7ece1c26d9e3a77dbeed165201ce2ff1fc3f4ffddfe3feac205ecdab9a54af9ff37eb524338ed971a46ddfbc76a67fef686d5cefce94e8730bd4daf4f57371d73cebd84d65e25effdef04f1c183149254ca3f9c6743ad732cc76f3d28cdfe09e91453de3f13de3fd31d53dd3f13dd3fd35c343a210a75a361fff1187ff231feb4c75e5c207461cbffdb7087836b73434b23bced999e1bb3ebbfc6bada0d4bd77680725fec7c597ab05fefd830cbb5f07dcb2a2639e8568547a85b81ac6c4ef11d7c3ebb9b577d370ff1ddbcbc77f3e0decd6b7b376fecdd3cfe6eded4bb794fefe241faff00514e059cf0c0bc8b47e45d3c16781adec5bbbe7b07e5ddbb20efdeb978f7eec3bb77f1bb7716debd93f0ee5d8277efa0ec2ebd6b47e85d3b38efda9579d7cecabb7640deb583f1ff5153585c5507a6a29aae5ddebb7663efda75bd6b07f5aedd1370a3ea96bc5b17e4ddba15efd63dbe5bb7f86e5df5ddbac477eb0cdfade3130075c0caa572e510bd2b57e75d3934efca759193f2ae1c8f77e560bc2bf7e15d39fa5d390aefcaf1bd93950b1df32ed4cbbbd028ef4225df85c2681690facf72a17cef4223bc0be5ef42a7de850278374ed1bb716ede8dabf26edc24d47dca79f759e3dde7e1ff3f78ab080dbc18fc3f8427d8bb101abd0bc1cebb1068bc0b21f15d0870ef42987ab7cdd0bb6d68de6d93f26e5b8b77db0edf6d6b7bb70dc0bb6c8ede6583f32e1b9677d9a6bccb76e45d361fefb245becb16e25d36c777d9e077d904df65ab7b97edf92e9bd9bb6c08de65ab7a97ede95d36a6771f0c7af7419c771fa879f70198771f54f9ffab2e0052039a040c405d3d11b8b2aaa292729bb2acfd6702633abfb85717d7965616d61593d55203531573916a4aca6d461d80325080c000014f01989c98cc7b8eafff4c00989a9898984ccb2826dc25a62566252625e6a4aec894f4cc4890fe99171e396ac4a8eedb978ff65bc454c44cf44474086e691747c734a4c452a1ff6f9ac3520fff991908f1418298769881988098677ffcfcffb28f5c97693f58a6f1e61869b9cb6de5f9cf8667ad5d1d261626072f817b0746cceeefc912d47931ac2bf07480fee9acd035fe7f49b1d4c3e7b3c35ab94ba558eae1952e4e4a67e5ce011a8b1fd19d1b9beb4136e72417e9b1d4c3117251505c289bff2ee8e1ff33dd71771c9f138665685c8277c25d02a4ff27abbb042eae0429ffcf1b5fac2b018884121c8ef3a7e27f59992b81e9bf5337c04510f4fbcb1b9cf323d48b1e42fba2ff4c3e4efb9e63bcbb0bdabddb6d5fc0b5a59d1b01aa53ed59cbe77710eed6d876c27d6a4770da6f9fbe32b75f46e8f5aee797cf53b139afaeeb14ffea1c639b79b78759e04182ffa7da31d68eb165ee1a8db52b97cccebb6495ef92997982df8ef7d62fe0ff52465d165be33576382f8badf1e0b7e35133f465c0d2c16f0fdcd6f75c07bf746e9a0efc47f2569981ce6ed3329537de599a361430c069733b01039c7395cb2f734e21852dcd94b30cc49065f59f68d900ba8cb46c9a4349df71fe7f397816f6ff6b8b65d4daa6e09a3d525b944af93ce79a2dfb2158afb35dffcb1e4ca8ff9fbe5b76f7cfe7f39f09e79ff9e6ff9b2a5d671495ae3b85addc4cc776bc18d33531e3d2de1d03f474d2454e5b6d48327f88f8bd771d84f8988fd17e1b5c07714c37ffcc36ff4c36ffcc35ff4c354c34ffcc33ff4c334c32ffcc31ff4c31ff4cca7f66987f26987fe6977fa6977f66977f26977fe6967fa6967f66967f26967fe6957fa6957f66957f26957fe6947fa6947f66947f26947fe6937fa61326937fe6122b957cd041bf3f0a79817f785c4992356235fe5fda47e817adbb830a0ad6f545e902a1868084d143689f9489e4c8ff47975abc94fc3fdcbb615afeed595bf308361c6f7cb1bcbfd2deab11a45d2afd5f72856dfddf593e067718886060c606bfbdfe674ede2d90f2ff5aef2ec8e12e809f3fb7774ca5e5bec83bccc7f8f359bf554ed3f70477c1d6ffdfb46cff5341e5db7574a67b349ab57b5cdf485c057c76edbc1bbbab803f671af967166112f9670ef9670af96706f9670261fef867faf867f688e2f1cf941cf2cfdc4123434d57c70408102060c0fcca6def3a865f4e73a81d021c4ebba67b34606a7980928be0edda2960c04c6ff90c7572c1dc9ebafab85f6cfedf8673aba973ec7e4dbdf683b858c1dfffcf10cfdaecdbafcfc13ad72b86ebc5bda2dcaeec124d0733c707074d95969c6bc843f867e2b801008cab6cb9f5e46a1162b2f867aef867aaf867a640d1e313374d07ce8470af93df7fa6096a7c77e83fb34457a407ff99246cdaafcb5a98eefb678ef85f4a1061ff2f13b958852e961cb1b0bf485ab0c2fcb8577c5e847be5e45f50055d9df37ab7abce79d96cac2808cfb41c63f7a363ec7e30dc61186a33763faa9d37d3728c341f4d0f62e9daeeb6633e7a30efa6fd18bb1d4d0f3a13a570fc6ea7bceef05676da63db3b8cf7ebe0d67ef9b85391fa4eb7526ed73495db701f9b69ce9d761dbfd9421a90bfdd86659a91dfdaa91d33dabc33f6fb0dd442f91cf3be5877411e8d466b32b2c0779b4afe9d73cf6619e9998f91f53a5bd354b749c2d16d626178a6e502bbf505acf37ab769e0df8ee90201e5e94bb8956398f6ebd61790f60bbc69bf28b8ee8eb1a033fbf66b59b7be807cdbafb7acad769b903ba55d86e3c6b7afcb70dedd6da9fd7cda5c96dfad1c5bbb6cc361bccbef2dd8f8f639e1be1da3c6357ddbf1b5d77f5c4b15abd2ce4469f56fee7c556ec7f21bd3b49543b9131b77ab58187d63ef97bb0370fcbb03e07f27b5c1bb03ccfe9d73ee80a75931d01520b918dbb5f68ebbf5cdf63d62720500fe3fa3147e3b5ea7c68b751794ba02b2fee5abdd6b3bf7098ffba4049eb57415769fcc9eb61ece730394f9d75667b801403078378085e7cf313637c0981be0e99f3fc7f8734c3b7d3e6d9bf0eeb673903bed991eab69bb9a66b4e92b95d75a1a913b6d5b7bbff59bcdbe5c0b376840dbb19d55a4e92bef731ee7f4ed3669ff5fecdd26be7f04fc7e39b6dbf6eb3639709b18bcbded7bdb3b9eed6d57f4b3b77d8ff6dbf63d184afbb94c595ca6c3dff63d679e533ad352ce7867bb22b52ff22a9dc79d4ebbea804be1bc9edd0e6efb9e54b12bdaf1b553632c3dd3d4fe6ebbcda0a6e1ae755ff1a6eb4b8a6cb837c676cf645ec2f1ffcb10201e65b8f46489d1db3d86f3b6ceedfd3aef4429cd3828adc0bbad85eb975191fa4e9d732cc75746be18c7b21c63c7662d9d4a6e5fb01d68e3dbf77c0ef26e7f780a436988939ead796fad7d29d59400ffad737cf3b1bbb1cf91cd4925fe5f4a77d3b2a094db0ce2addc15695950caafd4fbc5521d399154e6c7a475b7bef3db27e5dba95f994768ff4c0f776327abce312ddbd17beef7dbe57edb6f149ee969f4ee36242590224072f0ff3d1092ecadb5ef5b6b5f4730163070fbffadfccfec20e0ad9114b3ec7bdbd8f86e8ae9f04d318edf14a3758afcff126dcf54c204f1cf4c5bbe58f4ff3efe9914f0781e53e9721be197714bb126a3fe6fe66c3b739a91dd345dc7d11e03fe3333d8bddbf57f66d60961143d6365450cf1b5cdca567dd0d760356adae13f738a76f8cf7cffbf8929fd5fd2e945fb85c4ffb7b5711d1bdfd16aafdb2cfca13662618a347f148bf2313b6cc1231f8b6ea7d12bb57ebb1d75c2797be3b596da040960ac69a8cdffc700988652dcf4cc9b18192abce72e0868d3d9c137ed3777db341ef4a6fddefba52bdccac11dd0fe8bc273b7ab7217745eef98912e8fa6c28c7d96e538b18eee6f4e311a95e5ce5821b1822cc763765f8cad334ddbdc86264a15691acf8ea9f30d776ebf807d84ef2df71a4bf31ed7f7dded44a9b402b7e3f7765a9d9d8f8dd5837567744a63fba5ce5a7a9e395d6719bb9fb40287e7d78e6db8a619e578863aefbee5f77dc7772cbf75c7f66bb62f5ae738c6f29793d156f9e54eeec7257443ee8cceb494cf6f97a9bc4fc9ee99a6da371cd7755fa4e9e82a635dbf54b8fac22f2d3be1296ddf73fcd6375de5807d817876b67745e7f3297da1fcced8bf9b7fb8ff332b4441f8dad631ff5f5e5158b6d9c1b3c3ffdfce13a3e74ffaca08a6ce19bbc3637370ddeb7e675ff88d56eb74d5a96ad281c49ff4cdc7f8738c3fe944fe926e329c4349c78cce817f2ed6b5dbae183607c6fff3fbe58abcb1cf4c42e604629863b6b4b4b3d8deda721ee5c8bf7c3334b434c63730afb1c3f3fb50df8c53a9575f1bee331dcfd92fa0ddbb5d3d1306d672fcda6dbb757e07edaef80e763b705b8de32dfcde7317843b1c09dfef2e779b70cebb1969be5bfb47fbbdf02bb7bdb3d8ba99d00e9ebb20616591ffdf46e1b9db6e20fcffa02cbf735bf8ed81808a549acf694b6519cab9b353e7b7c376b75fea365e845f380f10fa762c9f69b8d7f0f5a6a76174dc49df9dd169c37d7387dc2926cb6fccab313f0babe3ae1593da52bef8600d8d6ffb1e77da7695e6d8cec8ed5a4bdb5a3aaedf45be6e7deb973b6b7afee3dbe96be701429dbdee570ecb32dcdfbeb9c3aeadef9e28c5520fa51578358f4b6b7a4cfa7c4acb94b1d6ef754ec0953243f89880290b9a65e09ff1a00c8fce2ff58908e2a1936958850c8c03688564dcfe9f6bc9c7f0f9e7cf0000089b6386f45ee58401a606ad1839a5600a7a39701afc5f827d899162f75e0db02626c13d777b0a15d3d4a484cbfe9b6ac0f1ec4b8d32f6b9e90cf403a5bc847f26dc33ed67569d5c44119dcee26acce5d949cc9e6aed2b757e6b39a721524c5e8dbe986fff4cb7a70f6008d3b7add5fb6746f8673e21fc33db7e7c019c63b9308b1748ceae7d9776ff41ea9be653acb6a6dada2c5fa6b863731b5bbfbc2fd2d3bc0ed37e72743dae07393cbfcfe714abade7c4c8f15b95de5aacb4f745069d449730bb76d6f4fdd6fd8ed70862b2fd331f34b928696ee1d3dc9265aa2d7d34b548fd9a542cfc77199efb1ccb73bc4c73dc36322e6a6fbbce90a567ac2286f86aa580786858483828183801d0af8f6f4f0fefceae8e6e4e0e7ac94103b130060b14244000f675897b755da29a92729b972efdd24296abbff496b0307e1a9d9adc3f33ed7fc9c644fb679e99c173cb67f5af0c0ae7d94e26d998837fb8a6196dfb0584fb4dfb5def1710ab9db36b5bb305990de057ae6daddbaf1cfccac9755d47a56519facf14fb6732553bdf3f334cc08166151819a6445599faffafc1b433e71919b52f229902e65f9e92776bb1b5136bbf8cd4b74af694c2e5a5d16f4a5193d2f4fc19a50d7fd278fe8c62e1f9334a187f22e24f2852f8134a877ffe64f04d2880cf9f50b6fef9f3091ffe7c32e49f3f6ffe6c3e697bd2c49f4ecaf0e7fb4d27d2ff7732c69f4db4d8cd2625f8b3c2379b2460428889947ffe6c32195cd26109dca1f2d0dd9b298c5f260528e2944172aa190ae63028e38026a6ed90c6a3684c87665d143d22b2d06884057364411f8ba7aa568f2c69dc5caa0096f796a752cf06fb4d95561921cd1a544f02c04e8c00ea62db66924a425053cdc41d20ba58992288048c2d3e145279539e4c69ac591386451d2054aa7cf4d0b681d142eb88562bdb565128dd2d0f8feacc09c9336cd1e04ad81d3010b624c52962402030248516630c28b44a2e564e2a9575c2b88da87c8c16c9c0a5eeeaa2a034c267077547b31216218333390a714dde764f1a3be040301a11168014a3c1ea8a981a4a472c2e124512ebf898d166ea56aa020561d29dde1a434b366c6d179f744dbdec6101a96e4e800424264cd8954352754ca2fa462221b43d81e54522aeeec5a50592c591b5c4d142d38c90934995577c2e702d52424581324f0885c2476576a402f1e1c365111d9ee493105e9d8b144cbce8f0989ba460a17fc14ece9320472858907d8972480dad691b764cb13144e9620a86a3c0a7cf95d2c8529f22730ab0d028c0333d806f435e3cbad4f585664202b52cac4018b028c511ca6249068e0d8020149aca43a2c494183327d2d0dc2a32842625887222cb7566b8a7304673ea68449482b2a8dc4dc58034330035e8e2a949d69c28aa77cd629d4a5a0664a8039564819724aea905f565035c8307a325cb3e0958c4d7bab0e169cc93454cc2c30d2464f4843d559f33491e9435953567aa14d080f837433068e244b202e5901f7642208ee8f05b4e40080da68131208707ecd4e8c77f9562144473167464d0e00410400bc0ffe62130886820f754508a8c6bbae3feb798543abaa26cdc43d6a4862495ffaf6ad4e8058fa4f40916b1bb26948992d8b2b0fc3c223308c182e4ff7351c51275b1f0238b91298d5eab0f2418e1d0e3aa824d6ae0c43dc3a641158f255d1fa62522ada4cc19c65a30531a391ddc4868b65c0f94d8d623f7d661fa3e3820c10250579a1b82e03c3235c2f1137715640f989d181cd22322a4a43c790260ae6a82a5c94bc5a1124f4a12048a91844384209922d09c361dcef048f2f3e6ebb108833f655eae07b312587132f0b11462f9276eea08a328684c6a535034180b6346c2c86a9cdbc6c9e1849331c3e4d173d4210a111aa73e372eb2f854ea3904489584abf1498ab8433ed2166f5a5bb0563f4a35431170249b5db66c783a6c29365aa05665c54b57a4198e3e7159307ae02d027225a39ccb4cb77a13c24d83a18457dc63fdd4ffffffffffffbf6ba42a451779328b80c3487b6a2168f0b022a453c6e8ee8d213249d8742c816019c3e54030422244338236dc0020e2c301e7a31b610ac0221cce8405df6367c88384aa1213c7a9cd8ab03b6881b600fb6ed099b0f5f88767624993aa4b3ea8f0cb64d39b6696a8283c70c050144c5797151e5ebcaaa05c888cb86013e85114204114c7ac31d052d3e54ac6127cb44058443959e192a139bd033b0d78b2420c892d86a6b54824a8aacc780ba062aa484320432f22e618016935802c9097ad92246bcf9127416010ea8992b482c1113fdabeec19be1625b69e20a18270b6a486d49534b3bcf34410823d58592436705d926295f772863ee0c93977a260205993850d940a49b145305d8404284e4d9143820eb3e6ec29ad0d63102104c060019a20b63705cc15bd3831312201cf48893a439b826670eb81df15b292239a87b4a9370ed7393104737a98391ee164f9ea80c577474e70913c2507041d1d284381ee78495ab98010dd6535135449b9457e184a7981525423632cab0745059941458244b97ae23a7d99142545227c06e7270620dca5a7b004a32310fabef2b642f2f90352aa6f914a6f52481fe2c86e9b20419033579c8d8d1c866a2448a5c1b1c292a5f9d025f6e481eec8971570fe0c17de96ae871b0708e0184237b677df614ddfde183f5691503f2a09e16248430b0f273c3aaeece0ba5dbfb72693421b1089c1a30106ebe45e5164fd3838d0862e8434137d6c4a60c544d2e7f5848d1814635c71512a0020dbea8288c7ea03af4aa1470922fb9c81696c4aba1a8b0363407b535994c342c21681aa461d6c808f22d7a80934d5220d6a0fd456d5972f0ad6b4014650119f705873434d7345841b6cd03841134b574d7a0f0f2a55a2311f0034bb60f154d24145868e092b5af31bd88842b53cc4370037e0d84c3a6826f0b3f6a2119e236fc020f010806eef06d5ecc505d2a2c3387d5126df3caa0b1eb2c4086912c5acec000f0533b6372840018bc2f29426700357ec25ca86c9e5d512375385503459b97e7859eb73c17079c18e5e5658a12f1dfa18683477e109a12d5062d48942847123cb226c8a9ed65993482e5791293a12173e268afee0e061893f2a044285850212da9d3b497880180349646dcc8f829721471266bcb96182cf152ea90f8c2f22a946887fb0f8d9f2c0e9086decec448e07578a94bc48243380a0d2044b94b7427420bcca2c3866e1a9e043448aa41bb8c6f8210559a30c20f61049203af2a5b03791c455e24b804b2091f343b0049ca2ad13405d77dcdf5b11392dfaae18fc925c8181493d0d391b7182d2469a572e03194d55683566cd1b2032c72d21b62f0c348e916384ebca0833d3b63819facc7894a6b2528b4257c61d974825f1a76c83243d232e38565ab935016f2b319e4917a6dc60e0080b051701aa1f2d0685407c3eb82e132dda61c85d5068c49c34b4285ea6cb255bdb5ca32a254300b5b874a93f71f40015e134a868cb021b30281c2e49008bf3d5c2cf9d2067930a532326072e91d0867132a98c90a2c4aab0108bba5e5c8a0521f4803009909391169320657eda2ecd0d791a40e9a8e7b9a4f927a4cea13d3b4ee40170bac267064e42e5a3851b453a24e4397678b2e45f91a35560510f0c1b2278ae951c12945f6842d41f3186acd0e59921e00708469d872148f565058322a185383fb01e54a6e2a658b3303810e1d4a009e06b64ef5a03f0cf96a80f15d210c851e2be1138f0155d131967aa47a24b84d458a82ad3b7265094306b07b0c820e1000840932d0eefdd0f05539881515e7d071231b1488406471930335ef0b8a71870c14274b6a139109720bd359a50836506e80a284664a036f025477c9593755e906020c0a8344a90b741c2223c050c0ba590acb568e424ce832386fe40920340917155838b231bec4008440c3c1c9110f02342979634270704bde8f417bd3bac8c013da9360e6842110eaf3ec7af440d0e25fcae9cc2e0497a8e818440b00659ee609876056002f9068554a2030b8a3ca1dfd04972bda05911040aa145228d0d96743f28b6ac16996a4058080cc1cab5c42c52b23d5c8221fcf840aad95ec9707952d606c1823b88b6c4ef4c8f417938202e790381404e552009893d3eec058973c78c2bc51e4269b67cb0582300662732b2905202445b809a9a7e207858d0884c1685078933aadc265355344c9e6c93373c88ba30a23447468c39ad4753e294c16300718c912237f7f109a3e9d8268c177832b48265d551a8f891832a8e09b4b00303b046bc12e10c6530e045eb6fd00626ae1901547fd45614313041add95c5456b6a745c111251de234c00bdc4255484b9ad4a5a40c6b8bc7815b8e3543d624814853fc5878dea18a110c6c2efba28c90789c2a8bf4838c8ec436527a9269a62ae30a58112019b024a251de15575e535601a625774e386a6e6050dfb890e0465dd4dd873f0750e28c310447088facca3c05a610a77a1cfd887c2bb2428d8937450d3f2cbaca0d277d4efaa8d0440c9a72284b0f2f32453ef078bfa4143830a80e514c7242ea8e5e8b1f749cd06963d1282936d707e5ea0dd70328293d52764d0a29d3607c25f02ac007440b1a72685c49c71ce3b66258a92bd49ae7a5123b0eb86995b9ee4de17325e78e10930503183604c0b27cb83024b12b7046d863595d23093383e8a9a3c0db5da463db970385e2209e260a72c45964c24ac9082b1e4d86ec1a5cdd39040030abc88543480e1376626f9cecb01204e6478a11ae07405cb464e42c48b367566465c19b365660d3455270699488d8d24ba268a682724bd0a42470a042d009424dac504769c9d375ebf4fd112025e08fa52c4402570c7da99a018387948c6199b792e421bf231816b7deb662a45822cbe20829c001d8e117a96610b94b4a079d19932b145069c4a861ac2a2a80021a235e5c99b12489181337888fb4603c19b234a46db2928e2c465e2794689b6e226244c510c85d1fd2c0790105491e158a2092a02c92ef1620100125e507e29c2c439153602577878726ab815533d9c0283759b61d4363336290c9b2a8435c358d6ac01b554924ca150d70491049013968c9e28b44586050c1a41ca80a841ae11d4aa2b25a6e18389991e2092f924d066ecf99a914606332f0b8420fa8c096060b140e2e58491c40420d181b534611002f855ccc298a32a0c11906a682252d567337a63460609c8a8a1389c9234596b25c531ee2d8a8a3c2a28a6af47a72084b8104883d00bf9282178541190c7c281e6141961dc067492091148dbb0e1c3008f8b68f55a7331d84305e6d7041e5f768d26bc61c0e253e7035835d3a1ed8e8f1640603c835510c3d8800202205a6870359b2a111890f0d3d8010f171c26a908d2168b8ec54c9e1a380e26faa80cce5c78320b30f218ec83b199c60710e90709544b1c98386d0831c1441acb80943e4899505aa2739730df44c7046ca0420c000e34acbda8b62d3c8461f473512500122357025209a0585d87640234bdb5cbd4ca2528586d009200a66e461badb0af8e0e849640bc49f237e73a5387458867b7b9a88116caec9969900e08290312c2a59cc80896daaee0a5cea7650265c492b9396c7479ba705278d04381a543f631db2c46d25bab8d1b47e6040c20b8e9e9386b144938a061e545ba64bae7fa008acf112034395589609adb32a895db89a1561b454bcdc68f23a88ba44955583d187486e446050c903b855850a4ece1b97c14714146c2205aaf10381a0ac1591912257140032534282dfc0189fa81a5937241d86f9d3df6ec07578723b3095e595836428521e38154c283e604f0aeb108417511a48415c6126d76248813a6fa2d640500a746cc206c98ed80d014f03844cc9f19964c592027844e5644946cf002096a08e67dc1d2901ee395f4cb8adc81f5e434450aab10a2220b01948ce8c0f2766a4c408e6f88024cc17367b6762b48c98f057558684d843611b058fcb929b99903e715d825c04d93abb5290a500928c9b1a3b9152813784a8d191c4cacc70dee1c4e420eb8a815c0b304e14e850c2ead98501d1284fd799d4033612896044288103ce165e84e8da995cea71e2881290b56980bd3b575756320c9915a66a55ce7ca062a2abd20e179706425c143db708e2822076504d1103888bb523bd81a7a04d29fcaac2adea90b07a4a03a9efed0f07ab91226280b8da04da2203e08154c51b85ca80e164aa400f41d3d21e28a5430b734a58da4274c5d0afb06a030e57c37cbb8a6f3fc6081df91ab4d5356795a5d7b4723260a08d011c0cd5a68b4086b2b31e122a65d8dcd51fb90e548f7e602860a20e22252a688277bea4b8965645081693834a3400c202a8329021a0a658631d878c13437d1918278c98509988caae641109d7430c8f19370afef8e13b9295a64cdaeda7e70471a947bf77eec089e2c241324c8921255124cf88c8e160d09fa9455e25d0ea34b0d0c7049341d3a29f3c59657f3048807133ea1a24a7e40b0cc91a5189887a58ad88b97a2128008986473ed229ad4c666964d205290cca646559e06ac47211a612972a490faa6759b8e4a204955e8fb695d07048e972098b139098324304a4244dc249e0b1c89143659d0990571885b98a64d887e64416d1ab0c0c2075c83244f2623bf235872d405bb34c4133d8788651ea4319b53109187c917cd3e60b06190f03aea81ca0948e9c451160408c58244b6f5fd763d4d500674302321322dc8d909124a50c54515461f5abbce47e0923e367a5c94a4d03bbec16b01458de2cd696c01401707db9e11448a4586648c5e0812b460080716fc4019eaaf4546a743568a2f2c41e1c5672737863b66e5ca92fc4937c64384070b96db228f9e03279c65193551402930fd78197c83a7e102797885c56dc00d1c3ae0aca9327545144564b25696872f009385b30158c4214425b5cb624585a53e9c88a26962b8d1a992c6a116982904b7376526cab9cd8baf28e9245c620cd4b53956533b4a83ca1493103070c2a163236c82150e5b13958e7218a9d2d74546cb11cc0bb2374521129d4a3a0b216540287ec45166283ad96d4a48d247c50807240b53dc273486b1545680fcf111626264cc3f6d8201483481915830b70e02c520442a5c996072155a41cb3323c6071c0cc8e8558744f8f261138885123b860c51bc6b0b6730cd5ed0d60d03635a4ded1a54e142f3881812a739459e284b586c59d133709fe9cb1fee0ab9350e72e0a418d146a00b045e57930498cdc09e29b1ae38008805d08375871902ac8c0e273078b6d0721271a0b06445b3ca835485eb0f21241e2cf135c598a47da1007b24a68734835b911f50575a46411a94d6463e47db8920c1cdb908db1e04b108d8332546eece9a2ac46d151800a4d640817475902c9cd917a8b43e3128691883102b672d81289117355205282bd4d24c40d051cc02f86dc22741d70f207783837092039033f8ac9e8b962d22c71c1463746485fdca5ac1e5e50568f74751c7da1aa3481b780cba317640d578542aa7bb1c22390eb93153572008e7011a9cc5cc54422fbf366d87266e060d5a10a715c28316e5f0866de5c997e026d19418b5a542586e19412d03e3926491f4d4197f46810be15b2691496264f8eae5ad5b370c1614831594291dd54c1859b0e5a979c7c5600287b63a4ecc0979af1c49d2607483f385ddefc8ca914cfb19e805286ea8e851e2b181990425af11208c5c21cb40cd3c8a02d899292c341a3aa1aca2b3a9a551d2e5c4252c49e46d84ae7b816415059e08b596c10463dc7184a11e4d326415693301874074183798f85825ced08188b4a12c9c217252eee2386a64a2c4f7cb13235e75193475b519a71e21a459685181d64b98d2419ede153f9e7e5a73e5cdb348ada8b13a206140d20919c0e05d47810913575014d9f2c8103c2cc137814803c7ea4588b20ee942d50b1cdeb739d0a40210ac9ab4d824a4988ccdee0b8c0054b80d92347527c663859a8ec1ab1a50a843f20586c48baaa63250c230241c2ea06017cb07042a57851398894088a0593154e4ca4a823ad5920858df0f88008169a1a666e1e85a183e0f00aef44053a41e91c074802da296cc69c5d142615515ddb06a316fc04c3ee1e4170214d5af5bd08d4aad450b228d298a4105650de70f129f25250072eb3478d101470fd48c1e7fa270cb3f304906b8577b33b020ed496483a2f07b5aa3350424240acc256b8191a496def2cb73ca831623db3a568c90ca8faf0b0700bb5875d15748b6b85c4521c8091ed419b9d3a453381b0aa72d8c0c294470880290c5525982dd4e856dd121b42ab881d9d94397c8d19acb0358d703e750b3d45418b4c1c207347d5a38a2a10178bb51c4e7d94280a62b99a4340eb06155ec4d484b7636b44d8911d4d334d689797551e3d47a819d8c491412872aa163d481d39150eadf8e1a109f3a30e5056a32c513100153907bd7398a6a8309c46c5998121317046a22057981e4148853aa80e54a26324e44d4d081f3b0b9023b92e4b90928c3335053ca8a9c3228f0fb500855137649d3b6042d42068b02de4f421644cb5c841583868c217c54657d7bb0e40aa6041692a321964c103308a2e7c4094a26910d5e422c8c89f377adc0c353419e978d030559506a4308f193964b0886edcb0e111074b4bcb8a491bd0091bf094ecede19df8f39a2a6162a8450d903a72e85ce8ca9adc23950309bd080744802243c45059936612ea4a441681c1153ec4c408a4244622c49b665710e90a31267c552409a99216a85aa0528a01a5433907e8468f43dc7c863b944e568926d1512ff614cd6133206e852195005c861cf5da48e93ae4224c99344b0a0d31e13e29307b59244558c50a1e26ad66a2d06afa7762b10c8e830693574c0496468c703df760a5aa9c596b64d8c564864b550c97ee4c823142699e29853517690dbfaeaacae511360c53b2184c1ea82165e101640bc5872b117556e99219527f500e9d806abe39fb13dc93e2982dc9cd358450f2aaf82af908eb6aeb72024b802abb4108e668e17923a048011846586c89397080105ad39417306034c471e805d69ab05a9d305185295b424806626e04e962d016288f26adb649398a34f2d16342e75190252e04d4359972c89d40b1024992315994ce497564a8e842bc2073d6142a58e1e1f8a462059715034814488e50881419110347cc358781a81c245c4ce2cb2b9c61e40203c83cd3b0292f8118a97855987c93b1b62411910a132c4708dc0c118236836f52172e274ac4a251102abb892b071d915c888a220d61cd99c2dbb33488cb8e116a0994d06b62928158142e580ad2e4ceba858c0e0ae6fd64f552e2080ba0140bba642b78a44b734007a933361e28723b20066a501bbe1a8282445e7189f382848c5495c02062a25a3aa05a97a2348de440c229c200504314251b7e40a50cb9d418b242088643177d8167fe1610b9226b12e732994067080a1e211ef42012ec82c0a3ac2ef491d100a6a1665a61f2eaee713200eac61fa49c0d773ed4d9ee6db1c2c1a3800b39048913ba665061bcd1b1840b84983f286b26d58054dfdc1c0d8c4874eb44356be68c48fe89faa92b83674bb2826a6467d74686892fb63b6f2450888cb4b586c90c3826ac388845bc3173e06c8dcd654aa2e34f21d58daf4541f0886dd8325715e2c6081e0e6e2c05652369a4d9c9f0624b01976bdea2072ebd80132c719506203567d4f04026a889c58101224780445056a1aac421cf168e11b593a8905a0f214f415a8f77e2bcb942254a2592ab07321304fc683a09005a500983891fb2e5042f374800aaed0a3d662920dcd921050aa4fae6af44d61b4536ac24c133241124104a8b0a0418a2c03c927c5248c503276116e5b51d3e009b82f4a4c49e2ba2106f24445a23e63923590ff40cda9a1b03e2d828835d05b4911135ed5b9388c8a43f328ab9181e9c12d89972cc7387840aab460d1b5c46a0ff7fb08686176d347b3edd1888dc1816dc187318dc3409424334a6820338cbb4aba241517f9024aafd51f0ff7e84fc18319461c07c1f457d1089399dce30feff84ffccffaf0b046c5b1314e463a7d77f6a0fb4f64dcbf64dcb827cd4f6f08866b3eff309e16c6d0dadb6a3d9bf3d4f76edbce3691bda59d4d6eae9f0ff01a47ac4fe9d0aac737cbda375cfb6a19d113e51c913fdffe793d774350f8d8f61bb7646cbec379ab55d178f92b76b271d67f967c272e5dfeff5abce3159b5abbf7b27cdff770e79ca2c1231885292c3a4062fb7c93b8dc322659f310822c538c83d27cc1fe250e160a081169f2616911bde0ceed94a53cbd9d0a3c89f5e5c053d1bb075f68afc0ba60d6c7cdc4abc16c706a6ad4a106a3c5202790606a505b1147c9034189d947cbd0d80a4ba50d4923523026419e064353211a4c748060aa5d88cc23c7a1960044c97066d8f914f2614d10c4a7a441422677745c2d16302116774383a4535205224b064521b9f062a747740385061a74104931fa689b7351a00d10c5a7bce98a321cb4fddd723c40b8e7d4434698ddd71705cc2f6e01181dc1d35a038216d3b423bda0988d081cc308563539252f5c464764b472dcc0046c7be6d90c81d115c50be6d6b037c654324cfb66e7aa438dc691fb601b4e4ca5870b46c5cf3b896d5bb606dccf1a244932266401aad449e62838ba434a215a2fc8ae21289d1525e9cda1f1d1618299f35ba10daa084f1c5740ac715b4058c51a641b24920b28c6168165194e1a0b0b8288e831e2920a916b36cb983c1f1adb268c2dac710a55fd9e2a060d7a1245c8a452eb7e1089fa54aad20a64a7aa6c624d23a4163c98ff1498bb643904ec005d6aad5f3d1e60e6012025aaabf3a1cec2c6e779e024bd0b85ca2de46ee3c08524343c7ad5d7386f076bcbaa1da0c751c514edd040e6dc9300c32c2ae3cdc63641bd864f3c003052ccc9dc6966d5c69aa073dd80ff498a5a9b068c666faa9b01fa3046a337a1a06493a3352cf98c59880c1c8443dcd1280c79ca3363599a5fac68e4482191e59e25285a5824e862e686a761cdfecc9b0020a954a2d1015331cba283b834e1566403044c196afd3cc1eb2e91b6a300265a7882da0ea5264301b820fa2c351622e0b98c5ea62a503a5050e341f1c32cc60abcad04a20eb322470df68c7e9d313271030e83aad6703451c8b74941e1886ac408ecd739abe2025bd77662038fecc98e481c39c412ff00e256b8d394291ae34b29ac5ecf4b584cb092e05b06ab2581c8355555cecf183ad12312246ee6f1016edabf079c34620576222888424854d0e0346761149c5465b183572a293c42e2b0a7c901974d9f080c0db9ae22226990b34dc40118b6209420e67688a5514051969300a2b3453c40068d0b24d848a01189a1c522363fbf18481431f8e87e86463c8c330152391a32c2c10e319447195a4ab8c8bf03b06cc3c13c5f79fab376fa614e23829782627fd92c403b6581d8fe82db9bab18a9874c50d852b0f2f1d534b747857c5ebdaa0a7b13130de5c9725d8183e314a59cb407525129260905546062614087199b0be8c562441a0d7c63a4605b20f00ba115547936608c290a3adca657ac750152591aa2a305ce213f5c0a26a58a20c1426670a54c1d854426241e0ad1bf2c2426143e3dba56075aab348803a88bb10518cc210ea7e70d8f80dda53fdea8816ce1c8b3978cf9547781e14780027918158b582a710ded64d059a356984e9e0a8b1b54762d71dd438780a0fe5e22c517f3469b4bb6145a36659bc504c22620bf5c921b0ae4f0f3454006843d8040102324d33bd01d675a9cfb79976277d6a847822d3b37712259b474dd40abc609e73632c2c54be2d28d7ae48806c7c2d307a177069a380a6d445aa686d3104e20ca6f48a9484dfa493048325d2402085287604329272b931ac2313a550f44f9d3b8a02a289a24dacfa1c7a366110dd90c2c755e2a03a8a97fdb0e563a542f4c21d3cc2aa3f861c2e10712ad238c52d73e1064048269d537921ee820049ac939ed74d6cd3c1b0ed49963651b23a499688c44b2286373c7831f86013d50c139f4c76cd9ebdb530e64e962037b08285181cf0aa40e73c5a78d124639cc933b3e0a0af8223260b80a0f0b70a5b70e8097af133a3420ab2b9291106a4a85058018c022139530015c874508842a5c5f6986003850f6814fa2858f520e4654a5202a21e4424888d88735b10a1d60ee11cfa38a40f21281e361d324c93c2b0061e43b883a159a1a4c22f7a66776060582a6c5158c12d5450c6d2db1a1c28c2445932b003290984550847041ea223bdea413861cea2aa2676800807fdd0b99a2331b230c824ef8802185d20072107efae289e40e05330e85211901826db2558c791760aa7095550c89fa05d40100e490a248ade23824f0902d97005ee088c03d5027f8218c70930282748b1131a5458420140a801b011a016c013514042c0af1a7f83fea054fd79f8e3fbf3d3e33759c08ff1cd4f0ef633d0f7a6af47dfdcd7f6c7144842a4467d4c754ca2fab6e043c3a50592c5a1c547838fca57b61769cf8bd45e24913d0ad0bd0467f410e951d113a137410beacb06d8865e5e1f3b6f8985bcc73cbb3c3e3c3a5bbc202f5efd07ef0d085e80bb35773aeeae7beed2ec2cd951dabdb0b28bda8909aa6354a7a5ce04b36e70ad8ea3683016c66c412788ee095d07ba3dbab93c7346e616e7a07305e42ae57ac861b360f4f87f3936bc4540ff2fc7948cf2ff879a31d3a133de848052dd345067880e719030c4752911c7222e0e312e8104446fe1b575553bdf53ed237a3c9fe6b5bc92978cfc0e9f7de9037edc9b3dffa8ff3ff0506fe00b3c811ff002fee903bcd303f8a6677ae62fff9957662153c033992e2d252d8f00f9a5641a6ed74ef8ed35f300012673d223178dfcda19cd201abaff97a249f0689cce40faffecf63ed3e6cc182e67acfcbf187f8ef1e758987ba6c699c819ab95692ae7f7cb67bab65f2c966eec576ea5eb9996c5aa734c56e680d734f77bfdf84d5bc338b7fb4d6b33f06d57dc764531bb333333ad053233333333ad8530ebfcd65230c8c431e9534aed18a6349fdffacddafdd5ddf8179bc3b6f55bbf576ad48e9d55b943396591011c83c64a957f262aff4c53a4fc3f5a9ad6ccf9ff3ff64eef8a91ff69904b7948ed0cadc375bfe737aeb1dab9675f2c639fef78d6fe7f807729afcf29f99efad6ef954a3926a58caabbb5df731ce6921bc69154ba7debfae52fd3927f2625ffdfe486098bf23f05c6fe483005defacb09bb765eef375bb6bded407ebddf2c671af27cfe33094150df45f902d0c5f5f2d80590cba504ed6e59e434bb7232c08fd0dc6360639eb359fa03449454970875210eaa96ac03a3c33ec0ab1c2f1d5f405c0903a004de19412d1a9ac87184b5a7e6c199dc840d4060314d86980648536e422ea840c4744216e0c042e35799a42833117de01071cc56da80299166a5a0c4fae3c6a39205324849859afaaa9179e2a6a454f283f3ae589a1a3ef81168c0c347bda80407f069a96b35420271bfb151cd1a315dd4a4c58c27078cb4ac194cd1e08daaed4fccc2ddc8a630d10a55444015382a129c427b22a5449a7c02eb2f1e53285a6215aadafe2ca1403809025c43d364484525099da4402b1c9ab62af605090f269b53056928a184d024486334e273e90222ad346e1ee2e26da04b7fe8501b3264b2aae2ccac552a610c751b88298ab8f5d0d02cb348c420ca1d8904e5c9f33658c94fcc96062fb7c93b6dcb08d3725215279fbb8ab2cf180491617bac30ad946264e16ae03e27cc1fe250f913736304b5c825bd125c0db4f834b188e43004d21b00763a9d01abc13d5b69ea3897a848279b9608c2f1c62df2a71757214f8dd70f22a62d2520b75c75f68afc0b260b2602583b701888b1c28a8f5b89d7de0082e3290fdfe39b2270a4b62a41a8f12849142501b27ccc700f4975c1a0b42096820f11ebc72339c1a02baf2b4627255f6f02f0e401fc8288ab041e2c29178a5ab26644c8ea93c3b6488f8d1f472fc0c96a6422480daac41675d9f17ad300cb40a1149b119812cd2a617d3670b8333835c008982e0dda64399478141a55beb9a827138a680623214a629635b61d947d802544ceee8a84a3be3e585d1c2d18f173654ac4191d8e4e177e44b94313a3ead0a031518a04964c6adb8312c9494291c62c87dc64d1dd01e140455da215d95e47c5253f60544c7e9826ded4ac20a0a24befcda5417e516806ad3d67c88123e06d899b6d0d1304b2fcd47d3d42bcb0405bf6e15058a3212e958826adb1bb0d221aa4b866117c34654893edc12302b9b311bef11a0722bd825a284e48db8ecc580c51aa8561b1607a550988d081cc1005194510ba08de01b9c1d794a4543d3149575678e3037288c7b72db53003181df94e384003c5612a0099b32a72470417946f110a25c095890270058b6b80af6c88a41925ba4b3d5b400d4c634b8f14873bcdc30b1e347af0e5f90b40c14a4bae8c05474bb93f47fc7cfca9653cf7712dab77c17a9d62586f4d092b94e47a51a24911332077852c5b92b6c849906645e42936b8284a645e574279632a5017b710e55714974871cf285a241fa3aa30bbf2e2d4fee8a850e3510aab75ec4f8b1c57cf1a5d086d488224ea4304210484ee0d52d3291c57d01628c0fad35108024124ecc53448360944364205263210a959d381c7159a4514653810c55485356a1132c74696541c073d52404e49da1911e647e4e151796db983c1f1a562a3d28f0c36b2da38302c6b1f43947e4e3d308815540bbca4f94d09761d4ac2a1000418c331063e6091214a6ec3113e4b958236893e06fd2261451653253d536310891001643ef2e0a92b994263c98ff1490b06351314185ec0762d880ad209b8c056abc81132c15d8288cfa02c1f6dee00261900a60110bf107f7fda4c52fdd5e1606789a0a64a949ac9f6aa53d82cb0048dcb252a54d917466858f02d71b5ee3c085243430724961b953e4ca6fc4050ad3943783b5ec153663142ac5804f2a0a9cd50c711a51488390acd07702e086aa175684b866110113ad6883d49a0022ef2a1ea708f916d60130728125eb8de0481319b78a08085b9d374201062259606012db76c5c69aa073bf4e0d148fba7d26090b84a8f599a0a8b66fcfe3c6a31029e9332e8faa9b01fa303808c880ad10105c78b452e3d0d83249d15e991ad51e02c0199942b56b31813301895f0b0e8880f0a200cc15872b304e031e788cd0d31c3277a98933bceb82cd53776240a6c31278b8938071e4b50ac2c71a9c2523147889e4b3cbef6b2fa052934353b8e6ff4107a52a3cf891b15579f2e28542ab540549c22e4425568cc908c08572ecacea0538444eca8d93134544047496a88822d5fa739b8a44b1f77922b8d182f9bbea1060310890f6c9eb8f0943959c4125b40d5a588e08e1b749c36f02972e4658a0fa2c35142ce4f559e0e32592e51a6a82cc2349010d074353efbd02aae7c4c585dac74a0b4b061c6022f3df8a8ec493553386498c1569199f4627a22a008174442a220eb322470dd4c30891379105eb58cc89a3e3d710201e300883962986050b501545acf068a38166b347b02a8015213e311a50786212b900e581478b2237144e4979f56d317a4a4f7cede5d094b94067458cba3c2f167c6240f1c68194b434c8f6fc0a3a917788792b5860244864766ad34c23ead225d6964f50a1f590af4e47de932b4a7ea6b0997135c083c4112e24980347d50006a3a59b116e10ced05458ec41d7226b851d91aacaae2628f9f00759431ca447a90d4a58a881131727f832034493256ca59c688ddafc2e70d1b0c065393172ede926e908b89201292143641085e13a0c4b92139c24a2d501c98485fe8903111f2c5518f9f356e24151b6d61d43c26aa300503e513115a2e27895d5614f8f07e18ab60c7c0a10886acba6c7840e06d2deb8c893d570414884121c5247381861b688033ffd9d11366015d18ec54e2e05424aa45c98637571ff2865c1172384353acc2c10ce225d771d9668414230d4661856535324c9581a25c2eaef152b0654410447c614e0c49e9c5b99108f06ad0b24d840a01541a1c1ea0a71eb718903aa446c6f62300e5179e028775cc6c64bdfa703c44a73a0e9a327856389d34f0c30a81c5c65191c55490b70e19b0382a0137152391a32c2c1015c2d253b8e440122d6a10c55592aed2ce41e3f1ca80f95991c5ef1830f3442485d282aa1c3c6c6ff4a6fe5cbd7933a540264384175587b1c9085152f04c4efa213961274d9f226510991093b5c5ea78444f899d333f373a0d598b5054561193aeb8a1b047e14725a61848bcc0697ae9985aa2c373c3035109076316263c5a7d0c5ba5a701b2383665d22c349cd2a8063d8d8d81f1c6d07a00310b0d4f93a3660936864f8cee64607d04090bac1c2db6a0ba12094920e808609447bd0d2ca61faa3230a14088abc4892091cc22d0a89fac3519ad4882408f4d0f2f322d6547c2221f6d5181ec03804e449a333d66b0a80c38c952356986200c39d8e26cf9f076a44886c4ee4eef18aaa2249286a59aa50425282ce9e2c270894fd4030b076b5f3224e5347052c9aa4419284cce14008bfc63a4499e2a5d2ab96c2a21b120e006c82664c28213190fe02e2f2c143634be36a12343600c2238f1bcc2ea546791feb04074a2d0dce2873693a4ee4244310a43c85ee0e6048e0b68a2c474c161e337684b0dca4058e4c5183e7f865c1dd1c299633107b6a54938f714c58b8ad49547781e14587b62853699d8281f0c2f1988552b78080f11b8e187018f862309acbaa940b3268c8ae824190c496e27ed98629bbb1771015321458a0a0afa9c533f51636b8fc4ae3a92f8f0f1c8f5e6f0af0b0b4fe1a15c5ca53e6b768c5142a165c38ca9268d7637ac672186703034a3cda0b9512c5e282611b1e54121b4c90559de5490cbe21058d7a7c7192c7813a432a0297290830a6d089b204040ba9310392d8d5698d08b6a24183dc44300aef1badab84080c7931658d7a53ecfc609887e3730736fc45139e95323c413495c13d46570911c9599666208c7344f02d24b15348c624b2303b428d93c6aa246801236d25ea2396b7618ab9e73632c2c545251905556a0f2b224529372ed8a04c8c6bf1e289a7a18d3ac906ed9269c64262d6292c3c85f9a2c4eac96555cda28a0297169a477c83650159b5cba6b8b2110673013dda4b03040ca57a1950bccf0199628c7f4905da4225ad60c0958f94d3a493058a2a56887132c2df18e9b0ba410c58e4046551598a0f063b181cf8ccb8d611d99288410b98852a7c7c16cfbc7aace1d450151445ab4510c3c2e214ef292b2ea73e8d984c127f631f4e7928632beae143eae120765b05156b957800ca4bd2357d90f5b3e5628e4c962a747d60799004329156b4e401949b22ef5f1d3f2e104f68e171e61d51fc30d2d6acc7cac6c737a2e613915699ce2921172441160a00b1c4f684c01202493cea9f4c252c1d6ca553d3a12d54497a0efd1081f8d3cb10637ea5c24c292c43ae979dda90573b45190e8091ea63a18b63dc9bae90cd2e3c5c08b1450b5a8ea245922922e7b0e01c2f186c8db3fca7ac38317838fbcc34f9c244543de1b09a62cf539d9012cf433c14c8c1585b2f4808822935db367374a8166a0ab0df19e3cc8ba9325c80da4a0e5fae40988424889422a0e7855a0733498e19193ec1e84b06e134d32c699bcb2377d7162c4fe109cd06a855003db33840f059233504336ec983354c564011014fee686020e812996faa049d4c4a127e8c5cf0caa0f293718640615d8b245363725c200146b748820a372c78e239aaa1338609445f001d587cd90b2c9d55482fb04397748e9430d1849caa9d111adbe25670aa002590e104eb648210086c4859b159516db63820dcc474be913201443cc5c46bc48665c67372099f1831d51c91770a110d2088bf22fcae8d1879e0c8535de58f520e4654a5202074763b69e3068d420e502ba1217c12a538d3e553d0a044349a70b5a766091c5912c8d02895044f971bfb852530004529d68019e01142ac740446aa52c081a4416a665909f3949f3ca03030eba26351bf1a06a933ef8ecfdc5408047774529078ca45a03045000dd2414f920c926c5464f5b07b12a7f5f0a50ba54e54693a8d79498d1cb7de19b35383ba4485d96b0deafc85fc3e003450c2c512449fbd098ea7362a7251a0ae4c64806ccfba74b2a0a4eac119725280a898944abf943e8ea0056a503877200baa3870e173e10052617f14d8db1175815e20048c98decb51211171f513c1a49914a5015d4f28858a851cb0d285938ac204a79be059a79d1856ac5474240a56776070646a5396f885ced104893f4a1f9b460c79a283255765f7c490f16936e68c5bd9501e1d7bcf2b63413a809cb850baa3296ded6e0409955ba4180a799d2ca35b364600752702a662471cb8a8b4a3ca3f5c7089bc6ae318f0afa8c392ae387126455e0213ad22b1e9406715d71f12642c698e69c45554decfe542ac167c6903336236eb65e749d9092c12e8362b3d81e144d96a8a2733547626441819944ddd1720522ac9b7744018c2e8f83083008e1e50c814527b8e490f420b1c3496707088fe71ebb4046ab2b8a2710f81430f599d047f944cd1c172e15018961b25c38a2dc704f075079725c8eb453384da6300ea75412b30329c764052911d0110b7e75a4c254aa7205aba19122b32a64084980cf013667d36da02e71ba90a440a2e89d94c49a0028070b3a328b2b143eb284f92168727753b10a9287a896ea11a84f81b3c5424e7c27450458b4c24bf1800031c4307b7862c4534f05a4f0c102abb154904010d08191c41c041f61b8a6b4566071260af3343326af8581d2c627aa8a0099c32d72d90013881c38399042c1b272c7c896b52d9f3a443ad0551a3183686a0656a15a833082f40c3d9b8b800083a8c63ccd2e8472153a41194873fedcbcb22a2ca1002074fcb281c71d23175752342a2a1b909ae8a66c343c3825428f606d89b243f5e2410eade811350056d62b48d4b2281d9a8068d2854b1a46cc01a51da854d228d6a11c740ccd8800000000004001f31000303824188c05c3e1a0844a6a26001480015fa866a45a9d4bd4308e510c214390013200000000002018491410c6abd1df9060d92e8f9f02433616a4ebff406be17bf6d8d1d32ee546ce2d906a5c7fe6abe095fb9d0691e7e941fe770a0bcbe3a64164eb24abec7cf5b002efc952b0085ab5bdbab0deeb11b8f86cbba36d8224c0760baee1f21e7bd285e27f184f30832084cad528d27196919edfcd60bc54dc658c2a0357906dacaed7bb677bb7ead3d20cccc52eada6a59b8d2d4674abcb06193ada62c334556bc0a73c911d0e47d7189111d15cbf9b73f7c6589f7621de2b75a8de6ea348ad065810c75dcc9e08bd4ec5c79e7cdc8c91856d3117bd42b7fa80d9b357aebf9833535a1f904e5ba59503ba29b59bb9136484d6c9b51723c1b67a8f89d94d6df80dd9d8b1bf56b98c458701f3fb37550a72adcbacbb526a55bd2513a18521bb4dfe0142ceada8c566ffc4d72484a03f3931de9fa618f70b8b5d7fb5c5ca2fe88b9dbe4668acf3457cccfa34c8c45f19b2f3972233ff5423637d55c99a5fca64df9f3c99efaf5156ea94e513a132b8565977c4ca6ab9325eaf2c132ceb15cb8a92659b6619205aa6572d7b64cb1addb2847019af5c66235d16b5cb722e5e46cb97f9b27e19f897307b7f2a66e7af909959cc8c1d35b39d9ed92a68d6289a4d24cd80a6d95ad4cc5435a3c89a9da06b762a9b21d266426db3206e16a66ee6e4cde2fa669fc019a27056489c111a673191b392ca59237346e99cb585ce84d2995e2a7546ca9de17a676bc1b3a27826933cf334cf5a44cf5ad5334bf68cd23d8b50f8ac2b7dc6d13e63c4cf1ef533247fa6a47f0609a06914d05409b48306da2e8236a9a0e13268101d344f08cd5742d3a4d0782d34a3185aa386e6c9a1e97a680841341415d17c4d345e142d52456364d17e5db49e30da5246d3a5d1c8da68451c8da58ef608e4d162e21bca3eb46ba867230de863fadda4b2bd0ed8a68ff59a6b4dd492c6d1cdff76b7ec10df7f2f1d6c65997903647aa723195b52fe0eb992f3d7548f5ec740c2f7f38d1b31f98725cb03bbc2674e92d0edd3cce783cff4e6bffd8399513004f4e0a44ec3f06d2d073739c862b7136f08e6908b107ac5b84cf0b6423cc4dcb1227dd8402cc551e303625224130634a522732127e61fff77e3860399c3ed00eedea397a4e745754b293e2a511974ae046cbce5e6abdf5c059ca2bbb40c7d9826cdb3f3906d690ee128c7d3b94380610f51c0e224bb44f0c812cc5804bb094e1d13e43905170fa2a5b0b5537b875b189bccaf682c12ce060b0dfd004f4362ab907f23be186f5926e28b3e6dc1c005d64307481a17aab12050f1735721e04e5d03a2e9f310f872865dae39e0a81856ff2beb2fea393202ef83bfd1b8865b937b273d36618adfbacd8009ae9a435cebda7193b31b2caee7e4efcb6a57f1c135aafee1ca4c93f6cb3254d688c1dc5a4cc543c9d0519567c1387f97f926201fe0f80dbfff516fda5df6814a2e53fb5a8a45ee2bdd7a4f9324de5f9836e4d67d96d6fc1f35cf405ea4a20d30b06d5604d74d420bd0ca220df92a0145a99991a10190607c54c5b04486fa6cc16237f056c22687787052422f22e481071e605b20fc17ae88bacd3fc6ff744bc4bc5cb408d4061db6a86bfd77ef836a7fdb4bdabe5344cbffd3cdccfe7195b0d1d197049a6f5ee7eaaa4ee144fc531cc53ea2cb83760c66bed4f53218fae8620f70b0c24b469006c0ea0fb9936e9f17450febcba27ceddd6a51fafb1132df49e220d79439f2bf92e0fea70e26b9a68252acaed144ea7fab65341e70b887ef77de230c9a1f5681ffbcfbf059a9a9cb3fa253e254c597cf6b2e030f548b35ac93f0bddbcdf948375e963731ba6377900bd2d299740596fff025edb5ed2b3d77183e8f61dbf0e44b37ca4dd265de4d7a6f734a8bae6896d674f4a22b86dcc1f6939cc0b1752e9b8c3406b50d9daed8c8a0a0b89cabebc1ef6231a50ef24f00f903402fa2f7d29bdd5cb7d2cde426e30674e3ac3ecb20d3512afb66a2fc0f24b0423fbe31f19996bcf39de40d0a41641f9e05723755fdd424a72635debfcca2ae6756635798ce8ad4a3bcaa8c25e1e8876963f1f0297947b48443e51640fd9bcd0dd69ea1fad4e9da5e59cc22b75b96000cf41356711ec9564ea06e9caaa1e5add960a91ebbf18348d7ffe97592ce4e351e015031bbbf1188feb0f6657dc9e98fb18e7a3fe02de5a2cdd0438c20f66b8f8ee64643ef81c8a04169c98aa2d016f8bc41645070e2da47d2ae438a2ecbbea584608994bb1b2e70c31182ee3b3eb52a96a37271fb31eea61be71683a41ab2576b8c08e7e2b463cb769e4ea17930146a6587106d149bc2a557a90690c13471b4f5eb03a2424acc2aa04956543384113798bd533b430920be98a18402d2f5d5bb783dbf1f1ad3f742c04361fd42843753dfef9451675ca6714fca59ce6cba00d27d6b00d1690c87b2d62e049a1e29bca14dc56fef13d1fee8e77f8252ec15650134a8f4ffed6e8fb571da2b0b9015bdb1c67d9781f50e2e4c392adf7da5486715595ee4929cabee22f09d5e339c395fcad4bf2c92c6f08659c0c9404033f3c91e3e855b72d389cc4ce001fada7e4c90e42b76490496001b31c2d613e75cca786a0635e40c48f93ff812dbbaf959f9d347d7e12e47c462a07b76bda6de7237977cf3c78dc0df9e2387c736a1432e11d05260504b3c4787b48c127285edddc50368cdba4242b1e8b5eacf4323cb4b35b7e745ddd5a932795dea9b6e02c941ce8a1d95ed6a517dc54abe7e0a8e7f0b4bcf3b429d30f7c1cd34cf4af4e05b1a398df78ae340699fdb5dd073dd72d3e2fe67c0a758fc1c97b6b63100d8e8668fa8a01d2cc47982cd9b12c4d910802719e6cffc6df3e71aec15f504f36e2d5012a0e80f3694e2ace25c1985fa0c12e8725242fe79e442d393e6ea35b3c146601144443e27fcd3841728ed1b7dc9c4ad119c58e3115057e1f93c20c49b8995bbe0e933ab18fec2f7b0986be1e2f7975d0488c22db9dbada021dcb88f998dc83bc1e3184468a39efc0f8dee9169792969469097f7f3bd1c9ef2c11ec21dc463d49e07bf716bfc09e270c4f8ff7e6a84f45c83a4071f39bf00df1071f55703f03cbaacd3fa07d39b7189a78a5380d6e3b3076a5d7974db2cdf7d58fee7b9cba6b74f141e577edb32a69d205d22d004c55f6346a9149961b43c82c352af38bfc7ec2c66ba2d08c83a0ee37df514b9f16818823596ad8cf1113c42fa5f2009bf87c74012bfad4b4d314695d205ec1bbccdf88874f5c2b7baea1e81e9adebe045813b87d6181796cf0eaad662afe36b2b2248d99dca89c71ce16aa80e62718814916a270c5e6117f4e0a7bf01ad6097bc7759714adbc960fe33c254acedfba4238e5e15a711eae1a14eab06b4a55a2fb019432381e4b78c5e8f53226a500a66da5f225888dd6e6136a1518f45155de99ab76ca6014fa33e3a5b130c413381d145bad48afb4e2af18d34ac2056ec9d884d26aeec59e25374b8c5e4742d52d216b71eb415403ec7b9b8d1e8df1c1688c845cbf2956f9c9176786ed5dfe15076e6c69c70adc459ebf9fcad742ec9855ed2ede1e59edd9251cad578661ee8d0c5e9d133247d2519ba2de920b60dfe3b4f3203a2d6c095c100f1c12bed17381066a3302f80fe02626481188c6f00f14e04bd6dd0d6e6204ece6e8657a6413d0099eb6ea9599e6e3646c5521296edd8bd968af937e328ed4d6cbecb350ad3eea5c9da8da3dc87eeb67c981c1b15564dc29d367ea969a35be74fc98be047f168fafa46ca7414dc4fca0c8482607b41b7d3c46f5f9bd7171476e25a49d58054ed8f9c36913764a83859e5363cde71f5c93e93f0b7f907317e98e22be0ad8bc4a2f102fca187de1efe755022e0c5d2f3db46d4c921322b94ee35a71a9b693f6c7ae871ce1221c8c4fbcdbd50267fdb5722c94f908e8558ed88c49720fd3b8a349bcbeaceb9c01800e3f49e4b91bb9449e2faf1c31db618fb8d1968b2def5b4487f8c19bb3314d761af535a0b72728dbf9d8ea7bffc37a84a2210a1e030e418c11b07053a3d64b3385b696bf7873e2d9bd8302a16c77c9df70f1f7925fb3f11bfd604de778a7f84e43f2043d1ff6f042eaebf3773de98ce77fab56b8cdcb7fc4872b091406233d2a3d5d025443d8cdb6bcf5794f96722cbe4dd97ecce3c0e24ca1e6205d11b6ed10f65bb7046cfe707079447438c59219b0be27e81dc11104df1e384291014a04e100b64b81a124b7daab12db19006d171c57c773cd8a82986c558edc419fa8ddec7882b44d132503a1d8480ca88d753daf386ed81d9b7d8f13b250c0c3e76dc677f2f6bb14be509a86f55d6f3ae7bca9e2d51fd20ecde392d898b6c6e511555c2a6856650e6c3896bf5d3ea3bad5dbebdba6237009c8e296eecba3852f03b91c1c696761e3e3a1f89f4a4ace5678d4348a0f7e19e11b27806fe2c4e11b1aaf5d94e3d14466fa9955d3815fe7f391bc66bccfaccd66d0efeed3599398690a3048bc67921ff07500148a43b91b80c2c45bbd387875a8032cc8148422c0aba6d17df96428c47e7e6fcfc2d1fed63ceae6d205df422f31acfbce0fb9eb01d446b83f2a686acfad1cc3559a1e736c848b2e9dd817979faf6e6354c5bfd117b22f24defc89e229b1f2b8451aa80483aba6f3ee92b9cffac081b974fc2a1231d12c9767b32c10dcd7f315f206b17210a0add440e82b63435e058ef630df8aace283d09aad99cb987869b095ecabbc0f35653824d888d7a277ab6822730109ebadd0b5e1dcc9f70848af7e4bb5a790c9cba979f9f735cef7b38bdbf7830c1d20bc933130ff16137ccdd38bf409a1e6297aa6e593d68e40d1e7dc15726be9a4cadda9dc6a83943d3c83142ea1db471466f1dec160776a92f012a071aeac74289bc56d71ab660b95171f1bbf5669966fef93c2bb7789e833de59bc68107ad089bd916e53ee33a7d8b19ccf7990cb3375780f3b4e1e4cb1bf33a2219d03eb74b0bf63c6af1741572ea8a487a430b3cf6529ccc5e70c8dcdd6e78cc338abb319b8d9ff836c0b28814c18b92d8839a8f8f6b52a6fb9f7f7dbad318c93d45435d5b1ca050884cac844034aed8edab10e6b712eb91ab483ee589da41acd320b912476b246a2fa96e4dd9600438eabf7d5ad03e42b2bc08e2ed5735a39750a73296a249990b6cfeb97b6dd6d2ae210a29c1cece68487f96e5193504815e65d633806d5b4b748c763d64a3477e0ab444436e2babc36ee2684609dbf9d177edd70cc2823413ebcf71e295c2e68d70c3ee4f3bb918e58c7e107bdf7cd05e4833b8656c46bc09f259f0d9ffee44e0f6672790dbce5280054c5c817f3dc595c35863cacaf8a439d7d920ed52e4d731525b2f7af096cdf613bbf220fcbe53dc077e6d4af1bf95874dc8434760b265fc80b2696138e87e32ac3206c86da68935dde33ab5f97e95833f119df2f1a729b2e1490bf4a0fbd2466fc24e05d0f8d3ecd9b4cd1e03ccc6eaa78c9db5ba8fa31fdc6376b8aa2f3cb641ac3d4ef502543dc0603a2b1f7bc70ab368acfa60dd9a0427e98234e914eaa8496320f89e0d9394c8150aa4831b3be953ec076164845eff20122fb6afcfd851b002c69e3adf3b34fe057133421939d9b2fb64405f8ceb6f0f66059060c3109c374f7cfee6e9e93499919dfd3addcd7b5ef68cd6ee5c3e524bf4e917ae97b169ececdf90957b0526cd3761aaa58575cd35b4524928f2ec89551baad670c116d04b91af0395a8df7343a8bc43512a4d0a180b0e048f3834dd5dc14920caaa2c15939832b7cd6d64aa39db7d3dd8313fb358d1b51f62b50ee888eb72f6543ce380486b6f455cffd08f43e06ae64027f87cd28670389231dc81f9b069027327e7d0acf477d3cb782e8e4f20b57dc43e1530464d505a8047ce9a140c8b712987eef9a849801351f2a194b5ed3762fed9deac98f0e730242d30f505dda35474723e28c7d6ca67dfcf25622ddda6b47770797322ddf903f59ad30bf6d19fb62c799959db259a2d8ffd5be3498bb268f39015ecf045bd8de83d22fd7b7dbfe353a33d31ee6de1df0b2ce7d2c12f527bb9765ca275a584f7f1e68de2bdbb079abf3c799d26f293b1c03f6678f9fd64f9575c9f0e715076b1ac9db92f8bda0485957c9b1bf355af58a60b62e437b6380a076d857676b344b45d0f3f17cce8a25d8c36226e021a4d38f9d9e6c139253ebf241988e1dfd765465caaaacfbe0c026fc8f7a2f6df094a3c7f241908e2bf473a56fc80fd1f2ac6a64e6aff6befcbc3f2a8dee98ff7bb3e4ded3de8e5068cf13ff676304ac0ee6374a92c73ca7f01cf38265b544a7c6d7dfea6d403662435eed2a7b7b7e546994eaa99d4754fa1a08252dd2dbebd454f52fa0c9673de4c0a5b7356aea91f38f329c5820b18f47c02f70646fca3f6fc0d88406c46f57db28832aa6fa17c697e54a09c18d68548581754479ae4ce0835b9e8815a267e3238af53eb3b0cabf0ba26c7bc49af7bc403816f9b5a6c3f4ed50d8f43466c8b0ef10dafea906d34d4c3f210facfee211814ec0d29fd30be73dcf33de47d923fddf6286621ccb275ce79cfad7727a48b5895df9f18de19aaf2ec3cd6980c375e939e6e0ec04b6430bc5a2551e412c66845e576aab68b49bdfe0e1e281ffc57eae73456c0f9abc737a9a2aa7f3df62f0cd26311ed6a0f17346c17d58327e87539b344de4e06b6d15fa665adc9920983f4bdf61ae18f2ae3d7bed8d59575e4d44bcb751973ab5da73e0e2ff07401e7fb73b3a86ca61bb6b2aa2b69c16ca8fa91996777ced10ffb8d8756dffe22d4faff331879b4150ee3045dc8cdc711a488ba217bcd7e39eba01c6ba10eaed4fc984351c7066fa32be6dc66794504e7be4f7edaf0437d63fda5a17df70a3f1686c1b9897f997de683a19434bd5066398fd91f9000ffb2747fb0de74f48ccb83d62f714584b59f77206b13ec32edf21ce33736018688def01d29785cc04d5c5cf4e5c2e875dd640ca5331c32ec979b6c877d06699b6d92db2cc56cf32f176e3281c032552b72d1533d9f5061e1c5c8666010d8ce97fe60e10547e2fa93ecfe323b02e824e7e06f8b305728499bbf2c29d02ca92ff748aabb855f19bf62fe0e4c3a4600803f1ade1e3de9664dc03d51e3a371af1dcd4dc8803d6e469f14855d3b3351ea79dbdd5ab08e6fa1a5d8bedae769d13c7f502cc3ed375b9e20b4c8ff447184428d0dd0a0e47ef88e034e7bc3b0ab8c1919d04feb9052cc96bdea499f2ac4bd821a6bf449da3039e2e3ff4a09eecbfb97a65712e13c82caad13f39cd055e05abc2a30d7f80608615cbfacdc37210f608471695d52d47e9ff5d1cd79146394d8095d9f9d4d41379fea91d7bc0b98e780582eb3ae193cf43e73f9271d145b0e67993b6d516d4435009cd80c5d612245cb704a244dfcabcdf7f3cd817314f432da489d97d715be4dd4b9aa243aa37fd2cbe4b717a7b8f95cff11522e073cdf580fbd04764208d6da132915d6ea2cf2ec74b9d220d87cf94b43ed4c3d6501413175735a837cd63b2b51a275e445af68c232c044b3a23d5f11b0ea1f813f7135cbba390d33843b07fdb56a70acbfa08c84b9ef15ad42954472bcff8833d54822d8bdb0e099d3343d62dac7710a8df0c8330fe3d2d9c2c5c11eb93efb25b4dd9b5c2cb715853bce6e5eaa6f05e5b472330f69ad10bd50d093ab9420ce1d90999fd3acd062f9679e5955c1a3623478ead4c5111948d6c9c3c97d608a43453869303b53285d59b37c997f24ec6006ae79b0eec29be429019e64a1d4faf7f8e261e1d758fc9e82c45ddc24f85881fe81feee889ed3231f4a4ecf5014c2ba1dce16bc676f7afabeeb23ff55d538dff6d87536e7f9977612de4f200ee8cf94b19fbf76ce65650a2961a25c5eeecb0b53c45203de48993c54d84021f0190242970b22ed2084cf6b9c5fbbecc03ba97c6b1c99f7910ca5c6a240b9c3b36b036a5312c3e6cad7802e3598aa0d605bf13ed3cbff740a7d9d87f253af187e030e0ca8bebc3af787d0a6810fb26c958fbdf5d8ed0f3e3689d4ae374744652cb98acd3b3cab6a4f63e3e5c6c7c31757b181b2eb5f1736c7132bddb36dffc8bd9e53c80d6a53e86d0fd9dc4a76edf33336c95f58e0bcb7dec41cda051ec5b1053fabcd08233a865c4a70cfae523a965767d4fd78ee0fae8ccbd50c531aaeba76ec9e7c03c475ae17af8f0d822b82a592c014dfaf4079b7752252dd8d892c42a146bff865d347e87ec39262cd324d9ae0861f839d1c149a43c180e774188ffe78609ed6285c0a1fc8697672edcbab2fb770e6879a96081ae7829033af58ef7f82e230d6718566206c7fa4695842c76dcc2a978190d9424acd0bf54b84fbe78015c6f7be6bc247b29863dceeef130dff5c9ec2893514bbb75c1e32e65eeb6cd419b6fddc86d15a999c0db3c15e6442baa4016968ff3c4ad14a3f0395c7dee2cafbcc7435c531bc97e02981fa08f42bc3b692abff4b6b5deedf2adc7eaeb85d6a7a25fe1f848bbbcdcaf96871214ab14f24f8e071e29a3dca6fb9833907f7afdb976de50fd3e129675719cacf42dbc87aa6ee1fbfdc3cdfabc9ad9543548968966d05ee4bf97f500db1bd4b05177d056391db05377da6737f545803e8614f2ae59f6fc4ce72cd31a03ce4d56b3f32b0cbf9b1ba7f4406a06ddcf04e90f54b1c17b62d623c6deee54fe855511da53d4456ee53abe1ef27da950c606f45bb176aef3d74f8e24f356a3f373d08b0615e684c30fba8041b2e764c3f9c10aa447158f4a9e132367aeca070e4e3fea08a089c073191c7a1accd4c29baeb9fad1487fb83f2800845d2f468aee3d0489b4dc616e29d7410db352b2dd76c5e7d32f386e5efe3705d45d0dab867aa6f0fce2211901769fe66a43a71c15201da8fc01f5798591776b765619878f5ee66c8128ab306b8409f7a76d549a09bc639ddafeb138d6b1e0cf06df949019cfd54cc1251cdc5d018281023621cc2c982b4157f4845f9c80d1987ec490ab05a3f6a0693af294e5383711792aee5e35d10daa283ba483c266465d521e439f09378c880461d2d4ce113d0afa6bfccfede7121f43ed950d036e9f6506103fb792afe4bcd837b94ccdd44e2ce33ff23064be216bd6a4c9c14395a23ed57b7feea82d8d090491a81be39e4d3bc2a724637a5da5cfa92c18b51da40900b39a2a3b1374778c82ab9854c90a90ab0e3a97614ed2df2c587bdc736a87df549a7bc8ffc2b6acba8487ee059c7013f458c6290f9f87ec7d3ebfd6ab598a61c88f59e4f079f58aad8a34fee6181b1bdf6d643c76912fd6906ad98d98a54b0959986b21e54e39c5bdd46667eaa6bce964a4f2b43694ac6d125bc6f052316b72701e4234a3a1e46e0c2d8877fe233ce6c9815f2abe6c4396f987d68561eebfb67312ee0944359762203789e5c650beee63ada20f90c87a363bf1d3fe35dcbc2d73f2d68425ed3e83b368e81ddcefa6043a110801cb44fe7f07201c3cac72404e70d831af67ae82811e938030b61703da23b68aa27063819a723745d0805474daef3c5cbe8469bc9bada57594037d1cc5caa802fb23dc0b911d04bcdbfd194b5ca298e1dba68c6d384fe90f62009f0fb5c86254e77dd46586d7b5a7f36cb660ddb01dd9fd6be4911c792e4784cb8d6d11cd7b3dd10f4839593b9094bcf7bbadf2842987e2750a3e383e2143e72939d36fc8eac064014b8ab7d122c2f602bd3da575df93aba5b54d59f50cbcba6f63c3e77efd0003bcf90aefac190f2aef63f485f10b276f22c36f20dee8723ba853ac10fbf233fff1b703bcde3f3114764c6b81f07014e19d5837c787d2c76583c61b53b6bd886de0bb52176f01611b94c236f33d7bd9dd949b5aa80b62c8bdceb96ccaa5923788a4f255a2cdc2e135533e77d3d33f1d9f1679ab7aaa0a66ed6c214a2b375cdc2cc60703bd6d314d65f7d2fb1e654f0f9c418118883a02af4b597a759a1c9d6e49a32be938d09f0ab27402ee9c136e5077f368deaa7e31f9d0663cb7f81afcd3701c0cd4d0b5fbd44c824734293b95b0421948b9c734b328059bf2f975388470e7947b670ca1d41146a3cdf32b734410040cd1acc99820a7e67ba942415446e3470ffb33506dad8256c770a4f387cfa028a67f210db9407a84e3d498ed9a819c9b95962c022c7ea3fc2415d8f63839cc58746f046327d740559d93722e5089f8a7897057f4f97d25e8aa0910fb37a5d7dac0e72beaab1df528433d052763d897eaa7854d8c3558270f31fcc7e96fc13adca35627c92264540f11d8e28038d97435b0574ec3bd521a6df829780ed2cf113bdd886415cc340fcec57ef60a4e7e50f5b6010f057d7428c66866cbfe9aaff24e6d29a0ffeb020543aa6ec693d77847ea76159d94888dc5a248e4c966827ebfccd7c385b695c4560e089c1fe110e86ab2882922195983be643f43e7e9bc8077c6f166a3ae77eedcdf7eed3d4e9ab2d1570c5fd6fdfc8dc2f8337d675e94af98554b0583514dc90cc483333c6bdd36e0f5ff6350b6b4aa8f7b65d926a569973fece6229b97b73cbbb7d3bc0d4d6e82129611e0ddf0e88bd0b34a0e6bd4bb006b2631917766ef81c5cdedba90287db3bb58107231ee636d1c5678c550e8261d06d4a0c45aa346f66531b7bad4be26118e5c354f34fb7032eb7316276fde1fc97e1ae90daf68baef6fd5bb2ad8bb3f14ca9630442e4608742f21dda24b6ac7db2584b6315ce62d7bfb7a68cc6636381012a43287e99c940a6f1aa5f4b6351420698a0200a124f18f664a3aa28dcbf3086833197511b9bef624b11ec8ecc4b785890cf4e141c729071507fca8efbb9a5388abb281fd945384704587489e51bca402e3362dcef1648bb3fc2e5df6dfa0acdcd2e873599858b50115c68299a9aaccd83c9734799bd8e1b7a9aa6f6a3d713669d2764426b210a82d676441de36d4d2baa8252b48dc5ee37bceb0ddf8b311dff8181ceee173d2679a18320121e695bf06773b9ceff7181ed07b01189a3cd23eb5079d5fe3ce1f2034ab74666229463b53596dfca99c4a33656a86932eb442150d3283f6c990b6be11c6f4d2bd00808df3bb5b75b183ad6dd5f4b74ac4851cfb54d718fa6d45516a1ce2f26c199c51461e78c0a95899539a3dba7415a81ef6878ce1d4229f0008283cca6791e9165bab9d8fc29af78403138ba8168763981bdd9ac84831f5de314a4c6a3647d4e7075db5276343b1f101346d2bc6a06b415c594934b453a0c69fbda992dd9382f813662f6c10bbed29697b4a78d518e0d964d07048fae84768dddac5139bf222a028cb4518f5356c250aae349a90fe97949c31068fab241081a90248d54edc1ff87985805fd9040b65062526e3fed9576a24c7685747cd9dfd2451ddc1eff452140ec729000c55ffdc6cd1bbc86d0b2d27952fec57f633f70c6c3c1ec8d6271260e23a858fcf2f589eccbb7d0332a39a351a1b80307153ce2becef373f4ddfe807f4a78a5e580b436b6df06e3bffa1c7ee3fd816ee908f9e4771a83917d1b2f3b5342ae0f673909e7e04c638ed79189316b105fa79dd33776bd9f4f65ef1be397b8ce267ab5a2b89400a6a64b9373b4641b73753f61af8932e0885016147cf379d6dacb9d89d78cf7175b4c4a5f036b3f9e9a242c19b45da2daf3b1180b5140672b8383bfe0b3ec549ffeccc53fb695cd562f37324f3b18157028db3a235d30e3bf1c0541df42c0fe5237c5b93cfe381948f45edacec81026f3087bad16574c2682dac07d50f292da448d22c7ea7afba060bdd19de305a37049b52169cb304b3c7b1611ee1831c32494f36b0be2b2734899e0e1925c65dc81ef7957561998cd5c47b15f0686c599ced1a01a5242fb4d9b4cf7b8df03284663313899c27c5a71ad6ed8f23883934622050ae2d488f4098a1810a40d08ce4c46d1eb3d02599122a7a908bc8dbda0d6ed19d92d13aa6dca47a0a129a4fb565fbd373cdd33f95755c7b2d25c6da1e3fdc9dddb33dfa77dee78488c3164a245b4c0c306505d5d5093a49303cc98f78824ab88b37f5f7a5f494527161732411cf1eab10100275d8ab7544430a0a9db162ed02dd1075e965640ce5da5c8168fd70c9f35130bc3d8cf5ef8922ea2e3dee9c85c23acb23486980d4dfe503ba8fd1dc82b7d834096df17abdfce10e2465a9b57e2519d26de9e6c6f34db42d81144e74592d96f95e2b532120964b84e81881bdaec9693ca036b905e5f11ca595407a860ea257cfebc603cba9859a9dffabb3069e33c046771838528faaa3e2237589932aab6a694dd6fa0ce53c049b88ff15efc29741adef7d6aa504d1d3bf6417b3716623c41c34feedf1ba1f4c97bd7bf13aaa722a4ab633d1977b59830cc90fa5bfcef6c9ea70af579bdd0d65b57034ff7028a6b9843428bb3c08a432cc0d89fc15a1d8aff6d6fc200d518d3e0d23e898dd48cd44e5e8604fca1fcd4b777d1dbb79544d50678c0da87eadce32c708e4985fd629d0f47b959d68b60abd92409b0c2df1e3c32e1283fe03831f6004233e2b4d4e838d3561077bb0da1834215fb30e4a0534de8b2973b1475046702cd214068d05698f22c63c41c43f8789c610cdfeb023291e8884884e33c850d9e42289370ba9f57b6976d84d3746dac94f9b06f6bbd6c6cf77e7bf052d596823225ac21cada6af6dfb318d3c1a1ad82521fa810492c52f1809eda7ff76f2746dd2f5554bcad92747f997746f1262463c98d838f03ababec418f800f37783aea7162199e9af103c391c16439e703ddb1122b2a0ed1713b6e023de97c58eaf4c2abdacf382bb04eec9f2b111cde89cccc60a6eb6212847cc321bc04add1cd32cd2af7b1c254d6dbd77c69d77e638175980f5300f2a48ab13d33b826610d5c4aea04ac63a6ac1c3a477e126d83168918563f1c3be2867171da3a7cce2cd5e77bc2a2d00870e240ad2e15fca015d0b47be20aa613fbfa92a7b81909a087a43b7757ce28e6a6d29e76631d5ca97f1c6ac7062b1c9c5657fe4d93a95157f9245b487c4b6bcffbe3ef39a7de634d53c74034d0fb570bb938890362b26a8c847b8054dfdc0cf4a5a07e5e93d470ea9e88b19d3129d3ad3b851b292efe70053fb8b2217865dfeba84daf8244694d37ebf6ef5f246805f5af52c1aee1f70ec177a6298d927e7c281ce459da6800a738d63b560dd612dbd66e15cb539f05fd454d26906dfad51fbc478b5b8a9a6ceea50df879b5aea60d49e178b55379ccc569adc0d19f23be4b4cad3ae6faf5ec537f53c8c819960748b8861ca178f93f653f4d3a6822a970306fe83de88370320095eae1a879fd8d99b744264ab6a28956dcc241952f4d933c1c3f951b19659dbca15c13f5f66ed6f7dcdde5d8cce2eb6a0e26696f3e7bdf910fd5019849f24a5c9b2114fcd8f1e6075e3a93e784a29da6c4e158ffdc4ce07ea46e9b180732518581241e30c67190839e4f6809734f578e8fb331d4a01d7eb8526c39102982a66f39dcb3e44928fe21d98b9affc309d656d89952609340390c068be09f800df7415530df12dc69d3990274204c30d90884cdbcda84a511c8890baf8acc17389c26e77f6687bd7684bafc45dd09021d78c59ede2b4d737fe8ae9e22c42e4bf0a7a233e4af1a19bee18090f7332a0e9887744e5722acfe68ee48f7cab4bd2396a316993d71e5a12116b8bda024cde98e6e2b2c7c4217a7a327b97008ed5d60fab4178ed901e7f147313cd17485e8e8aebe583249eb7d3db1498253192cc52e5f945e5f2d39f303a81bcb8400bfb7b254c39ee1701b7b9053c1446f8511e017288c78414556eb61a041e846bd40c5f5b8e79a48dc786121e0c018e3060b7342081116408fa2e107b1c6a55e7e50e808a747d71cd3cc31cb1b7a5baca8f440d3e2f7f181dd07b1bfcb14a98779ba1d7b8ec8ef51e7d975f7a5566bca88eb94fa9f32c60419dfac488cd0f9738cd452842d0668658ff4826243d1b9f8ae4bfb7a6a3c5f96176547ff379966dea4e4bfbc94793ef67cb3a82640d1b2428762b6b14551e56860d5820cc05565474d2fb1ca14c6e2e964f2074252a774382ce517838457f9bbbbe03136207dcdb1dec2a63a3fd112324dcec951c39477d1e10ba31211e4bc72a0e14af52d1a4c14c981b311fea42e03efc626d36eb41a695faaa1e5a88d8b6a2dc776bc3f40e3db07b3cc9ec18f0965927210c1222514c35699dcb0d8ba885615b1b6d69166a6c7d7bc404c6070a3dcb43af83c02a861f4aa481ad93d720d1afba3dc6ec8105e95261a5536af65135676363a62776ea1c20a40da988b5c8c12cb67c8a8b42b3ac6130d8c21b2323c23391b86afd0c570b04ade29a445f5df290def6b6edc8214228f90b9b6503eb67b29adefb12a0a8d1ea47c741c6cc56e04606e22d226589cff88c26439266f72e892e57d289d63ce4a11c8302b2103b5598f63ddf719c89e2659d31063c7096960ba44e368f460200225a0a9d35a457266f942c85b0e159aee368f776a29d13e15068498332d1a5a882e1126db20022a28e68007e91dd23c61528da1daad80bd642483b7907b6a8173c562392b0cc8b0d773ef7e603224233478167cc1256dbf15a72df800bd44b7d24e7757111dfe0e3b32f29ae2575682a1650a5729c9642ff627155eb113369188a42870d625ba91d1341fa4de5a3ac3655e36b67d2c49e44a1e20f8c77399cc3fa136ae0c8a4c9061bb5d34eb0bc89485f22684a342b15dad460943920d57496cbc321cb17ac87bc1fc46c358384f6ac9fb644ff83cf0986678b99bbc391183f98987561cd50c026a28437d6eb0fb863898717739f21650337ab71f16330f3a350496a56e888edde77bab01fa8259d480b9580065343b54de29f48781f3a1ed4900e70ad6a079c4f36dc405a41a4a9c2ce31242d3d55199ed0c9a9ff3a80387e5a77ec1b20b0d8e12e8f440f763bbacb2e8e26462b9d221ce15d514f9229993a715c9e7d71f995b193321d18a30d708f5012c4009aa9a299c003c22305ca33c49fd64ad952565da0ac4daa0ec63e2dc4f3521a8c2dbf6d55ffa617f71c44e306a8c3a726a53c8a469bb6825a7835eb237f2306c1126f0343c79cb0831fea6e016bed398de604e26e1337543c644a9c107e68cb2935751b2985d55d81ad8e6d87fc8964d7ec4d2d1153619507953c59a056d3420e9a68465bdeadc46ccb7a8bd51eb1946ace0c3580152257c705a6e0eafe5589839b2c8a6f05ca9c6fddc235181610c8dbf3dcede7028c08c11ac6dd68b2a8294ec096ec5af15088f76a0afae31729b9f0f08922bfd6eee0f702b59f8ab4138cc3a1e285885a57276387f2a97fdb0655efd1b8e74a67b1ac970aad51717bf1dd47f45945b14783d15765e43ea06f859a31c1d8864ae30d70b43802b6bcda2dd022bf6c2aafbee13121e76bd32fe47217eaa06b23bfff8dcbe14fbbeed84231dd289e097add00ed28df1c983cde0877e7d979066c6b91570c6d03351b0f42ddc48276d46ef7761429777b95d0a6e2724699557a69f46bd104a873d48f90f64707c18762ddf96d62647513614ecea1dd936bebd78a84618cb94ae56c5dcb4e8d1fcfa8c140004aa0c4f6f13a5b78f6896b6c7ec8acad3c1cf3b2a39b5a9e69a9fde3a2d5f98a18d5dd5abc53207de8fc851da55a9b9642d729969b2892f7a0117dd02b49a79c453bd7ceec08c3cec64e3098c0ddf650f221b4a201321e68e620679a561c45ee2989d91415f87a8374dd84f596f6833496edbc1cd8d79d4f279773f9dec56e6143cce93377a1a37404c5c21cbba8a94af68780fd0c573693562d4d7b9448260850013bb25747c4e10d51100a51705a7dd0c1c90b9f2aba1f34dfa65675b54d6ac184c677aed9c758510c06c6566a7bad65ee3024ab3a8060cab52efdd4d5c105d4a2eb6328e065a11b2997aa21b08ec8f37f66c1914b55ac8aac178421b2bb659156dae38587232f08e026210989a2e3840fc5a89bfbcb9290a3ada476ce01c68739eb6ad6dc0cd09543e8e5104b82ae76454ffc058e9d2ad147f1cebd537911a5a5663541a3768e28ae0042a7a7bbc31439b826a93047874ce735b926ce82a2add623a098cce94fdc1144e2f32418885cca366b630cadb42422c2bcf16d98e9a5324f6aca44ab7d3d234d5995f1b990b63f616a6191dd0701d7f332103057e9cfe06ab2ec4031d2732bf04596f84e3f6be197296980441495746b38ea284637447d20774e851fbc6100be5980845de3f3244bdca9809aef73581ac90363035fcb4259ac789ae3839253dc62cd92492724f74737f1e8f47972a24fa1b4943dd4328ed717b413b6481be7455b33b62b830471a2a0fd6345cfee6ac3d0b1f07d241e47b25e4c8aea5a915461da88c4a14dc114c8fea7db02aa7b31cf6e99dff982580e81b9f3a6d8a2fb20969f4e0a4917e2af2483933cd2784dd295ad1913c32645c4e642bc10a14456691aceafbc9011e52183f7c9d537e411dad5232074be80121a0debd40a22ca0f0b8348c061c863c4bafc99ce40e6f31abfa29f94a9e5c3929e8284046895380a0d23606898d2d459576137384edaa92730976adaa9f52a8500f77b5f56870760b347050f15c1d5c61309010cd61307f89ab942e9c88330dcfd830928b13216d5aa40d59257724b2e6f0951eb3c224651218574871023597161c36a32cd615c789683a258e4ab5db51adee22e03f6ee1616e1647c3c71457e1776c8025b355961d5ab7b332cf255899c2398d2b76a3a575595aff990cc2314dda3b9276f4b9b3ee0e81e3830e8799a5ba946631c1ab0a06356203bb021782169f3af23b0fc114f509a0b3df0147b7ebae976c75dbc4f7c161ea228a11eb6520b1239216d5b3b73b6c0c7640b84e7872d483e5a4151e5cd098db6d883e7c7664a3d6607fba142179eb074a2cc50e1ab439d9563de31bcc3131391139e8cd5bbced2935311fb0a324594815b8c370f92d22890c3ee9a317e97f23bde585116bea0e08492ac9079648ef15f2bc9e73055e5374cf0eaba9cb28fea59621e12b90f9c8626adf8b81fa50f173141f13eee9f0dd7a5b25083f5f7714d5ea9ee1bad4df227ca0322704d26544ba60ea85b64af63a49ba10b3a09956fb2aab1b5dac02900dcd9eb7bfbf08fb9aa032897c9a013919bbbfc89c7d89a6aa8cb78d942775a328c80c9fccd6a0abdf64326f12adf305a641fe8c856f7dfa877d3dbd6894f369f33170f3e6786cfcdc62ff7192bba622f99dc9b0237fb512f19870a8307b074aa47cf15f153162f26b35684de1dc7c4aae4e119e9dc90f258648f6c44715dc4fa47bb9d8a6b9312e27d2d987a99eb2a37f4ce1272816aa259794f74c5e3c38d576f10c179cb37c8eab4f573d8b503cad78d6ce6af42ee601d1eb0559bfe46677925b30269bc8ef72c376cfce54a4407094f690e7fb83cdf6b446cc52382ad81cd7b3abd8edf3a33d43cd53b0b694319c13788dc3ffb314d7267b22682dd862287c684527353b2d7a96942802aefd5de9831ee76fa4847e210b2db7937ce44910729fad84f9e820a882cee6509af2447f443cbb5f9e58d9fcc233dc5a07d23833e4323d703e20bb088804be557abc9db1e04b5d1294fb8387f1db0c39f433cd9c5fcc46f2e0f85eccf132fc1ed4bb5595f549e483223d4c5bc0c40e9921340e120260ec3f4df498dac53bb9cdae85ec2e6e090383865c2604bbda7c5ab0cdb1a051ac5eeec9ba078cf7ffa672bbf16584ed5e4445813122e773211860e2c3a0750f3849c99af0eeef650123e8f5813776613cf2b2ccb1914eda0cf4ce91411d60deef465fd387052049f4f3077719b177b29c62938c9b9e19fdfe63b54b315efb9ca806befffa65846dd181ee3cd35f0b463ca05f65505f0a5ca189179b939970db3989e973e28f956d4c1f5bc28183b18dfe128e0afe2dd6873cace07f135076d46b122df5a2d2e3ab6906229bd0f84c9734198ad375c398216420703f3020dd7620d7e1374758e9b325d60e74ea1e509d436058d9779cb1c67de5fd452ac795ecdd3dd31978174c1ab590a18e68aa7a52808a068d635bd633a8e0959e3fb34cca4e369f2e4afaa7bbe5cc5889f70d721631330cbbc9373f747c315fe48f0a040d16052e508dee84bb4e42c9eb79150578d1cf80732e8bafde256ee37a6d74575de84ab847590b7f6fab38525defb36c706df980309c465fed1633f6759d40df601532349cd9b3f21b8d7efcc86945ed11e9d64bbf9733a38a009f901d9c6d3b1a87338d74c5cc674952fedb2eb551410bcacc4214faefa414dab6354fb7a31e70aad7e67076db7532760f282b9ff0a216f2bd06db7630c0dc06ba5b823be8175e2dd9d80eaff8bbf974b3d228d7b5c906bef790865eab2afc234491aad86e07d1547389869fc37faeae428eafa9f66d793bbf92bc59f681e374bdd362bff871d1ea60ac85051c7c4a863ecaef949e50da926bb7bc513f54be3aeffa7c4159d4b9eee13e891b26478de787713bf22561ce269f248f35b5417e2302a933e377f203c7202103df6c6436180a0f56c0ebf9699c92735d0ecb4304685be55101c1e78e9e8703829a552b893e38fe8282d2065069579b3d32f60cf0deafe45b477d51512b610716f8c0f966835d57c467ae1353cbcf54219b8f1136c831912f0c415447543fc6bcbeb1720c5fbe9ec3d9ef456b61131a5814e3539ee89b95c8d34aec6683d6059875b48a303ebf21229369cf128c464c957970c3497acd64852e629fa542b51f2fd7e961e8b3a6fb99d386ff7cefb9af67ebbf870e009a45f9aebe77b13315560716d02c7960e9d65d9750fc2c204a24a155fd4c1236a2f448b85b0a785051dc8d148ea30c4f32bd5615d4dc8a55be9840f8fdf2bb4ad7fd5a3660c76e9d33b3e3c3913e7b385c45387e98e6fefd570e581e8185c0d900aed186e8b213b4a7fa4e55cae720855fb99d581e923673788b8b23e672ebff83d3be71713ab9cf719ec9fa402b5dbe87baad55a938dd7c5dbcd7be1c0afb747047c75200b675540d303c35538929c79807f567eaa4f54b00adbdf1b9de1cd4dde089ddef9589ed4d11b3ab423641522b00fec61b5ff9b92cf820c782a1dfd1b68b62d56f0fe64f3899a4a53e3031eb720bd8863d77ccb66a6cf169387f8bb9e5b14df70d83abbb4060086e32b9a46836f1d822e5d2b75fe2743194b3fa107123e679084815bea38d0f765cfb624ee426286971b749c791290fb7d9492149dd345e02ee9d569407595b7f71317c59f9f522dd336f2998ea55b67f7cdf58fdf5044e32d35c6da63c5b99abd0c1bbbe16cf67fc36d6775e8d60f9fdd2f15ddc3f4ddf10df25dddfaf6f5c09b4e1dbb77a078823150ebb7ce31451539c2f6fb956c468b9df7c9a9d772ed1acddf953fc272fff25c4724a94401563b9b28974c9e0be1bbbb8d1d7a27617ce33f4305717831bd64b63c693dab28d9e53b299b0e4abcccad2fa50c4c42e2d09cd1159d781ce9b98d299358f60293749f877133f129d45ae83a2fa0e3c1ed6ca27774c68585398be40ce296257dca0feb1b3ff00c86e4c8fc2753d91237be5c3605a69312e2987e1ea30a3d648cff29d071ba4471bc81d680f29aa0df645b44c1c1b2255ed82b6baef3846702633be89cc627fc4a4a21f48ac2ef2d7325311fe1a8a2b7ee8ab4507fb03ee5ad6ccaa2c932c9569b5208bcf7a6df8046951551aa344ab9da09a0da8ca92f8a71caacc23934bc264702f593ade3a99f5b37162067b6d75c8aa6012d8d1d4340610c5353b42dd6e2ce7c6744d5f274d8d909ca35e7bd77d7a7eedabcc14fcd79c4191c084a85409764ab7b172540fd00878db48e6879c61a98b3d1acd830fa1d51c26a5dbfad2ce2a4e07f5b720b4f10161b448bfe6e643af2c2acf74e255753b51fb0be3d4f745e7104dc50393292334b8454b316309d417cdefdf1ab7f52d05adeb89a8720b89462c122880c74ff24487a08966b98ca1cfcaa303988b3849ef24e10b8b711a584e56fcc0f2be9cd0875e19226abf134b9e9394e5f8f36590ffcb843bf26b23f2157afe0b340f44baecfd54eb30c4ee5c252c02a8611d24c561f531fcea81c58b31d7644c8f981ccaf679c9b9c65a41b3631ca24eb89c399ee9f42737fd6326bcf97a927b7364346028abe09151c2dfa0f861810275b0ce31603856db06adafa2cfbd1695975e720031568739db151bd2fc60fc64a5ecedf10c525b53cb181c9bd825ddb2a16c62c6bea562a32b952f2b0091be5694582d5a7cb91d5d3703ab44ad475502ee95afae0f571135afab839ca3701b10001129ef84917032d3a4571d51e0f5c3d8fa8bbffb04a0610ac9213570cf4c8e3a07c4572db79149708b87cddff9d08dadab50bee9b46625ed2bc3d2fee08914f0d8aa789d94a7d824ddd0532015a72c27729376a05166793094fcea5719afdb9be6d1620b6f8c932a227eaa56f379051a45bfd42fc6128570d7b9a39ded86f3db9b516f78d51925449527a1d3a0d27785dd62552c9f27b0a0151032d0d0b954ef7cacaa68894c6665dd112f807d0bd177c28623461ab0edccfddfab95d47b7c5e2198966ea8450625be0f8b0dc8f113723b1cecd48aa912c7d800e66ba6eb6f8557d190a3cf1b0c5176fc5300d98cabb3c82e7c996afdd02375a28f6bad907fbb4af95597766ad64958f03b1ece9234e6c63c2d6159e5c8da2266dc9c4dc79b04c33b00cef62941e0e9c510a8ebac1b2f8d25566dda4bd3d2eceaf43439f2ae4230a8a20e3fa96ae9af92a00d29e1303c6d809a58c714371c08ba953bdc7131154c8d505a2adb2fd6d7a2c76f12bf28e4b861b35a77cb99220bb210d84dd0fd34cbfc48909c891e4abdbe2a59c45535ba0ff81ff73b3187e4f2eb41fce8ffec001405e854362aab065baef321e50315fea1ceac195e2cabe9971ddd8ada69d1d4bb2d24d7672061b180cb16aabcf1e01d6daae3dd13cb176e76029296f749f5a4859680b1b0e3e67b2cdbd36bea77866f464280c2da56b1b4c6fba573a55d8eab3fb693774c1a3e90624017050d1bec4e9cb1cf0b11155a8312b36fb97e6cb0f7feacfd218696d51dccca403f12be687fa7f3cff5949bcbf2c5b1b5894753d10df36cc8de45b00a1b032aa600de887c20bb18169851bc2ea2aedbd25338af4dcceb39eb92115c7c673aab1caf6e91b3e364f200827b876139f17b24d0ccae6c4c2885b5cc366398a87cf5f579eaa32fc8e7662f279ed3488e402d5dd03c50678518b32a9112044de3737b62a7f997d4799d7ef5f690d62e8e75091c0d355e6f0a201c176069f21ddfcc6784e1b93334114661760ba7d0479fd46560b32fd0b7a10710e1c121430973959a41083585708a77a2549ca91e8704a4361527f17cdd67602e69eaa11724b28b87eed54661183e4788370fc37f16c3d1c4222ee6391c82322501e0a4fe23cd7e9de959616b44023058e7d86a4a299767ea6626b5a4d4c6f2525ded3ca2427b2ee324c601856f26833bb09b6513889f4390f7e4d20ddc2310c3a4421197f1bf4694d2346e0033aa15e67ab482353b7e468a37813baf6cb32843f1b2c5ec3cc3c3268300955cc4574553800d1fa8a058efd06e324d864021c86999942a017844ecd134e733e3b21c3dc7bf87453e8439e03b684292075f195ae9315e1e5db5f898352a65ba8925be58ab148f2d4d7db97b3361943870144b2ca1b4d38b89ac841e92ec97233ac91006aeb2e1f7cfbdfb9a0e1875013a4fc9d319b33388ac52522f2f0be545f32b0dc68c97b5693853e57f63f1ae41d5bdf157cc3b3cf61bd8eeb5bcacb1efd27fe7ee88de5eb98278287707cb9f0dc5fb3d9e4d773ed75e543df6a7afc8eaa3a92d7c250dd57d40105d72402b601b3b11b38b3efe02385674cd0de7aaa4c2161202623f02a3a1ad9eed9911f220ad4590a221d6885e8337d5fc0a6fcf477d0a5406a7c570d72be5534687c91af0ef02ba7ec2f3d90aba888a61c74097b95514188141d4d42583b1fb018fce714a3553905a2eb961a4ed34ff10ea6c8907d2f8a3c6283e90f273a4af074666da21b9a9bca6b991929389673b99e772197f691a97e7672219bfb7a2e61db94d069bc2efb5bb5ea612686196e03a93ffb0ee0675dc78e0e9a801360eb860bb50a3323421f190c503b02b5c22a36cb9bec203b6d10b21343e604ebfaf15f86b9524f6887028604c7a9617d553d73cec9ecde5f77ffe6f18a1f15f682aa84ea54c02782e035ebd049ae3b52cc6cdf53e4b325916d9e2fba1f27f007badde11e1f2f104e8e7093966e5f6e446387b0677043638ffa036215b7c13ee2d021e243e5a2c4cd8c571a7834fd7d08de9f7d203ee65a6d87c4131cacd3df56c0bff7d7e6abe6ec4590d4d4c3a0b50401c83fac2f8ff2171ce1cdb90916fd1ea20807d00dc08462468f5773e8927497d0d7d28f63920a7706d754cbff9e4f609e77260d01d2e9a9d54027bf6ad58d8cbe960420b0bf2dfb04cced5f3d9aa0ec305b4be3c2f66df94fcab9e07040e37465c4f960b870e4c73a26be1de4d2279c38fa5cf20473f013c807b44ad9b4b72595d83a6151acd144269fafcddfe6da6ae59b3ab884dfd33bcfecff65e60bffac107ea2fcfb73dc2b297f4e132b1fe6175f2c3e8614e1ffa09f242447d2ee29d42fe933b6a51120680e412b76a0889406ffbcce78eec26819030df442b2dc17aad1795e6fd2906d097c6f3b2a9c98726c70b23fbd3e3378343631109be60c502db2589088374753858e9e25ca767c749b5398dcb1c478c7a7126ff71865b4f4b20e780eca582cda1c2d49f6b23628a0ff52520d2e24e22df5e72bd9b3108c6ac06ffcdd290a29bb23e94450bd2db9f9d4e3a3b42a729878c23d2e017e54a1221bcfe1b90b63bf4e0383a43fc86b529f02ffed244167dcbecbe9f33f7a77cadc27d9f671f088b1784f8ded3031fb27aa7132bd1609a99424e008f7b37c158033e38b466093f1ba932e2e73a39e61317371f37a8c656c67adae8bb57b336d1e82a1bd27a00fe5e7f2c4cd41ad8024d21b847cf94de789adfe876146f8eafecdaaed23aae395f75210e765fdefd0a5d51ec2fd6db6064a342dce3763d397b35e206d51fb19d3dc2e6060409ece1d5f4e89cbe586c7d14dc7365588c8d80309ea533819893f7c3c0becdd8373fb3df1bede17f9527f7d82909e3d88cde80eaad4a6cceac5fbb805c0bc2eca621db970b53d6ab435c11a9ca2c5c91031336f4fff5d20a0dc046a6dc431c36af1a01cdbdfc8d35877300e751fe03fdf6e9fee56d98ea8469284c3c4ed9a6205bb6b5d8de6af1b4006cc5600e78e2dcaf36bc9b112dd6c32b9d097b4d262757a7d02c6dbb43db3c569d709d65ef2176e8504fc34a3450341a3eeb98b33a62dc3f3acade9b256ff4bceca0f69001fa33f9b3b41f219e48314222c9117c4e0703489d7f68496f0747c91c2cff80e0b4c4b4d6dca8b3e3f102c8da09f5e28cbfde5853f7db5ca2e3dfc6600b4e482a06539f826d8cbbb93cf7d7409fe410aa906d55e8faeed1c086f9dd34652950b45ecab0f6ee802356b56287f1cbf22815e13a5cad313ef83c80f961b4346aa3ca700fdb7e49627b1b451365de300fa41f23ad86e03beb088a2153a0855bd798e5d6c8f25c3fb4ededabc6ce97332c52c9d676d891c2ce69098b11c4f8daa0cddc7913d07db5c30322caf135a77ce972f9cffe0b87346868bb3f3ba71fec0f3a0366cd9e857eb00b373836e81e879c0307bb3c6679e405661bbba8615bdc18b85e381dbacd35e291dbcb5e89ca892c1263bc596921020ba6dd1d720b6dd9a94839e276d8c42cf33059db88f45b50b3e3ed2e11a0b222594c5a85acdad9eb220c2d401b0167c9a1afab6211e3bc680f247bb1c771e97c04ec93212fbace26356116923eaf09474e921697c1fc5d9e95057d205bde923b33cd9b1e984193f55f8f29e646b3e61dfeecc7540d14d19cd3e7cd7817e29e8e493c84500d592e3f0e4f99d4091c64327c6b3fd95f0b525e0acd8f97e83361f47e7a656cd39e44012c44ce0944203f2772012ab4b8a7f96483411857528349b558194c82de0a5ca12d45ab3ae925162e259976ec3ea39d561ce331138041f1dc88496644a06ff1d6108698dc07361dda12799a9fda9d4ad955584af9a9fd7e9cd2da40d7038dedb1274cccc073a41b84f55dc42a1ea75e1b7522ce34c24ae38c4892c6be6af39c1d0caee38b25f5ce133ec48265c311c6c9c1bee954191d1e6d7a26855926d0a57ad8a9ec50679cfb51798729070490924f03efe0ae295ea553def32b1e9db7396c445d9265cfbd787168874e28e30ad4bbae558210681ebb35f2d8cbe5425618f1323143f606f24070449d1635627829ef24001cdb77c8aa9f196e0d571b1f0d8581d1bff0e8c6850c72106a79392df2b00b397f3b3fc992296267059cfd5f687563fae90f365eb8c3bfe190dd3021b83328685de650c15a435c6d3af8a34284bc1d0471131040dbdbc92a5be390c018b85d40116e8631d276ec5bafa56f133513a88e19281ae8dce69a1fbaaca64a826ba6cb3261821bb08a5b09fbd6f718d3ca4ce6a81e6401132a7c499ace814041f9edfe44282a5651c80a5d2802ada161f7dd21de5838c47b4f8c56630615086ef6d6c3538780aab59b4109affe49b801abbd4ccc9fc5cf082cc0eaaed10211777fb78c9d784a2d1313b1398886aed2634af5f375246d0bbc316dd03b563ed5598815b5cd5daa828a8e72f17dda5fe1a66d08f8a35386b301f1ef6eacec546d31959903e704a20392cecac66cc85ea6c079f597d49d53fe633447d60db2227ab87b00d285e48d9aa4a09e6052d1c0752c6a5d3e6296878a64ca86b6e16171f825089f6f26b6e3c6f4a9fe4b0345802a7c8915e9f87e918eae94a2e39af20948661907ab3c64833b65a704c7d78f0542927e9d84083ea124406078600514dd50302675b08a9346f9c92600c95108dd1a99d25cf7f34c821b13cb8c70f02e37b5e62cc3d514a00f0cd1ccf106c6adfe27330f5014f54ba2b93d064f6c383b5014422e213b1d3c3bc7efedc79d4723d99e085c47d1b0eb3d069afc8a0db04273551e0c4a83ca83b5b1880ccd7b05d9c302d1f9d3a99531699982685f6e196fda1d1caa0b8e2f80a715463c0853507e77774e59d3556c68427268bbcd8caf6e88713877171b9247a4b4f54c117906fb16d2d7c483c0db7a2d3089bbff5c7061abaa9b34c52fcd47a8fe361e793efbfdf7375c9b322d7560ae21ac1349e75d3eaa740d740399ae99b141263b661dc64016a20af3b6cda57b87057a2708d204901d1b33c052a2a05fe818838cc52ee6017d6f54c9ef34c483a1b7ba45c45cfe6c994df8f0e1173646a47dc5c572b8a93f5f599f91a63be45970a07943aba97560a78c8426fa1e1a57e270214d4b8e600dd67d02148e839685b5b0aa0af5228ecffadf0a420d60b66be30625b481faf2d61a08743d952c7d03847b5ebc7e00f6144185e53cf7d3311f920c281901c8e29ba3c1dd4d8959e58d18df2068cef7f83dc973a8248a37a37db416e748e8ce387259e16fca5c880666e555163e96d04f2746bb081f208e2d7acd93b2d1108608bfe551a545d8b42b1c6a01ce944b24a9963faa41b4127ca7fd6088cfeac93ec251ed63ff2552e94e01ba135e8c957dd3d2d99f0c2c1bd492858964f1ccc2a1f47ffc3ba39b407a3f282c8602b964ecd2695bd6a5a0622696600e8ab8d4546dc3171c1f15c5447b4a26aa04452370a32d2aed72681c3e6d20ae24f1152009745771451182a9c5074af912c62a1890997a3ef106a2fe3eb4e5c38e269bd850c613793775d83591b1e412d092d076e24d807ebefa6092265c2a0c3c275f1cdc1e2f9c51fc851d0651fb4bf95af9ead31c76ab857906d0c8b97dbfbed6a9ccc715f3e3727c56bb62ddda1dca16e7f40585378fe9cfee213b9e9aff5144019e00f5b53bca0f215dc6b93470361aef31619c427c4d5f8dc13ebb3d5b56525c12e160605dcbfea7e863f403cd653d247b7c8c010506020cbe9fa69484b953f105163717c65034da06b9499bc6c8a546f08ee305ff9691dc7b6899f28bff42c0f747d6affcb6d042ba7fc43aad1c274982e6290af63aed739c056582994c2362c20d110a06c5918b6231904c33c60341bae7859566bfbb173ec0f452035d75b27323da8ee06287337f3ff27cb5d9bb20c2b2952b8668fd74986cb4eb8c13271eeb5525559d4abfe5bedc29f429d5d0d61e74c3f3a60002526df72c30bfdb39b201d28a69dfe1dadfa99d6e7d1ae6b70a1359b937a57f7b88da5f72287deedbcdb2acc3b705d5a8884a9043fa5a3e81249f1c61a78ca5fe72e0936df8c8f4cfa866705e0eda96fba4d7eaa7f90ef3748b15bec1029535895497a5c552767571ca8b60a5da7062f24917e83a44f25975b2cc0ad51f42d8acc5bc0ef1b35b596069a784be6f8210a883b074fe8186e875a062fe0dc68fc6c086fb74281e96f1960a4353cb5039faf565260c95b3aebf4c6ce34d9c8f00f582ecdbc876b1d3f65283cac3e1cfe5e234dd23c5f92ae4a9e34bb23c2ce38e6f39d0a88d81f4125a1a0c6b7d66470067b5497fae40abbd2b20ffec81dc77c3e95272e8dcaabef4b4c39ac90d1b298519c5ca150ffc04cc3a178db159bab261b827792af9f3a0e3dfd9a785bb2425bd46d63d56fbc18bb6672578579e3ed120fbcf688c09dcecc3aac68c1cbae97fbeec2423d7cacfeef4303980f63682de27bd772b8cb77276e18ac50de6a29f9bcdd4e2ce77805ca3dce5c4ce78f5225117ea44acb817a5dd81b5d4a905747dd2bc41894434475e976242a232f6fd755fcfbaf04133af944ec4d726624287cf2a1f93728b210936eb92ea97d57fdc7c7c7f848fa9f3c0e623fc7d93327f1873c68a3ffbb518cff055a5fe30377d78e06c47147770b85b74622cd8c5074df7de4f12c322eb0c0535d8929c94442b2a47ff638c71ae58099eadf758418dc0cbd02630acbb922b0ff5cace08258ece0583ef247b2b89b9057cf3ed11bc12c0ba89e7e6811da46e0f0c4e0f0b152a2fd7b8f708025d576b28cccfad70772693303c7bf9131e1de5c1a18505e4d4245bb281d5116d34c5411de983f312cc55d05d3d5e7fbc04b01057a4bd7fbde791b06f18d9cb332397b9767c9f1cd0b645826e97ec350891aa671af5c511a904ef0ecf776393c9a25d207bd46936bd177cf0b8bc896d8c26ab3fa56960796edd0ae01f5b68d8178ed138b1363cd4933a2d3f445abdbaba2f39bf57e31fdff79b70fb05892e425017a0e31a439f35d117b8f8b99dadba23dc2c9913d0c2aa45a0f81e19a2fa65060e9f54174b463e0a8e9c05f76fc0ce81d175a865f0e4019eee1509262b8d931dbd978ee5e88260017b1eb54438e3ff1f4b37cf1079fb6c2f263d9563c42adc4ce90c735de3282648ac0dbb99828f6f3e4dead06140d9615a9ce3d2237358e45103cc258f8126bfc81cb961af37a707203e51330ecb27314a761806e8a2275ce500219107e13416a208fce318e28bb7100f71075562ed101dd64537e1158ee9b717d19becad445f978e2a820a85a9889cc8786a9b15095929dd84184b1810a6d19cd98f4b6a18d88c743024d1d117dd7e1cf3b87c450366b0511ec0fd17ba8481fa21bc244b5839cf0618b1a8f97050007f47f1c73a3800e7d9b788efc4a782361e11d17fd334983725be267546578f414fb24afc4635d9240e27795f586fd4de642412c0df4881ab211db9a04b06c40d5f268933ae10c38918da11909778673bf7c852989f3530ef831779fe6dcfe1dbf8167762d4640dacb0edb328be0e6ccb022da481bacf0f58bfbef72f68838b1aa0392a913f071a1608b15faa6b0bada2714f951508c9b2826c32d0ad037474ab4bb376014ee9fa8ba4190d3102e013e4d1a60898a660aa5e71e32706295b9b22f34902138c9bc7283caa40d2035afddc71cf40d1dee9322557d8875e6758095375148155c91c4ec837e689b3ce059c6f74df5705128a96b7b86a2b8d0969e6d6863848836cdbc1f2a3cd45b11c1ad9d49bd23b1bf74136d030c17a4fff27effee5ef036a59fb35a687b0aa105c5439a5518cb1010302676e1810daa844fa17dd0d144188a4f323f96a9197b3d49b435bdb1ab83f741dc126a141288de228468868121c7e668a2160bdaf1952830e1bc50cfcd13663e4a5d2c1993dde18d8c0f1db6d2206eb12032900f52744d972436772c7269923c904b82161b95b7a116fb0d801c52f2ba6f210105c272c785e7c369218f67ad21e25da184166a84b4e85c2478f19a199e50f60e57ae1424c630bc8da04be847608ab8b1e6a90a4f74dad9dd727027b10c8c0051de4cf9ecb1f4925863587f494508d0e90c1c517911f08647788ed91a03747fca5f283faffde86c0d8aa7074f311891ba265e615f62a9c50c40703f5423e9e0f2171dbc5a9edd6e750f49cb8bd3e2c7867fb4114d9611f48f93bd31a484ea42538d6b456dfcf61aaefc6d4a1ad4c0033090099a7470304a6f026f716ef5570316372c46848b2010dfd6a3e9f0d189ef1a99460dc618c8c96575a1d7da459f81881c102094883e8a8dc57c44aa320d82d422921468c439e4a85a0a19fa250c1a6e8f73d2114cfd2a8e9bbe03f94bcd9bc62a043592a8d39772096bf647fbafc9d51ecb17e7b1cf228eea1007310c3a7e91ed212ac809bdf216f0da0d71c90563a68ceeb83e55797b32ba28d0f55c582ff0152a671da01b863eb0205eafda95dac0920b93a4e1e7bedce463e51948c8c00a05342c40108314e08d19dca68ff1682f7cd2bb47da815f800e2337ceb757fffebf69c21eb9842d60133bb3023bc6bb2c35f0996de5f02972f59a64813dfe3b0af5bb5993bcc8bf1f78135ae35e93b5e2b5f463264469bf747166438daa127afa85f6080b84b2147fd3674054f07f4a554cbc3770c98da94a85d8db2b4013cbc6c55c3d9f069fa9c54a125d4ffdefdad7b5ea58695dd6afa445467a8c8028d95eae41ede5d5c7d8d970bda083faa35cc397e3359cfa2dddc890b38dbb6090f5613f8ca4493bb4d52f32ce1911e237765248fd99389e832a920771e526b15d79bbeb5c8116084e3d8d965702a050a0b5cd5dde78a14a1610adcc692bd1643709cf119d8fc362f94795089e085cfbbdb01b18a1abda2055d2d3abb3a6295937addc05f5429e5bfd20769c5e335f2afb46c1cad3d5da45235c7387a6ce276887980cb3c9046b8de5483ec846343987ab0cf4489dc76ca1c36b8afa78d5dd886b340c3caa776c1b9a3626e44533212b7005a7ac474d395e99fe40396d6f351c7c7c9f1ae0aeca640f01c4005b8ca12a07041b3a8d593c93c7a167cfda8bc5f5a0f10f2afb87b5a9b20a46a3c5ff58157c3c500d0f8ff48985d8774edc9fd0eee091b089a5049db42a01eee1abc991f12c3c2c288f2469f033d0ea35c4ef1197d757da3bbbae230fe4c94e0fb96e35335e60ba3949bea24ebbb343c9954c89aec151ff4ff9aed61111ef10ba09b2abc388532ee89c282e25c228422fb7e88c7c2f6c4aa8af1c858ce47759c43b3022c64c3f2b8557c9cc2ff477a29658cdcf88f333234209b18a9465ab16e3ddd1182fc385d6047877b03f68e3a211d1a6ab1a6dd63f48cdf39a83e172c1a1630052ec92a7efaad6ca8934eec04c6ad5f159c80996ebcd10dd0e6f2f1c842d960e40de0b6d4ba46d660a3f65e56fa1db6cfc76391ef97a02f5172776cb13d9319300fb2f3c3848e474a933b0dd2b8d74909e2b6354d1d77982a0ac41020cd8d729679d35657c26e4581bfe287e3c627a32948df873910aa91ae9208587fb4fb71ed7b516b8540dc5ebb903397be49904e8797e98302a1e773badc97df769664a38d5f97a795cc6d00dff3d8435bdce68e30eda1a92983121328c60fcb80b7ce55fef46b22fe461df90a9798d85551d9ea090373a3802d28b332a9bded1cec6e8a0ebcc70a5c75dcd395451bbd2cbd431b84d0e23689e48f82b377be4e9ee545074df9aac50eb4ed7bc140836df50ca1fd58578c4fe0eb43b1cd12055753461e77455a59d9e0d533f50cb060a32e80c3e41c89a37795315e49c56814d1cb438f083aabbd0927dcb35694fa6eeada1ef5e77be61f5cfc0ea674e39aa4869ff1af7cbee6c7f913f9b1a50a3f7c827f83657524d85e0b791082d458c02d29d81b8eabb8de7f0a7f9b22cdc93b8e89a531b36cbcefb00fc3f69c1421c0c76002d71ad6dd4b608abca187888f479dbd05b363cd9fbfc1041193a0ffb2d829e744dc9b0246a3f193c63f715c28871f236c5f80a6ca7916fdd780a6cc9dabdf1ca0f08ee5d3b1c62852602c3f8b90e38b8345d8e0ec82c995b8fab7e0d5c26dbab669535cfe40959ebca40d0b5f1002c3ba07c7e7f18da53af259fbeb997daa785330e4bfe33b493be3e1c9879ccdc09d33fb76ea3db41b00a38770b7ae2702ce3bf5cfd9c1b062bfd24d3e47568ed8b066fd119b6385d2252f1cfed6bcf71778a3bff167371acd28f0ecba8747ea258fa6602fee293c998c102e2360203ac665f0387ee4a18db2d695cb875d13817cc3bedb00c80da3c0ac8069345f61c3de0a784ae14a17fddf7d1ca05fffd4d2f2cf872ba22cdaf0b0d7d4abb6873aaf6eb621e23468aeb368ff01e330cfba4969000c013b46cb5d3992eb434ae24b300c46e9ae3bade60d10b269f4e10c1fd11018b8afb1c9fa1f357de9e702f90a7e13373751f4f0b07ff0233c467d01e0aa52ba4e8ba475f0fd702fe4cbcbf997e6464fd142b725c481843afa321346eb9888990a253420ed2b40a1192d06ca5ddfd81b4198c70da29e56f62e737c801d642ee5c211787f23ec243224b70ba29b9f84cde748ae304edc5ddfbdf5f1613a37968c307a8d16dfc843b8917ce1ad03eee3b3fac554cff28dfca65d6bbe2ea039b98fa05f77b8ac1350a695bff65e76df8aefdd8de65c54bdc269e1f35f722c9f6e3c1a0f80ef6f6376d6d72c3b356a74f6f6dee47bee8abdb3ff9c6c7ef82382737ebb2c06e505cf7ac25497e5facd60430870597082c9a74a2732511e59801f20ab3da34e0949502da3687b8d7bd341f62b8114a38722f972cf43bdfc01e7a576937b369ff8bf8677a970b8f55b77716d6f7ec2de3ed46b2ba8f8bf39b11dca6399fff353bf9bcda003eea8b7c26a1ea4de7e453df8d9b688955c3dd73bdee455213ff36ca96e4ea84b4d1bfad264db939fb30fb4d5339f387e652bee4539921816e53e1916288c950b2ed0d859f731e802c8fc8f4f9c928d14b34f175c516ebba27b980892dd2e885604022f3d259af7226aca228e85d642e6c055ae2f14561f4c01ec63922810878124cc3dcfa71912a3653847a91bb47de225d7dc92c4c0d0b4319fe3036d0bcc76bc881624d17c5dd0692659024f764b2d84da4b14d08752ad56244431ca41aa85f5c6512bd5fa82eea686cfc42cb7936cc9c20048a953af01be494d520588e14651c51093b4a6705198c320d209c16a92290b5ca245c1f4566ec0933486f585e630972d353b3a7b272ea65af660dde9d1eeb6a1b022057f6c72e1676e320ce1e1158f622378f06fe2b130b99c30431126e23fc8daea13933b30eb3158e40d88818adf65edf962001f7404d09d80b08d40017029250584bca4b1d582790dbcdefa51e1e6912eb442732b4ed19bafd9f0cb035fc427077ebc09d38f2be33d6a165442cf5f719faa8d3d04a3dcbccfeaeec3a8b18760cccd52fb282ce67dec8676d99e9c4b3106b2a1349595e4813f192a55a19669819d703edecc3a2beafe4013e0d050d845a6275d080cc36cdd97b0b5684414dc86f1989df1af81d9e780ebfb6486fb3d91734cdc34a6c22c7b96d789b4387fa464d1a048316899847fa1900ea2380a2fda201d46d3878e53336646326652ce36173c2bb364674e8fb4012fdc041c2a150598d0c6454edaadaecebd672f786f43bd1dd904d902cc4362fced751ca3228317b21251c5fc2c75a61671a8b1450c8897d994966888e735794b9ce0d76a64490f01e3e566189ba853ef2afbbdb0b34077fe5a9ce5d09de08f18a5607b09201b59b2731a3fdf65188ba53aa781fc613f1b7a808fa619ea71dad5304b8f69da8ce6cae67eeecfc6176064808e7ffa463e9218d3674ec5a5ef27c007739a6866cbaa71236fdedc687eea843e7c8ed182726ffb72d11994fc067f29a830b0e02e593fdd36a47c52661b7f5094915bc0252de28b070d3b4fe89e1143c3ec2d468e0fb1d0e508c8b0a37c4585ecb7643f8c3fcd584a590a3d9697018236790658bcdf51cb513602457506d1b1b59f34ab4ee81ca6a2740ee05474c92ed20401fa52ca929f304fb550348cb14dd340a5088f3ae49472e829e8d726f3257ef189bb975f568eb2127bd7c76d45b3ebc0a081eb4a2ded0fc11ad2eb72401e41432239cf1faed2c83d9a16b70edfb02a26213a1b65a0eb4bc6a94a53dac7987e4f2c0abd81f691e5de23a6a5a1a068c2b274ba450397f1c2ed08b0a0eecc4f66c9d8902a86881be635b6f7291b35dfa3fa597998ccb41768674e32aa4bce815302dcc271b832af201a76c3bab982ca4a37862c3a6e237fa3515d71307f41a04d3caf4ccb416fc932b148f52c06c445a56e947be00d72ddf5f771722824eb94a63183593571aa0bb47d15e9adbef94f807a80c7c26b379d74f33e12f334aa58b7488d6c541e23dd74a76e8bc972f87cf5f4ccc3c48a2c49ded4f7e702fd938cebb9250eeb8fbeca823ba053aee452912a70463c44514af781f9004d0c1045d9a00ddeb3c101cf7010598f2d7713a9ffc870fbf036c4531fec4f89e4ad2d70b5cdce872c150cc0b0bcd2e6cd329acc25d3911c00cc0d01126ce6026703aefd204b2ae482bb8f5c1730c2848fa15f8406b450f990a40111f6de0ce9544e1266c40ed3cb737dbb1b36961161b99234c45d8188b8f4126d7b0bc22d3b7b17fc1f22ca0721c28689ef194caa11e380172aceaa24ffae78eb4112de345cbf8adb4158f73bcbe681ea32c731ce15fadf04bc37fb63cef3b1b4fe9c78a8e4e3e055d538be5248b79a6fd38805330b1a195f8f1d8536fdf8e40286442b438e23f5720b15072e01bba6f56d62607fcb55bf5d805102008888bef3a8f56a4132e224a15233f9717119d53d4da062bb85716bdbfa611fb5152687c860d86834b11a885a7f673a547afcfa1a6247d153ee73b36f21e0553df868f4231447a3579ec86081214bc5a075db10aaef6a480d37352683a82ee6e058ae0a36dd947af9c5acc54403e3556029ce343140331017bf4a3367b321badcc658892c83ee05e730ef49e71f4140ba5495bc95de863bcf6d87e52c0db19be55a8762f6122ef1db171bb30dc10e573f5b9a6bfe63b5ecf25aa45ba3692472ebca31533ff5ef2b701770b45d3ec81fd21aba8dad410330ea0759edb2db8b03493529aaee9e1055277f9f648cfa76bc4c5e83956b4c360d70a9eb3a43cbd0a8b5f9869c15e486e0f42ef39416952f70b72f55779e6006e8527088475fac26f50defe533c7ce1a1e68e2cc3c68ca96a1ddd49e07d0624ca925ecc72611099e948e37b68c46984d6059e4f8cc69b44ba204e7248694171a2be53c2b40574b0c06be09c52b2d103346e8a955750ec633df1c445f1a65b2810701498cfcbe1d6fc8465edc3add3a63cf9775d071a2b944836f6ad7747fd1168b6293130026261f9266fbfbe0d6a1c3be3ec5179dfd3a9eb0a305c662411cc290c5e261393b2819c083cdc311b0093a3fa7cf8bd9ce286a18a26eefb0b030ef80a73347fbb26c36ecf0b1a4f5d51e1f64345f1786d422a904f72aa93a927439754f4998798e65b33507801d08c30ebe5bb2d45aad4ba524ca5ab9caf7cba79f1d1719eb049cee2505517e674ab130a27c41f9f778b8e5ea170accf78152f31207376bca53b86df4c38ab27de3a928ab0e789ee628e005cb06547c299652b503b90f947ff49131a70a71a4b032f089066463afa8262721645d46493d68ca548cf789c2838f10232cb557c5deb245cb90c0570cdaa742bf2632ef01485bab76d7b5c8f3f71bfa53e5e2a2a188772bb85e6dc92926f07e1f92b75c93a955338363ba73418703f7a28151da4299a2a84ac00328bb2c1e4c267aea960d2dd1abf2c11e674693970d3622c4f1ac255687694d69c9f19f0a6230fb2282966844e0e28f8d34dc466cb2f340ba0337f8d208d55a6a9010c5539572c33f570ca73bff9ccf3042388c3e0b9e58108d67c77dff7162e3ba2a321070cd1a477878bd447fcedc7a3f048b000b5fde2e19887a9a6ffb2bc421683e298bc398f1343ddd5dee68cda8151a559c4e2276105c6ba9986c11e63fa6ed79a1ae6d1159fa2fae385a418d767f6d19c6a0496a6786748604ccdeb2271b0c7443cc356507f2265387b7558311576ca63703b54b206ccbc3109e65b930e70bc583bef841caafc0122e567731b103f9992cbd3d225bb9138fa324c3538569612f05b15e6bfbd51a9a95860c30c138f45eb2a31020c56a61f2a7a67c5342c195d481f559a7864ed0cc053d18857ad7b1d03cfb6694b3c4339980c2c8470adb8559828b633a0c4109dc1ed1ac3a8ee1390d91aa46e895ce0c2204e1db231ade0992a118d0f8c26ec5e7a88b4ee5c16c1eb4047a4a5b2c524e2c5ccfb54eb2e0010caf2e3076f14a13ce41e754ea39eeb44d955a898ef18fc9a15d2e7b7dabe8d251f992dc0db5e4d0161dafb7d9440913f7f9d6b600230cceb10a7ac9ef9265ef9e50d73140856eb37c5f49e1b00ee9b7ee457e885a5ac25e647aa4cd2e684b42ba7f2f7ad10ea57de05b045d6394b3b8f9420fabfa6342439e6e54999564af3d290a10280b7656a17414f1c55012a5bcd20b11c0c86b0f0e3a6d8ec90d281c633da6d5e340606bb6329e591bb866fc674bdb32e4ead3a9d1f33075b36333c5569e39753ba632639cc2c748fad0285c32027826747eaef2fe494ddd4764ce2ba06d2d9a34a94eed5f9c2b1af3cc8bc0a03f85e8b922247308de6ca0a8fbf00acd5babd1e540e40bae02bb292c62031e5d451e0f3e10a0b64907db742b084c98a2283a2aba1810ba4eeca0e6dba682d43da50992524a49701f3e5ce9930923d13fb85fa694524ab27464613a036b4da0b56d3b5f12940eea0d0d3dd6d98179d1121e2ca70946c6fe116fb66c55b283b50f9b2f3b051f3e4be5121d2cd4a8bdcae6d25319d36f3c5f72b098d24c2c3e0713e45eb6f1e40f94e080ddbdb3c749d3735951c32586e3f23f4ad8584937c245ae5843cd62a31bacf6bcb539d750467f50b5c08e0d56baa49841a49c68a1db8d6792a641b2f2349a60328db8aca4a16ab0d239d62c5bbe969eb5dd78b2705690685e87a371e1685252301dcecbd260b5e4970b1d73e5fab56b769ac1822c79e7377396fbf16938303f02192ca60f3a33e60f657bd029122459681d6aeabda6dcb1f78803c36a8df5a2ee366f75f6cc6d3ab2065463a5e58528a64ff4ed9b1d7c1a0e8cc65304481674c83554cd51a1fe572c2f9accc73a463853ba1831eb2584faa1e8cab965e81b4f18c7d680302b79b2468de9b277f576e309f302359c415e56f367e454be6d90a57b1b4f946a708e6858625a54bfb7c4b86cd1598a58c1440d326f88bd6e3c352e90c533c9118e6395c7312a44541ac7a8a8c4b0c37130068bb9d4a78d3336f4dab60d726431335af61cdb86aa614b5d70962785783d3adb5fc59688ca09c650495db82ce6cb8a999b822fc27eea45cb7275dcb6237299af3d962cab514cc7af34bef758531d9817b4d258eacef1ee3ec718ba63e1709a173058aabb5d27bb76442d35afac6d903d8491d5539ccaed6639e610abb4ab1f13a6a746d6438faf355290b1b3d79266c5d59fca5cda868f98776e3c35aae913c0f5f839a6292147fb992a93166fb1e83493e2d79a36a6dec6f305a6c38e681e845125700bc59834c5c65cefe7634734d356b264d4526aed92cb7402b6507cae3d06152b82d175e3c9c23a46208ccb8ae6b123293b1046c1b4447d341682ef5f4bae6374ec2af290221a33342b23538dd4fa43e6baf1c69333f0828554256ea815dc085b522fce438a68ca38637d8bee69c3d860d3976fe309b306d468bac336e9c53b6ce3960c17ac7fcdc4106b116e6b4fad01bde840353842d20b5a9056db7b359faaa38de7a78cd1c0b4c4efa2c84230c157bbe82947e7dea74cc2588ebcac6858a0acebcaf759bd112794fa9c8d206ce4b83df7e0768c06be74d891cd21662c84b8bd8b4c69d2d631453886b7919806df91152cc79e13db169d2a065bda78c274388d5da08c17a152ef39532932d632880ad2a36df89aaf83b07de710222d3d2a726e1b3eb54a464c2936fc7d0aa327e5be18c345e768193d7c771453b0fe716c4a3d17d9c5c49e188b531fbde7a3674ee6eac35809b2d58e9f9b9e507efe18b23ed55bfec4bc1342cd1ee70718cbb7557a98f6a53ab3bec711b234f553ee9c61aaf62c51525fac8eabdee7762b6eed70e3c972a465450313d3a2e1c068de51b032b17a08b26cbaefaf8920c8ea86dba276422935729c9208bc58de29ad37a778db6e2a35389eddc552e91a723755f74d31e60291400da785d33992e9c709b63bf725f8ba657271e37984d3791964317cca8fa2431b1db5db786a5e567a0817ab57bdc3c8ac39e288d8c61326a605c87a861a8b8a53cbc7a97cc381d1dc22d1bbc4a242703d7d3616a74989022d56ba5ed462aa9456b58e1bcfdf828714d12413b41268beaaabd27795995a71ba5d87f4df735dc61494c1ca4b4c54412986e548cb4ae29618178569896a64b19862df4d99fb9836d5369e1c1897154d663fbf34122ca8ac2a75c7c4a9a9bcf17cd8e100e9b0325adaec2145343e82354480e4c77addaa1d22ed7e866a379e9a940dd312b5d54a317aaba75be82e3e7ffaaf26e8163dee8c9ffc6532998cea21453433f0f4d17937e650d45d663512d320179a467742cb2ab9734acc09652fe4a5aa9c6eef7b0f5d39b6f1842e1c8d1a9c8e9107b28d5e8c9f984666a5f0379e6a703a4634f9852ed758e7b7179b6e3c93a6f5f43da8b1354bfdd2ddc6f3b9b04cae061b43b120b795aafc4f8492d5ab9452a605090b4b662d73ceb16b8409bfa5dd786a3c3d05a885c3d1b0bc8e46038010ac66e560667e5450b17631729490cce298d8796f844f75741004cb7da2b8debedeadfe379e1acd5621d2858a162a405488a81011a2e260199d0e22d1201f580c2aa7bac5a71445876ce3506a3cb0d49d55356bc858fb531bcf0ecc8b0a912e543219989698c9c00ec7bd3015946234cf438a68cee8c06a57de32d3a7858d62db78aa10e942858c4c26935121224445e3b292b60373440807966355ae1a4c8fb554d9369e302d51a320cc4a67050cd3121576386e8714d1a00da4ce0534a0c1400692a6039168328de674c4c117c7406a341c9823493aee022a16b8408489b1406ae046a3712f2b2d11a8c0839a0e60d161301aa0800326f00009703a1081074060020f70800322d080959616ce06186081054040011148c0b60452d6e0a80133011fcd09019f5283c501d6602d1430406a382c46382c0e2840cb0408909a0e07864560005808c00201500480170d5c9160381c2b1e839d0d54f1811ea981463480921d38494790496a3a0cb2380dcc1a900325a9d1ac01f5e9c01c34c088384a5a5e981a190a60f1c016e682a28146ec40008f1ff060438be7496171830a68442ce023e86862888135000d3632a111e3056fe471068b1c2e785ae08234145151020b0660068c1d2b383247191f07155c4184081970d831069136a4800b39c4c0228c3686f4618810300a01c623047d410314d081820304d981177358c20e38365d6cc00e383627d8801d706c9ee74945cadac01170c0312949052a4e618a5290621479e0218a50dc018a4f78021397b0841d95a0c424241109483cc2118d60041d8b50c449224ee64044209ee7e9801d2c5ed4218d0dc0a08d40acb8200e9b0f18a1422c841d30010d8e1fb004a0e102cd7b9e27152fa0c20158d080e7794a5e7b01d42180c70f9878430b1f7461831bfc00668e035c81821498327a6041812bae7881c23381e7794aa4f03c6ff8c00b1e7801d35901630b11242c3080850b64415890a00258b840164e89c11ca2b8e30b61482718e1294219629420e5481c2947be0a4ec4f13c6bc0e752b289124d28c20d4f491b382c753c4f0f093ccf83d22302cff370e079ac5843c923801e54b183941617b498c075b470c0f3f468c0f3f460c0f3f458c0f3f45040029ea747caf3f4f0f13c3d10f03c3d0ed0a300cfd38300cfd36300f04961c1e229c5939246049e0c1a3f7ebc54c0484cc3824198950abc40989517161606040d2c1816282930802393190402d8e1381018715971ac03d323000d10408f003480043018c1f390008be7e90180e7e971c5f3f4b0e2797a54f13c3d7a3c4f0f94e7e971f23c3d7a94a0a0e214a6789ee70a94523ccf73451e78882214772802149f3084273ac1093a36a1894c3c33b41002e18067402c52de1d4fca5379526660823abc1ca4bc12780567c318de161e14de125e049ee7a57fa060e279502ef13ccf1af04812222e02cfb3ad10cd0878b43b3c8ffe065a063a51c526d63d671001460466c623601a0ca60fc0ae88c353851dd010c58bea0c50d03882c607de804663799d3c0f0d1d9a3b684a349c97aa33a1798d8f17d05185dd47bca08d177c0f305e408317b8bc40189d350b714624ce88a30717cff394fc6ae6e10c22296774e08e1eaa9a6d70411c485c60821e1f70c1113449629ac66545f3e34787b349530c2de8440b06d182375084b4e0122f0865ca000b2d18411e28166841e6d90d388abc3b8a3ce1b9b0946d1449000b0a81892201e04497154528d960c10e58200412aa148c19779821c721ccb08426fdc68c67c6069ee7794acc3863db052b10431b588991b2023056d00032f42a2ecac8434a195f5843f3fa32f83250fe0754400515d440055ea800081d4d87d3e9e46341c40e227a205242e4101a65974102888c20858804c8b083089c65195c41861b649c41c6066a40860e605c981a1ad736c518738ca184e779504ac6d083a6bf13630831c5f33c4f49ca1899bc8e14eca0bb798e147c408c3b4c3e90020ec0e63ec421061a1658c2f3f4c6418c06f4c1012962943c4f127644c381d128cb11c60b438b300660028030cc08634818286889b0a38130be401833c131a40e6d94a0e4378624a08413ac8c00861c60c480610246807105309600c60f1e7c71df1d08b1434816841c799ee729119288e77916f0a408a143cb8a0b64d1c017e634302f1d8e1a2f2d312ecc697ad71072802f3cf1451bb620e48a537c410a232f2dcf05be2ccb172ef822026d7c918295568a060ada80021ea0a02405055f4081169ee739c093820226b030a7d1c0bc705c931041c153879220a360f7c205415282ac200818ce295ec491e2851b23f0628d142fce48f16203cf93e2c54a6bfa91002ed6e01a24b9a28e8e29d858e9441d9d3ad85899a38e4e1fd85889431d1d39d85881a38ece1b295c1422850b369e15f5a40031450a90423c2940daf0a4ac6104205800c2460a902c9ee7d9e4820ecff330e29428763c0f4a259ee7592305081552803c210548129ecb4a1a3f2831893a241109483cc2115b54d10846d0b1084524620e441ce279500c61828d0932f13c4f4a09f29052020b3c4f218c909245191e232e2b6930180d0beba010e2799e2b500641823ea490600329ea119f8727e53fa6f81152dc060f163fb0f821884000e20f7ee8031ff680063de4410e3cdce1799e12143ba0aca10e6680031de6208737b8a10d6c789ea704650d6a48031ace600639ca408631b018c2c060f8421c29a801285e10238b2a70a001c587279894b0818d32cc71451573b00213ccf8c40f328cf0830bbc610757a31157bc2cb600458ac92ae0141ff0831cba0b1b090c91c21f04b1840c50220f51d821461c27ff092090e8020a17e0d8c21529993d3c0f4acc1b4308920145c60e2decf3a064010b57b0421133aa40852948210a6f40615fbed302d601117400a13c010d27b8d104262c4a1290d0e2cc782e2b5f90a1487efcc8000b3230a4c5ad91828122a46420c5082845208209ca10da50953254be7818083ff0410f78b0031de460c960243f7e5880011618c01849d65003094a0c0acc8f1f2866a0bca074505ab0e245903258586286f0ba784bb3701a0b035d491b232f2f281105a23c14d750180a1a281a94173c0fca19cff3a0b800a5053f7e28921f3f521e1024e50159b4b8359e07a5080a0b50cc4059c1cbcb0b2371e1acc02c921f3f9417c98f1fca8be405f9f143913cf81293c519efcdab298b33120b11b46004aea345162f48169c38821f3f3af048cc8b0b1492850b14025bd810e6f292c5081c2c2326be97172dcee87c980e1323c9e36c01d361475e90248fb3c5c79c81521298c65bc9e205491a73c66ec764a1001b3c063b6d3b2621d1bc1c794951404a8a027aa424e00e0e60f1630309c8c1c73858c6cbca112d5e568e30232e2b1dce062686b11c69594949c1c19117226a7080a4f8f8c3f308c0034f8a8f23293e22f07851821404e4e1190191140458000f07d838a206b085b90fa0c101cd0690c0ce919402c8914200395208108107076cd0600603700386c1bca40820082811408100ca03501c90120037d6480980908793028034a400608de759e3b9a45481527a1c494149c4f35cf154d1837520eb2c666666dedddddddd55555555554d29a594524a29333333331325945042092594504209259494524a29a594faffffffff7777777777efeeeeeeee6666666666dedddddddd55555555554d29a594524a29333333132184104208a1544a29a59452eaffffffffdfddddddddbdbbbbbbbb9b999999997977777777575555555535a594524a29a5cccccccc44a9de9b575382d112e4614129e6c02019b023c6e3238f323c3ef260e3f191c7e6f1910790c7471e0a787ce05189c7071e73787ce02185c7071e34787ce09182c7071e20787c88028fc78728fcf0f810c5169ee7f94003328fcae381f6118a4d3c3e428188c74728e0d080c74728a4f0f808c5cbe3231424787c8462018f8f3b4cf1f8b8838ec7c71d74787cdcd1c6e3e38e341e1f7794e1010d3ccff380c7011548c2e212067c1c30e04a87087b796e91b15ee0e3b8645ab0706170035da016990741006192342d321b981608b3a2450686c319036af1a08b1aecc84b0726612c58b030172d3a31ce715cb4786981812f2f2e50069c4e8489698dad3c18b681897919c2b6381293060786e312848c24302b2ae00c69090261984b90ce635b3cb6c5182f1b98ceca166c0bc8c260c8809d18989813b0b030204960569a13470d222b1d188d1197e7791e14063c0fca029e07450109781e9494e741f1818200b8d22172c62a12840c0d178b8b30800c599a31c218638ca5594254c045186068ccd0020cf6f21c7c4290646184111123c9c764d102618218e1b0bc20d065c588cbe2048c114ea4974e86d9099bd8c34b27b389a4b30282cd32b0f0b18903f8d084193e3441878f4cd401134ae0c0064a9ee7819d4e70e2794e3671a209382e5187974e2649d3bcbcb023cc65a565a5e35e56de4bcb0b53030b8e17cf739289174d1a9c172c3e300206c3de911706c3b8b0040f2c71c5f36432b08543063c01841ffac08727aa5b57bbdfa9d65c3f537aedaa63c288952e7ef367e3d7af19a2636547e70c2e640bfbb52f62716ac6dedeb929d5da2a6225ffe49cb596f6bd739988952fbe778db5e73c3d772673ac6e94606bd59139f7974c10b15844c990a65d983ebed4e4a62e981c6245c40da36a47a6afd51b62a1cee7d247f5943ff55b88b5eaf9998941f62851b2614288a59a7d849a1e22a6c89ffa26980c6235165dd26c0aa9fab7ca6494263551c144106b5d46d7bef1dbbe4c4e1388952fa64bcb228c8ca163cf0313402cb66f1721c75864f42ffe613da4ce1f7a5335f1437eb2086143a9ccb5aa2f4cfab0d82aed97b6dba17f827cf8a8eed4e1724645bd87d5b61b47151974f7b4dbc6d3558874a17202152242543cb930d1c3aa6dff550aa1fc54bfefb135b6a4108c4c1f261798e46139d6922b98148aadb96606c3b8505130c1c38a287173e2cdf69f62bcc3f2d4d82a21f43a9baba2b4c3f2cce464fd54d9362ba5494e438a684aa0605287051bdcd6f8dd26f274c8840e2b46d41cf4572c327f0362328747dcaa3e91fefb37f3736ccace0d6e6ed4fd672287e5efe2839fae39841a4c4484a86432308e1d69411987b59129141dfb2695fc221c9632e862f34b2a9531d5deb05a23bfe71663430e2adb78aa10e942e508a7c345267384d3794ab9211162e4ed4a254ffe57f98f11da7d5598761ff9c633e645e334d985c302249351438a684ab660d286c560f77373d67cf48d3688091bd66388f5aaa34f4c2d536b58acb9a89c3c3dfc7c946a58317e529bcd2a59438ad2b0206aef1dbff89fe8211443888a0a1121ad7198a061296cd56f55ec6650b173d913a8008941609c1b2667580af18b90955b05a3e3cc648eb01694523031c342a8e522e690c187dc6bd5d1a2d3b92479593962e27cead2b9047f73e3407c5192605286c5528ac96ce32a777ecf840ca9b93904dddd2ae5de6d3c9b0e9331a87d49bbf142159b956b3cc1c48491c9c04e767ec2440cdb2d726aca7e0893307853ea32a5f386da458692244cc000f31fea3f047f618369a848987c215fe2e674d54bdb106a3a9c842b1d2270e318904c06122671ace81263943c35d52de56e3c3d6305132facb4de4a6982ee96337a17b607265c50ed6fb58b904ac99177a3a79a52cea942293dfe8d274cd781091c2be9228d2dfdb2f6de511097980d196e259379693404932dace8a87f1ff1fabf87d0c6133e8e8b267c9c21fb265a5870993d14d5fb448f35516a6192859558736dfdedb54f0d061a7915225dc0950e11215858719955fc8ccfd4356f57589bbcc194dfcf76dd2a2b2c8812b7b45075156abd26555829b9e7b0d375b28deb2c2654583155136c47e9354bec6dbcb3698c4a73f22293512122a43d152242543219a4d98139a225593099025a5e4ea4ac08adcae586ea627a4c514caaed4522984861217570d52bd544dd36893186ca162a4054b4501143c8c6984461254b1b9d43eaf963769afa8dd5abda124aa5103e866de3b97130ace35e341b981535342c2cace525d5040a6b1b82df31b9e8de9a71e39a87e9705e529a3c8115fafd57d76575a5144ccf86966174dfd1a84b260c07c9ca116e81891396b6d5f675efaf5d32f418f4a27f48110d121337d6a646e9c5d6cb28e993c1ca4b36617d6c885969bfe6103fb6f16413262ca6203fbb73d95057ddc6d36585b568603a9c1798b8d2799b59982c6121d54ec9f37566328f2493c9d4e42349ca4409eb17327e97df9c76b2b7f15421d285179b17e6452693c9b4703a27c864f6f345d38714d1946cc224094b7761ba161944ec923241c28adb5c7ba6e2c64f53f2828c137cb105176024e9705a608bda32981c61a14edf5c6aad254d4dd342b4d8220c215d743431c25208417eda9aa7d6c0a408eb918a889939e927979e086bd17ee354a6a993ea21ac7f7d8f944b9bee16bd3616a7a78e9013aba60d79e3c9c2b29286521c9808617db3850c3616bdb18b9e64601284a5aa58f35309c1f7ddf7028495cd9beb92f3f48f15fe60a14e65af8cb89f4a30fa604184208c89db2bd3846ce399494c7ab05063513d3af7d930f66e3c7506263c589b2966beb7d1db41861b4fa46b98ec602942afdd3eba47c931d1c15297583b7a9558229876639283f59e6d438e54274dcbce2f2638584e3db6da88bcad267338266c2cc838b53b748f1aed6b1b4f4d312849c5621a3f253bb81bcf672941c5828a195a951c7ae614d28da73ac572cef94ea1b4cc696b6fc414ebe16bbc11328acbf0f3a56150528a959ad3b49bade90ea9073b2f9c8d27291654082ad4506ad890a9da783218f6d2a6663b25a3584a35c614afc3e7dfe78dc3c719a250e6b19e2933474929bbd2751bcf7f43091e2ba5e4a674d923f51a34a25e6cc145a6bd3180a878a10244850c15224254d61670a5432493c970aaa144146b3b1fe64309a14daa5aa6261025a158c8c54dc7fe215e6dd04118f63299d414a2e48ed5d84bae2fa653ff924a156432990cca364a40b1124b5d7ee794b17cfb3a302de6e53fb112aad68b3cbedf687e7a62a957ce8f696b57ea88851335493a30af42a40b954c26a32808259d584e9982aedd93693f569c589ccfeff5c25584a2f3c6938b4c2693f12145342e259b58bae935c8df94fa44cd4b34b11cb6c6523e6fdc870c6662a5eab6fc185bebc78f03e3128403e3b2d28130415a389d133878848c8c6b7ea10413cb99e39411f14b986ea30a912e32190791b0bfb01c695961946c945c62a92bc82e3e841ee4f425e313234a2cb1f4c157cca2c3b788d9379e9ce8b292c96432f04812229b9abc44891dcbb1ab6ad517f9e56bbef1d4a4f2a0a4128bd5abb6fe8fe93725b4f18cf91c94506229c396c97b5153c84d6d3cb5062593588e3eb1e2a6d63342a5369e1a954a745052c74a94d8a1d7d87b286d4a24b1583786943db727d6e922b1ba31a7b8abd63dc6e728514a85c44a550d31f8d21942947ae3f988d5142ebfe634fdb7943862b582cf9363dfd814849b464923d6f7bf4e2f26a4d225cd832e6a70348f71583ac12861c47afd9499a6b34caf10a36341859e3e6bc5f231e7849d98170e8bd37cd21c89d1a4c1e1b0785221d2854a0a5480a808512122444565b7e7438a6890942c62a97a6ac536a542fe386d3c559ec7a8b4ac6c5e3248549ac7a840e7c2d1b0b03087163a97305c4ed0454a14b17ed5a6ee8c9abb9974e3e97282f42f473859928895b9e853442dc6e7e7e21c2b39c5d1132a76a83943c452cbcb58ea8856b9d8f0108b3dab62cc15c5cff70cb15842e6d879c7d66dbb108b93a14c49b1f4983e2116238eacde693b4a8cde20d6b7dbe7dd9bab9862278805d34197ddef0df9432d102bb79dda85bfaa1b430a10cb9f72cd4a3d7db2f887e5bc15c3866f599932f7c35af8da7d73cc1e52d6ba0f4b97795d520c616b30311f965bd6f8bc3d2f7bebba87059d45e54c31a8ddfe921e96538c15c35df80a75ccc3f24fa42c46f6203f7a3c2c56eb94bf478ad2b9efb0d89ddfb5efcf478fedb010da54e990a974ce451bcf0dcc8a1a9b047ee19904a6a1519a75580a66740655b6f69a33db78ba40168d834854b22609cc4a871df91c52447382123a2c86efaeac8e3bc27d3787950a3a45d1e343abdc580e4bfb5b54711f35b3a610909238ace7947b301d6bfaff0787a552b5a7ba1ea1e6e72b7983ae624fab52352d2b4feaa9527ce9b8791bb1732c71c37a895083f9382a7beab4f1d42049e37b11256d5809d1a2e731be965a616e3c91a4a1890cb26896931f4ad8b01a832c727cec5817ae6e3c372f3147940a912e3a61bc679788a2640dab41e831a598dc796b0a6e3c359cf76794a86175c76e1fd593cb085b6d3c3b0d4ba5e7d654f9dfbe6bdd78c244c8a2616110e64593064b4ce35421d20511212a0a96a061b973c7e62eb243cfac67582fc14f19a16a6861f237c34a6cdb3b73985a7faeda78b20a912e54caf88285b9242494c8b19083af9da27e745d232e9a8446de6250081095325480a87ca102440526068c4ca603d3e8b09608c7808f332493c964321065828fe3b29c69f2464919563b524cbda7cf35d658da786ea69618971232ac670a36def4387ed4a4369e28b5c4b88c617dd4d7f7d9f94de33e4d2d312e6258cead5b88dcf2a7a6f1379e9a97959461f057eaef492176f6e73c3e7ecadbd5c36635a4040c0be56beaafa74711bd68386f933b302f5df285055582cc106abccb14cc8446491c2b31851a1d36e651d36b2cf1c252ca25b3676d4f3516d1c6530de8a21d484607868c4cefc0bc74213dadb3ef3fa355efbaafd1b9a87a37dbb63b1756442caa36b4ed254af756d680ad70ac6fc510a5065b29a766522f4ab6b014821a2172b0d13f04d985d3d2c2711a16d6b2a212c3b4c455a285a5983b968e3eb9429ea403e30219e6654588cac2f28858bfbaf7cc3156455858e9b33543d61a82be0c598ec410415758aaa9354a8eda9453a84bacb058bdb7ecf9262b971ada78328c8be9c09091f9302db10a6b63b2e728b2e75c555f29a1c262f58e35629bca2e656bf1981899cc4b077e0f29a231523285c5d2b243d7586d7afe8d91c2dac7de6d3ac75637916faf4461bdc36ce756933bbfd76456f2467a77eb77a657aad2d5fb6a62ff6d53bbd8db0e85e5e0b38aa9756e8b0df548cb8ab692272c944d299752df37828f369e1a96981694232871c282bf99acb97fedb81ddc78a647bb4652232b5b7cb7c4b8a81b2b2964ea796465077fdbc6b385d389eae53b302faa254d582d597e6c855836f5cc71a24b10b7e2c2c4c86438d16545134b98b09aa5d65874f950a918d5129663ff9cf63d234d09612b61e9a6e432254e51d13a5de30531e2c2c6c864329997239c7c254978b6ed5941c4ad2dab10e92293e174a00c5c382e994c4b8c8b0f29a2b941091238a5e257f66467949e99db2a73cc65722f2ac43c298a2939c26a7ee81b54f789bda760324189111664acdf7d53fe55eeb5e6a2a408eb39ab8fbab0571fd4202d21c28a8c45c79fa2b2f717dfc653c3c202dd960c61357b4cf46e1d730df5f790221a1794b4b122e73f538e35f8ef5866322c2cd0a116c24a8d6272946c3f93c1e40259346bc0b7894b82b0fe6143e87f1f44fc126f3c9f4b0ca7a3591694006179ae4b0599b1b6ba0bbbe4074b63bfe7fadcfd0fa5231c8dca9897958e112ef1c142ce65832e6584bb8c358deac162ae5f6bfb14130aef5876630945458995ed11eabba8ebb8d52456b687ebbcb5f832dfd7b1de27a4ebd053fb4ebd24d627776d993db6c4f223b19ef2e61f335db9650e89d55c1fa7728cc9bffd118ba96dca5c3e7f3021a723d63be8da333ff50b9fb311aba5e689607bfdb6cbc988d5db7c35755bc7759b742cd731355b6ba6d6747311ab17a51761bb72313e4d452c4d503976db7c18396522963be81e5ac57c13749973ace79165db6edfc45c262296c7f4afbf99a223957988951a64dec9ad2ad80ca52156bf16d33dc26ed135ca422cd5af3bdf2995dc104a42acf7ac3554f9b123733888c554dae65eb394ccd913c44af9327b61c2ecc45e2096b716196491a1e75a7a8058cb0f9bfa774aa9ccf787f5da2284e2c7d8dc3ee787d51a4af650a276edb0f5613de449a3a2f8da7f647c58c9fc2c72dcc372ab301d63afc5b44f0f0b29bf0793b5bfc7647958ecdd3e57ca5b7fbbc2c37af416c1ce676c63ff1dd643d44d5b8409a3363b2ce79a7d7adf55d5eb75580a396d28a26d86b6311d565410f9a77cb60ab19dc3da8f2cee3a6b4ea52787c5a06aec9de3d656598ac34ab0755325736f47382c871e622f21872939956f5828adf337c4603b941072c3520d9b2b979e41d80c219436acd487cc7d44ffbc29251ea0b0616944cecfd1dd3108dbaf61a177f6aeb913ca6c91d5b09e420b1d52eea23be62c0d0b6a63c9a5f422b7e4ca685890456e4ea6c8e9abcb33f8aa6ee4c81d75aaf6e517f9a5e68cd273ec12344366e4580a3e7aa7d2215bd568cbb060b24feabdd654217a08850c994101ca18164ba95a74cf4d8e5b3bb318d62777f89c4a3dc73c325f01250c2b324b2e328ab153c27560584e958ae8bd4c0c914245f9c28a8c3ae9536a99c24e4ab5b83550e258c9956b758e694b915f5421d205cc0b8461421cc50b4b6373cfc1a7dc1bcf5721d2854a260361565ed490221a1ba07461bdd4b2254f9dd8b7c7c785f5526ad784fe1e058eb5abe0734839a6a25ac7285b5809bd3776427f511f5c142d2ccff4c91c7e36f58ddac6f371fe6b124a1656ee7ba2640e3ef37799c96432fc8c850539b6cb75d8ae5d7206d3616228942b2c8df95ca2949ca5d79c6732998c22c96494265f91a49c028a1516fbd7fcc1d8cfa16b321b2855582ce37b6df51d73b4887908285458fd34f62a3b5ae718fc292cedd65ac25c96b11b631429acc6aa9552cf35732829eb841285e55a548af52a4a6df7a137166bff770da6566f5b2914d6b74dd6fa1d73a921744f589999b4356a94deee1ac5094b25d68ff13a8b2f7954286e2cc7b0718baf1e3dc4ad32994c6f1b284d584d19729dcf5282dd4ead0161625a349f280d29a209038509ebb555579aba28dfc75ef5049425acb8c91cebd614fa2b273e028a1296ea16b793ab4ca63e5d2693c930276139b6dbfe62720d666b8884e5b6b16e752d55c76ee9088c907a27a490429eaf5257ad6647c5d05f7a8a6284f538ba37df149bf9b135c261791d5244a328454031c6ef16954585e2b344762844e0854a1d82aff56b46382cca5086905619bd6af26cd4fe29397a0832e816a9c61ec28da773841831c211728433249331c26181286dacc7ff2cf553cf92316c4258aa1c52d6e0d36610a94409c27a9808c5071f620cb6732840589c1a52f7cda973dc18ca0f16b142aadaebcea9906a33530e376ab666cc74141fac4e09e53b5509df636f5c04941eac57f59934556caedd48bd81c283e5d425c4ed1e6a0d9dcb4702ca0e9652ea8c62648f0a283a58b99e92738e4c2d720f37283958dbcc2a93a156159fc64c464304141cac64aa8f31528c5b19ab8dea02858d15d3eabbe45cfd7aff4bca1798959615964ec5820a26571d5b4af075f7092a96c607fb3b71f49692f5146b7b7d3d2957698ae5ca1362fb3ad345155129166aa59145a5aaa55f760fc2092996c3cc452822c81a838f3cc9c92896b7cb6fce3eaa7b5bf89a4c792ce88b944598b639f6e8e1b1d03e6529d16bc99eb26732573811c552999a833129a56c33b9d5124e42b15e836d1f67a7a4dd9b2777acfcc61efb1b3291d7c10928d6ab658f206bcf6f86e0c6f30506763869fac462dcd653a9e71c59636dbbd18927567288791ddce84a3dd9c6730db849ea05c224699acc21453499934e2c8eaccccdfd37d3c7cc64dccb4a4b26a349f7b2d2b2ad9c58ae238a2a32e71131deb7f1ec703430d0c8e66fe215b6b70e714a88a14783134db022f58f52f2ebabf4ff845e4e32b152a27d8838c5c61cb6c6c47a956e916bf6dca9f796c95c62bd6acd89deb766efbf62c28925d643cecd2d6af01929732f499818994c26a3c9b7a39167e6665a95ee093d628a32d539ff642a5662a1f58fe9f9197bc9f4d5e000c9647a48118de68412cbbdcca692677b6fcde2c92416dac65293136deafa491d0b6d64ec4566ecc68924564b10fe32151725cee54ecd482ce58960e417198caddc7b1d16d34507266142d83881c44af75ca9aa6ee96092a68901e4ffee4098953d79c462c4bebdce770e717b4d44482603841347ac5551d14bdeb44e9bb5112bbef4ab2da6849e9c2623563e975aebeba65c4a08d1b156ad2b56a53ae1e6b69345b4b77e424a515aa4fc9f3b39b69d3091b6e4a67fa2888538a955511374e69f582256cbecf4c98e69dbe6453e07ab44c8fdb33bedbb982dddd5356c2de1c63393c964be3b3047f406278858bcec9b63acd3a3476d4f0eb15264d19dd3b81b5f6b9e186245e888a17af03147adf5492196eb7faf8c7f9dab8e7a4288d5baa9f8eaef3de528d1c920164bd95af5d5c5f55e3f11c4e2965e53bdcfab92620a4626f39a544f3809c46af1b165293dd6e96d6c2780580ca5d4cc29a8ceeab5a7933f2c87305ba63f95d9b69d1f56734ad74c8e587583ad0fcb9335ba1457f35d9cf061a1c60f935bc794117abd0786fc5eb3fe86901e56eacc97e2364beb129de461a1a82e9ffa63ab6e639fe061a54efe58524ec41a25c5c1c91dd6a798ca7ea173b0f5be1d96a796524704618229a3d661f58b6959326a84de3f744287a5f0c5c89e7f82fd89e21c56d3a69852cc951f67d34cc2891cd683af908ac34a6ab53976cc207af11f0eebb90493a745ef4879f30dabe3324fe8598bc8f521548413372cef961e7bb91b19c26ec3caa71f91c74c4ead438d0dcb29a4cc137754e41aa3352c15d94b9d8add635dc8d4b0d6753b656a6d0cd1f2491a96fe67b2976f9bf2646c4f708286a55ca5e79ad2661edbb9332c854ebdd564895c72a9ccb014fbdcdc8e2ffa42cf1339168c49775f6c08194b88294d2ae1a40c0bed4be5f6f72f956bb4f1ec129c9061f17bbbce1c9172f5eab57132860561839f6e2163aa08356631ac4791a9962931526e671c8695b6df2b84186bfe670a86a5cc9c58367c9099bfb0d42aa6e97144cab9d588e0388963ad8a9c927ad95e7aa58ef70b275e581cfbb56f4bdbca3eee6e17167388f1a66baf1173ec77970babd53bd5a07aaa916bcc70acd5c4d4f63bcea73297b7b058448f92b704f7575f99b5b0584caeed23b5d0698c69b3b09439f8bceea5d72d3777b1b05053285739f7bcca14325f613573ac90e9b2ab75f55d2bac7f2cae7af119a9c4a056613517156adb9882cfa9a28d6be00b87b371698b132a385b6d2db5ea14563ba9948e34a5fdd4baea58d4ef989452befb792285852073aacde9a386fd3a0aeb9ddd8b889f73834ebd234988fc1b276f2ca5a2b276fafeb1942a663228355058a935c7f8f7c5e69bfa329997b6c2c913d6632aa5a70a41d4225428461298845293496012144e9cb0dc7b72aa8f14674cedc2891b2bed832f796e63e610b9d7c249135673af7b8cd97384923b13167b62cc2157cf1d42cd6565a38132587941553859c2428a5b772f2756274a58ea9ddab56356df56937486709284e50b5b6caeffbaf143ee2ce3819c20613d143945e4b4f79b83ed8714d1203939c272ded6dbaba831a67cb08714d1bc13232ca56fb9518c1f9973cb8ab0126bac227bafeda918b7130c274458f03d15235a841023f40d1e4942448e9321ac574fa57b28e1ebb7abc7d9c04030568e68f162386963d3116af694ebaf922bda64d4da366f0d6d63b52f319c152e32994e24994c2712a5c389109642ee5f6383f9a8d543680c15202a28052a6208517196232d2b7d1284f51a73aee07be512f2084f80b092337a91b99ffa7fd44e7eb09aa587529fd5a56a499df860edb657fb281544c87f1f1423939b5c38e9c17290d55b558fa9f7ae310f1663d852d9dfd91bd2ddc1eae575fb5c62e7ec902a1e9ce860b92f4b3121a5ba399883e5f4fdf75f2553978983e5ded426f82819840ae9091b0bf5534819c34e41506a52b11cb142ee865699da4d1bcf8e18990c6469998ce2334c50b1d85be68eb5b90833613cc5ea44ccdb631c9f42dccf144b1352ce2e328329b22fc572fa9ded5f4be6a79c142b6a44cde835c79e2af6512c7dc9e12a7ef7d8a9751e2b3a7ecefa92c1542f311ecbffa17599606cc9185251acf646cca0effba454e56a289663662a3f39f60cf7e11dab45e522ea559eec1f9fe362028af54fa94fce5f5372f0d1273435a776b5a839d3556b3afff8be5f73dbcdef7962adea4bcd18badae7669d588ad43183ad133fd4a84c3801f312d3d1681e60b289c55a6cd5eb4cd13674b9f17cd1c04e9f302f9c4d5a1f986862bd4c1d5f29067f237c2e130bb95297ee941b26d6b7562e355b95da58e24b2ca60c664746cc41c8495962359498f27431e13a74bdf1d46c3b628429a58b3129a7d803c2a412f9c9dfd5a7da7c6e4d55169ba36dfcdce94f858810153e98506271f68b9bda3dd2c4f91ae5b2da3099c4823126945472ff7dca8f6d1ca5c1a48ec52a3947ad9255848b3293c9d4a46b1b4c24a16054b95093a97fd51e89c5503388d22d522dc6f620b1d6c1d4defcfbb39dc121090c268f60f50a26b6eb2e97933a62318fda4d95319752296c44a3479988953956c0b1587372f50ef23aa79edac272aefd919d422869627e86155a582cbedc4d57cfd49a601696e7c2dee6e6d89a3eee680516d68bea2594988b2d1b8b7f85c53439f40e2ab7565861a9b742b055a7829a623b7dc7f05dd2468f0a8b5faf538d2d6e33e5535828be7668d1b737c69c93c27ae7e6450abf5323635158f98e1835f4d2bdb11e6be988bd366caf49a1b09c8aa8bd7b6e5f838ddb1396336329935a6516ff9f13d6f3ecf47ea850634f9b1beb1ba184efe911bb6e13d66643deefcb5046041513d6c3969a23f7aae4ccdd12967fc7d716bd65dc8a951296e2f6b8ac11276141d7f85e72ea0926871e09ab3fe933a62023d85ac123aca80dad1b61b1a6f853838b10e7272bc27a551615b523e8b9de4458df187a19379fa694dc0d61b1d65eb91653a96cfedcc66a8e586a2d1f53689b2a21ac4ee4de92832ab6fcb44158d9ccea5f47c61e3365560061fdaa04fd91b727659dacf8c172f04514d3216e8eec212b7cb0a2434eea9c436dec1759d183f51ab79f39e5fb8dc568050f96c27eaea0d3d808fa8b56ec60ed3b4ae9b98beab3b968850e564a668db7116a19918a56e460ed53c40ab1a792e2c8d10a1caccf65f1f3bd9552bdd10a36164c84b4f5bb26624c632a9627f4a49a4a0ae37b29a262b1b67b09e9b732e8289e6221c611e53e7ee8c18ed0148bbd36fe759ba9ae232cc56ae81e2b0613ae4be42029963fc698e1c654efb0c151ac44eabb454ec41c740ef358ee35b6a9ff3c9b2b87782c986cddd3a24eae614351aca7a0fe3e530d43b17edd3bfce8caf572c33b16234dacfdb8d9353504c56a0e32851f51cb85bef0134b2976d490795288e5424facc7eddf217f85546b09766235a55a7aa44abd5711e4c47a065b44d0b1975e8af16d62c575cb9035e69bd4c7a789b548a18690c174ac237c9958a9fc9f3ddfe82d397c9858d1d323f8d45f4ce7f72eb11e7a31ba72faea59dfb3c482ea29b5ff927b04157b762c7dea48f927e589157b9558d9f2e1b277e7a9117b9458fc8e315fc7ee0e22f526b1f43153895c268454fdab634117196c8c3d0699a67f92584f917a0db7e1eeafbf48ac77b125bb647ecd913f48acc8daedebb4d91852fd1eb196d79143f8edb1d33e47ac4faf997b632942c7f43562a9479f96956a099ba7c788c5fb5a6ae75cf22b941e1d4b41cf7daaadf3639b6f116b5d37c5be5f71728f4e118bbde6ebd45b5acf145d22966e737a286e73d61c7473acd6ed0da67faf45eee710b17c953e43ac295e97cf1d6231e71eb3db4cdce99c33c47a3139b68dfd9ac16dae108b39758b90a9a4092a7384580995dbd5444f93a3778358eadd99d2d40b9d753b41acd4365f2d6ceaae905d2096d34ea89d0fb5e4640788c5124ab82c766cfdbaee0f2b9fd9631ed92bf4509d1f566aeb9954a2e4fab06284dafee36b494517f9b05a738eb465f36467710f0b66dc46dea81b5410ea617d4acedbf3d490c107f3b03655a7971e52dd941e1e1663fd069382cc9472ee0e6bd9b9767a8289127376580ac15608e3b7f5d8aed561b9c75e7a88f96b17916b7458e8bb7d83db3641065b9bc372287d917bec9463b03539ac66ebabce34a6fdc65a1c164a1c3b358eabf121d5e0b01242f99cfbf8d4bb7b7bc34a486d2b53691d42ae9b1b96f3541035c59eb394bab561a5460af1db98acb5e6c686e5ce617ca4ed1541b86d0debbdd7ef97fd2997bb4d0d8ba37beab4b96a6c685b1a166b10b54685a9a161b1734bdb2d7136bbd4ceb09adbb6c89d428c9fa56686f5e8dd37c8de1b6c949a1c4b356e28b982fd383a2cc3624e19656ba64b55488605937b9ed1593f2704c7b06282cd557afc7f951e13c3624919e4975ec185ceb1302cf8ec2d17b66773c6181856eb2fa78ad0b1f6c4d817964a0e137ae698f79d62712c065d4ccc113e7b9129e685a5d23be5d49cdc5815ebc26a99de208b8e1f4aea1917165398aeb2f935b7ff0c8ee558b775937bad5e2ddbc252f60eba6ede981616e2e7c91e6a844f9db22cacd7dc21ca169173cc956161255ff55473e5dcf213bbc24aafd33164af30c14dcc0a8bdf5f43a8eb5e1944ac0a0ba95b55eacfb96d8454580e1d4b989c455de7e014d6c77d5fd697babc4f0acbe1d37dea1bd1db7c515830a33a5e94ae9bdbbdb1a23b84fa9f2ea3670c0a4b5d3f665ea7dcb7fb272c85f45fbb9f6a913577c2fa97cc296dfa10b37537d67e8bc9a93e1b45d44d585129f736bd67c2829950dc14fb39b625ac883cbd6782f17172252cc74a41c6b645169372121642edeb5c3ea51a43c24a6508ba0799192bb647580c3751eae5e8a23735c28a8821d6e60cd17b5684e5a0be7c2fbbf9ba4a44582efd1f8aec598c0a0e61418509bbc1d6767053b5b110f4075326c3af42088b354d10424f17e127fc2a82b032363bab86d1753af7550061b56b0e2946ecff796c5fc50f567a4a5f7b52a939c4d457e183a5ab9ebe8eeedffdaaafa207ab73b3fb39e5df0ca9af8207cbc56c664e6e5779ff57b183d5204b998af93f98dcbf0a1dac5fd67693bee247afbf8a1cac77e7e8b14f5e97de5f050e50e474a8fc257f156c2c5f951cc14cca0eb1fd542c7fa9a053d99fcae93e2a167bbe574e59428494fe295662c9922bc8b98953df144bf5554b885b334796be144bd73bb172b5eff4d3936269667c992eb6bec7e947b158e947f5e9af5b4ae9f358acfd6bae5395531af3f158bd0e2ab4082d6a07f345b1604acea1f4dbd843951f8aa5122eabed08df9ba3bf63317dd1b5f61f41b13e2364ed3c2d4399f013eb9383c8bdc5d876e1f3c4da868923ff7372efd689f58d657bca934af0c1e7c47a4eea1abbf46f66bf89e5b1b56657af923f7f4d2ce71874b4cb1ea3659e89f5b8695b756dd91b31b158b965c38fc839d5fa122b3b76530dbe1495536d89e5fbd827761437c66e3b566a0499ba863431f65c89f5106b7acd21764a2c6e67fd9efb8eb55a9fc442303e37729d9d1253af6335777764e69224567cd0a9f40a799b7924167a30592945f0536a3a2496738e8fbd7bd54f2f3e62b562d1a1b48ef7df738e58edc5d4547bbcaa62b646ace4bc717b49638acc88c5f8698490ff65a4636543a6f225237d7db68815932d533145d8dc3d45ace5e720f2ccdfc85a2562bd3e7b330797f9b939962bb42cc2c71ac5c71e110b6a8befd13f9522758885de6b94ce3d63b46e88b5afd8639932baa7d80bb11a6adf9d9417adb326c47a4e89993a94cf41e5412cc7705f55f4f7d4a2201647a736726ffc4d4f03b1d0b65e47b12d4ce901b158b277881b846e95ff61317cefea3aa66bb1fdb05482d06337d7b6a5d48785bea11715b27a8fc987155f75fac434d932947b5889e527f5ba9fa2f8f4b0f495a9dad6542f2dea3c2c849ab3a1c69a830bf1b0544bc99c8b2af21d96ab5fdd7f8e7658c9689fe2e6cded35588715a33b54ce13218690a3c3529e10a24e49ddf5b539ac754f9b5b6a88b966268785d2378f51633bf85ac561e5bb8faabd63c42ac26175b2f7732f7e72546f58c9bd42bcf1f719a5bb612954ed97f81f4c11761b964acfd2e16e8b482166c3d2e48c10437e319f5bc3820a42b7b0297dc46ad5b0382e23e89ca2b6eda5342cb4dcdcd545cf862fd1b01ad4c632d3535f27e233acc7767563b3c7de6bd10c4b5d7b97367f9f5a87722ce5705fb18bfc9cae0ccb5122965e8bbc09191916724895b1a6b3fe3486a58825e42c5b95337a62584e5b5c4f10aa8ccd6158ed57b1a7e9a1e45d0786f5b931b5d20661e2fdc2d2f851b962eea5f8108b63354b2cffbd775dc2f7c24ab69adad4bdd4e9bb0b4b5d7b4b94227acb2217d6f3049f3277231893e15828b296345dfc6fde760bebdd328c4cbdd5774d0bcb3d6aea16316408a92caceda83c5da1dd67d5b78ad5be65f5d25e61b5a428fe4bcd07a1b3c272da60d4843032b82d566129a69b5ab38fea34526169549c5454ada1278453588e1dd38eacd9f8dd4961c14eed9eaa9748914761414fa40fb3356eddea8d05a37e422db2c7c4984361b967fc1ca2a652217cc2420ca56a486db783ce098ba5bf5cc5981321e6c67a9e1a212f4b8e3dea262c98fab5cbd4941c7bcc84156346b6ad1fbaecd525ac067bf3d3538b4d59094b5bec4c758d63fc4c4958cca27622e6de43ad2161f5aaf7fc3db6ae341d61ad3f67cab9ba47976984b530555b64dd54e68ab01a630f394dd8323d23c2fa54aa219592f7b587b0a07b3fd46a7f1b25d236167b666ff74eb18e0b85b09a17b9baee87a85f1016728d50364e1fd3cb0784f5cb72f9f12a6776f78385b613a5c7da5daae883a5d15183fb9e72aa600f56db46dcf0f13695140fd6bac81e549e29d1f70ed633f4efbf9b4f79361daca79b481d7c8c5d7c9683c58b1e649a8ef92dc4c1dae6cce81d7f0f3616f287564507638bf03d15aba58fb129d3c88fba51b11c620cdbb9a79acef8144bfd5bd6dd8f55215f53ac6f2eb7156c9650db2dc552fe0c1373cbfdd525c57a1175bba89aadb075140bb6be5bb5eadb2fa694c76a6d4e69a64e098fe57063534f2547ed30932896ea87a08331956a7d9942b176d9eb67e2a8485da63b5627c48dd83db3a63213281654b76f113f7e1c3be627967a9ad4b2e4ca138be363a5b9317721c74eace430d56eb77a8a19e4c4529434c5f4e263ccd4dbc472e8d37ba9d7f5bb7f9a584c3f7a63cc1bb2aecbc47aee75c294183ee4b1616229d4fc18c2b40826d82eb1228c2f3f7b9f2f73b3c48aea8ab5dfc24dcd66c7422fa27ecd9fa92d5925164cf5029344a8b4ececa6c2501c05310c83000003a6ba2a00f3140030281c1c8e45e3e1380f1451071400053d62609c2e30288f05a38140200e0542a1301005310802310cc3400c4351b4a495d9015734e071b6e32bd76f7c1bfda622362a78f35830cc6fca9cd5ccf474f0af12aaca3ac0cde8f563fb5f404f09bcd982eb6101476d9dadc2eeb605371140cf16d643016e7ba15e13586f2db85140e9c142c42c170ea0779380b75af85e0ed806ab4eebc57267f960819b05e8a185f456c0375bd01301418f167278f3007a5261bd06d01b5690e4509dbba3fee04edab93982a995c62424654e4b489752a4a9a45a12db76b21640987cd22c6d48a57497d2a75c524b366930b992989424cd4909ed928a24956ac93e194b57129324532ba9249b549da4905a5a2443c995e2935cd24a956433a9a5624ed6a916af2829caf90bf7f29c36a7c0ed39154c125593153248e7240ac95243524a76a93ec925d79674b94673b852e42f65694b95f6f63975268fea902d194fb724966452635225ef9c9ae354bf8e288447e2902bc527b9a5b52b39db9e3a97f7d43d7b6ecc39700ea7069139d87f70645e556022c7cadd0b0fb0b5ccbd182906b714cc312d61d7d09b28233b22cee9881df9d37654c7ce94101473aa41a5b0e49ee2922c6949c56497d4a424a94eae92678ea68872d941bdcc1de9d1f7d4bd3ca78e599063ce8113e78a40c3ba6925a7261249b34c0e3064aca7c630b0e352e83a9d0b318dafc5b754335dae9a70d3adf5836a520777e7343065328199831660187400f5c902a60fdd436551690d6860825fc5250bf36928f73c9fcbd3ca553de94c55dc9bc69e3b17c724486daec0d933f554eaa9d7ece238e826456039a90153931b9c3a2781d5490bce183b80e2a4646510d48dfb3434adb35133ef8d6bed649c93c0b4c102684faae0724e0566270730372882cc310da01adc802a95f45637ed3963ec008a8312609b54e0ecb152804a1370762f4d42ee951bf5d2dda697eeed9953a35a8f5437e911154fb614966612192b518b94a57fe3253875ee821b730f5c3897e0b439056ecfa9e0e45c0567e70c7037278249a266524236a93a4921b5b44886922bc527b9a475553c9b4e8dcb76aa9cb537e6dceef3bcb8ba29d220e064797b3952cb5dbe7a7c2f599b27e55805b9305368478eb96b1e0770cd8201204e9046edb925db1e2d02b13f8724ce95ee6ad4214bf751c0cf62688930d65cbf40f22d11a1659c4336d4711fbefbe50b6504bd52f1c631a04bc36c9649ad6998dcbb48a7d89b926aed251d208de584938f61869f57d8a53ad38dbc483176764d6b11e09e3604f07d8f459c7e74dec5846ee742e7fa89728cc649f7172d46874473191d00f10849ae84c6f47979706f0603afe23f4b67cc4f826e4588534f36f87d426708e1c724ef0714bd8dabf33dda72bd96e34dc6bc3733272ec68ce339ce7aecd3dbd3a5390ca8df7a97e9de515aeae8cf813bb1653661505af4362ae2e813982c8b8d05c44ba2655a12b4e220a759d0c343478ca64788a2d20eea65e465cdbbb97353f1b6b1475170e95c88a5d3fad450d908e513a8a496855cb41d5b494ce7c1e6e6413910645297583ddb973246693e8508a924d4874b6feec0e3f20490e7b8f09966910b3a7596627863bc8ef59ab2a07fe7c4a2bb36975fe1ea00a1ed2db47d3918b786a60e8349c4ebb502b95c918e58a642969f377ceede664ca45ceee1b5c10cc2d40382db4e42b3a024dd25006a145b110b953fbd5a7b5faa12787fcd0ba43ea98b2b4cc65da0932c1a4ecfefda5e5bfc54bd773eb5ca2c3f59de3a11927ba171ebd99748f1d5b588e0b54b928be023ed65997b47d3b68bfee1d166b92cf944a0320ae660f0b117c1b3d2bfa4737cf3332df326345041fa5da4df4368a5d07c7f7e2fbeab5cc3359a87c3894afaed18bf254e46c214cc10daa780ba73679c1a3dc19706532c55ae163e733d8f9e75566905ddd6a1a85c210ee7a21470d888b63f1db1562a62cab24afe1b26d96215d4269a4273b51fac840f1b6f96fef522c27526e2314596f33cd8cea0dc5595719fc273143c1e43a3b4fe5756438ae9880fc6db0083ae2d467a36674d0af482b0c0df4dc3593b3e0b4bb47681bd786abe809e3a0b72cd2c471c1a53cd9506febd6e45533a289b39eb4be2ee55064bfc7ee95971da141924b452854740f14598082e3c3725f07ef131e1d38661899af554e533e96bada5e1a1308c9dd4d041742483aac65f1b5af74c0ec38306b87eb45e789bd6f222732cb61adde705e28cd4f6635a3b18c98d0b936fe9453a0e34e6c409af65074b11db5df6e051103991252af4944536d928bfe673f761b8fd5f2cbd031eee3213dea0f9e84fa610e78b417cb315dcb71752002e56c74f586cee733fbedd05558670f14fc02c9d35d4c5289582a0aeb2c7cea29698bf92f735ceac97cb3c4ca23b1a68c5f4bd3abbbf0d108cc8a9ea58883274a2deddaf5578e15813406930207ebb438067b529137a3e9120bd2fd2284955570940db23c6abc847edb6d1be3820d6937a42cb2550632f03843533114b701c956773eb29342d5bc47c982c4dec2c10d67b85a6c2791158700018186ac94d9315d2e4a6e87206e06f05b0008b084a57e6a26592bb5109e2895c953169cf3cfb6d5335599d66ee65b6c4388c34c961795024a18e9930bf1ad1795cb3b8b7ff737665f7e659c9cd92d49c1f68bbd2e7045820bb7486247915964b5ced672146d821f19323a192ecc4402ef99c74fdc30434b61c434845f6067f122365b194d1efc9cc8d6b202c4b36f5749612616cd1a49e4078760f0d73d2ac93442fef082e5eaeed7ba491858f01cdbb7d1ba277c13901322100063bbbee9506d183b867cd86971a35563fc43d2215100d22a15b85a736732a032771739ddc91cbb069d31344d0d20411910e9178ed2e8f678a708b9860581c5f94aad9839f21499b5a89cdcce9ae4141ce4502cde2c863af3084d4c2d8daddeb0d29a499b764e910491b40429827b37c71cb5bfc8b27e94d04c0cca87a05b6f87c2403c4aad913e812b3241590eccf08cb8290943771df8ced609d6b9ef43c01129d151031eb6b52a28014e52f8508549fb1f5d8c11b6130cc436fe06178843437d49f8ae68a60355e7823751fef3d1b9da84a16135046520ca73ae1055faf4fa08dc0d325dd3e24a7b08c0844f14e1a3015ca085ec1a422caa6e4b9b5fbf62db9bc5d7e4013332a48413b2e0e866e5329ad4027eb8479ebd9ddab66848bb51aece4ec20879f981f22240a9013e89e54f57440eb4b513a4f2211ab520682974a67d0ffd4f1bac21f2b3b1b344331a28e6d5035646a305325d6401dbb52e1b45e81db75ee2aef7626ba2965a900d0d6047e180224ec320e6ac1b7cce5615e5bcf039484a5ac694bbc5a632a4fa91ca727517495fdba395b6237e287b3ec58be166749fcb1bd08ed8b33c94abb150008abc989a5d2daf91cf711c2286eac1d2a4539958613506d5a67c3ce8f02d7c201f0d223d1a584b7640190e741a2710bc30dc3849325a5d9367b65b80caf0542159c3cc5315d4c4baee1aa574121e0c54d5c5eb592128742394a37294275433a9282a243843de71d4aa82371426745c1a25edd52e76deae686d32f012f1ca5fe11e5339840e50c75012a90430cf33d342c4195b7ad1c31ea98b2cb76b25135efb4db260d859af071a080c7c3f92e6eed5f546d834f9c4240aaabba6c251afb9e3d006bb6f824df42301b2f5c2953d1bab1326448360399225f0358453273e697eb6efe09ac204226965282ea03265ec912dcad4520a0b123857dc19fbdafba505fed8f31b8b3cfda911ee9d3a0006447fb2387bb96520c467e716c4941f92b01ba6f24e7cd7c72c72969dce09e6e7d927b3314ed12a18cfd003bfbed601bfe833aec69f21355dfe1064e40b76616e3ba9d2e9f30768fcd7858c8d0c0b83b8a977c0af329b1520c032fce1dc8008ec5db33966600b972d9fc609e31baa02ad794e3338b7971551eb7c6e61857f512ad3edf221e6bc9d4dfaa8aa97a09d4c67e2e628b138c90141b2e52348dc5f6cc6afd91615c4dc5f59e25e0349dd39a0aaeea16e8371c3e7ea1a951ee4fd7640d4c403b467e67a8008aea2cedb41bde9064c5c833d113a25d62cde3ac9a4b3de58a2bb43ae30633b3dd219334db30e47b2140b9386d4ce964b280fc235077e37ef6969fdaa2afeafed5e7a17b6d4576926167fb343aa91af4367deacd0abd2cc2d17e26ef1a224c5baadf0a3db93053578381097ef5238a3a5638fa8552334a22bdf806ac8d9d5aee2ec895b19356cd43202c70d4c0c1548066828ed3694196bc63cfe5eaec2dabb0b39889d2dbe04d1bb83058dd866275548ef71aacd26bb25c10ad6ff2ae2990e1f40e662707e79735700b4ef914276533411d72b072433c0207c8c4d65b805b4fbff43626e61ba0f786d80a97fa05c521ba9f39cf97b76fddb2e889e1ad27a1cbd987ba8f899edcb9ec7d2083ef2a4378ffad7738f173a83d7165265b303d9308468bbaca34cc0922f0cdd3a0cd06a64085774acdf538927aa99dc8f350c9a69d35d91f747fa716e2d5d39bc8fadc63e213ac1fb72bea7c3052a361e4613481c906d616dfc17d09b97a99cffe03c0ee73011600028b465575e8cb76a5b0a5652ba5a40a25b0f45c3b701730b70d36741b15ce8a46d41de99830210cae36eeab85c881e8bbda72678cf69582d1d401a91e76aa8853c7e1bec64a82026c6177a43161d3bf92463c2f15ef463431cae97c6f9d30951c37ad9a02f3fcf86566f62b36bfcc456b9b8a20388130bf6ff6973965bd9e47ac2a74e6f9210564979bd1dbe110869d2dc9d11ce7691cd081571a21221fd8bc13f91c8f77cd330f2d019049718102383084e87a4179c29d124214549839e0fa953bb851cc383a16b3f4546d74a74ed2aa14f47501c87239be06d4124c4515d683bfb5b4eed70753f006f021f282de12d6ceae91035e53dcbb210a05a944f2ecf64fcaf22d5225fa6a41ec6c3936a4042f2a11bebf25e2987542ec0f28e513443c2c604cdbd07bd181005dfb71bef208ad3708aa4656e4d557dce0295b0a058dadf55c815c1e9e5ac4e212e8d4151afb2d2e8604d051767a6fb17b0819aedea1ec1500e4855e1713274b6f0660fc7ee2892ecebe4d96bc4d8e50a0fc84b900c8e8c2f016de1193aa6e134a8409a061717c096f90e10337ea5e95cb2db952605940fa8f5ba4327c5075c32120859353b43cca0eeeb3bf6aa84212d767d2affd193ee6af4dee128ee42f40216a4387df08245a3568560d8654b2d86120cfd3fce80366e26028bfcb9ac978f67cf39a74dc0132c9e7fe8bb47c566b03f89c590b8e95fd4d8c57eaccab2503bb3a91dc2a6e49e6260b7d81f215e8f9860fb85d1688998557dfed2153539d97d2cf06e28bc4166b16e865e8d856d86c3aa80ec971b45703c16e6add2a59c7267c5443f9a184e903cfba964b917f1983eb3f24694e0071522a83490dfedd2004f400631c47fe812d7da1b83d6b3c68d1c642f27757c382f6a9751d69e9eee1a9c2eb1388cb919fb59419607f9c667ad401179c8e5c1c517a846890afbb5eba6ec1b47b838707ac69f87715f102ab10a86e002fc76daa387ae21ae99da072d4c4b64e2f8b1dab698db846f076ddbda35c4f55745e6c507bb63da743dbe89c7bc2fc50924a4bf8aed119676c4f953c5356e8c09622c791cc3174a2082087a873500be75113c3055df359c4d58b520bea2585409cda60a9ab1b379823506dc738238f285938868041b49a1f02263580eecd46c492b540fd17b788802ea0ee8816698deaa745e4bbd00c8ae89cfe571fb91c46840008e5bb7dcdfa2eb5ffd3a5d094851acb7fc7c190b4a2803254917c6a8a42247bfaa7b9d1c05fff51d4a4406855a04f2def77dd5cc4c0edf6c6f815b7dd374ad0ec6afcf28a56114efe8a67161b20ea599658a43a01ee3e3605ce451907723f438b89aad80a67dfdbdb21df5bc84e5a7cc459376ea92d1f62ca2466e6314dd20b47529bbbd647caa16e85bace1915b005f7d123e24f56e79c57b55e3bbc7c24bc0d7b7e36fcd352748aa58281948987dbc132e3342ef7f79588474ef192874ecfd3b90c6d023f27fc1039a0772926ace6673ad5a7c090b25e5826dd8a17726ad293cd7e2500b89ba2757a2014ff8fdf20303029517f3755959c9090697c49975003c2f5a163fb8aee8eca541dac94e38abc97da134f66754a20e55a0c39e6c532dc4878c00b1629b3331707274c0748e617df0811182c15fe0593246e963ad38b0ecc68871f80f5e584d5b518dea3384e4d1ac5a7b9d58bb536fe27b25f0550dada19c2d2b38e192c4a26d66b1ab353443d4d48de3e406025b06cfd97b183b33cc3dc7e710801cde572860dc9af1e7427fd95f6cf07453762cc183e19c790ca9a968a34189abe2b95bc92d027959ead4fd54653d834c2c246761dfba8a7a9a7fab8ccc65fd4a1e8ee6012ac0c57c06bb95084b0bfdf3d25f398cf39293e52f0bafc44f4cb35811eb9168e3f51e4b497deaf4a16eb041b8038a3563078d0e8c6734382b514c026210394a3c2912aad6997f0bc1b8c981e484e31c031a5647a782f40c20ab8d32e0bc7140b2d83ad827cf1eee7a7c7e328cca14dff37c58b73043637a3fe25576a53b8c12220ec7705524cdf02c0e6845f6f75b5c3c4969281b8eb156818c7a01c7d43d620ff29549185a2395681e4c0538194958ddec6cf2cbfb83a04d8460011bc13d8780a7e833a394beea63881984e80f832b999129c4546300050c5a5b4db25544f804fc32f3dc62c239f9d5fa138adbeeaf2c9b17e1cf7842ad41127026f274df5e7f25ec9df339117e9d2e1f70d9aee335a7650d8f49e00c1188178cc487fa43f1e56ba8eddccb0d4020c00a50f70102843d75e152dccd9e1d2659cc6b9d3c381a7e45a521c54e97764652bdb4fc786597d4fbc0f0a28d26b7c57be5a1e5b58e4bc503ad23e8ad0dff6323750470df37f867a8fbd386861cca5f1639f898514f5d7ce76737a14ae77432f1b64091c07bb2638c159568fadf0e876e0c5f904ef6cea9dd3eb2b22ba59e41a1865d114291906699b4739cdc480964531fc400555a87c81c311612150497a3078ac6ba80bbf51716ce555843b26a7fb2f0f3905f15d5cab8a51d0bc440b2db0e24e4b8f9a9e6f54527d71b7180d9ed60e8ecbd68afe75eace39214dc7fc4e06e13273df8eb42d6a95a87b77c5b443c9500de2fff51dc2c36e42325daa3a1ef72c6b2bc14dbd1b593575d6b4873fef74a1ec6473708b97d1323405de2f46c6303718a580efc90b398cc47b2276ae93e24efa9a76322b73c471ccf08323ee0cd8e3799ce16e5dbcec5c1acb4c771b0b15ae8f8705adfe128ecb26de5e22b919ed800a10aee34e074c7b46c61a9c400571aa1d55b8c7558685f87f188472126aea007eeffa46fb90b754991d28c5d4536e01365fbf91f59114ad8eaf3ac249c1574521c625c6a1332c9cd755874699c630d1d421b9fd2459b25d8e5310ffd12018977099290c07ef7f7408625a305d9536d9c12ab67ca8b41bdaf0478297e666ab9f3d94b0a05b691aa9e2106ed3a24f7608442c680ef32261a9a2d25615011c5e48e67fffff5e38214050e05f54180841037e371aa841e45ccb2fdca260646e8768b1d10093fad726da52b9377d3a7749583b70bb4330e6cbdda135eec955027ec9de5a8c4aa4ef082a61998560882a4dfa8b69add9f7a4dc30abf303557a265340339cff4ae30e8c8e498879b5792a367b6cfa636e720cf2231709eb896a8c15f37bb67d3775f0823e52cbd56721dd698b126d540bafb6d95e00586199910d142998571a0da3df1b0f9066da968175a9cb71cbbb42d2578d71a13048596137cffd509b31d787f7c0b95ff2150de954bb7b2ffa36b3c9104602d94591fe4ba8abb2a638b070d7d2d1757eeee8205a6475e2ed4fa36f5feeabfe572a80db40af6ffc7b19b220618793a921e280a6bee5ff68a2d840ac53f4841b7ebe4bd88aa040421077144915447a2b63f1c13f9d4b041b338ad571827dff64f3e7b349824243a999486c40b43eb6fb89d8164c210463e894ca0bf1a37001182456a482018788fd3b107429d0dadf8dc937ecb273bf42088081b70ccf464d718d78be4606488c0033940f50c901855aa4214211dc338628c5b22c1cecfb28a9348dd0a58379a5da679b5633465d03d03f111020ac62682aa008eb0851739c4f4edb5dc7ca7c35b02ca0b287d7f81d099a5b4bd83725a3d3778e978367eca3add3bd1991e53de0da915d8429a57b5f07002bf654336704ed7a547f6965ac9c35a2bac484b12546bd6d41350c04a06467962efac355e97d17b2a7138fc25dfd4b5a2c7e52ac89e4694bf03cd0a7c02452c361cc31236a95ab63a9cb3404e7db434f728cdd618ba4ca0b51473670f36d90cef3c59f10d19538817fa18e5f47c35c98482830f65c4279677b80dbd937b91479d9120d578b7a7d94c09f957c1b424406c7698e4682140a8775c62a313409638c5657345c4c58da1e4f6530770d4a028e3ebf25fdcab887f5295d1c54ff2579b0eae98c9089927d530fb2dba016f66fd7f4a6f3d7a5b49b578f2e05312332db1cbd77543db4ffe57603a827091046fc3f2ce5aaae72a57ee0b7d4c10c75f1f6415af4fbead9a86a010d813c9e2e4234f5dace546bc1c36ea9ae5476ce93ef881858daf4aa342fa3f5bf5692697df54db99b0c4fc71e8ce91cc42d1de020c7dc1924aef3166d08093f289e6a1dc9341899173f4559dc81381e1724215c1fee3a5d8c8a3b9f60c22f86b6b32ec3a0000fdd93c5d2cd2ff4c367f30112cb1d25a0ddad499891620307f8fce095720be42ba3787a978c13c59dfab9765b22d238090049ad86baff6ba3bf7ac3f877de673eed492d45caea8bb5008b7b4cc8a0a5d359e798513962cce5d8b878da07379699e97e92f29dc56702a2636ad3a482e1d2d35a3aa71df506c8fa9917a96bae27c00d196c4a79d0d8eab9b10274350e37fb3c4fa0d242588fb1a7ec3c371d5d7c5191dc5084ef9ef2720151a9731f12108d9261ea3380651ee319ec00b9b90f3a3b27931efda9bc49c10c61b82a943171addf9d2c6d5339cef0ea6694c2d5591756979a17b0cf390dd0f017fed6497dc9ecd1c42505bce001af6e77fee58f6a64b3cdd5661216d32db77554dd749a0353b02da7f51e816bf17bb116f556de0b349c4e0212f10d0b433badeaa2b3a78a2391d4142266bf28b25d98ba072b97979a6e01f882bbd1cc9d01ddcecb4f983bed10d6cd412059d17a456caa0631c8aee35764436b79107ebc87b958793681f7231aed2013231c48b40c091dfe4bde66d3b7405ac2988305091208058d628d78dc95571f906fef6e42a9f05abb5acf4343afe21503b2179f93de8dfb489b010462c589794098d54b86e23d8712de43aa38c5d9953d1d1f986ccfc0dac05cc3e065cc8172b597e82fd9bcc180b9decfe4fcb69db8efd0b59406929f67b98e42bf134c7ff05e1a23b6223220d38d26af4b8a27a2dd8275a02cc75d9317e48631a4ec67d6f895288e0eb53554991c82c44e700e17dcfa30799c27f55a78ead671cbd93176d79fa964c6c565cd529903990e0abcdbba17625679876d5ca0c61426b7446dc3ce4d8d0615ba6aa2b0b910c9270ddb5f2d4f8cc0bfad4bfa6870a8b56301e8f3d4e69968e8984c6da34f556396c34cd5769a5a6f985b6cb8dc8edd331bb8d0d45aaa7e59303779a666d7a9adf804fd3e6aadda815c8a506bb342b99e94596940faabcaa48f39a57d928ca7aab5746d499c939acd4bc90d7302f1518f4450fc5491116cc4af723742519be455e20d87c24bfd3e6671d1e37988df50feaa550999ba92d9298f1e0e774ed8da28bae02a9e7f73b73eacb27769feb99e744be77ec9ebcf087e65ef4bf0a001dca76b1f002e2082d38e17526b0348eb556805d53b42c978a4d66105d7e9faa817301a336d94abdf5036b0fa8748b21fa8d54cd90d0ab053d95e34a59ef4a34dbe7d0e554639a61f4aa65f38deb40bf3186b7efae5b64eca6d885daea935a47cdb065e779c4713731a6ecff50527b1efaaf2851c2879866c7eab41f4e38e078eafdb16d7023548db8e0a7e828f57dddbcb19d04e82821c795eeff47df9d582cd9f21fe224e0f30eceaadf0fb12a1620bbe9cf0d59bb80099e9052b5ad5a39ce47de7d6a067dc2e4dac6e803f187d6a6e1adadbf7f6de2670be652aead7f921fc3b413663ed69a91f17edb10fa8ab8bb0b513c29f92b1eeee2b7f2cdd8019e0f65325154c973bbbfaced2ae05f0983e4110f55bf558b5da6dd99a3cc66e1d7d15c43b0bb5cabf1f46c523724bc69e6694bb77989ad0aaa85b6be8ce4b19fd99e43e61329947e42cd744625e5392a6fa02e17371f3ad741a4117e11e02ea412ce6cafc06afe9358dc1d2339e3e4beb6296b5ce72cbe83e0f0ebae01ae39a10fb22abec587e301cfd116297cce50523679e9a40eb3bc1fd90e086ff1efa1c040bcb9a488e54f41055120653cdaf84f45b95fd85e8320980cc93368043008edbf5ba1b5b64852087c911603e662422ca32b28a3dc1b3c40271bed1c0bdf0329a2bd098a7c901d610958ddf3d41274f5e8e8b46f1c039824acfda8b5bc98d6612083d35e8c23428fa702768e83ea91e8ff383a16318af347324a6c5857cfd97be9117d67abc5f5232e6bbb9737a826003f7609da9c07533cbe9e9e7a802cfe5ec76a84e933c1fee76673be936445de361637fb711f54ab7323b5488b58c21280f4de554902cfda0997d6ea3db78bcfc276ca49269e86009f380bd3c63ed15da75b8402b5e49260c8a90c4e0c9492f0ce293e6d16f9f29ee17eee45c057066510fe88bfb8682c7a484f7f88bef7d7994357cbe49b25f0c04c91f0893f0ee185e0ee8c5d55a5699af216bb5b5016c7258093f3649a180ddb3edbc276395a8007d5dcb3109a507d05a0a2381c34b9e6cd37bd148358d2551caca8b0a888c90a3155399015ab80422360911828d0d21b9a0e0f909e3c2061abc3a620ba18d6667845be6b8738347d5b9ffbd36662bcf239f687f46fe216b1a5f433b3cbae83979c57284b79e41b5bb1347d5013b6f124a6cffb73359c8706f94f223357f13cc6d3e6534cde9c2817e157fc1962769338e2fa69dff6d471dabf844da0568b7274db1f3775fea80009ef54173228891c262e602259ff735e77cbf3af02dce9f37b5eaf9c5a5ac9447207cbe7aadcbdbaa3aaf3f292d65f8e797fe8e098f4bc2b1e4fa14acacc62f5aee8f68fc23c55059389afe0156f186b072790cfe3f3231dde57e1edf2fc94622377d24d44d17ab56011d1dc16e474571943afdec6e979f1b13b868590c108379dc54d7038404313e1c44aa7c445b299b2c2558ee1b819b15351cf3105a9c44e993140df350089ef6b6705b07c19da7695e997899aa37080a7ab10b269d5697131626bd33e911d5c478401148fcab023ed580702bd1a866c8c9b61c3c55bda591ff052db5f4c01580b97fa557c6ecc2fb04046dcf6f3cd553f5975459b151fa74bff7507f6ca3c52699f157e14c1b525af5ed3ca3a3cdcaaebe76362949100e26b6e944f258001c484650e3093cae6aa3cd64c3f396896b14f04b837c9b5267fb2ddbdcacd2c4f4c9f9fe617f4b9b8f15aa3f7a5179ed5cbe574ddb56160166fdfc963ef77ebdf7d1ad0c305db06beb18530cb1efcdfddd20746c3b8c11ec9610deeb47f2fa30267cdde41112a688220dc72db697b4fc4459d9d48b007118bc00cd447527d7996920ecac1050984afb30b93bc3ac2f57a577467fe850ad578168d5bdeac49d350014b072fc6c3aed711b80e7972d39ab4238efe3faeadfbd6ab91cbc43b9cbd8f87141ba117ac4f8a483702ba9298ab37dad6964c3ea5796fdbde777d3fe4953abeb8851c6b77d8f7482c4396a5d7eb2353a3997a98b1fe70005a06d89eb968cbba485af7604cacf9df0058ddc52e823c6fae12c100e505f717c27ab0356ea1fc7ec17772091e0565efc64d63b8d4cadb2b5f07740f8af40c27f2079fc6ccefcb93bcc26c58cb96957ff4e45f3060cb58cdfa2d4754cfcb8dbe9cf402212a44413840063133a81e4f33d1046609e9aceabdd83fbef36667b62d3dfad7ccd1a1132c75130ab0fed98a81d125bfc6c47b74c27b79fa0a3d6a5b8baedd3c6a98d3fe2b97eda5d5b763b7361223eca818687590e0e1d9a37ad2a23cff3029c84832343f469fca562e740febf6b675b91263668fa6985179f3e2323fdd84176f607eeed7fe58cdaca7342ad0d1c4c9e081cdd6928ee2a1a7a34f10919ae3d5d7b293169bbe4fbc2f4708a17a9ff0c9569409798e990d4b3fe3505af26fd9ec688f9ae3dffa76e377aa4e6d60de5a167ea8eaf9f47eb58d6de7d20a08ecd5272c74cd860f772065ee399d34a7950156005a9230fae342d4747d1609c71d956240f6cf1a22d517f8974d4df2219f5d74845fd3d52517f8b44545f201995572445fd95481f9c7600983e30ff3ac81957e7e5dbf9a9d9f9bc1208a3edfe331305cc2dc2bc7b217c0ef8f00bb1ef8907ed063baeb856e3eaa7c91dc6604a9fd815c10da9530a0523c6b0d681ac49123b76892c66f45be966f758489d817936bd5bf15751cdb1b54c92bdc26f28191f7a6f9ad8805ea9512b15caa5140d6f4d3c34619d865d82b221ebf1e0eec12e56bbd8b0ab56abfad9487cfadd569a9344aafd58fcb7d7e1e07e990cb707ddb75e686e6db0b375741d0d4f4ffeb33b850487258a249bd27e76bf8c89f757613105b2ca7177c49afebb0e838dd1cd58ab0ce4f53c6058488e4c8ae4c43127024f3705e22fd3014f6a3874100fa272f0e58c486aeb48a7a031f52d98e2fdac69bcf6206d7910d34c896a3e9a889137951886bf7d076efe907d308d41bfb12ff2e87b6297d27b938ea79d7dabdcf86b76d3dbde6f4a93a1af863b7d3a216ea9d59f26bd89b895f3334e2e3f2d5879c626fd8370ca7c8cff6264ea7a1caa016c2cd8c6353c2a8dcd32d4076bf103ab806bc01812dac56f6aebc953775311aa55d4810be7fd9d81c61a7e59ddd757f80c2b6c28dc49f91dea13924a2031489c420807dc5b6f7b3b9ed4df31f10ef6fb82a792950cd8acba1c656eadc6f38f6dc2890d0057aad5fef984f89491b8474c86db165d422ea2edd115334bfce123d8db7de858464eefc65302beae06c8be59beff2224b1ae49ba9af2ffe2913f3a723ee3f91fe7db56e9260f82a0e65ae7131843cba0656815b8f21cf9b393f74d963bff7dc74d1e1dde05f0f88d89034461f264939e64a5939d599dd271cac85a5e4fb6b777fa7cb3b385cf84d6873a7995a78c5e6ac56df9aae77c1a8eabbe9196d42e3908a8d9ad9ea7c65c8778aca96ea8437287458fc2ae91ebd4234d36fa8008a4d8aa8180fb77726b92b33e097888b4ef0b7dbe159d106110b9129f78c4fff49d1f276291ffad74e3b0b7cbb28d3823f98bb2cd02237c00732200dd3f8f77de60ce0784fc8b549f63f31c8d5476c771cd04d91ce0e6a85d3f74945d0e95f60404dcc3f329d0cb2f306e1b5b3b32b1d360ff67818ba257aab5c76ea7e6ae91b2060f0a5d61d5725f1df590eca5f1bd023453aee3bcca3cecc6cdf6ac9d2909f40902eab4ee28aa5a5a7e211d88cd422b878444243c7d0013c5b6183d06ea0e43414e0b3a4054dd7a5b88c7b7c452501420848236ad479466301b00f60a645660084d82c8df100b4b9c70df909f09b67586dec75997f94ee079a45f1f4b7a42c394e80479716474084621bc8810057f38cb9a48a825a25a82a84b88eaa5433e01083a88d29321f45d6c6014417c58af9ed7174299b4121b63b8ab1dd922a76f91236a2aa6efbc6f0e7d37b5a345df02753f3f99aab522c72becae654a192d3160e1a58115270b16d9b228d603d2bc2a923bd41572b2ee8ec31e456a1542a0f7d08f1bf226fca857fd2ac8b94e73db32dcd183f67dfa8abf78006022cf60fd9e6ea626b53f8f2e80b0dadc6794519284245e80553a2b260e4dfa815c01d84ab4022eaa18fe4e251a5d9bfccad83a3eb206863879357e95129ccd48388268f7095678fb198ed88fba3464f0cd98cec73cafc1b66638972007eee16d8ec877de2f85dc4159042990c0cbf2959d647de4aaf2ea09d3b455dec09023937f6e5ff845f1ae9d9d3814f80a7add9466e13ebb199c68deb17a5a502a81caa1031949033d75e4e3c63c98750c4deafad8819c19eb4fd9d57b88ee6e683920f9ec50dc9bd7b1e091d35a9d0475ba059f8d5c275d44a49c4146a324fc2daa6c0905d85eb98b3f3899f4a3a96948d4c6d781ec238e77b233a8bc114ec9aade95750248c913889b0e3a757f7e6af299538f14697487bbefd86dd7b2cf35571851dd964e567c39d4cd8cdf9f6ebfbcb875032e3beb664fec2341e3d519ffce67472ad0eb37caf665c98f6a401c0c3ab318d0549dfbd5eb50dfc9bfc95346dccd649254698999175d63480afe57e4abe161a666971b96c38db7270c608dbebd055fcd140c7a938e3616db6d781a97d54679919e8667855f775cc6c10406ce9df70041544b92634420f3bb30f35387ec91888c2bb879aaf7cd1f45acc90d8088670b3ce736489cab4b2bf3296ff80266cd9a0efde647be4bcd3770af24d378d4e4a6d08eb385c08d13a7e3f1b735f2601493868c847d6a6da55e7e738ab2f80c70ef90e0cf98fa6b7c8c238b5372178ae20e13b1295e55ba5d69bc602fae7f979e9cb797eb4ce37fb299d7c3611d72ed56fe9af8d16be21b8dc3956b9b7baf7bca2c6b1650b9f81f571382563b0d19de894f57f6855eba78798699dd961c16fe4ea559e23a6575b96f66ff1b78c83785c9d97174fb4c52229e850d623c9ee9cfda4b1f76d55f0302131bce6833d9f5b69cc2f573986b5376bdf6741dc4735209b905f4288cbfde9dd5d1b513e70fa929f6d76bdc307b27cdfeedb3a519f44949e170bcd9f6e187b18beeeddacea41cae7d30d2e2ed3af81ca9a37bcc13e8493620be5d2fae6c8dc0fe4ad94fbe1b59906618f2fc93141866c899e83f8de47c38f5079b69c9301b368a39471998cdd059b9ff21928e3c3aa8396a0c793514d00ae159a823fc1b4f856f032aea7802a0e701bd0c665190668d47569f1b2e981d5ff31e2d3d28a0b5746f43e94a145399f2abb969d7ef9ea3dee0b4b49a1a30b7e27c61b7b65dfc26a17ab4bc0c693f974e9b20472fd1e9ba3f97c9d1fd0ab5867fbc89eaf8d13ea30923b87bd4e6f1776a2da6f79616cf6bdc5e39f67bb6b8379c6ecbe0b5f470b588b7305cddba50e87461b8a4ed2866c67c32462b695ef889334f1a886a540092f592509bb68af9179b7db1c10b197f66d137217be5d3773ebc25e3c72c7e2fe5af4cfac0065652fc882279afb1c46bd9cc13d5c4874aff02e504182c59f5511edfa3d1bdbb9ac3a335b7320aa21b18b577eb7338bae4ab27397e8763376e610a844b7c752ec777617cdbabe7a17895a90ec4c8168a6ffdf51c1aaed9a5bb042e056c691c37ed6d78d7c62e9b606ca65e53610ddf6a0c5033c1045e2595d37cc93d31bfc06b9f554a37a49545567ad75e7e97cd9eab93f6f4bcb76ce594517a2166adb9ede77ad9adbc7a894fdfb3cb5ea419a74ae2169dfb2eaf9f65cbaf49f9755ed95366252f11a590349e4b9d2e8d1619b016d386fb677fe3bf5adea0516a7c3de769f41cd4afaf48df3f88e43f286776406326b935790ccda5307c415740ee00d8169d498a32df1934fd780e24c91eae0d3c358a2e195affd687faf27a3a33fde6426a7c73baee6174e2656e42d7bd51ade0941a7b2e1fea766790ff464c000275ab799dca7ab8f453bc6490f4307a819acd7fa417d4e160b98a23ae10d5c85e85a8ed5515172ccd8501f66632eb60ad644d82024f98ac5afd816d0fa655f09ab942933d05992c830676c098229e665d08f38c7029d4a0215516f5a19e7584764098f678ce6b0fd076d065895b61f8ff4a00f7cb90b145f382a43ecd4c42b6b28f6ecfce20c6b04e37539414beb38a3e73d62c684a963f3c3b1d453cabc90b90fcf3deee6700dc94e1c8d01787f0c4e83c283642fe55a3711d5b5b68b36d213329bf44c8e5a596816d20162c67162dc3489aa68a26a55f8ba01b44da46e60f30255e2c1e8def369414dbe785f6e031ba36bd9840291e8ee135c5a5de0e235858237e4266ed27ddf18c76165b8e80b39fd51b0c0f9cf604f2c6344551afe60c7da4ec9e6612d045080b9d8a95f6b33f232989200a9c814b53c825c3d50dffcc7b51c77b7dd3bb523f747787fa3a04f2168f298529b5e23303553629d47c233dcf5c301ed194f2b6e593451682d747aeae26af4f1b2948694098dc2cb4032283fce48dd98ef86cad26157402fc9275580e6c0a5bb52e66fbec9a2ce9e4468a062a241f66afdb8d43087b00e3cb04a7d024a4b101bca2d62b0ec404e9946e70819a85b11b7b4a832f78ed684aceeb7efa9fe92f88e7cda5948d3b24207beffc1b0f3d8fc85bd25212b15d67270e26357bda1058b123266d48aa5b8fa456032bf1abeee494df08588af2f2777bfcf76f3ad61240448ce830ef93ca6a47218c3df0db239b2f617af0af3ac3f40e69705ef93fe824df4f81428ba2889e5d8853441f2a376f2df65c1d5e7619a6980c2fa8c1d9061bb4d2cedc40477d1e49e14a6760a11559199423e085d375c5f2a30847678f5d153a51d7ca9115dbc10b8eb59dc0b86d367276697f57354d18292dab380f77e887180dbf2f311ff58433d171c324d8bf6ffd9e9ef5370bf62552522eab2b7f252d60510f21e0a2af323f2efcf01e4e99be75b1d14ee74c6050c9ae5ee206c78bfbb3083b04fb4302cee1e81ba2baa63b21d2a78a9bc7c598fc8f8d42ea1e00f5e3d79a7fd06e08acda1983ba0ffb31683d0d875a9cb170ec81b55b4f675a590c414e0aa24ff08dd972d29bccd904fc5ea7db98772bc468e17d80334ed6c0ac33b21e547b67a776cc6d233c4bf95f849013e4106f6e1f55ad61969e3842132a50647c266f432f821ffa900a3d68809063aadc161b0012d9b33f307838ad718cecd1ff59c78debbad04e2b9302de5751a2410a5ef1f379dd1ce69d1b7ff8d78dd8c7dc5c7f4a67070440021e0d44530a3960b46439330f0f0f0f0f0f0f8f6c1cda08a9ad9143d855a624252771f6d29367b94c29c9945212bb83776177f00edec13bf86d8d904689e8063a0a440aa90a76f2391daac2f0e6e541f72499f5a9305cc587adce522aad870a830a5572e92c3fc5cb4e410218a6a8333592e44a2b5300a314e6f78aefc1fd46054b11000629cc914de708692ae57b6423b0011a35608cc2a0d65446493a71ef5014e62cd162cf77cc59964361ae92203e3afda03009cfa2c7cdefed6cfd84412991ae536668650b767123d1c851c3468db20e2d1490fa1f808214f4030ada80e109a3788f9e0ea2f4b2993a610e3a73cf4d7cbeec30270c4a5eae8eb54f0fdb6ec2a4724a1be716dcab239a309a0aeebe97265d821046264c9f3b4fa574bbf3f96060c21cce4d26c42c1751c704965100e312263dd2c459dc5858bd3e16c709ba38808787ff09ba385bc0b084694e826ccf73b22f7659094a98477ec374277e593c258c4998adc5cc6399c7e9f725612e0f4997bbf47c981146244c597212f2bdd3cdba83010983a78d98a1dfa56cfb4718a47a9223457b1c61ce63b9e5c4544cfe2b4f0e301a6108733a8f8713af3cefc71a1a8e1b9f70d8401a58cb048311a68c3bcddefc8e79ca4518ed5454493e6e3a9c48173014618a64d52374de23b0011a20809108d37bc9c9d3f191b67d620ac0408461eb82a5186b99e4e3c71ae2210c1e74740821a789df591886308439ddf7ad092264e463ad840046214cf9b453d87afd630d871e21cc417b8a5e1d96523708c3e47c26ad3e959ab57cacd1c091e371e06841983e5c96efbe9cebbb05c2945428f590b27ef8540184696b54640ba254ab5f3c3ccc7a00e30fa6f89621352f992931f9c19072161dcf716a74a537727801a30f0613215c23d8872bf7f0c17869a263efa8f6603c93a17b359b1e8ca54214e53f661364f201230f26153d95905971e27ec18341fbec4c7950615424ddc1fc41881c5562a2164c3b986b5ed582c987d25a571d0c9facf427a55289dc14061d4c9f4e8f185539e2a46d3c8e84008c3918ebea3ca54fa76efde2e1810e430ea66ff1d421ff85f03fb18d83d953d453410795a384110ec614b35a11bb7fac87478ec781a3750330de60ac5e33e5173bfc8f36baf002c7df708349a730fac32ce4fcdf6983214ed0118fa92eb15e36982a447b3db5eb89b2ab00c61accb676f541a84892b57dec0860a8c1d8f9e29a89303d93920693107dfd3977c8d62e7dacb1a1c1b07121a5511d2939991f6bcb011867307ca59b54edfcaf265402186630cd4537e9f13f1f6be4108c3298a28c8a6f3ed992b9850cc60fa72cee7f4e3242f2638d060a6ee4380cc01883418490938731adb4edf9581383f9357bd64270b1a47f6901230c2669bd3e6a2ae4dbcadb086c80c6c300835952503b1b6f393d8768043640c30330be604e67a3f426e7a057cf8fb5340dc0f08241044ff1d74b3cfb6b1f6b4b6e0230ba608a23a62ab7d6240b326b040c2e184f746eb19c9cecfab4058352fac2f525651a9a6bc11c31b57ecb5a2e99593088b11261e2c9d90c190b467753e1d5d2a6de55ae60d0529e2aaeac3b7e680573c897fc616e61af2e877e09185530da84bf8dd79a0a8613f39fb19cfb4272533004dfd2e3a93c548ea00e2d1ed051e709185230463cad5951439e129f0e2d1ed0518c1230a260d2fefe3a6b0f1e49efc8a2e3c67768f1800e0f8f13050c2898939fea04256ff3974c097468f1804483f104739d7f3a55bb6542e47caca9a12fc159161d653a26f0808e64030c27982ea4907a7dddb9bdfad82ed0ce2c00a309e612da45b52ea70e95b480175ff823c0df8b2ffc9d0a3098609c4b2b1dffbb3ed6f7f04021a8916c74d18587078c259863cfe2fc435f0aea625c200d7c991209c0508261bbc45c4e7ac437bf28c04882f923a5204ca41061ef22c194ea47335c3b680feb11f60a23b4957fb038018611502bef9870c254123c09cc165d2387175ca085510473e9a8a392af2a6e46221442c85d92a2a505118d1c22d7259d523e8e873f16070e722387173086609012f2c8479bf7201a3970d8a861d7a185023ab8401ad8628bce02021e1e1e308460fa54499bc54afd5c75100c3fe1f4ef2ec8109d0204d30521ab1a7136d6cd1f98645784f07152981a9d0f0c42f20813d7b1b28f1501dd086c80c60d183d30f648bb4815229ec4b6033078609021398558a6e5365d0d10183b304e0c213a57b985dca102c0d081c9b4c87512e321c9fd3930df8f1e71133d4def8c0343100f9727e818f58edec09063a43c61ddcd4b451836306d6b57c81fbeb23dff58fb1c283839bef0c20603880160d4c0acaa7fc13def45ee3430e4d4eefaa6fdd5253e03935bc457bb9c27847c32307abefc8adbb12ae11818b49b49d918216ea6c1808131fd4a644dfa7ce4ea05c60e65dabb4abaf99f5c60ac143f717a95e2492d306ac70ddd925e54dc61b0c02ce69df596fe55182c84e01527f8a79f54617a19a52aa439f7244f8531af6db4bde5a4f3a830dd8f05c9f9cbd2854e61eca02dcb2df69252318541997ef67bc81143f552184f4ac8c154d44eaa440a938ef61e4582dac8a1519846b353626c7c9e4461ae8b3b0be2633cbca13077302f7929cfe8374161dad5b80e12b2ee5fe813e6f6a0bce2b25b4f9e307ae9603729b885d83b611859c9157b7438618e34b1f4457d3661defda8dd595edfdea309e37bbccef57663227b3261d271b77a1d1bd9e1c184692e240936e73e97ef4b184cdf7dfc8cf4b49fb68451b5934ee965251fee4a18ffe3e28238a574244d09836fde88df24dd23542661ee4c89fb69ff73c74512a6d8fb31d6db1dff964818265a5986be0512a6cf12e172d0213eb1f20883e9c9fea5f427aec911065d3995d52eea74851b61fcfdc897f258107a654618c5bbb7fc525e84d16d82f94f0e42e59115618a113a49ef79539171228c3ef7a29238a57ff4883087949de6a34f3bea7b0883ca093713d71026379562f13e4effbd85308799a4fa176467939710a612a735b725bab5b98330dcbf891197c3a7cbad20cc7372d79427df4ed706c2f871bf2e9deedef51510c6d4165517b3fec17c177984cde574daaa1fcc1e72984790e5124fed83297c9b9e209267a750f9608a5b4192aa7cdce7640fe6b57c721372125516a2078385dcc86361d5828ce4c1e45ef2da6dee9386120fa6cb197341750a923c79075330e5352b2aed659d763085f0d892c3fee79c9275309f50d765baa6645c920ee6cb17c485d5679b36e7608e4f79a28738299648cac19cfe7bef831cf17d310ee62822be75f77aec43389884d5c7dedff00de61035d4cbc8e7dddd60921b3a67670bb55f1b4c2ed6262aca68cb0e1bccfa26557e26e8f7780da6acba90b198b70b4f0de690929bcea12cc5bd6930c57c9890bcd52c28d160d25f97dcc1342e353bc3b9e17f2ae39bc128fe1d9447f9506ad700ca60d0db8a2297f4db29918f351a5b34068e0300880190c174656974f55a4f7c3e06f3aa094bea5a7ba29d6230a6fd487defa4f597a261d6a18502d202318030182c92852ca13f3098d3d489fea5147c2dc67a88017cc134ca3e78b0ea9920362f98e4459fff09d9179235802e18e46dd7a48a3dd92d890ba60b4a477abb1925f45b3085edd4414da5a924bf160c3a990a2fdeed6f5acf82298ad0bb146b373b0461c1a4cacd64eca5af603a895942ee56309c50d1ff21bea56e1e40150cc1d466c80f395effe6118930797808fe676957218a08a3dfa93b551ebe811587309cc89e252899dded88218cdac12e7789b3904585306e7fdc54cf6526ff26842944ef58a6a1469e18076118f911dc7c2c42482a419843a96cbe252e95a43310c691b5adadb4fdca0f200cd7dac1ac2defa98efdc1acf1216e42b4943b323f98b72fff855121a2c3d7079378fdd46bd739051de48321898e8df1d13162d23d18d287d93db73c2195ac07a3962a37a517a48e8ae4c1e035bb5ad123e7dc140fe6eff4e54964cc427bee60b4ac7ef24c5aec60b6b10b16627292666a1d8ce7793a949c1c54d0111d4c2674a4cb5ce7d8f6cdc154e15425a55b7ed9a31cccafa16d4a5e8514411807c325bda152aac8b29be160789157d5c144e510ee371844ffc745aa4a5adc7783a9c7c6532797a8164f6d30ff87856d48900d2651efa52778ca49e8790de61a392affb11a4c4a6d858811761accb6277794f04ec9a6a1c128d9f2670bbd1fb3973398549023ca2cbedbc86630056916948e37e9da7304897f71c2a229c960b02ce329ac4c3eebca184c1192867f855be8e7c4604aa359ba544a294764c36090ef1c3a64490183b14705914d4c104ac5f20be6584aa8ef104eccaaf282218eb69db2f7f38abb0b465d2d593a45fe5fd2b860b8e04984eecd9aa8f0168c96e27122520ca1c2d48231526effb34bb3600e2286169d5772ec9163c1e471575cd74a8c47d1158c6527a311436205a3454e97b4891252fbae8229991c153e675438f72f85f26ce1148c2be2ff6de7b92e45a460b8385a546ca4c85e320ac62a13592ce47d92775030e4246af6cd54ec2da52718949fd08f12c4f5e8a413cc6e27ecb388fcd14bd504e3bfa90f232e8509a6d117db6bbb6f0fcf120c4a081d6b399ef00eaa128c914e975e9057120c32b3cb3d2e998e104182d12a2bd48e107bba224730b5a7bcf179e5d456d0088653215b4b4e17845545304cb635ada915f6c485088668fb6f39a46c08c68f1eb327dd650b5108261f17cbd143f038a20a8241484b115fb234b427816030a5595717e28bfa4e0facf881397374921a995761621f1892956984931c94a8600fcc29c7474b42454fff393c30c71a11c24b5cdfcada0e0cde11ffb2a4322122aa0373881c7e3f5bd45c9f3930752953a622769e970c0746cbe5e96adc93ea0b6f609a91a2ce43cac9d2496c605029a712bf97e5edda1a983fa49c9f46a740b082068693a0f15ef319d9a3594ea692aadecb3230699b3bf17891cd4c1603d387d524115729672e3030c4daf051f9cf0b4c9e638d4a4aeb424659062b5c60ca2b77ed114fc29579042b5ad084b31219ba52eca87bd15cd879d1655e341778a38b1c5d5060050b8ca545d5bebfaa821cadc25495b38fde96bcee715518f46baf7acdf99afed4a5c21447a70ac9dbb72ac562a0c2f879f32faddc5afbae7488710a8350dabc4c45ed369b98d13064430c53183ccfb3bafc3ceda8742146290c395cace8eaef1549cd56767a88418a4d64e96e3725b2651f6b34fec8284ce92b7fa65858f61f89c268f6f12a2288699f4b284c79e47bfb29afa44a068539cd6c7f0ee12badbaf20943eabb38a2a727be496378229116b53fed9d3085cfa1948cb5f8133b9ce04210614deb85a56cc2dc39e97d7a9dfcf7f977648186189af0259ba770939fd20f87189930894fd4db89b76d31f281a317d09145470474cbb0100313e609b2c473d62f4996121c5b78c1450d2e625cc26865b327c74e928318963087d81b7d49ca477757095396b9c8bedf23c4a77010831226bb735f77dd132ae42010624c42f3d8b95f4f430749c2207256df6dbc87283912664f891e613f45ca128384f9c693ea968ba27fa9188fb022859c154533b3625accea711d9713a3d7730f0f8f24c47084c1bf82ce27425dea1b61502d73c9fc52cef3154618849cdde4bc327511c612aa84259d5662b8ae0853fecb2491f248d3179408f3aaf69e9227228ca75359f270fae67d8b71085377877a1f09328421e5dcd96d4de37babb4428c42182b4c9f55fa8813e3ae81a346094a87160ac85103470d0cf8165f2301346878787878b887078d128310265377dee9233e0873aeb6d23b2652be1395208caa216d2dee8460d53f214620cce613d4b7881a953d579a100310e61c5e2c87ad6ae8be3f96060d3c1c7a02e3e1a184187f30e85abdc88c91e62ee9d042011e1e3846d045171fe6079388ad974665bca95a8710a30fe6b82ee31e36e783594492a74bc9ad3042bf07d38585b42bafa57f923ed668e0c0d15ea0e0d0b8f3b7718128c4d083e9b3c3536a55b3d9109e1162e4c1742552dc438d16cb311fc4c0837947e7cfaba264b5857631eef04cca298d7659ca6611c30ee60e6e661df1468e7d4cd015b88bc0066804800531ea60f8f49f6ffc2c7af2f8c71a0ebd410c3a9847a93862412811529659831873304512f36515721c39a11ccca6bcf2d3f907b9997dacddf82db640c18d1cc46af40d2d8210230ee6c8413b878f32594b040e065db9e28a5bb8f8f83ed60c0931de6056f78827bfef7284186e309f1c712162a7dd2aa58f35c5b185175c3080861738f4c410a30d468c6fd3181369e4f810ace57805c460832987e8b1eba13a5a823ed61e3db2d1850d8f1c5978787878e0c8228b0e14a41a371e0310f0f0b880878787878707aa71e36d7c08140337ba4834b640ef5ee040351c35bce8bc220b31d6f0f89d10f2548386d98220861a0c3a26a88d9efecf1c3f768bd331d260d2ad3ecf9eba123d090de620ed44d3aabdb73b67306ab9a72f3565d283da0c0695d304d3fa6e19cc21a3f6deee53be4632986abbfd83776e0c86e4eea37116924932d0a1c5033adcc61731c4601e2a881106f3b011030c2588f185e3bc8de58df950211f6b0d011ce4460e2f70c4f082a19212163eab48d14917cca30331b880d9a5ae4e10211fbb11630bec65fd28eed997a297e30b08d8480099400c2d9847c84b9ea47f09cf97185930659d59b3b46a6afa63c114acfe737a9f9d8e9d87c7bb87871631ae40caa226857e5793350331ac60d0f14b6fd5e468a3a12a18de652d678fad0a31a8602ab1974232b5ee95728c2918ce7e2589a9d1665a9682219a88296ad2e5e37ca260cebe9994225f6be494420c281845a599d129b4e69bd213cc2e9652457011bd697282294bc821bb1eaaf4969a603ab1ba51394ee2f8cf0483107e3b39854ffafc976052a6e447751e9544e89560bcac0e9f8f93f1f027c11cd4ebaf5bc59160ac1c492c587a2e71fa11cc69bbee929b86a52c32823154afbcfe8553f15011ccfa715384558808a633d5dfee1fd65a3d4330967bf2115f2957bf2304c365950c6ddf39d4768260ac2024e4524a0d04d3f94b0e2e52437fa57f60d07f39eea7524a04bdf8c0a47492f7db22c7744ee981b16c54a94eda2a558907263db1542a9b841879de8129490fd1334cd9ee87756052a7272ee32408dd73600842cc6797bb383046d2340b96d444277b03e38c1651a9c2b581c9be3d5645c598ec6d0d4c41e77c96795093946860c8a1839d69664593e80c0ca6772552d292741c5506e61c4f581a1997e229c5c0f83d7a17f46dbff93030a567c50f93931718c592ce49ca25ed124262b8c09477232f9e7a69e532460bcc1eb28a25e1272f9b30060b0c33618216319672897115e6cc02db874bb12e187f63dead2d74ce29e58259c66757d6b6ba2f4798b1057347eedc9c9b34b3bf16cc55c12cad9536bd22cd82f953fe609795632c984bdd68affde8dc21c28c2bec0c2b18e7b4756a8449150c17dec23c584a22fddac10c2a98b763acd867e7f80e01a083195330755a5159d2c498f0310233a46098b4dba172853ed6baf02f7018036644c10433a0602c51a5ab62f43ead93c28c27e80c279873d4acd8113a4ea51e002acc6882f947992a3139763e533fd650b0858d2e68a079d098c104937cbb54f12b5c16110537b410c28c25cc508231e52598e985cf8e77e15f586146120ca94424695e614ca6450e339060d24a9575c5f39ef6682788194730489d20622afd067464d1c100057464613ab2e84040106618c11c75aec3b4998a60128b1e5410e1e9c8a2238b2c3abed81cc30c22146edfde4fc55a52f92118948fd87309a743d25f08265379478eca1ae71d9a1184b392685cc85e9553433e4d76f854229955980104839604754b93cdbbee1f78512c25bf6879516a860f8c9643dbb9c4539154b607e68f11c9dbb43c30472faffcb9a456077d07c6f3d021e8567460fa1447488ff5663905093372603e53672b491accc0813946a9b7679e3898710393259553239b4990f5906d606a7d537f2a8f5c86d0c71a8ef3857e31a306a6b82ff93d92aacf87c65987160aa871230717fd377258e06bdcd0c2a3c68d1cb911d8000d12cca0c1993183e72455f4aba469e2c71ae962860c4430230686a8f5fb417a369564f8b19645077b98ddc8e1850f66c0c094edb9d572d671e27e8131d3cc64723009fb161798439c5a5b49428cfcf5632d015f70d191800a7478787878e0b8c18c16983b758c8b92e3887479060bccc303325661909b62c1eb3e774d491506996493adc6aeb64da930bf9b52f3e3a93ea509152619ad19c923b3e3884e6152592ee9eb74540829a630c5c457ad1f9394ca520a83906149f5cc74fe8a92c22484b6f7a95daa557414e6b8f14e974db46fa9a230085915a174291426fd69b636d7638a0a0185d14698c627d9965bffc4a6f26df484c9457d10e1b7c34350c9e88421957a8dea94e6844909d10bab244d4748d98471c73de890be2449963561b6ea72bd90947e39c984b9a36ad6a43055414d199830988af59fe3989a9c2f19973005f19fde418c99d50532408720c31286ab3a79ab6e22d6fc1f6b663624903f90510993d6fed412f4d2af429223b0011a2490410983f8d61029baa6ec34b6d8a2699c7206f81b5da477828c49982744bdbbbac996a52e928d1a04c72364008cc0066808e0053224612e7db3b795654dcffab136ba283533332bbc4a3231021ba0110055c88884f1b3e775b68a9cadcb196440824f2b5256cdaec2feb1b60cb021813d198f307bee2c9b4f9d4ec8cac75a0d1a4f030529e82df43820c311887759a5150b49a66a234c21924ae7d15ff972d440c68386286430c23012a4eadca97d10fa451852faba77f9965ebd1561fc786fe2222811a698edec1724049521228ca391cf2478ae92593984c1e63ae4d2b9c35b8e210c29e44937a52adfa2c617270dc7091a021d5974a02087175c181b12e8c8a2238b8e2c3a50f00e301985309967c83c3cce681896e04a1e6410c218624ae25ec81e2f3c0a903108c3b5ec7dde5a10227b2b08638fa94e2cb73fd654bb901108b37a3c15d39df3b106088308f2c945db1f821b6e3664fcc1944ea4939f73d7bc3b8fc30fe612d942b44b37ff21d67121a30fe6be1821ab9a5021a65bb690c107c307492376726c0fc61dd99e95f318d5491a37fab7d083317444c65daabcefce2390910793fc20af434e677d6ee1c11cf7839c339d83952b02197730eaa8b33633f59145e7634d6bc8b08321071531ef43d86dbc175e748640461d0cb96372b6099ee479fdb176e373d8783a1843af6f8549fa9d82f6630de7608a615697fffa6f6dddcb9083f94d6d5b8f349ddebb8f353309905b7319713077127db223a27030e4a44d49992dd951256f309ae4f47071548559851e0326c30de612793227a26d83b9da430525421061e21f41061b4c75c1f7f6bf84e86b45c61a8c6a7f1b1f49a4d4136d1564a8c1a8ef2945d193d47212a7c190dadd5487caa820030d66918b21399d56b3199dc118da3d5c10e2f2741ad50c06f167ab66a3ae3ee7b40c26a52f955f5e85b8ce2183d14bcd66895114648cc160c14389d2b1d4e91e930c31184490c97121d682fae0174e2304366af0911106b366c44b67615a1dc2603057ec2a9d42e48e55e80b866829e3b7744e7d1df4b1568c20c30b46bfd393b6972571b7d20563857b10392cf985b2a840096e3c8e4301420120820c2e98d26993b9ee24c63db60573ec5b0af72194e95a309da4178d4f6aa53fca82c9fd4e7ff8249763592c18ee2ad7a57bfe9c73bc82392c9ba59027eb87cbac60eaf418c1d73bf2cc5530fd8d8a753ae7b40d8b0a66684b946ff59c7da0ca9882b94b8eeeb49072b458a4607c51d5d222259edc370ae6b29cffc5d4e710c38482a92d8d486b4b173310643cc110c5239fad4e50a1d5324186130ca6fe6b54ca8ab3336a82f9d7e563a88f6a7521345070234759116430e1205e44e819139e5f42e12c74589edcd5957045509616a3245ca14a9e8d6957598d0457a297584aba2cfc233897ed27f9ac6284947ccff7d9f4f28f5784ffd427779cd8aa25934104733ccb612bd9c8f98b7daced104c71cf8367a58830a62f70e0a8419cb5902104933091ce83b2ac3c2faac145108caef1693fd543d84fc3c68d2f4ec1800c2018e25e3aafcaad223efab1563c90f103b3c5aafd094ab2b87c3ed618027a01193ea853d7c9f99ad7174f0f8ca691b3467d4298b4e6a871c304884090c103937c56fbc95912d5d3c7da16a706fac2ff860d0678787878d0d8c20b2e6aa00520083276804db26c79b93ba866e30265cb2b90a10383921b79a268c9554f49828c1c98d2f23bae44d0e7938403a3a5cec9f44d2dc5fcdcc06caaa7335ffa84d8ad1423c8b0815125486f75fdbca293353075fc885d675aa2ae847b208306468ddd9ef36cf722321ec89881a9ca4ceb9966fb5f2d8353f69ce05dbede1264c420870c1898c4497c0b216c077904379a8bbfa10890f102a3766a496fd92cd1fe210d192e30e9996ef517bf107cc5814e03325a608a72e14ea9ab202eaf4624208305a6f819c1245e27f529fdb1c621b0621586b56c49989814269645a30bd71458a10a73a7e7a774f1fb58c39127b02215e6f1012b50619a70a3adc2d684741736ee4a60c5290c41566444515afd4bf6b1a63772786185298c599ece3bdd4878d172d8f812bcdd175694a2ea36b79446fb3c444861b01e6d1db4baf4ca95e371fc8d51183d5ccad09b868d46b78515a2308bcffe78887b65118115a1483bbc7729b36a4b21284cfa4ded5c6f6276ea393ac7175c28e013860db1f1cf126ff6ad159e306c6b79291da18f351b5db07568a1800e1b12382958d109e34648224adc96cbab114e184485ec749dd56dc2a0848827cf52af0953d4cf71d2f6ec8514ca84b172526be32bca6a2b4c982da9f9f7d39ecb7e711b351c7009531895ee21faa5b2ab58c2a463357cf48c8caf9530a4904b95bad3734f650eaca084215a9feab4a8a5ad4d1e58310993ab4bd4cb927941642561d2fe381f43098d0f9e48182cfb66b66ca81c2ec290304cc821b623c79520921e61ecb5d01d3b08d3db0f696085234c6317bb313ac88ad88d304ff02472b2929d4eac5630c27c92aec53ebea814293ab06211c613d275c288eb10395682158a3076a9eee810614530975ccb62635f3b29cea2a326d091454749a0238b8e8a4047161d05818e2c3aea011d597494033ab2e8a8067464d1510ce898c0033acce08005110c5ff15e544a659e45d7062c8660dc4bd2c63f6749385f08a69dcfa5cfc6b287fe836050a9424bce1813baf3c79a9905081640309e989cc376ef64d7f807e613d1a74ea784fb6c25163e30ae089bf0d5a394a51eb1e881e1849e4febd0a7656488050f9c49b2d427a798cb80c50e8c132b5db2ae78fb21494307a64bea6f92d5fa5c8a0c58e4c09c53974a166533de3c16a081050e8c12d4549468f90d8cad23ceca74795249b18149a438119404c921018b1a9844dc4babb3ffa1ee9489050d0ce1440ead8b65a33e9d89c50ccc17ac7254f2505713cec44206e6b42421968c18c27be701163130588d978c5e280df3c0c07467c1c672dc4c1a60f10243fcb42939e3528e185102162e305fec74fd69c2cad9d80253f8ccd2e1912e49ceb901162c3028917f44503677e6adc2a4f6218c8a53722ee95561eeace599efd65aada5c2a04fc952b1720c3dffa2c21c2f0725dfe659468ee514063badcdcff3164ebd798529ca0e7911a4fb7a28af2885f9adf745540eaa3c7e0cc215a4306a565d7aa9bc25ba1b85495e8bc52ea5623f9b44619cef109534a48e7c34140695f49c77e45b33d182c224adb3d47877aa929c4f983c08a553c84999b24ef284e9b54dc607ddeb172f9d307d8647fa94d725f8cc09d3e513257d239f25153761ce9252a8e09126795c13e63d4fb94ee798f6d6326130a9a363db3b4c98f47778fa7a08e6e9bc84b9433f947d7fd061439630e814aff48b4ef9c7649530d85e86a898bb091da284792fcfbac7235dc57412060ba63a4d96b108b74bc21494c98cbfbbb624cc48983a58dcf848272f270c09938a9de283691b99117d8429bd52d6f4e839c2341f29a84f4a9784e88d308f8b14156cb7a3e6cf0873a9b869d92f8d6af98b304e5231e5516c2e59a7088398a03c29eb8ce0964418f3c295faec26a77e22c29ca2b227cd49b7d4e221ccf7f1c98457ea8f690d6112f391f27ed89c11ab1086b1ddeaf4edd3b71621cc1f9aa5229cded6910dc25829e9d6ae5c3f2a6682308a5093eb66f24efc2d1046cfa56d2be4b1f11610e689356ab93ded837f7f309a08b669ddc9b476d40f660926e24649af2ea5d20743f814274dc8079d73f1c1a4437c4e3e619aa2f27b308b87ef91ff922f4febc194e592eb281dca8339e8a07aa4c6fa8930e1c1a4cdcf534a95b4b57dee60f26c2e5faa61da4d6407c35f880c0f3975b3ad3a98e3669b8b7ed272713a182dbdc4ecbc4b2bc99e83c92f8db8e88b13676a39984c4456244f6ae9378983c9f3072da23ed256b8e160ccb0ec3e93e659ee7d83c9bfe368e4ea9c52fc6e307edb7e58b3ce224ca70d265571cc4696bcde2eb1c1207c4b27f9dca8166d0d668be69fdd752599eed46052a762dc5ef2d360b0b06f932fe5b0ff27349822897ab6249b30217606a3da07ddf3d8da90b2198c172a9dea2b2527a45806c3588f99057921b1f26430cbe8121754d24adef7188cfb7a3ad6a53cfff71183f1c42acd8ab084c19494e4d1f92cc160caa9adcdde43be8ee40ba6359542cc49ea828ced0583ce0ab91d8447d0e9ef82a9d4c8daf028272388b860ee9c09a2b3cfea6edd822948fd60713ad382d9c25aca9bfb22d2e22c98ea2b2cd7838d05c37e8ab7eb5eaf60fc891f4d847aac60b80aa954879c2a18fd2f2b7a2a513da3a282e9fb53aed21aeb5fa12998a49aca75fe9d93ed2c0583122a8ba83c71d5dca360ea9e3d21cad7e3ae0205e39f0abf53dfca11ff2718e3daadd66ac4f7ec4e306609b12e5dd397736f82f13a42c8a61f37d2e64c30feeaf62811d43e9a7409e68fd439670f9920725a0966f3943d6f5d9d0493ac5239fc3d46024aee64f50886a82ababd713552762318476df88648b9ddebb9a208e6cff9da59abadedf315443068cf50e7a722bc78ea8a211843051121be2b243ff5c7da11ae10824984948408a13a657cf28a2018c373271dd127dceeba0208660b292be95caa1f18f54eefcce909a92ec5072679fbd9e47d96a7d63d30a439b1593a85f93b9d07c6f64eae55a5e3a992ecc05867e355df639fb4a70373f014ea7396e031a49b03b3099d82e58e0bd5a3c281e14314d15af9da69fd0d8c3fe993cefdca8cb0d9c034ea2f3ada5b0dcc974c88ced49ddf100d0c2a68b6a890fa2b66608a1cc1a36bd5e9f4ed15323089c9233e21a96cfa4c57c4c068b9ad2553c3e5620c842b60606c91907d6d7f4e68ff0586efef5c734a7dc427b9c05c6adb32758457b4c06059944e1d13f2efc457b0c0d8f9793fe76a9953e12a4c377af45bdbf6580a5561ea98dd59b9c488de532a0cf259ca57f28608924685c1639cdccfd9908b9ec2143efa7c4827dead7c539853a99ec94998be387a29ccf5dbd1f7ef23c81191c2ff52b2452585a3308574d123a2f4a23087c578b1c87f4ae40c85b972f43b071d26f959406110fb886da5a58256ff09e36887a44a23b8574c3c617a3799774ae28da8b713e6922d572a2997138694f42531a9952d7bb4099392bad725d6e2e43c6ac27429d9df489026adbb4c98c44434391174aeae7a06264c6b319e236f45f1d832675cc2dcf184123d92bb25451208332c618aeac14d8bca71375209d3aa76ee534a52d2a7a5845152862c9590cc199330dd8851497687049df399332461d061aad3f4847b38397346248c27725f92ad1891d25b8619903008e9f31af28312defa234c5fe7ad3a229ff2ec1c613e1d59951f3a8d30ec84fc9f446626674b136630c21443dba7d68a29e2468b306751223f5ca5acac8b22ccf92384f7da9272ab25c2d84967a78aecb56d0811a620eb39cb9d7d08f3e40b7dbac3e8b4962c86309e59aace67a91006f915f1c2ea76bcef0961d021ec9759d5cce5f4204c27667ff59d546a7f8230a451cf594f4d8a1b25e28c40187457ac4b8d09330061b03d5356912d5b98f107d3bd24a1d25b502295ea07830739612ba5583aec3e1866245b0771295898c10793aa7afe7b1c69f76beec1244975c8d14f19adf56098dd91232ebee890b23c98637ecc8ab815e2c17c7144a4e5990e1ec27507f3f01976309e864c4af2de4277ca8c3a98fc5225156542d0d9dfc75a656147811974309c483c254272e6600afe4174f68dabe823e9c10c39642b1fad2d8fbb650a33e2601ea54b58ea86b81b351ccc1641d6c2e88e92764a1ecc78c33d27ad4d92d2f9cb124061861b8c57396e67a737b405b5c1a47b294d0a292207d960f26cfdb80db575a5cac18c3518644cbcb6af570a29aa2125d773627c4a7549926930798868fea684e98cf73870e4781c38cab0048706a38854d2c48de68be79cc17c6abc4f8ccaf69cd40c4651ba434b4d5606a387cfbba4091354ef99253825cc2083513f57ee093a73f1a931784a5be3cc4e7677c230430ca6ff3ef5ef3bfa946c6130c84e936c46f9c8fcec6e0333c06034651d474b508fa2a3695f30e9d39343a6ec5e307d559b68fe018ae0a844afe5025120108682c1502010060308f89e0423140000000c18918662c170344d96ea031400034930264a2c2c20221e181616120886627028100804c44030180c0c84036150801c38864344fe58907986847b0f7b8be69b5731a8833822d4105328783c03a63b55ab0576492905a62438aae8e4bca0432725564786f29a7c4279ea207bd8a12cf432496af696248b54e93a633b7ab7ec901fcfc53b303bb25f9de33eacaa24581d59f3a727662b6d4c0f62072902c14ad97aa10a004ec4fb5765d741e8551606e0a7758100f2228d107f545e5c51ecacc56132e6609b4ccea8083fa61330a8f6b95db6fe18ba398ecd1cb52afea4c72d923590eec0309f5b95ee875ec5ea2eda6263b3187dbc75b40ae050790d5535389f963af6c549b7107a7c3dad8adf04b5f757390d2918b5b70d569bd1f71c597558a0efa3e742063aac6bf6377084d3b10603e9cc22c0a090d286969c27e68438735b6421c8a7d8d9358201db30ce6fce243fb3d70286f52a3c2b8a94ba7e514ef8b1d7a4aeb1d33f7be3efe78dd95f084221bc8f6603310a748347a30dd50205860a80f2417542dd40e1a11e8a5a948a13a1fa82768bb8a0ac00b51e12ea3f94178a1a6a03050f0503551b280240b02ee50bf582fa09456714a0c08bb293da808242cd433da1a20c5576028a1faacb51d45219282c5414ea190a0d6aab82aa9d9ebd5e48a108a008a13aa1f44435d7a2332650f0504d2842a8722821543fd4150a1b6a9350eb854708dd19540d0a1aea098a08450c2504552da080f4e789faf28dd2017bec3985b242b501057c0eeaab0905ab9cfd517d58972f0d82fa8151c005b6caf3200a38db7cb3223e0af8f44cbc5964a8bb9c08f59651aa166a0a0a01b502c587e201d5835242e540bda1805006ac42c3e7a38d50408bf647ae1e940ec7326e70dc4583a1eaa1684f934f676702ab94a8203c99fee9a25b2c2a503da0ba50d45019283c540cd41b8a006a4743f5210a733e7d51f42405c4351bd2ca13d7d3ff137d8b55e2b21794ac19052cd8cbf0792a7e22831a83e29f40960a56b9486d423d40b58a02fe009eac993fa8384ad8899c07140314124a1daa0b25ce8fa20d1d6bd411a13e50f55018a87882d55a64dc4c9d07794ed6657fb956aa8c514030c1e0187644828cd25bc8785c8d0412c9718ad5509715b1ccb85c663f1d071a4c776a54e2d0eba96f0b355d0d9ca2b62d0a5a84e8994b3952129d308d4545c8d6c311059654081c01da4539818ebdcf096f3918b05b69d869c0ddbff9c8c0a5914609eb11bd21a451820451c76d010336e6c910e066769359649cfe4407cf9bbbcb6c7cece6d982098bd1d2660ddc7ad34cec0390b54ae5a1a15e1d2ecb19a99f6fb2558b2a11b88a178b418dc3265b86e761ffdf96b500c92761fac9a0ff99142c5d6d9a37d52a67062f24d84da60a8c99dd2ce9af6ab724b2b10721a6db8d21594b01d1ce76af4f59eee44aab3d61d8422e6715c41200928543e821b6a06c85efc36c660bdc55e8f0e05a7b80e7399b04762cb0d69c867688deabe34000d8ad5a050be06f5a110880f9bd5ba7eea4eabbe58f6b0e6e88c559d62f4be705d78a9c8d918310685ac039f950107c988bd53debe905d74fb310249e8f6b78307c6995f318c1afcce58218c1b98a5cc393e33fcc5396406624ea61e15c214fba16f704286a560539ec284f09c1ae8685ef9c9a4151d523715eee90183a69fd25a56a68365fe618c940d497f2684151593bed3e13a689e8a4f0d1c270c61f872847c52512ec63feac0a0dee3409265e7c94be0d7b0aa34680644be956032f780329081186402d07d2a736e5332b2f25ad23be8d0eb2a6d11b2ce615ecc47245b54396a3ef563c32e48a785b8f15222df8878a0ab7b7f97ddc3fab72bb08b3f7199eb4b2130ba005091314f8616907ccf2a5da4b6c1a9f3370040a0034b1202989c35368461baabe42728897c0d9cc088b1d40af3704487f648210034526c65b2d536d83d8e90ffa29046fb3163e61ee141e955e8462540077bc4d90ca03e17400380e1f01bf3a1194032ab76dca73853c44cb2f10c0e1fab87cb4fbfc59eb41e12cfabc38e02df466b9a3fae6a8b2bd37d4f7d9e517da82de5803da6e8053d01aef06414a042d44a4b2d131bfb711b094b8f342fc6fedc39a1012b09776de9ba108028a332483d6b2874f16567f176375fd4a81590ecf5bdef327e9774838f05f1b3bd14f930f4f064c19a7739282683d406c390705786824ea5e094c44a6c1b78d1b3e7d8e34b5b44a154f2d294a591c5031d8c9005633a02bed785e7510cad0c77bd70690e7fbb2d1ed054c940bb861114ef0dc44fc1e3c8cce461b72536955bf9f0c75f2088594eced2a790de33327e09842baba5eb5595de22b4d21506928fc2b6bae65e5c00e7e45565635cd0dc09275df44c6bf2021d800e36ac4439a08d712c5b21309e6c5abd670d159374c0b4f7e75ab5b1aed55142ac0d0568a300e92dbfa51b759014d779573f942e897afb4ec2002e6650b7dc5e4b1fe1370a6a77b35095905684f51c92f9f387284f163f81010390dc77f29e6738de3030e3a9d29f32e0eec18ff9449e620220c9e536976384916f2040ca9359c79cd3763c1d0fa84b5d81d3052b56a91c8b7dce93b8c0310cd80ed675eda07ad1f63f76fc5ddac0e7f93e34e2e687df4e6b4119683850e72a486a15c0718e7b714cd84668b7ea6ab7d112b87484c5e86fbdbb7bda12a8989070e47e197bebc530aa15388a62d08193d64815851a46634a56ca0df3743f4a60103eea80a9f3bd3c1861e63b470172f708e2ed725c02f79ff2278cfdf777e2db42232246e9d819ac05456e0e1cef04ea8efdc92ae482b8a90f4728d414aefde8217b2c2d422b2c827504576229b6bcf5771448158c9c4b6828f865adb5b16903682f48dba37b92215de74fbfccc6933624c8423c34ecad7090f8ea4711e72615e8c2c1f7d2fb6ef6622730d7a10e2848b0431991a6201976869727ec4a7ccbbc3c8f50bf27bfd349f29afa08745e7bd06fbf412b3646e46c4cb04764a48b237bafaabfa18c1fbacec1232762588b1446907f163b576466e883f8eeb3cc2145f1d36544262eaec8c16da23d4124853cc818e7a520112b44932d0132ac9f38d18d57c2bab023ba1251e08ef80583e8d882f72459542ec6ec919da2758476dad7e952c2661e3e75ed5960254e2daa79cda8e2e2b00ddd4a37ec3123c4eac291763ebc7cdf99f20ebd00cb5106dfe2a41a560283e1b2e4245045800325baf341c5f34a5ea0a86c9eab6029c302882e0a494b0939a72115b7ea0904d048348c8b832f91c7b8a24fae9008b5ddf23ec600daef4460d0446cacf53990b3c20a8445cef70b286ce1e669066359ade2e326bcd97634c7178a0d3dab1bca25290c2ff7e48264182ab2b934454daccc2df7e9b7cf6fbf281687cb70ee475ff0a56c6deb3dadd80218baf32009b29ad0c256c070329996964f2803c421ef0674932788f00c4e843376302462c9eba4249634d8ec394103c94790735ce9a1550b5a5ac8161b8265107b5768f3e3b563824c7aaada19bb727de4bd72fd3b3368646ef918f9e6af827eb0ac977b380c66fe377a17a0e487cab67d21ebc11cc5bb60d8e1cf6e142adb99eaf21cf426b856d13ba88eb129218d139c7f7425b4fd0855fab2790d51ec03c3a8219d279ffe5041b029919ee7f8b6931c9d003d1d42d8d36f112f5863c1ecaa67466e71b5a009851046db095753fe642f55aba6e5d777925f17efadddbe44d705754f5d6eb678b0b846614c9a877d18d1b51f3f30db5af1f65c2f011ab44464f0ca77b278617749fb1f699b767494ddccb04b05ab47da1919f63bfebd562eb4f8cd13cd63178627a04a2db1c4b57af0823dd55dd43c00d45c0725440be3581af9691289b469f564ea86e2282cd50032c0f28d1adc7583d39ca57c0b7f7175576a61721a5f884f060eab45a51f19bfe89044892299f43c0074818f297705ef9eb39dcbea12ebe5e928f61e694809d32480e6cd67c028674a2ec3e8af3c7693d8812f64773f98117108f0bc849745d6cafefbcfa886d6e1c31d6811f36af62f1d981a82e6f37a762591a1dea77a1a3bc808f9ff571fa5130c9b6930decfb55c2f390ac8f9715c73c9175de4ec34fcf4bb49f8f99697e70fa753c58dcb725ecd0121b247b2249aa52d5cb5a2a10f8863374a8a98a78beb487d0a898178c928d15af595b64419003ecf72640bc35773afce9a48cd35cc33b502ce3f1acd010d33f9f949f4019eb9ddf4348b7ba0fa120ef11ac1967e95d6bfab46d03cb925c770c193c426050ad221501414e113494d7b7ffd7153378b823f99310fd6a3007dc9195856554105775b86a70f57ca3818a0853086ce80a146ef132214aec8ee55668e5b6b6777268284d0ed6f1707255dd45ed81ce8f50d3eaa0b06dacb420d04d750485051171b485f56bac7859bef7a291265ee331f92ee40351f238a30a27db46994b4940ffaa91e2d58f816c2cb738bef02cc9261baad4388b7311e83c91a1cea47979550978c9929522a7bfc381070767dffebaacb46d7f06a6fdec8d872d961470c9320207ed59b6870e2fb03131dc713dbd23a00a7b30bd05d2d007cf81e5420ff9d3b4b770679dd94c07e9c9373e2756803109891e19041a8031e8e3001ac2068d2e2e9e4ee2ddbfc8a03e5ad3f5e773740916ce9784ce021604d021713ef0061b0ccad5600376e3b476a1b049e9c724c759d7154c4544d022c4305108d0554472a1a2fb71ba3752fc5e4d6995785a4a4194f17e73fc6aa0e6609ba62345e5e30e7208a7df85f177f05591cc2137ee6a0238e89fc061aa8461459968f3a1feb4af103330b046b34a556dda991ccbb03d507fde133c38de337881de72df46ded352ed7f6d60a8173570ef79591c8201c5a0c099c3817ae6e304b055745b4c2b4cb451bbff911fb477d73bb739345ce55844199de487f0c43782411e0e5299df7943808a4cd171ea74fdbba25c7691e4652d10df6fd9b5e795a2e88d5cf3799bc9301717c184537c4c3a7cb1c4bae17e000177959b8c8e5b0d9b33a8e8166da5dbfe582cd24b172fe86cbd0d1c9d232a6211aeab197a9f7227e819364109c525420c0ac53138a350877d9eb77980b254ec92d995c8320c54ba039072c9e5cc61342288742f9d0e5d6d389889d66d478b085c9b142d3548fb8e5a609a545d62431b995803163cc25ba71558dc75ffab361a8692e3012c85958b789cf41976c2316289ca91abc364297e1a943db93f37650128d48b98e4323eaa8b28d62a2747fab19956f575853632d35fc2f9edff15f9a7840eb01a2646e1d6a6e48c13faba949011c9cb9742c56690d4b00b3cd940adcaa0f3687b73a0e30e60f6916d2c46108b9d7cc8d8ec4ddd2ce407a0ab9251165eb5642b8910509e6cbad42b305a452b177525adfbaa0a74ffcaa463cf9132d6d0463647d223107e9b1292369a01e34ac38105ade78be7c48f0026cd0025c73423031c8973aa03328bb87c4d9b8f7bd14d3a6bdaff42fe50dd133e275b4509d7dc748fa3157bbb0bf77c27afc7acd50e0e0342f76d32d24a29810a0b1295e96f0d2b981d5b8c3fa32efe0e71836d303d01b026a63472653acd7199f874062797c8dd7429e13d1f8021a2cc6b5d63eb32a45750e02d983b346b872bc9948d4bdd4820f580280525fc54934594a87f1eba3a05353324e49f164fcd8530368746d04c4c4563d40ceb8cc0c7084d0b409d631363ab8e72102980a710091d2a8007abe4b352f87b3d10888033487167cf89ed3e2870a94c24d6cd398712599c36965787bc30b1728c46f6858c94de582a314691d41ea4c4874a466fe0927af070edc10e059ec8846cb702dea0838f5a72c6243a152903d4a873e097cd5b030d361e5153d49c141bc50a0a1d8a07d51d4a81ca859a818242ed0520f75a42e94080e240a1ab514027e9f97432541f8a0a6a0f050d0585d2c120f7cea0085028fea83e8c51a128a0c8c128208699c022238517b71880578a7c5054450dd78283843216e41080416945713874284701d573447e6e1d403d4315a10450c1a02e50c07fc0e190e613232314050a7c507d364267d881a2c22860ee66f2b738951071e84d0110ea02450b350305d3e0704ca054044a4751020954417541d5e7a0a6d0ef2d1e1214b91487c303555b544d0a8950f7e6460a96c028881422d40b503df34c217c25f3d40619097e9175e36301501ba07050f35034287aa8081415aa152820bc4361fda16bc2f49f76596469a7f97c889ba940ea16a2ea23fb320e0a1054758102d2e3009f850296a96ac00244bc24729bf6059c465268d0c7ea0025e9bee1c9cdb54b4d857cbc8baac682c4d36c4085baa86dcbeebe0bd1eb20f654bb59f66075a73a339ccb28721acddecd53d1f8691b7bf483147480522f8650b0ee6566868e5b29477f1fa7071119ca2073c98c09c4b4ddb1d237f9a4e2c9e453b8102b69ce3c7b56931084a6809fa35823439d2c6441398c21b41fefba294f407e3a41b1370eced3b7c338e8ca54484b8212da1a088b517b0e09e8e6b4abe23f518922f70d15e7a4b7ed71917b82161b2f0b8c312062e9a6f043c6096875e8df071e9f529b96db2a0a4f54de1a21ef0551a7b08c6e8d853a5c19ac3df51f2eb2cd3ee5bc44f7a6198a0b211a0107ce390fb9e51ed0cd25b8affc8ba20794db247d97c95f5ab8fe8186a0ca17d2f247b28a33dc18ca17f3672a51bafceefcdf66204b0bc18f27070bd16bfb10b64e31b1aa6df16c6aa96362243250d441dabcb6a7f6d0ec9c53708f066936e3104982842885f1aa941fba5141fccf1ac97db81a87a00a3a88e79f03440d5dc439c4571f208ba2c031ec805997eae756c3bec8ae630546cb40a23c7d0059104d1135344d0aae4f3ccb66be20ef2dd820de3202a61b3b590e5ccd9cdbcfc85a54c1693a37d343ba5a8286988ce03493bb562f1991af282c9a57a4468430272d0b3cace3f87e8adc646a20081eeca1c2c8c567ada75d08c65987894dd10a84515fcba6a653da1635f1152ba2c59a376713a8f04172040bdcfe76e3b3cad01acf220cc46486475412a1dab8b286c92e541b2c863620aac61b3f5dd7bbe41a31f14188d2ee1bbb6de40f362c5744f237013965bee5ee670f533600005f8533e5e205889b75570779cbeb46820fc9f5c76ec76dc2204c0662d3264ca53535ad9c1c93be2644ca770837e32da5a91616af2a8b00144ed8e28e80fcd0cb376156f59669c0f154df46b1cf702c37d9b8ab994d7bf4266820bfb5df32ef94ba958e44595ece3b7c83055649c211c471d88608bac949c61460f520aad177b8398851901afacf48ebaca66125ff0502bbdb20218800142c83b4b97183c3918e0a0011b62b908b1d4d1c5d20647b5e04c0911aac2030a4deec1e04b7619ae8f2ea0df4bff25f6618e67fe385eb425e531482da14584ba7f7e219a1b3122c7154dc05b898a02dfe5aaa3278d3bd7e22ef60e16f38a72fc8b885bcd44fd1d4108d3b901fe5c94b8142c3a9fed6fa71f8837eab2a74db6a350f1c32b75d95c40d158b63176188d6db10fca619005d830118f9db36a5a9064c8843e38de9e48e47692e7a673fc86b3cc933803f029db5259712ff9c721fb9351f1138706fd6fac0bb41ba9cffe3a25d9a188d4ca52e01861fef119a4e196fe4b50c428a8eb84e8811a2aac455db5364433331d2848baad4e91da2926bccc5456a63ff31697e854967b43c0a43b0f5ac8063208387ef25634dbe7d8a32ce4b488829764721bcdfe633da32e9372509cf677bf0337815a464569855d8052f02946684b3dfe09d4dbc7aae9978f1dbff59856bd3a63031345a771e5c64ccf43d6d968920b0f02dcd50c0fcb02afa0d3f94d0367883c85f7ba40a157605c960aee9923248261cd789ece9f280abc95c4488548cfed61530c5569490407080558f51893f274aed5efb8e6bbe4ca623a0f99e97f3bbbe442c023ab4abda0d37efbe1482f4df3a7e65da022868fa8a66db2b2881470cbec3386393ec0baa9409731dd8990e6e660c4bf61036accd9b1175dd5684786ae24d37855f22d8ce36733fd6385265efcd2c88a2f9e844dccc6d9346b4be494e1c4335562df81d2cc3bbc818ae85a5b1f332e4d1d5141bcd2ff499a61df6e852767995b54b2ffca6bd440bc7d00b590891467683899dd150b2259e91f4a32a44bbe8982ef4c61ea144bcc764d5be56a931a467fa654355e93ed8a17882dc7fa5239deb9bb1825c6f9022c04d0acfbb5953d11041dfd1ad0a9352b1dd9ffaa6de065e95d95bb00a4366c92337ef10a782496f5bbfc37e0bdb0c6fc7418718c7875f990f6c8f3a9fe86a3d99bf0095de12e87a178c477edd00f9157bbf766f9ec239ff176c2cda36d7546f2e7fe3d387dbf9e526a554c2eff7515b79b3bbf17bcafaca33a443d835e3d3c0c864973d3e6f5e4567f1fa84d0359379cd9f662a4eea34398b0ffe266756023f3acde43851b9d4932ceea8ebc4646f560833ca2ceb1d3213835ddf9fadbcf2945d4856f43f629460190e334b2dd4df6301113eec3851b657451c18b0c6f091aba855a98159b9ec1a6343eb295785571529545ae81388a7b837c2f0eadd8312192e400af1bc1e60eb5934ac42919fbad116817bb457278c316f6129dca5cd6ccf3ba68c1371916ddd826a1e64177abfd10daa24fc2e5a3e13cb6d8bba7f538741b9ad15976adb5e4a714f8b6c6648a4866823dff8abfcfde33702165fe9126db26eaad72a803adf5b36226705d5960c2bdf754f871108a63f600f8f3c770ace1f20369f827b7ecf3ba005905cd1f67f98a1826fe2a9f770284d93e665852c84b87d60a9823159278160254e43d6afba8266c887fc47aa94cb7b45be69f0206a233746ebece0de7a28f02176d0884e52b2873165a654a35a33d904774775dccbb6e142075c558e333d46dccaa735a49c22e94b5e608d72f715a3f8bc9322af5cc50765bb30d9c11d688f631eb8e19a77c8b051f436d14f643e697ae74971804c7a0e1ab64361512fa204a5366b056db0d2f4d9d725f1b5e65a36f89da4d6344e6db2f6e971fc938a725e602317df68db6b7e0ed3aae399dce37814f0aca294b012cb698c7a7715d1962a3b141ad7085d5fb65a01f1a59b9e7971fb175337ee9a18651d96ec8342055b2b79466b3f78b6dc26a6b412d15b52825f1a453780f5a8a16635ba769c22776c1952aa48acb991d228570ae165fb59590401330b40373a13bf36b1e9d4f3a4148811b5d69162f661282fe7470a5852b260a84fa14c420f13f59d3346dbaa38538e6b4c8bfc295f927535a62b9042128bb8b2b9bdb1d3cdc225acbbcefa6b0e6dfbf0a85a3f4aef53e07980d5e61153e0b26f0a73947c2883e7d9a57b9b45acf94b725452485a3a832cf0065f1d9f4da83aa9b7ed10f0c712d8d849c5da8a69a6cd6fb11d3d71ca30750f4f17d70110246680a111d8c5a2bf6ec7c9eb581a77fb858ae474d26b4d03c3dc8b859f5a3f828f66e9da153793932d955441d86b77e990a540036210eec0a27e750952470454307a8fcb6500fd1bbbbeeb622dcabac74fccaf9139642d285959974a8f9ca92cf13284c21161a7674b082e115a5bf689e0d99184247178472d8f5d1a4bfdeb70aa1a4cc8fa0eebb7f645c924ffae936151ead2b291784db1a4ea66f8e7dc20f8004c144b4c071fcf0b6dd39dc63c96b315a68765787f8e652e0bbb05e4a517f1d328975927e819749db785b7330cc1d6f2f032f1431851e1bc4285d1e100c070d7cbfbf662d71a3b804261bedad1e1f9bd0851f90255957417ceeb683d065677db37a6863c27ffe9f15e9c04f0f8294160fe378a1804b2d963e7004414bf6c1af1078a258b18fa87ba05898b81d5f8c0aff0de04b16d08a04a50e635f70c920b38f16246ed95f9dfea2bee30e1cdb6e22a010a3e19c276bbff35266a1d9f0ddcfd86eb597f7c4a8767138053dd54de41c208b6a64711918a749274adb30c8abfa62b2c8c7d373c2c76ad71676080b07e4d8b895a0188ac9dbdd52a96c7c20223a9a2bd67f25392f6fcb32f85f2f9e55235fef88e222e11ed72dc3a366ca1d9add2c275b55256cdb4ea8a6f24805ab7e8742279f5d6aae35320b1bcd81254b5274bba383668edacf81d07118e43933e33ec2a1a63f6ecae5ffaf79604ddb54e759fd6a5ac2e1181fb62690612052419d03b04afe4472f552b9742fe7e0b86fa22acaa4d5ce02bc47ca30c65b808e22229458714753e9109b2d1332ee26f49b6e3a0581344693717e8b002213b126ee845e6837b46e45bb1bb29cbea16ac17c154f32e5e90f9250c0e1e682ef4a64d5680cb6bf42d2fc874f4ce88ebd1696ab7efcfaa821824d6fc7e24a44b9e0066456f5b69931d6018894cc6d4bf816b534e3f5e5ecff7854d44aa024603a4010305e0058307153ee5d43536c9aa160cac32817035c27135e2071bc9fba8231b495916b4b77d044d5d6001a30dd2ee794b1d66d44710a6c97ca33c85a343af4d638d0c61d0ed0da29ac14c8a1e139e175006f399aa9780f2883b91b0051832b340cab864527d9f4cd1ad47b978ca36990c0ba466a9a76091eb50714b1d29a08c05524dcd383439e335a3ad900c72e22dc88a04b00779b11d5c440009c2c196617089d5cd66150d155d8413ff4859da482bb2300f2bff8e02a64b7cd104f9103fbe41a40804cb1c9cc431a1748bd01d0ba00f73e23b888c037013a34604148e59503a2e400a1a29c945904505900afcc48dbe1dad384b36d8e40a503233e0d0ec9595b9aa9ac85c72a36f28640f97eb31d254e8f9e1b4d3ef3202529e424a44f50d62c9e83a9cee88be237211f885f00a7344978e4fda2f89fe982954b1a8d419b5d37a20c489ecdd5df763e022cdff5ddea42025b17ab6a079f61c0b797e38a9b27e4334141295e043c347943e85233c54d2ab773869562e433323945795f99572ff53f02cd52bf7c7600e72d0cefde3644e1aa660458cdd022ea684100c3540b0a6d0013c0c3c0c3c0c3c0cdc2e187ae36f03438e4c3215230c8d326ac494924c29a5e45e580fe6cf82776b44dd7d817d87245c100cf60b190ce8ea0e06f1279d9f25cf3feadbc19473cd92cabb0e06d9ef2f79ce83901fd3c1b42fa74cb68fe6609291bb27b7252f7f260753ab76b838954d5c5a6bc4c1a499abe695560d3898de94e0a54a8eca718b0935de60cacd54bfac3016fae51c9c1294e31a6ea842c655c4d2d65d8b96665f87f14f97f11ef8428d36cca8c106f3af28fb91b71825ff22d45883016aa8810035d2c0a30035d0c0030c07d43843d530c38f248618078c3290a10135c6c0801a62e0a1801a61e8318605de7d20a006181250e30b6494f13458400d2f2ca046171c50830b5b3066be7c6bf5f9e86b47d6d60835b44086026a64c10035b0c0e30035ae4046016a58e10035aa60a306157ae0a831051af80f3192901a5218408d28f060400d2888d478028f1e6358e000b9810570d470824928b573794ebe09e64a7156b289ba52b533c170d2c99f20ddcf82da76ece810b080470f6a2cc1205a62b797a0425e2929c1fc7dc1df83ba2a3f3f0906f1e1b93eade5b82f428249906d25c653c9abd1110c5e597749b679afb811cc9564ff1cf3f12218c4df8dd89d9d08c6132dad29152f7a949c2198b3d9d6cd684708e6ae122c09d262bd0f824934b145b97df854e91a403025a59359f76b955ef81786af92dac3058b76f2be3079d015dee6e49c44512f0c339f8478372fe9c479612ae93f8973aa94fdb5bb305896a4c4341d7d495474614e7176afdfa3b9309524c9559d312e4c49ceb2def5791469dec260228490cf36a6458fb630076d5a3ab67f79deab85392761c407fbea0a971626159e4ff84f3da1549b8579aec41f1d538ffb18631c2029e810b080070088404316b1a0010b5329d163e96e6f42ee25d8b1c3819457982f3b5ff42c7aac4faee8636d9c1cf5f3ad30059d59b28e12b2a3faac30afef7d291d42ab309a9c9434ad3cc553912a0c5eeaa352a94fd9829e0a93601e4fc899643d9e0415a634e3b992ca62d7a6730a839f24e5ff4d10f1f0318539c925d7a858e9837e4f294cfa3cc5740813c4b6438ac364c57333d91d85f944a5a4a275236eae284ce9fc72c92a96c2741b0a93fa0fb527c9899bdc82c29c435fbe0a8b8ba5da4f18f3d2ecf65bda13e650516abb449b31499d305b9dd81a4a4e96ca9c3099aded56dbc596353761cabd8b2f0be7494b34613691cd8f2f4aec5233613a499ad5123ac4844996be943a09ba84c19227a524d5cf12c6959113c4c35709c385b66c3537cf331f254c62d6e2ba095182d06d12e673d7dd71d1b66b3149985a4e687f55226192bb9fa33f5f6643c2bcdba73f3ee8504ab34798d297d292fd4246fe8e3096202b8e127ae249428d309fa0b3e69daa08edd06084d1e46ad172d1ee640683c6224c41c682525259567daf08f3ce8b9afe786294c68930cbc7aea709efd7af22c2a03d4fe5575d1c75ca4398b36c4ba6ad58c5958630fad6c8b533955298570893d4101e21cc974649394c5ba6091a84b904b55ebefbf93a1784e13eaed66428af52204c9229ef92613140184cbae98f87fe60b0d492632949e5dad20f86151f29eff667a2587db06bbfa357e911a90768f0c1a46c3c8425b9e3b8d67b30e94b8f6a2abb3baed3834189bc1ca773decb93e5c174494688922ceea8933430eeca0012c4c7eb18a70698d0c0834988b6bdc77fb7d15b183ac0d071020d010b788880c61d4c9e3ebf6773926c635286183e44c079071a7630e512e4732e1d472ad0a8834957c7adcade72279ce8600cd91bda7487e6601051957b3aa4fe071d3998743ef587281fddf1389844ff790b268809f32c1c8c2722fb4a7c0ed12fdf60caff26fad647286d1a0d379894a462b3e26fc2a8248d3698ca3abb7dac144e9ea883473e7ad880061b4cfe725246b5b40955a2b10653898ccaef39f6499e527671a0a106d3953241972cba654c12c8a7a2910693f44168e594f30ad0408341f7374dd0911fd7c33cfa0234ce60ee5efd203ad77e1e44c30ca6127e466b5c69c627bd40a30ce6f61d3f4957b4d9cfe7a5818006190cdebd5a2b6e4a74101a8351ef3c981c3e480c66dd36b182769153fa0983392d4d49da713ede3618cc6741c43c8de7fefd05a395a7ff957c6ee2a65e30652b6552fe757ddabb601c19565eb9f7e4bb4b0834b860f8926284906a73cae52d1847efa47c502abd85570ba692a3c3669ad866c1249768d2c4891fc753090b46cb0f42eb89f90aa6e46a17ccc6e63f9b56306528b7d2a1fb3f88bd0a26ff0eb7d81aa282b9f6c3868a65224d7a8e0c1a5328c4c29ca872b2537734a4607e8bf7a0540c0b1e74ca0d6844417b37f9701b15b515b39c95dd7672d5e6cddd020d2818d333c4e7196df3ef368d2798e5e414b5f2a94bbab01312954ebe36c62698f24dc5e5ece92e5d260d2618e4a82a49d0f79fe653bc04a3aec994ed204e92945c19417a88d1a3070d25182d2c8912eedfc4b2388d2498c4c9ca23d4cbe394276920c176d0388249c8522ee71f463e286123d88e43a308a66ed336e94e4936228facf11809d223071ba4470d7a88c14307348870348670348460d0bef433b76bc25270644d5140230826bf4f51de92963480603b7e71337c71337a610a4fe285f7cd5de7bc30985ecc7f8feeb57817c692bae275a7f59ce9ebc2d86a55a272f548cfe5c254d209ef26888b140be1c29c929d6b27b94c7fe816c62bd9ff4a6249253db630a9205757bf7a2d92a5843a3565c27cea3198410b73a84959f456888a53320bd3a5b89c794a6eb4c6b230791ef59cb2e57ca5ef8c589884ff3c4a09f9d1a3b2336061be3d258f56b3939fdc19af3099145b7274b194f7ed0c5798467c7b9056f1a2dd65462bcc5dffb763d2df87b9cc6085f972f6d295e52075cb19ab309e58dae964d4ee62394315661d9dcfff73acd41fce48852949c29612bc2c6858380315a63195acfb6fb4b289cd388549d23905ef7cd254f4d80c5398f479ac98a3aef2519b510a73ff29e153929438f7ce208549963b69ad94fc9ccbcd1885493494acb6df1213a600086286280ccaa4ac5c75b2c9298a720f334261f254fa2edb2bbae6748719a030c9a923fa4c1a8f1edc4f989229d1ead12a5bce559ae109c3a987329ffdbc6046270cebc946ed7dd6935c4ea052e54e2686bf657b642d070788964d98be5ccc427afc9f5819592b23ccd08449dc4992f8717b634f34ccc88449e4828eb893ada30be424686e98810953a9581eed703179f54b1884cc99f4adaa549d829630780ab94fa759f2255a09f3ee7e6fd8e8d6133f258c71d5fa964bf211de24ccfeae272cda9b14941c49184b8923945ae9cc8884b954875f0f2a6e9e1e244ce263c74e9a96f48eaa86198f30f7b5a89a32418a85e808a32949c94159d0657add3acc6884c1b7e43cc85f2b3735238cbef339f3f204f95ad930631126a9272531ada4fbce7f8622cca7d4955c302311065b0db51ffa9268262a87198830bfc9a6a475eab9c6e041468f2e03c8df80046cb698710873b8ad0eda497857b9cc3084493e5162c227253c98d04a116614c224dbb93d9a925732f2f1a31807d0cbc0870f11c4620621cc292e3c2c84522a55c91835701d14901d3b3231631086ff4faf0d2933db1d226608e253d9ecf2fee5647278654620cced7136aaa454f6271f595b1d616840dd300310c62e15bd3e56e938d2eac58c3f98cc5c65bde54e8e3e068f2087077abb33c30fc68e16ccabe3babbc73098d107b3fa65d1fd5932b2767c30db95f05822775a6c1ca3070dc8d883a982c55e515ac4e4e746d668f03f46d91e0a66e8c16cbb67ca2c76b6ca56c983c143b5c9154e4eb2fab761061e0cb2bde272f47f927e251966dcc19ca1b75de9aace04b18e303ea003c730c30ea6f571f524e8e589274807183a7e073d4cf0323823d8b163461d4c49ce51dfe309e2615b8819743048cb968388dcbbb4a43998fe925ebc0adfc1aedbb163c78e622d881972308eb7758d85d1d8cec5c1149428ba72f2e8683a38182e98a7db33fd1bcc39ce6bed3a65bf6a6e306875d7c89ffc249fce6843a11f4b4bdc8e550533d8601cd3e559cf625c10320dcc5883b9be4c849237a9c1f859336fbe7ff489a63498bc6269a57ad060b44f71ed627730d19d3318ab84103326540a25da663078f87ab332614c54bb0c6611de97fa6a6c94ec92c1a0c2579c7da9cba2eb18cce9529fe72427f2171583f14ad03e67a25b2c3fc360dabf7d39bd120ca612a497d44f39c9a17ec16c26a79dc395dea91003c5ccf08239fe96b09aaf2447785d309d942fd933c44b30f1e4ec236670c1d4baa5cbe2fae7687246d6ca160ceef19792c7549117d38249296126cda501897e30ac9f38ba53f78af49c9b10d207a39b5a2ae194ebba89f860d0f94b29ed7ce7700b2442f66070ef9133f932bb63c43888103d18949cd893f63f7cffce83c1dc3e2529897d0d2178308cb2fc78a621364cf60ec6baec7f927b497292cb0e26d9abc3777712bcdce6516208a983b1e343ae76bcb33b7941081dcc314d79366579557ff442c81ccc9d2548cb2537ddc70441881cccdf492739252c3dc5923898dc2f89d70f26d2c403f9383cfe53a20f42e06052827bb96df75e3a930a216f309f342f4a99248683103718bb7d74e5933ec54e9f1b6490903698bd3eae85f5f8a95ceb5b84b0c1e8a592cefcf89c1db41f014307183ac0d011c607745860072f42d6605633d95450ff512620774042d460dccfe92acdc92208498329bf6d759f9ca757151a4c52878b27699646d67a6d107206931cf6e42c7592dedc6f64adc6083183c9c2454813ef7622e391353c3242ca60d056e2da6782dd9b8909216430a55dc9e1399ed649dc47c8184c92b4942bc64d924f8acb20440ce6ddca9cf5cd47d6d40d21613058befca5e73d5b186f646d8c1f41c8486c4308188cf1694dea0b1dc4e8d810f285102f584817f404215cb01d5982902d98539013c2937072f0a4a43c42b4703b4a1642b03083902bd88e1a8458c16023f6c4a9999ea00f11ecd8e1e34715cc39f7eb2529d8f8497b64ad06ae03534308154c42ee7ffc9d1eb55b9982c9d4bce2840b256dda523029b9766449a33e3fe95130cc988538cb43c120b34e5225d79960e2279892b03af6d92c4e305f253f394b6b79254f138ca21fbf724ed761ac6382614ede9fe8f3124caea562eb452d955e9560dc50d2ea5fb5bc949c2418eb63cac5f9d441ba88048377bd6c974e2bff3982d953acfed6fbb8f799110e32cda42b9f2db7428a600e96929c8bf5b94b46214430a91e695ab54a4330c8d6cdcd4f31aea264647f1022844dd261a5725d10a120b82775ca56a57ebd630810187b93f4c979df03481853b0f10b639cf614d1296d29ad8dacedd8b10308367c61da4feb37a3266f4e522ff6916d92ceb0245e182eebbf8390a5d3c8d02e4c9f265709da35d22aac0b83ea9ca34962ee9bea9a0b6325afafcea5feeb212ecc2687bf78c2a9f9a7b730fa5be59496ebcdaddbb1c37d944146dbc27069dac446fa8bacb916a676936fe76dc654286dd0c21cbf64869ca0c4dafb6761f8b72e93ea7dc33c2e0b936c297946c58f6fa6b1309f1ea15e4b437b7c7164bf07903060618a15f3b13bff0a53fdaf9fecc5ae308926f8ba7b3cf934de0c62a315862b59fe94543affe42456983cdda913bd74779b9c10b080870f1bab309fc58f193d2255184674ca853f15464b7bca04a1438b7f8f0a5338758bad97526b074f61f23219a5f374d62439a6309ad89ff2b957fb4436b256c60f2041c878b41f78f4230475a530ac998e975e27cd934a073d5293c2248f27a5b4e6cc97ce8fc29483257d1384ccb8de47d6140c1ba2b0438f0927cc55caa1306b581274e89077712f284cf224512f675ceed2d7c8da0f1eef478c311ec88ec0c62730c9e773da13c64effb9aab5ad66eac14304363ae18cd727294d9cb09285d47513863ff149caf1526f6623363491785b7dddfef7b57b26bcb4279824ae39b266983075ca494992b09b7d5dba843925ebce393e9735554b9883b24e5216719ff6fe4a984ed691a9172e84920c7e810d4a98a43cff93a3741825f3c8964dc22477ecf730b9731ca54b4213eff7717fc9489852f2f192d7726a318484e9b774d076ffcd4e623e7a9ca5066c3cc278eaf4bbe4da1d614e162c79e93c7b26a939f8a18d309c94263b999c6684a9ef9438fef941e7828b30c8f1a03644798a30854f7297501b27893f1361ca395ece3916fa4fc4883049c92b8758d57fe67f08c308912b6a02e4078fdc800d43982de8a0ec93b0d713bd33cc808d427c7266abcfd97c48b38ca7018f20ae57860d4218f4b968070b35f75a26076c0cc2f81eef3956be2759b991351afc93c1c3c78f04e1712eb02188e45d5369965aac1a7c901e3c727080a005229faf0aa72b4927fd051006954ca45c89962533cdc1fb38fb8349969d32398b3e7bf7479607901f3c727080a875c0861f0c965ff5b633afaae436fa6052c9d3d78a2a61dbee91351e641cce2b810d3e184ef98bffbf75f0770fa63241cf873a41c4c564011b7af0e2560adf718bfe08a2f736f29058f2ebfb09a2f691352583950c1374593c184c9075724f656ae30ee62407934fc9a5a4e5ebc8c0861d0c26f44b6c8fecd26983c0461d4cd97e97449810dffbd0c19c9469b3f441c9cff93307d347ffec3cbb6cc82133e5f17cefa29583f781898d38183b94bc99b814d4e30a07c379a951a293f68f2dbec15c73c972ca4f82ca22731b6e308dee8b9e6bd6a673ab36dae05f923ecf456b2fd11d3b76ec6043a93b895d3183a1031b6b30bf25f1b28cd6bfadaf06a378be78de6d0bf1491b6930c8d8ccfb60e5c84ec0061adcd13958f99ae90ca6cbb99126eb6a063d8b7bf23ce15406537e936dc40893b64e440653cf6577137a4628d5c7608e159fbcb4e5314b25623089f755e1bbbc84d84f188c1a3a2c0535f9d0d182c1dcd97f6371bb19a62f984d3ca9a3a7a87bfc4c0f0536bc601239e9e956a6ee44a50b26adca3d51511b5ce092d2e1e53aa82c7d600b36b6608e77a54e8a258f507f69e1249f4b0a7e626a0216ecd821c62901df000c1d957c0330748ce0033ab20d36b2606aad0fbf5541a8d2a1b5c10616be24a52c49e7ade751dbb8822925bbf2d22176c38258061b56308997a474d5189db349abb0abfb2949eef6cbb512071b5428c6679eb0b09c8d2918edeff5930e72b584df86144ce29dea58a91cc45328e8322041c680828d2818dbac2a5fec0a964f86800d2898245d4968bff109a6bc1f4dbe68160e6c38c1e8694295497a6cf3434d308d9fecb5fcd9444b15261867cd2a5d6fddc8899760569332d342960e56660f1b4a305b50170fcfb1f29f02c60e200724c8180148828d2498eed287bc7bf2ced1b30d2418d544aaeb8c360ff6114c5b4976a6b7c58611ccc9474fb2f44790627bf4c34762c04611d617f55f8244482e7966f48886fb890ee13bdd4943cc4e34ddd9106c0881bbabbfbe1191265d82dc00046c416e5083e6818d2098e63aa95cbd612a5d361b403005659db2d7c9fb74f20b9358ae759572bcb57e5fa8b226c88f9da817a6203a25699742bc30acfc6d08b16d2a42be0b938f9f9e5d708b16725d9864d6cc77f08bb1b4e7c22422c4a5f0a9e3c2ec965ffd991fd7aae416c638b1b7dfea43547b6c61d6f4ac26499f61599d5a98c2a81276fad3b4309ce8342a74ba3cbaed59984e8689a2f2949ca6c3b230a5e8fb29e89855ca6d2c8c214e3651465f60619e3349da0a2d9ee396571867cea289598b2bcc75722c1fa1ed8349698549fe938452baa2e5569515e64b133a49e3b6b1ff2accb1d2cb9325b7d0a9548561f6d229edd2ca2aa5c2a4c41c153d49d6d9535498c4ccdec9bd1c7362780ab3ee5eb81c744ed2e9670a63679cd2f3d495734e29cc23f4c9bfe7868ed24861cc52e2495e4f9e521a85d1842e5d4b7d92f0c9aa210a93787777cadd4c12be0e852945277d424f0c8d9747d64a056a80c21cd6254977afdbb95d8d4f9853c9af8713cb3f9de809b379f0ff9c64fb09c294408d4e98b26ddaa7f814f428d1c637d4e084c9cdfd64df0e65e9c43661d0cffc74eee7fbf861d7811a9a30c8ac526ae74453b27e9940f5cf25ad327bbbd05003137d4eea6de71e1e5be310b080070a6a5cc22ca7ddae73475dd34a0d0e102016b000f247c18e1d58c312067962f69892ac4a98e47bface5fabfeda2961bccdd5d06946dea427f1999decb8a2a263d49084494decce0b42ac428d487c234c86faa8d6808449dac96b52b6f023ccf62697eca354c850c311e6583f32e2c467dbaa34c2a4a7fc7f3fa4f78958750b3518515ba5ef2cc2e4a77fad3e478a309be852829824889b134f84614709b26cf4eedb3d883056ee903be2d94af810a61925af78a924f89b0c61926d323f497225a98d8530b9a56eaba03c761e11c2d4234b9bf0fb31c4bd419884b06ad1f434498a550d41986d474bddb3ab04b9d20b350261b62c313d492232b70784e1e442bd4929ffc19cffbb6fe477bfc5b5861fcc276fed537eeb8339a8fb3541c7b95739f960909df3954a6ce6e4a43d187fcc3e94b8fc5142d68349101d4cdfa48925a83f0f86b32459e53c4a5d7b840793349d941059cd4bbe9dbc7b30ea5ac92927aa07c3989810f914d4e74fe6c1585ed6ffd92fddcd87876295beac244677309fc9b2646dd2365f3b9842c926c9558a263f5575309fbefc94a5ae7ef4f68114b8410793146579921eb5a054b06c0e959c63a911ab758235b20b24c81844b82107b3eedfdf5bda13f154126ec4016fc0c1bc2676784982cba84adf7883d194c5519743663e99c9811b6e30a57095ef142c3d87db6052da39ada767b354618331a4c7f1144e2f9ed4afc1142e5b9fa4d28ef523351894d03095237a1a4cd9392234c4070de6f592f475a8f80c2649f48dbd12c3ddc26b0663e97cd51e43a90ce692e4fcb1b28f50d793c1249d3cb9c7c3979ea563309ed0a7befcb2cf87530ca614bfd869d9cc4abe30182d8855d2e57b3a540383b1f46e7e56e7cffbd48d2f182d694fddf082490e1bad25965f5f236e74c194b2fa655d365584aa1371830ba69bcd924fb0591137b660102769ad144b78d58984b8a105e3873b5d4f712ba994d8871b5930ca7c5acb9fa4381037b060524aa55dcef2f1491ac148c48d2b98d49859b6cbf69c6d84c50aa6157516f794dca882417f8d786851e94b09550d37a860124275fad50915d72f9a871b5330a5eab0726725465c89871b523029edf92a4f9c521b7e148c21ceb4890f3a7e250e058392c4b4d3f9a44b92de27985abfd7a49649cd927682498915ad4e9f54d7e146134c2add4992c73fa19354c204a38ea5774a9de296b4d87063090695c2d7280ff1c8dac5e086120c57b1e5e40921dbf51e623cc3e046124c1ef483aed692bfdd6f20c1e4d7f757e3b98487d008e070e308a61629ea574b5092db78c30846d121b2be23457e7e2cb8510493f69255673a44ae5b64b841049350929825175b7bacbf37dc188259d7735b8e3d5a41772198b5e2d228d3104f49340c378260dc132babf9997c4a7c1b0c3780602cb1f54de9d13dfa2fcce1ec93d817a61c3abe9e921dbca2a717e68e7bfac48e3a499f102f8c6dbda25c554fb08b7761fea43bdb76324157d4d620a20ba3895689572a69fb996220920bb386ce21c455eec7897061b63541e673a97a18931f68ef16858cd041df4b467741c4164693939a4bf1acaf9208088f1f888520520ba3c9dd5aca927f57bc8c8c51831b246b4110a18549a825c94b84d2552b8dace11d41641606ef8f0fa124a5c4833d109185c15da4c8bf3b3d13cb26446281271defd48f546f0b8f1f880b2c8c3257e972feaf68496af802a009915718ee427b2e1d952c87d6550b1157182c8ac5a524e55171433e7cd4e007900c342b445a6130ad1643873139643b9f81c20a83fed48b63c2f357cdab30483529b77ec4ec8950441586b3f8f25fe22e920ac3ece7edf89f4fe72e2a4c59727e27ad2405d5a791352485c8296c8788298c2589ba5b89df55f847d6ca7484a1012061f81001183ac0d0c1a110298541d7c9ce784fbda1c5420aa3adc8aace7fe7685e7181c828cc496c4beac42c97d2771105ee0885e974dcc2042505853955f7e5ded176f5f013e62a4189792373fce3f535384032b063c71e443c6110522ed44eb6aaefa04e984a36f573a3d29c30e66f8930d32d6a95ee4d984d4d7ec731bf5822c45410d184f192a8ec1a1fe4fb148348260c57a3224f720beaf6cc2f8860c26cdd6667f6c1d3bc7e442e610c71f224ad654990964202114b98f32d6445e8ce39443ab2c6e307421b8854c2582aaba449d1fe81ec8708254c729222a36b2f483b2d193dd07484a18163bf10998441754922f2761c594b388848023dbfd89e932abd236b08021d60e80023b1b445c29c61c25c1e1d2577744898938c594ba3f24798faf458d493b3a7188f88234cc27d496255aefe34a748238c6b618492262813db9608234c56b36e5254ea3f576411c6d0169ee4fdbaaaddc50b228a3067dddcbbcb89979d144407cfc30e2289306e7f2ec964c78aacf0d541041106b792774a0efec94cf46e0e228758114398348434355963e393285208833ab1de345e2fc705a5408410a614bd3f592559d2bde700b9c11119842927e1a24678877cf4c8c08e1d381084d15a3db4e3839ae56b6f1009c4773f27e8d8631b440061ee5425e4d35638f1c191b53b1d6168400f901b9c20227f3057de77fa5c6975e7e07d9815113f14731a1ee48752a236b226d207737c29378b1e4e41840f46dbfd3db172f27c627f05913d984bda8b63a72f1ce001e48388d123a999881e0c9f76f6a49b3d0fc7c97c7b878d99227830e9569d93e2c911b983494cf0fa18276c68554e10b1834934552a5cf70421428805913a184e5a9bdad8131d4cf245c62a295d5e3a670ea691fbb14a36572d8e5d05113998d2a510fd9cc44db6afeb06913898cb942a39e9d00af755183c8c11227030c69aaa92c4adceee0da63a2594982785ca577260e800a30c11371846e834c12d495592b6226d30c7ad0edfd36c3933f9917c70000c1e86106183395ac7084f59d5728d0c0c1d3ac2f8c01344d660ce259ef6b45bbde551113518f7e3c72c89a2d396f84541240d668f279e6c4b3933261444d06030f14e1cdde555a2967a226730e948cbfd93bcd22feb10b080c70344cc6050fa59ffa4541fe47a2d5206833449d0ad2ce522d6152183694d3865da23ac2fb88783320673a812cb4d87c7bf908b88c1a4c3c7bc518f399f2d913018cb949d569cfa94672d41040c06378fcd4bc2e9d25519590b72831ab0059220f285db0103112fe00b44ba605031af2429573429e713174c72977041e99e1cc6f25b30ea8554936d4b6cf7245a30eb993cc253d0ad66255930ef5ee838a3f2fbfd8305539ef4775a21c42edf2b98bc74734d999aa5e45ac1ac6e920c613196539655306d9f3aaf10158ca77b7d72d219da9f82b9fbc354490f4bc128de4125a5568b8241767aac9cba824cb144a060ac90234e923c6d1910790257dd25c6673c14d320e204c3e7f927dd1543e787c7239e41a40966f1935dc177e42441c58309c63a59d49258a2a7a5c720b204e38d1eb197efba3ab8236bc705112598624952d23b91a1fb3f3cecb6209204a309e6275d6aa5243aee428820c1f8e9269af925a1de1521448e608a5f5372fe1caf4f220b848811cc775d52782fe9445a5a04c388bf973427c7d99d050f2244304952f01cfd3f9558d27f07912198e48962d2872ad5eb3922423067fd4ff1e3cad6e99c8804c1f4af2f7ab1f2dd994704080661da74ceb623f3cc1a596bd3118606f617a628aa57f572b72f6e44882f4c622f9f3fd3a43415d31186062ea417464ba25cd98e344994b82a11c20b73d63a1d3b8ab22311b20be349d317a2a61594705a4084e8c2ec6ea125f555275174c07893019842482e0c163f26c9214c8e26687161eed5d02994fa236b69bd08b985395ada4e1f74c86ed3d0426c618a36632acaac1e40c608f2e34706541221b530f75cbe37adb3d3cdf610420b83a7fe5ffed1499a4e1d1e426661d225574a8b77ba2cb621b2305f5a0e76c227b99773426261f42e398db813d6e2f46708818541beac8e5c93d773f22b8c1dbbf24f327d395a5281105718c46b6bf45d89fb17c221a415490b9f93e43217fa0a4258611e9de91d3d59566114ffafdc9ea458eaca1055984b647e89f6d4364a7c88905498840aa253575edfacd4c8da211823f88062210415b643f714463b3909425976ce2974c20003f910631d11620ac3aefb950ea6d385f9b81426f115fc738dcc0f21a43007ad4e49ac6ef7399511326e30461919ad81905198d3eee2532825264b92aa284c496b975229091923a3cc50183f53c4548bca497f050aa3da760e953ba87426e913a6309e3aff8b92d28cd813a6b9b0ffd1fe72facd9d30275592486b4f6267be4c4e1884ce21d772d5849b9819b20993645af45de5b0d4b035618ecf92534e213545949930accf273927f15cc9337c4308262ee412076b6f7515a91f1f22d001868e30aa10620953ee8f7982d9083d1b932188904a18f76e4f0a2ae78a4b77ec00430825b291bbb3380db3d8124226610e93cc8437255cb660626908918429595af5a06d374448244cb96327c6cb56a54a8284295cd6763f97bbf49ec4471847a80aef9de504394b078f7c9ce521c411468d33512eeba411060f9d5a424b2ff9ab5a08618429dec922e345b784911761b8e49b7e2abb8b97bc9621441126ddecbfd70e3a9f5425c2f81b6e4accc4e64b141126e9234eef7a8878091dc260e2b7eda5c8d710b883430a61ca26a99c925b3ea8feaa21843025f7bb1cb75416932ccb42c820304410069ddd792efc68287503610c51a9dbd6f1a1040b200c4a98ad74efba1542fe605c4b297fedb64615392661aeac5f57324e49e2491226dbf368a7a4124df991309a78c9b553699d542724ccb13c4bbd9be8d9f9230c7367929ceae8bc26e80893e5ddbf60fa2db6748d30452bbf70d729c7df8d8b9083117927b192243f8b4849f2d38afbfa8616a91a7228c2144727b123e6e184957324c2a47e96945e1263f6d376861c88308a30eff6245752973e8461438956fac6a396a78630b8eadbc54b21234e0a61b2647ad4db2eee091e21dcf9bff44a6258f2b5f420c7200c26872b495f334170207200c276648e3f600e3f98c43b954b9c5f7d287ddf85881b37e9c9400974d45922071f0ce23f7a21a4ef8e05f770831c7a30995bb97c8bc2c8c30e72e001b9a173e7d3dfe5e6650858c003e5b8c365e1dd4354d36abc5fcc538ea7ab3701dac1a4ffe27dd14e9807cb0739ea60924d2595a4fc1fd3ce1f591ee4a083e9450921af34078338b9ad839aa42a85c6e891832c23871ccc1e439560a2ddd489760edec71b5621471c0cb2254cce1fe5467acb52c8010753bcb151163fe7917d1728e47883614ecdc28a14cbfda41b8ca133f4eaeb32b2d60683f873774ffa20417a00c9013aaff7841c6c306d776ca8bbe4c94b6e0da61153aaf6e28c29f5dc3121871accc1b2083359c42b28531acc9e4478f8d221dceaae53420e341843c8f0d610695250692721c7198c9d930efafd4add2f480c317ad0c00ca6562d295f6e5668d3cb601ea52795944b2783c17f7b4fcfe5acdd298c841c633089a6275dac4f8eacf140e78188c124eaef2af6e3a552c6d3a0182147188c6352944fe1dd313a82c124f849a52b855dbf184a22e4f88251d53b5e27211ebb3b32bc60fc13f34b8ce54f79a45d3099b8b9cb62652af319590b42460f313e488f112e98629fb2acb81a59fd8690630b264b0da56546c9298b164c677127a9353d2242250be62d49343d7f6641ca2e841c58308f6eaee838b677520000428e2b1894c5f2b06157e7256761e4b08279b3e4a072a27e3b997e04c914e4a882792b463b4dac13d20603c8e5a0824958b94f2dbe7767d9f70012240239a660da4b9d66fee42ba5444dc82105538ac8eff5fca9418e2860dafd82ebbc4923cf1c50309ecc2fa1829fb228678e27c02087138c7aad765259dd9724d84506399a90399890399660926eb1e37a560190218712ccd982c9bd9f29c14a7c8b227224c160fa154d0abb648279236b3990605062265af796fa397764adde90e308e674e2f6965d6531e194c308b683b790a308e69455ca4e0a7ac24b1011cc61bdb2945cf221984a9ffc54417542307849eadaeaa431d12b47106c470e20d88e29e0f8c5e1f0855964fb241d67acb5ef5e98a2887cce9fefa57b7961d290f5b9d4ebaf78128e5d9893b62ab14d8aeac294636f62a8905fa1bd5c9852ca5a1fa2449564172e0cf7c992ae9cb52ee0b885b12b7aecdfdb5cc0610bb3bd7c345da5fb6d7961c3510bc35d67f79ffad8417a46d6f48a05d1c1232d4c72c5c56b2b497ff764fc10c3cbf500c72c4c3b962b277ffb47103c1ae09085416d9658b19292d764a810386261fc2873c2f8beb03099f87c492cd9f990f1f8c1658de315e6b4a154ce90d5d3370870b8e2979326df2549c503385a611aeb58e24faee5144e64854989ecf47abb796a269ac1b10a22e05085c15ec468a9b74be96338526110fdf9cad27470a0c2e051c7835a10f23aa7531854501f53df6f8289de1426e1f49bcbe550a733540ae3a54bc1a3c50a553a278569472fd6ac75cf29e151984bc7acabcab24e4aa230b627957b95dd5018445f921767495018d4f77eb2aa93f498d0c85a0112640c1c9f305c095de9cfb2ed69e90953ee14b4977a92be63d809e385975bf56052d6ce09c3989d0595de3fc7689b30a955e8b9f0ccac94d284a9a3a9fa244b4e260cb729e2eb2aad5592c7844989a1da4da4680b42c44b18342df65f9b8af8fb96308b90352d3e5a472869254cba4f08f1414ee8543e254c6b7a5775b64cd879276192f3d37c7a8f1da27b49186e3d96a4761d6b8a84d1434eaadf8f9030d8ff7b9fce3fc2eca992f0ba1139c2202b8e65b3e065234c525412e476329537438c30892f7582879d0f9f4e5c8449d2fcca0ff61a1fbb22cc29599dcef8927f5e4f84f947cdc51abdb1110d110653f1d4c9b9d45cb0f8214ce1c496e0a32efee40c6150a293aca1534c185952089352279a3067a2ca4b843089fe97b482e83a0853c89a6abdc91184f1b404bbabd41b4a92f281231066b5249b3e21b445e91c401854d21546bc6cf907a345d910954b51166a3f183fa558ba9e5216b9a6f6c154a28af6fb68f3c154b9f288302baf94927b3088f20a4afc9ef56092ca4c8efd3cfb4bc27930a8ab4bdb62423e4cc454d68791bf23dfc194d3845affc9b5a7640773496adc44888d9ab60ea692ada427cf15d2c1a0e40f3aefc436cb973207934e53fd3c7aab4437a91c4ce623abf297bc74d21287e44925e7d45f0207931c5ae2e9d115a652de60be1833eff78b4f517583f93b9c741525850db5c124edad859256549815b1c1249bb05cbe65b2baca3598e41c9b65bda1ee564d35984d3c1de73ddb96cac13498cd47858a361e66d6f1c08106939295ebdaa5b746ed67f82495733398b34fca6f9d351ded32984e8b32492e25a8b718a980830c6613da54ca717a2f2d63309a25e9313ddd4927280673c91dc2554ec7385d18cca26455bcc90506931464253d4aa56cdd292d041c5f3009b72449c2eeb8a98eff10c3043b7690e12160010f16e0f042e2e882f162c96d278afcac5ec1c105a37b49c279523d901f3c4e0238b6606ad1a5e71fb267c1d282491e255df77c099e3a94028e2c1864fbe5284bde6a2333137060c1fc9e5b3e5d8e9b7716b780e30ae63d4f6b2655924b3099d1020e2b98c6249346e6474f4e1d1c5530fc9ba43b4b4a97ec526af041c828e31207154cba4b8cf8ba6869f69f82d14c9f9c2e48932e9fb014cc313fd5a518c292785a144cf573a2675bea5338c101051c4f30fa8997a4594f8a030e2798437fb9878f62e25788a30946936b629e94332698b24a94a815765a4bb80f816309c6ed94520952fece724a09a6f82d7df32786c0910453c5cfdd924d5dae510f0207120c7349a71cc1242b6e683b612d67ff7118c1589265f597604a3544388a60cadae9e9b21d2298e405a5d3567ccaa1f9104cca7475d99fd6f1fc8460fca04f9c12fd71c2c5c43828021c4130cc97b0e2a5b7e265fb0f388070bf30e999885151a265377c61b21c2642e9f0cffef11bbd3068c89e244d09fef1a46ef002cf2c418589a193cde50937766150e29e4a9d436cc6a5236bec3dc420c36ee8c2dcc1537cacb8173772e1462e4c6af66424439170031778e3167ac316c6b7cbdda7aaee3edca88541c5f91116a49df0b86961aeb212a653342f490791e1186eccc228aac7d25e5b4aebcfc81aeac1a38c84c701f911c4043b76ec10e3f018838c0b04d181c1c20d59c4c220da749a3ed3a5f2f4b030dfa5ecb78e36e9e5e41526ed64e6f2265adde4b8c2eca27e2c547dcbc5d28a1bac60ecbe2e5acb792553b76eacc230aa2df25e7d3c4caa0a73c5fd5b7e79cb8ab60837525143b8810a83cc59ec60ca74f44f374e61b2f1134b3095c4e335b06307196e77c314d8e84629ccf972cc2b9dfc0faa2685394539134fab83acbcdd1885490e3142279547fdf2130216f068c10d5198b43fbb4dfc9c651385c2302aa58c5f5d6e80c2246d56eb93c93655ca1b9f3086acb01a8b7a56faf4c45fa2c627615f42d310b080c78f1b9d30a91497deb99f4fba3c270c2e27769f1cf7ed1f6dc228e29e3f4c652dd9234d18648927eea5d0cf4fa14c98d48716934e2c0f6a1e4c18fc53aa1359aadd83fa120661f7e12cc592f29ab0250cfa7323bf4a5e4d732b618acb1b5da64b1653522861d895137af95326615013d6bc4e525d76154998d3ee99f84165155566244c594aa824a73036964b4818d746e5b56fcdfb183ec2245ac9d0d175471884f6e79eec27af74230cca83499f849af7b418230c5a4a5e33a556bf841661aaf130b735a7797214613439c9733947936172811b8930556c9d9d0b2f9d5f42842998f8f026f6b5e41c1dc2181f3f6408739d285ec2e82ab3de0b615a3d7dba3ea26d4408831063c2aaf2e2a8f4204c695e94896c3fe9d74410a6b0d749c7e45c92e407c2f07392feeaf7b0e702c2d4f969ab15e492d6ce1fcc295ae3639efaca6b3f98937579679fb459a53e9892124c4e352df3c1943e258f0ea82431461259240c8702815010c4303cbc4d00431308001834248d85a2f1803c12d67d1400044d34224232361c2a2016161a8b84236128180c0643a2302014060602a1501048424a9c651d2b37f54dd34deaa450b5a00c0bf03052fb1efb559596adf6285c3d511d63581afbf38214e5ea8fcd695c0c8c1919957f55b1ba31e0c130acda6159615167cc6d6808925c991760852dde1ef9159ca604e7b08c9be38ba3e189315b6dfcf826f419cdc34a47d4798ef8fd229ffb903abf2b775a37f855291f073e26ba0f4b780ef18345027926c8c3997fb412882c6c9d8848af29e8038c549d984e742e364366afb037aaa2c8019c33bc0d0e49e9904d774ffc151a176c3cf9c97e0e30f14be4458fea5e4427a7e3933b0b8664e3cdb3db4acca4d7a75b2992565a8d0abb11acb8ff6ef5fbd939dc2dd237de8ef029e9c2acb947200d876b66ee2cfe9f284121c4ea4fb6c7f10426039e442239b320804e3c156cb80e8c340503a7b95b5dafce54eb6ce9ea948a5e139732dd999a37664f3419af105aab0f170ee1075587fc8d5796311ff33ea6fd092aed6dba0604f3b641da9fd640e4c37591b344c7786c2914a803dbc7d69c2761d4e497abe3d679acf6d691d3d99785a13709fdbbd898a221b74b3c6387253152068fbf6cf99bd8dd50cb5b1e544450a64b5986017a54e9b736dcd1ec3dd5848405d4adbc8efb11a88c8ae76a91808178f171768ea7829f9ed879ea434c8eb087281d161d1e8dcf597d3d02cc3942ac88ca8a9c45e75c4066ba2ce89e7f50b380c184473c26a7c2345f786c723cae5bf6c0dc567d167c0863c444b04276080a8e16970e8063368f1496e0763ed17840ad4d50212a53c7e8877ef18bad1f68b39b83580c84311123af76847c7689286b42d1b40f61a928910d91503804d881ef84427fa897413bb5e6825f439b4a8869bc92658d7046227145062fe6f38783b46cbe3a0dc6e4a9fd74169fc4168bef411c596b0ca65f224496b56884770e43f8c07540ab44881c416a6349d55c5e148ec16dfa1411df02681433efedea3611832519aed26be51f73630f850d0065bab4085c4c5698c0ec49232de3a375e8f007896f80fae5bdb3e57acae88e153b32a6cb8c12d0629600cc6b20ca9311ae18af04dbe28ca5a5d441f017b869e008518b4bc45c27bb10e3881448db81646e820c9927014a8f6fd396cdd2579fa64009a02db7e874e71174ef9fb2ddb084a3ddd46fe658d761431dc7c6c7fd9aa3949a23561d65dc15b0a3707d4d519ed7c4071b9971c3301335a8434662a2a1aff3732915c0c5609c01c62d83311ed7ca0365e0b08f734f6792f9b70199ec1be6f9973058cec5d0d2b102c5f21dd56dcc64c6dc125c089f57bcd6904c5e5f95f8c7343b9c4dad3403649c1cfec6c76b3f45faa2e30b0a320b012bd32696b2734cb1daf8e3672d05d346d5a6c195e9bc6236349f6cef8f6c00eec30b13ac0d965b531a15ffc878435a10cd4a8ac2e0cf9c2939a79a0d6dbea3e59678a618f4808da754229e6834325ac72ebc94f83394c2c67c27cd170510694a9ef23b3ce49eaca4ad32203386c821a857a9d59dfe650dd633632950c04215c2176354a3b15cc0643c39a34b612dd1a697b32818da143c09752be7d7c8265fac0918caa59eaa0bf79d73ff93b564fa0fe9917125e9079283562abfb234dcf6fe54f97750326698113968144b7a6db7cf4fd857376dc9e57d97f744b83c2f882baa041df49501f66ea89466c6a3164ee44b02fda89283c3f5145fddf794de34409e1fd584e6884dc0d147193c8f1497dadc1534412967560a46fbe510b22063fc7b99ca5ed7ad69f05b05e4aae222a000eb2d2f514c9b67af6409a13a7e6594d8fe1165105e7df0a4e365d0798c68343cff44774746c8d09d8a91c24467ae63c05566e14a51cafbc247abdabf14804e5ed86713a0ea3670996e5ce1f3eb06ae2a9a5142eb85aca84ae892802f91562ec1edf091d96f4237f8c4139eed44914f531b89308e570631b4003b95a9303f13fb2d093ce4eb620c535ad7d67787ae76136e3cb871b41f5e9c38a98c391be6e1b87562f46688ec8406c792d9e0dd21b603925ed40a01b638e898892d7a26f0ea40f093ef67fae5e095ac32766e2d5e47da6d3b8e78cf3f3ac8ca7c296376ae89f00ecb9a0291ec39dc79df0eb97a8b329f683565c88fbfa40f1859827cebae7794da2787bd0d852d4ffe199a251ebfcb97acb323d54ad235aaa710e52d8501ebe19218984799ed2077001fa44878cd26eb5edf43f52abfe148063139631322d6d2aed57ee29def5caa2b2ec79d5d57a6b89f2b066fb9e915d099ce37ef4799dd1123276f7e759e58e9c792db5873e490dd0ab98cc069351af260805ba1e976dbedebc7fc159935a3ee5cf18ad88eb8e4775b8ce230d32955c46f4e0bd09e47765a5871cddb5704f75b6c1018032542429ff4a6c4bcaedc5863cb72aaffc798ef7d669e7f3a40b6f39388bba77b2fc84fed636d7ae2dec71d1e99343c30e2132863858060458b2f220a4cb8eab549d8476c7a62ad9938ef4dc63b39a6910730b5c0bf6999904d6246c356d57543f671c982d203f420ef0a61c3da70f001932a0008b93931544d1036f2d913768361a90707396222b98b9ce6cae36c24a50c0d0d532125b98a126e43a9bbbdb585a638a9b4bafe8cbcbfaa2cf788db1a19b64ef9862e1495bbe5ecc44b30a74b1a1987d71a4e14a560c81c314ba1d79ca9e962dd3b5d80664128e8c59214e459057483d82e3e2f022749f6cd329bc6661f5423bafeab7bb1978b3757a9fe7c2a8e1d9c51cc6f2801191a49897360e612c8b6deeea4616f3bd1473b64a652e55a78823b4f0e581ff56dd1ee448bff0b50b8bd5c6d0bcb62950a0a2ba85583ccc6efcec75b70c73bb653be66fe0e85cc1a4fe1b4fa4a890ccab3ceeec000124cf7b7724b3629f76845e1180acdd608cecce0e1f2435e1d9492e9d25620d1c801619ed63d54a2279fa31f1b55c38da68145e0475a42591d95c1436f419d5022cc769b8cca27958760b6709817b629c410852c65f9a5e4acad0367166ce15315cc7a917f781b6dc637bcb2b566e08c93cac26a838d1d735f882c6405491ddf2f8e272399690548fc9c7c90a0adb29d14969cc7930eb8e631a94473739e773c8693ac873a20e594294d8aa14ebf0ecaa5a8581d292f51fd83c355c92021d3820de11b40c83ec41ff5f225ff3c2c77dfa3711fe417357f2caec1a34f6d7e1df9eae7825deef2a1a93dc961314a049619d88ebfc955ceeeeec0d3b53063f0da604890c2a166357c2ae70d62b50810187a8d7ea698b43f91c53bce3882201adf6ac817d486c7547d916f5f438760d2c9f39a9e2644e161731f3b382452ea2c22a43cf8c9b7f0416eb8991abdbb697bc01269c2650263a14f7ddb8caf9769ac6c831d57e40533071d1b31346d6338329d84938772edcfbacb33900e763e8c57cbc9ac04c9ef0b78f7b3d89e8de4185f2ccb0e961e92064b46a151c42beb86bf855aae6f43bb19b5774dab3303a23a00b9b831046eb590ce1d0b713310cababdd8e6f0913a85bcec469ebe247beb0360000954023378045b403eb881824b5e13d01048078c800b00a3808b2c9b2f1ccd2a79543c60e69c20441d7c55db4ff795e82668085370344a63f35230c7c9e245dac0345734d25acd343db5486db826129a19211e60c429b90a711a73816c218311436f83b9069d009ca7b0e6c53bc1a17610892852c12388141a913788fe708ac2cc64546696c039b461e72b9325d830ac2bc0138f3d10d44b1a52e6779cd69ea2d46a134a09dd3fa811fba514aea11498fe93ac7eaddb96e410649e009d711fe6e955a7b5d1a1ab94b6486d7a403e3702b4974aaca176410debfa7b03e98303bb8e3754a6faa493a30d194edfa0ee4c66000e4a4a74f3534ed0454f05d73036355f52651c5b5528f851a3a8117a06f68da475d4d6f88677a6e7fae74dd346a3ec024612c5b98e067989908f4b50730c72dba4b5cd101c93121ffe38a2c0e38f2188b812cd7a5877a29e8109c366be41427a1e9a7a5b4c8e7a8dea653a71c3cc9203147c5208c85e4c8abbfa16d91e03ae84d4b4047227447d1146ce4b34f035e2e1573d505f97ead1e94329f84cc2337520cbc67fdb40d58f820d5c1f98ab19e4c623c775c8e38fa65b48566f1b9bfa067c9ff8f4bbd55f520c242a68e539b8e0f4409c154fa34ab10bcbd4018a2245524a9e9a59dd2a03cc85ba08f74ddc04d9606a1ef8cf241e199ea66b85aac105875c3e8db721a5d7b143da25dc676d4266ad82966f88779a180ceac9476090e9ad8166e809262d9970c89ea24a2df8a548fe52c9e490f3e828da2709778a11fd54f218b61116283091d204f34c34b674309e5adeb5f24c12fdb4c106cee08032a415deb8459b4c37cc46d00ab6c6ca4512d5587aa12f43e861805558faaa1bfdab6ad20015ccd86a1bb6cb071dbf42f31c1d6ca1aacfc4b8740229ce2ad27adac2dec8cf8b94289128ec6b81fcee04b721a40e5f078c02f6000dba2d013e2960f3304a70292a5a6092d85a0a88e9fb3ca765fa43f4a546cd0e3e08b9982677b2ed417b8463f3ac5d1a434723d31dea8ffac439f5a8ea4f80ba60cd5890ec184f11164bd905723d903cb74db8888fd87dff0c29ec689c5468e9714724460a2a3a5e39d86daeba570edf177a633f7e837049e0d2c2800eb1c16fa9c16cbd93bc440c091a1cbdee1ffa78abd131a887abe572268ce42220a1202c894666cc9cd1750fa0190eb398fb6af539892680fd2a064b3c9fc91d4964c0201503113b0cd4ac0e660a69d501d7df9647671ce58a153805dee69585ade70b0e6866bc415417046a1c71e9a44ce29ff5666a607852a608a3b5f273f77d627193cc52f249586283f37b0955dbd8f890245732a789947f26ab61557d8726ae894261a5006a1cc1ae578c428f1de934aa2b0f0f41f51a17405162e3a97ba5b2c80d7946679289ecb6737a39e2f8d6877c9405297a298ffec0f7e48983328671623af28bde49cc1a33ff34cb641139ca8bb7a89485a3e196070a298f5052452d14200e1fe45e263f419041d3aa92e44d554d4041985452711963fc0399ecb228e3a567c6f569a882e3526f8fd1cd6b5bf4e98c67da4ef6665683df12fcbd7245925639552376cb5e42a66f79cf5595d2ed86964f187bc48a2914b7de83f18e545acf6176438ce5101ad28df5dae01b83c2586784bbc1b5b89a609a31f1da089f10a68294a607233cd508416a6d27974d6a7a28a8f2964a587d53e1fad661f5d37341c40793bfe0c06af12a12a945f97cecb5e9d96ae008f6dafa30dccdb254a8a15848017aa31436f28f5385a50893c6da1acc931863128a9c2b2691969350638b349c19ffa87336e4a6cb7042bd1ef3b1ee12f75c9a59bea5a5bea8a6802eeb7e4ec2adf7311ba68c93f0a35473e054ed048a2f4aecabe158e2ea8f73d87828050b3b5ce0cf50e848ff9441df283a038ba41e5c3b03bcbb0ad54abf8e4b29b6159c3727d81d1c72ec5462e70d4471632a8b5a128f75752b8b446e8082282270f44090599de8c283b28ad6b3541f50685998f97980687640ec2c0c2c4d71e316475dce212f9e14433dbaea30036bc95c8ca542ba4be471321802c4480842fcf404ee9058a80b99332d98cfa8e7ae549cc57a3607d84217e68ef57581c54bc98d7b0f5360d6eb6dc7743838f245519551d1e366a352307f4b0a6e00a27c347838ec4e7bad2f3663ad6c01bd7925bfbb74290bf1770b1326fb276a9fb0c3656e5237cd50d341214089de0a44d8efb177e0cd4c2413acff8e0449d4b791b31987c0d1a8665f478c305ddb46dbd5c19fd8703e83f7aaef57de25e119d0e4a5d8aea2780388476a2fe4a0794b1b5f5efcd2e47fb9275d1131c39057647bf19623ce4e1317a5cfb3eb65e2f0459e6dde0a7dbfde40a8a3dcd2358398692208824f511bed6c0dd154e71f885debdd6fab57380b4ea94d625706cef6b8d6a224d5046a9b2b1aa4b166e0dfb6c4c20bdc5642a29f5866c1708322b2b920ba5861701e1b7d98c022db391d1929651ae2bd2a3d0f07913389c96c37e6b709cb20ea932ae4e91b97ee170cce85d7abf96908a730eb0b9a60f29af0ff9086db5440fd325ae4662c68fc03bf1ed68da8574cfa019d731219824c8d122c048491563840224a05e40c0d35d46b0c936d97bc9cff35df1347ddc6e9e307adce5f09dcf713b44f4e90cde2f08378308c5200ac582d3dafd8c2528df71446864390d8081ca558eaf908ef7339fd1c8d1b0b05bf8682eb6e5b7c6550a4a2e26211f455deacc6049cf6453ff08cbbb2543a12f8b4bff0518d0b28b7813fa1df88560f282a2271f8053a5175b9569a02417b81ef4ff6d1dead167fe53542414ada959f0fd7535aeac7d7198ef905b6a77fd1af9668b0639ea80c501b2b075b49084185eef17bf68c3c50bc41a45aa52895e3bb874acb32e63efd0f19e6a233a797939a33c10a234ec4ed1607133ab2cb60faf09b8cc09d917d405e3f28e87e4f97195d37ae4ef17eec46801fdfe39ed0388501fbc6d8de25a9faf079f28432f9ba6ca231c57df7e98ec5157c42ad417af1a35ef898812202e65056b5ccc1e11d34102fa0f7eceed824b6c94b9f6f66f215f2264a06fa6b29442fd08d8fc6da3c00a86ad4e9b5578f5202440bf0ffd56f535ae4a5b6d9434ae77fc95cd7abfdf87fdd385409877cf3ce262bb28878378ffe39a8a1ecf46c1810aed970a6228f257d21febd1b8f33a0257810cc2bc91f8f6f8b180acb2b273562091af4080141b6aa9ce8aa78bc20154275eabf1a4ccfe9229d4475d15e4c6f7843a4b34de71b3ffb3650b4cdba8d70766b73baa66343e761afdd5e6f6b987546ca98e6ddb5503c28e38eafe062a7ffe2f5005c8caa9d05f608e8f56aa84ba2a628ef7ab3afdfc5e354f14afa4383315dd20b514792aaa28a387ecc40bc3d5d898c0f0d465a0f7ba9f08d68d2ad98a954c45a0cda917440e2995fc878f1d0ec5477d5988a893cc929de22de5a2658630ec0317bd2ca8e40aa3c408419e7fe17fd102d236ee209c9f2060bce07905170a0212af1e31b26e8976a47be73c38c7fd71a44448d9136eea834abcc5d9eee962f2f4cf1524f447665851282e1d670c9088891faa3fa33e16dd2adc264203672dcd93c1880f3b98ab0ded9f464c33208e71160d52034dcba15aa7bc96797e4b26944cda8a70f6342cd9e96306244532c054947c718d8b9bdcb9e4ee56536f575b326441c92c6dc51d6374401fd585a749b0c685ba161f0cd30d0abfad8e4b186a80878a18ff9957d43493080f33fe2c80795ca3e84460bf52ed8b03a1916bb59d3823ec39264b0b57383a3a762d6d56ee237a4d16c01d7d4b8cf241b0cedf449061b011710decb5c0e854323c299e1160e7629819852e69d694ca8bdde1b270f32466fbec1809ca7b402c14b1ee915420ef0086a70d86d79e153ed641561645674ed0a523ddd7597d1b4605ce85db198c5c5d7a919b7b44929630619bb2a948946adcc0aa2ed48fef18ec82e070a507302301ce23b2ee42ec6ac22424761d80ca3803c7c2ce37a8a6379f94938e48920edb5fb26d393b7066e0f9a45bc89ed0057e6ecd7af3acb99aa53956949cc693fe8164f000b022d811d7929e66750230de804a1f6ccd3dd49c40ca185d560959b9ee1498c9be089f785b9fac106bf3fcab3115b7ba8e5b4018334dd7da83ec44056d88c47165c5542d7dcb6017051b47879cccac1d9a335a720e66d341a54f41d7866860703c2941f3c4c9ae54952f5341ff1b348be2b99628cd304a5d4e9c478c79f3bf680885cb97a43eb351f89ca94e7751b841e21e2cf83228847b13f5686b783fe3e91f117584b1bd18864daadd45a2222081b631cc0445f3715f6d0ac0b9e5c7367b5ac1c7b651f0857276659493915ed1a40a48037c604fa3009010155856f70e26e1e23cde6becfae972e174ab2280e893ad1ed138b14d55133178286aa3c135ac83e813d90897f3323d5626931c74fa4f134a025622e1a4b43c7a6da80c6a76ed7bff42c088501306ff430285d6aa397e751f1d86e6d8face7b3c1c5ef693d1188384b60f0d11ec1a79182edaa89b0c3b021a5807665ef9a538a2ce6f5814e85054dbb7a8cce08c281bf3d045d7d5c0e7b4fd001ecc6cb09c87861371412637e5f5048a0fae388ecc28d7ef8489ce9ab01289eff668dd2c761bc8adb1043a96ed34a616ab0bc2e027266198ea426d4335222c8e517cf722c362568010c5e4a40bd9d80fd72ccd81728ef24692501e30939f0978c4309aac894ed6551a53693e47ad088ef3a0f866000b6a4bf8452053307697bb25a12338c133ae9b58540b18f7603acc635d1c4d5c0a6c0f30712ce24a544ce18aef353437aa8c7659c5e65ebd41bf607e51410af6da2b8e2101d9ec413eb9b66228b7a5ec662b5d2c275b55657bb6fd4b28ab7db58902d2c40cfcaf1fcd844b64427173ad04481e922fa4ce016b0d20eb1e4d2b084a73bf82f6a276264489451ba0bfe0d4eaa01dc52609b42338352e648699568b05109b82c11520f1496c583d74b23bfd418a16ff61bc6b36479d6c73890bfb19d528661007aa2ea5671143a9ba8a18e02e6383dcaf69dedc969fbe0f2e26384eb62fbe194a6e93a8ae2eb1dc5d1a8f2720e3592d3a6ccaea36c02525d57df247f18fa32f2782f036290a376997fbe3231b8987fd37fc9a05d3fd83548750c58ca20d94c18abe5d3f634329fc9f6ea9643c602a0931c0b25815e43ad71c3e4608e58d4e983808ef68a1c58b8e8148cabfd42424eac6a0d6c0a3fc0b9abd1f76dca5ddb5ba3c1a82d63eca12dbd03bc16a6e126f238409e7ed48c5575fe40b39126b742512ed52be5eaddad635d1b0b683b18f3324ba73f890f62b5361c5473dcac322cc20c52f215ad68f423f7b312e7d0301d57e4c885d0ee01d18919b40e13bc8b44df136294b4ad3e9998dc124a530dca78cb49485296910e48759adaa27cbc2c9a930c7e08fe0ddca1911ebe1a812219707c10f6ef1c693d0eb52b1aba71a3363b066e784ada141ac099c64a9fc2908bd0d51a56fbc36ca5cd9f568e40adc400f3372697c250a2dc993d4d24dec292d50cf22da2732e6b83e913f5f3fde07b0c98b9d5f5884ef619d2217f08660d1fdc920cd1e41fe4189fca4c1376f4a5f61a19d8e3aeb26152cf97485175d0f8d56898c501899d897b0f1d9a9c9c30e524000afb2cf0630f8c9190bd7563b72eeef77d07f2bc3f98f634d48202c4699c5cd4c663432dd9b48261e3329a9b2d150590d0a2442f3d10c995a3d79bd8546ae942c8256737a3f7d6bc51fff626ee1180fd7501d40e4e9ea06915d6d58d7f3d11202f18e5a9960b421a2605104a3c6d5003762431966da0bc46721df2dfb358b182283d62a3b76f5e08311c94b4e2b7b622889f4b8cacfcb33985a3430e51c583816f153bc94dde87a73ed57fc0489c13a179a2a0915853738aaabc708a063b1c84d2271c1df4b3c44687a46cfc865a58e583b877eed865487407ced6ea9290080b12380c0ed9b5aa559e147a20434098c87367db1b182942d3814c4d6cccf1da29793e000ce23308df5cd0af0c19c006cab661eba74ea553a0061358952463ae77e434681fb138a61148e00fa249960d83e72fcb38a761f2443850321e13a339ec4f21984584729f37282d459e649e0b04b362a20411f740587ab5a8422a28ff3187cc46e7556a85522769517b9f794fc8fdfe9abd313ff83ef0877a71dc9357c3b6ee8a58c453dffc61efdf13ddcd26fc7e0558875a9a394830810e2d45c378baf4c3a931660fd837a46a448191d9efdb6f3f2399c9315016fb649ed319b967e3654b84331e2b38a4cd6492e446f230cb6137157a380f05a1bbe0df9253c3b112870e53688738b8e018a0fa7ff74b408f8732be68866f54ca2d4217be3b85523dc4d44c65ff4a86f23a91f725082749a12b12492b2a08bdd6b02c3053a41e832c20084c57145dba53b984bae583133e41b566acef2dc66acedba98170a80ebb131e00d726a0bff1b8425c431bc2bb94da2aa85daf376c1836978144506016afc9769b5148f47040e3bdf58410855cd126619f84aa3e89043e8f5ae43000aec4e532b87ab7210f0e67b9c6f435d05e4e2cf2915dc90fb5782f396b0093bf071aa710a630e6dc10156e27a796fc4bfe664cb855c07637eb4ab0e90f0f621a2ad2bc9c1fd7db7f5cccc74c3375dd91db07d561c2efadb2ea6ac5112e5e07647ffca89622b8a3dbc33358d9f057dee321a006e40a28c203d81c39f66c1d3e9c60ef7db45b401cb833402f6eadab135d7d696d4ad83dae50bfe186e64f3f1745e2f3e7c798d468488caff898b6f4d38df831403296021055ae21fe96c37af565dc6259d111f522e433dd2ef0f8f348d6cc21eda1c87728e28622b43bf5ce941114dec11548f1025bb0b80c988c6a7c312a50805488b087d6b4d328c59fa53a9f278c24e9d40d83fa6625de706466304296b55dffc13087401a59fbd12c1bc1d288c123358e9cc53305ab17dee9614983a2fc8a74459f612778664220f42d51a0d25091c977072c36a7a1ba0199ba3369892826db406e6b36aee06040e4658884a3f8607433f6a2692ed3e71849e3dababf6bff382b73794d03fa3ffa58ebd9a09ea53cc707e334ef230ad53ca86f2fa01b1042080a920c9e206c6360df25b723459444768be75d1fc082dc544218a9b553e9c8659a5d5974b60766738f4e62734733a0063569ef4f2c323b65b7ab2b236a6a54246bfff1fe7018fb57284a2828dc080f182a2e38a1bd7f17883af86e982ec387e77464c9e6d406d1dca684f26de51b9461a031e82b3c72eb604f0af6dd0dcf48411d1940f2718509229793d0fbe8863db431dc0f510f1aa3ef6d1fc09626df74c0bf310f1c62dcd0052029c624ce4b9f3957e565f2f8989c13a493de10282e6408ba7d14073a94e9ec29ce59db25f651a4deb5a325c8b55bbb10e117077ba1150899d75cf4ccd2b8795ba2c27d8653732006be59e1033ea202242b2ce564d3eefe46788bf0a5436565351daa8961a0934cbc56e7d76844b0fbea480e68dee6a1e69efe5c93564b254b1260f73229e5fd1f73bdf85572042cb6f51184580387224fe803c92c35dbe862b70e7bbecb4a6b5c76a76c9352e152578f8eac0be47141cc38e2cd06fc64a1ca5958223a3afd0039c1edbea70635820ee61b9dc0a51a9475d7e55e856da2cf3f11d09fd0313e0b0bf079d4d330d9c97d14807ed62ad83fad235717f9470962f294aed5715838e902a1dcd7190f29b30ea08b571f9472ac2dc5e1e6ad02679082fc338ff4d27ae0c146d1ee39f06100e9bf831e3d6b30c95145541b9e9d9508a458fa171d0654d3389561869220cbeb6498e698ada6c13d476cfea39d1358540512e612dcb83aaf90aa15938954b7fb5f0fe7fec5785ed2e66d554a963aec289f254d5b816a39f934c1709a006705b55f20e16bdc2b2be8b0e1ba60420a1a08af6836885212cf7733fdb1b0295c5c08fd4d124b9902a36f61e52b3317eb2d13ee89030e4951738fb1b9daf8811fb855c1ba521912d73e5578dda1f7cd5d09a085dfbb7a4fa1aa71d795f939ce68839e54853ab2ec45cf9359eff42ddbbd1a15ec279e45c0ac5ed28c9ffbaf1e884da912f6726ee10e991b74a64801be5ab123ac2333676bf2c140de493d534966fd7105b5583a1ba54c6cf7a169962408d06d45df04523100481df62c7b949f4b4762bc46cab3795efd0313be62c9726a5a98a1179a1a8d559e15ed9c951ba0b188e2c0bea8dfd853ee73607649a034b1d2a8b0ad5d51abf1a81f94280cd9f00f622cb18200ac963cd5a2df4d80800fe3c5ad2c3df04307defc4f7b996638b68bb7f428a4d3abe00cb0d073719c423c81a356151ebeb45cf37b740d4824fc30e5ceaecb99000134aac20439a532a76a5dca79c88e10e1b20a813157de8f021850f2eae95628495197f298b75dfcedfd312824807decc462530017f675440652884c1045855c3bf092a15511b55a34ddd7d02988fa4635bc5727ce825dae13a2876322c08d607410bc82849536f03e05ea33b89e041add0e08cc741ddd7fcefc70ac6a4419fb2ac0b784a4f9c90de17f4b6174b554f29619b047c7fc30b6a3c3827d696e5335ec219bb2c1471b0f28b5c1a10fac67db34aea02dd3d9a58dfcd45c64a4b6147939c00f37aecd0bdbc8ecebe331699073bc5cab86c7e42a39adfeca7c4ae1c6251cf9339dc8a3b26c99a86e3d90143cd3dfa205e41d00d38edd56de7a72f305c619eaee8c654639b337d9ba2fc650da8bd79a6f7fc5e076f5045eb325f1ec6a8e72cee6bd9dc6911d8ae2ee9dd884a63b3748f2a4d088a9f3e93fea41a9aa6d8552f1c162911909eeba94fdf312a3839567b198343d8e1914a26ff23233a090bbb28d034ca5073786e3a74bdfbb09df7fe3c53116bdfa025d7500a900d6ed0b5d735fa26e0e732e55d85dacb78063f7e3a74aec156fd6681925b6142e184dcad81c5b328cc34134f1d2b60c79398f88012b85fcea2e592ad33bb5eb73fd78d7936ccc9c3d3171e5dc002e2067d61c5221c397727ab5455aca1f1a5589b52712756a9455b005130dfd6fe78d06c82d8c80f6e7baf1968b9b21e898ad285d8df812fe9bea2b4ea4e94099256ce148362ea9d73bc2ffa14ab3ef706a61db1a3c4bdafd652a32af3af75cc789a1277c50c3c2066fdb9b58da73c2da5bda9b7a9b4c76ee311d05a60b154d1c62a237276e6e57d4a9599e696efe123c9242b31cd7319c9c3edd26ddd874722846f3027f2ec6a75eda57dc9c7929ae8767a83ea6f3a60e9e574353d45c3c26bf8eddc05854d724c9458dd07320611f15915c1418370f7ef30f00e0256dad2b10f7b3ee37fcf25b5bc059c6bfb771fb6d594181dcf5a02e5b07350ead35ef8e32d33818d8b31e290e4495335269ca0cd8635dcb28ce4862595e5eca58d127fe4b98208b28a383b89251045ece41731aff8261c5b1c44240ae9e696a3953b2a70fc5b4c249287b47761d4f496900a1f562cc062047da891405b22d01120923df8a0cf6e9842d7a165373d94ae0040ea252d3d12bbadbdc4a6e3f9c04ce43b939e1e4bd99bf7dd969ef73c589861722f939ef84f554f47610f9bf5a65b148e0e7c2cedab6798a57e4b254eecef1fb9d44c0ba01807640b1b9fedac059d42eac30b80d64258bfb354ebe2e1ca7d08359744ada6a4eec4e06ecca2bf655f5c99acaec46e2a96b1a9f3e4a455fa849a8a32a40541b8c2ac5ba02e1589d37ef06d96c80387808604ad39905747ee6d40c17b7c722976ce9ef2501e588ad13d87381188ad465c5a4143cdae023dfcee13a575ade9a9c31611d1279ae1c4f78f8fe0c8bb6a22f6b4d350e630503fe16c4937bde0c556095d9446352f66ce53dff0962b6972e5418f1b25cf849860d0b77948075149e205bda9257807ab9ba0cde1320984c6a880dfc96c8071b1c7f2da8ec8ca047b147504a80a99b61c91ea7f4d06914d78d99ce373c4c2186424dd6cd6df079d2c208719d896ee787b807a5ab9d56737bc4d81c03cbe4aa35c5b32ae12f9e2c5614b15c2166120d713c33d93de3ec91093772bf8d08e4188a15c9328236b87199253b289d3c41d8cffa7c00641c92ed10f3101abb2f1e77469d91a66c06325388dbfd2f2c360673ebfd8c193596492ef43a1ba6170b071d388dd27335734b9d9902303021aa9bfa130cd418a5997c9cfdd6937aab1b1082303f459afef59f6451e0f25e87c34286819927d84c7cae19cb08f5e80ea20d8cb0de5314825323989daf1b6eca3b05acb07d3738bcd3fa144cfbd22941478ee78df607a3ae9f4ad9887e573dd0f53a606c4568bda02aa2c72a14e79ba775ed7b5118569c9897b30a9b764c5b90f4a0f57518d230f53b70d5c403c0a602df63c657b4fdd4f57e9dbc2a014eceba0959f50bc31d397f3c6c6786c2f412c1e7fa43cb96029c2ad2f040320bc955843500da0f3bbb5afec172677da5a33bff7f84e52998227b559158a08a67b506d263cfec956c253b7dd35169063b829d3543e7cef19cae2fdb21e50612eae0ceb97aa3850d31dd8a56026a6332274b5629913b72df09384fe6a4a928dde489cc83bff7e04f1add99b36d6e2e433978b9f4c812cf334d620d7b7dc0151c8fdd3eb2a05c205dfda81855432895012d30409216736b07d2b65023921d686accdf1a56f8ca8d26d5eb62a716cb8a9e2179dcfb31cd4eb75040ac4f115cd6b61cc94eb584606a5f28426ee44d086c3606aea10fab2c50534858b573dad364501065f4b620afaa2e29a3c0f36a9c7746511efc373f24db2037861e4f8ea1d8e7b4c016ad3b06a38c4c09d3520ae8e0f4cc865394153585a12927f5daa1b1679f65694ab847f237b7d6a7cad5b93c014bcc52b86a784f4eea03dc9aabfb4f44cc0b728f07a9533b77cbe9420793ecb299dcced0ff848af5596cc971cf117f2f2f50464a517a2f180c8cac0bccf1ccde67c92afcfad11177e96d2335be9e6a861990221b38ee469e8d264cab5097348184095253cbe6b5fe582eaa09c685929db3bf1366262832a68f9f1531bce3383000d9bd5f4a01406a3bce021ca61a628a0d39d83452d36cdef0843e3b2eaf28dd10a8d9f5f2475e932cbde780f581eeb74227022cfe8823ef4b2b8864a308b19d07e16e75b1c6025970a52d34ab02ca04b5efb575a277f6b83311aa0460d8b6631e64e1f02e790aefe6993ad3e68ac03ff9b7ed4b6bbf0c722a16e0ede6596874c793f58f10a96564d2f0d90a49aa7110d1195dd5d437b21a1d3331aed4621e25f418123a4718c11a59693e4eb5b550c90d71e72a67cf9607cba1a7c4cc0df13d9b012c860f9cfd072dae0f4a7933cfd6b5c021ade72c496dada4819a5051e8143fafc360876de800a65260393e3d398df9102280c5dbb2310b01ccf5801407d0822cd3e72991092450367546d3adc405c4ac1c3fde4c85229ae8eb41bd76b4d4b19f4b8c2172b03ba8f8825084ef642e280f703784b1c90054d32e6ec18832b6792cca8557c3c06d28e1405813874c765297c5de4c2489bd5b71b50157b4a05702127a927283a5b7b6f09a1662c092dde0730f44aead54811e30edc89c30ad48831060254e16aab08ad008d7adecb8010904f07e28c1da4eab1ed57436d97015df59add78ab654190f121b204d79817ecfe1007b5fd4ee94eee188c9016f930fd3c73984f1492e0da4b3dbb4e10ade98a0d420caca0419ee259fba55f6ef1071249f101ae2de60ecb8337c4afac1924e41ad62c9a15b5ae6f488c3922d5a4b2f911c399b336c8adb59b6e218b658901aee1512393e3adc4a954365c97dab20e5e08d15853bab36ad316d0f244cd9a1cb56435b118d990d5abe5a7e53203624c93738e2d744dbe08cac2db32d4dd2dc979bf043fcd11a27e5b8508e446337f67024fa71d5cf5423b7c246dd2796a99e876b8658027ffc79546489500040ba7ebf931cced46fb2c6ccd738e7b755ce0b4fa1fdf1512e552b9ad7d71d20417821638fa257061e4ec5f80c3268e9d719d87e6200dd1dec2a02be1484470a3560b25472ff000000000000000000c098452916d54e26b2b759d13f148a7f65666666caa6a733e3a75cb5d6dafa1769c35fa4f1bd01dd09270a790af582187be8347892320b8f62751c7888e1685884cb3c394cb40c889187567e42ce9b5df77d3731f0d04587985390eacd15d53bb431335e624fcee1f56587fe226808628c3af412ab934ae68896928b18830ecda6f0f04835e200dc418c39b42bf3de51c7c670f1e5d057f42c99722cdf15def220461cda141de56539d3089e85435f312162e76056c9d537f4a2213d4a1d3c6e6863e6d02df7a67468d486f64344ddd3b91cdd356ce87adac4628e1fe4e9b9862ee6c8760e52f21cd72c21861afa3895fc52b6381acf4f43bf495edf64e2565a1b030d4df9484e2db2c53843e3c187a7b21e1d87b8c43083a51ac4e2845a42885186def462bc8abe31537c18830cadf764700c5d2789cfe183cd71d44b0c76036384a12bbf0a4f0b9f27d4fc410c301c0c627ca16da91439cc6ad9ab295ee835275e4bfc11f5adbad0655c5f4971630a53792eb49d2b06d5bcdb99d9db4293ca528e27648e2ffcaf85bec36288f9cb3a96cc91857eae674456562b468a852eca46f2ef8bafd0c5530f737a07b22945add0871834b664c689d75415ba88a233f171c50ecd42853e78cd51740e3af630595368a3e7ddce1239e5698814ba68fdc8d55a8e429f3dfed8b272ecf14e1c0a8de7103cf8209e43b92ec6139adc9224a26a8e83099b184e6873182a39c73ead19d7184de873a0195eae24648a520c26f4a1bd271ae3c64c118cb184c673183e7dfd31675e6328a14d1159561153232b3192d0b7eba4ec78258e862606123aef8dcf61b13fca1dc638422b32792f07db1d3ff218466827c40a172b349fe64a8c2234137fd1d91e7f6ad8c42042ffb21a9dd38368b1e3889187730d13456384d0ba8494529e6409426f792b46b8f0bc794320102d9a5cd4ac961fb47e1a3e728f731821511fb41772e841780c1fc4553d68337686f89f7c91ddf3a07df5384c1527c5f1ca0e0e4a426f4a7d49076d940ef3a875071de6600e9aa462217cf8c1aac9386834b7ac4cc5ed066d7e309123634f63de6dd0b65bfb58d0a9064df250c643f6284a4a2e060d9ad0c9f3694771164d7bc871f276a7585a9145df1d1f6ee70f2a4e43b138751c89a44e4987453f9d359707a6497399afe83477bce0715e47a8c515ed598e8348953dc36ada8a5e4bd63a435886b7242bba54d5211dbfa748f756d1871a27c57cb9aaa2e928f4a7e6a04945afa61e2636c22793202a9ae8fde8329339452316e43745e72978788cdc6174ce518afee31039887e51a2722829faa4a15fe6294e3ebc51b43a29a554077b953d13459b2124f9384ffead9443d15765b2b2cc713c971d281a4f32497b53fa4473299e9d52bf9e6833a37e0ef1f289c7519de873d6cde123894d7393134d8af6299fc8a964cd4d343e55214584aa89762c858796cc29dc249968c2b463928d39fc520b137db4babceb76f47fca25da5c6a121a1fc44fe55aa2f51c476948a898a2e554a2ff89ea711cbcc31c6390127d5ccf1b637238717712bd5ed02f8d1da6c7a424da31d5cd5615e24ac623d158f858513c3d8ed45748b4a3eb715275a99c1df6883e12b51cf8e7ef887e634c21937fe5602635a255550b9ddd714d731023fa68993cf9aac30a162fa2492d0bbde82146c88822da9c2a6c5ef85422fa8ca3390cee23a2ddb0f815f5cf9d1d73882e7bca68216bae8eec19a2bddc30193965c7bc64213a7d8f723f2773ea0e21baec417a7c8e98c373ec20da8e73427994ca0e761344a722a22169c8c91e6502d1c7713ccdd7f1b25fe600449382c470cdd3d281997f68c5773a08ebea79c4f343ef41f7f27c103b5ec43ef499e4c3c398738710163e3479b2248de8a83df451d4b70e37f52be9470f5da5eaa4da41c6d5f8f2d0ef5fbc50d19156d2091efa6bf3c9891e6edcefeed0e57ffd1ca56eecd0e957f7f7abe428f7ac43734125e7cc1aab2a69e8d04647528f55f739b4efe17f8e28fd98c392439b3cce76c896c4a1f7d32d4f3117d9a30b873e48c13a53f2541a5dbea1efcc7111ad2fac45d90d9d0715cf3d48ce5c296943ab53295b773eb35ecb865efc4d43ca316c6bbe86f673b66fbae4c8e174a8a11f4d8fc27bfb8459240ded49d0ecd0ed912d5b3434159ac163f57586bed33ca77c42a4ead00c7dc81d1b53b8c8d99fcad0a788885f4c1573243b19fa0eeab19769ae1c31c7d0a687ea19d6346282470cada8f459c79b1663e5c2d0a49e5a94b5f2cc9a0543ef1343fe38517207f5bfd084c868963d6e88a5215e6835ddf737050f34e7b00b5d4cce93fb530ed58b71a1bd6c297f11a6b2256ea14f7539970b9131e74c0b5d0e9b4d4a43b2d00637b1489da11dfc040b4decc8ad1bfa7d7a2357e8b2e7f8272f1e5ade6385264f6b747fe4abd084cb3953787ba8971a2a341d87726dff720a5d880d39c5f786040148a1efd049349b66d1108a4217191e6139a484df120a8dee6aebcac4774bf8098d987af2174b9e52e79cd07c588eeb8af138ceb14dc0a3e3287648d930a1d7cf1f4e66b31c834f10c0123a6b5d0922d9732cd72aa1718d714304950f5de249e843d8eff81f2b68850a129ae89ca4fa41895210c011fae99823cb1e5b94548d11dad1ca56913a62bbe68bd0c5158d5711c9a0312142eb9323f9c15778ec39844e4366fcf48adcecb910da37110fb7dc0b427fd1b15298af88d213207479bdd79ba32c1347fa41d739c75e321d64f2c010800ffaea6bb5585afac1a23d6876df63b63d8a7fa83c68f73788a876a958e876d0b4a51c56b2ec74d0e6f77964eb901a729f83ce733c39c79b7134070f07ed45b076c9ac33fd3937e8e37873304d217c55678356fb3afc8d7f914d1e35e83fc47fc9a730a20068d049595c0d1ffc46882d620066d1c804e9b8adb37a283b222100b2e82d5c949f4c215b7a3400b158faa30811274a58f41b4dff5774c92ab4a78eabcc88b8a28fdfd01b9287ddaf592bda30a91c5a5f94e698033100ac684fa6438d7269b14a425c451fa9d32ce726ab10005534c95f2259a6e8a0234b2a9acb71bc8826e1525e0a156dd4b0cae3339b16255208c0299a14170f736c66d195b942004cd17c987c9c98c13b46be147dde9c2c2bc796144dea578db3e09d0a8a0a0118455fe91d86ce711cb91c288a3e49c86134ed485b4332430042d17b841432a9a48e9e5e1702008abe83144288e6191f661f87007ca2ed28256b8fc7e30ed6253704c0134a7e4d3147d18ef368e1c59144aa088c1a04a0139d7ffae8e9e6a674f80701e04413743ddced142a46c74df41b4136e5dd4b134d94efd48fc833d1c7e3d152f0f2c044179d6b1e26a656c7c197e8af3359afc865574f59a2dd10ca2d69254db293ab44e7951242b6750a7a3e257a1fcb29c7d0f0ddc19f44933985789ab2e6abe992e863b0a47b1e138976373b35f5e4df9921d1856c642e776dc9b9e311cdeee6c9ea85a4f670442f9d1da237bf46b834a2d708b96fa631673c19d1079eb23785f894d245b4f279213e33f24e7714d17ee8c12fc4fb3d4b4e449f25bf5e6ee850a45244f4f11657d11121c6257310e78a78d08a1c3ba30f4df68b67390c1b4298541866f0a1cb710cd251f3b7a4552fccd8c3f98360293cca04ae7a6835e7bc153b0ad1a9c43cf4e781071f07ff1cc7bfe1a1370fd73efda1cc435c045c942ffac60db42cccb8c341f0921cfb5fb8551366d8a1f7387b0ed13ca943939e93c3901dc72f2f86e185175f3861061dda18e3e68e33723b8c7368736cfd4f321e973117c30c39fc3146ddb2ace5db831971687ff344a586fc1b7905c30c3834217ec5ec751039f88c101ba937f431afa71472e0296e5d98e106ba5182196d6866f562a8a491cf4d2fe2c20c369c12ca2365b6d2b30a63c61a6c861adabdfe164f39ea1b37740a33d2d07b5c48ceef289377680361142e1870e346171e4618858b2bc20c34741ee718f1fb31840e5d33cc38439363f42b9f37cdf185cc3043671166743cc771bbc9769851865eb3cf7cd20889cd7f17ed861964683f85789824fa038d8fe1d0f1ec8ba199e86c5131f230b4a197d7623e2a09ba83a19998f7e7838b7fa16bf554a13ab0144b752f7459693d9f61ffa33dbad0c71e48f63eefa0437870a1e9f1a883567e8e727fb6d06676f4c6a4e296b5a385467390254569b2d04f4e492672ca0ec90c16fa2086ccfddd71852ebb721093fdc3d1b758a1d5e421e7aaa72c725c853ee8ffec38b03896f952a1bd6ccde695e7149af071d0f1799c526825dc924cfca814938c423f953aca68a60ef540a18d9952dea8b177c2e5094dcc1f4be2e778ecd509856c29e48fc2894d685ac6235135154b1164422b5da1fff4634b6824b968c81c87d5d79712fa9ca76139e860127ab7483196fb3a0c3b123ab7ece781788ed0cf67d5cb39c63c9f2523f4d9639ed79d4911fac914516a1e4984c6e3f01e5f760809d3cd184297df375f5b3c43089da6106bed993a0e6330b8702dbef0428b2b24cc0842e3df413b3bc975e8905b3003085dc66487e4b164a9c7ffa08fe829870c7125bac67dd05c4e996c58756f8bf7c04b99672cb2ea3c68fa677c76bc7352fed8419b2a5b8e2715bc25e5d041fb13e384aee8a22b1d39e8347f90373fce259f1d1c34fd96b3e6a07383b6e37c9ec34eb0281b1bf4a1a79fe5f03bf2b052832e325c84947366060dfa941c5e314eba575366d178ca61f8e4a1c9442bb2682e23468a3ba93f771c8b7e22a4bf07fbc1a2f3a83ae84a8486f17845177b73caedcaae5571459f1dc353868cc43db515be9c2695157dd8be2d12e2660e5256d155fa07a93f2455d174d8d1bcc38f730e26998a5e2c3579240d2a3a8f3226edf0e651ce53b43eaff147834c080d4dd1447a473e394ad1e5c927b12c86144d7f703a55194563f9323648454a7745d1c649ff6ff9ff48723bb6e89a62a0e83d6874dc9a235f64fb44b33e1b62b65f9e68a347e21f47a953481ed5894ef4a3e03a271e592a4ef412b2c3d0f165136df2f88fabd039a4a8263acbb048e1713f8e9399e8354f3ff4f74eae1861a2891591354e8304f52fd1e8e5588b5e2cd16f0e212aae5c89c6257948cbb12b7328d17a9cc38edd642c773b892ebce55cd52995e294447b326b7db95dceaf48b43d3972c9f7458d89882005c675053220d1758b65f7ebe441c4ff117d903a72579fd58ef1e188ab2453998c4f320c198de83a4ae1715a8ef453d419d1ab6e881ab543e7969845742985f8618254f294174574a5a982c410d97ff644f4c187f2316e8ff45816115dc7703d3391d1237888be3ac7430e9e27bf86e83cafc37e7f6aefea0ad1773c1b22e6d46d29436410a2f528796ca229b197fd20da890e564a551744e3bd593a632726ff23105da7a81e425cc8d01f8068ad3dca8c2972690e2b7f683397e51ccb23f4ef7e68d47526aabfd887b6529fe51023cb8746ccd2a3b4aa48bac93df431535a94570a29f5d34353513eb94f18d7955c1edacab0e1a1cdf241b3af62429a74875e73f35a92910cf34164d8a1ed8def8e95490d7021a30e8d8f6f0471bd8a093974e8ad245af258cfb821890219736834e37c8c8e869eb8caa1e2d0679c643987511ec57ec0a14b8df6c9c9315fdfe30d6d8ad529bfb347c9c38b1b5a9d9c9afe314aa78ed2864e6336569e9197f6980ded45e96b7994aea15379d7ebb0c24346530d6d9c5cea4ec94c43a31da468be4142e80a0dfd07ab1b3de4e8cce4ced04791cc6216b3199a9016e121765ab092c828439121f3fee41d5a121d8f19c818431f625e7a9fc50b1eae32c45032c24037880c303421e65863487a4af13832be5032bcd087eef3179bf13c74bf0b4de7a66cf940e63b7f5c6835c61c27f550775d43646c816e14195ae8cc23e6d46186b24ead8c2cb4eb17c42da7182cf4f1fe57b58e445ba45ca14da1fb91422fc673ac153a2d0d0def5fd9a7c42af41b1231739531b74428d082f2c517054d20830a66971c446aeb778e549cc21eadad99db42c9a020430a4dbce61817e3c88a7e2b230ab98407a571e115833190010594f10494e104194da81b2983094fc6b0fa71b6503a12c858421d4186124e46120e043290d087a88cd8eff9fdbbb907c838423bc934e2e48b23167f23b49763988ad06eeabc0c39ee307bfa44683d332ac4cac1e4caf121b41616979f12cf73182134f11fa45267b02c6d10da32ed2855360b96ca03084dd8e83d8fafd1d53a7ed07faecc922bf5efc7fba013cfc1f8c6281db8e7f4a0ad9c53f848b3234bd2e1411fc7d8d8f8e0f7ad6307adafaff687954921a7e8a0dcdcc932725772d0a86c5f7433fff0641cf493e3b9abaa7b83fe32668d19bbb5413319c2c37cb25973af41fb7bbd92643c6b4655060dfa903b05d5f8c859f42987d68ecd4dafe394459f62a5a8933bca39f2188bcefdddb3ce0716fd680793a963f53daf683bf0cffec19275ee5cd1677f13f3381b3db0b0154de8143175fc1ab276b0a2398bddcc27dd2a3a778dc1f35b4c15fd5aa890b9d69959a954b4eb9bbf637c9fc9a1a8e82aae7a2e45bbbd3c45bf2946758f3f65f19c29fad04f26a77a90c524548a76d5547fe3a4e8a7430c495f243146d17b5a9209ba9d2d440c51b431c7a3a3c396e0211a8a46c3fefa6b8e234ecc83a24dd143756c8ce521c6275afdf8c3dfbcfe20fac23a00fc208627fadef2c02cf632ff97dbb58a189de84f64437ca0af99e3a07a430c4ef459f73c761c8452cff19be8e3e082bf54b8943a734d74391253cb71898efec844df163b1ea19bd5c483893e8ef2f8e87c984bb41a737c8b19df6f4bb144936463428ece980b964af4be29c307997ce3c62c25f6d8f3c952cee3245a0f524988a79fcc7494449b34a6725c11cff21e89364a7e04f3d84c360e89aef72fcc8b6849c4f78846526aa61cbc8ee8256b79107b82c64c5e23daf6edd8584ded396c8c6872ca43abfc210688b1883e85b214223624e6a42ba20b395fd45a3c89e8f24516f9b024c4f82a221aff98af3fb6fcc8591ea2d5bd3896f17286e8f384c758fac196c75b212e06213a998fc1ab34c53b44901440000598e0788033c620baac9732c5d2cf88e1a62186201af50f2696048bce71e8186204a20f8ff8f863ef934a2e7f2106209ad6d8159e939e77c85221c61f30861f9aac32d9620555f5d843810f6811468c3e9c4f4ee6f53db650f2e2203e589173b92fca09fc0b12a5977f13a8a1865efbd7caf243979e8b6282111c3fd2d078ca139a3d99c71d15811a68683b30cb9c60153e6e8f2ebc38010bdc2450e30c35ccd068785895dce1f7a60b2dba28078c32b4a12b690e5312df13030ba082400d32e819f36e669da5d19dd515af9e2fceabe6503046a00204e38b6281223f8ad4184397fb03abfc963bfcf3c5d007267fb9a9ba628e1f863e4baf426c6684101f60687308e2f2513faaf2385f6825859f30fbda21c5c60b2797b2484ba6e942132e1b7285c7f6ee0b17da9c32840b6a162165d9421f478e5224e9cf49bda28556c3e73bf3255c42ce4293a7473aac3c55be2d16fad04218ff7c5eb2da5ea10be51a46e5d3275b6b85fe744e3abec3d0b4b60acd584c31c518374ad228159ad2d099197a9ac333a7d0658fa42947ec1c3b4429f45f39c68a5f6114da8931e49437cf8f162874792924cfcbb2d943ed096dc8f6788edd5298c6099d473119f32dd5843ef43d779789daf9312634be95a274d4a14a924b68bb653cd43c309f6029c1103989a65c3d09fdb507e12a2712da0f36e53c7d92c2647484b6c5b25d3b9c84fc92cd410d23744922e47ce27b70e914a17789c9c3e3738aad0c11fa9441d22207d1f11364e0c60d0deca0c6109a0929c594952457831a42683a3b48de112faf466829f0012d625023089da7f00f5df56b410d20f46122ef8194eebc783f685f5b333eb4466cf6f041ff192fe7948e10535c418d1ef4f1f479c7137e1ef49fdedab19122849e6aeca0f3340fdaf5e34e67a483aeba3ca8a049d5e4c41c34d149f24bd80c393cc7821a386852e7a5206ef23d95bdc111e3ee88c754010448c0020e3fe50419688147a0860dfa20cc780e9723a8f6cc356ad0ba498e1934436adccb177de346f9c20d8c1a34e8c2e310decb2db3039d98c62c9af2ca19fb1f6356e6b268f6c53d6477c71c6e8e58b4213ea37bfc0e58b4b9f9f3a1fa85d51caf68b5a31cc5aace714523b2d15b3f9690e3e069459316e3e6a798561e3cac6826757e157d9e305d31e5475976a38ac6aa453c5a880e2e2da968aa47c2e6cbf1c3ed0a2adaedd824cce78f4c854fd1860f63724a12b3076f4dd18985ee680f9b430c6d29ba1cc71f2a29c324742829883a164e3eccea28fa7f519dcf4f09f993a26843a7c68e8309a13a9ca168368711a2cca7e30a2128da303ec963c8dd279aade8ff9ddb424be3894e2cd7e72706ad90ad136deef8f2ae7ac6893625749cea5f923f8e36d1e4204c87f84f13ad9fe60bee67a2954acfb9a838ad6c31710595c8f2b3b1145ca2b7a4621dfa4e08d161892e071e4e14cdbc9aaca312bd86e9f30fb246893647cdd424fa38ee08dd894a12ed44eab7f66a7ff39048d08d11d080443b91a3c85ee99242e71fd19407a6a596f52d65de117d2aebf6b22893253b1ad1c648d8e9d8b7ba4396117d8a37bda0315c44af2139f977877f9d15d177ee7f4b114274b8881a1a89e8ff4c2ec4dc715ce7454413cce398a6a6102ea964018d43f4a1986cab8caa6bf486e8e4dafb236699df8f42f421fce414fb37669f0f219a1cb9557bc97754298368f2e6c97d7219ce1444ffaf1b52c86169cc9803d158a7541ee976e50f5a40742121cc46ec77540dfea1b5dc0f3dc2bca7e7201a7e68dacae3f5cb281d5a376e2c82610c8d3ef429e553b7b0ed81ba071fda091f07412a73769196c61e1a9978952d0e65000d3d741d8f050f1515d4b52264001a79e8b522caeba7e0b1b58787e67c46f63b26fc011a7768a7bae3fff88d39ced10ed0b04393d719e2293a75e8a3b2e88fff91275591061d3af52899c3c5565ade68cca1f3d8189770fed5e9cba16bf97e0f836ae59039e2d04408173e6facf8970e1cda6c41b6629759ae4ebe219dcc95c3298bdcd074cac1ff840f3c1e0f6e43a72166f6b5eed839291b0841cbd4a2055f8441b44a630d5d868788ee2137e5300d3534c9652a5f87930e714f43ef137b2292e4d05c42435f117b23ffe6a4e61d6738dcd01e242bc7710e2d19d030c3b98046199206199acd29562d3f8a31d21843de383174c94fce530a1fda058d30180d30349122593d44ad60256da144e30b74430b34bc70d8853a1a5ca01b84c616e806b12dd0d0421f1df1510afbaf1d9b2d948ab040230b4d7a68493b7a626877b1d0c667ce39f57bbe620838685c019d40c30a75a30a4dfc38ae55b1df42ef0734a88021a03185be3ac6ec08b99431e66948a14d615c35557cf120f734a2d07b10d92c159d59244f030a6dd4cb61f478e387ec3b8d27b49d51c67bf3625c8fd37042dbc96349686fa6ac864613ec98ff9782e798d0c4f9e7f8348f9a6b4b68a6673d8c090d215baa8426667f97ac978b9999843e458e34de527e8f1e4403099d4faad8d2f107f1d1cc6840e3086d05fd8ec377cb8145d7084d08dd9e6b957b524c119a0d9f3b8e31a5888a4884fef2438e39325856cbaaa805be25041a43e873102d4e5f66757fec0a1a426842d2b77896b5bda573e3c68d1b1e20800208300102282003ee0c389cd00842fb41ca43f01cbe3bf26c01460a4ec061a4e00461a4a00209a00184b635e438495c6e658a4881c60f9a8f29c44cd1e17c9c7f1f34a2571e49cf9224cff6a0ff8b11247a4a79d067c9af4a21ffad8370075d0acbe1798e2fc2c3940e7acd518b478b912d74ca41d731b5f55fe45a2a84060eda14352cb956f4c8f0d2b841e351ffa34792df21ee0e58fa040d1b3459da71302d611ea2a9011a356892fe5e794ecbf68f5e80060d9a941a3ffa879ff3c5970b2e52f03a8b2e7e3e6363ae198119b2e883c891293e3d0950ed19b1682f564795f348d61d19167dd28e63557f6fd88d5fd185a9eb55e5e50ac95dd17a6acb9edc3d948c885ad17a60aa122561062b3a7de98b983f5632935b281d9db18a76cc72c2ef4ce854615574b2d1fdf5ca772d663ecc48459bb434eebe25f190e344cc4045937b77fa2f3444cc3845dff162f99363c71d047d045f84818798618ac6524469cd9b139255cca10518250531a3147d301dc7612137637ffc164aa430d25c34427ec628da68221ffb0731572c8a9c118a3e0ab1ea71f810a6bbc20c50a479a26e5f5552553fd176d6949234745b28b5a0707170b1b488199ee8cabfd3bdc30cf903b130a313bd690e9d67a3da422939d19eaae5382f2762f8bb8592a115666cc20f33a3a37794acfe164a25f0c28b2db4702e8e1168d18517a5802dc2184107c0e0820b041040010420800208a000022880004748139dbae70c7ba1abc4832887199968afb5a5dc727a7f4cb5610626fa7ecf1c071ff9047ce15d7471032fd1c6d43c91fa934fe9821164e0bdf0e20b0b5c1d6658a269990d31e61cb3c39057a2b538af493593b9871e4af421a4320b112939263c89d642c5f194301d2e2b92e8b237a3454bffec919a1189d643e327362cef56740b250f6304c4856135cc80449b9dc2c6e6eb7844ef51a8ec6fee1dd17f18e6d1882668888799b3c71021c688a6a373f0eb6023954716d1c68c59a2e440bdc3941294304e0b3314d16589a91d1ad3434f2a020450000128115bd6301d6bb01833f888609276f8d055e63c590e718620739cb73f539e767e0bf149ce7c9023df0b1282a491da7272fa073d0d828eff726e4cc870ff4f504ca085736152e0c54108083304d187d152fe7cdd577176422728134809cc08442bc1e37f352b4290f89d6146dd1e3d5acaf2650b257368014621040602c630e30f6d08cb3deb32c99e3b7e68a2b26f44b99c1a169a1c9b396f44ec0abd98c7f7c83744685b5668826bbcea38b4aad0a9c5f1c8e3826e7aa8d0bfe4694b88ebcb0b4da1b78ec2791cbc95ec8748a18ddd54394f3b6accf92874dae26e312dd53be750683b65b694e3e811553b4f687573b062b2c15dc3754297e38f0a8bb16d42d36dd9b147e9eea833a1b98ed1c1a866d518ea12fa909a4a93c42befa8534293338fc714e28894e749e834723c31b6e4a47d6124f41d1d2fe768479afe1ea1f1c93e11275323b425171d97e61c1fc414a193b8e6679eba23b349845ed322e64e1e1dbee68a308021f4e6e1a679584ade5008fd6c0ad13765e8cfa90c42571acef3e57720133b20b4296b72f99ec254693f3069d0f0953b27b02f0cc007ede489e163d1b37c234d18400f9a1052ecf81dc60e5f291e000fda18346a65fef5e8d0d9428b52c02418c00eba18434acf588c3d213c001db4e741bb468b99170c20077d4c595ce27320f182555b18000e7ad79ca0d67963dca8aa30801bf491a2576ceff44d56b141af1172e7be0e461c400dfa09ff1e93cf6e3c1311074083264a7c0921675609db816863164de544b05061e23fcc21da90457f12ddf3e57958b06a23167dec481d2489b12a2f9a50b0018b3e94cba9c3a4d812a3e7041baf68e723f7dc73b2055cd148b47f472c897ee17c80ab600461141138054c30821b378e60a3156883158ca485efccc06063154dce4bfcff38ee8ce0045e68c05380821174b1812dc21841076edc104117230823055e7c51041baae8e3383ae738683f155d96599094b763d2142afa38187d9f10bd9c277e8a2e65667f10f130459f5525c699eb94a2e9dce2e3612115a2428a3649e78bc1ddf2b7c6a3683cdc4a51b34374922b8aa6b4827688e7610a910d45bb9de5fb4242dd520a8a26fe856ac9cb5b74f313cd74f841cca41a235e7aa2d98f1e5a19c74e74ad39c849638c73cd2227dadf2451524ae126da78dfef12cb983b779a68f522767ae8b31db59689d63bf2b81e251eff334c74d6293acad10309d3d4259a4b499625fa09d1de91e53c39eaa884b9325928d174f0dc1d89e47e1cd3245a8f1d5e8a312b8926e71cc7f1b198c60e4291e8334543ab88878521d15cb4081ae3c589b12244b0f188b6e369276be9482f446d38a2abf89573c578f7198f58c14623d4cb39a7fdf14c7fe92af8c2c120840d46ec19565a6591e53a44d898c30601376e88c2c622fa8eb859529ccb1d87dd0b30baf0e2106531053ea005046c28a2f51c4398ebd84944efa1738e83558688262fa2e3dd9c3944f3fd1f22e78b1ba20ba5f17298e8d739240bd1872192a7a82621cb619c1410a2b9f6301e1ee7e830a70ca28dfc23a125b958c5d4164a652910447f7933254ecc03d14fc741638907207acd393fbe183b3b24c71fba30f3d54f96236b0f7760c30f7d30b1279faa9f730af6a18f289b7bc9fae25c700c6cf0a18faeebc1a6a6ac413b7b68e3c646ca9763f4d0c5b874cfdcc179e82f229787294a3c749a211ecb897a20f91d9a6dbdfeaca8c1c3425c1833822eb4601ed8b043f39f324fc9a514255587b6cfe42273d82f1142ee0636e8d049acf89243c62b8f337330e89669ec0e3639b4195112524812b61187b632cf2be2c74a193c36e0d087bcd27192b13c7ebda10f643f47ae3bdcd06b6c86cf19391b6d6872e821d61125bbc462830d8d86caec17279860d858439b1f444df9e38f39c7b91afa24398a791cadb93a471afa8cccb9da63d0d0c60e33758aa1738656dcc7dcad7398a1d1581d5899c694a19fce143e272f67a60c199a9e68a1a7b2bd742e6368a2cb3fbac5560cfd5828d112ff304454c3d07e64477a06eb783a0986d662d04c957c3335c62f3431c152afa744476ebdd09fba68ab791c644a6217faef4034efc4ff6f0717fa702d5a12d7b8d0d66da1cd1ed772e071a8122dd342b31255e2b92c98c3a66acca68585434831a5282dcac0c6157e4d8bfe1f6e04d0400318d0028fc05aa18d0d99fdcdf94b82858b13d8a84293d69a51e6f1c24308151ac9bbe1e1ae467b944ea1b5a0dda1c712417f379e010d98c08b30ba285b5860a5d0be68a42025b9cf636e0bd515d88842f367e95a216a1e89996a8197c04e502650031b50382af1cb71c6369eb049969764b142947eb0c08613fe1c448961739837a10fbb3da77a0a086c30a1c9d11d699e9cf494b38436c6d520216b6bbbfb4ae83cb80ce971ec93d0c9e76c312ff32986843e54797eebbc1fa18fbb313742df1e934955c97af48f2234e24157bafabcc7e42042bb6761a3277786d045b6f81a3c2e478f1d21b4ed2164c77f9ee151138426e6c3a5ce540142f361c620c992c87910fe41fb16933569b73ee8a3ece99e35986e47b207cd6453ef30bd189591075d4e68478fa53b1e863b685c453f5f3e1db4d171993ff6dc2e650e7a318faa7f44cb72200e08213f9ed376c9d6170e86d9b84163fa192cc420fe9a221b5c513a2348272813c8401dc0460d1ab1d6e8707a27935f3668d0cefc84c99e554fec67d1ac96a454fe57167d5289ee5839fe0aab89459f6b39086e9abf298eb0e827456387392d7a0edd5734b12affe3f638ba7ab8a2fd6b734bfd817c98a956f41f779a4fc867f008b1a2889a1e54f64ff85b28690146d922e97007438b1b37fca8b18a3eae0a29898520b93aa1c033c05b43155d2c379dd98e825a23157d90835859244c9ca7a408ba702e2e6ba0a2cb98b24f2486ba5881175d94a08b149c82a82954580a39f64ce0451860a4e0a429da4e96a59d329c248b6fa1a485f95158a314a418c51729c3f31aa2e0a246285a0b721e4f6e07148d74567598e8f844134392fdf8c3d00dfda859a3af54eeece944db1dc79e5f633cf8a8e14497ba3ac4c5cf6ca2eb945253b7fa59a88a26dab8333daaa69199e34c7421eea943f7707e3d8b893ec6b918ba83c79683f512fd8e7a5bf6605aa23fe928e4b2c71f5c2e2bd1e885dda8912b627b90125d7657d38ee1d232c726d14ee50fcdf42aaf7aacd839e58b45a2b9a81267e27adc6c8144bbbb9bcc27bc5787ea116dca4fe1710e436a38a28db0392523af9976846b3462f18fdc999ae4e71a8c68bc93896b47994afcb1023516614e51ae438fb34731a2085e525c4959fdcc5212d15f0ecd53dc337cb2ab1c5003116df8d8714ac97e785dd538c4416846cdb11aaf61887674bef285a5e02907d72844d3419e09ff94f1313406a841883e658c2e61fac72ac80da831884ee6db82c458312b530d4134d621fdb387986522dd42498ba51a81683afe797a9c0c9e2140e0d11f54ce4dcd164a57e30f4d8804358fe394e3a50ea41a7e682c8a6cff7c27f12054d5877e420a12c9543fc2f67c68b68276a4de9857c5db43a773fedea8a1873ecc7153f0285b2af01b8109bce8a2055c78610177066c81c1801a79e8f354458e1219fdc2c130c4431ba2756c5239ce13173f4139602c7de16058016adca18d9fd5729869fffd981d9a203193877029f88e640b3dba70ed1a75e8c3fda81c8f449c1cd485175c7871b408e338098c177438de8e526e929ca2050b46f045881da831873f8aec33d7261d995b288d404950430eedff4f6a4895b32976e3408d38b421646cf049feea1e8c14a801873ea64c16436f042df70d4dcae21fe78e391174e105177b811a6e384a3991de31ff3dbe461bda70396c7f962fef1c52b5811a6ce822c6c821c49c3574c93399e96b844889caa8c4eca54222c2501c1288c32151180c2e672e00b31408001830220b0442a14098e9d2fc1480035a3016322626221e1a0e16101812128802814028140a84c16030180c068601c2b0781418139d1f008016cd28b9ba03e8f700e1f6e235787b404fa0070c2b697693455044df06d0011000f01700e167d1dba91c5029b00300c6aff7ef49b4115d100d88c481d076e636c3d28a30915837c41bcb31497b1b25d020d48500258578aa9924b192ba8acb04035b2020128755edcc9989162165216c6842938552a532ae868b3a32df6b42eb028e7bcd535c7479aa446413c435989cd4750e00559df0d189a48f8373fa054997c1c5e1ac986b73a5ae466ccdb1f91c60f57b360cd122124458b6615c6207c601925f6aeaab24205c41ebe845df81ce9239797f725c5f52d7ebea242df6447f75a389bcc077548716191497a08798a577d64e463599c3e51aa58d7b73a94c96a37f1ef125887f89249e8ad4a80c5b258fb5f53a72c3cde74a4f7a53728641206a2b1caec3e3c131cbd98890d11fb84ecba63c8def426aeb11c6376b314e5422815506710b0640162ba8cbae79a8642c7fe58b1c882262b3d6798e4e06fe0b68eec530cec6ff56d0969ee678f03a59971d51e6200ce05708c86d46c0fb3e9b9ad6120809fd4f3531c0f95766eb4f8cdca0568610a3e940164e27b3f6c1e00ae20dc9c16a198ce2009e43df3c87876e2281d3ef07b0abe4fa6301b6edbf188c729a62f41f85da4411de97f763fdca27f6bbbed7477f7fa537cb31ecf897c33f2755a9fd237fe87bfbfb7eeeff2b0dda4a18e9fb0f7f84005600d6614eb691612280d4f6ffa2f825660030f903042efd8b81ee7eab78c054adcb3db0030c051a79c573c192d591e1eb7a69f00fd25ddfeee85b57947d1ba5356cee833ee5cf29758a39d5f0147fddbd6aff6c4cfc37d62c317ed6cb45e443cbe53908272e90b8089a4addd2fe8a945542f2fdf70bfdda84a79c372973f4b0013a93e65db59369ebfb47256b42c991501913b6391238b6091a41f287a4656d5dcacd2b47e203aba1d68cdc74beb0d011e60646fed0d14666209bb619ea1c716beee650eb70e377fa218a0a64455bad01d4b9333add4be13863a513741f821b57e319b80fb786deddffbb413d74df22f186801f38e00c4867cd20a8bb6e2ade70908a2620dab8a0ef40c56efcc77f94ed24db833610f0acc9c893f761486994fa8ab51d11e66f6d25c0e8ed9e8128b57c9f35644688d8792b78cb9d4ce733c087826ec4e2229e93de0a1d5c87e44767108b1b683b56c695ea22a6c55d2c1d4cf2744fab5a981c9c11ea63e154fb46f8778692256504763330443530c346f8cb01603aecb5cc18710c8c378c61b07d0c8e21638833a996c5c8138cb00b4f168b1b219f23ab91658430ed1685c5781415808a3ecc4aa5c4b38807bc882966274c3ca26a57d8bbfdc5dc35bb7baaedbf82749fba60ab50cc28b51b5b59ab684c82bdf757352b4a08533987232ae084c07639f8a7343d751605c81a10e1ebd6095c2ed420e58e87f4745f378ee899ef8cd23736fb8369f37da593a60aa911f86de16aeaed5ddcfa55c98a3b0cd6113dc2a534dfe16cb0f3e6729f2c86342e808c685ce4dc452322f0114ac88c919e5eb9a8744320f401b418999571551091630e7286fa325b1bbe058c2222fce5891de97c8883c6af07c4c8f86413ce8c36b19133dc12c62fdbca8a9b32ddad6d9b84d9f87d7a67c9bfccd7b9d81947c798e66d22a884e1fcc47ed9878e043d241abbb02c62c726f4dab5f640d836cd8d01f949350dec0d6016a9a51ac843242d414508915983279a7864f7975bc54ee3faac58e5a042e4845e2dc1841108fd47d3ca9e1171894dd27931d9f4fb196533f094b7bcf2d0cc1cc05c25334913063228a761688e3cea7939517d4863e696b8b11aca908b2c89bbf4c1eeb1ad3de75e7a35d755e4f35a49561bcf0eec913a9bc9d628440d7e39cba7b853e608cd7706eab1dacd286fd68c7564df35ff74a636bbe3bf2ea8c0caf0812d555dde3e429d9fe935213b01800d74718d7c74ccf1c202d69a6a58836d73fe5935a02f58da5977151ec01add148bc6c2971d316cab0551846d44b15a00464597af0ecacfc0f7455a1c2c5783770ea0c98377b74ce6a016e8ffb89c84d202e90f7dd9bc2ef902fd65f65a24ccf8cbea7fda345979d8ac05cb3f6f4c0c606d74297f283f1f0810f36fdff5ae0b84dfa49cea23f541a1cdcab314a0e31cb8ee803be5329e4ca06c654306198416db404e05ab2f1836bb89a023d29e7b355938fda75e2bac048f9a186eb8714c451eed3c9324dfd3287724c158f788ba6998d0ee4d5dd4d6f26479b579defc598e566c966a0328b1fe62944bd29d30bf4af81908f6db86759618a6e2a62e04c496688e95e758ec637a81854c5a2d55bc8a662117994183414366af7bcd872c9812b1c5861eae0d0d35ee5c9632a92f870238d3effb9ff278b781db6b8f3ea22d6cb06ca9e69cacce15183eddefc6b0615c48cb84802e1d1205f4d681a2f646bcbc350366da7a0e95517e2a484ea088a66fcc98a5ba83724e419b81478d701a61289c57e4b4de08e5790ac446b2b67cf327c2ad7515c01db49c0788b7e06074ddfa3a6940ce1d24a449b3e76b625245786f09cc6dee8dd512b9303d9c73d7a62dba394f1667d23a9439b57cdd3d6e44dbc763040c9fabfbc499c351be57ff569472b678128c137c7c2b67e4d966024062258ddb3a3f4c38553f5d8830c7ad2972194adfda361d3c956e74ab229a32ede763de78e4994c8437e7278ac21b5ca6b64a15066cff66e32b48b905221b4c22bc6957780322241a50dea96e56bac25968043c4a2fdaa9d4e9103f87a0b317ec4eec66078c23b72e9b2b9d0f7966d59d2e1b466d7199849f892d28d233f1207489fd8f54ad5f86595da72f7cb31cbe0ee634c62ddb34b437208f3349dc4e14254d7795d2f8a2140135505514888a3f7df30c931c3ae83feaa0b868332a430528080540b150144dd5d01312961977cd34aad64a3149f584e44ca04ee89a5455dd8c326c1baf7520ad155e029274a2f24419214fd25323176b6a4753f7d1b8c71d5032d0138d3b62890d31eb0078fff5bab8b3e6a92d2a175756b9c9de84ecefc55a52c396f301f529a98867396bd2806037ae3f25bf348221e12d98d74b1c8dab7c6427d2b2df6c839c58fd0b3121c8aeacfabb29b6e73ce8699251e2ce85bc123765b37af3e2389892141f4918e4816403e6f8601670b23c91979807558e70e959150d9de51026c98b09dd64d9e34ccfdebc63fc533e3a5b878808794e192d1e1bcc697485c58518a64c9bc807f4a6c5d58ea25bbe27e04d4ab2baf2a2f89ea5c32b4df3ac5da1c56b23f3645fd85d1ca3c1c68761ee4e7ac2a2c92165873a3036444f98e84acd80ff252e972566b3b6ceae7e6d85b7e719cb317b0c5be1c5371a3a2c34ae3d40f8010b8fc963d5980dd684b9271fc3df79ee114f8aad60f81f5b6d949665f02ca32eab71796f68a842c39633d36d9b65a23da8690b1452a8faecf41f3d5abec7a776e26aac1a2c09c65920fa04d33f9660b53d8c4dc662928a6b14ffcfa6de5ea3a6197296df32997b8caba60c3691a8e9052f439da879c97c61a550b2a88c44999f2d023e72c396214025f538ddcb1c37a1800ae017107ca1b99a0534e7016d059ae7f511a9b818e8ab827818ff91eb8d993f2542542c1cf8afc2e01940dd40599769714e0f4c02331d4c0608535e1275426498733d0be99fb0907bbdce831f0dec71792b10c64a0df3963c055ac866f66d0dcf8d92da2386d522761c2d48d61c1c365c14d7c296da73cd04cbb1e7df2ca7bf22a96a9f070d87a07162cc4c35a9b1c57448e94cb82603459872149ee2e958971ae3254d7a7ba02c2b2393f56cc5d81c60e78bc408ca3e8a28fe9d1359eaa615bb7b68e8933113adf03e5707f9d45c16854614eaafb2ec2e6a9224472000aaf3a359766aa4882f3a316a6625cb781b61581a6d8802eafafd4c100b20b133c8f4d1419d28b23817ea4af44af892af1c8db2b6d74875f4a865ba1bd235b177f1a111131bf9d371787c9f9f2d2df95002bde7285104d2c967eee0d35768a922ea562069205da0b1f7b3234f764a4e2709871c01afc98536bfb48ae8d7cb7fe26efb9970b05ea6bbde244f3ccdae1d49bd173f8022e6e7daf29812b257f191de43f9b9a30ecb46c61403f00a7db461d0eb8698aebcce5b4532b3fdeb86f78407f6216f13b5821a3ae39ef0a7dea62ee0e8ade915d578885e94d7b5a85db0453f2447453758a54cbc0123fef2c2988b5005d453564298173157787a61b765b69f2b68862f438b56a8771c751312805ffe78c9231144f3550d76ea05b403ff444529330cf803a01c9340a54362c95aa1f1361ba9a0f7e5ea2ac8da7c2ab999d0d8585c071973ebebfb581ca97f2699f7d29887dc040efbcefc79e8577489a0cdc0021af988b219902d0f91433338c5f78e22c98cb9fee4f5a2ea88551aa5926542e4d9b7eb54dc63eed03d3e8ab952896faa260656ad2057da27f258bb03450f9677df78ce6db07441bd589ad2f5d27905acd92a52cc285455ec0a43f9f1fb25eb1b8427ff47f13c299daef058052ae3286d5f0b20b010f8bcc7c0445b8e37429e4c7f729419a05ae4a528f2520a53287a65c59f993175f48d02a110a1489742e4756290a7f337389233f9eb7a0d68b8fdf8bef9001646314b1c0a5964e847dea8828255ae90772ea42478e0900c3fe8695f14d2aa179ce446bd12223d8c8bac52eaaf309401daf36f1808dfaa63b8776a11957feba2eabd77b97472ab33676f1a25db138377922960a68b4458f9fea037ec21cde05cc7a4c3a52f1ee77f69428a9347d37687b2003ec2e5de5e6f361bf17c498eeb81c854af2fb7b4815d011507435cddf381be611427aa7eadab8e082bc565195e1d0da8a96d3cdd13dea2ace375a8b4c101b68e6c17ae84f32490ce62be8a9f3a8d92e044b463933a265f7645ab9acf81f71935ea6979039dc9a4c9947e3dafc0cdcfcfed5778aa49a3926951b075e0714be9b6520ae9e53d0b087b2e1292718838ef38beb88ca44d1349b7ffa8c1ca26172ba4899e0e803c364d120058379aac256901aac1520ebdae5dde612846c8d03b80f6d71f49505f9ce9805bdda9bd70f7e21184809f6c69ebcfa3c022a2922828b9c30ef14961b2be1c1076c3ceca9962c55ea0978a3864f9d8cd6b7f5e41d64f926ec0ace18a694d45d8ded4740093c146d0b3d2ee56101500ff06a19dffcfa992910ab7ee64607f251024b9cfa86ac672ea93386cfa00d007a2f83a1188fc89a4a4636fbabf586008dd9931029fc0dde683ecfc0dc5e40812f392200055252bec5d8d7a08d6ca436cf8c024b9d5dd8440faa7179beb29f5964d45903f42dd74b293e6363b5c648004c2a10389403ae8d8083f3940b91f074ef5ec4345a59f44e0fb8b6f9413413f586628b4b7af858c7a009f0adadaa85f921676a128a62b7100db29e96d12302291590786f42617467b4ac23baa33fa2415e5b549fb57b7e4848c1b6e9fa0e3b59034062cf2e5408e41ae8ae8ab372e6d8fd795efd30e426df536355ddd11ac1861bcb274d79cd21e73118493fa6705a2b35d16304b93122a00806296207b7db01e9ebc86af25521b521d455e960e15995d72a58aded4fb9005586af03161655d8e28281f1363dafdd3787d85478e4a41b5c35c19a41014c283dc94001354855f8ebf1ccaee0f9c51dda07842ea5d910be32b9eeaade9f3eaa30b6cabffa78ee7ae4b6b0a35e2c809744516e5c2cb30046281b4bd80063834eee6574e37db3d45664198404dabcfff937b0e4e4b6fbcbf052c28fcdc5a8459578012f3a2612bc0d13d8bcf9473385eac05c8a166602530f2ad0a81bb193e5494616aeda09414119d94e15d1903556fb6c332cbcfe46722ce1bb1c5016b5cf48cfad30efd8ed0574e10cb68c013dde5b3998e473886960f93ac91a19e378cb0bc06082d5a49781630b8f5cf353b76faf5ce03e2b2024b6d0ee8b7ac2df02bbf69f83f11961d46f873de0b72f90aae6192e8965229086b038af862ec39fcfa5b2997fa4f7161b4f7c85ae52c71c3b74836147b1424514f92ebec9edcb0850e2d4a4b479fc44485e517db374f0240bb2d24dbf246f3b030ed1dd0e2a4c570e043e14649ef7fac44b8ffe0b7f266b2ddbfe6039aa8a7deb5b17ca282b74c714a0af1798e7f4c957e09d94d153ce8b3619b073c80271016024faff5a6eedcef40f2b57629d4946e1bdccdd04c8924dc3752166adb3ee444b9a084b9d784a4a3c05dda1ab0a58b0c8a85d4ea5444dc9fe3e86053d772ff0bf61860175918b5f57783663b835b9264a750108030ea4ee6cab87ce00108832bf9713a26a37c0b1030e9fbde19f7360e5fcbc9cc53f32c5ed373f650f95be8e3657d665e917e131225ed8d3750e9951ee67d07cd09a15650bd4b9f749c60e4b5fe66090f8609860b0db0ed90f04aff5b286000395d3c2bf25b78467a98e96b8be102cec89f9420537a87d60a8c969fef9f9e646d938c7f35ba1cc2e21ddb13d93801db2623d48a010ff2b50e1fbcb3f0811611f92c22a197dcb3efabbc5e6edecfbc4de9f7e82b4605c24b9903b9a1b1aca82b07a5a760a71277fb7c9a9c5b386dde61a6ff1f80539bf0940785af52d6017a3699162a9a5b06d672ff476e429df6e772b80cbeb91f082c34a550f11277003010ec9a45c6452bfdafc7e84a9237fdef3880e5dddcede1f91aca362137d3dfed7a8b2501681e3151c2c21297dd234b6a65ae54b14b99d7f96b8735768d14436011d0a63f6cd95c2568cab0ced0662a64d23c2b1b541b0b4c9fcea7f14af02f46afbd9ef28aacd318e97b78b9093c10fa2c80f5d8f68b0f20febf6d1fd1362bfffa8811012e8115ee24ed14290603c24ff0d0f41fea1045dfa4acdb13d00e6f82db21cf20f2130fbcd01ab651d430cb48100851014d0c6fc53178416f6b40853008f94502c582824151d3191c8c3129a822836187b0f0f28a088d61e12fad1827347c315a960cfc6aa4d5cffe3e335982935a2abe2068fc2831989a9d761dc39d6a9653000156bf4f91fa53103a1d9f24186802f60107f002a45ad00e988f5901907202620a6426b38367c1c601788a157f8d005cf5fb15512ca966dce40552f3aaaaa4fac4557f5f3a9b330162fdf59531f48def6be138d54076bcd0da17714d50c7e96479288396c01474e5b17ec51d2da82ecd8b14086d870ad254ba84f9c27ec402ea36c37368d47ccd76fcfbbcacb3a75febbda1d9559713334965364a32d4488821f6d4fbf018fc2ba19f988523097714b3c99fdf72839f1316a1d08f1d6f0fb5abda39b7e7f2a04b42a27b098965a72ca3d5ceec71684575b51a053709dd2288804adc68c972a0512ba38b1e7d5585bb4fcf1072d0a980449e9e0da37cc036a3a65eb9699aa7203d5655356da0ae5f3291d3eb0ed4082fbf5f2bc04995534c9ccc4a82dc7d654a18bcb12b2f7d87ab1c3f40c725f8585b0f425ff8cf9e9a7d200382cb66c6a1374cd9f40c48d312d2ebebba8195833a60d091a96c3943df9b6da8deb7d540262742d20c51c9aaf2f2b0ba3733deecb66a0c41af04a8b816e8db89b1d75aaddc6c420960c13143ecc54f7b27c9c58660dc1c4dfdaf35daf0cceca183a06dcbbb8d6c4c1ac52825d5ab5951eb9a3ba922b9d9237e2bdfe62677ddcc783986b9727f9b49f44ca960b9a134c73736e676b5498a0bf5a1c29b74bb034199258dd8554382b6b8d89e343a58ee4bf96219c99fd6dc3eb4587bcc4b06600ce1bb5863b6c37009c5bbf43e804a987692266b2e2fc02de14b66498c1b0cca6b2f16d2545b1888e8b9b38babf8bdad3e8699029ce59131e4612d8ca45b04402d1896a2897cfba9fd32b39fd0ad80a29dfa4de946455f02b636127de78f3f62553144fa238152c9615bd7cff23011289082901a595ec80c6cc070f507ac55c5db97829e93529499020b6baa85575ce86b9c1ad5a64af035b5f01797686b70b606bd1bf3aa92d1dcd8cda80c22a54905eba93948834a28c154c35cc388458eefa2e65135096d9867b3e07eb469ad1a33d2c353478cb2a95e954255af53b9bd71efb9017d3eff9e651b732b02b7c30c4f1f10f6542e6657a50d4f7f25fdd46c7f563a420327df3275341d5abb44f61303c9674e1f6b7bb323a4c71a9983004b975de5a5b7574b73690a1176cc9b4d706a004ed474df126642e633576ec8157a12e9da5832b812d831919428b8ecc14ecfc6a90958d0d3ec5ec33f536cfe825a8262f0916ab813f089fc47a0b9dd0dd75fd2b9166a287347a1c9ac406fd43357a69ef42167dce0cb01d961c5a4b2abcfcd77277b2d5384289c8639536ed15196662f00c4656a515fb45eb24289baa5135d06039a740a0a3505dabdd05209e6681f33e656ba676bceeeeef72f210bd388497ac3a42a4ab1280887494ea9d2d247aa7c14629bf3ef43bd23e92ceab3f9566910a91210c18166a890d8c37e4374af03deb88f96025f641215c13077d8e06809337dc74ab9468a306cd81ba22dc786bc4e22e9cc886784872872e1317e65b0d1fffc86bb2b87991241bc7704547c4eec2fd0805a2130e490b1714e035f8d5db48a3722049bfd0a8e7b00b1a030e514d472312f8b1567bad57230d07e9b41a75a45bad01210d567a81f4ddc8724bda808882e8722bfdee7894e7769e825bc0da6401234981f7f05a64e518c8a8b30f08c6131fb41647fd6a06c50a1505b2818547728438d8a03c37f1813a128285f285a282328d88bd2f125e554b440d9a0863eb54fcc4ffc4ff84f16a76c14f493da13c693f5c4e329bb8948da42d33459f4a83ee48239364151158ab680748404a116a0fca0264565b4948ea8958bd341e5895ad3a7fa746478c007d50aa2fada0436063ed45d75026a4831288692415509541c79fad2bb07b5878a84aa41e140e50414d0dfc8a7d01f507d901b94670d75f5443577a69ebb64374524f547e49303d51205c5863200a540c141714031350ae87c1839ace0a87b23e167204099a100a160559408b2b87a8f1d6a0e15020a03650785818a1aa5ea29568a0d0a1e282035086599e7ee5784a1cc50e250b9a7b8b50d4201df53624022103591f3647c21a9ba69dcda04ead4282f29050a23a1fa6e5091814af34f21bf4508b229d4560445bbe64c9db750de501b281650c675940e43bdab1a543cd4cb14c5895ccf6602a0d8f6e0d69c7b1d1d51506da837282c2829145551d859dc1a03d40fd4b38ee2f6d3bd490268dc9a1b2a3c142b50f70ca1aca15e500100353e9b900b41083212d427d42c54c888ba97518f928da4344365505824aaefa7a69b7541b54314b0b052992295504c8c8b0fb3a5756b0856487585924291879213054496d0285b0854360342bb4414b2502fa0964d282b003231d6ce96281f85df08407da0a8a184a0d032aaaf379b3241a1ac05655262855c89bb288b48a495892588a703e507f56aa3e27011f97340ad417941518d0295620f1490272ea2454734c227ffa21c9e710d1183733b994472fddabfdc74f0e455e58327957844e928994211879e9da440d5a09e4201bb5a76c18819414554412b9ebeae523e08f317dcc0164476b47e607e64544e4be3618d12e0c3816bb9fba856e2a8d65c39c380ca482fb2941dd5de985435eba88056aa350021e5e3341ce29b36e971c243c60fe1fb58d54ea65a3692506e33223fc96798cee51d6a4922b4ddd154024a7de4ef0b1c63768b7c5271d6ec52911c29c6210c91c3843a9c4496c65d42d42b731f458a95213d5b931a03142fce1eb45b18940754e035f2cdf02e3a6e7f33424be28169e46d9be0423b7787b4437c4dc963890eb7f89ce0da17a2c051e506f2d85213e1b3a976efe3145064b9be04186ae5a471c738d262905a552dedb7a41a588236f731950634e0240fe9ddcdc8e6b559f7248c62711d9588744c3b917622f31f40cdc12cc9377fd3dc75cec9e3bb46bc095ef8efa67065e0d3927846b008fe1b86742fa2c9fd5bccd061a385ce2b38c2ea5075d449a31c498c1d188eadadea93b4eaca61e586ca66d168110545c5e469189f0104d7026e5085580d3260b25472ff000000000000000000c098456138aceeb4a92a02305175a8cccccccc746bf76906d156fe226d9e226df80bef40bb0d350df60c6c67a7c69ba4e6a7091d416a3d19cfb93455051a8d20cb5978ca78e162c91d6881186340a00c32184192bd1ccf738c93f1e30cf8e28b13d42d82bcb9153e3aba692882e071b6cb5b9dff30f33412b1988d99b4b74564a8b6789966ca0ff37e4504c92b85cc63517a410bc2f8e20b31c24813d0388419c20a515f1082f49f73cc586d1d43b78320e5690f53ba4d7515d382209e86696b8d658e2a0582209ed42d6764f6954e186584d10282010604316eccde9a2d17fcc180c61fc81b25bdd27fe05fbed3f003b9d34287be19a6008d3e10cbc30f6b62d2d1fd4c830fad4c994ab767d7a498a8c751e5aaa367eec01ab8c0b5f6408e52d0209e762a88b527a8810b9c861ec89d6eb56637940ea09107f25b5d7969ba7acfe281f0723a5177512ffb5f1a77b83d1ef5a03475448307e3044bc30ec4a0fea9a3ec71496a474575205aee281f5dffa4860d990268d0819031e5c187a9296ef470e08b2fbe0883c61c88f5b17cba83ef0fde6a39d85e57b32b8b5106c6418be832cbf46caed71ab1a139629c80030d074245f54af789ee0d24d7bdcecfeeac39185d20061914a0e106d2a70cf95eb9da898f551b0819bf3b0e513954d9bf026f01150d361877a9f256916d6a2f6739aa0ada1d2a86c61a08aff6661ef5a1c1e234d4408c26ae9923d559f788461ac82935ff626fa68186a27186721a6620f9a5e74cb5d4393d47086894810334c8403a9b1c8f472d795a4a630ca4986bf19307e3ea1d6220ed6618d7deeab4a08581d8a2398805f1928cddd00003e1e6f2a5ad9c3ff6188981c617c8c1c4e6858fe3687a3f6388018115882186056878811c3a77b26d3533f02ec88007687481249ed241d2634f9d9c2c62600427b0000d2e90bd2b7a98dd2317842186061e004619618480c61648921bbe7e527d177c400434b44050fdb191fe68b35807d1c802b134ef479a73f2de206381a81ea7cda76439cef1a5710592e6ecb765df5a8110193a98cafefd99f715338d2a90925e9e4b6ec79be1830615b81069b76bfbb6e9f8fa0f2f5c3386181068c118621c23d09802795cc57ed366968f3aa62105525cccc17bdc41bfe7880229b5e7c830bd6840811c05c9f00d1ec6501d78041a4f20f85d5c3d4bd9569b3286182770c1183f81ad319e3140c309e4ec38b4b8921d4b3f2d6841186394c13546196454804613b491dbeabab46f6f158ffb831e4f62290c4619ae812fbe00a30c7fc1175fd4a08c1ad06002213bc51c87fbe99b18b0e0c778c1328d25102ee79f17f5043378176835808612c8f371d4a041ba3f8a396094e11a4030caf017600dca30a49104a28d79545a99031a4820f9e66c664d66c79ce917d03802b93c76e5944246646c842e7e24e6fb97a308845a4dbd740b8f331b229073cc8f6aeeeaede06bc56008c4d83e1eb63ddebb384f05348440b610ef913a2fde7bb2400c314ab280461088f159c4bf3bcc1ee7edfc0364400308a4f038dc8fa596294a2f830c5c018d1f10ac9255ce1f1b242b19c609838c30b8c860411865c42001347c401a9f3d6d37f56dd5cba0d10392e6759856663a7a7c80b175821394f12918438c6357d6c7cbe00c0d1e105eeb83c630a71166ec8260b12a2f43cc83a7ac0b7287f16fb283f092517341781dfb0b2b99e6649a810be27c676ab4cfeae1036f41fe9c234ce7634af71e5b105cbfa3b5f871ec71f55a903a4f643e990dd53968b1ac5767b9da4d9add78f4f9e6539e858b66410c8b994e467c5990e3fc602c3545c9af1d0b72feb0d457deb1d6535890b63f7a50e1b25790c3c739dfe9d015248dd229e656b6335a41d0cb9dfeca5159df398315a4ec21791ad36515444d77bfd071f4610c5515c42873b721b7ec2e7f5241ce1bd37cb6b7cc400541722d97b447e3a9d219a72057a7a5a8512dc7ea22166698825c69de3d59eaf428d403cc2805f982bfb467a6c4e60cc6563919648451860bbc055c8332caf8315ea031a8c1188781119c600133484178cdcb51ea6b14e4ec91a51fee473fd619a2f863678482f878eb5e73f4575f18cc000531f97dde3c327ea296ef92b12a7939bb56fbeab0734f102e784756f8741d21ed098a8cac4eac51d21593e9c3f49339d2b00c0438c0093a769c16dd9b7ff1c5d961c62648ed159d1f59471fe93541ee9ca973e4dee8d54598910992da9ea4dd8baf5d3a031344dfd45559132a1da72f414a7b1fb35e35e5bca725889e1d3d734ca6614625489e2dcb05750f534c770625086ab9fec1cd63cc2408b96b95ca83e8565e24410aefdb890429ece35cb6d289fd784810d7c7ec2c5656d4d38f204ddfd6a67d98d6b6750449b47e2e7dcce139e78c4634e9211675d335b532d9a13f91e9e3bcca19419e8f2f96e65fa7f5db1f0c068811819ac28c45103778c8afdd4df1605e618622c8d551b9b7e6767fe9b130231124b3609939b68e3b9bbf3003118490d5141fe7d8198720a68e3398e6cca179cef1608621c8915ea956cc91fb49f7168c210604661482e8792fbd44479fa247d9308310043ddb91fc92670c82b895b4d2cd7e5c5b7907f00c4110e2f2eb497f1c73981108a2b668c6b017a73a1e5661bcc04fd002327c066c802055188dd9816f8ac9d2197f20ade5703d5d54c50d921fc81d1ea8c71b3c5ffc75c08c3ee8563775dd9676e3b21ee5b86392accdddf08130fe31764cc333f68079cacda94e3d0ac20c3d1037da772244fd6b32be8c1a94b526ccc843a76f79bb5c2a639031460c1c30030fa4b49fe33bcd8e4e39e30eeb5a4eaf0eae62969131c30e64cddb58bba7767b65b9e78e4cb162b2b35233ea505f180a66d081e8276ae1e34f9e3a8e9e3107826af4cb4eaa39cccac88198263ea778513cce678903593ce907716d9ffe0287dd03cdf18dcfdf40b8eef9bc0e6337902c5e3ebec34aa1f5b681983fccb94c1bb281bcba5b55e3d9c3c8dc8c3510fc834b29e66a5ef6a8196a2086d3ce8faf39a58fd78c3490c74dfe430bfa1d35cd4003696e4377d3976571c7987106d29ebff9df651b4bb918c0e0f38f3058d0812fbe78410b0e061c1833cc7033ca505fcc208339e623b7510fdfcf408c3152801f0318fc038e0741d9c700066f4e70bc0c5a0533c640c8aca67953ec8881145dbc73b87d3e1d7b184862a135715157b739184822bf17bf6dddeff22f103405cf8ed13f8e7d5d2f10733d8e2183e6d04c6e17885a731e6627910be4101fe7c7a934d3e8b605e2aaeb5ccef832ef282d10462ba6b1fed7789b2c90ad2cf2d2c7225e2589600616081a677a234ec118df8213b0c0c3287d8119572045f77f57a60c7295b102a9fd72bc731e7bee385a05927ffe0bea62e2e32a1548963eb0adb6e858bee4291033e578e521439602f9eab3ae7f45b7ba8f02b9ed52f420938102b1346e4ac7ff387b684160c6134829e4693eb91496e9760231785f6c0e463e58b401339af0cd6f9e5ff2e03b4c40c47265ed6c1ee6fa2570211ed96c5a094e87498f2478f361fd4b4ba33c1c09774571cf51d6472878a97758a91b36421f87861f8fee2a5959047c3be8345babd1d522425b2d9f2cb5c710d2f8d216bb16559a210462c6b49e532f130462e7f78dafcb0281d0ee192bdb4ce5b3f507848ef151ad2ef3672f7de057ec98e34b1f6bc18c1e1c85ffd833af378307c418dceffe3d788b8f4300bb10802e2c17fa850904808b2c81006e41b0fa4ba9d451c4aac716a4cb389a3f4aae96bd530b62d0d473f1336ec6ead082d421fde3868dd3b7ff2c48579b2dba6e644148fbb7ec605e7dbf231664f10e4f4aa3e5c8a50316e4f9ece15993af20447ec717dd781e86ec0a7256aff7b8a29a7f465b41d432c9545db2827411b167a3a1005641484b1d7f530aafa13f544156d970677bf9efe145080a01a4823c9b37ee71aa33fad684005041caf13b6364953186182fa0c1ab0504700ac2c654bf11e9cc97c380b12718a38c17901863a8004c4188cbaf397de95210c57caba7669347b7a420e658cd36d1397f2d3a0a72f8601a9e655110632faf7b47eb62658682b45933cb73cef972212848c95734bfc97010c02748fb399e003c41fc8e5dbfee5f2704d009825aa799850f4e1073e8fa9b20e63887ea7661bb82003441caf96521a6fb995d0cb8601402c8c49a3e4cd075531fe5d8b9735e9730df7f3c8e49754b10a2e296f6a78e4a9063e5f19ad4fe0f3e0a250a2d2bff121db34924dae6d95272936c61210049dc15a683a97b9ca4e365508a104024c81f634e1d2e53ceed40829cdbb1ae7ed9d691ba9310c02308f2d15d343b868dff6b108023c8e9aa41ebf3a6984b05d00842bb7f647a58ea5ed1b920031fc8800d07d8c8120023cce93b8fed759c45907e534ce1163e7de15104396ef4e497459cf49f084279d45857d10ed52382dc9bae9fd33dca2d9b4390c3f5b2ac1fcfa9d31882b879b5d28547dd61fc280431b6edc5b5cc9552cf842056fc9473983cc5acd80e82dc49f3e3db5e1084d19baab48e2c7ac64090223d73732f13c0ab97742e498502f8035153b725b7189a322f01f8811c6d83f56fbfdfc6d50701f081f06176f8e1e6a5213ff6408a4fb2e992f16e1eeb8124df29878eba238f03310f44b5d5fe6449c603a132bf83d7f40eb7870a7677323e197620e768e6a27986eea0af03d9cc9357aee859d3830e04f1bdcbedc0a277650ee40f476aeb633a47f7c8816ceef9c78110629b3d880e1c881bb335e5cbaf29e70da4594b1e85dbfcdeadb881bc57e7e12d19d367a50d44390f3eb6a9760a196703c174aeec7bf33510f6c2858d4cc9fd6a56c3fb759aa3f4eb700a200d24a9ca1ac34746032942f5c26d46e58eaf211ebbed071fa46620870aea66177d9b3465206eaa6efa8e73f0e14e3290c5732096e38397d46320071e69c667c95c1915033146c744a6d457336118c85a7924811873ea50afb2ccb2030904cd9e661d3b2a68e50824f1f39cdebf10d98d4096983513af9cb8bc08e48f563689ba945b4e0492664be6f02190ca529a660ff7576d211035f665d36946e60802d13ead5a9aba880e0472f775fabc5eceabfe80d83296c93c8e6d791f90ee37f4ea3a04e801315af4d698c3541fe508c003427b740d336f17e4b03f0f64a4d50539e64fa17fd397e614e7823459a56a1f769a458c0b72989e23cba1e71604d38f74b2c27bacdf16840e2e5ede768be7a616c4f5ee6cfb118fb7a10529f9c60fee17a7736616e49cc2d2d3666441c8f1f0d43a866d78c482147f3c6ac66f1eb5c082d8299597ac82665a5e41ecc02f4a8c69a58f760541cf3aacb9689aef6e05f1b22b8cbbcd0a62fa38ca9fb3dcebb4ab2076ceb12a589c78abaa20cea5df6c692e6a9fa920e7384faf73c731e640541065e3c64acb3c05413c8ecdf3e6d4486f4d418e345553eeae14848a3b0d5339b62e460a72cc9d0c19b51d05b12dc45cc7942808a5966b25d3868220529b42490b0a624cd797aab72fa3ff098207cf1b3fcc17fd3e3c410aa19ea325bb1304f18ef113d6b15ce60429c5f71c4ffa9f2abb09b25fe68e3be7e649ab09d289eaef5db4dd8d66827ca122cde348c5876282bc71392f53b9638ebc04c95ebe523497cd0fb4044953c7fd725825c8f14ddb7a2ca9ec965282983175d8183c93209464677c4612e4cc619ebf95a5892341ea682b8db9e54ad9438250973ec6bdbe15dd3c82a4e2fae1bbc8edef08a29fa6f839763482507996a37e1823c8abefe1fda6b3987d1184ea28bf94ba46ed8a20dee5dae8719fde9708f27bdaaa5cf23c891141c8144436c8ec7ea5872094b57d954b678fa521889b73f0c9ef14e4a310c47a1fb796bfec21128294d3a4837a5c0e82a8f14f54df1304b9fca6d6e338c4931608e267f258d9b3f3930182ec9ea4a3cfa92b452bff40fe301d68ee78c5a8d20fe44cadd511ff9199e90369a7925c4e153e103c63de94ce3a67660f64ad170f1fe87a20c7b31d62e3cd03c1bec3b23ecc588fc303b9ef3a0c9ebdb2c7d41d4829a7438de6f93215da810e75c23a9075c377bc1b257b3d7420f6c570fb58e7404e8d59552fcc4c460e04cbb92b156369943f0e844e1fce579f458f337020594efd96f1c312ff7803c1afd4f2b279f4386e2047cdd1df35875dbcb4816016fc7e565d2d763610f5c64cd7dbab52d640feb8de2acf564332f599e2d99c06a29b86ddac7a0f2f1f1ac81f722e1d77dc1988dfb7c1e2e7a84ab3670662c59c9f7ae3398eed9581d4fbef81bd87d0308f0c24bb4fb116637be8f1c640789d30d720e229079e1888b795322b5fde7d776120f795c887561d3263070662fa245529d7e68deabe40da8bab1b3ed7c27a2f90e3283be5e5d105828876aa646def9dc205a2f5a5f9e83b4fabbc057225fbcfb19767fd5a208cffa71c8ac7548d6681dc510e37a91f768e8a05524a5f99e226ffe0d22b9065438686b4a8b75b819861773bf3a45f38ab40700fd1ba8ee897930ac4ebb037f476943994532076e8d32bbf162a074a811493ec9f481705d27b4d077f9977320705a27754f2bd7fe3d1b327a0215a9f7c730279cbea2cbc751c3ecc9a40a8fcd61c6daa54bf8c09a458cffb1a83d7c6644b2094768cef41c73186c894408cb1e7a1a54e13bd4902f9633ff1769140bc1c6a788d995953e508043ff538ff9ba3c618819c35778ee7cc3fb4bc08a4f0adaea989408e57a1773e0f81d01a3df77c98a5f61502c9d2d66719ffb0b00681dce5e189aeedb8402077944751f43c8be607a4d7e4e639f0f7f5481f903f4407aa1afba330f680acd12646d24307cf0078404cf3dda476da9fdf0529c7b9c4bf35f3dcd30571ff6a3fd59d6eba2a17e4f5787f3f47df6d1e5c10d5df34665ba33a6e41b2b4f05bfdd6957e5b9053266f09cba9b673d482a89e642b3b3787c9a60539233547e529f9f5b320e598e2be5d69265b16a4928b69dfc5e3d734168493f89c65ee5b42840529bba3a0a7c9bd3faf20a5f8e71bdb0349d315c4c84a155f4ad3824420a8d10a721cefc85eba9ac90a52c741cff2782c972d263357413aadb9a8f1febb5ff10f355441caa12d15911f47b7169b438d54904f3d3f3ce9e8e6390a0b0418871aa820bddb9be6bf6f95354e410a15fd1f73766b0f2f621c174ca08629c85f9537e64c390051a314e4dcc15b660e37793691fd508314e4b0cef22965d0de9b6b8c82f8e97168969673a65c3544419a0e63c7c93b478d50103764cc9abb3c5a32f8e20b0e4cc00531602c3cd4008522b5b1f1d5758d4f10aedc54dbf2062fbf1450c313b487bdcc27216f9f4ea812df615aea6ed336d51d7df09fb26b708218f769e33907cb1ff49b20ff6fe4e9c781ddc657431364e9ac7bd12b8fb2dd353241d6b9e8ba5369c53fa70626889e5394c7f16da8fab3c625c86187adeef33f579ab104d142746e4f1f9cc6116b54823c192ed8c7e1a3b5cd518312c4be1ff329e994c3ca93204a48ed26bdec0ebdab21097247c918d472caaa174782f0137f52c1e3ec870b09a255eade0f32f908a2c764d2b2f779b73882bc6e2a253edd919d55a311c49bd713ff3085cf69acc1083be6be5ec3dcbce4a4ec36cc858fcd22634e8d459033e65f8db5acf617540459234375cea9aa9108b265cdb6b757410479a3773acd4187dac56b1c8258dba931dae60c410c5ad925d3779ad1e2408d42103c4aa355ec06630b8f10c4dcd1058b513b4cb51930b6b034506310e46033d999c771f43e05412e97ab50a933ab747c192e0804e9bd3d88d7d3e85f0f185b0d08726c49b54e2f05c6d6591863948178f8f01d7bb4b12d3f90e39dd4128f7e7f6292d1076286060badd3f181d839c8b8fa25bdf1da3d903e5d7ab4dcc8e820d50339a5cfb1a3c52632ab7920795c9a73e4a16ec4777820c827ff900d899efbdc819c520aed7c39da0ea4bcef49dbaa37ee6aea40cee12ee94fa7e039ce4107b2a5741473cacc010735e4a071a80187372c6b1fe56253df2117590d3790e3b27ee862b9528aba13a8d10673ddc7e079391e8c2d66033174103b7f611a2b6f30b6346d0da428a35ef79e06638b6ad540caabd816377c98197a0758c082302af0c5176938810bc248c197139441460cc608400d34381eb356b2fffd0ce4384cf46914cf0c870aad9f2e18db801a6550d4a443d2ea2bd6b756edbf642045f338c60e3558630ce40ea3bde7a293740c21126a8881545b1b7356ccb5f3698d30907c24d2c3f128bca72b50030c248f99c53e34058c4d810bc248410ac460c1921a5f20b4a61c37462d500ad4f002a1e3b39c3be77f8c8363885186b7046a7481e829e477488f9e273c2e50830b640f45ea62adc729a61c1abc7141185819a8b105a2e5b30e3ded23e99bb5402e8feffd53792d97919105c29e7bae0ed3a7f41e6181ecc12d6435fe98a407185b1fa8710552554af9d8e976c7510435ac40cc71fe918e39da64a67ba0461548b25e9a52b414f362a521a8410562f889edb474595edd2990f247d61f7c9c43fff8679918619ce00520a82105e2079533fa9a7a16b36a44815063975c654a3fa7540d28903ecaba981d5cddbb19c63958138000045a4086cfe08b2ffef033064da1c61388b9d5b3e585cff34924420d2790db652ba7ec2d1d5df9c517d604a2b5c658b51f3e6db0984056f5cc1e7f0e5c34a53596400e1be62635690d2550696ed7ada1a651e62d919fb9d7bcb26f359240c8b2186cfbe242548b048247b9715943e589696a1c81942dd47eac38d92c3519358c40f6de289ac6c3cdff0bc438c8b87a08d428026973873eaf39c72c27d6200239f47c8fcc2a5e6308044d3dedf4a1b6836a844092d094c2b22debf858230804adab9c6393a9ddacd40002e9a35ce9b5ee996d651abc04bef8c2d0e0c1a8f10362e5ac4af7632e1aea1a3e204e67ec309a47d5f57a0f88e3d39d3cf278257ad7e001616ed1a1f162d6153589416317464317e46ce1961fa249ea6268e482fc1f7705130b17e4706c673a588d176b1ab7b005f1efb285b30f291b99462dea96b5b02cab880f13cfb466ee394c3963b78516c47835dec1050fc3444f6316c458f1bde23fdcec0e1ab220af87fbd1eae76341bed68c9534ceb0208b0772f572b9ed018d579043e8b5c796c398c2423fa0e10aa2af975f8ee38c45cfb782b0d71bdf038dcbf839271964f0a108a0c10a62140debd0166b8bf9096a1504d5980e9995d2ca215590f34b8731211ea9209cdd7e8ede435141fed8b6b3f71ef754770a4b4b4b54ba5d3a3bf96a87214653fcf7655ea1592e05a173cce71cd5e70445461962b0800c2e1aa420a99bcc5db07c14c40b19638e2ae698ddc1464314e40f2c9ce9054f60c30316b0e1802fbeb8018d50905a3a6365f774f431ebcab431c8d85d030d50902b274b37cd3f41d4cfd172465a3c2cef09f298bff7b5071924fa4ed0b1225f562df15eb11e9557129f374b1ec509c27c3cd7298e4af5dc9b20c5a6a8e1e28f6df24813c4b60a1630b6641086b907345b7526c8d175a77f6d1c8cad1888c1023f410dca096ac08217e02126c8c1e628b3e865587fe612c414dda666630a5717b70431fbb7e58f2e45479fd2a80459b33d684db3c9a35018658cd7c01734284168c9cb1e37a79836ef4990365d77caf13c49103d8c9239dc1c271224efb91c75fa24f59783042988d5885dabc6ded023a8aa8ece682f7b02410ef154de7b5ff97300826815b35e5877d369fb0fa45909f9d0b5f6037155e7a3e3555c8aba0f244ff59a830c8d480ff381581f74943def7b20b7fc9886859ba85d0fe48f3bf46dd6fac0a3390f44af4fba271d64f038c603e9af925be7ae1cdce70e84bef439b7746b076267f12fd33ab5d8b60ea4bcec39e5f0573a1063c63c7ffd49573e7320c58f376e597aebf022079245f31c47e3389083b1f85799e1d2bf70d823f1b052b9ea1b0c33bfe9327e6e2067b10f99fbcec252da0692740afefb9f3fb3a46c2086b9fedf909a7d316b20854b8674fe5003e1c627375af7264d4943bdc177eaba4503c1a793f8d7760672607fe255261f6ecc40cc174fe252fcd64e590652bcf58fd43d642055694a152fea63207794fd76ab34fc52500ce43899fb1f55c2403afb78ce32f84b46c140bcd87953dc302762e117c89e4365dbec5bb6df0bc4d70be191c5dcd2778124b622d7fe29bac205f279b054c9d26954cb1648f351b070f34a315a2067ddfd78d9f3e5ea592099789ce9634ed5ed628198638ba6df4bc68dee0a24db1c4794f9c7493f5620be5e775c31e6733355200792a5693b43ab6e2a90c2f67728aaf6faf114c82a1d79e5c83229904c3a6a31cf8902313beebab09d37ee870249e4d433d77f02395bc34fc71115f19d4076d90d993585d1dc3481e431548c7d6ee8cb308154b93c52dac385eda825904e6335d5863789b89440dace1d788c798fe25a492075c88c1ff7ca61cc151288df5e95639c4dbeb13a02397cb51c897434ab5b1981f01655cbf3c5d471a88a40b4fc9e39fb44205667d4cfd1474966aa2190f63c8675141e597884404e31f7dbbe3ee3fa4120b7aac6b38f3c7ded4020c6985e31eba334cf0f88f51bfebf635f2deb03e28bfd78865ed78f1e103d4d778b7ece546a0178407e6d0fa7c330bf70918d5d90be3d2a4d641ceb38c6862e08d75e15fc4e3788546ce48238219d739c335345cd94079ca0704152d1f46f9791d470d9c62dc89537a545faa94e69366c41b85e893cf198dba80539f029b3eb94539f655adceba9f3c325f730b3e0bea24e25aaa4bacbccc2b2fb76b77dd89997053907a55ec9e5473d6c6241ccf091079583ca1e5b0a0b928a068fe34be99992c43fb0f10ab24946e5eb2cde774957103e52f6c3e7681df5c7462b881d373f8cd132efa0831504cd0ec35b35b5365641ba79a914a443ecaaa30a52bda74d316d6ce7aca682ec92ad731b5ed6e30e54903f774dc71535364e413c39fbce0ee2c6e36c0af258e638c2e2f75a2730b68e3f0bea05364a418e66c7f36d4383e78d14c496ed313d4bfe6139a320a96cf6e9e4ddf1e74614e4a83a47fdcbd15038e131a693d79e111485dcc8a7967e06cd1b6c7ca214bb6f9578d578bbb80b1d3b73c7e758391b9e20aca614b3a5e754393a3a41e8a0562af3348de84f26c671c1040c0bc200c301cd093569f0143cb44375dd04216af12e5e2e36b6e9964cb4a3c9d3e4519835418a71efa64a3d8a282f1387e87a342a9f46e3620313549c47d8a475879be7ea606937fc87cf25086f6a611d3af29cfe470674e0aa0c1afcd9b004a952def0405364ea792a414ecdc82059b14109f2dc8d477dca3944692641f8bc8f1f73757cb13d24410a3b9fa328abd6b2151b91207b48d914331f26d98b0d489062a88aa9784b1d6c3c42d9b82899f1343bab7b97cf412f6eeb6fd4606c6d276c388274a22f9faa5f30b6acd2c4b0d10862a8b464ea24f371c65ab0c1089259caf414c6e36e778b20e6f78bb6f4e9b72e5404392ae675bab496d64289b8c34c3ddccbb24ddbcad35eda8e68eb750f450441e3ff484efac6ca151b872077698735a2fed1f78fc686207666c9d792f398520882660e6e9922a6800d4210fcd2a5b0221f45f86b6310c78688ba5675e80019d810c4a1c0462048398e62faa67efa381601418ed963b5e439364f5d36fe40c8b3ace2e1928fa7e507b2668fccea52ec021fe304297041182e7081bf808c1390f129307f82e34f670a1b7d20be477733abf6cb22bfe00531e00351f644d3ab79a7d2dccf400c16980d07a0c1c61ec85fe271e79395a5cff540cef89b263b1e437a65230f64f968493f16b54f36eb256ce0e1bdea785b3d750090081b77204be5c8e850626c2a67c30ea4de6471b5f257cad1d8a80331b87ca49a7ec92b54f26dd081f4daf9956473ae6d3a07e29f8f6f1655ddad0f39902cd755ec736473d9230ec48df92b7e982d1c48be9f3367a8d2d4d4bc813c95c3a5eeacf4e29d1b8899b3c2c9c766a30d24effaffbbce6cb081582b6179a36afa47a2ae146cac819cdbfb3f6df2f018c080d850035163e7774baff1728ed948032975dbc36ea6cd5deb73a0d140ae1ddd98ca73d8380371ce5f2ca6653310c36cba937e67a30ce4e4ae396d4e36c8400eb43d7476bcb6413f520836c640fceec0753dfee869c4b3810d31902a7fa6e44cae74eae4c0461808ed1e7b837f5e69b558e03eb00106e2744c72f7de5bd9f105c29dd4a7f09e3275eef002d12fc5d50f3a050836ba40dccbfc723699714fb3c105f26bb8fc21eb525b20e597ca9b22b1a9c1861688759acc62ea3cd071b09105f2947a1c6aa50e3d858f0542a698e3e3d5b2061b5720c4c5ddeed4cbf7b636ac40cc09c9cc18e72fbeb0b2510572e83495bd16bb208ca25428258fa962eecfbe6cedc0c614481f3ce2b492d559dc36a4707474dad4a545c98448f568a67b947c3fc67558f0c5173db01105a2548ea1d25d658eec800251722b7fdcc1a9ae5b369e40a8b34a1eb78691afdadc1865bcc08613c81e5a292ea4a4d6836d3481d8ae192773e8a19fa6cc061308eeb1e593ccd1a3e7b4b104825ace8c6fb18b61aa0e6c28e1b054c572b44e02394cf6fb293d69988d9140ca8f17bf6b5d1a6c1c816c5f9b3f85afbef5a411c8d17a875d6b8b76952e02f953b2fef9985fb92b1b44208ccbf55b2ad1ecd11902593797c79d932e0442fe76e8876bf4de1d60230884ce71f688c614ee360710081ec97c5c9a9a9ee707e43871c937060d1ffbeb037238aee7417b702fd1460f2a29a9d3b8980e8bd2508f3f54bacd7b19d9e001f1e4c3e7ca5c5dfa7741bae8b5fbab539f7a5d103554fed49a9f3373e4821c1ea690c1e3b0c3a6e0821cabcfeeaccbc8556ef1f8a4ec7ac76d41cedf18f3e1f2079b5a90ae63d9d5e76df09a16a4507d51eb3bc65cf12c48b3d6791d2ff4942b0bf287cff18fe7c71c1a0b62dd7d764ed391950a0bd274b61211cbff5ebe82ec262b7bf33b1e7cae2047151e3dafde3fb05a418cd60f0f8f0e2b08d6d5e2b9a3b30a52fd74cacba939ff795590aa7398f5f16d8e52fe53414e6139b24b21ba33f3a8205988ce8ea318fab2e54f41cc9b4deb165f3c57de14c4fa0f339505d91c2a5f0ab2c5b51c7ee4a420e593babd1b75e9791444ab0fdab7c12ab82c0ad2a6d431c723ab8a361484afedb4b8396c1c1314a4dace0c0d1a3a637e82d456169ae25567143d41ee8e2d6decfa381fd609f27959eb6b324f757182e8b93b55faf2fba6df04396dd48d5d371fb3f49a20476d76eda82f7c2713c41ce6a837e7d50b9a830962d8e660534c5706cf2508a329f8a59cbab97b4b10a7ee5365e770336e2a4138d19c038fef4cc18312240f3b5c77f5ce793b26413abb8ef27afef42309f26be5ae4e9648105ea38746bbcae7154890e3f02ba68ec31f410aab9bc3623addb83b8214e37de86c7923c8d2713eba53d6543623c8a1391115d63b98bb08c25a467b8a924cd92a825011ab1de764e93e3411e4c0bbedd35c5acb8908c27f4ed3de81872047b1381d366c5e360479f3454eac5708729ca3ae98b42304e93bab698fbbbac706418c312c7d6477f3a909821c79742c559a0904395fac798e3d26d3328020ced7a7b8bd4a5796f90329ebbd2519fd40ca1422c38de7db8b4c1fc839d40afe96a309cfe103c12537c71656ad42bf0752c8d7471b3adc4cae1e486963b8fd343a996b1e081adfaa952f96c2c40339a8646339d2c247df81d0396e5a904fc94cb4035935e53d9deea803294ae74d1e3a909267ffe44ec67a6d0ee4b0e81f3a9664bcd0210772ce213e5d5e37574e1c48591ebece0f07729ceba15954de40deaca19dbb811cab3ece21d3a50d444ba1734a7341731c2e6c2054efb9c61483e6ce650de4b06334b4e46d2b59d440cc7f51daaeb6ecce9206f28c7bb25819ad3956d040b8d0f2c158aa9c8164a531eb48c50c84f30f3f1fd8efc7be0c04ad78c9b2a9476b4f06f25e6a4c66dd2d8b190329e64b1eb685f4ca8d1848e7b13a63aacab1728481509ac2c79836b5ca010682a7ce87587d12dbbf400edb2f2ec60d973dd60ba4f8712ccb97e285bd5d20c7e51d35f9c90552f6d5a6b9bee67a0bc414b24d2ff3d7c4a805d2974717a3f4270b84d5ef3889a68ea3cc3916c87158b66fb6f915089dbaf76d3bea4c1e2b103367bc28bde729fc552087b3943359a70ebea702d1b3f5645c9dc8c73105629aaacb51c7aa581e2d05a24779d234f23af7de51207810ba25d95b7e7a4381386f9672743bdf61ee2790b38c6a7c8f8aaf6b3b811c6dbfb86e560f6eed2690bfbca30bfbe8ee239b09648fc2746c99ea25102e8885e6bc8e3bf695408c631e47ed9dab0306100229ef65528fa691f79e3481018240f0c062647554cecbf95e60002090da2e67acc757998e0df00342ea78d0e01f733b6b06f00131c6efe78a3719a007a48a0b4bb1e653d09a0dc003f2268dee5836ea9f6378b10bd226d5eaecf7571d7a744170add9941d7feeb88e17b920c5d0f217cc2f3ef00217790bded3238f8f677fe7425b58f5a17a7bd94c7f5ed48294fcec2dc2f38216e6c52cc8591ee6ddcaa13bd47b5ec82263d17d744d4998d974ab5ae8052cc81acc3b76625f727d7e05f16b37e5bf156b0b3318bc70457ad10a52beab8a79d89185b4c90aa255b0fcad82e8c9d2445590a6f7e38f175ecbe3302f52410a1fb7544c9d3a8117a820a4a5ecd2c155b9253ddd8b539063cc5462ed7b610ad205b958b5f9bb45f6a214a4d41a2dcac4ec7fe40529481253d1e163a75497f36214845b2f7debcd9979698117a2e84626cd2edb2abc6cfb552d655ec250f01726f00214fc09e267dbc7b14bb4079917313082136cc00b4f1036ed46ab8c9f4e103ef0f6983ec65cb1524e10366bfe48fdf3eee56d1344f3d8d2d24159f8ee35916eda6ff0582e9920e8d646b18f3986ca3526489b3d9ccf94f325c86b19e93392294e65bdb0044936e8a6c6eef0e2642a418eb6838628ad3c5d9102bca004b9dd73ee3a73c8a9ce24889239c73ae868491063a8e56d683512a4e8521ec743d10b4890bf6bc7cf5bbe82588f20a47472c9efd90b1fef08f29aa7b57a4a234813319b6275f869d48311442ff5edb1bc396d7a2c821029119fdb2c8a2078fe18a3578efb3b8c27829c396c6c17951141f03ce9483443e5bdf810244fe71a93e17332cf1a82bc295b5b08728af0147f344dc5781282f466b55a1d6610a49cefb38ab5e7f07308822c356a39f4f08247848120db7b9816f62c7fcc1c204821d38256f6f70fa4d2fc31fba6f5f6f0f10341ee4ce4e7a36ffcbe0f848ff7632e77ef7f6af8408e8ccbb0ab8c9d31650f64a94a9e991dd703e965efd264f799c49d0792b8879bc6b28d07427b58eb6b1fd5a798ef4098f9b61cfbbc98e3b50339ca67c87410eb404a0f6e7933aaf473d08194331d74eef03278c8e640cef5919cf94f4978e440ccea1e365327710f2b0e44ad57ebdc3e9a7a8103e1eef3486498b6075ebc81f0d15bbe16ad54dbb163f0c20da49391dfaa1443c71ed406a267548fff7083e8051b489983ece5b4a96b5247f4620d9ac56cd6c19ea586fa02bd4803413b7da0f13c48bee4885ea081f4d991f8df873c0d1f885e9c819c7647ecc31c63d4f2885e9881d431867fd2f4d1c64d8ac18ba3ef9ce9dbaa357a4106623697575f8fda63d731903cc5c83817c2a2552cd10b31907f343ffa141ec6f00d7a1106e2478f95cc3f59660c045e8081dc1a22439947b575942f90e39c63b00eaf7b81986d42647c3ce638d75d20478f769b37485addc8059287565d4fee7bb105e26e8cd3f78b95d2f49d175a20a608f1ec0f9683c59c17592064fc56cab697e99c82e80516483515db2e9d17572084ea5887d9a161c10b2b107ccafd6cdff3a20a841af97075f3a3ce1f4785e38eddd11db7e6c514c86f39f8f5d0a3bd9042de9d7673ec38fc38f4220aaa48856b989788666c45861750d81278f104c276a8ad91d69b17760231a759d08bb3ced1789a40d896b1f5bc14cb15670229f7db99c7a4174b207578d0bd3974e871e62881542dadef35d9152a2609c4cfecdafdf0f6ca72788104a2590cefc9827f04f25e8eaaecb91b8194a603ab0ee171935314bc2802e9473d1ccbd1debe4f04a2674f1d3fd45dfdda8740d62c1f53ceea6e6da11088a96396908ef2799a2c08c4b618b397e886f9090452b278e5b1475944b71f903d4fdf475ac1be2bf2c207c4ad0be9aa31f8032f7a40b295378fccd290a93510bce00129978a76a06a961e7bee7641caf04bdb7b41d64dbdd30551edb347131f7d43b8910b52864c0f2e32afe5eb385c10d43a28cfd71cf33a7be316e44c17563b8a2d1bb6dfd982ac61fbe38ff39f4866566db8510b62a6c58c1fc4466fd082745e2f9a692d6ee1c62c88a779be63a8e6afb4c882e43b6a313ea54dc71f2adc88457d710316e48f2ca579e315844be17a3da86cb9821c83e7cdd261bfc46477a315c48f1f3eb01cd3af7d9415248f2753da4b278d6eae82b81df7c2cd73cf6879431584f249ab9434dcdd89375241f268e77dcacf2a549035775ced51e7745c5d26b8710ad268c59d87ae212c365390ed3ab450398696f3dc2805b9c7df3ccb2a07972f29c861e6dc471275ee611905f92dcf988f47b1f3ad28082299ce3ec7a0ebee85821873186374bcd01cc5102808d1b166c9d45116ebf30a373e41fe28c7f238fc28e6e0367a8220193fd2357239909b3b41fa74393405ad89c898e5043908958c8f3a872cf3d04d90f77292d11c7f3253130451d7988fbf64668298d9af348ff707d79c4c4c10d3aa3a4adba1c6747ee625ea8b1b96a82f5470a312449791ce2bdb51dacb71460992ca965d5e0d97c5e3c708e394046e4c4212e4ff17cd1c684fb4a78a182c48c1095c10061865843183489cbf6eee512e7830c888c10b36053720419eedd84ad93c4e59078cad32c87016dc780459bd56f673760ef67f30b6ca38411847811b8e20a6ab2ae2bc5bac5c343b4c29adc2489e588c6f348218a3778ee3a67fa83532829cd472cc296e18adfe405c042987e52f4bd1deb01ca222c817ada265ab3011e4d862d80b1a9ba3aafc1011c41427a52e9f8d87207649c741e5f01b8274b21fc6cbab1f2f640a414c352e752395774d2604d1baad323cf640933e0862078f3c47ebb3395412c10d412093f1321f2d95f2956951fb1fc4ef7823104497148d51c70308425f0e3cebbaacda5bfe40ce1d4fea873f6b55e534df7d2068c7130b1b3d87b30a0e37f8403c8dd1118be73bdab1873f790e4f13eb81dc335b973f6ad563c90339bc7768b6a1e2811c5b901f8f1de664f9bc710752bead0f9f4eadaaab1b7620580cf5e9e13e10eb40d08fd1c737473662261963b84107f2e5053971b78d299565b83107721cf5c8b2484757e8e44012cf11933978230ea46d8bc9fea472a562851b70207f7822939e1a4a2a8d55b8f1069276d81abeca434cbe6e2057acdc15e63b0ee39f3690536f10d599ef60f32b24dc6043c1926b6b6ba82f8e1b6a302de355f47a3cdd480331dd3e63e59819c7676dc20d3490c3696edaf8b796e2a60c325c30017ec28d3390a6d2c72e66db0d3390ae5a64dc326cc67e4bb85106f27fec894e6c7ba550197e84c1822f630c318c0ce5c00d32903acafd81d69c7aa063176e8c81349af692771627dc1003e1363ac77130e9d2ff32e146180a6a1db6dee3606cbda0058719230337c080dca58bb85898a4b594a6ca77f66056eb23e3c61788a9ee27ec33ca1b5e2045e854aaa6982e9083d1ce5a2f1b2e102d7b79c7ddb060ad6f811c071f7665f868faaed602319c586b98bab09e3b6e6481701a3a5ceb9ceb0616882ebeb162fe4c3ba6ddb802692aab65f3308f70c30a24b7909ab1d283a7bd39b8510572d46ff3c93fbe8c871be106150832b71af3e33a7b70630a44cf39e6e7387f79430ac4fe20634e1a255aa627c28d28103ad094b45352aa1b5020996dfc3c1f9d1b4f205806150bfdf10d27e4b211751ab2a17515e2c13f6ecc5e92e046138829c5fab0f1ae39c48709c4e95adf4e7b698f72dc5802294dad53ee9cb5353f7f704309041fcded7f39f2e8f76612081e761ac9d873512d3c2490e3a718fdd1b0d62cf21188f22af61d3eaa4fcf6b0452908d1d73f7f5cdd32210e5530e99a82f11f748221032e7b86853529ee3c221103c2dfaee5b847bda8725638088861b4220974f6f90be0ae35a2206819c7a2a4dbc7298ebdd0d2090e3b8fe209b5f3edbb41b3f2025bff30f33264b1ad5146ef880e0d67eaaea5f87e1460fc8e91b57da63ea060f4849fa6374568ffe51b40b72983adcc35ca150005d10e3b94d671453f1b8d28502e48270ae97a39472435b0aa2130a800bfc02dc22f3e0a5593f76178502d8822817e3c51c497f0ebac442016a410e8fc45226d1f70b6b0168b1b3d002c8420b100b72984a4ae3af25732ab0a82f0af00a826af2d64fb96123635790c2e6a34dda6f015a414cd196199952ce8f530158414a51cf23e6631584bdcfb983cd1be26c55102f467bb9aaed5ef6a482a81e94de06c94fd13aa82084eb744e4178d7a9d08bcc315ade14a48f76ddf3430f2cf22f05c93d74b57ffa4941eef70fe7a3bfcca1f8a320daabaa590ecb22c41705b943fb4aff966ec1624241eefde878f36eb5a50d284862c93b6a2bdf05db7c8294a23f0a657b31796c3c41b45ce6c1b5359d20eb5f0ebd32678b96c30952c58b5dab1caea3bf0992e664bd2d2597624613e4358fb7671bdb75c9042966d94c2922f355c104e93a8a89d0f4188f1f97206ea7c81cd53e4ddfc312a44f3962b52bc7bfe95109729ceb3897a7f82defa004295c5a86f6d03109c2a49d5f364deb1d3a2441ecfcaaa11d47a7dd448210d9b5f2631e49cf9020cfbaf999fcc7b2f911a4eab093ef56b93a76c4d165e8f8a9e2d20852ebc7165efbb1c7b1851164b54a5b39b28de9916511a4725b0f7a3ca8b3b32882f8ff53213c5412416ecd61959e7a4907154410d39ce5a09be38afc8720bbd7c5982f3efd5a0c41f2e0d153f4e0e121f54290e37b54a32947b172ce094176abd3ccf05974733e08c28737d3b1362708928cc753fb4105298d08a8140d4622a2c160241209840151300c50e63d00431408001834200b456291702c9374f1031480024c241a2e2c201e28180e101c1c1412100e8602a1303814080302615038100a84c3029c3c08f601a364b1a81127bac4b6b71f7470da432d9c14ec4ade132815845da329fd36d3b017a072cc461fba9b631a61b4b7a5c011d174e25702dfcd6adb671c7d94a90b976a26341cad59526a280063d240483adc99d2bcd2756c69360cd5961c8f14c89879a870271db9f04183560599577277e31c5af52ccbd3348fa404a175df4ec8a90a577d4ec245d8e3d41901856ed9cf93ce08d80ae6d5bc48d36fded3db7c518e48e0b0cc5c83a7f446795f0014d9de6ff552c2c23a927e3814e560120a3ef352c3f38de1728913f00aa4870acce0fecc08b7e09028f156df3631c4e45994971cfde87928324d837e5277774ab9c86e1a41f8ac8c18520cd6c9b4d516234352bfcd3851e56512187a8cba1654c6b6d4fba6b7f71c2a6b6273e3948758430cf4b29e627e8ebc1b1a5bc3aa11aab4afd5e876fbaef3d36438a15d71d01b978e2cd224a3aa3b9729cedbf0d1eadb5842efae442827e3b750b12eba6aa51f91a35db83b8f11cf88266765e50a823da0320a86095569e230d96491be9874aba9ca5f31ead4a7ace2fa15963a89695593f9b9d18bf680058a4d2eb4a34dc20a58271287c1c6d88ce01c0ba8a19331429e5eb34a6ee45af10f2ab62729d89c9edc5dabba2f9fa283b2febe0d42d6c39e2f6dc42a60a3dbbb8d449d13b84801495e200097865d529ed098f3f5d77a618cb7e30c15260d7f43899f5089b2662ff9877eb05e9f7a4ab37457d0036f1c4adf30a3da779b9d0b4d114551414fd0611019b0a5053600c550a7ecc1aa3e2809291f9137a80c24a984210d58d094ec842b890486628abf1ade891151a2eef08cf9a3a7c12be20de463360587581daa64c82e08fc2447068ce0474ea578aceeead5ce90375e86ccd1a0a3f7a221b2f42a2ffac72a4624cf3d273ddaa1a4f7357375073d2063c64c4d1c0cb200f64add12a547a3c4d1875d9ca027961424a361bf2dc0455624bd47a180617a2fc0f7e9aeee6d8e47e2f276b1273058b4af3c2344fb7e33bb75824630cb3d9952cff51df2132d8a6d560c80c2ef07bcaf396b6f9eebbc144254f6d84a2e9e6b9818667bac285b8743055f453912895cc69f24aeb47fb242df5c4e1b9e1c01f6c03999ca04ea40039be147d06ca974e63842b50cf4683803b4675568041870ad7f30a16749225762714d95dec7c442bf16d07b991bb84e3c92d97bb117bdfb43c8149ac1150781f7f7b59f48801f38faed204b62c6dfdfe76c3ab4f0961fcbab601cc5c836c135bb887028dd699d38fcc5a1d97537b2f499f3dd8f9c1238f32bb46aab10380715c0bdf0841447f5048eae0cc4ccc3e7e5f1d35bf81ee081fdee324be5a55162fa2088c0a5c8b20b8eb16e69c8f78acfc272f381597cf9aabbadfce3847c36d3f3f43da3b5af1798bd4faf1bc3b9dbcc1eb019d1d624933da18fe672e636913fd9f71351482c41fda0a6edc5ae81e2e5c84e118b26a298ab7d999948318f5e2364174c54b3e04893719cbc69498bfb20f4b6879e135215b01b6f112af9522f2f2ab1c016c358c4842139693509a86942476feb0e8494f1b44b06f0ce40f2f94171749a7d2481ee2e4d2387edc0d9e009dba44ea5d28daa0e7c6e1ff2ca9802e7e66d73333b9ef53c22a4cf05aa9de21f8a0d1449a5f06e32d53c321c4df56aa82fe97c9b1cee4cdc5c300f91d2ae885abf4882947567a0a3d06f304ed3091a6d42c80e0dc891b6891736ee1a2418b96e7a5a2e0300c182a6308c22cac7ae1a7f817151758d06ebf434face6d99ffa185c4479399b811ed97e65b2090d597bbedd366072d22568762a54d28eaa56c715ff6b5de495cec5615ac5132f92255dddb99fdd2e18f82b469059af1a9500c75a685997b2e744e4b615d2a419da594a883afda93b814d273549217674be0088d37a209f3b0509b23e245310fad9d6c63f08d29e00af57ba733e6a6170e22fbb8df518e27622d7b33c3ca6fa53cf2848eca186ec0299e52fbd42bde082f51515225e618315c6bbc6ac0a612703f017c681e40fb912ed90a35932db5469afa198504d0ed66515d4591059762f8a31c094cb8d3c89bb609059a864e8a5320ddf84c5e6a1b403d36a54a396bf78d9669f13a77492154cb93ec72eb6536a6d7e1eb5625365888af8a7b24e4ad2520ac663075f76e17624f2794ada579e01acf089bb8b5145f37228bc0f0c8f9c79b2df02b3efb2364341c98927e99c5bed0d3d1c404a0b0dc447518bd3847bb0c41c75a22e78d218d0286eff9319552d2010edfa1a9d62bad882a7993f8512d35941118c19e1a6b545a6a8c14c61a37a65085ec85a55b3c409fcb084060e34be27cb2f21e23d5c91e80095e64dd22b9112e6496fc2eeb09211304c0f8f5b87d651e99f3d95e70341d2bfc3d999e800ac8706ef8a5d9de982629d1ac6650036d0455631a3c80e10ec4399250deba967a608bad02e4c89a59e90d3576e17a1c45d243fd9010f182fa14e93a54c90cf02b3d7ae48418c552836cb9f434a8d3b5a07e449bdbe07208e20e2455b07e8aba51febf47c05dc2a6cfe1aea6a75571616df91021f7b893930692defc19ba65e08eedffc449f0f546e29cec2e56c2201c6a0caa6bb29024120b12e7f35f263dda43b0255a94cd0a62f063f6cbd13c7dfd4c74f9a002dfe507c558640f8857ac125699e3d3d8b4e81e478b3c26e2f1a351c39b36482e4b801344d3627fbf329e44735380409fa7a3c7eeb7b7565b669c09edddfb58b69802b6b69244f0fd4c652c9ad2728322d9d80e3863f8e3ce2c8b96e71dc0fae2b22f74560e30c3ac6f1ebc639d47004e166cd2cd2bea9608ef5d51ff142afbd17fcdf657adaec8c86443c963bc28ab2a01a007049a2075492e4c78ecf67f666b42b62b7bcb4be0d47e2f58c2687e3535f5e2529ec136ea8d36786a3c5309e3750f1875524339f6ba47c8b28b5778c50fb1d2eb61d7b289dfce595998ea7ab62d06051e98c2e9d1992aa88c1f6670100c3fa420a3464461dc52b1267738b66b106dfb607294ca7b099fe3c3703444d6a02908c7aaa6d0e1a7d7268e9aa819421bf2c8518df4d3d2567a496e2cf7129c52780c1ff4eb7420607d6433079cfd4d62b9786b4625358df5cbe3507aad9655401fbaae25e144f71ac7f2c98d5888d705d129ab878a8a55c26fa9ac9dad660da3347bb3fa67ba2408ac060b966783b2b1ccc91a39c9c2e5f4da7687359a99a7d024e45a688d4e14fcb6b8c274d84e6146e8961cb33c74cf99c649e7713f4ee67911b0cc25386b18e7fac09302391ce1e281b166291b05dd5b4cac732aabdf22ef77f20ccf525410c85b34f50d9ca181239d0d7bdef46b24983592c455d4d27216d39c49ceed408614b38caa63b9a41a37fee41b0217e71997bb097baa8a4601a5850bb44c56079835a0ccdf0dd8d9c1d00f981acc2fb629e9746c248e69fc6002567ad25a081827af521d8bd57046e08c9d2a1cb0604a3e0c3d05c8e942d00cf2604d03dd9d14cae04584c9f837b6c4f311ae8f3c02c5508f3b2d54ba57cc8afbf964eaf1771c3223972b040c790203575312a5fb470fbdb44795532905bd3d7cf3f1344179c58e44810aa1a92b0edea659565a3f931f8d339a56c6cc83864c3314360a9b483daef5080551d37044cf16411fc38838e423411ad29058ae92dd73b4c0e7c2bae3eb9dcde9ec82e644459641ad209de05704aab01b7586b7cf3e3e9300751d512847ad5cf4a8010c40a17a35388bede94b0c667d20513e5014df754cc5088bb7917082a8e896c90c66501ea7afd50e74b56eb148cf37c367276e4558824674c23a1d7a2fd21942f0321ca3a67c299f8be3eb03af1be861eda2d812b6450271eea0b3fbda14617228bd920d7b01ca25a07d31894b29accf5096c72234ace71504a6c44205e5eb7e5cde6816c66aa3786ed8046b7ab376533ce773b7ef80a784388779c42b42a82b25af433a83385b6bbb134fee968e69163491262969a75762991274abae9d52c82ced5b7e13e393dab6906a95d2647804850000698a6af093c47561fdd8775861e0810721a0a521256e1f31b9553aca2eaf494aa04105469938c714f909e5d92b6fefd3a1a104f221dd5782aa76001970867ad6940b2dd5506038567062954ff9e1fc1c60ce50948794e91e6be038fe417206d3ab5f74bc1172a54bc6554f3e7c88b3c0c6b0314f05ce000a33501014245f87127ee13647d1f0934c18a978603cea5fcfbc8af1a159f2bab48fb12a7c1d7a911c48811ad4f9a9865ad4d65303ba4c9df22f4e1c889dcdb22fd59b20b1781ca4c059a33a85075a4fdb7b3839078b2dda1d982ff45d0cdefe48d7fd61abfdc49443135d0cba960c8613222287ff9b614d7f206239191c4e7ee0be4fd502ace91e4cddb6681ea6c8651946b735ba3dd8c36fb8fae5014d7bfec697288509f9c70b42eda0148263ab77160957d13b043dfe662675f7a7362a6389da906213ba0c571eb68b89649f48dcdd04c079dedfcb47b686cdc0d49b824185a17861fdc0c26b0e8f166d5cc3dcedd9730d4246e13160937101f01eb252551270648875556e2a1293539efe00919116d8ef12c1df2a7949fa4f5a40b6cee1c9b6d8888b8ffe9036d72628957ee03880ec8d9af3bbe018c1073d42e02e8c06705e3b9b0b5fa15c64f44b3200140d9013102efb6bdd35907cbce020464efa90fa0c23f5af9783b984f71f8cf12af30d96b914c00453577b0e2f0b17c48a1f1223644aa17509e6894dfae3b1f1c08273141a2f8eece2acde6a4265c4d28656108f8143201a06f204043155ed035d027d0bd61e882d5d4558b44492e2a3c1e05c615201b755694672d12271937c075a45416b48a0f11ca88d571e82f25a8a9a6ac655c8488dd84086ce5c9e637166fea0e42a74e4f9fe1d66b502f47ecbb4b842ec5b0044ea264d409dcfa1cec66dcf1e3fd0a9086ec5fb86c661d42072577258ec7273d03ae6b51d7f25a0da9e554019c8f133077afc9ff5a207d273e8c0fbc7173d07ed1ab4911105dc5b0bbab70d786315c1516a16ec9580d9db2ee4b0955f424b9d008e4db4fcb25ca5ae40fcb4d756be434bc2a53c40cf880326afa45222cec4e18e6dadfc480f58fe761d0eccaaa5516799e58c40c372afb25aa10c2f7a0e6d316cb8a3982b94ba6657686861fe6fc915ee1534861d02e145b197924627b1051672a9b0ce51fdbf090e367eafadb35ebd0413768dae2bb0453accd59195be29583d3f494e7df7b2a47882eb43d989363514500e7663e38dcded2da4f6f63e7a4c3084257a346427d28dfc1aaaa3fa87f5dcb0d51225a1cc9c20eee7c22a16a8486ffe3e8216e4b06b34f8117a2461102297139966a8aec388187994cd2448bf59b4ab1270323b860d74cf95fcec5169d3e2bc002a82afa326242214a1035c7eaa9481e861b10ec9bd0abb1c451fce991546b82a422dc8f8996ede3f3c6fae1a97732caa102fa59e5ad6b7ac046d51fb6f33ea8c0dc2d1df345eebfab473c13e72eeb06d162993b5c3fa9b708e2376c769b027a134d32fc8920f37edca5f41bc3abd49a04f6b91d8242388f3fcca3ce8e102fc0c3ec58d0fc1e2038ec2371c9b4c1a1351043b83c7a9fbca050da78bf9e01bb97268ea632f7c7c3397350f85c12d8ca8a44c0c7ad7c7606450a3c8b885b7eb5b4439ad76320fb60b29471b6e874b48200f2054d22cd54eb2d698479eda8bdaf8d3c1cc3cf842e221502940b5d1f98219dbe0592b16b9244ec767c3b6df5a2e32b453dd4f1e77e4c3e5dcd33903b80b507a2b5b713c2d31c0371319911a549c5c824333b1980ee3c5798b7fb5ea4187395830cacb19f6dbe2b6a0475ede41f876e1fa904b721f35e30899246e61cee1d1881f9010ed7c5f28ef696a9b5f9e88cf409adaca0938d4459fee4b365fbafe29db60225df3d9b34bee39ec53b8999842493d820cbce8c719d352c996e894451adad2592ba43297590bcfd7e15de4dcdcfb349ce93e6d4120e4205fb7fbe8cfe73a2767b2889178f5a367f19ebfc8a539ceae7588b4d63fffaa3f9cd601decdcddc742d2234963b302c065279ada49ee967d812a9a62e3de867ddc3ee4ede5a03b150699120b783230385ded29c9a9bbb1f8732cbf99f53d0d4534136d7c7963a960b947e432d9598b220d3ee41f629fb20cbcc51539e8225126c67a35f5691941d5a5240eef4a61d0a9a1b45d994acfdcc8243298eb7b99df33b8d5bbae1e3776618764d3cd47032a62c798b080e6f43a66c5df5b30045831ab8a6f3bebe19c9437b1a130bf9e89bd78ee82ba0b2ea70356773bb6fe15067c7d78a741908cbc8fcb68973f9650042450503b29d064b917efea9af38c372fd9c6be986e2456652e2e324db99ddc3319dc6af759a4b8a115dd56ddffd9b1c743833810596edeef9c05de42ce6fb40877ba850b1ba5ff9227d5266918eb01ba744cb6831d9d0142a70163274616d8ba1ab36caeb4e91ed080e9759d56bb8a11bbe614770ae9bb517f914de596ba1258534c7e8ad64662bce222a091a26cdf9d8fc5261217b3ca2d404b90d4b8bba2fa38796dc73db87604e39f153c81c0fae61abd6d9115ee1e6bcb335015dd6305e44829a58959ae450a1eae7cbfbed438a7c88f6e40ecb5a2a427972aeafe71e28b9aad50982cc4dc269ac747c6777c158e9fbf6a64e543af2be46e91741b78ec8a85617950c608b839722c8a24f52d29dbe842695f501bb1a07f886642f7335e63b1feeea80cd2c27dce9789ffe0c6b8bb3cd0edbe8502b81a05b6dbf5d44200a0a294bb00fa06dcfe78964fab9cca774f3036ae9693b34007f5c9cc33f9bb440ada6f41dd8d411a7b73dd78d41a7f965cec67ce3435e1db099e113a8602c3c3ce92931c1051921ce2f37a52c52eb7eafe558e7958ecc79ec63b96f7b2bb1baf93856d94ff8ac9a582d24d93037a29e5dd1e8589fa4a00a3ed7b00db09bac56bc6358034f30a56b0b5d8d4bd616e37384ea6292562350cca89790bae2d251fdf931f5c9bc3450474a03a382e8926219ad59749e9fa338e758be252de9642f8fe9583d7188f40ab9b4317606a6accbc0a49df3928a63655446a4a21ae2902765673c66b5cc9c883419fcc1f4a66a67a15d8b899cffb92701f9cb587c2b73114789a8b1812d5db22497371abac1f734481d001c6d09787539f2ab90f50bc84ec70aee65f555f77d244fe057202cac806a097408c201b78ae402637e6f0994f543c86534f0955700161d3a322711cda501fd547c7494f147679a96264703169d4f3411df06711f848c739f911a3320c3d1126671f83357857928754cf6d2fcc47929bb869b28a94e13e16b370786fef9496e54a50f981c89e54fbf072bf81e328e50b5d0a2dce76a92cd6b358d3d073e7718d64312f634c59b33bb11e8c030d5b782d0a3f3228da007b3f02f69fb2ffec6a8318210fc8e761ae00495a4c34a0b5e9b1892b4216e10064a81e27681001cb15550dd69dd05145a2145409e3a107dd663319005a457be24903111f4cba43af8454ba49714c3e7b0d9e7f1551d35cd6e22fcd1995ef9b7b0f3d2142008900d6b26ec1c35a9c437933b4bfd133c0f8c5f7c5af0be20ca0bc07c63ea0fba98f5819d5c1232a18167e2207b0c45ac6a5e61e2baf1aed5e69efde3faeae97347d5bb7ba1aa0ac1c332524a14f5af0fc9c494b646e4970750ca6a91fef694b36bae0a3cf859d44fbbc29e1b50c09752e322f35d46646b360a44871f41c02190947c6302b79da240dfd84010300fc35170f2afadc58967af8ca2d9e53eb9d144853121d84bc54dcbadf0c0e8c5ab4844ebbf13719ad25cfc753ba0d94870db7676e0826068c19042b82c042f650f597a02af74b93ef61a922d2d8a7df53f7509fe2a00ae8f7427157ec8d1bef2114ffde3c88bd311d1e61d2d5e7f92433d209989465e36c3f4a105e0c4695c1348b33d1d7354954581b207ecb7e72fed4c13cf2bff290e607ad5b0f096f62f631ecc9202c7de6384e6100879d8b1cfe4b0d96f6791615f21b116dfcbe974c85b46ba7d011ae86d75415d3fd06b5b405b22cd589390868a3ac65344e6732d373a4754f05bcb059e1238eadc653fb8bdeaeb30cc00f6aae4e7da262b8134bb3d3042465849781b9ae302b0e2f9a3c5d1ccd28ab883e96e5812549d5f3e71171d8ca4a2b5472aede4934e9b929d995f85acd7bfb26273cd75738c1163c46d21e300d95cbdf306c3fc3106637416915300a6e25af4ee58f171776382b0cad123817d1e0f713a3b97146079bee0080b9adef4fdd4d28dc0388726e737c93e17955088f7fc970882230b86550de2db295c30eec5064485a2f3bb63340ed582a7e7f1ff43b775d62c86be139e328131a4c3bbf5c3e263de3e498cf81432ae68679517a8417c5521fad73b71d1cc936fad08433f6417a165a0c756d43cf979db29483fb0bcf72716120a42f3e1ef05ce3514350d61d720fb7bc77fd2544b579d5c191c9a8bbc0939a377c34f3cf99dce704d48d435852cd559b7c8601526f24609b33a1123fca00b0f48d9ffeef738c0e1b02bf97d5bd971f808e8b00384ee7df1cc64cb40762c3e467b3574d43be4a7ffe4b9536d69344465823c8f8a91135d77a7d90283f41b1dadf59f40e548c96370c2c10781b0ea7d8e2178d24dd16dced0a7dfe0174465289027f4d2ac15f6ac4d3c9f01202cb3ff33c65803e3c248eac57b44000427ad6b3b56afeac40108138d5fc0f307b2d844891cc202ac27931270dadf09fd9cf012368af535cac44f1d5ea0eaf4dcc0909531c33af2c26967f4edd0131e8cc23a58c49b40d67638bc8f16a3a0152d134c024d57ac94cbd7fa11c6c949bf7e1fd10b1bb36219b6a408ee333033d9ba61239bdeb4268cfad745b5362b4f6a5287560a77c9c345a490116327f9c91430fc7fe3bbf259a0ca0148a961cd7af7822733e671a97ea95ac8a107eb2666395ee674fcf52fc00cca7fe55af97ccc8b8f37b672dbc9aa07c845651dc7cadf873ffd14fcd5187bc68ec354e54c9829148fdf333942aaf663279904426073780629874b50c883faa1d146f137e427d3f863349385c2bc84130e1298d0380c871d7168d668435e55c835f93ac8c7e0cc11bc39014b1c7e91a91f53d0c22467de285150a2d41c7ca63c0e9c775f6dc24952ec0bbcbf51ee8b5f90144082e4193c98447f24d1431b2a8102f7d05a847b70c14e29999759f12fff3a6e3e4fbf2a2d2ffa1ef2206d0a51779321662cf3e6f6e6bace24a072fa28fc612b5020e2fec7f59f6533130f8fc0f61a9e63505c6d7c5b9afe7fa6fbf08ef6e14aa442b35749e00fb5be58c184f5513d23f2254292a791637e61dcc7ddb1c56bc6c0a24055d457e18fca8ef858df9f266f29eed34b8e8807eaa4a8f300d13cf0c62d3d750387a2a1db4b581e74a8db21efddcfda02674dacf7782e9bfa41b44c7a8c4fe7e01cace790920d83890ee6c69be28caa96689d7024bab845ed30a3988fbe6559cc2a8198126e9a5312805f2d3e71476fea91b720c0b489742e7c1f68a9112be6dcecbdd7757d9617c5059f1390576f4c7b2ef1a841b34d16e7a6b5bb0c3f207a07164c4c2b403677298c58215e24594ecdc809ecc45e74f4cd903bddb150897ed5b35600c2034e50121b676061d424513fc0c9712258ba28aab181a60fc8ba3dfe0fae2f89d2514fca2051e8d11a72ab234b17a08186972d7429f9578acd42c33d709a5f1ab86b920385da2ae56732d02a2d347ab9528e56b8821300741e369b9848e230d10d395b1cb8bbc52fa3a9b2dbca1f3313e4593af53f898482da8e474dd83e93b5144fd947edc70beaf39a2020f38483c4a577e54d86ae5d2c506fb596a473bd2a55e57a6d80cca751fb40473b28ad5bf97d4a69f13d59a3e23ac13a90f9d13d91f1650f7c4fa8fd7f0870f40153bed9b20c30a659f1eeec8a4056bac9c5d894a1a563ecd8526cadf46881c20ddc1339f51f9f58d7514e7ea375d9e2b382b2635ca2e946b5bdaa1e5a57a4e123e09f325ef91ec26af421c0f702d154063af5c67f44f58d46a669028720cbe06f9b4aba92643d09ee7119835bd6f3610cf5e7c3256d2423adb72ef56f3ca0ab50da68bb2821a74cc85b2e913642f888c6793b7c87a1e7759c665584fd31c30cf1828ae3ff84a66af52345b105fd45771cb84c3838bc25cbfc21a5aebd7dbdac11908ac44be0c4958e2c6e5b232d9284b2aaa5307167dd92e0444d281fb086a1c18db86810982adf435d4119c6ea14d9381f6166b7fe753e2b4f00e4da138c22a16ee0e462e16ce6dce682c07a392a223d89c95ea0a92a1987537c1a611245d6ba596f47ecbc25f840870ad05bf0056d0f8f913b17f2434c9c46471c924ce2ce17c70016bad12507fe30fdb21389b54690d3951a2fbdc954ba19914fedf5910189aaf599057cf26544a9465b83bcd0a187380fc9e96ba8f67ac81104cd1619795f0eb9bd639bbe2a0f226641b3591f8d17102a2bea4e94ed83862dab552008f41779ebcbe27cd9a1844fa240d796ad2233d6da4a9171f7e2e43d1287a40bc67f07016347cbe2e230e2cab816678d44f4e225aac992abb8b5592060521b69663d2f9049cb4fbcd8cb7915e741715ce00916ecf3ae9bbd99d6bfb1869691017ba4b71629bb533b22d467bd9ec32bafc7f3f58c23122ce652901bdb7222ef232be8178fd1d9000d5fa5c86a2fbe9839368ea0242e3761921d88e64922cb33f58cd44639b43c8b0e011ecf571dda0825dc924fcde21ed5c02efe1a2c5d53b506771082ee8f92072843d496cd3dfd74af7a99a6ed5836a075cec4c8b30d20296bb2e8e980f69ece72aac3aef92cc80d5cd542715acf2784be4b8a3a4a3246400e683ca49d7b57a0b701dcb1194047eec845d9d39ed36e2825f92f6c3cf72f65d9e31dc275bb093e2d4f87f13c5db49e1744083908b2d2fcf95aa7e98189cbcbfcf9b7521a42f085cd4fa1a0e64193124ea44ac02adc77690807a13f3c77a0e0aa43b9ea05a383214813e2b7caa98186a9975b7597561a80ec4a5913ee08bc3ad354625e7a91fbcd94e8b3f3183da12bece487b2da412b02be8184f7e6d3d84c9e050871746095ce202af7e2157aeddca6b1bd119c0f040b8a9337e80aaf6868d1796d8b4fc3f4224690d606d9892c5a6562ffa3db974d4502f3386fc8818a6644d995eb79a37c14c726c1a17da414d8f79ff980757bf97be007c921f7645d9f9547061167c366fe912b96fc8a6b6c35fd9cf50ac993ab2e9e7b4add7295f8cd887eacecbe20009d1963125924cbad07e3908080464a8e524713f731989639800e97042a040e1880ea877e038b9aedb18487a4d451fec73c2a0b728432041212ff283636ade37b82fec8e80f0548f7550a992764f59daca95315329caaca94c037098c9d5afe1e15d841e657fe70f586250a73428a7c34ee54e568858ecbc892dcd987f4fb52a638fb50787cfb3f410cb6f8b87954a8767fb4d40696714182a16a00679b9820b2192d0dca6c51fd2d29dd5c9d9cd1bee2d3032c0f438a62585b134e4216176e5e3444c1437c62e66031a44925ad043e35c84302c207850ca17c8963b477eba877072b53cd67ff84eea71d2a741d7bdb4e4b96f5936a29dae3776da2c40727d081d83f571ad905ce12c48001c5b82be984b7dbaaf7981771aaa907ffd3f0583168f52aef6c7ccb0d3724b925c6f9d5d3e26cb4ecd2ae5f8a86514684cdd022ef504787b64f77ea02d90071f587fa0068a953ffdb7869534de9e14cf1d28ca3b0278f387c571eeb952d5576d29bf9913d43ba6c126812ac9ca96c028807a80bcab2ebf5be219cd116e37552cb573ea31cb3ef775d73cf30cbe0ef4ceb533cb4ac44beb67be09d49475b9da09adc2467bea3180e2cf07b01391e70ab79d9ff81417b2fba218e0ef890015ce02e09df33809bfa7ffddfd1bf9b0775cc706ee246e59c66323113482f3eed82059cec425642d3ec260e4454a8635b55c2c7c850f0fc9ed89b2200137d766892c313885895fc11d61cee8ca8bb8983b1a266d844668190967f50971d09977818ec703e284160be3a1e73b5f38ca3c1c1e35274168e3707df5460d8b8ff3ada3fe587df545b97ced8f6dd6bbdc5dd0a6191a3e4596289a462873fc844c166f1aa9c24edbacf9bb2609c8cba0c0d1a8eccef29ebe55cacb82fa98240005309f852fb3ab6ecb93a7050f89cf6bb7f2126c88890b220821702b6b67beb04070d4573d7795e51a331820feb1c87891e771c1c2ab68e3706c979a523dc878314d87c1bf72d28453b98804148aebcdb1ba873aabe770ab36edd51d1a2d14ec49df903dd72e187ac6ce3def0ab3eccac91664162fe4fc424316d2afdd091da27a831412974c98d4971a040b4c2fa86c0db70f886f9319c9eed386444c86430a8e2509a96937b4ff65b324667fbfddb0e0e858aee8dac6bbdd74368b865a10985ca55d36c78e49a0b96278df03622d59aa3f3eea6c5703224a9240c896096d86a1007a0922cedaef58a68066f67735778fb8a6496fbc2408c6cd52cb01ff86436ba6e48a2a01b21b972573605d979654c9eaa21fab3d863000edff243db8848fbbfbff396d59b31103d4057a61a0e0933fa6394ee1cad6825fa25f322fd6584b78c76655c9e79f32eb22cf7c1650a02dd0666004c41f360b89ee442205202d5bf35a12b049a49725c3aa10dff342f962356ec42c473e0e9f796a4065d0a911d329b3d91de9fefcb28fd0b4306e0ad02080c795186a93de2fe2142905360b4ab3d2af102bace7b77d46d7b5e606e2fe8d830a4c91e35482325528746551f3b6b0f642804a345f8103a00558c5e01cda8d76f64d5633ce7316aa659283726aab644cb5017f4873f82c6c9f54ce6cbcdd261067b5ee61f27caa474fad5138970659c1da6370db8f412e30d3d7982d198e0e8dd17c39d8ef4ed958bb61e9af2c4cad75ccfa1d58891ee3a893bd72aa994cbb24f92af32971b61df932aa048ea0a9420fe3683dab8eb715850419768410ed79b441c8a0494be42f4dde5b270aa545ceffe82443202cc681e0b78f8ba8f251ae0839ba726460803afb0208e249b1eb75909daa690c037d2a813784ca9b476d9941c0168dcf843eaddc0da406107c6228ccee04f032e0db41a7ce70e6652ca018e7484b288c1661a1c9c1de5b277afdbb75ab6f9fa8a09752abded74dfb6a9fb4d22f4075be9d2e7c31799d57963ea320017838dfb538a7216fafaeb51cedb3000c05404133ace8de1d69a1df0f309dfe21d52133400c403fd0bd8a5009899191d4b4bbdd3fc02b053f46659cf022f1220005014804559400b5c2f652a003ef480c4f70976b1e8313ef03207faa2c2bb349f005a015e8abd643e02a6146866c0f5038a084874002650f5a3518d331c55cb006a561d3945606a49d2010cf00cf00cf00cf0e4ddf95c0f48433a548730b1efffb66dcab6d651d54699a49c16bcc1900c22c8979d70534a32c99492500040800c7cf12ff52bc3053d41285b035e03710348f572cc9a56b34e7d5a3ba0452d7d7fb8a0aede65473c3c0a92100f2f2119f905e4413838da0f933ae0840e885bb142beb3349a6da698cc61e5707140a7bb52d9f15aa99b05077446251ab38eec7a527a0332a3cc5bfb2637a0e5f4ab9aa7ffd9bdb401ad4bb6df689cd739173620a574518d68e8875df51a90c14df6a97bb7b839ad06b414cfd53a37e9ea16a701b54a2653a75f9c96548c0684ca9c326b2dca671963ce80546f3a2b734233204437b78ea1f93a369601a5e3cbfdc14ffbc6cf9101293fb5faace336069494fa45e1f76fe73213035acc41c53e834b1d3b596312860503326f307d4acf6a51ebfd0b68c1f566ccde3eb1df4dbc8096365c3768f85d40ba2cf8c99732b5cb555c409ef03d199feca5b7d31670cfda279569470be8985a3ead7a655b689805bb0916ecae809c55fd1b557da9b66505946ca6b8fa2cc75ca2aa024aab57263726f38de24405f4787c3a9d99672f5ea680922ecbbe52d9dec92c97024a07d5edb2cb1b33c9390a28771d36e75bbcfc4a0b05b44a2d4b231ef71f1ef304f469f962be6894a63be8047406931fa7329f225f13d02af3b4f79c7bbbac4263c204d4a6eef1ec6e2da7dbbc049496a5bee1a22901b5af336efe7051ee629204d4ad6292464e9070266820c5b7ba60de9fa7fdd1627204d406d5e2fe75cc1f8ba3607206cab5ec5cf23fcb966a6aec1928264640b867f92f5fda1cb2ae1fb181133390aa19d64b69dda48c0919e8a81e5e9bcba5dde6998c81525a10fd16cf2e99582762a065f99793d8289aeba2c2402b69a6ef4b4675652a02da65b655231bef2d4d4440bb2cb5e5e6cf9321a0c37c7d0a939af4b8ec440867d89cc7b22fb371510fdf9804a12740e8c90f50ea4b47d7729e9c81890fd0daf779ffb4cbdab7ef01f2f4ba70a633ffba977880306f71538bc15789690163f2057a3ce72cfdc39ffcac7780dc8e579f33a35ea0738db9985ef7bfa67480de64aa9bd585dcbc982e10665249555f4dd5262e1062aac53f667ad7bacc014a5398a613179fa9411ca0cd4ed8cec6a46599bec90dd02eaefef0c268709dc5263640b620af65c75c6bf6bc1e2e2635303440bee0a352e6ec73259f015a5e6d7239f732409a1899976d1944356a1203b4742536ebd31ae2199cc040514d73cf9c549cbce0262e40cb132fea6b693a78fa4c5a80f650cfb73109490267496814b60d90443902625ed024e37cdb6a8c52ce409676d2d24c871423a03fbe9c55ea2c6e067ab443d5677d9ad2329432502febd6fff01dab341432f4ec385b55cfb42ad54ebef8f974ae9431902f8b7e5fa752b9cb2a8a18c87fe165759f1b763426014a1868a54fe97c71a2dd325904a4aafdab29ad5f7d914284d4630e9bdbbbdb3e643bb896b9a4259fd33a65081421a0de5eca8e4d390868d9749e5b541a05084615fbd8eda6ae6680f203c4c97c2d6c8e2b1f1ca50748f5b2f0f55df2ff1d850747010329b5723f7d4f7e81328ff15e7f9221734a9d0565076e38a54c7e9662a5b9bc7065b4953f19141da0e52c8c6a502dd4554d5da0cc5ca72dcfb36a2e4ee102255b769d5ee6142fbd8a92036416db53c3a6dccd2f8303a4bdba36a9e3a310996f80967aa75e6e8d694e77a6a0d8002de65be5e1ea37775bf02835c8533e7b8a7ccea09d83476d5068808cb2f6d7a3fabb973b79066506a8d582dd2a4f9b366d2e03b4f2d64968d2edcf9d2931404a71b3f9a953e8bb161820b3fe152e371fca0bb0a0b8e0dc2d1fc46eee637366cf494169014a67fa12ead9620165058817a6a5b4672d45054771b99dbbfa33650bc45c0ad78c4ae9cf16142d5052b6a8f1f3b426794a942c905a83c9fca5d665e9cd1a7b211e200905250548f15a2fe84bb2beb4dc20e9502fc5e412481dc387172af746b38462dac3435e978b9540bcffc6d3abbbf351acb137a104325c76d4d7f93a67a999047ab5b0ae53d4756e3acce91d0e4c24815cd3b256e559d451fa68d2984402f9313a37a7a517172a15211eea4384022150e0041208137feaa28e9a47a023d6aae54ff2be1a7b5f856f808383a4118db6c62efb1835d3bd93ca08a41cd5d2d7bba4514d5fc4de5a6eab2977f2a269fd4c8b57f3c21345a04e29995d6b6dd72c69268940786dc86ea8cad6f905110895bff299d5c7965beecc617208b494cff2c92463cecb2e8640aff8e6bd96c5a4fad44921502a755ed7109d10c8ccd9fd7d579e350f029dd1fb63d799e8c65a10a81753dd27d7629340a0e36ff77c773897999d00022566f477e77635fa39f9034a4e5d44337cc6fda8891fd079ee638c975d706933933ea0349d0cb6af4d7c409ab7866711d9e4b233d9035a0bda7a6d74d5af6ae901adbd2d74c73379404b5a759e17e475ee9499e001ad741817a2525fb567933b04a8cf2895d457195b3eb3c66a49c85bf196407ccc1b53ed32af29299540f8c989fc46a5c3f5cac2a050e218bd357c69877ca737f3fa31d9ee38e2db4d022d2a3c271732777424042892406b3186989829b95dea20f1e8e8c01319490ed2d1a1b791388e830472f36dabd559beead31e9447a023d01dd7fdb54c8637ede2416904e25cfe99541aa7746b1881d4fbff39b5dc627ed517b1762d4faa08c4294f1d2e99cc26635312814e9bcbbb3dbb6719e31b8840b9d4f5e952feee36abb1c7781e8740895d5fa5c5551f66121159ce102873595c4b9bcc8d42a03dfe988bdaa9c273eb41089496dd83bf169e3a52a101110b70705021f2c1c131066510e896560a974988abc3aec483c4630a551fefb1f4011441a055e764ca93efb8f297ac410904530081b615ae99e3d2bc5f8cf207e4be26f5a736bc5e3d51fc806c79bcec65cf8e3997fa804e29e22e5c4a7571753ea0b416b328650fc811d7d7b899758adf50f480105bbda93999fa8e32943c1c050f7c073c1050ec80ce276ba58bdf2e3e7c287568ccb3c78927edd8704fff9dc3c6342a6715020a1dd0217abf71e3a830a5340774dc739797858b1c50526af9cd62eaee4d1e07e497d6c26dea7bfb8fc3012d6deb9371577ed36340286f40e9c9a05ad0cef1ab85dd805a99e59f12e31ae3c76d40b9e86267b89645ed6ed9804c42f36729b65d034a939ad7f64a5b098daa016d1febd3ee734aa5c63420d4b556a5c598c54d8d6840ca57dae552d2eded73674069e9e5def56ebc4921c50c5986e3a800850ce82c76bcfdd4a2f80cbf032863408a18902eb39ef6534a1850eed9f59eaef7ce280506846af95b94f5a7b64ccb17d062cd76d4a832a61a7b01255ecb72683cbb804e339de45dc6f5932d1750725e6e33dd967295ce1690415cb7dc1bba22626ac1532f5ff39e785940a7d5f9cd0e1b1690dafbe4bc95e7cc2e47ca1590f2ca3d9e8d8a0babbfc0ff043cc4413afe175a01a9c757be3d2975fc885791442c50c515942a24f39ef98b5f2d692153610aa99997d6714b55f3018a14a2d0788eade9a3c72023b3690a1490351f9a94d86bef6935b3407942529c8094176dd749cbf5a97913d031e9aca6ea51ffb687041fa2548cfc05129f8a9107b1a3306109cba973fb658ba758ffc3c68d1213509480d4b2dc7c3abb64dbda2420b3e9265732a3165d87d3400b7a267569b1a120018dd36659d9ae778d0f6f1b53d90114634fb43d61d09001e72a592fff130618c731c72d209590dc18194013582c4fa83f401324234e3c80c4c3861fa089329868e289276a20c0034c90902c1c02008097b8e21840135ee24a6484a3004c0440014e88d8e8a8227909494707029868220105406b93496787dd35f6b2808e2633cb47ed9cb5da0a0f75c39484783ca082850254ae6003c40aa8fddcbebd1d35b3caab80bc9359f6c7ce7dd9221590d25e473db32e9d963b900759900799026a4c479372855963ef803c8856a4901e255678a828a0850ff677af4975cb523596f16ee081fc4887151eaa43a5b741d297810a14b0b0919ee044c509c8f5d8518dc81f3740d2019526385161427a942811901b1d1d4e549650f224200f42808a12509b5f8bd75a8cbbd91da941f29104a4ecd62435a9ae8ea747c9470d12efe8682d09f14892108f9291bd34d2a3e4e306484787012a4840c7d69d85dfcc1534902fbe9b4ed25f060d5793d810e9b0ca11906e662a4ec45c8e4bddf5a99c6176c6cc12d5d89a2e41c508a66206ca5e169549995cb86a315054ca4087ff6ed3e4c97fe74506d2b32cb53853fd1f7a8e8170515766378b295d4691511103d92f8bb3df4a1e1f241d1f1e22e25162430433059530d0aa655ba5b5be13173d35f63acc85548af04751ad3765f02b4440e6ece1e46bd9e6497d24fe612782ca10d0a2562deb8fea53ebd0685111023a79567fea6549930eaf1120332a414087b6affcac7d539fde0a122020b3c78e2fceead8a865490d2a3c464ca4f20394bad33fa95bab0a558e547c80d462a7f8ccf625951ea06ba3b0d72e9e4809498788b9abf000f5b1dfe9b27751482181282cd080f45360808363025234e0fa3f96c523e454c040ec29295375cbf3323c3542fc238a572221262b5fb4092a3ba878712e39bdd29369291c49211e1b812afc3bb027a8e8002ddd4bbf2d8753a58b65e5517cc9ed2c93bdf2112a42ae10f9a870818e5aa836a9aabb4c87951ca4e619e5336dee64a6a9ae3cacca471d476594a9e0005ddad4366d964a477d312a37e8e4ab57aba4da89046fbca8d820bd1b95292d76fcec3b18951aa8a55726c4b470ad85e40a918f0f91be312a34c063873fd3fa2f2a3340d9bc89c6c7c78e711615191cec7e370b07475762b0979636be185342e229e40a1105833f471ddee56e122f785f7981dba667838bb870897ca40e2c464a5c70bed7e6e0d2ed9516f09a7ddeeabea3d886a50a0b34efd262f533b55c95157c5ab9f8a699a3a2a28263acb54fad4f232e655ecbce8e8e1011430dd88dd88154b640e7563aeb738ffe4127f92d2a5aa0ef7e5e7894b6fbb15e250ba4d7cb1c55ed93dd7d2a2940e999ebfc71cd97b6dc4b0483aca8426b8cc3943106114288218635690c9209120461418ca3308e03a1b5d803126030398ae4300e821886811885083184104208218410420831c492b26e30288912cac239351ad1dcf9bc97469b7fc2a209d041d71772cf4031a2bb923ce58245c05748137169d6f1ac8a45d81363687fd25308468e8703c6d2f8a926256991883a6c8f6c3c8e24da015fb16974e4c91e64789058d8778778c8a8db84f1acc4a21178afa2d4d2d3edac09aa4f9557283a8b731c740db0e4339063359c4988d34c90660e14ae1e296f146a01a0e664db62a28c830831f8edab2294dc43051e05690e1d9056ec0ce19f55d034cc0b9ece95fdfde7ec668eae1dbb6ad0acfb0652944af3e6d4a74e76cf02e305834b90e6d6092a78290135941e3d0f46b5d1470bc5ecde6bcd099ca21ea1e30a95ccc1ba58d0a43f1e22e5d4081e9c765d49b5426499e530fcb59add2a41547f56919e421c9ad2c605b89553b0cc188aa40b190e992f95f925b799f75ab8c5d6e0d5405085e141ab3347e17d40e0c25819291015b45e38c6a267b7c7b229865dc43cf127b43737b2c0e3508ac9e1a4938320315c4ec10e4ca436109c44423d0ec06e897bd2db9679c8ae6feb81b073ff4bb65a363e0338a80bf7a21b5354ebb9c355fd04e59ac8e99841c0a8123cf3b614cc8ce409533e324998fb79537fe85c03f617b66405cec53d2a629faa5ec349c5541a72c506b8e8bca52b226aec211b1cd639c5aba8cdd4d0118605d50ed9b5cb2cbbecd4a70297d7e5116e19e25c19fa7632d591caede47bc912b6047bf7bbeb68eb56961bb7ea8e74009fd5471a1306b860b769bc5e04e397627935ffa2d1aac53e5daa96f0d4e346928dd95679ad4c98735383f9c3588795db33de86164be5cd3bffa397514883b38c47117a1f63a0e795155d4d79f303e728515ca695850f2003d1e9c4d275c8e000c1d530ba71e7de8c004709d578640232f422985ec5bcd044ccf93f26c7d3d412c161169229a6581246233229d1e5449dc7cdbe0fb5e4b037dd02d53de108905379fd9b4b95befb14af67fcc643c9436fb5f376f398c6c1748a37c93febba81a4ed84279a51b24811743cc1fcac6f2ea67941adb9994ff253b703306e2a3333ed73ae07ece266536a3feb9b76e1604b4c19a704b36636bd15b15acc1071472d3138675a939e1e33f0542e76fc899a357f371ed31c93525bb076a5e27cd8347bf6700f05da5dff368e4bd5f64dfb147e1455ecf85bbd85c71178f3c7f244865ae894e3d34037ef68e0c6da17d2b2111f7698543d91501aaa790a21aedefe1b75323208d114dc40296c5b4cd000aa0d572959d0d4aee3b3d8e65ceedb5872a5872f65c8dfedd42b41b33a1adfdf592a41931b8d30ce90b0d199391aa76f2efe8fc9034451d2f9ff4b18be89f9f08bfd4c3c4b5c09c67c82a7a2fe8c1e281b2a8add1ce5a6680cceed0643935f543444e02975fa4326b2a3750901c3a90d9fd79020674b27534583bcc1aeba4d1a372fd461272317099aa063c2c839da460dc58d38ef804f48b61304f2c0a5220e423972ec76a06f996ebfb8216b56bcf53fd6f274da18c7bc5e284c712d01ac699eeb52916077eb08ab910f7c522fe05ae669d1762f217583d60c360cb9ba4f2a24890748d6181d3d9c19d10129b8a3dcf30cf54fea895678ed6c6e5520bdb981abac541f6a8bcf80c7e515ccef68a48525a08b6929ed6834313dcdb61bfe6f416c7552064d278d825274265a15a0fa47676f2f7d2d09c65795ad619d55bd1eaf79c7d7c779ab2e213a3960d4ff3e779644c8d86288f565202d7de35ead569db89710f1d8e10c37b6f3d82fa11c56eb737c6979936e363ee201af4192d78d2abfd4f3b0d488de8daee09de0afb8f9557756a9b44904466f89d5db4c826cf7fa6bae583eb436a8b14fdc055e988ee074f8effe71c46f303fd68781c14a53da73a319d1d81823791f90b85d78c492135a2ae41cd0fad2e8d5c325f5c0e4febee3dc9f446a863df256983ef3201753009e9f10666269270d8b8e50eca5a34679a67be0e3f0143706b44e323bd626ada057f0bf53a6615bc6dbccf1f185548bc1ca6eab3ab6e95341c2f30d196a549f5b05d3f63373eb2304f00663f9ae9dcc07f4459b090ff76f4b253d321d0cd7537b3dea05e600891310340357aa8cea1e2885c620ecc5182fdbc4d61b020832bb21d66631f0419591e88120da0b3970bea63f9c6c947cb4987089251903b79fbdc94a29a6a7ac526877c5176548670e955074c2e3d165cf77d6b1dcd68af8cea834a64cc1328adb6dee63a85ad287a16399772fa00bf467005274123b2cefd97e1e01913ca0c2c74cc3e4d88840f669e039622124c595680758321e7144c9a200320844c22fae45ebcd6923026b095b7b10f13b05d526a2880fe3725598ec44fa2b064bc603433bb5f9a8c3f0c9f48418c80a45cb84c932661f97417df85bdef215086ee21210e7c021fd48e8e58ec776fd88abc59d483d4776157fa1ba7f7f668d301a02a551a2056cf56fb2ce0c5c3732080141c49e1286adadcdd94ccfed2482c5635399a8ad9751777668e31547d015ac5fbb0bb10d029640b34707c6e872bcfb66150934b10d454f47fdfac51e916fa4b8111e8c66da35df72a479b49fef8a2224a7a60fc9a960081c8b89d739f8e989100b4f067d07792b6d6229816518f3c078101f874474fe69a5ccaf767ac0b3a7c5e50fabf06659213a4de037a5ef8232b372a42e12a5836062e69befee58412dcc86e6ccd8d1698c4721627e9f76304b18805f7e15a3c15e946d000370ae02c145ea8d90fbcc9979ff47ff3f829274bdd9ab441b4f1f5ff2294eaafe08726d7b5974f4f6f1f8800861f2a0c2be032c43577aefd76d65478f82effd06f935d4ebe1597ddfe6e569d19af53ba95e55fbec7f0d677257c51615fe7a65286ad9aa8614ca97770d6375f3c2528efe1206aa94fae6eb959d893d8f504efd811ca19393ca3327ffb7f22a0311cfbc4c1fca529ffe29c80996a3134a36225e411dded313281111af3d87d1a40c928123f75cc0b9a399fdabc342fa3e4049ecda6832affab1878368160f15b468a73a5ebd93ca1ba3963fc85c0f58eefbe1fc83af4d5eaf0eaa0a39c7ef6ca7605fb7be19a359935578d9e9cd0139525f498362b290c35603642b150cd5bc01b12239345534c2872ceb019f81466484a5f7a3dc54b7a37da1ff82060e7f028e9f2f4689748e9b6074dc157978d5a32136e404b1860d4f2766f768a52a4471ffac90a78685714421de4ef915990b104a5df56b551aa9fd9559f36ec3a282bc5c83eaba6aa87aa90f6c61640a91585eddba6ba7059148708347c75dff33c67fc7c374658ad5b68836c476bc7c6963a3b216f9156288b179900d90c7a3249832ecc36902f5a41e755d4bab7ff5ae7ad24b4aed5c3917bfcfd300ef31dce8dd8dae7a9971efd214d4fe80b3edfbc3748733b2ecb762a69f7cb3f3902b5c18267c794883f3ab6ba16ee98ca1caf227337c7a19ecd9d973003171704bb351955e7fe1e4efa1eb9af8b87435fbc1c80f137e037da2333bf9954c75ac6f76459d02fd25f30c6c58a6a46374001800eeff22cbf97cd41b69973161cb77ef39cade29c8ba37c243b37469cc3da6066d183c204412101160b1d034091a916a730dba528b282bb51b0d2eb1b46cffbfe82eb73180e3dd64bb50d07cca5ac6cbf8427911fd0eafcd06342833cb7ea0d8e52a82997767fdaafacf8cf5f8d703356f8e9e210b1c717f65417627aa9688a2053a74f78d686581fec24a86d02ee0a8a6d5c6e8add5ff9f93be4c297b2763d2bb3412160f39f9225ce129ea2fe8f67e430876153e96ddc2de7a6a6de2d3ff996b660131e7d67d04fdd33634455c3453e739fccc2d2d685c25cbe848054a6a9044e4485471538eaf854e96add4cd11d7683b2ca7332f3ed011bfb4718a23ddc7002b0839066f139a2e1971556a2f8e82e104c110c2d83135e50c", + "patch": { + "balances": { + "balances": [ + [ + "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + 1152921504606846976 + ], + [ + "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty", + 1152921504606846976 + ], + [ + "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y", + 1152921504606846976 + ], + [ + "5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy", + 1152921504606846976 + ], + [ + "5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZcCj68kUMaw", + 1152921504606846976 + ], + [ + "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL", + 1152921504606846976 + ], + [ + "5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY", + 1152921504606846976 + ], + [ + "5HpG9w8EBLe5XCrbczpwq5TSXvedjrBGCwqxK1iQ7qUsSWFc", + 1152921504606846976 + ], + [ + "5Ck5SLSHYac6WFt5UZRSsdJjwmpSZq85fd5TRNAdZQVzEAPT", + 1152921504606846976 + ], + [ + "5HKPmK9GYtE1PSLsS1qiYU9xQ9Si1NcEhdeCq9sw5bqu4ns8", + 1152921504606846976 + ], + [ + "5FCfAonRZgTFrTd9HREEyeJjDpT397KMzizE6T3DvebLFE7n", + 1152921504606846976 + ], + [ + "5CRmqmsiNFExV6VbdmPJViVxrWmkaXXvBrSX8oqBT8R9vmWk", + 1152921504606846976 + ] + ] + }, + "collatorSelection": { + "candidacyBond": 16000000000, + "invulnerables": [ + "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty" + ] + }, + "parachainInfo": { + "parachainId": 1000 + }, + "polkadotXcm": { + "safeXcmVersion": 5 + }, + "session": { + "keys": [ + [ + "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + { + "aura": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" + } + ], + [ + "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty", + "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty", + { + "aura": "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty" + } + ] + ] + }, + "sudo": { + "key": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" + } + } + } + }, + "id": "custom", + "name": "Custom", + "para_id": 1000, + "properties": { + "tokenDecimals": 12, + "tokenSymbol": "UNIT" + }, + "protocolId": null, + "relay_chain": "rococo-local", + "telemetryEndpoints": null +} \ No newline at end of file diff --git a/templates/parachain/zombienet-omni-node.toml b/templates/parachain/zombienet-omni-node.toml index 29e99cfcd4931..2f263f157fcfa 100644 --- a/templates/parachain/zombienet-omni-node.toml +++ b/templates/parachain/zombienet-omni-node.toml @@ -14,7 +14,7 @@ ws_port = 9955 [[parachains]] id = 1000 -chain_spec_path = "" +chain_spec_path = "./dev_chain_spec.json" [parachains.collator] name = "charlie" From db3ff60b5af2a9017cb968a4727835f3d00340f0 Mon Sep 17 00:00:00 2001 From: Ludovic_Domingues Date: Mon, 27 Jan 2025 15:37:00 +0100 Subject: [PATCH 155/169] Migrating polkadot-runtime-common slots benchmarking to v2 (#6614) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #Description Migrated polkadot-runtime-parachains slots benchmarking to the new benchmarking syntax v2. This is part of #6202 --------- Co-authored-by: Giuseppe Re Co-authored-by: seemantaggarwal <32275622+seemantaggarwal@users.noreply.github.com> Co-authored-by: Bastian Köcher --- polkadot/runtime/common/src/slots/mod.rs | 121 ++++++++++++++--------- 1 file changed, 76 insertions(+), 45 deletions(-) diff --git a/polkadot/runtime/common/src/slots/mod.rs b/polkadot/runtime/common/src/slots/mod.rs index 131a75f3d743c..1fbfe451dcbf7 100644 --- a/polkadot/runtime/common/src/slots/mod.rs +++ b/polkadot/runtime/common/src/slots/mod.rs @@ -149,7 +149,7 @@ pub mod pallet { if let Some((lease_period, first_block)) = Self::lease_period_index(n) { // If we're beginning a new lease period then handle that. if first_block { - return Self::manage_lease_period_start(lease_period) + return Self::manage_lease_period_start(lease_period); } } @@ -237,7 +237,7 @@ impl Pallet { let mut parachains = Vec::new(); for (para, mut lease_periods) in Leases::::iter() { if lease_periods.is_empty() { - continue + continue; } // ^^ should never be empty since we would have deleted the entry otherwise. @@ -381,7 +381,7 @@ impl Leaser> for Pallet { // attempt. // // We bail, not giving any lease and leave it for governance to sort out. - return Err(LeaseError::AlreadyLeased) + return Err(LeaseError::AlreadyLeased); } } else if d.len() == i { // Doesn't exist. This is usual. @@ -488,7 +488,7 @@ impl Leaser> for Pallet { for slot in offset..=offset + period_count { if let Some(Some(_)) = leases.get(slot) { // If there exists any lease period, we exit early and return true. - return true + return true; } } @@ -962,7 +962,7 @@ mod benchmarking { use polkadot_runtime_parachains::paras; use sp_runtime::traits::{Bounded, One}; - use frame_benchmarking::{account, benchmarks, whitelisted_caller, BenchmarkError}; + use frame_benchmarking::v2::*; use crate::slots::Pallet as Slots; @@ -998,10 +998,15 @@ mod benchmarking { (para, leaser) } - benchmarks! { - where_clause { where T: paras::Config } + #[benchmarks( + where T: paras::Config, + )] - force_lease { + mod benchmarks { + use super::*; + + #[benchmark] + fn force_lease() -> Result<(), BenchmarkError> { // If there is an offset, we need to be on that block to be able to do lease things. frame_system::Pallet::::set_block_number(T::LeaseOffset::get() + One::one()); let para = ParaId::from(1337); @@ -1012,23 +1017,32 @@ mod benchmarking { let period_count = 3u32.into(); let origin = T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; - }: _(origin, para, leaser.clone(), amount, period_begin, period_count) - verify { - assert_last_event::(Event::::Leased { - para_id: para, - leaser, period_begin, - period_count, - extra_reserved: amount, - total_amount: amount, - }.into()); - } - // Worst case scenario, T on-demand parachains onboard, and C lease holding parachains offboard. - manage_lease_period_start { - // Assume reasonable maximum of 100 paras at any time - let c in 0 .. 100; - let t in 0 .. 100; + #[extrinsic_call] + _(origin as T::RuntimeOrigin, para, leaser.clone(), amount, period_begin, period_count); + + assert_last_event::( + Event::::Leased { + para_id: para, + leaser, + period_begin, + period_count, + extra_reserved: amount, + total_amount: amount, + } + .into(), + ); + Ok(()) + } + + // Worst case scenario, T on-demand parachains onboard, and C lease holding parachains + // offboard. Assume reasonable maximum of 100 paras at any time + #[benchmark] + fn manage_lease_period_start( + c: Linear<0, 100>, + t: Linear<0, 100>, + ) -> Result<(), BenchmarkError> { let period_begin = 1u32.into(); let period_count = 4u32.into(); @@ -1036,9 +1050,7 @@ mod benchmarking { frame_system::Pallet::::set_block_number(T::LeaseOffset::get() + One::one()); // Make T parathreads (on-demand parachains) - let paras_info = (0..t).map(|i| { - register_a_parathread::(i) - }).collect::>(); + let paras_info = (0..t).map(|i| register_a_parathread::(i)).collect::>(); T::Registrar::execute_pending_transitions(); @@ -1053,43 +1065,48 @@ mod benchmarking { T::Registrar::execute_pending_transitions(); // C lease holding parachains are downgrading to on-demand parachains - for i in 200 .. 200 + c { - let (para, leaser) = register_a_parathread::(i); + for i in 200..200 + c { + let (para, _) = register_a_parathread::(i); T::Registrar::make_parachain(para)?; } T::Registrar::execute_pending_transitions(); - for i in 0 .. t { + for i in 0..t { assert!(T::Registrar::is_parathread(ParaId::from(i))); } - for i in 200 .. 200 + c { + for i in 200..200 + c { assert!(T::Registrar::is_parachain(ParaId::from(i))); } - }: { - Slots::::manage_lease_period_start(period_begin); - } verify { + #[block] + { + let _ = Slots::::manage_lease_period_start(period_begin); + } + // All paras should have switched. T::Registrar::execute_pending_transitions(); - for i in 0 .. t { + for i in 0..t { assert!(T::Registrar::is_parachain(ParaId::from(i))); } - for i in 200 .. 200 + c { + for i in 200..200 + c { assert!(T::Registrar::is_parathread(ParaId::from(i))); } + + Ok(()) } // Assume that at most 8 people have deposits for leases on a parachain. // This would cover at least 4 years of leases in the worst case scenario. - clear_all_leases { + #[benchmark] + fn clear_all_leases() -> Result<(), BenchmarkError> { let max_people = 8; let (para, _) = register_a_parathread::(1); // If there is an offset, we need to be on that block to be able to do lease things. frame_system::Pallet::::set_block_number(T::LeaseOffset::get() + One::one()); - for i in 0 .. max_people { + for i in 0..max_people { let leaser = account("lease_deposit", i, 0); let amount = T::Currency::minimum_balance(); T::Currency::make_free_balance_be(&leaser, BalanceOf::::max_value()); @@ -1102,31 +1119,45 @@ mod benchmarking { Slots::::force_lease(origin, para, leaser, amount, period_begin, period_count)?; } - for i in 0 .. max_people { + for i in 0..max_people { let leaser = account("lease_deposit", i, 0); assert_eq!(T::Currency::reserved_balance(&leaser), T::Currency::minimum_balance()); } let origin = T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; - }: _(origin, para) - verify { - for i in 0 .. max_people { + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, para); + + for i in 0..max_people { let leaser = account("lease_deposit", i, 0); assert_eq!(T::Currency::reserved_balance(&leaser), 0u32.into()); } + + Ok(()) } - trigger_onboard { + #[benchmark] + fn trigger_onboard() -> Result<(), BenchmarkError> { // get a parachain into a bad state where they did not onboard let (para, _) = register_a_parathread::(1); - Leases::::insert(para, vec![Some((account::("lease_insert", 0, 0), BalanceOf::::default()))]); + Leases::::insert( + para, + vec![Some(( + account::("lease_insert", 0, 0), + BalanceOf::::default(), + ))], + ); assert!(T::Registrar::is_parathread(para)); let caller = whitelisted_caller(); - }: _(RawOrigin::Signed(caller), para) - verify { + + #[extrinsic_call] + _(RawOrigin::Signed(caller), para); + T::Registrar::execute_pending_transitions(); assert!(T::Registrar::is_parachain(para)); + Ok(()) } impl_benchmark_test_suite!( From b30aa3193048d6bbdf21408bd0cc4503010fe3f8 Mon Sep 17 00:00:00 2001 From: Ron Date: Tue, 28 Jan 2025 01:31:05 +0800 Subject: [PATCH 156/169] xcm: fix for DenyThenTry Barrier (#7169) Resolves (partially): https://github.com/paritytech/polkadot-sdk/issues/7148 (see _Problem 1 - `ShouldExecute` tuple implementation and `Deny` filter tuple_) This PR changes the behavior of `DenyThenTry` from the pattern `DenyIfAllMatch` to `DenyIfAnyMatch` for the tuple. I would expect the latter is the right behavior so make the fix in place, but we can also add a dedicated Impl with the legacy one untouched. ## TODO - [x] add unit-test for `DenyReserveTransferToRelayChain` - [x] add test and investigate/check `DenyThenTry` as discussed [here](https://github.com/paritytech/polkadot-sdk/pull/6838#discussion_r1914553990) and update documentation if needed --------- Co-authored-by: Branislav Kontur Co-authored-by: Francisco Aguirre Co-authored-by: command-bot <> Co-authored-by: Clara van Staden Co-authored-by: Adrian Catangiu --- Cargo.lock | 2 + .../runtimes/bridge-hubs/common/Cargo.toml | 6 + .../bridge-hubs/common/src/barriers.rs | 57 +++++ .../runtimes/bridge-hubs/common/src/lib.rs | 2 + .../bridge-hubs/common/tests/tests.rs | 85 +++++++ polkadot/xcm/xcm-builder/src/barriers.rs | 14 +- .../xcm/xcm-builder/src/tests/barriers.rs | 232 ++++++++++++++++++ polkadot/xcm/xcm-builder/src/tests/mock.rs | 7 +- polkadot/xcm/xcm-executor/src/traits/mod.rs | 2 +- .../xcm-executor/src/traits/should_execute.rs | 64 +++++ prdoc/pr_7169.prdoc | 14 ++ 11 files changed, 473 insertions(+), 12 deletions(-) create mode 100644 cumulus/parachains/runtimes/bridge-hubs/common/src/barriers.rs create mode 100644 cumulus/parachains/runtimes/bridge-hubs/common/tests/tests.rs create mode 100644 prdoc/pr_7169.prdoc diff --git a/Cargo.lock b/Cargo.lock index 64c68d81d42b4..d04808c0f0896 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2595,6 +2595,8 @@ dependencies = [ "sp-runtime 31.0.1", "sp-std 14.0.0", "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", + "staging-xcm-executor 7.0.0", ] [[package]] diff --git a/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml index 2fbb96d751635..9c5c7c5139096 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml @@ -19,6 +19,8 @@ sp-core = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } xcm = { workspace = true } +xcm-builder = { workspace = true } +xcm-executor = { workspace = true } [features] default = ["std"] @@ -32,6 +34,8 @@ std = [ "sp-core/std", "sp-runtime/std", "sp-std/std", + "xcm-builder/std", + "xcm-executor/std", "xcm/std", ] @@ -41,5 +45,7 @@ runtime-benchmarks = [ "pallet-message-queue/runtime-benchmarks", "snowbridge-core/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", "xcm/runtime-benchmarks", ] diff --git a/cumulus/parachains/runtimes/bridge-hubs/common/src/barriers.rs b/cumulus/parachains/runtimes/bridge-hubs/common/src/barriers.rs new file mode 100644 index 0000000000000..6b5dee3de3842 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/common/src/barriers.rs @@ -0,0 +1,57 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use core::{marker::PhantomData, ops::ControlFlow}; +use cumulus_primitives_core::Weight; +use frame_support::traits::{Contains, ProcessMessageError}; +use xcm::prelude::{ExportMessage, Instruction, Location, NetworkId}; + +use xcm_builder::{CreateMatcher, MatchXcm}; +use xcm_executor::traits::{DenyExecution, Properties}; + +/// Deny execution if the message contains instruction `ExportMessage` with +/// a. origin is contained in `FromOrigin` (i.e.`FromOrigin::Contains(origin)`) +/// b. network is contained in `ToGlobalConsensus`, (i.e. `ToGlobalConsensus::contains(network)`) +pub struct DenyExportMessageFrom( + PhantomData<(FromOrigin, ToGlobalConsensus)>, +); + +impl DenyExecution + for DenyExportMessageFrom +where + FromOrigin: Contains, + ToGlobalConsensus: Contains, +{ + fn deny_execution( + origin: &Location, + message: &mut [Instruction], + _max_weight: Weight, + _properties: &mut Properties, + ) -> Result<(), ProcessMessageError> { + // This barrier only cares about messages with `origin` matching `FromOrigin`. + if !FromOrigin::contains(origin) { + return Ok(()) + } + message.matcher().match_next_inst_while( + |_| true, + |inst| match inst { + ExportMessage { network, .. } if ToGlobalConsensus::contains(network) => + Err(ProcessMessageError::Unsupported), + _ => Ok(ControlFlow::Continue(())), + }, + )?; + Ok(()) + } +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/common/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/common/src/lib.rs index b806b8cdb22db..dbd87bea2142f 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/common/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/common/src/lib.rs @@ -14,9 +14,11 @@ // limitations under the License. #![cfg_attr(not(feature = "std"), no_std)] +pub mod barriers; pub mod digest_item; pub mod message_queue; pub mod xcm_version; +pub use barriers::DenyExportMessageFrom; pub use digest_item::CustomDigestItem; pub use message_queue::{AggregateMessageOrigin, BridgeHubMessageRouter}; diff --git a/cumulus/parachains/runtimes/bridge-hubs/common/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/common/tests/tests.rs new file mode 100644 index 0000000000000..84c135728d5dc --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/common/tests/tests.rs @@ -0,0 +1,85 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +#![cfg(test)] +use bridge_hub_common::DenyExportMessageFrom; +use frame_support::{ + parameter_types, + traits::{Equals, EverythingBut, ProcessMessageError::Unsupported}, +}; +use xcm::prelude::{ + AliasOrigin, ByGenesis, ExportMessage, Here, Instruction, Location, NetworkId, Parachain, + Weight, +}; +use xcm_executor::traits::{DenyExecution, Properties}; + +#[test] +fn test_deny_export_message_from() { + parameter_types! { + pub Source1: Location = Location::new(1, Parachain(1)); + pub Source2: Location = Location::new(1, Parachain(2)); + pub Remote1: NetworkId = ByGenesis([1;32]); + pub Remote2: NetworkId = ByGenesis([2;32]); + } + + // Deny ExportMessage when both of the conditions met: + // 1: source != Source1 + // 2: network == Remote1 + pub type Denied = DenyExportMessageFrom>, Equals>; + + let assert_deny_execution = |mut xcm: Vec>, origin, expected_result| { + assert_eq!( + Denied::deny_execution( + &origin, + &mut xcm, + Weight::zero(), + &mut Properties { weight_credit: Weight::zero(), message_id: None } + ), + expected_result + ); + }; + + // A message without an `ExportMessage` should pass + assert_deny_execution(vec![AliasOrigin(Here.into())], Source1::get(), Ok(())); + + // `ExportMessage` from source1 and remote1 should pass + assert_deny_execution( + vec![ExportMessage { network: Remote1::get(), destination: Here, xcm: Default::default() }], + Source1::get(), + Ok(()), + ); + + // `ExportMessage` from source1 and remote2 should pass + assert_deny_execution( + vec![ExportMessage { network: Remote2::get(), destination: Here, xcm: Default::default() }], + Source1::get(), + Ok(()), + ); + + // `ExportMessage` from source2 and remote2 should pass + assert_deny_execution( + vec![ExportMessage { network: Remote2::get(), destination: Here, xcm: Default::default() }], + Source2::get(), + Ok(()), + ); + + // `ExportMessage` from source2 and remote1 should be banned + assert_deny_execution( + vec![ExportMessage { network: Remote1::get(), destination: Here, xcm: Default::default() }], + Source2::get(), + Err(Unsupported), + ); +} diff --git a/polkadot/xcm/xcm-builder/src/barriers.rs b/polkadot/xcm/xcm-builder/src/barriers.rs index 27153a3f441da..9f9d3c2321228 100644 --- a/polkadot/xcm/xcm-builder/src/barriers.rs +++ b/polkadot/xcm/xcm-builder/src/barriers.rs @@ -24,7 +24,7 @@ use frame_support::{ }; use polkadot_parachain_primitives::primitives::IsSystem; use xcm::prelude::*; -use xcm_executor::traits::{CheckSuspension, OnResponse, Properties, ShouldExecute}; +use xcm_executor::traits::{CheckSuspension, DenyExecution, OnResponse, Properties, ShouldExecute}; /// Execution barrier that just takes `max_weight` from `properties.weight_credit`. /// @@ -444,12 +444,12 @@ impl ShouldExecute for AllowHrmpNotificationsFromRelayChain { /// If it passes the Deny, and matches one of the Allow cases then it is let through. pub struct DenyThenTry(PhantomData, PhantomData) where - Deny: ShouldExecute, + Deny: DenyExecution, Allow: ShouldExecute; impl ShouldExecute for DenyThenTry where - Deny: ShouldExecute, + Deny: DenyExecution, Allow: ShouldExecute, { fn should_execute( @@ -458,15 +458,15 @@ where max_weight: Weight, properties: &mut Properties, ) -> Result<(), ProcessMessageError> { - Deny::should_execute(origin, message, max_weight, properties)?; + Deny::deny_execution(origin, message, max_weight, properties)?; Allow::should_execute(origin, message, max_weight, properties) } } // See issue pub struct DenyReserveTransferToRelayChain; -impl ShouldExecute for DenyReserveTransferToRelayChain { - fn should_execute( +impl DenyExecution for DenyReserveTransferToRelayChain { + fn deny_execution( origin: &Location, message: &mut [Instruction], _max_weight: Weight, @@ -499,8 +499,6 @@ impl ShouldExecute for DenyReserveTransferToRelayChain { _ => Ok(ControlFlow::Continue(())), }, )?; - - // Permit everything else Ok(()) } } diff --git a/polkadot/xcm/xcm-builder/src/tests/barriers.rs b/polkadot/xcm/xcm-builder/src/tests/barriers.rs index d8805274d3a56..2fb8e8ed0363b 100644 --- a/polkadot/xcm/xcm-builder/src/tests/barriers.rs +++ b/polkadot/xcm/xcm-builder/src/tests/barriers.rs @@ -532,3 +532,235 @@ fn allow_hrmp_notifications_from_relay_chain_should_work() { Ok(()), ); } + +#[test] +fn deny_then_try_works() { + /// A dummy `DenyExecution` impl which returns `ProcessMessageError::Yield` when XCM contains + /// `ClearTransactStatus` + struct DenyClearTransactStatusAsYield; + impl DenyExecution for DenyClearTransactStatusAsYield { + fn deny_execution( + _origin: &Location, + instructions: &mut [Instruction], + _max_weight: Weight, + _properties: &mut Properties, + ) -> Result<(), ProcessMessageError> { + instructions.matcher().match_next_inst_while( + |_| true, + |inst| match inst { + ClearTransactStatus { .. } => Err(ProcessMessageError::Yield), + _ => Ok(ControlFlow::Continue(())), + }, + )?; + Ok(()) + } + } + + /// A dummy `DenyExecution` impl which returns `ProcessMessageError::BadFormat` when XCM + /// contains `ClearOrigin` with origin location from `Here` + struct DenyClearOriginFromHereAsBadFormat; + impl DenyExecution for DenyClearOriginFromHereAsBadFormat { + fn deny_execution( + origin: &Location, + instructions: &mut [Instruction], + _max_weight: Weight, + _properties: &mut Properties, + ) -> Result<(), ProcessMessageError> { + instructions.matcher().match_next_inst_while( + |_| true, + |inst| match inst { + ClearOrigin { .. } => + if origin.clone() == Here.into_location() { + Err(ProcessMessageError::BadFormat) + } else { + Ok(ControlFlow::Continue(())) + }, + _ => Ok(ControlFlow::Continue(())), + }, + )?; + Ok(()) + } + } + + /// A dummy `DenyExecution` impl which returns `ProcessMessageError::StackLimitReached` when XCM + /// contains a single `UnsubscribeVersion` + struct DenyUnsubscribeVersionAsStackLimitReached; + impl DenyExecution for DenyUnsubscribeVersionAsStackLimitReached { + fn deny_execution( + _origin: &Location, + instructions: &mut [Instruction], + _max_weight: Weight, + _properties: &mut Properties, + ) -> Result<(), ProcessMessageError> { + if instructions.len() != 1 { + return Ok(()) + } + match instructions.get(0).unwrap() { + UnsubscribeVersion { .. } => Err(ProcessMessageError::StackLimitReached), + _ => Ok(()), + } + } + } + + /// A dummy `ShouldExecute` impl which returns `Ok(())` when XCM contains a single `ClearError`, + /// else return `ProcessMessageError::Yield` + struct AllowSingleClearErrorOrYield; + impl ShouldExecute for AllowSingleClearErrorOrYield { + fn should_execute( + _origin: &Location, + instructions: &mut [Instruction], + _max_weight: Weight, + _properties: &mut Properties, + ) -> Result<(), ProcessMessageError> { + instructions.matcher().assert_remaining_insts(1)?.match_next_inst( + |inst| match inst { + ClearError { .. } => Ok(()), + _ => Err(ProcessMessageError::Yield), + }, + )?; + Ok(()) + } + } + + /// A dummy `ShouldExecute` impl which returns `Ok(())` when XCM contains `ClearTopic` and + /// origin from `Here`, else return `ProcessMessageError::Unsupported` + struct AllowClearTopicFromHere; + impl ShouldExecute for AllowClearTopicFromHere { + fn should_execute( + origin: &Location, + instructions: &mut [Instruction], + _max_weight: Weight, + _properties: &mut Properties, + ) -> Result<(), ProcessMessageError> { + ensure!(origin.clone() == Here.into_location(), ProcessMessageError::Unsupported); + let mut found = false; + instructions.matcher().match_next_inst_while( + |_| true, + |inst| match inst { + ClearTopic { .. } => { + found = true; + Ok(ControlFlow::Break(())) + }, + _ => Ok(ControlFlow::Continue(())), + }, + )?; + ensure!(found, ProcessMessageError::Unsupported); + Ok(()) + } + } + // closure for (xcm, origin) testing with `DenyThenTry` + let assert_should_execute = |mut xcm: Vec>, origin, expected_result| { + pub type Barrier = DenyThenTry< + ( + DenyClearTransactStatusAsYield, + DenyClearOriginFromHereAsBadFormat, + DenyUnsubscribeVersionAsStackLimitReached, + ), + (AllowSingleClearErrorOrYield, AllowClearTopicFromHere), + >; + assert_eq!( + Barrier::should_execute( + &origin, + &mut xcm, + Weight::from_parts(10, 10), + &mut props(Weight::zero()), + ), + expected_result + ); + }; + + // Deny cases: + // trigger DenyClearTransactStatusAsYield + assert_should_execute( + vec![ClearTransactStatus], + Parachain(1).into_location(), + Err(ProcessMessageError::Yield), + ); + // DenyClearTransactStatusAsYield wins against AllowSingleClearErrorOrYield + assert_should_execute( + vec![ClearError, ClearTransactStatus], + Parachain(1).into_location(), + Err(ProcessMessageError::Yield), + ); + // trigger DenyClearOriginFromHereAsBadFormat + assert_should_execute( + vec![ClearOrigin], + Here.into_location(), + Err(ProcessMessageError::BadFormat), + ); + // trigger DenyUnsubscribeVersionAsStackLimitReached + assert_should_execute( + vec![UnsubscribeVersion], + Here.into_location(), + Err(ProcessMessageError::StackLimitReached), + ); + + // deny because none of the allow items match + assert_should_execute( + vec![ClearError, ClearTopic], + Parachain(1).into_location(), + Err(ProcessMessageError::Unsupported), + ); + + // ok + assert_should_execute(vec![ClearError], Parachain(1).into_location(), Ok(())); + assert_should_execute(vec![ClearTopic], Here.into(), Ok(())); + assert_should_execute(vec![ClearError, ClearTopic], Here.into_location(), Ok(())); +} + +#[test] +fn deny_reserve_transfer_to_relaychain_should_work() { + let assert_deny_execution = |mut xcm: Vec>, origin, expected_result| { + assert_eq!( + DenyReserveTransferToRelayChain::deny_execution( + &origin, + &mut xcm, + Weight::from_parts(10, 10), + &mut props(Weight::zero()), + ), + expected_result + ); + }; + // deny DepositReserveAsset to RelayChain + assert_deny_execution( + vec![DepositReserveAsset { + assets: Wild(All), + dest: Location::parent(), + xcm: vec![].into(), + }], + Here.into_location(), + Err(ProcessMessageError::Unsupported), + ); + // deny InitiateReserveWithdraw to RelayChain + assert_deny_execution( + vec![InitiateReserveWithdraw { + assets: Wild(All), + reserve: Location::parent(), + xcm: vec![].into(), + }], + Here.into_location(), + Err(ProcessMessageError::Unsupported), + ); + // deny TransferReserveAsset to RelayChain + assert_deny_execution( + vec![TransferReserveAsset { + assets: vec![].into(), + dest: Location::parent(), + xcm: vec![].into(), + }], + Here.into_location(), + Err(ProcessMessageError::Unsupported), + ); + // accept DepositReserveAsset to destination other than RelayChain + assert_deny_execution( + vec![DepositReserveAsset { + assets: Wild(All), + dest: Here.into_location(), + xcm: vec![].into(), + }], + Here.into_location(), + Ok(()), + ); + // others instructions should pass + assert_deny_execution(vec![ClearOrigin], Here.into_location(), Ok(())); +} diff --git a/polkadot/xcm/xcm-builder/src/tests/mock.rs b/polkadot/xcm/xcm-builder/src/tests/mock.rs index bec7b253977b6..127888104a4ad 100644 --- a/polkadot/xcm/xcm-builder/src/tests/mock.rs +++ b/polkadot/xcm/xcm-builder/src/tests/mock.rs @@ -31,6 +31,7 @@ pub use codec::{Decode, Encode}; pub use core::{ cell::{Cell, RefCell}, fmt::Debug, + ops::ControlFlow, }; use frame_support::traits::{ContainsPair, Everything}; pub use frame_support::{ @@ -40,11 +41,11 @@ pub use frame_support::{ traits::{Contains, Get, IsInVec}, }; pub use xcm::latest::{prelude::*, QueryId, Weight}; -use xcm_executor::traits::{Properties, QueryHandler, QueryResponseStatus}; pub use xcm_executor::{ traits::{ - AssetExchange, AssetLock, CheckSuspension, ConvertOrigin, Enact, ExportXcm, FeeManager, - FeeReason, LockError, OnResponse, TransactAsset, + AssetExchange, AssetLock, CheckSuspension, ConvertOrigin, DenyExecution, Enact, ExportXcm, + FeeManager, FeeReason, LockError, OnResponse, Properties, QueryHandler, + QueryResponseStatus, TransactAsset, }, AssetsInHolding, Config, }; diff --git a/polkadot/xcm/xcm-executor/src/traits/mod.rs b/polkadot/xcm/xcm-executor/src/traits/mod.rs index feb2922bcdffc..fe73477f516fb 100644 --- a/polkadot/xcm/xcm-executor/src/traits/mod.rs +++ b/polkadot/xcm/xcm-executor/src/traits/mod.rs @@ -42,7 +42,7 @@ pub use on_response::{OnResponse, QueryHandler, QueryResponseStatus, VersionChan mod process_transaction; pub use process_transaction::ProcessTransaction; mod should_execute; -pub use should_execute::{CheckSuspension, Properties, ShouldExecute}; +pub use should_execute::{CheckSuspension, DenyExecution, Properties, ShouldExecute}; mod transact_asset; pub use transact_asset::TransactAsset; mod hrmp; diff --git a/polkadot/xcm/xcm-executor/src/traits/should_execute.rs b/polkadot/xcm/xcm-executor/src/traits/should_execute.rs index ec9ef70cc817e..48a562a1648dd 100644 --- a/polkadot/xcm/xcm-executor/src/traits/should_execute.rs +++ b/polkadot/xcm/xcm-executor/src/traits/should_execute.rs @@ -127,3 +127,67 @@ impl CheckSuspension for Tuple { false } } + +/// Trait to determine whether the execution engine should not execute a given XCM. +/// +/// Can be amalgamated into a tuple to have multiple traits. If any of the tuple elements returns +/// `Err(ProcessMessageError)`, the execution stops. Else, `Ok(())` is returned if all elements +/// accept the message. +pub trait DenyExecution { + /// Returns `Ok(())` if there is no reason to deny execution, + /// while `Err(ProcessMessageError)` indicates there is a reason to deny execution. + /// + /// - `origin`: The origin (sender) of the message. + /// - `instructions`: The message itself. + /// - `max_weight`: The (possibly over-) estimation of the weight of execution of the message. + /// - `properties`: Various pre-established properties of the message which may be mutated by + /// this API. + fn deny_execution( + origin: &Location, + instructions: &mut [Instruction], + max_weight: Weight, + properties: &mut Properties, + ) -> Result<(), ProcessMessageError>; +} + +#[impl_trait_for_tuples::impl_for_tuples(10)] +impl DenyExecution for Tuple { + fn deny_execution( + origin: &Location, + instructions: &mut [Instruction], + max_weight: Weight, + properties: &mut Properties, + ) -> Result<(), ProcessMessageError> { + for_tuples!( #( + let barrier = core::any::type_name::(); + match Tuple::deny_execution(origin, instructions, max_weight, properties) { + Err(error) => { + tracing::error!( + target: "xcm::deny_execution", + ?origin, + ?instructions, + ?max_weight, + ?properties, + ?error, + %barrier, + "did not pass barrier", + ); + return Err(error); + }, + Ok(()) => { + tracing::trace!( + target: "xcm::deny_execution", + ?origin, + ?instructions, + ?max_weight, + ?properties, + %barrier, + "pass barrier", + ); + }, + } + )* ); + + Ok(()) + } +} diff --git a/prdoc/pr_7169.prdoc b/prdoc/pr_7169.prdoc new file mode 100644 index 0000000000000..f78dbfd8d2cd1 --- /dev/null +++ b/prdoc/pr_7169.prdoc @@ -0,0 +1,14 @@ +title: 'xcm: fix DenyThenTry when work with multiple Deny tuples' +doc: +- audience: Runtime User + description: |- + This PR changes the behavior of DenyThenTry to fix #7148 + If any of the tuple elements returns `Err(())`, the execution stops. + Else, `Ok(_)` is returned if all elements accept the message. +crates: +- name: staging-xcm-executor + bump: minor +- name: staging-xcm-builder + bump: minor +- name: bridge-hub-common + bump: minor From e02a7ce897f68fc9595fd3a4e75e082f29c36f03 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 27 Jan 2025 17:40:07 +0000 Subject: [PATCH 157/169] staking benchmarks are mostly green --- .../westend/src/weights/pallet_staking.rs | 55 ----- substrate/bin/node/runtime/src/lib.rs | 14 +- substrate/frame/bags-list/src/list/mod.rs | 2 +- .../src/benchmarking.rs | 15 ++ .../election-provider-multi-block/src/lib.rs | 4 +- .../src/signed/benchmarking.rs | 14 ++ .../src/unsigned/benchmarking.rs | 12 +- .../src/verifier/benchmarking.rs | 15 ++ .../src/verifier/mod.rs | 2 + .../election-provider-multi-phase/src/lib.rs | 1 - .../election-provider-support/src/lib.rs | 57 +++-- substrate/frame/staking/src/benchmarking.rs | 211 ++++++++---------- substrate/frame/staking/src/lib.rs | 4 +- substrate/frame/staking/src/pallet/impls.rs | 9 +- substrate/frame/staking/src/weights.rs | 115 +--------- 15 files changed, 195 insertions(+), 335 deletions(-) create mode 100644 substrate/frame/election-provider-multi-block/src/benchmarking.rs create mode 100644 substrate/frame/election-provider-multi-block/src/verifier/benchmarking.rs diff --git a/polkadot/runtime/westend/src/weights/pallet_staking.rs b/polkadot/runtime/westend/src/weights/pallet_staking.rs index e5d637df212e0..eca023c8b42fd 100644 --- a/polkadot/runtime/westend/src/weights/pallet_staking.rs +++ b/polkadot/runtime/westend/src/weights/pallet_staking.rs @@ -610,61 +610,6 @@ impl pallet_staking::WeightInfo for WeightInfo { /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListBags` (r:178 w:0) /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) - /// Storage: `VoterList::ListNodes` (r:110 w:0) - /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) - /// Storage: `Staking::Bonded` (r:110 w:0) - /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) - /// Storage: `Staking::Ledger` (r:110 w:0) - /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Staking::Nominators` (r:110 w:0) - /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) - /// Storage: `Staking::Validators` (r:11 w:0) - /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) - /// Storage: `Staking::CounterForValidators` (r:1 w:0) - /// Proof: `Staking::CounterForValidators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Staking::ValidatorCount` (r:1 w:0) - /// Proof: `Staking::ValidatorCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Staking::MinimumValidatorCount` (r:1 w:0) - /// Proof: `Staking::MinimumValidatorCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Staking::CurrentEra` (r:1 w:1) - /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Staking::ErasValidatorPrefs` (r:0 w:10) - /// Proof: `Staking::ErasValidatorPrefs` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) - /// Storage: `Staking::ErasStakersPaged` (r:0 w:20) - /// Proof: `Staking::ErasStakersPaged` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Staking::ErasStakersOverview` (r:0 w:10) - /// Proof: `Staking::ErasStakersOverview` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) - /// Storage: `Staking::ErasTotalStake` (r:0 w:1) - /// Proof: `Staking::ErasTotalStake` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) - /// Storage: `Staking::ErasStartSessionIndex` (r:0 w:1) - /// Proof: `Staking::ErasStartSessionIndex` (`max_values`: None, `max_size`: Some(16), added: 2491, mode: `MaxEncodedLen`) - /// Storage: `Staking::MinimumActiveStake` (r:0 w:1) - /// Proof: `Staking::MinimumActiveStake` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) - /// The range of component `v` is `[1, 10]`. - /// The range of component `n` is `[0, 100]`. - fn new_era(v: u32, n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0 + n * (716 ±0) + v * (3594 ±0)` - // Estimated: `456136 + n * (3566 ±4) + v * (3566 ±40)` - // Minimum execution time: 654_756_000 picoseconds. - Weight::from_parts(658_861_000, 0) - .saturating_add(Weight::from_parts(0, 456136)) - // Standard Error: 2_078_102 - .saturating_add(Weight::from_parts(67_775_668, 0).saturating_mul(v.into())) - // Standard Error: 207_071 - .saturating_add(Weight::from_parts(22_624_711, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(184)) - .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(8)) - .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(v.into()))) - .saturating_add(Weight::from_parts(0, 3566).saturating_mul(n.into())) - .saturating_add(Weight::from_parts(0, 3566).saturating_mul(v.into())) - } - /// Storage: `VoterList::CounterForListNodes` (r:1 w:0) - /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `VoterList::ListBags` (r:178 w:0) - /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:2000 w:0) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:2000 w:0) diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index efb7cabdfd091..8a963609fa3d8 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -827,7 +827,7 @@ const MAX_QUOTA_NOMINATIONS: u32 = 16; pub struct StakingBenchmarkingConfig; impl pallet_staking::BenchmarkingConfig for StakingBenchmarkingConfig { - type MaxNominators = ConstU32<1000>; + type MaxNominators = ConstU32<5000>; type MaxValidators = ConstU32<1000>; } @@ -843,9 +843,9 @@ impl ElectionProvider for MultiElectionProvider { type MaxWinnersPerPage = ::MaxWinnersPerPage; fn elect(page: PageIndex) -> Result, Self::Error> { - if page == 0 { + if page == 0 && !cfg!(feature = "runtime-benchmarks") { // TODO: later on, we can even compare the results of the multi-page and multi-block - // election like this. + // election in here. let _ = ElectionProviderMultiPhase::elect(page); } MultiBlock::elect(page) @@ -926,7 +926,10 @@ pub(crate) mod multi_block_impls { type AdminOrigin = EnsureRoot; type RuntimeEvent = RuntimeEvent; type DataProvider = Staking; + #[cfg(not(feature = "runtime-benchmarks"))] type Fallback = multi_block::Continue; + #[cfg(feature = "runtime-benchmarks")] + type Fallback = onchain::OnChainExecution; // prepare for election 5 blocks ahead of time type Lookahead = ConstU32<5>; // split election into 8 pages. @@ -1029,7 +1032,7 @@ parameter_types! { pub ElectionBoundsMultiPhase: ElectionBounds = ElectionBoundsBuilder::default() .voters_count(5000.into()).targets_count(10.into()).build(); pub ElectionBoundsOnChain: ElectionBounds = ElectionBoundsBuilder::default() - .voters_count(1000.into()).targets_count(10.into()).build(); + .voters_count(50_000.into()).targets_count(1000.into()).build(); pub MaxNominations: u32 = ::LIMIT as u32; /// The maximum winners that can be elected by the Election pallet which is equivalent to the @@ -1137,6 +1140,7 @@ impl pallet_election_provider_multi_phase::Config for Runtime { type SlashHandler = (); // burn slashes type RewardHandler = (); // rewards are minted from the void type DataProvider = Staking; + #[cfg(not(feature = "runtime-benchmarks"))] type Fallback = frame_election_provider_support::NoElection<( AccountId, BlockNumber, @@ -1144,6 +1148,8 @@ impl pallet_election_provider_multi_phase::Config for Runtime { MaxActiveValidators, MaxBackersPerWinner, )>; + #[cfg(feature = "runtime-benchmarks")] + type Fallback = onchain::OnChainExecution; type GovernanceFallback = onchain::OnChainExecution; type Solver = SequentialPhragmen, OffchainRandomBalancing>; type ForceOrigin = EnsureRootOrHalfCouncil; diff --git a/substrate/frame/bags-list/src/list/mod.rs b/substrate/frame/bags-list/src/list/mod.rs index 696b64d40e9b9..f96c877e4bf03 100644 --- a/substrate/frame/bags-list/src/list/mod.rs +++ b/substrate/frame/bags-list/src/list/mod.rs @@ -331,7 +331,7 @@ impl, I: 'static> List { bag.put(); crate::log!( - debug, + trace, "inserted {:?} with score {:?} into bag {:?}, new count is {}", id, score, diff --git a/substrate/frame/election-provider-multi-block/src/benchmarking.rs b/substrate/frame/election-provider-multi-block/src/benchmarking.rs new file mode 100644 index 0000000000000..67a7f9f8980c9 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/benchmarking.rs @@ -0,0 +1,15 @@ +use super::{Pallet as UnsignedPallet, *}; +use crate::{helpers, types::*}; +use frame_support::ensure; + +const SEED: u64 = 1; + +frame_benchmarking::benchmarks! { + foo {}: {} verify {} +} + +frame_benchmarking::impl_benchmark_test_suite!( + UnsignedPallet, + crate::mock::ExtBuilder::unsigned().build_offchainify().0, + crate::mock::Runtime, +); diff --git a/substrate/frame/election-provider-multi-block/src/lib.rs b/substrate/frame/election-provider-multi-block/src/lib.rs index 282c45b76c38b..e736d17aaec4d 100644 --- a/substrate/frame/election-provider-multi-block/src/lib.rs +++ b/substrate/frame/election-provider-multi-block/src/lib.rs @@ -184,6 +184,8 @@ use verifier::Verifier; mod mock; #[macro_use] pub mod helpers; +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarking; const LOG_PREFIX: &'static str = "runtime::multiblock-election"; @@ -515,7 +517,7 @@ pub mod pallet { // start and continue snapshot. Phase::Off if remaining_blocks <= snapshot_deadline - // && remaining_blocks > signed_deadline + // && remaining_blocks > signed_deadline // TODO do we need this? => { let remaining_pages = Self::msp(); diff --git a/substrate/frame/election-provider-multi-block/src/signed/benchmarking.rs b/substrate/frame/election-provider-multi-block/src/signed/benchmarking.rs index 8b137891791fe..67a7f9f8980c9 100644 --- a/substrate/frame/election-provider-multi-block/src/signed/benchmarking.rs +++ b/substrate/frame/election-provider-multi-block/src/signed/benchmarking.rs @@ -1 +1,15 @@ +use super::{Pallet as UnsignedPallet, *}; +use crate::{helpers, types::*}; +use frame_support::ensure; +const SEED: u64 = 1; + +frame_benchmarking::benchmarks! { + foo {}: {} verify {} +} + +frame_benchmarking::impl_benchmark_test_suite!( + UnsignedPallet, + crate::mock::ExtBuilder::unsigned().build_offchainify().0, + crate::mock::Runtime, +); diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/benchmarking.rs b/substrate/frame/election-provider-multi-block/src/unsigned/benchmarking.rs index c6f28255a1d93..67a7f9f8980c9 100644 --- a/substrate/frame/election-provider-multi-block/src/unsigned/benchmarking.rs +++ b/substrate/frame/election-provider-multi-block/src/unsigned/benchmarking.rs @@ -5,17 +5,7 @@ use frame_support::ensure; const SEED: u64 = 1; frame_benchmarking::benchmarks! { - // an unsigned solution is a function of the feasibility check of single page. - submit_unsigned { - let v in 4000 .. 8000; - let t in 500 .. 2000; - let a in 5000 .. 7000; - let d in 700 .. 1500; - - // make the snapshot - - // - }: {} verify {} + foo {}: {} verify {} } frame_benchmarking::impl_benchmark_test_suite!( diff --git a/substrate/frame/election-provider-multi-block/src/verifier/benchmarking.rs b/substrate/frame/election-provider-multi-block/src/verifier/benchmarking.rs new file mode 100644 index 0000000000000..67a7f9f8980c9 --- /dev/null +++ b/substrate/frame/election-provider-multi-block/src/verifier/benchmarking.rs @@ -0,0 +1,15 @@ +use super::{Pallet as UnsignedPallet, *}; +use crate::{helpers, types::*}; +use frame_support::ensure; + +const SEED: u64 = 1; + +frame_benchmarking::benchmarks! { + foo {}: {} verify {} +} + +frame_benchmarking::impl_benchmark_test_suite!( + UnsignedPallet, + crate::mock::ExtBuilder::unsigned().build_offchainify().0, + crate::mock::Runtime, +); diff --git a/substrate/frame/election-provider-multi-block/src/verifier/mod.rs b/substrate/frame/election-provider-multi-block/src/verifier/mod.rs index 014371070cbda..f7b9a6100c7e0 100644 --- a/substrate/frame/election-provider-multi-block/src/verifier/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/verifier/mod.rs @@ -69,6 +69,8 @@ //! //! - TODO: allow less winners, and backport it. +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarking; mod impls; #[cfg(test)] mod tests; diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index 09bb54221b573..461f26e2f8d72 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -1827,7 +1827,6 @@ mod feasibility_check { //! All of the tests here should be dedicated to only testing the feasibility check and nothing //! more. The best way to audit and review these tests is to try and come up with a solution //! that is invalid, but gets through the system as valid. - use super::*; use crate::mock::{ raw_solution, roll_to, EpochLength, ExtBuilder, MultiPhase, Runtime, SignedPhase, diff --git a/substrate/frame/election-provider-support/src/lib.rs b/substrate/frame/election-provider-support/src/lib.rs index 234314d21d3b0..22e83630a6a86 100644 --- a/substrate/frame/election-provider-support/src/lib.rs +++ b/substrate/frame/election-provider-support/src/lib.rs @@ -829,6 +829,34 @@ pub struct BoundedSupports, BInner: Get>( pub BoundedVec<(AccountId, BoundedSupport), BOuter>, ); +impl, BInner: Get> + BoundedSupports +{ + pub fn sorted_truncate_from(supports: Supports) -> Self { + // if bounds, meet, short circuit + if let Ok(bounded) = supports.clone().try_into() { + return bounded + } + + // first, convert all inner supports. + let mut inner_supports = supports + .into_iter() + .map(|(account, support)| { + (account, BoundedSupport::::sorted_truncate_from(support)) + }) + .collect::>(); + + // then sort outer supports based on total stake, high to low + inner_supports.sort_by(|a, b| b.1.total.cmp(&a.1.total)); + + // then take the first slice that can fit. + BoundedSupports( + BoundedVec::<(AccountId, BoundedSupport), BOuter>::truncate_from( + inner_supports, + ), + ) + } +} pub trait TryFromUnboundedPagedSupports, BInner: Get> { fn try_from_unbounded_paged( self, @@ -951,35 +979,6 @@ impl, BInner: Get> TryFrom> } } -impl, BInner: Get> - BoundedSupports -{ - pub fn sorted_truncate_from(supports: Supports) -> Self { - // if bounds, meet, short circuit - if let Ok(bounded) = supports.clone().try_into() { - return bounded - } - - // first, convert all inner supports. - let mut inner_supports = supports - .into_iter() - .map(|(account, support)| { - (account, BoundedSupport::::sorted_truncate_from(support)) - }) - .collect::>(); - - // then sort outer supports based on total stake, high to low - inner_supports.sort_by(|a, b| b.1.total.cmp(&a.1.total)); - - // then take the first slice that can fit. - BoundedSupports( - BoundedVec::<(AccountId, BoundedSupport), BOuter>::truncate_from( - inner_supports, - ), - ) - } -} - /// Same as `BoundedSupports` but parameterized by an `ElectionProvider`. pub type BoundedSupportsOf = BoundedSupports< ::AccountId, diff --git a/substrate/frame/staking/src/benchmarking.rs b/substrate/frame/staking/src/benchmarking.rs index 4538556f56c71..aa73420304b37 100644 --- a/substrate/frame/staking/src/benchmarking.rs +++ b/substrate/frame/staking/src/benchmarking.rs @@ -19,32 +19,33 @@ use super::*; use crate::{asset, ConfigOp, Pallet as Staking}; -use testing_utils::*; - +use alloc::collections::btree_set::BTreeSet; use codec::Decode; +pub use frame_benchmarking::{ + impl_benchmark_test_suite, v2::*, whitelist_account, whitelisted_caller, BenchmarkError, +}; use frame_election_provider_support::{bounds::DataProviderBounds, SortedListProvider}; use frame_support::{ pallet_prelude::*, storage::bounded_vec::BoundedVec, traits::{Get, Imbalance, UnfilteredDispatchable}, }; +use frame_system::RawOrigin; use sp_runtime::{ traits::{Bounded, One, StaticLookup, TrailingZeroInput, Zero}, - Perbill, Percent, Saturating, + BoundedBTreeSet, Perbill, Percent, Saturating, }; use sp_staking::{currency_to_vote::CurrencyToVote, SessionIndex}; - -pub use frame_benchmarking::{ - impl_benchmark_test_suite, v2::*, whitelist_account, whitelisted_caller, BenchmarkError, -}; -use frame_system::RawOrigin; +use testing_utils::*; const SEED: u32 = 0; const MAX_SPANS: u32 = 100; const MAX_SLASHES: u32 = 1000; -type MaxValidators = <::BenchmarkingConfig as BenchmarkingConfig>::MaxValidators; -type MaxNominators = <::BenchmarkingConfig as BenchmarkingConfig>::MaxNominators; +type BenchMaxValidators = + <::BenchmarkingConfig as BenchmarkingConfig>::MaxValidators; +type BenchMaxNominators = + <::BenchmarkingConfig as BenchmarkingConfig>::MaxNominators; // Add slashing spans to a user account. Not relevant for actual use, only to benchmark // read and write operations. @@ -114,8 +115,15 @@ pub fn create_validator_with_nominators( } ValidatorCount::::put(1); + MinimumValidatorCount::::put(1); - // Start a new Era + // Start a new (genesis) Era + // populate electable stashes as it gets read within `try_plan_new_era` + + // ElectableStashes::::put( + // BoundedBTreeSet::try_from(vec![v_stash.clone()].into_iter().collect::>()) + // .unwrap(), + // ); let new_validators = Staking::::try_plan_new_era(SessionIndex::one(), true).unwrap(); assert_eq!(new_validators.len(), 1); @@ -238,48 +246,102 @@ mod benchmarks { } #[benchmark] - fn do_elect_paged(v: Linear<1, { T::MaxValidatorSet::get() }>) -> Result<(), BenchmarkError> { - assert!(ElectableStashes::::get().is_empty()); + fn do_elect_paged_inner( + v: Linear<1, { T::MaxValidatorSet::get() }>, + ) -> Result<(), BenchmarkError> { + use frame_election_provider_support::{ + BoundedSupport, BoundedSupportsOf, ElectionProvider, + }; + let mut bounded_random_supports = BoundedSupportsOf::::default(); + for i in 0..v { + let backed = account("validator", i, SEED); + let mut total = 0; + let voters = (0..::MaxBackersPerWinner::get()) + .map(|j| { + let voter = account("nominator", j, SEED); + let support = 100000; + total += support; + (voter, support) + }) + .collect::>() + .try_into() + .unwrap(); + bounded_random_supports + .try_push((backed, BoundedSupport { total, voters })) + .map_err(|_| "bound failed") + .expect("map is over the correct bound"); + } + + #[block] + { + assert_eq!(Pallet::::do_elect_paged_inner(bounded_random_supports), Ok(v as usize)); + } + + assert!(!ElectableStashes::::get().is_empty()); + + Ok(()) + } + + #[benchmark] + fn get_npos_voters( + // number of validator intention. we will iterate all of them. + v: Linear<{ BenchMaxValidators::::get() / 2 }, { BenchMaxValidators::::get() }>, + // number of nominator intention. we will iterate all of them. + n: Linear<{ BenchMaxNominators::::get() / 2 }, { BenchMaxNominators::::get() }>, + ) -> Result<(), BenchmarkError> { create_validators_with_nominators_for_era::( v, - 100, + n, MaxNominationsOf::::get() as usize, false, None, )?; + assert_eq!(Validators::::count(), v); + assert_eq!(Nominators::::count(), n); + + let num_voters = (v + n) as usize; + + // default bounds are unbounded. + let voters; #[block] { - Pallet::::do_elect_paged(0u32); + voters = >::get_npos_voters( + DataProviderBounds::default(), + &SnapshotStatus::::Waiting, + ); } - assert!(!ElectableStashes::::get().is_empty()); + assert_eq!(voters.len(), num_voters); Ok(()) } #[benchmark] - fn clear_election_metadata( - v: Linear<1, { T::MaxValidatorSet::get() }>, + fn get_npos_targets( + // number of validator intention. + v: Linear<{ BenchMaxValidators::::get() / 2 }, { BenchMaxValidators::::get() }>, ) -> Result<(), BenchmarkError> { - use frame_support::BoundedBTreeSet; - - let mut stashes: BoundedBTreeSet = BoundedBTreeSet::new(); - for u in (0..v).into_iter() { - frame_support::assert_ok!(stashes.try_insert(account("stash", u, SEED))); - } + // number of nominator intention. + let n = BenchMaxNominators::::get(); + create_validators_with_nominators_for_era::( + v, + n, + MaxNominationsOf::::get() as usize, + false, + None, + )?; - ElectableStashes::::set(stashes); - NextElectionPage::::set(Some(10u32.into())); + let targets; #[block] { - Pallet::::clear_election_metadata() + // default bounds are unbounded. + targets = >::get_npos_targets(DataProviderBounds::default()); } - assert!(NextElectionPage::::get().is_none()); - assert!(ElectableStashes::::get().is_empty()); + assert_eq!(targets.len() as u32, v); Ok(()) } @@ -630,7 +692,7 @@ mod benchmarks { #[benchmark] fn set_validator_count() { - let validator_count = MaxValidators::::get(); + let validator_count = BenchMaxValidators::::get(); #[extrinsic_call] _(RawOrigin::Root, validator_count); @@ -664,7 +726,7 @@ mod benchmarks { #[benchmark] // Worst case scenario, the list of invulnerables is very long. - fn set_invulnerables(v: Linear<0, { MaxValidators::::get() }>) { + fn set_invulnerables(v: Linear<0, { BenchMaxValidators::::get() }>) { let mut invulnerables = Vec::new(); for i in 0..v { invulnerables.push(account("invulnerable", i, SEED)); @@ -888,29 +950,6 @@ mod benchmarks { Ok(()) } - #[benchmark] - fn new_era(v: Linear<1, 10>, n: Linear<0, 100>) -> Result<(), BenchmarkError> { - create_validators_with_nominators_for_era::( - v, - n, - MaxNominationsOf::::get() as usize, - false, - None, - )?; - let session_index = SessionIndex::one(); - - let validators; - #[block] - { - validators = - Staking::::try_plan_new_era(session_index, true).ok_or("`new_era` failed")?; - } - - assert!(validators.len() == v as usize); - - Ok(()) - } - #[benchmark(extra)] fn payout_all(v: Linear<1, 10>, n: Linear<0, 100>) -> Result<(), BenchmarkError> { create_validators_with_nominators_for_era::( @@ -1005,70 +1044,6 @@ mod benchmarks { Ok(()) } - #[benchmark] - fn get_npos_voters( - // number of validator intention. we will iterate all of them. - v: Linear<{ MaxValidators::::get() / 2 }, { MaxValidators::::get() }>, - - // number of nominator intention. we will iterate all of them. - n: Linear<{ MaxNominators::::get() / 2 }, { MaxNominators::::get() }>, - ) -> Result<(), BenchmarkError> { - create_validators_with_nominators_for_era::( - v, - n, - MaxNominationsOf::::get() as usize, - false, - None, - )?; - - assert_eq!(Validators::::count(), v); - assert_eq!(Nominators::::count(), n); - - let num_voters = (v + n) as usize; - - // default bounds are unbounded. - let voters; - #[block] - { - voters = >::get_npos_voters( - DataProviderBounds::default(), - &SnapshotStatus::::Waiting, - ); - } - - assert_eq!(voters.len(), num_voters); - - Ok(()) - } - - #[benchmark] - fn get_npos_targets( - // number of validator intention. - v: Linear<{ MaxValidators::::get() / 2 }, { MaxValidators::::get() }>, - ) -> Result<(), BenchmarkError> { - // number of nominator intention. - let n = MaxNominators::::get(); - create_validators_with_nominators_for_era::( - v, - n, - MaxNominationsOf::::get() as usize, - false, - None, - )?; - - let targets; - - #[block] - { - // default bounds are unbounded. - targets = >::get_npos_targets(DataProviderBounds::default()); - } - - assert_eq!(targets.len() as u32, v); - - Ok(()) - } - #[benchmark] fn set_staking_configs_all_set() { #[extrinsic_call] diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index 07d38a139504d..690731bf85865 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -1387,9 +1387,9 @@ impl EraInfo { /// Configurations of the benchmarking of the pallet. pub trait BenchmarkingConfig { - /// The maximum number of validators to use. + /// The maximum number of validators to use for snapshot creation. type MaxValidators: Get; - /// The maximum number of nominators to use. + /// The maximum number of nominators to use for snapshot creation, per page. type MaxNominators: Get; } diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index a08f15b47b7aa..192e84dfeddef 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -82,6 +82,8 @@ impl Pallet { VoterSnapshotStatus::::kill(); NextElectionPage::::kill(); ElectableStashes::::kill(); + // TODO: crude weights, improve. + Self::register_weight(T::DbWeight::get().writes(3)); } /// Fetches the ledger associated with a controller or stash account, if any. @@ -633,6 +635,7 @@ impl Pallet { start_session_index: SessionIndex, is_genesis: bool, ) -> Option>> { + // TODO: weights of this call path are rather crude, improve. let validators: BoundedVec> = if is_genesis { // genesis election only uses one election result page. let result = ::elect(Zero::zero()).map_err(|e| { @@ -652,10 +655,13 @@ impl Pallet { // set stakers info for genesis era (0). let _ = Self::store_stakers_info(exposures, Zero::zero()); + // consume full block weight to be safe. + Self::register_weight(sp_runtime::traits::Bounded::max_value()); validators } else { // note: exposures have already been processed and stored for each of the election // solution page at the time of `elect_paged(page_index)`. + Self::register_weight(T::DbWeight::get().reads(1)); ElectableStashes::::take() .into_inner() .into_iter() @@ -746,6 +752,7 @@ impl Pallet { pub(crate) fn do_elect_paged(page: PageIndex) -> Weight { match T::ElectionProvider::elect(page) { Ok(supports) => { + let supports_len = supports.len() as u32; let inner_processing_results = Self::do_elect_paged_inner(supports); if let Err(not_included) = inner_processing_results { defensive!( @@ -759,7 +766,7 @@ impl Pallet { page, result: inner_processing_results.map(|x| x as u32).map_err(|x| x as u32), }); - T::WeightInfo::do_elect_paged(T::MaxValidatorSet::get()) + T::WeightInfo::do_elect_paged_inner(supports_len) }, Err(e) => { log!(warn, "election provider page failed due to {:?} (page: {})", e, page); diff --git a/substrate/frame/staking/src/weights.rs b/substrate/frame/staking/src/weights.rs index 6c028c7119a63..92fe0e176a2e6 100644 --- a/substrate/frame/staking/src/weights.rs +++ b/substrate/frame/staking/src/weights.rs @@ -50,7 +50,7 @@ use core::marker::PhantomData; /// Weight functions needed for `pallet_staking`. pub trait WeightInfo { fn on_initialize_noop() -> Weight; - fn do_elect_paged(v: u32,) -> Weight; + fn do_elect_paged_inner(v: u32,) -> Weight; fn clear_election_metadata() -> Weight; fn bond() -> Weight; fn bond_extra() -> Weight; @@ -75,7 +75,6 @@ pub trait WeightInfo { fn payout_stakers_alive_staked(n: u32, ) -> Weight; 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, ) -> Weight; fn get_npos_targets(v: u32, ) -> Weight; fn set_staking_configs_all_set() -> Weight; @@ -94,7 +93,7 @@ impl WeightInfo for SubstrateWeight { fn on_initialize_noop() -> Weight { Default::default() } - fn do_elect_paged(_v: u32,) -> Weight { + fn do_elect_paged_inner(_v: u32,) -> Weight { Default::default() } fn clear_election_metadata() -> Weight { @@ -628,60 +627,6 @@ impl WeightInfo for SubstrateWeight { /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListBags` (r:200 w:0) /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) - /// Storage: `VoterList::ListNodes` (r:110 w:0) - /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) - /// Storage: `Staking::Bonded` (r:110 w:0) - /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) - /// Storage: `Staking::Ledger` (r:110 w:0) - /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Staking::Nominators` (r:110 w:0) - /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) - /// Storage: `Staking::Validators` (r:11 w:0) - /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) - /// Storage: `Staking::CounterForValidators` (r:1 w:0) - /// Proof: `Staking::CounterForValidators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Staking::ValidatorCount` (r:1 w:0) - /// Proof: `Staking::ValidatorCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Staking::MinimumValidatorCount` (r:1 w:0) - /// Proof: `Staking::MinimumValidatorCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Staking::CurrentEra` (r:1 w:1) - /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Staking::ErasValidatorPrefs` (r:0 w:10) - /// Proof: `Staking::ErasValidatorPrefs` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) - /// Storage: `Staking::ErasStakersPaged` (r:0 w:10) - /// Proof: `Staking::ErasStakersPaged` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Staking::ErasStakersOverview` (r:0 w:10) - /// Proof: `Staking::ErasStakersOverview` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) - /// Storage: `Staking::ErasTotalStake` (r:0 w:1) - /// Proof: `Staking::ErasTotalStake` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) - /// Storage: `Staking::ErasStartSessionIndex` (r:0 w:1) - /// Proof: `Staking::ErasStartSessionIndex` (`max_values`: None, `max_size`: Some(16), added: 2491, mode: `MaxEncodedLen`) - /// Storage: `Staking::MinimumActiveStake` (r:0 w:1) - /// Proof: `Staking::MinimumActiveStake` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) - /// The range of component `v` is `[1, 10]`. - /// The range of component `n` is `[0, 100]`. - fn new_era(v: u32, n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0 + n * (720 ±0) + v * (3598 ±0)` - // Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 692_301_000 picoseconds. - Weight::from_parts(708_732_000, 512390) - // Standard Error: 2_117_299 - .saturating_add(Weight::from_parts(70_087_600, 0).saturating_mul(v.into())) - // Standard Error: 210_977 - .saturating_add(Weight::from_parts(22_953_405, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(206_u64)) - .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_u64)) - .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(v.into()))) - .saturating_add(Weight::from_parts(0, 3566).saturating_mul(n.into())) - .saturating_add(Weight::from_parts(0, 3566).saturating_mul(v.into())) - } - /// Storage: `VoterList::CounterForListNodes` (r:1 w:0) - /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `VoterList::ListBags` (r:200 w:0) - /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:2000 w:0) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:2000 w:0) @@ -878,7 +823,7 @@ impl WeightInfo for () { fn on_initialize_noop() -> Weight { RocksDbWeight::get().reads(1) } - fn do_elect_paged(_v: u32,) -> Weight { + fn do_elect_paged_inner(_v: u32,) -> Weight { RocksDbWeight::get().reads(1) } fn clear_election_metadata() -> Weight { @@ -1412,60 +1357,6 @@ impl WeightInfo for () { /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListBags` (r:200 w:0) /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) - /// Storage: `VoterList::ListNodes` (r:110 w:0) - /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) - /// Storage: `Staking::Bonded` (r:110 w:0) - /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) - /// Storage: `Staking::Ledger` (r:110 w:0) - /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Staking::Nominators` (r:110 w:0) - /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) - /// Storage: `Staking::Validators` (r:11 w:0) - /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) - /// Storage: `Staking::CounterForValidators` (r:1 w:0) - /// Proof: `Staking::CounterForValidators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Staking::ValidatorCount` (r:1 w:0) - /// Proof: `Staking::ValidatorCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Staking::MinimumValidatorCount` (r:1 w:0) - /// Proof: `Staking::MinimumValidatorCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Staking::CurrentEra` (r:1 w:1) - /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Staking::ErasValidatorPrefs` (r:0 w:10) - /// Proof: `Staking::ErasValidatorPrefs` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) - /// Storage: `Staking::ErasStakersPaged` (r:0 w:10) - /// Proof: `Staking::ErasStakersPaged` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Staking::ErasStakersOverview` (r:0 w:10) - /// Proof: `Staking::ErasStakersOverview` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) - /// Storage: `Staking::ErasTotalStake` (r:0 w:1) - /// Proof: `Staking::ErasTotalStake` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) - /// Storage: `Staking::ErasStartSessionIndex` (r:0 w:1) - /// Proof: `Staking::ErasStartSessionIndex` (`max_values`: None, `max_size`: Some(16), added: 2491, mode: `MaxEncodedLen`) - /// Storage: `Staking::MinimumActiveStake` (r:0 w:1) - /// Proof: `Staking::MinimumActiveStake` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) - /// The range of component `v` is `[1, 10]`. - /// The range of component `n` is `[0, 100]`. - fn new_era(v: u32, n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0 + n * (720 ±0) + v * (3598 ±0)` - // Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 692_301_000 picoseconds. - Weight::from_parts(708_732_000, 512390) - // Standard Error: 2_117_299 - .saturating_add(Weight::from_parts(70_087_600, 0).saturating_mul(v.into())) - // Standard Error: 210_977 - .saturating_add(Weight::from_parts(22_953_405, 0).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(206_u64)) - .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_u64)) - .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(v.into()))) - .saturating_add(Weight::from_parts(0, 3566).saturating_mul(n.into())) - .saturating_add(Weight::from_parts(0, 3566).saturating_mul(v.into())) - } - /// Storage: `VoterList::CounterForListNodes` (r:1 w:0) - /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `VoterList::ListBags` (r:200 w:0) - /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:2000 w:0) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:2000 w:0) From e6aad5b010e630dbac7d86873fef45580630b406 Mon Sep 17 00:00:00 2001 From: Alin Dima Date: Tue, 28 Jan 2025 10:32:00 +0200 Subject: [PATCH 158/169] cumulus: bump PARENT_SEARCH_DEPTH and add test for 12-core elastic scaling (#6983) On top of https://github.com/paritytech/polkadot-sdk/pull/6757 Fixes https://github.com/paritytech/polkadot-sdk/issues/6858 by bumping the `PARENT_SEARCH_DEPTH` constant to a larger value (30) and adds a zombienet-sdk test that exercises the 12-core scenario. This is a node-side limit that restricts the number of allowed pending availability candidates when choosing the parent parablock during authoring. This limit is rather redundant, as the parachain runtime already restricts the unincluded segment length to the configured value in the [FixedVelocityConsensusHook](https://github.com/paritytech/polkadot-sdk/blob/88d900afbff7ebe600dfe5e3ee9f87fe52c93d1f/cumulus/pallets/aura-ext/src/consensus_hook.rs#L35) (which ideally should be equal to this `PARENT_SEARCH_DEPTH`). For 12 cores, a value of 24 should be enough, but I bumped it to 30 to have some extra buffer. There are two other potential ways of fixing this: - remove this constant altogether, as the parachain runtime already makes those guarantees. Chose not to do this, as it can't hurt to have an extra safeguard - set this value to be equal to the uninlcuded segment size. This value however is not exposed to the node-side and would require a new runtime API, which seems overkill for a redundant check. --------- Co-authored-by: Javier Viola --- .gitlab/pipeline/zombienet/polkadot.yml | 17 +++ .../consensus/aura/src/collators/mod.rs | 7 +- cumulus/test/runtime/Cargo.toml | 1 + cumulus/test/runtime/build.rs | 8 ++ cumulus/test/runtime/src/lib.rs | 28 ++-- cumulus/test/service/src/chain_spec.rs | 10 ++ cumulus/test/service/src/cli.rs | 6 + .../tests/elastic_scaling/mod.rs | 1 + .../elastic_scaling/slot_based_12cores.rs | 129 ++++++++++++++++++ prdoc/pr_6983.prdoc | 17 +++ 10 files changed, 209 insertions(+), 15 deletions(-) create mode 100644 polkadot/zombienet-sdk-tests/tests/elastic_scaling/slot_based_12cores.rs create mode 100644 prdoc/pr_6983.prdoc diff --git a/.gitlab/pipeline/zombienet/polkadot.yml b/.gitlab/pipeline/zombienet/polkadot.yml index 878f241317a42..e09be328c81d0 100644 --- a/.gitlab/pipeline/zombienet/polkadot.yml +++ b/.gitlab/pipeline/zombienet/polkadot.yml @@ -379,6 +379,23 @@ zombienet-polkadot-elastic-scaling-slot-based-3cores: - unset NEXTEST_SUCCESS_OUTPUT - cargo nextest run --archive-file ./artifacts/polkadot-zombienet-tests.tar.zst --no-capture -- elastic_scaling::slot_based_3cores::slot_based_3cores_test +zombienet-polkadot-elastic-scaling-slot-based-12cores: + extends: + - .zombienet-polkadot-common + needs: + - job: build-polkadot-zombienet-tests + artifacts: true + before_script: + - !reference [ ".zombienet-polkadot-common", "before_script" ] + - export POLKADOT_IMAGE="${ZOMBIENET_INTEGRATION_TEST_IMAGE}" + - export CUMULUS_IMAGE="docker.io/paritypr/test-parachain:${PIPELINE_IMAGE_TAG}" + - export X_INFRA_INSTANCE=spot # use spot by default + script: + # we want to use `--no-capture` in zombienet tests. + - unset NEXTEST_FAILURE_OUTPUT + - unset NEXTEST_SUCCESS_OUTPUT + - cargo nextest run --archive-file ./artifacts/polkadot-zombienet-tests.tar.zst --no-capture -- elastic_scaling::slot_based_12cores::slot_based_12cores_test + zombienet-polkadot-elastic-scaling-doesnt-break-parachains: extends: - .zombienet-polkadot-common diff --git a/cumulus/client/consensus/aura/src/collators/mod.rs b/cumulus/client/consensus/aura/src/collators/mod.rs index 66c6086eaf9ee..df775f6c9ec07 100644 --- a/cumulus/client/consensus/aura/src/collators/mod.rs +++ b/cumulus/client/consensus/aura/src/collators/mod.rs @@ -44,12 +44,13 @@ pub mod lookahead; pub mod slot_based; // This is an arbitrary value which is likely guaranteed to exceed any reasonable -// limit, as it would correspond to 10 non-included blocks. +// limit, as it would correspond to 30 non-included blocks. // // Since we only search for parent blocks which have already been imported, // we can guarantee that all imported blocks respect the unincluded segment -// rules specified by the parachain's runtime and thus will never be too deep. -const PARENT_SEARCH_DEPTH: usize = 10; +// rules specified by the parachain's runtime and thus will never be too deep. This is just an extra +// sanity check. +const PARENT_SEARCH_DEPTH: usize = 30; /// Check the `local_validation_code_hash` against the validation code hash in the relay chain /// state. diff --git a/cumulus/test/runtime/Cargo.toml b/cumulus/test/runtime/Cargo.toml index 71509f82bfe13..711525a297c72 100644 --- a/cumulus/test/runtime/Cargo.toml +++ b/cumulus/test/runtime/Cargo.toml @@ -94,4 +94,5 @@ std = [ ] increment-spec-version = [] elastic-scaling = [] +elastic-scaling-500ms = [] experimental-ump-signals = ["cumulus-pallet-parachain-system/experimental-ump-signals"] diff --git a/cumulus/test/runtime/build.rs b/cumulus/test/runtime/build.rs index 43e60c1074a04..99d30ce6dc372 100644 --- a/cumulus/test/runtime/build.rs +++ b/cumulus/test/runtime/build.rs @@ -39,6 +39,14 @@ fn main() { .import_memory() .set_file_name("wasm_binary_elastic_scaling.rs") .build(); + + WasmBuilder::new() + .with_current_project() + .enable_feature("elastic-scaling-500ms") + .enable_feature("experimental-ump-signals") + .import_memory() + .set_file_name("wasm_binary_elastic_scaling_500ms.rs") + .build(); } #[cfg(not(feature = "std"))] diff --git a/cumulus/test/runtime/src/lib.rs b/cumulus/test/runtime/src/lib.rs index 01ce3427c1f19..09e1361603a08 100644 --- a/cumulus/test/runtime/src/lib.rs +++ b/cumulus/test/runtime/src/lib.rs @@ -27,6 +27,10 @@ pub mod wasm_spec_version_incremented { include!(concat!(env!("OUT_DIR"), "/wasm_binary_spec_version_incremented.rs")); } +pub mod elastic_scaling_500ms { + #[cfg(feature = "std")] + include!(concat!(env!("OUT_DIR"), "/wasm_binary_elastic_scaling_500ms.rs")); +} pub mod elastic_scaling_mvp { #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary_elastic_scaling_mvp.rs")); @@ -98,21 +102,21 @@ impl_opaque_keys! { /// The para-id used in this runtime. pub const PARACHAIN_ID: u32 = 100; -#[cfg(not(feature = "elastic-scaling"))] -const UNINCLUDED_SEGMENT_CAPACITY: u32 = 4; -#[cfg(not(feature = "elastic-scaling"))] -const BLOCK_PROCESSING_VELOCITY: u32 = 1; - -#[cfg(feature = "elastic-scaling")] -const UNINCLUDED_SEGMENT_CAPACITY: u32 = 7; -#[cfg(feature = "elastic-scaling")] -const BLOCK_PROCESSING_VELOCITY: u32 = 4; - -#[cfg(not(feature = "elastic-scaling"))] +#[cfg(not(any(feature = "elastic-scaling", feature = "elastic-scaling-500ms")))] pub const MILLISECS_PER_BLOCK: u64 = 6000; -#[cfg(feature = "elastic-scaling")] + +#[cfg(all(feature = "elastic-scaling", not(feature = "elastic-scaling-500ms")))] pub const MILLISECS_PER_BLOCK: u64 = 2000; +#[cfg(feature = "elastic-scaling-500ms")] +pub const MILLISECS_PER_BLOCK: u64 = 500; + +const BLOCK_PROCESSING_VELOCITY: u32 = + RELAY_CHAIN_SLOT_DURATION_MILLIS / (MILLISECS_PER_BLOCK as u32); + +// The `+2` shouldn't be needed, https://github.com/paritytech/polkadot-sdk/issues/5260 +const UNINCLUDED_SEGMENT_CAPACITY: u32 = BLOCK_PROCESSING_VELOCITY * 2 + 2; + pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000; diff --git a/cumulus/test/service/src/chain_spec.rs b/cumulus/test/service/src/chain_spec.rs index 5ebcc14592d74..b59bd7ab46bdb 100644 --- a/cumulus/test/service/src/chain_spec.rs +++ b/cumulus/test/service/src/chain_spec.rs @@ -117,6 +117,16 @@ pub fn get_elastic_scaling_chain_spec(id: Option) -> ChainSpec { ) } +/// Get the chain spec for a specific parachain ID. +pub fn get_elastic_scaling_500ms_chain_spec(id: Option) -> ChainSpec { + get_chain_spec_with_extra_endowed( + id, + Default::default(), + cumulus_test_runtime::elastic_scaling_500ms::WASM_BINARY + .expect("WASM binary was not built, please build it!"), + ) +} + /// Get the chain spec for a specific parachain ID. pub fn get_elastic_scaling_mvp_chain_spec(id: Option) -> ChainSpec { get_chain_spec_with_extra_endowed( diff --git a/cumulus/test/service/src/cli.rs b/cumulus/test/service/src/cli.rs index e019089e70fe8..7909ffbf7142f 100644 --- a/cumulus/test/service/src/cli.rs +++ b/cumulus/test/service/src/cli.rs @@ -274,6 +274,12 @@ impl SubstrateCli for TestCollatorCli { 2200, )))) as Box<_> }, + "elastic-scaling-500ms" => { + tracing::info!("Using elastic-scaling 500ms chain spec."); + Box::new(cumulus_test_service::get_elastic_scaling_500ms_chain_spec(Some( + ParaId::from(2300), + ))) as Box<_> + }, path => { let chain_spec = cumulus_test_service::chain_spec::ChainSpec::from_json_file(path.into())?; diff --git a/polkadot/zombienet-sdk-tests/tests/elastic_scaling/mod.rs b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/mod.rs index 9cfd5db5a096d..a993e8e27214b 100644 --- a/polkadot/zombienet-sdk-tests/tests/elastic_scaling/mod.rs +++ b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/mod.rs @@ -3,4 +3,5 @@ mod basic_3cores; mod doesnt_break_parachains; +mod slot_based_12cores; mod slot_based_3cores; diff --git a/polkadot/zombienet-sdk-tests/tests/elastic_scaling/slot_based_12cores.rs b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/slot_based_12cores.rs new file mode 100644 index 0000000000000..4d0e1adad0849 --- /dev/null +++ b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/slot_based_12cores.rs @@ -0,0 +1,129 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Test that a parachain that uses a single slot-based collator with elastic scaling can use 12 +// cores in order to achieve 500ms blocks. + +use anyhow::anyhow; + +use crate::helpers::{ + assert_finalized_block_height, assert_para_throughput, rococo, + rococo::runtime_types::{ + pallet_broker::coretime_interface::CoreAssignment, + polkadot_runtime_parachains::assigner_coretime::PartsOf57600, + }, +}; +use polkadot_primitives::Id as ParaId; +use serde_json::json; +use subxt::{OnlineClient, PolkadotConfig}; +use subxt_signer::sr25519::dev; +use zombienet_sdk::NetworkConfigBuilder; + +#[tokio::test(flavor = "multi_thread")] +async fn slot_based_12cores_test() -> Result<(), anyhow::Error> { + let _ = env_logger::try_init_from_env( + env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), + ); + + let images = zombienet_sdk::environment::get_images_from_env(); + + let config = NetworkConfigBuilder::new() + .with_relaychain(|r| { + let r = r + .with_chain("rococo-local") + .with_default_command("polkadot") + .with_default_image(images.polkadot.as_str()) + .with_default_args(vec![("-lparachain=debug").into()]) + .with_genesis_overrides(json!({ + "configuration": { + "config": { + "scheduler_params": { + "num_cores": 11, + "max_validators_per_core": 1 + }, + "async_backing_params": { + "max_candidate_depth": 24, + "allowed_ancestry_len": 2 + } + } + } + })) + // Have to set a `with_node` outside of the loop below, so that `r` has the right + // type. + .with_node(|node| node.with_name("validator-0")); + + (1..12) + .fold(r, |acc, i| acc.with_node(|node| node.with_name(&format!("validator-{i}")))) + }) + .with_parachain(|p| { + p.with_id(2300) + .with_default_command("test-parachain") + .with_default_image(images.cumulus.as_str()) + .with_chain("elastic-scaling-500ms") + .with_default_args(vec![ + ("--experimental-use-slot-based").into(), + ("-lparachain=debug,aura=debug").into(), + ]) + .with_collator(|n| n.with_name("collator-elastic")) + }) + .build() + .map_err(|e| { + let errs = e.into_iter().map(|e| e.to_string()).collect::>().join(" "); + anyhow!("config errs: {errs}") + })?; + + let spawn_fn = zombienet_sdk::environment::get_spawn_fn(); + let network = spawn_fn(config).await?; + + let relay_node = network.get_node("validator-0")?; + let para_node = network.get_node("collator-elastic")?; + + let relay_client: OnlineClient = relay_node.wait_client().await?; + let alice = dev::alice(); + + // Assign 11 extra cores to the parachain. + + relay_client + .tx() + .sign_and_submit_then_watch_default( + &rococo::tx() + .sudo() + .sudo(rococo::runtime_types::rococo_runtime::RuntimeCall::Utility( + rococo::runtime_types::pallet_utility::pallet::Call::batch { + calls: (0..11).map(|idx| rococo::runtime_types::rococo_runtime::RuntimeCall::Coretime( + rococo::runtime_types::polkadot_runtime_parachains::coretime::pallet::Call::assign_core { + core: idx, + begin: 0, + assignment: vec![(CoreAssignment::Task(2300), PartsOf57600(57600))], + end_hint: None + } + )).collect() + }, + )), + &alice, + ) + .await? + .wait_for_finalized_success() + .await?; + + log::info!("11 more cores assigned to the parachain"); + + // Expect a backed candidate count of at least 170 in 15 relay chain blocks + // (11.33 candidates per para per relay chain block). + // Note that only blocks after the first session change and blocks that don't contain a session + // change will be counted. + assert_para_throughput( + &relay_client, + 15, + [(ParaId::from(2300), 170..181)].into_iter().collect(), + ) + .await?; + + // Assert the parachain finalized block height is also on par with the number of backed + // candidates. + assert_finalized_block_height(¶_node.wait_client().await?, 158..181).await?; + + log::info!("Test finished successfully"); + + Ok(()) +} diff --git a/prdoc/pr_6983.prdoc b/prdoc/pr_6983.prdoc new file mode 100644 index 0000000000000..fddf831ead127 --- /dev/null +++ b/prdoc/pr_6983.prdoc @@ -0,0 +1,17 @@ +title: 'cumulus: bump PARENT_SEARCH_DEPTH to allow for 12-core elastic scaling' +doc: +- audience: Node Dev + description: | + Bumps the PARENT_SEARCH_DEPTH constant to a larger value (30). + This is a node-side limit that restricts the number of allowed pending availability candidates when choosing the parent parablock during authoring. + This limit is rather redundant, as the parachain runtime already restricts the unincluded segment length to the configured value in the + FixedVelocityConsensusHook. + For 12 cores, a value of 24 should be enough, but bumped it to 30 to have some extra buffer. + +crates: +- name: cumulus-client-consensus-aura + bump: patch +- name: cumulus-test-runtime + bump: minor +- name: cumulus-test-service + bump: minor From 4302f74f7874e6a894578731142a7b310a1449b0 Mon Sep 17 00:00:00 2001 From: xermicus Date: Tue, 28 Jan 2025 10:03:21 +0100 Subject: [PATCH 159/169] [pallet-revive] pack exceeding syscall arguments into registers (#7319) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR changes how we call runtime API methods with more than 6 arguments: They are no longer spilled to the stack but packed into registers instead. Pointers are 32 bit wide so we can pack two of them into a single 64 bit register. Since we mostly pass pointers, this technique effectively increases the number of arguments we can pass using the available registers. To make this work for `instantiate` too we now pass the code hash and the call data in the same buffer, akin to how the `create` family opcodes work in the EVM. The code hash is fixed in size, implying the start of the constructor call data. --------- Signed-off-by: xermicus Signed-off-by: Cyrill Leutwiler Co-authored-by: command-bot <> Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Alexander Theißen --- prdoc/pr_7319.prdoc | 16 + .../contracts/call_diverging_out_len.rs | 3 +- .../fixtures/contracts/caller_contract.rs | 46 +- .../fixtures/contracts/create1_with_value.rs | 14 +- .../create_storage_and_instantiate.rs | 8 +- .../contracts/destroy_and_transfer.rs | 4 +- .../contracts/instantiate_return_code.rs | 12 +- .../fixtures/contracts/return_data_api.rs | 12 +- substrate/frame/revive/proc-macro/src/lib.rs | 39 +- .../rpc/examples/js/pvm/FlipperCaller.polkavm | Bin 4584 -> 4615 bytes .../rpc/examples/js/pvm/PiggyBank.polkavm | Bin 5088 -> 5090 bytes .../frame/revive/src/benchmarking/mod.rs | 54 +- substrate/frame/revive/src/tests.rs | 14 +- substrate/frame/revive/src/wasm/runtime.rs | 59 +- substrate/frame/revive/src/weights.rs | 1004 ++++++++--------- substrate/frame/revive/uapi/src/host.rs | 5 +- .../frame/revive/uapi/src/host/riscv64.rs | 159 ++- substrate/frame/revive/uapi/src/lib.rs | 11 + 18 files changed, 715 insertions(+), 745 deletions(-) create mode 100644 prdoc/pr_7319.prdoc diff --git a/prdoc/pr_7319.prdoc b/prdoc/pr_7319.prdoc new file mode 100644 index 0000000000000..d572f7e707e1f --- /dev/null +++ b/prdoc/pr_7319.prdoc @@ -0,0 +1,16 @@ +title: '[pallet-revive] pack exceeding syscall arguments into registers' +doc: +- audience: Runtime Dev + description: |- + This PR changes how we call runtime API methods with more than 6 arguments: They are no longer spilled to the stack but packed into registers instead. Pointers are 32 bit wide so we can pack two of them into a single 64 bit register. Since we mostly pass pointers, this technique effectively increases the number of arguments we can pass using the available registers. + + To make this work for `instantiate` too we now pass the code hash and the call data in the same buffer, akin to how the `create` family opcodes work in the EVM. The code hash is fixed in size, implying the start of the constructor call data. +crates: +- name: pallet-revive-fixtures + bump: major +- name: pallet-revive-proc-macro + bump: major +- name: pallet-revive + bump: major +- name: pallet-revive-uapi + bump: major diff --git a/substrate/frame/revive/fixtures/contracts/call_diverging_out_len.rs b/substrate/frame/revive/fixtures/contracts/call_diverging_out_len.rs index 9a8fe5f5f6cc5..d084c4aed6df7 100644 --- a/substrate/frame/revive/fixtures/contracts/call_diverging_out_len.rs +++ b/substrate/frame/revive/fixtures/contracts/call_diverging_out_len.rs @@ -66,12 +66,11 @@ fn assert_instantiate(expected_output: [u8; BUF_SIZE]) { let output_buf_capped = &mut &mut output_buf[..N]; api::instantiate( - &code_hash, u64::MAX, u64::MAX, &[u8::MAX; 32], &[0; 32], - &[0; 32], + &code_hash, None, Some(output_buf_capped), None, diff --git a/substrate/frame/revive/fixtures/contracts/caller_contract.rs b/substrate/frame/revive/fixtures/contracts/caller_contract.rs index d042dc2c22a25..b6a9bf2895fa6 100644 --- a/substrate/frame/revive/fixtures/contracts/caller_contract.rs +++ b/substrate/frame/revive/fixtures/contracts/caller_contract.rs @@ -21,6 +21,9 @@ use common::{input, u256_bytes}; use uapi::{HostFn, HostFnImpl as api, ReturnErrorCode}; +const INPUT: [u8; 8] = [0u8, 1, 34, 51, 68, 85, 102, 119]; +const REVERTED_INPUT: [u8; 7] = [1u8, 34, 51, 68, 85, 102, 119]; + #[no_mangle] #[polkavm_derive::polkavm_export] pub extern "C" fn deploy() {} @@ -36,17 +39,21 @@ pub extern "C" fn call() { let salt = [0u8; 32]; // Callee will use the first 4 bytes of the input to return an exit status. - let input = [0u8, 1, 34, 51, 68, 85, 102, 119]; - let reverted_input = [1u8, 34, 51, 68, 85, 102, 119]; + let mut input_deploy = [0; 32 + INPUT.len()]; + input_deploy[..32].copy_from_slice(code_hash); + input_deploy[32..].copy_from_slice(&INPUT); + + let mut reverted_input_deploy = [0; 32 + REVERTED_INPUT.len()]; + reverted_input_deploy[..32].copy_from_slice(code_hash); + reverted_input_deploy[32..].copy_from_slice(&REVERTED_INPUT); // Fail to deploy the contract since it returns a non-zero exit status. let res = api::instantiate( - code_hash, u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. &[u8::MAX; 32], // No deposit limit. &value, - &reverted_input, + &reverted_input_deploy, None, None, Some(&salt), @@ -55,12 +62,11 @@ pub extern "C" fn call() { // Fail to deploy the contract due to insufficient ref_time weight. let res = api::instantiate( - code_hash, 1u64, // too little ref_time weight u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. &[u8::MAX; 32], // No deposit limit. &value, - &input, + &input_deploy, None, None, Some(&salt), @@ -69,12 +75,11 @@ pub extern "C" fn call() { // Fail to deploy the contract due to insufficient proof_size weight. let res = api::instantiate( - code_hash, u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. 1u64, // Too little proof_size weight &[u8::MAX; 32], // No deposit limit. &value, - &input, + &input_deploy, None, None, Some(&salt), @@ -85,12 +90,11 @@ pub extern "C" fn call() { let mut callee = [0u8; 20]; api::instantiate( - code_hash, u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. &[u8::MAX; 32], // No deposit limit. &value, - &input, + &input_deploy, Some(&mut callee), None, Some(&salt), @@ -101,11 +105,11 @@ pub extern "C" fn call() { let res = api::call( uapi::CallFlags::empty(), &callee, - u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. - u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. &[u8::MAX; 32], // No deposit limit. &value, - &reverted_input, + &REVERTED_INPUT, None, ); assert!(matches!(res, Err(ReturnErrorCode::CalleeReverted))); @@ -118,7 +122,7 @@ pub extern "C" fn call() { u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. &[u8::MAX; 32], // No deposit limit. &value, - &input, + &INPUT, None, ); assert!(matches!(res, Err(ReturnErrorCode::OutOfResources))); @@ -127,11 +131,11 @@ pub extern "C" fn call() { let res = api::call( uapi::CallFlags::empty(), &callee, - u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. - 1u64, // too little proof_size weight + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + 1u64, // too little proof_size weight &[u8::MAX; 32], // No deposit limit. &value, - &input, + &INPUT, None, ); assert!(matches!(res, Err(ReturnErrorCode::OutOfResources))); @@ -141,13 +145,13 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::empty(), &callee, - u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. - u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. &[u8::MAX; 32], // No deposit limit. &value, - &input, + &INPUT, Some(&mut &mut output[..]), ) .unwrap(); - assert_eq!(&output, &input[4..]) + assert_eq!(&output, &INPUT[4..]) } diff --git a/substrate/frame/revive/fixtures/contracts/create1_with_value.rs b/substrate/frame/revive/fixtures/contracts/create1_with_value.rs index 3554f8f620a29..a694a9b091898 100644 --- a/substrate/frame/revive/fixtures/contracts/create1_with_value.rs +++ b/substrate/frame/revive/fixtures/contracts/create1_with_value.rs @@ -34,16 +34,6 @@ pub extern "C" fn call() { api::value_transferred(&mut value); // Deploy the contract with no salt (equivalent to create1). - let ret = api::instantiate( - code_hash, - u64::MAX, - u64::MAX, - &[u8::MAX; 32], - &value, - &[], - None, - None, - None - ); - assert!(ret.is_ok()); + api::instantiate(u64::MAX, u64::MAX, &[u8::MAX; 32], &value, code_hash, None, None, None) + .unwrap(); } diff --git a/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs b/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs index f627bc8ba6c41..3db5ee1c573e1 100644 --- a/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs +++ b/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs @@ -30,22 +30,24 @@ pub extern "C" fn deploy() {} #[polkavm_derive::polkavm_export] pub extern "C" fn call() { input!( - input: [u8; 4], code_hash: &[u8; 32], + input: [u8; 4], deposit_limit: &[u8; 32], ); let value = u256_bytes(10_000u64); let salt = [0u8; 32]; let mut address = [0u8; 20]; + let mut deploy_input = [0; 32 + 4]; + deploy_input[..32].copy_from_slice(code_hash); + deploy_input[32..].copy_from_slice(&input); let ret = api::instantiate( - code_hash, u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. deposit_limit, &value, - input, + &deploy_input, Some(&mut address), None, Some(&salt), diff --git a/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs b/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs index c2c7da528ba7c..b5face97e2360 100644 --- a/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs +++ b/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs @@ -29,17 +29,15 @@ const VALUE: [u8; 32] = u256_bytes(65536); pub extern "C" fn deploy() { input!(code_hash: &[u8; 32],); - let input = [0u8; 0]; let mut address = [0u8; 20]; let salt = [47u8; 32]; api::instantiate( - code_hash, u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. &[u8::MAX; 32], // No deposit limit. &VALUE, - &input, + code_hash, Some(&mut address), None, Some(&salt), diff --git a/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs b/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs index f7cbd75be5aaa..a3643bdedbdbd 100644 --- a/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs +++ b/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs @@ -28,16 +28,14 @@ pub extern "C" fn deploy() {} #[no_mangle] #[polkavm_derive::polkavm_export] pub extern "C" fn call() { - input!(buffer, 36, code_hash: &[u8; 32],); - let input = &buffer[32..]; + input!(buffer: &[u8; 36],); let err_code = match api::instantiate( - code_hash, - u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. - u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. - &[u8::MAX; 32], // No deposit limit. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &u256_bytes(10_000u64), // Value to transfer. - input, + buffer, None, None, Some(&[0u8; 32]), // Salt. diff --git a/substrate/frame/revive/fixtures/contracts/return_data_api.rs b/substrate/frame/revive/fixtures/contracts/return_data_api.rs index 1407e5323ea18..e8aeeea44bde7 100644 --- a/substrate/frame/revive/fixtures/contracts/return_data_api.rs +++ b/substrate/frame/revive/fixtures/contracts/return_data_api.rs @@ -88,8 +88,9 @@ fn assert_balance_transfer_does_reset() { &[u8::MAX; 32], &u256_bytes(128), &[], - None - ).unwrap(); + None, + ) + .unwrap(); assert_return_data_size_of(0); } @@ -117,13 +118,16 @@ pub extern "C" fn call() { input }; let mut instantiate = |exit_flag| { + let input = construct_input(exit_flag); + let mut deploy_input = [0; 32 + INPUT_BUF_SIZE]; + deploy_input[..32].copy_from_slice(code_hash); + deploy_input[32..].copy_from_slice(&input); api::instantiate( - code_hash, u64::MAX, u64::MAX, &[u8::MAX; 32], &[0; 32], - &construct_input(exit_flag), + &deploy_input, Some(&mut address_buf), None, None, diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs index 6e38063d20a67..6f087c86b5ffd 100644 --- a/substrate/frame/revive/proc-macro/src/lib.rs +++ b/substrate/frame/revive/proc-macro/src/lib.rs @@ -355,6 +355,11 @@ where { const ALLOWED_REGISTERS: usize = 6; + // too many arguments + if param_names.clone().count() > ALLOWED_REGISTERS { + panic!("Syscalls take a maximum of {ALLOWED_REGISTERS} arguments"); + } + // all of them take one register but we truncate them before passing into the function // it is important to not allow any type which has illegal bit patterns like 'bool' if !param_types.clone().all(|ty| { @@ -369,39 +374,7 @@ where panic!("Only primitive unsigned integers are allowed as arguments to syscalls"); } - // too many arguments: pass as pointer to a struct in memory - if param_names.clone().count() > ALLOWED_REGISTERS { - let fields = param_names.clone().zip(param_types.clone()).map(|(name, ty)| { - quote! { - #name: #ty, - } - }); - return quote! { - #[derive(Default)] - #[repr(C)] - struct Args { - #(#fields)* - } - let Args { #(#param_names,)* } = { - let len = ::core::mem::size_of::(); - let mut args = Args::default(); - let ptr = &mut args as *mut Args as *mut u8; - // Safety - // 1. The struct is initialized at all times. - // 2. We only allow primitive integers (no bools) as arguments so every bit pattern is safe. - // 3. The reference doesn't outlive the args field. - // 4. There is only the single reference to the args field. - // 5. The length of the generated slice is the same as the struct. - let reference = unsafe { - ::core::slice::from_raw_parts_mut(ptr, len) - }; - memory.read_into_buf(__a0__ as _, reference)?; - args - }; - } - } - - // otherwise: one argument per register + // one argument per register let bindings = param_names.zip(param_types).enumerate().map(|(idx, (name, ty))| { let reg = quote::format_ident!("__a{}__", idx); quote! { diff --git a/substrate/frame/revive/rpc/examples/js/pvm/FlipperCaller.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/FlipperCaller.polkavm index 29efafd8722db556b949b04c47c88eeec07535a6..b7b037c1c7b31018fccc0d7bfc0e058d05b68588 100644 GIT binary patch delta 1257 zcmY+CZEV{_7{_zhG-o?a>P3CifX)q0)|7&_xl;=oQmD*oXH2rQuBrq~LyXxeoz^dq zQV-fB9Z81PHNJUCix6e%)D0no82vI0RHPwZq>5=&XyU`@2PF21(Upyo5z4u3P_g`+ z@1Ezc|NZVB-+naBBc*4*!-0G`v|KmqeEFsR{;{jBBd-kfAAiF&UTlS9UttymcmC|^ zKtG}F)OG3>b&?*VXK0o=#tbr_x|iI?s?t@}=#+Vgi1h<2quTRHkIo_FqcpQ%V{N+N!s-uX86R2~N{_|3|8a2L;3iVqiH zxpoH1!Cn#y35i4);>OB0EW1Lm4dM>^iAE36Tjp1y zh{YE59(W83^mE`A9-%K*@BULo{r?btTeE9BZf71*Eo};HfUfOs(=Ox@!2<-ZFu_@U z4w(OtTbN{$F?Kz*exxzKT9BCG=ha2{c24^1sk=h!iY* zpNZ^=lnhNF_Y+0oQfellD~2W+dRSqfx_3`SttHamA#IVgxi_gxU!+k=UxdU9B}fvV zaPO@=O=QV%z%`sm$UZJ^K%7MY8)E+l$5ju}MCnk?rb`#%*TfHrO}_l6v@~bf8E&RT$)= z)I-J8VA4LCqOg%4?y88v3GWT~_Yyu>9R^qMx#|{h27g)I96Y)dMNUVG+!+vPo}tOz zIeMB4O9E2Z9!x$!VSC@f)$Dh}qA%>v^0Fh#Ke;s`ivC0GBsHEpsr`J5#BKIojDwDlL- z0&>I@eE~Wr`D(MnnQ@8t`H<|Nhvz!sl tiv|6>t(Rjaf2r(l^48h4dBk^Kxj?!pnNf*qa!Q?$IG$|1G(FBb{srZnnKA$X delta 1264 zcmY+CZEO@p7{_mBuQ$7Uy>{;+$7!%!kL~8}R46XQ+SH`hv)#QEv%5g8N(e^uwlA^O zgtT;H+IunG!`Q$MC542f2~FCfiA3rb-_nR5h|vT|R16`xC?EKsX()*0u+(QOMM(CU zoq3-B{^z&P%&2&66VW<$cs$pFN63xvaqk9kFwve7uYWDUG4+!Ca3dz_+$Q`{CZ_i!{(`hsfgc7K-1Wwh+T&h_3w8mQ<)8|Zr#&!DUHvx?M) zLwK#0#i}#`aTvme7{RorLPjH0^(C-`X)Cw6<`%NLqE>I*>Z!N7Bl&6IvN8>t5Ia-W z$**+YcYJsT4|ScvmN^L)mmn@coQIglGV9H`VsYWNP9lIFU0q5gSaL{_N;s-Y5oSe| z1b1NF!NaU5Qk02|gP&!Jj4IW;={U18p7AiSH#44@$5y6^$(e#>HZw6rN-tG#!D7XdDIaovG+2M=~}F$=f|aN)P>-E4)^0_^ z5EM?)kU!njxol;$iGip=URusiXHCt@D9{s4Xbgg;aT!i}D6O`S~8#{*A=y6@x%R*ziKgqC_pze4u}1~$&! z(8-mui^fG|@&H@w38|7`dk8%8;^`QuT! z4Cgm77&X{LIX{?le@)Yb^ui)sCVH~!B91K5cdDDw&-BykB>IS6sg6sz>jv?9^3XFR zb0W_{?>rsxx$82~gpN<(g*2ganm!?XH(aoVSS(Z<`^}68gT+)V77VF|)9oA;6UoH4 zwml_|Pl@`Gw2@HNP%*X55me{2sDdaIQyp>bDF~suP|>!<6d$ZD2(cF1RtmPg$^AwQ zCR3+XTU3qoPG?F~McomK;AykP)G%!5THZ6NyFYt)vdeKCrA1Ve2{lw#yQX$EhVw{l z_5t%)(J3fm@la7pgZ{)(Np;fq^@h4?J0;fgyZPFJn0P-}5L!k=@h}*jchYc%o__&s C$CUd3 diff --git a/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm index 78455fcdd7c64a3a1f5e93b6d62cd03b46eb5953..2fc5e139825aa174d85127c3f29543fd7ebe05fc 100644 GIT binary patch delta 1076 zcmY*XeP|nH9KQE@$$NMC=p}6J=!UzUZ__-F`=i28CZVGE1)UXK>zwhucHPHu&+m@s zeSXjH`EgfioNsjMPj@lQe9rl2?+|xj|KMOY;p{&+H2CuCPV1V&X4CtaLrju6&eX6w z*a-V9n`RdsRmVYChOhCj3VGoRVYB$0_@wIvSJl<)ZuIQ)obf#4{mc77ZJo4Dib@G7 zbzTS0vw^fYQ?$oCxC#_e!@ z#NE-NB58Jf1HLB9jvUxd-gSPK8e>(c^14*#{3i+zvsy3>aPR`C;^4GhPT8dds)bkx zOeExDLSDx+^s>v`7%as5z=SCmO}Q{o0ux8&xp6V`qCRm^E?$%i@w9z7cfqalRaK~p z$ykrb0|WkU=P}?}XnHRVE+y0King534x(b0RAW;Bhh{_*#ZLIHO=>n=tR-xzuYo7@HOkQ%e zf|F#*RRLd-BW?wJOs3r*0w?M6^nxjJ(sO0!4~tP~+{(oCO#HD+V=xof4##J~TNJ7w zLNFYg1(|s0+}P&jrj}c4&f8Csv))}`FIn2P2eKPoz9QQpWU0<-N_G|P3bSZq5T=9LGGEqA0$ChRdAVsmP zZp#XHSo!iK3XHE)6cjj3U*74q%4my)3VAecN!W;B3*iXGeRngCZh0zvpB_hyt_L)R zi3Cf!OuNbwZt&&u2)9_N$q~)!HY^lrN4WXK1D3Y^>WES8%p?k1<(4}W4IDw3rXJDO zS~S0MFH!S~EGWo0Ko_F}%!YA0{~nt!Q=@xhbirqF*>XNiWz_KQ0p+?8Hc{>_gD`_5 c9k$ylWtyg@C<<;z17Z%f`4!CwjE*q>15_JcNdN!< delta 1095 zcmY*WZD?Cn7{2H9CgURfGlIj|zj)BGzrzcoMs{!hO!m zeeUx<=Xu}z>(=5{yZOv+K`3VJ%RMiNeFp~y#*f+iUpPE)=rue2L4)IO91va+lEPV` z3Wi`O+z(T5#a6QQp*%V&9g(U0mHd!nzvBs|S6Ne@tqwT%Ij5c7u4UJ|HMQz?b&q;X zJw9iG=U~9MmQ)vEvTgw;>*ryvt_Vlm#r}E&g$>DPs-Drc7+PiDSM3CItXgc8m#X3& znOmp6W^9M}SW}NJo!C44>eCIPX(Rd|B>G_vi=&qfi6PL-J{Pg)9eb>!;QVc2QH*!& zKHj+fCy}*?A?2*N-i+U8LECk3mEEyrK?{4^{^g6K&_IS{s`d7}F*FFHdI}Kz3s53@ zp?pl2k4aF9>Q{i2v%(;0eZvI{WqKZza*0WhOe}(2qL}9dNs`=Bu%c)qay`jda2T=!f#HMz(^h!)Qx81w%{OEc1iR(#lfZcUn00nlg<_xH1?dlmY$Zn`_ zHZ9+dp}xO{t#BoCKhMl3GLWgZ{>V~pAGpLmazCuzxLu~&e&z+3YUDe1-FLD~jvHw8}9f@uvs>r4YjK;?>7g3zwrIqt{^L9^=8*N-;wvAwbgMloUal;26I9EqJ h8rfioCrsmI**c|Z8qH*)Joo7~OvXnf+C}_+;a@nkUt9nH diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index a19ed28dd9b09..d3d648cd2125d 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -39,7 +39,7 @@ use frame_support::{ weights::{Weight, WeightMeter}, }; use frame_system::RawOrigin; -use pallet_revive_uapi::{CallFlags, ReturnErrorCode, StorageFlags}; +use pallet_revive_uapi::{pack_hi_lo, CallFlags, ReturnErrorCode, StorageFlags}; use sp_runtime::traits::{Bounded, Hash}; /// How many runs we do per API benchmark. @@ -1650,16 +1650,12 @@ mod benchmarks { { result = runtime.bench_call( memory.as_mut_slice(), - CallFlags::CLONE_INPUT.bits(), // flags - 0, // callee_ptr - u64::MAX, // ref_time_limit - u64::MAX, // proof_size_limit - callee_len, // deposit_ptr - callee_len + deposit_len, // value_ptr - 0, // input_data_ptr - 0, // input_data_len - SENTINEL, // output_ptr - 0, // output_len_ptr + pack_hi_lo(CallFlags::CLONE_INPUT.bits(), 0), // flags + callee + u64::MAX, // ref_time_limit + u64::MAX, // proof_size_limit + pack_hi_lo(callee_len, callee_len + deposit_len), // deposit_ptr + value_pr + pack_hi_lo(0, 0), // input len + data ptr + pack_hi_lo(0, SENTINEL), // output len + data ptr ); } @@ -1690,15 +1686,12 @@ mod benchmarks { { result = runtime.bench_delegate_call( memory.as_mut_slice(), - 0, // flags - 0, // address_ptr - u64::MAX, // ref_time_limit - u64::MAX, // proof_size_limit - address_len, // deposit_ptr - 0, // input_data_ptr - 0, // input_data_len - SENTINEL, // output_ptr - 0, + pack_hi_lo(0, 0), // flags + address ptr + u64::MAX, // ref_time_limit + u64::MAX, // proof_size_limit + address_len, // deposit_ptr + pack_hi_lo(0, 0), // input len + data ptr + pack_hi_lo(0, SENTINEL), // output len + ptr ); } @@ -1713,7 +1706,6 @@ mod benchmarks { let code = WasmModule::dummy(); let hash = Contract::::with_index(1, WasmModule::dummy(), vec![])?.info()?.code_hash; let hash_bytes = hash.encode(); - let hash_len = hash_bytes.len() as u32; let value: BalanceOf = 1_000_000u32.into(); let value_bytes = Into::::into(value).encode(); @@ -1732,11 +1724,12 @@ mod benchmarks { let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, vec![]); let input = vec![42u8; i as _]; + let input_len = hash_bytes.len() as u32 + input.len() as u32; let salt = [42u8; 32]; let deployer = T::AddressMapper::to_address(&account_id); let addr = crate::address::create2(&deployer, &code.code, &input, &salt); let account_id = T::AddressMapper::to_fallback_account_id(&addr); - let mut memory = memory!(hash_bytes, deposit_bytes, value_bytes, input, salt,); + let mut memory = memory!(hash_bytes, input, deposit_bytes, value_bytes, salt,); let mut offset = { let mut current = 0u32; @@ -1753,17 +1746,12 @@ mod benchmarks { { result = runtime.bench_instantiate( memory.as_mut_slice(), - 0, // code_hash_ptr - u64::MAX, // ref_time_limit - u64::MAX, // proof_size_limit - offset(hash_len), // deposit_ptr - offset(deposit_len), // value_ptr - offset(value_len), // input_data_ptr - i, // input_data_len - SENTINEL, // address_ptr - SENTINEL, // output_ptr - 0, // output_len_ptr - offset(i), // salt_ptr + u64::MAX, // ref_time_limit + u64::MAX, // proof_size_limit + pack_hi_lo(offset(input_len), offset(deposit_len)), // deopsit_ptr + value_ptr + pack_hi_lo(input_len, 0), // input_data_len + input_data + pack_hi_lo(0, SENTINEL), // output_len_ptr + output_ptr + pack_hi_lo(SENTINEL, offset(value_len)), // address_ptr + salt_ptr ); } diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index d8b60e38da5ef..db4b4da2b05e3 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -1584,13 +1584,13 @@ fn instantiate_return_code() { // Contract has only the minimal balance so any transfer will fail. ::Currency::set_balance(&contract.account_id, min_balance); let result = builder::bare_call(contract.addr) - .data(callee_hash.clone()) + .data(callee_hash.iter().chain(&0u32.to_le_bytes()).cloned().collect()) .build_and_unwrap_result(); assert_return_code!(result, RuntimeReturnCode::TransferFailed); // Contract has enough balance but the passed code hash is invalid ::Currency::set_balance(&contract.account_id, min_balance + 10_000); - let result = builder::bare_call(contract.addr).data(vec![0; 33]).build(); + let result = builder::bare_call(contract.addr).data(vec![0; 36]).build(); assert_err!(result.result, >::CodeNotFound); // Contract has enough balance but callee reverts because "1" is passed. @@ -3129,7 +3129,7 @@ fn deposit_limit_in_nested_instantiate() { let ret = builder::bare_call(addr_caller) .origin(RuntimeOrigin::signed(BOB)) .storage_deposit_limit(DepositLimit::Balance(callee_info_len + 2 + ED + 1)) - .data((0u32, &code_hash_callee, &U256::MAX.to_little_endian()).encode()) + .data((&code_hash_callee, 0u32, &U256::MAX.to_little_endian()).encode()) .build_and_unwrap_result(); assert_return_code!(ret, RuntimeReturnCode::OutOfResources); // The charges made on instantiation should be rolled back. @@ -3141,7 +3141,7 @@ fn deposit_limit_in_nested_instantiate() { let ret = builder::bare_call(addr_caller) .origin(RuntimeOrigin::signed(BOB)) .storage_deposit_limit(DepositLimit::Balance(callee_info_len + 2 + ED + 2)) - .data((1u32, &code_hash_callee, U256::from(0u64)).encode()) + .data((&code_hash_callee, 1u32, U256::from(0u64)).encode()) .build_and_unwrap_result(); assert_return_code!(ret, RuntimeReturnCode::OutOfResources); // The charges made on the instantiation should be rolled back. @@ -3153,7 +3153,7 @@ fn deposit_limit_in_nested_instantiate() { let ret = builder::bare_call(addr_caller) .origin(RuntimeOrigin::signed(BOB)) .storage_deposit_limit(DepositLimit::Balance(callee_info_len + 2 + ED + 2)) - .data((0u32, &code_hash_callee, U256::from(callee_info_len + 2 + ED + 1)).encode()) + .data((&code_hash_callee, 1u32, U256::from(callee_info_len + 2 + ED + 1)).encode()) .build_and_unwrap_result(); assert_return_code!(ret, RuntimeReturnCode::OutOfResources); // The charges made on the instantiation should be rolled back. @@ -3166,7 +3166,7 @@ fn deposit_limit_in_nested_instantiate() { let ret = builder::bare_call(addr_caller) .origin(RuntimeOrigin::signed(BOB)) .storage_deposit_limit(DepositLimit::Balance(callee_info_len + 2 + ED + 3)) // enough parent limit - .data((1u32, &code_hash_callee, U256::from(callee_info_len + 2 + ED + 2)).encode()) + .data((&code_hash_callee, 1u32, U256::from(callee_info_len + 2 + ED + 2)).encode()) .build_and_unwrap_result(); assert_return_code!(ret, RuntimeReturnCode::OutOfResources); // The charges made on the instantiation should be rolled back. @@ -3176,7 +3176,7 @@ fn deposit_limit_in_nested_instantiate() { let result = builder::bare_call(addr_caller) .origin(RuntimeOrigin::signed(BOB)) .storage_deposit_limit((callee_info_len + 2 + ED + 4 + 2).into()) - .data((1u32, &code_hash_callee, U256::from(callee_info_len + 2 + ED + 3 + 2)).encode()) + .data((&code_hash_callee, 1u32, U256::from(callee_info_len + 2 + ED + 3 + 2)).encode()) .build(); let returned = result.result.unwrap(); diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 4fbcfe1b47f5b..d02c75247a4fe 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -569,6 +569,11 @@ fn already_charged(_: u32) -> Option { None } +/// Helper to extract two `u32` values from a given `u64` register. +fn extract_hi_lo(reg: u64) -> (u32, u32) { + ((reg >> 32) as u32, reg as u32) +} + /// Can only be used for one call. pub struct Runtime<'a, E: Ext, M: ?Sized> { ext: &'a mut E, @@ -1199,17 +1204,18 @@ pub mod env { fn call( &mut self, memory: &mut M, - flags: u32, - callee_ptr: u32, + flags_and_callee: u64, ref_time_limit: u64, proof_size_limit: u64, - deposit_ptr: u32, - value_ptr: u32, - input_data_ptr: u32, - input_data_len: u32, - output_ptr: u32, - output_len_ptr: u32, + deposit_and_value: u64, + input_data: u64, + output_data: u64, ) -> Result { + let (flags, callee_ptr) = extract_hi_lo(flags_and_callee); + let (deposit_ptr, value_ptr) = extract_hi_lo(deposit_and_value); + let (input_data_len, input_data_ptr) = extract_hi_lo(input_data); + let (output_len_ptr, output_ptr) = extract_hi_lo(output_data); + self.call( memory, CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, @@ -1230,16 +1236,17 @@ pub mod env { fn delegate_call( &mut self, memory: &mut M, - flags: u32, - address_ptr: u32, + flags_and_callee: u64, ref_time_limit: u64, proof_size_limit: u64, deposit_ptr: u32, - input_data_ptr: u32, - input_data_len: u32, - output_ptr: u32, - output_len_ptr: u32, + input_data: u64, + output_data: u64, ) -> Result { + let (flags, address_ptr) = extract_hi_lo(flags_and_callee); + let (input_data_len, input_data_ptr) = extract_hi_lo(input_data); + let (output_len_ptr, output_ptr) = extract_hi_lo(output_data); + self.call( memory, CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, @@ -1261,18 +1268,24 @@ pub mod env { fn instantiate( &mut self, memory: &mut M, - code_hash_ptr: u32, ref_time_limit: u64, proof_size_limit: u64, - deposit_ptr: u32, - value_ptr: u32, - input_data_ptr: u32, - input_data_len: u32, - address_ptr: u32, - output_ptr: u32, - output_len_ptr: u32, - salt_ptr: u32, + deposit_and_value: u64, + input_data: u64, + output_data: u64, + address_and_salt: u64, ) -> Result { + let (deposit_ptr, value_ptr) = extract_hi_lo(deposit_and_value); + let (input_data_len, code_hash_ptr) = extract_hi_lo(input_data); + let (output_len_ptr, output_ptr) = extract_hi_lo(output_data); + let (address_ptr, salt_ptr) = extract_hi_lo(address_and_salt); + let Some(input_data_ptr) = code_hash_ptr.checked_add(32) else { + return Err(Error::::OutOfBounds.into()); + }; + let Some(input_data_len) = input_data_len.checked_sub(32) else { + return Err(Error::::OutOfBounds.into()); + }; + self.instantiate( memory, code_hash_ptr, diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs index 52153d74ca758..086b64c5dde40 100644 --- a/substrate/frame/revive/src/weights.rs +++ b/substrate/frame/revive/src/weights.rs @@ -18,9 +18,9 @@ //! Autogenerated weights for `pallet_revive` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-12-19, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-01-24, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `19e0eeaa3bc2`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `cc3478f23e9a`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: @@ -141,8 +141,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `1594` - // Minimum execution time: 2_859_000 picoseconds. - Weight::from_parts(3_007_000, 1594) + // Minimum execution time: 2_796_000 picoseconds. + Weight::from_parts(2_958_000, 1594) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -152,10 +152,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `425 + k * (69 ±0)` // Estimated: `415 + k * (70 ±0)` - // Minimum execution time: 15_640_000 picoseconds. - Weight::from_parts(1_609_026, 415) - // Standard Error: 1_359 - .saturating_add(Weight::from_parts(1_204_420, 0).saturating_mul(k.into())) + // Minimum execution time: 16_135_000 picoseconds. + Weight::from_parts(3_227_098, 415) + // Standard Error: 1_106 + .saturating_add(Weight::from_parts(1_175_210, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -177,17 +177,17 @@ impl WeightInfo for SubstrateWeight { /// The range of component `c` is `[0, 262144]`. fn call_with_code_per_byte(_c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1463` - // Estimated: `7403` - // Minimum execution time: 89_437_000 picoseconds. - Weight::from_parts(94_285_182, 7403) + // Measured: `1502` + // Estimated: `7442` + // Minimum execution time: 89_144_000 picoseconds. + Weight::from_parts(93_719_381, 7442) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Balances::Holds` (r:2 w:2) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(409), added: 2884, mode: `Measured`) /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) /// Storage: `Revive::ContractInfoOf` (r:1 w:1) @@ -202,14 +202,14 @@ impl WeightInfo for SubstrateWeight { /// The range of component `i` is `[0, 262144]`. fn instantiate_with_code(c: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `364` - // Estimated: `6327` - // Minimum execution time: 187_904_000 picoseconds. - Weight::from_parts(153_252_081, 6327) - // Standard Error: 11 - .saturating_add(Weight::from_parts(49, 0).saturating_mul(c.into())) - // Standard Error: 11 - .saturating_add(Weight::from_parts(4_528, 0).saturating_mul(i.into())) + // Measured: `401` + // Estimated: `6349` + // Minimum execution time: 185_726_000 picoseconds. + Weight::from_parts(165_030_228, 6349) + // Standard Error: 10 + .saturating_add(Weight::from_parts(10, 0).saturating_mul(c.into())) + // Standard Error: 10 + .saturating_add(Weight::from_parts(4_453, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -226,16 +226,16 @@ impl WeightInfo for SubstrateWeight { /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(409), added: 2884, mode: `Measured`) /// The range of component `i` is `[0, 262144]`. fn instantiate(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1296` - // Estimated: `4758` - // Minimum execution time: 154_656_000 picoseconds. - Weight::from_parts(139_308_398, 4758) - // Standard Error: 16 - .saturating_add(Weight::from_parts(4_421, 0).saturating_mul(i.into())) + // Measured: `1294` + // Estimated: `4739` + // Minimum execution time: 154_669_000 picoseconds. + Weight::from_parts(138_463_785, 4739) + // Standard Error: 15 + .saturating_add(Weight::from_parts(4_389, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -253,43 +253,41 @@ impl WeightInfo for SubstrateWeight { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) fn call() -> Weight { // Proof Size summary in bytes: - // Measured: `1463` - // Estimated: `7403` - // Minimum execution time: 138_815_000 picoseconds. - Weight::from_parts(149_067_000, 7403) + // Measured: `1502` + // Estimated: `7442` + // Minimum execution time: 137_822_000 picoseconds. + Weight::from_parts(146_004_000, 7442) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(409), added: 2884, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:0 w:1) /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// The range of component `c` is `[0, 262144]`. - fn upload_code(c: u32, ) -> Weight { + fn upload_code(_c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `109` - // Estimated: `3574` - // Minimum execution time: 49_978_000 picoseconds. - Weight::from_parts(51_789_325, 3574) - // Standard Error: 0 - .saturating_add(Weight::from_parts(1, 0).saturating_mul(c.into())) + // Measured: `164` + // Estimated: `3629` + // Minimum execution time: 53_476_000 picoseconds. + Weight::from_parts(55_795_699, 3629) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(409), added: 2884, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:0 w:1) /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) fn remove_code() -> Weight { // Proof Size summary in bytes: - // Measured: `285` - // Estimated: `3750` - // Minimum execution time: 43_833_000 picoseconds. - Weight::from_parts(44_660_000, 3750) + // Measured: `322` + // Estimated: `3787` + // Minimum execution time: 41_955_000 picoseconds. + Weight::from_parts(43_749_000, 3787) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -301,34 +299,34 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `529` // Estimated: `6469` - // Minimum execution time: 26_717_000 picoseconds. - Weight::from_parts(28_566_000, 6469) + // Minimum execution time: 22_763_000 picoseconds. + Weight::from_parts(23_219_000, 6469) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } /// Storage: `Revive::AddressSuffix` (r:1 w:1) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(409), added: 2884, mode: `Measured`) fn map_account() -> Weight { // Proof Size summary in bytes: - // Measured: `109` - // Estimated: `3574` - // Minimum execution time: 39_401_000 picoseconds. - Weight::from_parts(40_542_000, 3574) + // Measured: `164` + // Estimated: `3629` + // Minimum execution time: 45_478_000 picoseconds. + Weight::from_parts(46_658_000, 3629) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(409), added: 2884, mode: `Measured`) /// Storage: `Revive::AddressSuffix` (r:0 w:1) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) fn unmap_account() -> Weight { // Proof Size summary in bytes: - // Measured: `56` - // Estimated: `3521` - // Minimum execution time: 31_570_000 picoseconds. - Weight::from_parts(32_302_000, 3521) + // Measured: `93` + // Estimated: `3558` + // Minimum execution time: 33_359_000 picoseconds. + Weight::from_parts(34_196_000, 3558) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -340,8 +338,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 13_607_000 picoseconds. - Weight::from_parts(13_903_000, 3610) + // Minimum execution time: 13_663_000 picoseconds. + Weight::from_parts(14_278_000, 3610) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// The range of component `r` is `[0, 1600]`. @@ -349,24 +347,24 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_400_000 picoseconds. - Weight::from_parts(8_388_251, 0) - // Standard Error: 283 - .saturating_add(Weight::from_parts(165_630, 0).saturating_mul(r.into())) + // Minimum execution time: 6_966_000 picoseconds. + Weight::from_parts(7_708_050, 0) + // Standard Error: 238 + .saturating_add(Weight::from_parts(167_115, 0).saturating_mul(r.into())) } fn seal_caller() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 275_000 picoseconds. - Weight::from_parts(305_000, 0) + // Minimum execution time: 332_000 picoseconds. + Weight::from_parts(378_000, 0) } fn seal_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 224_000 picoseconds. - Weight::from_parts(265_000, 0) + // Minimum execution time: 303_000 picoseconds. + Weight::from_parts(329_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -374,18 +372,18 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `306` // Estimated: `3771` - // Minimum execution time: 10_004_000 picoseconds. - Weight::from_parts(10_336_000, 3771) + // Minimum execution time: 10_014_000 picoseconds. + Weight::from_parts(10_549_000, 3771) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) fn seal_to_account_id() -> Weight { // Proof Size summary in bytes: - // Measured: `212` - // Estimated: `3677` - // Minimum execution time: 4_000_000 picoseconds. - Weight::from_parts(4_000_000, 3677) + // Measured: `248` + // Estimated: `3713` + // Minimum execution time: 9_771_000 picoseconds. + Weight::from_parts(10_092_000, 3713) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) @@ -394,16 +392,16 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `403` // Estimated: `3868` - // Minimum execution time: 11_054_000 picoseconds. - Weight::from_parts(11_651_000, 3868) + // Minimum execution time: 11_260_000 picoseconds. + Weight::from_parts(11_626_000, 3868) .saturating_add(T::DbWeight::get().reads(1_u64)) } fn seal_own_code_hash() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 252_000 picoseconds. - Weight::from_parts(305_000, 0) + // Minimum execution time: 307_000 picoseconds. + Weight::from_parts(328_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -413,51 +411,51 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `473` // Estimated: `3938` - // Minimum execution time: 14_461_000 picoseconds. - Weight::from_parts(15_049_000, 3938) + // Minimum execution time: 14_675_000 picoseconds. + Weight::from_parts(15_168_000, 3938) .saturating_add(T::DbWeight::get().reads(2_u64)) } fn seal_caller_is_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 312_000 picoseconds. - Weight::from_parts(338_000, 0) + // Minimum execution time: 332_000 picoseconds. + Weight::from_parts(357_000, 0) } fn seal_caller_is_root() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 243_000 picoseconds. - Weight::from_parts(299_000, 0) + // Minimum execution time: 298_000 picoseconds. + Weight::from_parts(332_000, 0) } fn seal_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 231_000 picoseconds. - Weight::from_parts(271_000, 0) + // Minimum execution time: 313_000 picoseconds. + Weight::from_parts(336_000, 0) } fn seal_weight_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 683_000 picoseconds. - Weight::from_parts(732_000, 0) + // Minimum execution time: 663_000 picoseconds. + Weight::from_parts(730_000, 0) } fn seal_ref_time_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 226_000 picoseconds. - Weight::from_parts(273_000, 0) + // Minimum execution time: 292_000 picoseconds. + Weight::from_parts(344_000, 0) } fn seal_balance() -> Weight { // Proof Size summary in bytes: // Measured: `102` // Estimated: `0` - // Minimum execution time: 4_626_000 picoseconds. - Weight::from_parts(4_842_000, 0) + // Minimum execution time: 4_604_000 picoseconds. + Weight::from_parts(4_875_000, 0) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -467,8 +465,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `264` // Estimated: `3729` - // Minimum execution time: 12_309_000 picoseconds. - Weight::from_parts(12_653_000, 3729) + // Minimum execution time: 12_252_000 picoseconds. + Weight::from_parts(12_641_000, 3729) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// Storage: `Revive::ImmutableDataOf` (r:1 w:0) @@ -478,10 +476,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `238 + n * (1 ±0)` // Estimated: `3703 + n * (1 ±0)` - // Minimum execution time: 5_838_000 picoseconds. - Weight::from_parts(9_570_778, 3703) - // Standard Error: 19 - .saturating_add(Weight::from_parts(721, 0).saturating_mul(n.into())) + // Minimum execution time: 6_005_000 picoseconds. + Weight::from_parts(9_550_692, 3703) + // Standard Error: 18 + .saturating_add(Weight::from_parts(710, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -492,67 +490,67 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_910_000 picoseconds. - Weight::from_parts(2_205_396, 0) + // Minimum execution time: 1_981_000 picoseconds. + Weight::from_parts(2_297_488, 0) // Standard Error: 2 - .saturating_add(Weight::from_parts(538, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(528, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().writes(1_u64)) } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 224_000 picoseconds. - Weight::from_parts(274_000, 0) + // Minimum execution time: 279_000 picoseconds. + Weight::from_parts(309_000, 0) } fn seal_minimum_balance() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 231_000 picoseconds. - Weight::from_parts(279_000, 0) + // Minimum execution time: 289_000 picoseconds. + Weight::from_parts(315_000, 0) } fn seal_return_data_size() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 229_000 picoseconds. - Weight::from_parts(267_000, 0) + // Minimum execution time: 253_000 picoseconds. + Weight::from_parts(310_000, 0) } fn seal_call_data_size() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 218_000 picoseconds. - Weight::from_parts(267_000, 0) + // Minimum execution time: 291_000 picoseconds. + Weight::from_parts(338_000, 0) } fn seal_gas_limit() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 225_000 picoseconds. - Weight::from_parts(280_000, 0) + // Minimum execution time: 266_000 picoseconds. + Weight::from_parts(331_000, 0) } fn seal_gas_price() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 274_000 picoseconds. - Weight::from_parts(323_000, 0) + // Minimum execution time: 250_000 picoseconds. + Weight::from_parts(314_000, 0) } fn seal_base_fee() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 239_000 picoseconds. - Weight::from_parts(290_000, 0) + // Minimum execution time: 266_000 picoseconds. + Weight::from_parts(341_000, 0) } fn seal_block_number() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 224_000 picoseconds. - Weight::from_parts(274_000, 0) + // Minimum execution time: 281_000 picoseconds. + Weight::from_parts(314_000, 0) } /// Storage: `System::BlockHash` (r:1 w:0) /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `Measured`) @@ -560,60 +558,60 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `30` // Estimated: `3495` - // Minimum execution time: 3_430_000 picoseconds. - Weight::from_parts(3_692_000, 3495) + // Minimum execution time: 3_557_000 picoseconds. + Weight::from_parts(3_816_000, 3495) .saturating_add(T::DbWeight::get().reads(1_u64)) } fn seal_now() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 241_000 picoseconds. - Weight::from_parts(290_000, 0) + // Minimum execution time: 280_000 picoseconds. + Weight::from_parts(316_000, 0) } fn seal_weight_to_fee() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_355_000 picoseconds. - Weight::from_parts(1_493_000, 0) + // Minimum execution time: 1_413_000 picoseconds. + Weight::from_parts(1_477_000, 0) } /// The range of component `n` is `[0, 262140]`. fn seal_copy_to_contract(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 348_000 picoseconds. - Weight::from_parts(1_004_890, 0) + // Minimum execution time: 383_000 picoseconds. + Weight::from_parts(602_481, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(202, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(201, 0).saturating_mul(n.into())) } fn seal_call_data_load() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 222_000 picoseconds. - Weight::from_parts(256_000, 0) + // Minimum execution time: 327_000 picoseconds. + Weight::from_parts(365_000, 0) } /// The range of component `n` is `[0, 262144]`. fn seal_call_data_copy(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 240_000 picoseconds. - Weight::from_parts(330_609, 0) + // Minimum execution time: 334_000 picoseconds. + Weight::from_parts(205_756, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(114, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(116, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262140]`. fn seal_return(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 232_000 picoseconds. - Weight::from_parts(264_000, 0) + // Minimum execution time: 278_000 picoseconds. + Weight::from_parts(611_031, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(208, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(202, 0).saturating_mul(n.into())) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -628,12 +626,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `n` is `[0, 32]`. fn seal_terminate(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `322 + n * (88 ±0)` - // Estimated: `3787 + n * (2563 ±0)` - // Minimum execution time: 21_920_000 picoseconds. - Weight::from_parts(21_725_868, 3787) - // Standard Error: 11_165 - .saturating_add(Weight::from_parts(4_317_986, 0).saturating_mul(n.into())) + // Measured: `324 + n * (88 ±0)` + // Estimated: `3791 + n * (2563 ±0)` + // Minimum execution time: 18_544_000 picoseconds. + Weight::from_parts(18_412_253, 3791) + // Standard Error: 12_785 + .saturating_add(Weight::from_parts(4_214_449, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(4_u64)) @@ -646,12 +644,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_140_000 picoseconds. - Weight::from_parts(4_259_301, 0) - // Standard Error: 3_362 - .saturating_add(Weight::from_parts(194_546, 0).saturating_mul(t.into())) - // Standard Error: 34 - .saturating_add(Weight::from_parts(774, 0).saturating_mul(n.into())) + // Minimum execution time: 4_156_000 picoseconds. + Weight::from_parts(4_120_442, 0) + // Standard Error: 3_278 + .saturating_add(Weight::from_parts(212_768, 0).saturating_mul(t.into())) + // Standard Error: 33 + .saturating_add(Weight::from_parts(1_199, 0).saturating_mul(n.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -659,8 +657,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `680` // Estimated: `680` - // Minimum execution time: 10_747_000 picoseconds. - Weight::from_parts(11_276_000, 680) + // Minimum execution time: 11_065_000 picoseconds. + Weight::from_parts(11_573_000, 680) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -669,8 +667,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `10690` // Estimated: `10690` - // Minimum execution time: 42_076_000 picoseconds. - Weight::from_parts(43_381_000, 10690) + // Minimum execution time: 42_728_000 picoseconds. + Weight::from_parts(43_764_000, 10690) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -679,8 +677,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `680` // Estimated: `680` - // Minimum execution time: 11_703_000 picoseconds. - Weight::from_parts(12_308_000, 680) + // Minimum execution time: 12_376_000 picoseconds. + Weight::from_parts(12_658_000, 680) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -690,8 +688,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `10690` // Estimated: `10690` - // Minimum execution time: 43_460_000 picoseconds. - Weight::from_parts(45_165_000, 10690) + // Minimum execution time: 44_344_000 picoseconds. + Weight::from_parts(45_753_000, 10690) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -703,12 +701,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + o * (1 ±0)` // Estimated: `247 + o * (1 ±0)` - // Minimum execution time: 9_087_000 picoseconds. - Weight::from_parts(11_787_486, 247) - // Standard Error: 179 - .saturating_add(Weight::from_parts(976, 0).saturating_mul(n.into())) - // Standard Error: 179 - .saturating_add(Weight::from_parts(3_151, 0).saturating_mul(o.into())) + // Minimum execution time: 9_333_000 picoseconds. + Weight::from_parts(12_118_514, 247) + // Standard Error: 187 + .saturating_add(Weight::from_parts(1_212, 0).saturating_mul(n.into())) + // Standard Error: 187 + .saturating_add(Weight::from_parts(3_114, 0).saturating_mul(o.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) @@ -720,10 +718,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_611_000 picoseconds. - Weight::from_parts(11_791_390, 247) - // Standard Error: 308 - .saturating_add(Weight::from_parts(3_943, 0).saturating_mul(n.into())) + // Minimum execution time: 8_800_000 picoseconds. + Weight::from_parts(12_126_263, 247) + // Standard Error: 310 + .saturating_add(Weight::from_parts(4_181, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -735,10 +733,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_389_000 picoseconds. - Weight::from_parts(11_625_480, 247) - // Standard Error: 315 - .saturating_add(Weight::from_parts(4_487, 0).saturating_mul(n.into())) + // Minimum execution time: 8_612_000 picoseconds. + Weight::from_parts(11_888_491, 247) + // Standard Error: 322 + .saturating_add(Weight::from_parts(4_319, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -749,10 +747,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 7_947_000 picoseconds. - Weight::from_parts(10_970_587, 247) - // Standard Error: 310 - .saturating_add(Weight::from_parts(3_675, 0).saturating_mul(n.into())) + // Minimum execution time: 8_112_000 picoseconds. + Weight::from_parts(11_160_688, 247) + // Standard Error: 297 + .saturating_add(Weight::from_parts(4_056, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -763,10 +761,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 9_071_000 picoseconds. - Weight::from_parts(12_525_027, 247) - // Standard Error: 328 - .saturating_add(Weight::from_parts(4_427, 0).saturating_mul(n.into())) + // Minimum execution time: 9_419_000 picoseconds. + Weight::from_parts(12_683_269, 247) + // Standard Error: 298 + .saturating_add(Weight::from_parts(4_848, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -775,36 +773,36 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_487_000 picoseconds. - Weight::from_parts(1_611_000, 0) + // Minimum execution time: 1_535_000 picoseconds. + Weight::from_parts(1_637_000, 0) } fn set_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_852_000 picoseconds. - Weight::from_parts(1_982_000, 0) + // Minimum execution time: 1_891_000 picoseconds. + Weight::from_parts(1_970_000, 0) } fn get_transient_storage_empty() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_467_000 picoseconds. - Weight::from_parts(1_529_000, 0) + // Minimum execution time: 1_442_000 picoseconds. + Weight::from_parts(1_595_000, 0) } fn get_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_630_000 picoseconds. - Weight::from_parts(1_712_000, 0) + // Minimum execution time: 1_690_000 picoseconds. + Weight::from_parts(1_781_000, 0) } fn rollback_transient_storage() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_188_000 picoseconds. - Weight::from_parts(1_268_000, 0) + // Minimum execution time: 1_364_000 picoseconds. + Weight::from_parts(1_408_000, 0) } /// The range of component `n` is `[0, 448]`. /// The range of component `o` is `[0, 448]`. @@ -812,52 +810,50 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_197_000 picoseconds. - Weight::from_parts(2_464_654, 0) - // Standard Error: 17 - .saturating_add(Weight::from_parts(296, 0).saturating_mul(n.into())) - // Standard Error: 17 - .saturating_add(Weight::from_parts(342, 0).saturating_mul(o.into())) + // Minimum execution time: 2_392_000 picoseconds. + Weight::from_parts(2_559_622, 0) + // Standard Error: 18 + .saturating_add(Weight::from_parts(194, 0).saturating_mul(n.into())) + // Standard Error: 18 + .saturating_add(Weight::from_parts(319, 0).saturating_mul(o.into())) } /// The range of component `n` is `[0, 448]`. fn seal_clear_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_005_000 picoseconds. - Weight::from_parts(2_381_053, 0) - // Standard Error: 23 - .saturating_add(Weight::from_parts(322, 0).saturating_mul(n.into())) + // Minimum execution time: 2_099_000 picoseconds. + Weight::from_parts(2_442_655, 0) + // Standard Error: 19 + .saturating_add(Weight::from_parts(361, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 448]`. fn seal_get_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_853_000 picoseconds. - Weight::from_parts(2_082_772, 0) + // Minimum execution time: 1_936_000 picoseconds. + Weight::from_parts(2_160_919, 0) // Standard Error: 20 - .saturating_add(Weight::from_parts(322, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(385, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 448]`. fn seal_contains_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_711_000 picoseconds. - Weight::from_parts(1_899_649, 0) + // Minimum execution time: 1_809_000 picoseconds. + Weight::from_parts(1_997_103, 0) // Standard Error: 16 - .saturating_add(Weight::from_parts(208, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(156, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 448]`. - fn seal_take_transient_storage(n: u32, ) -> Weight { + fn seal_take_transient_storage(_n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_460_000 picoseconds. - Weight::from_parts(2_684_364, 0) - // Standard Error: 22 - .saturating_add(Weight::from_parts(56, 0).saturating_mul(n.into())) + // Minimum execution time: 2_513_000 picoseconds. + Weight::from_parts(2_799_538, 0) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -873,18 +869,18 @@ impl WeightInfo for SubstrateWeight { /// The range of component `i` is `[0, 262144]`. fn seal_call(t: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1292 + t * (203 ±0)` - // Estimated: `4757 + t * (2480 ±0)` - // Minimum execution time: 40_031_000 picoseconds. - Weight::from_parts(41_527_691, 4757) - // Standard Error: 50_351 - .saturating_add(Weight::from_parts(1_112_950, 0).saturating_mul(t.into())) + // Measured: `1294 + t * (205 ±0)` + // Estimated: `4759 + t * (2482 ±0)` + // Minimum execution time: 36_919_000 picoseconds. + Weight::from_parts(37_978_283, 4759) + // Standard Error: 54_576 + .saturating_add(Weight::from_parts(5_559_261, 0).saturating_mul(t.into())) // Standard Error: 0 - .saturating_add(Weight::from_parts(1, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(2, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(t.into()))) .saturating_add(T::DbWeight::get().writes(1_u64)) - .saturating_add(Weight::from_parts(0, 2480).saturating_mul(t.into())) + .saturating_add(Weight::from_parts(0, 2482).saturating_mul(t.into())) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -896,8 +892,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1237` // Estimated: `4702` - // Minimum execution time: 35_759_000 picoseconds. - Weight::from_parts(37_086_000, 4702) + // Minimum execution time: 31_267_000 picoseconds. + Weight::from_parts(32_495_000, 4702) .saturating_add(T::DbWeight::get().reads(3_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) @@ -911,12 +907,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `i` is `[0, 262144]`. fn seal_instantiate(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1271` - // Estimated: `4710` - // Minimum execution time: 116_485_000 picoseconds. - Weight::from_parts(108_907_717, 4710) - // Standard Error: 12 - .saturating_add(Weight::from_parts(4_125, 0).saturating_mul(i.into())) + // Measured: `1272` + // Estimated: `4724` + // Minimum execution time: 119_000_000 picoseconds. + Weight::from_parts(110_163_800, 4724) + // Standard Error: 11 + .saturating_add(Weight::from_parts(4_063, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -925,8 +921,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 651_000 picoseconds. - Weight::from_parts(3_867_609, 0) + // Minimum execution time: 725_000 picoseconds. + Weight::from_parts(4_441_443, 0) // Standard Error: 3 .saturating_add(Weight::from_parts(1_384, 0).saturating_mul(n.into())) } @@ -935,54 +931,54 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_090_000 picoseconds. - Weight::from_parts(5_338_460, 0) + // Minimum execution time: 1_057_000 picoseconds. + Weight::from_parts(5_659_277, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(3_601, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(3_588, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 717_000 picoseconds. - Weight::from_parts(2_629_461, 0) + // Minimum execution time: 691_000 picoseconds. + Weight::from_parts(3_368_834, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_528, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_507, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_128(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 660_000 picoseconds. - Weight::from_parts(4_807_814, 0) + // Minimum execution time: 619_000 picoseconds. + Weight::from_parts(2_422_606, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_509, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_528, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 261889]`. fn seal_sr25519_verify(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 42_829_000 picoseconds. - Weight::from_parts(24_650_992, 0) - // Standard Error: 14 - .saturating_add(Weight::from_parts(5_212, 0).saturating_mul(n.into())) + // Minimum execution time: 46_148_000 picoseconds. + Weight::from_parts(35_311_479, 0) + // Standard Error: 10 + .saturating_add(Weight::from_parts(5_452, 0).saturating_mul(n.into())) } fn seal_ecdsa_recover() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 46_902_000 picoseconds. - Weight::from_parts(48_072_000, 0) + // Minimum execution time: 49_475_000 picoseconds. + Weight::from_parts(50_488_000, 0) } fn seal_ecdsa_to_eth_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_713_000 picoseconds. - Weight::from_parts(12_847_000, 0) + // Minimum execution time: 12_516_000 picoseconds. + Weight::from_parts(12_637_000, 0) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) @@ -990,8 +986,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `300` // Estimated: `3765` - // Minimum execution time: 17_657_000 picoseconds. - Weight::from_parts(18_419_000, 3765) + // Minimum execution time: 13_735_000 picoseconds. + Weight::from_parts(14_450_000, 3765) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -999,10 +995,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) fn lock_delegate_dependency() -> Weight { // Proof Size summary in bytes: - // Measured: `338` - // Estimated: `3803` - // Minimum execution time: 13_650_000 picoseconds. - Weight::from_parts(14_209_000, 3803) + // Measured: `337` + // Estimated: `3802` + // Minimum execution time: 13_488_000 picoseconds. + Weight::from_parts(14_161_000, 3802) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -1010,10 +1006,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) fn unlock_delegate_dependency() -> Weight { // Proof Size summary in bytes: - // Measured: `338` + // Measured: `337` // Estimated: `3561` - // Minimum execution time: 12_341_000 picoseconds. - Weight::from_parts(13_011_000, 3561) + // Minimum execution time: 12_686_000 picoseconds. + Weight::from_parts(13_180_000, 3561) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -1022,10 +1018,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_899_000 picoseconds. - Weight::from_parts(10_489_171, 0) - // Standard Error: 104 - .saturating_add(Weight::from_parts(73_814, 0).saturating_mul(r.into())) + // Minimum execution time: 8_475_000 picoseconds. + Weight::from_parts(10_353_864, 0) + // Standard Error: 99 + .saturating_add(Weight::from_parts(73_636, 0).saturating_mul(r.into())) } } @@ -1037,8 +1033,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `109` // Estimated: `1594` - // Minimum execution time: 2_859_000 picoseconds. - Weight::from_parts(3_007_000, 1594) + // Minimum execution time: 2_796_000 picoseconds. + Weight::from_parts(2_958_000, 1594) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -1048,10 +1044,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `425 + k * (69 ±0)` // Estimated: `415 + k * (70 ±0)` - // Minimum execution time: 15_640_000 picoseconds. - Weight::from_parts(1_609_026, 415) - // Standard Error: 1_359 - .saturating_add(Weight::from_parts(1_204_420, 0).saturating_mul(k.into())) + // Minimum execution time: 16_135_000 picoseconds. + Weight::from_parts(3_227_098, 415) + // Standard Error: 1_106 + .saturating_add(Weight::from_parts(1_175_210, 0).saturating_mul(k.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) @@ -1073,17 +1069,17 @@ impl WeightInfo for () { /// The range of component `c` is `[0, 262144]`. fn call_with_code_per_byte(_c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1463` - // Estimated: `7403` - // Minimum execution time: 89_437_000 picoseconds. - Weight::from_parts(94_285_182, 7403) + // Measured: `1502` + // Estimated: `7442` + // Minimum execution time: 89_144_000 picoseconds. + Weight::from_parts(93_719_381, 7442) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Balances::Holds` (r:2 w:2) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(409), added: 2884, mode: `Measured`) /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) /// Storage: `Revive::ContractInfoOf` (r:1 w:1) @@ -1098,14 +1094,14 @@ impl WeightInfo for () { /// The range of component `i` is `[0, 262144]`. fn instantiate_with_code(c: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `364` - // Estimated: `6327` - // Minimum execution time: 187_904_000 picoseconds. - Weight::from_parts(153_252_081, 6327) - // Standard Error: 11 - .saturating_add(Weight::from_parts(49, 0).saturating_mul(c.into())) - // Standard Error: 11 - .saturating_add(Weight::from_parts(4_528, 0).saturating_mul(i.into())) + // Measured: `401` + // Estimated: `6349` + // Minimum execution time: 185_726_000 picoseconds. + Weight::from_parts(165_030_228, 6349) + // Standard Error: 10 + .saturating_add(Weight::from_parts(10, 0).saturating_mul(c.into())) + // Standard Error: 10 + .saturating_add(Weight::from_parts(4_453, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -1122,16 +1118,16 @@ impl WeightInfo for () { /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(409), added: 2884, mode: `Measured`) /// The range of component `i` is `[0, 262144]`. fn instantiate(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1296` - // Estimated: `4758` - // Minimum execution time: 154_656_000 picoseconds. - Weight::from_parts(139_308_398, 4758) - // Standard Error: 16 - .saturating_add(Weight::from_parts(4_421, 0).saturating_mul(i.into())) + // Measured: `1294` + // Estimated: `4739` + // Minimum execution time: 154_669_000 picoseconds. + Weight::from_parts(138_463_785, 4739) + // Standard Error: 15 + .saturating_add(Weight::from_parts(4_389, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -1149,43 +1145,41 @@ impl WeightInfo for () { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) fn call() -> Weight { // Proof Size summary in bytes: - // Measured: `1463` - // Estimated: `7403` - // Minimum execution time: 138_815_000 picoseconds. - Weight::from_parts(149_067_000, 7403) + // Measured: `1502` + // Estimated: `7442` + // Minimum execution time: 137_822_000 picoseconds. + Weight::from_parts(146_004_000, 7442) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(409), added: 2884, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:0 w:1) /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// The range of component `c` is `[0, 262144]`. - fn upload_code(c: u32, ) -> Weight { + fn upload_code(_c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `109` - // Estimated: `3574` - // Minimum execution time: 49_978_000 picoseconds. - Weight::from_parts(51_789_325, 3574) - // Standard Error: 0 - .saturating_add(Weight::from_parts(1, 0).saturating_mul(c.into())) + // Measured: `164` + // Estimated: `3629` + // Minimum execution time: 53_476_000 picoseconds. + Weight::from_parts(55_795_699, 3629) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(409), added: 2884, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:0 w:1) /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) fn remove_code() -> Weight { // Proof Size summary in bytes: - // Measured: `285` - // Estimated: `3750` - // Minimum execution time: 43_833_000 picoseconds. - Weight::from_parts(44_660_000, 3750) + // Measured: `322` + // Estimated: `3787` + // Minimum execution time: 41_955_000 picoseconds. + Weight::from_parts(43_749_000, 3787) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1197,34 +1191,34 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `529` // Estimated: `6469` - // Minimum execution time: 26_717_000 picoseconds. - Weight::from_parts(28_566_000, 6469) + // Minimum execution time: 22_763_000 picoseconds. + Weight::from_parts(23_219_000, 6469) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } /// Storage: `Revive::AddressSuffix` (r:1 w:1) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(409), added: 2884, mode: `Measured`) fn map_account() -> Weight { // Proof Size summary in bytes: - // Measured: `109` - // Estimated: `3574` - // Minimum execution time: 39_401_000 picoseconds. - Weight::from_parts(40_542_000, 3574) + // Measured: `164` + // Estimated: `3629` + // Minimum execution time: 45_478_000 picoseconds. + Weight::from_parts(46_658_000, 3629) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(409), added: 2884, mode: `Measured`) /// Storage: `Revive::AddressSuffix` (r:0 w:1) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) fn unmap_account() -> Weight { // Proof Size summary in bytes: - // Measured: `56` - // Estimated: `3521` - // Minimum execution time: 31_570_000 picoseconds. - Weight::from_parts(32_302_000, 3521) + // Measured: `93` + // Estimated: `3558` + // Minimum execution time: 33_359_000 picoseconds. + Weight::from_parts(34_196_000, 3558) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1236,8 +1230,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 13_607_000 picoseconds. - Weight::from_parts(13_903_000, 3610) + // Minimum execution time: 13_663_000 picoseconds. + Weight::from_parts(14_278_000, 3610) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// The range of component `r` is `[0, 1600]`. @@ -1245,24 +1239,24 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_400_000 picoseconds. - Weight::from_parts(8_388_251, 0) - // Standard Error: 283 - .saturating_add(Weight::from_parts(165_630, 0).saturating_mul(r.into())) + // Minimum execution time: 6_966_000 picoseconds. + Weight::from_parts(7_708_050, 0) + // Standard Error: 238 + .saturating_add(Weight::from_parts(167_115, 0).saturating_mul(r.into())) } fn seal_caller() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 275_000 picoseconds. - Weight::from_parts(305_000, 0) + // Minimum execution time: 332_000 picoseconds. + Weight::from_parts(378_000, 0) } fn seal_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 224_000 picoseconds. - Weight::from_parts(265_000, 0) + // Minimum execution time: 303_000 picoseconds. + Weight::from_parts(329_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -1270,18 +1264,18 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `306` // Estimated: `3771` - // Minimum execution time: 10_004_000 picoseconds. - Weight::from_parts(10_336_000, 3771) + // Minimum execution time: 10_014_000 picoseconds. + Weight::from_parts(10_549_000, 3771) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) fn seal_to_account_id() -> Weight { // Proof Size summary in bytes: - // Measured: `212` - // Estimated: `3677` - // Minimum execution time: 4_000_000 picoseconds. - Weight::from_parts(4_000_000, 3677) + // Measured: `248` + // Estimated: `3713` + // Minimum execution time: 9_771_000 picoseconds. + Weight::from_parts(10_092_000, 3713) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) @@ -1290,16 +1284,16 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `403` // Estimated: `3868` - // Minimum execution time: 11_054_000 picoseconds. - Weight::from_parts(11_651_000, 3868) + // Minimum execution time: 11_260_000 picoseconds. + Weight::from_parts(11_626_000, 3868) .saturating_add(RocksDbWeight::get().reads(1_u64)) } fn seal_own_code_hash() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 252_000 picoseconds. - Weight::from_parts(305_000, 0) + // Minimum execution time: 307_000 picoseconds. + Weight::from_parts(328_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -1309,51 +1303,51 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `473` // Estimated: `3938` - // Minimum execution time: 14_461_000 picoseconds. - Weight::from_parts(15_049_000, 3938) + // Minimum execution time: 14_675_000 picoseconds. + Weight::from_parts(15_168_000, 3938) .saturating_add(RocksDbWeight::get().reads(2_u64)) } fn seal_caller_is_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 312_000 picoseconds. - Weight::from_parts(338_000, 0) + // Minimum execution time: 332_000 picoseconds. + Weight::from_parts(357_000, 0) } fn seal_caller_is_root() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 243_000 picoseconds. - Weight::from_parts(299_000, 0) + // Minimum execution time: 298_000 picoseconds. + Weight::from_parts(332_000, 0) } fn seal_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 231_000 picoseconds. - Weight::from_parts(271_000, 0) + // Minimum execution time: 313_000 picoseconds. + Weight::from_parts(336_000, 0) } fn seal_weight_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 683_000 picoseconds. - Weight::from_parts(732_000, 0) + // Minimum execution time: 663_000 picoseconds. + Weight::from_parts(730_000, 0) } fn seal_ref_time_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 226_000 picoseconds. - Weight::from_parts(273_000, 0) + // Minimum execution time: 292_000 picoseconds. + Weight::from_parts(344_000, 0) } fn seal_balance() -> Weight { // Proof Size summary in bytes: // Measured: `102` // Estimated: `0` - // Minimum execution time: 4_626_000 picoseconds. - Weight::from_parts(4_842_000, 0) + // Minimum execution time: 4_604_000 picoseconds. + Weight::from_parts(4_875_000, 0) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -1363,8 +1357,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `264` // Estimated: `3729` - // Minimum execution time: 12_309_000 picoseconds. - Weight::from_parts(12_653_000, 3729) + // Minimum execution time: 12_252_000 picoseconds. + Weight::from_parts(12_641_000, 3729) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// Storage: `Revive::ImmutableDataOf` (r:1 w:0) @@ -1374,10 +1368,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `238 + n * (1 ±0)` // Estimated: `3703 + n * (1 ±0)` - // Minimum execution time: 5_838_000 picoseconds. - Weight::from_parts(9_570_778, 3703) - // Standard Error: 19 - .saturating_add(Weight::from_parts(721, 0).saturating_mul(n.into())) + // Minimum execution time: 6_005_000 picoseconds. + Weight::from_parts(9_550_692, 3703) + // Standard Error: 18 + .saturating_add(Weight::from_parts(710, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1388,67 +1382,67 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_910_000 picoseconds. - Weight::from_parts(2_205_396, 0) + // Minimum execution time: 1_981_000 picoseconds. + Weight::from_parts(2_297_488, 0) // Standard Error: 2 - .saturating_add(Weight::from_parts(538, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(528, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().writes(1_u64)) } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 224_000 picoseconds. - Weight::from_parts(274_000, 0) + // Minimum execution time: 279_000 picoseconds. + Weight::from_parts(309_000, 0) } fn seal_minimum_balance() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 231_000 picoseconds. - Weight::from_parts(279_000, 0) + // Minimum execution time: 289_000 picoseconds. + Weight::from_parts(315_000, 0) } fn seal_return_data_size() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 229_000 picoseconds. - Weight::from_parts(267_000, 0) + // Minimum execution time: 253_000 picoseconds. + Weight::from_parts(310_000, 0) } fn seal_call_data_size() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 218_000 picoseconds. - Weight::from_parts(267_000, 0) + // Minimum execution time: 291_000 picoseconds. + Weight::from_parts(338_000, 0) } fn seal_gas_limit() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 225_000 picoseconds. - Weight::from_parts(280_000, 0) + // Minimum execution time: 266_000 picoseconds. + Weight::from_parts(331_000, 0) } fn seal_gas_price() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 274_000 picoseconds. - Weight::from_parts(323_000, 0) + // Minimum execution time: 250_000 picoseconds. + Weight::from_parts(314_000, 0) } fn seal_base_fee() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 239_000 picoseconds. - Weight::from_parts(290_000, 0) + // Minimum execution time: 266_000 picoseconds. + Weight::from_parts(341_000, 0) } fn seal_block_number() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 224_000 picoseconds. - Weight::from_parts(274_000, 0) + // Minimum execution time: 281_000 picoseconds. + Weight::from_parts(314_000, 0) } /// Storage: `System::BlockHash` (r:1 w:0) /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `Measured`) @@ -1456,60 +1450,60 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `30` // Estimated: `3495` - // Minimum execution time: 3_430_000 picoseconds. - Weight::from_parts(3_692_000, 3495) + // Minimum execution time: 3_557_000 picoseconds. + Weight::from_parts(3_816_000, 3495) .saturating_add(RocksDbWeight::get().reads(1_u64)) } fn seal_now() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 241_000 picoseconds. - Weight::from_parts(290_000, 0) + // Minimum execution time: 280_000 picoseconds. + Weight::from_parts(316_000, 0) } fn seal_weight_to_fee() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_355_000 picoseconds. - Weight::from_parts(1_493_000, 0) + // Minimum execution time: 1_413_000 picoseconds. + Weight::from_parts(1_477_000, 0) } /// The range of component `n` is `[0, 262140]`. fn seal_copy_to_contract(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 348_000 picoseconds. - Weight::from_parts(1_004_890, 0) + // Minimum execution time: 383_000 picoseconds. + Weight::from_parts(602_481, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(202, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(201, 0).saturating_mul(n.into())) } fn seal_call_data_load() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 222_000 picoseconds. - Weight::from_parts(256_000, 0) + // Minimum execution time: 327_000 picoseconds. + Weight::from_parts(365_000, 0) } /// The range of component `n` is `[0, 262144]`. fn seal_call_data_copy(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 240_000 picoseconds. - Weight::from_parts(330_609, 0) + // Minimum execution time: 334_000 picoseconds. + Weight::from_parts(205_756, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(114, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(116, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262140]`. fn seal_return(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 232_000 picoseconds. - Weight::from_parts(264_000, 0) + // Minimum execution time: 278_000 picoseconds. + Weight::from_parts(611_031, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(208, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(202, 0).saturating_mul(n.into())) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -1524,12 +1518,12 @@ impl WeightInfo for () { /// The range of component `n` is `[0, 32]`. fn seal_terminate(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `322 + n * (88 ±0)` - // Estimated: `3787 + n * (2563 ±0)` - // Minimum execution time: 21_920_000 picoseconds. - Weight::from_parts(21_725_868, 3787) - // Standard Error: 11_165 - .saturating_add(Weight::from_parts(4_317_986, 0).saturating_mul(n.into())) + // Measured: `324 + n * (88 ±0)` + // Estimated: `3791 + n * (2563 ±0)` + // Minimum execution time: 18_544_000 picoseconds. + Weight::from_parts(18_412_253, 3791) + // Standard Error: 12_785 + .saturating_add(Weight::from_parts(4_214_449, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(4_u64)) @@ -1542,12 +1536,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_140_000 picoseconds. - Weight::from_parts(4_259_301, 0) - // Standard Error: 3_362 - .saturating_add(Weight::from_parts(194_546, 0).saturating_mul(t.into())) - // Standard Error: 34 - .saturating_add(Weight::from_parts(774, 0).saturating_mul(n.into())) + // Minimum execution time: 4_156_000 picoseconds. + Weight::from_parts(4_120_442, 0) + // Standard Error: 3_278 + .saturating_add(Weight::from_parts(212_768, 0).saturating_mul(t.into())) + // Standard Error: 33 + .saturating_add(Weight::from_parts(1_199, 0).saturating_mul(n.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -1555,8 +1549,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `680` // Estimated: `680` - // Minimum execution time: 10_747_000 picoseconds. - Weight::from_parts(11_276_000, 680) + // Minimum execution time: 11_065_000 picoseconds. + Weight::from_parts(11_573_000, 680) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -1565,8 +1559,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `10690` // Estimated: `10690` - // Minimum execution time: 42_076_000 picoseconds. - Weight::from_parts(43_381_000, 10690) + // Minimum execution time: 42_728_000 picoseconds. + Weight::from_parts(43_764_000, 10690) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -1575,8 +1569,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `680` // Estimated: `680` - // Minimum execution time: 11_703_000 picoseconds. - Weight::from_parts(12_308_000, 680) + // Minimum execution time: 12_376_000 picoseconds. + Weight::from_parts(12_658_000, 680) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1586,8 +1580,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `10690` // Estimated: `10690` - // Minimum execution time: 43_460_000 picoseconds. - Weight::from_parts(45_165_000, 10690) + // Minimum execution time: 44_344_000 picoseconds. + Weight::from_parts(45_753_000, 10690) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1599,12 +1593,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + o * (1 ±0)` // Estimated: `247 + o * (1 ±0)` - // Minimum execution time: 9_087_000 picoseconds. - Weight::from_parts(11_787_486, 247) - // Standard Error: 179 - .saturating_add(Weight::from_parts(976, 0).saturating_mul(n.into())) - // Standard Error: 179 - .saturating_add(Weight::from_parts(3_151, 0).saturating_mul(o.into())) + // Minimum execution time: 9_333_000 picoseconds. + Weight::from_parts(12_118_514, 247) + // Standard Error: 187 + .saturating_add(Weight::from_parts(1_212, 0).saturating_mul(n.into())) + // Standard Error: 187 + .saturating_add(Weight::from_parts(3_114, 0).saturating_mul(o.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) @@ -1616,10 +1610,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_611_000 picoseconds. - Weight::from_parts(11_791_390, 247) - // Standard Error: 308 - .saturating_add(Weight::from_parts(3_943, 0).saturating_mul(n.into())) + // Minimum execution time: 8_800_000 picoseconds. + Weight::from_parts(12_126_263, 247) + // Standard Error: 310 + .saturating_add(Weight::from_parts(4_181, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -1631,10 +1625,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_389_000 picoseconds. - Weight::from_parts(11_625_480, 247) - // Standard Error: 315 - .saturating_add(Weight::from_parts(4_487, 0).saturating_mul(n.into())) + // Minimum execution time: 8_612_000 picoseconds. + Weight::from_parts(11_888_491, 247) + // Standard Error: 322 + .saturating_add(Weight::from_parts(4_319, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1645,10 +1639,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 7_947_000 picoseconds. - Weight::from_parts(10_970_587, 247) - // Standard Error: 310 - .saturating_add(Weight::from_parts(3_675, 0).saturating_mul(n.into())) + // Minimum execution time: 8_112_000 picoseconds. + Weight::from_parts(11_160_688, 247) + // Standard Error: 297 + .saturating_add(Weight::from_parts(4_056, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1659,10 +1653,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 9_071_000 picoseconds. - Weight::from_parts(12_525_027, 247) - // Standard Error: 328 - .saturating_add(Weight::from_parts(4_427, 0).saturating_mul(n.into())) + // Minimum execution time: 9_419_000 picoseconds. + Weight::from_parts(12_683_269, 247) + // Standard Error: 298 + .saturating_add(Weight::from_parts(4_848, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -1671,36 +1665,36 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_487_000 picoseconds. - Weight::from_parts(1_611_000, 0) + // Minimum execution time: 1_535_000 picoseconds. + Weight::from_parts(1_637_000, 0) } fn set_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_852_000 picoseconds. - Weight::from_parts(1_982_000, 0) + // Minimum execution time: 1_891_000 picoseconds. + Weight::from_parts(1_970_000, 0) } fn get_transient_storage_empty() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_467_000 picoseconds. - Weight::from_parts(1_529_000, 0) + // Minimum execution time: 1_442_000 picoseconds. + Weight::from_parts(1_595_000, 0) } fn get_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_630_000 picoseconds. - Weight::from_parts(1_712_000, 0) + // Minimum execution time: 1_690_000 picoseconds. + Weight::from_parts(1_781_000, 0) } fn rollback_transient_storage() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_188_000 picoseconds. - Weight::from_parts(1_268_000, 0) + // Minimum execution time: 1_364_000 picoseconds. + Weight::from_parts(1_408_000, 0) } /// The range of component `n` is `[0, 448]`. /// The range of component `o` is `[0, 448]`. @@ -1708,52 +1702,50 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_197_000 picoseconds. - Weight::from_parts(2_464_654, 0) - // Standard Error: 17 - .saturating_add(Weight::from_parts(296, 0).saturating_mul(n.into())) - // Standard Error: 17 - .saturating_add(Weight::from_parts(342, 0).saturating_mul(o.into())) + // Minimum execution time: 2_392_000 picoseconds. + Weight::from_parts(2_559_622, 0) + // Standard Error: 18 + .saturating_add(Weight::from_parts(194, 0).saturating_mul(n.into())) + // Standard Error: 18 + .saturating_add(Weight::from_parts(319, 0).saturating_mul(o.into())) } /// The range of component `n` is `[0, 448]`. fn seal_clear_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_005_000 picoseconds. - Weight::from_parts(2_381_053, 0) - // Standard Error: 23 - .saturating_add(Weight::from_parts(322, 0).saturating_mul(n.into())) + // Minimum execution time: 2_099_000 picoseconds. + Weight::from_parts(2_442_655, 0) + // Standard Error: 19 + .saturating_add(Weight::from_parts(361, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 448]`. fn seal_get_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_853_000 picoseconds. - Weight::from_parts(2_082_772, 0) + // Minimum execution time: 1_936_000 picoseconds. + Weight::from_parts(2_160_919, 0) // Standard Error: 20 - .saturating_add(Weight::from_parts(322, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(385, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 448]`. fn seal_contains_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_711_000 picoseconds. - Weight::from_parts(1_899_649, 0) + // Minimum execution time: 1_809_000 picoseconds. + Weight::from_parts(1_997_103, 0) // Standard Error: 16 - .saturating_add(Weight::from_parts(208, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(156, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 448]`. - fn seal_take_transient_storage(n: u32, ) -> Weight { + fn seal_take_transient_storage(_n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_460_000 picoseconds. - Weight::from_parts(2_684_364, 0) - // Standard Error: 22 - .saturating_add(Weight::from_parts(56, 0).saturating_mul(n.into())) + // Minimum execution time: 2_513_000 picoseconds. + Weight::from_parts(2_799_538, 0) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -1769,18 +1761,18 @@ impl WeightInfo for () { /// The range of component `i` is `[0, 262144]`. fn seal_call(t: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1292 + t * (203 ±0)` - // Estimated: `4757 + t * (2480 ±0)` - // Minimum execution time: 40_031_000 picoseconds. - Weight::from_parts(41_527_691, 4757) - // Standard Error: 50_351 - .saturating_add(Weight::from_parts(1_112_950, 0).saturating_mul(t.into())) + // Measured: `1294 + t * (205 ±0)` + // Estimated: `4759 + t * (2482 ±0)` + // Minimum execution time: 36_919_000 picoseconds. + Weight::from_parts(37_978_283, 4759) + // Standard Error: 54_576 + .saturating_add(Weight::from_parts(5_559_261, 0).saturating_mul(t.into())) // Standard Error: 0 - .saturating_add(Weight::from_parts(1, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(2, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(t.into()))) .saturating_add(RocksDbWeight::get().writes(1_u64)) - .saturating_add(Weight::from_parts(0, 2480).saturating_mul(t.into())) + .saturating_add(Weight::from_parts(0, 2482).saturating_mul(t.into())) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -1792,8 +1784,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1237` // Estimated: `4702` - // Minimum execution time: 35_759_000 picoseconds. - Weight::from_parts(37_086_000, 4702) + // Minimum execution time: 31_267_000 picoseconds. + Weight::from_parts(32_495_000, 4702) .saturating_add(RocksDbWeight::get().reads(3_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) @@ -1807,12 +1799,12 @@ impl WeightInfo for () { /// The range of component `i` is `[0, 262144]`. fn seal_instantiate(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1271` - // Estimated: `4710` - // Minimum execution time: 116_485_000 picoseconds. - Weight::from_parts(108_907_717, 4710) - // Standard Error: 12 - .saturating_add(Weight::from_parts(4_125, 0).saturating_mul(i.into())) + // Measured: `1272` + // Estimated: `4724` + // Minimum execution time: 119_000_000 picoseconds. + Weight::from_parts(110_163_800, 4724) + // Standard Error: 11 + .saturating_add(Weight::from_parts(4_063, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1821,8 +1813,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 651_000 picoseconds. - Weight::from_parts(3_867_609, 0) + // Minimum execution time: 725_000 picoseconds. + Weight::from_parts(4_441_443, 0) // Standard Error: 3 .saturating_add(Weight::from_parts(1_384, 0).saturating_mul(n.into())) } @@ -1831,54 +1823,54 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_090_000 picoseconds. - Weight::from_parts(5_338_460, 0) + // Minimum execution time: 1_057_000 picoseconds. + Weight::from_parts(5_659_277, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(3_601, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(3_588, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 717_000 picoseconds. - Weight::from_parts(2_629_461, 0) + // Minimum execution time: 691_000 picoseconds. + Weight::from_parts(3_368_834, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_528, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_507, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_128(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 660_000 picoseconds. - Weight::from_parts(4_807_814, 0) + // Minimum execution time: 619_000 picoseconds. + Weight::from_parts(2_422_606, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_509, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_528, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 261889]`. fn seal_sr25519_verify(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 42_829_000 picoseconds. - Weight::from_parts(24_650_992, 0) - // Standard Error: 14 - .saturating_add(Weight::from_parts(5_212, 0).saturating_mul(n.into())) + // Minimum execution time: 46_148_000 picoseconds. + Weight::from_parts(35_311_479, 0) + // Standard Error: 10 + .saturating_add(Weight::from_parts(5_452, 0).saturating_mul(n.into())) } fn seal_ecdsa_recover() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 46_902_000 picoseconds. - Weight::from_parts(48_072_000, 0) + // Minimum execution time: 49_475_000 picoseconds. + Weight::from_parts(50_488_000, 0) } fn seal_ecdsa_to_eth_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_713_000 picoseconds. - Weight::from_parts(12_847_000, 0) + // Minimum execution time: 12_516_000 picoseconds. + Weight::from_parts(12_637_000, 0) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) @@ -1886,8 +1878,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `300` // Estimated: `3765` - // Minimum execution time: 17_657_000 picoseconds. - Weight::from_parts(18_419_000, 3765) + // Minimum execution time: 13_735_000 picoseconds. + Weight::from_parts(14_450_000, 3765) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1895,10 +1887,10 @@ impl WeightInfo for () { /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) fn lock_delegate_dependency() -> Weight { // Proof Size summary in bytes: - // Measured: `338` - // Estimated: `3803` - // Minimum execution time: 13_650_000 picoseconds. - Weight::from_parts(14_209_000, 3803) + // Measured: `337` + // Estimated: `3802` + // Minimum execution time: 13_488_000 picoseconds. + Weight::from_parts(14_161_000, 3802) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1906,10 +1898,10 @@ impl WeightInfo for () { /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) fn unlock_delegate_dependency() -> Weight { // Proof Size summary in bytes: - // Measured: `338` + // Measured: `337` // Estimated: `3561` - // Minimum execution time: 12_341_000 picoseconds. - Weight::from_parts(13_011_000, 3561) + // Minimum execution time: 12_686_000 picoseconds. + Weight::from_parts(13_180_000, 3561) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1918,9 +1910,9 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_899_000 picoseconds. - Weight::from_parts(10_489_171, 0) - // Standard Error: 104 - .saturating_add(Weight::from_parts(73_814, 0).saturating_mul(r.into())) + // Minimum execution time: 8_475_000 picoseconds. + Weight::from_parts(10_353_864, 0) + // Standard Error: 99 + .saturating_add(Weight::from_parts(73_636, 0).saturating_mul(r.into())) } } diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index 130cbf97ad504..2d7c73d26192e 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -288,14 +288,14 @@ pub trait HostFn: private::Sealed { /// /// # Parameters /// - /// - `code_hash`: The hash of the code to be instantiated. /// - `ref_time_limit`: how much *ref_time* Weight to devote to the execution. /// - `proof_size_limit`: how much *proof_size* Weight to devote to the execution. /// - `deposit`: The storage deposit limit for instantiation. Passing `None` means setting no /// specific limit for the call, which implies storage usage up to the limit of the parent /// call. /// - `value`: The value to transfer into the contract. - /// - `input`: The input data buffer. + /// - `input`: The code hash and constructor input data buffer. The first 32 bytes are the code + /// hash of the code to be instantiated. The remaining bytes are the constructor call data. /// - `address`: A reference to the address buffer to write the address of the contract. If /// `None` is provided then the output buffer is not copied. /// - `output`: A reference to the return value buffer to write the constructor output buffer. @@ -315,7 +315,6 @@ pub trait HostFn: private::Sealed { /// - [TransferFailed][`crate::ReturnErrorCode::TransferFailed] /// - [OutOfResources][`crate::ReturnErrorCode::OutOfResources] fn instantiate( - code_hash: &[u8; 32], ref_time_limit: u64, proof_size_limit: u64, deposit: &[u8; 32], diff --git a/substrate/frame/revive/uapi/src/host/riscv64.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs index 3726564e26eba..8179ea890189b 100644 --- a/substrate/frame/revive/uapi/src/host/riscv64.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -16,7 +16,7 @@ use crate::{ host::{CallFlags, HostFn, HostFnImpl, Result, StorageFlags}, - ReturnFlags, + pack_hi_lo, ReturnFlags, }; use pallet_revive_proc_macro::unstable_hostfn; @@ -59,9 +59,30 @@ mod sys { out_ptr: *mut u8, out_len_ptr: *mut u32, ) -> ReturnCode; - pub fn call(ptr: *const u8) -> ReturnCode; - pub fn delegate_call(ptr: *const u8) -> ReturnCode; - pub fn instantiate(ptr: *const u8) -> ReturnCode; + pub fn call( + flags_and_callee: u64, + ref_time_limit: u64, + proof_size_limit: u64, + deposit_and_value: u64, + input_data: u64, + output_data: u64, + ) -> ReturnCode; + pub fn delegate_call( + flags_and_callee: u64, + ref_time_limit: u64, + proof_size_limit: u64, + deposit_ptr: *const u8, + input_data: u64, + output_data: u64, + ) -> ReturnCode; + pub fn instantiate( + ref_time_limit: u64, + proof_size_limit: u64, + deposit_and_value: u64, + input_data: u64, + output_data: u64, + address_and_salt: u64, + ) -> ReturnCode; pub fn terminate(beneficiary_ptr: *const u8); pub fn call_data_copy(out_ptr: *mut u8, out_len: u32, offset: u32); pub fn call_data_load(out_ptr: *mut u8, offset: u32); @@ -165,7 +186,6 @@ fn ptr_or_sentinel(data: &Option<&[u8; 32]>) -> *const u8 { impl HostFn for HostFnImpl { fn instantiate( - code_hash: &[u8; 32], ref_time_limit: u64, proof_size_limit: u64, deposit_limit: &[u8; 32], @@ -179,42 +199,28 @@ impl HostFn for HostFnImpl { Some(ref mut data) => data.as_mut_ptr(), None => crate::SENTINEL as _, }; - let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); + let (output_ptr, mut output_len_ptr) = ptr_len_or_sentinel(&mut output); let deposit_limit_ptr = deposit_limit.as_ptr(); let salt_ptr = ptr_or_sentinel(&salt); - #[repr(C)] - #[allow(dead_code)] - struct Args { - code_hash: u32, - ref_time_limit: u64, - proof_size_limit: u64, - deposit_limit: u32, - value: u32, - input: u32, - input_len: u32, - address: u32, - output: u32, - output_len: u32, - salt: u32, - } - let args = Args { - code_hash: code_hash.as_ptr() as _, - ref_time_limit, - proof_size_limit, - deposit_limit: deposit_limit_ptr as _, - value: value.as_ptr() as _, - input: input.as_ptr() as _, - input_len: input.len() as _, - address: address as _, - output: output_ptr as _, - output_len: &mut output_len as *mut _ as _, - salt: salt_ptr as _, - }; - let ret_code = { unsafe { sys::instantiate(&args as *const Args as *const _) } }; + let deposit_and_value = pack_hi_lo(deposit_limit_ptr as _, value.as_ptr() as _); + let address_and_salt = pack_hi_lo(address as _, salt_ptr as _); + let input_data = pack_hi_lo(input.len() as _, input.as_ptr() as _); + let output_data = pack_hi_lo(&mut output_len_ptr as *mut _ as _, output_ptr as _); + + let ret_code = unsafe { + sys::instantiate( + ref_time_limit, + proof_size_limit, + deposit_and_value, + input_data, + output_data, + address_and_salt, + ) + }; if let Some(ref mut output) = output { - extract_from_slice(output, output_len as usize); + extract_from_slice(output, output_len_ptr as usize); } ret_code.into() @@ -232,34 +238,22 @@ impl HostFn for HostFnImpl { ) -> Result { let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); let deposit_limit_ptr = deposit_limit.as_ptr(); - #[repr(C)] - #[allow(dead_code)] - struct Args { - flags: u32, - callee: u32, - ref_time_limit: u64, - proof_size_limit: u64, - deposit_limit: u32, - value: u32, - input: u32, - input_len: u32, - output: u32, - output_len: u32, - } - let args = Args { - flags: flags.bits(), - callee: callee.as_ptr() as _, - ref_time_limit, - proof_size_limit, - deposit_limit: deposit_limit_ptr as _, - value: value.as_ptr() as _, - input: input.as_ptr() as _, - input_len: input.len() as _, - output: output_ptr as _, - output_len: &mut output_len as *mut _ as _, - }; - let ret_code = { unsafe { sys::call(&args as *const Args as *const _) } }; + let flags_and_callee = pack_hi_lo(flags.bits(), callee.as_ptr() as _); + let deposit_and_value = pack_hi_lo(deposit_limit_ptr as _, value.as_ptr() as _); + let input_data = pack_hi_lo(input.len() as _, input.as_ptr() as _); + let output_data = pack_hi_lo(&mut output_len as *mut _ as _, output_ptr as _); + + let ret_code = unsafe { + sys::call( + flags_and_callee, + ref_time_limit, + proof_size_limit, + deposit_and_value, + input_data, + output_data, + ) + }; if let Some(ref mut output) = output { extract_from_slice(output, output_len as usize); @@ -279,32 +273,21 @@ impl HostFn for HostFnImpl { ) -> Result { let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); let deposit_limit_ptr = deposit_limit.as_ptr(); - #[repr(C)] - #[allow(dead_code)] - struct Args { - flags: u32, - address: u32, - ref_time_limit: u64, - proof_size_limit: u64, - deposit_limit: u32, - input: u32, - input_len: u32, - output: u32, - output_len: u32, - } - let args = Args { - flags: flags.bits(), - address: address.as_ptr() as _, - ref_time_limit, - proof_size_limit, - deposit_limit: deposit_limit_ptr as _, - input: input.as_ptr() as _, - input_len: input.len() as _, - output: output_ptr as _, - output_len: &mut output_len as *mut _ as _, - }; - let ret_code = { unsafe { sys::delegate_call(&args as *const Args as *const _) } }; + let flags_and_callee = pack_hi_lo(flags.bits(), address.as_ptr() as u32); + let input_data = pack_hi_lo(input.len() as u32, input.as_ptr() as u32); + let output_data = pack_hi_lo(&mut output_len as *mut _ as u32, output_ptr as u32); + + let ret_code = unsafe { + sys::delegate_call( + flags_and_callee, + ref_time_limit, + proof_size_limit, + deposit_limit_ptr as _, + input_data, + output_data, + ) + }; if let Some(ref mut output) = output { extract_from_slice(output, output_len as usize); diff --git a/substrate/frame/revive/uapi/src/lib.rs b/substrate/frame/revive/uapi/src/lib.rs index 867f356339876..bbd647a7faed5 100644 --- a/substrate/frame/revive/uapi/src/lib.rs +++ b/substrate/frame/revive/uapi/src/lib.rs @@ -131,3 +131,14 @@ impl ReturnCode { } type Result = core::result::Result<(), ReturnErrorCode>; + +/// Helper to pack two `u32` values into a `u64` register. +/// +/// Pointers to PVM memory are always 32 bit in size. Thus contracts can pack two +/// pointers into a single register when calling a syscall API method. +/// +/// This is done in syscall API methods where the number of arguments is exceeding +/// the available registers. +pub fn pack_hi_lo(hi: u32, lo: u32) -> u64 { + ((hi as u64) << 32) | lo as u64 +} From 8a6a41938203046fef1594d0d97e1e980be4a516 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 28 Jan 2025 10:09:46 +0000 Subject: [PATCH 160/169] ah-next compiles --- Cargo.lock | 2 +- Cargo.toml | 1 + .../assets/asset-hub-next-westend/Cargo.toml | 8 +- .../assets/asset-hub-next-westend/src/lib.rs | 14 +- .../asset-hub-next-westend/src/staking.rs | 89 ++++-- .../asset-hub-next-westend/src/weights/mod.rs | 1 - .../pallet_election_provider_multi_phase.rs | 270 ------------------ polkadot/runtime/westend/Cargo.toml | 8 +- polkadot/runtime/westend/src/lib.rs | 18 +- .../src/signed/mod.rs | 1 + .../src/verifier/impls.rs | 1 + 11 files changed, 95 insertions(+), 318 deletions(-) delete mode 100644 cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/weights/pallet_election_provider_multi_phase.rs diff --git a/Cargo.lock b/Cargo.lock index 0bfca7665bda7..4f016e09d8e99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1101,7 +1101,7 @@ dependencies = [ "pallet-collator-selection 9.0.0", "pallet-conviction-voting 28.0.0", "pallet-delegated-staking 1.0.0", - "pallet-election-provider-multi-phase 27.0.0", + "pallet-election-provider-multi-block", "pallet-fast-unstake 27.0.0", "pallet-message-queue 31.0.0", "pallet-multisig 28.0.0", diff --git a/Cargo.toml b/Cargo.toml index b726177c6c2dd..09385cdbee122 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -936,6 +936,7 @@ pallet-delegated-staking = { path = "substrate/frame/delegated-staking", default pallet-democracy = { path = "substrate/frame/democracy", default-features = false } pallet-dev-mode = { path = "substrate/frame/examples/dev-mode", default-features = false } pallet-election-provider-multi-phase = { path = "substrate/frame/election-provider-multi-phase", default-features = false } +pallet-election-provider-multi-block = { path = "substrate/frame/election-provider-multi-block", default-features = false } pallet-election-provider-support-benchmarking = { path = "substrate/frame/election-provider-support/benchmarking", default-features = false } pallet-elections-phragmen = { path = "substrate/frame/elections-phragmen", default-features = false } pallet-example-authorization-tx-extension = { path = "substrate/frame/examples/authorization-tx-extension", default-features = false } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/Cargo.toml index ce9cb16c2a359..fd8021981063f 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/Cargo.toml @@ -41,7 +41,7 @@ pallet-bags-list = { workspace = true } pallet-balances = { workspace = true } pallet-conviction-voting = { workspace = true } pallet-delegated-staking = { workspace = true } -pallet-election-provider-multi-phase = { workspace = true } +pallet-election-provider-multi-block = { workspace = true } pallet-fast-unstake = { workspace = true } pallet-multisig = { workspace = true } pallet-nft-fractionalization = { workspace = true } @@ -157,7 +157,7 @@ runtime-benchmarks = [ "pallet-collator-selection/runtime-benchmarks", "pallet-conviction-voting/runtime-benchmarks", "pallet-delegated-staking/runtime-benchmarks", - "pallet-election-provider-multi-phase/runtime-benchmarks", + "pallet-election-provider-multi-block/runtime-benchmarks", "pallet-fast-unstake/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", @@ -217,7 +217,7 @@ try-runtime = [ "pallet-collator-selection/try-runtime", "pallet-conviction-voting/try-runtime", "pallet-delegated-staking/try-runtime", - "pallet-election-provider-multi-phase/try-runtime", + "pallet-election-provider-multi-block/try-runtime", "pallet-fast-unstake/try-runtime", "pallet-message-queue/try-runtime", "pallet-multisig/try-runtime", @@ -285,7 +285,7 @@ std = [ "pallet-collator-selection/std", "pallet-conviction-voting/std", "pallet-delegated-staking/std", - "pallet-election-provider-multi-phase/std", + "pallet-election-provider-multi-block/std", "pallet-fast-unstake/std", "pallet-message-queue/std", "pallet-multisig/std", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/lib.rs index cddc7fe98d5ad..da4e97c23cabd 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/lib.rs @@ -1179,8 +1179,13 @@ construct_runtime!( NominationPools: pallet_nomination_pools = 81, FastUnstake: pallet_fast_unstake = 82, VoterList: pallet_bags_list:: = 83, - ElectionProviderMultiPhase: pallet_election_provider_multi_phase = 84, // to be swapped out for multi-block - DelegatedStaking: pallet_delegated_staking = 85, + DelegatedStaking: pallet_delegated_staking = 84, + + // Election apparatus. + MultiBlock: pallet_election_provider_multi_block = 85, + MultiBlockVerifier: pallet_election_provider_multi_block::verifier = 86, + MultiBlockUnsigned: pallet_election_provider_multi_block::unsigned = 87, + MultiBlockSigned: pallet_election_provider_multi_block::signed = 88, // Governance. Preimage: pallet_preimage = 90, @@ -1355,7 +1360,10 @@ mod benches { [pallet_bags_list, VoterList] [pallet_balances, Balances] [pallet_conviction_voting, ConvictionVoting] - [pallet_election_provider_multi_phase, ElectionProviderMultiPhase] + [pallet_election_provider_multi_block, MultiBlock] + [pallet_election_provider_multi_block_verifier, MultiBlockVerifier] + [pallet_election_provider_multi_block_unsigned, MultiBlockUnsigned] + [pallet_election_provider_multi_block_signed, MultiBlockSigned] [pallet_fast_unstake, FastUnstake] [pallet_message_queue, MessageQueue] [pallet_multisig, Multisig] diff --git a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/staking.rs b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/staking.rs index 3ebb9c44dbedb..b86f2d5cbe947 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/staking.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/staking.rs @@ -17,12 +17,11 @@ use super::*; use cumulus_primitives_core::relay_chain::SessionIndex; use frame_election_provider_support::{ - bounds::{ElectionBounds, ElectionBoundsBuilder}, - onchain, SequentialPhragmen, + bounds::{CountBound, ElectionBounds, ElectionBoundsBuilder}, + onchain, NposSolver, SequentialPhragmen, }; use frame_support::traits::{ConstU128, EitherOf}; -use pallet_election_provider_multi_block as multi_block; -use pallet_election_provider_multi_phase::GeometricDepositBase; +use pallet_election_provider_multi_block::{self as multi_block, SolutionAccuracyOf}; use pallet_staking::UseValidatorsMap; use polkadot_runtime_common::{ elections::OnChainAccuracy, prod_or_fast, BalanceToU256, CurrencyToVote, U256ToBalance, @@ -44,11 +43,10 @@ frame_election_provider_support::generate_solution_type!( ); parameter_types! { - pub ElectionBoundsOnChain: ElectionBounds = - ElectionBoundsBuilder::default().voters_count(500).targets_count(100).build(); - pub MaxWinnersPerPage: u32 = 128; - pub MaxBackersPerWinner: u32 = 256; - pub MaxExposurePageSize: u32 = MaxBackersPerWinner::get() / 4; + pub MaxWinnersPerPage: u32 = 64; + pub MaxBackersPerWinnerPerPage: u32 = 64; + pub MaxBackersPerWinnerFinal: u32 = 512; + pub MaxExposurePageSize: u32 = MaxBackersPerWinnerFinal::get() / 4; pub MaxValidatorSet: u32 = 1000; pub Pages: u32 = 8; pub VoterSnapshotPerBlock: u32 = 22_500 / Pages::get(); @@ -58,6 +56,7 @@ parameter_types! { pub UnsignedPhase: u32 = prod_or_fast!(MINUTES * 30, MINUTES * 2); // validate up to 4 signed solution. pub SignedValidationPhase: u32 = Pages::get() * 4; + pub SolutionImprovementThreshold: Perbill = Perbill::from_rational(1u32, 10_000); } impl multi_block::Config for Runtime { @@ -77,24 +76,73 @@ impl multi_block::Config for Runtime { type Verifier = MultiBlockVerifier; } -impl multi_block::verifier::Config for Runtime {} -impl multi_block::signed::Config for Runtime {} -impl multi_block::unsigned::Config for Runtime {} +impl multi_block::verifier::Config for Runtime { + type MaxWinnersPerPage = MaxWinnersPerPage; + type MaxBackersPerWinner = MaxBackersPerWinnerPerPage; + type MaxBackersPerWinnerFinal = MaxBackersPerWinnerFinal; + type SolutionDataProvider = MultiBlockSigned; + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type SolutionImprovementThreshold = SolutionImprovementThreshold; +} + +parameter_types! { + pub BailoutGraceRatio: Perbill = Perbill::from_percent(50); + pub DepositBase: Balance = 5 * UNITS; + pub DepositPerPage: Balance = 1 * UNITS; + pub RewardBase: Balance = 10 * UNITS; +} + +impl multi_block::signed::Config for Runtime { + type BailoutGraceRatio = BailoutGraceRatio; + type Currency = Balances; + type DepositBase = DepositBase; + type DepositPerPage = DepositPerPage; + type EstimateCallFee = TransactionPayment; + type MaxSubmissions = ConstU32<32>; + type RewardBase = RewardBase; + type RuntimeEvent = RuntimeEvent; + type RuntimeHoldReason = RuntimeHoldReason; + type WeightInfo = (); +} +parameter_types! { + pub MinerTxPriority: TransactionPriority = TransactionPriority::max_value() / 2; + pub MinerMaxLength: u32 = Perbill::from_rational(9u32, 10) * + *RuntimeBlockLength::get() + .max + .get(DispatchClass::Normal); + pub MinerMaxWeight: Weight = RuntimeBlockWeights::get() + .get(DispatchClass::Normal) + .max_extrinsic.expect("Normal extrinsics have a weight limit configured; qed") + .saturating_sub(BlockExecutionWeight::get()); +} + +impl multi_block::unsigned::Config for Runtime { + type WeightInfo = (); + type OffchainSolver = SequentialPhragmen>; + type MinerTxPriority = MinerTxPriority; + type MinerMaxLength = MinerMaxLength; + type MinerMaxWeight = MinerMaxWeight; + type OffchainRepeat = ConstU32<5>; +} + +parameter_types! { + pub ElectionBoundsOnChain: ElectionBounds = ElectionBoundsBuilder::default() + .voters_count(CountBound(500)) + .targets_count(CountBound(100)) + .build(); +} pub struct OnChainSeqPhragmen; impl onchain::Config for OnChainSeqPhragmen { type Sort = ConstBool; type System = Runtime; - type Solver = SequentialPhragmen< - AccountId, - pallet_election_provider_multi_phase::SolutionAccuracyOf, - >; + type Solver = SequentialPhragmen>; type DataProvider = Staking; type WeightInfo = frame_election_provider_support::weights::SubstrateWeight; type Bounds = ElectionBoundsOnChain; - type MaxBackersPerWinner = - ::MaxBackersPerWinner; - type MaxWinnersPerPage = MaxActiveValidators; + type MaxBackersPerWinner = ::MaxBackersPerWinner; + type MaxWinnersPerPage = ::MaxWinnersPerPage; } parameter_types! { @@ -165,7 +213,6 @@ parameter_types! { pub const BondingDuration: sp_staking::EraIndex = 2; // 1 era in which slashes can be cancelled (6 hours). pub const SlashDeferDuration: sp_staking::EraIndex = 1; - pub const MaxExposurePageSize: u32 = 64; // Note: this is not really correct as Max Nominators is (MaxExposurePageSize * page_count) but // this is an unbounded number. We just set it to a reasonably high value, 1 full page // of nominators. @@ -193,7 +240,7 @@ impl pallet_staking::Config for Runtime { type EraPayout = EraPayout; type MaxExposurePageSize = MaxExposurePageSize; type NextNewSession = Session; - type ElectionProvider = (); + type ElectionProvider = MultiBlock; // Kaboom! type GenesisElectionProvider = onchain::OnChainExecution; type VoterList = VoterList; type TargetList = UseValidatorsMap; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/weights/mod.rs index d02d505efa1fe..7a1f7f7b8a4e7 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/weights/mod.rs @@ -33,7 +33,6 @@ pub mod pallet_bags_list; pub mod pallet_balances; pub mod pallet_collator_selection; pub mod pallet_conviction_voting; -pub mod pallet_election_provider_multi_phase; pub mod pallet_fast_unstake; pub mod pallet_message_queue; pub mod pallet_multisig; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/weights/pallet_election_provider_multi_phase.rs b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/weights/pallet_election_provider_multi_phase.rs deleted file mode 100644 index cd315cda2a7b0..0000000000000 --- a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/weights/pallet_election_provider_multi_phase.rs +++ /dev/null @@ -1,270 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Polkadot is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . - -//! Autogenerated weights for `pallet_election_provider_multi_phase` -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-06-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner--ss9ysm1-project-163-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("westend-dev"), DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot -// benchmark -// pallet -// --chain=westend-dev -// --steps=50 -// --repeat=20 -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --pallet=pallet_election_provider_multi_phase -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --header=./file_header.txt -// --output=./runtime/westend/src/weights/ - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] -#![allow(missing_docs)] - -use frame_support::{traits::Get, weights::Weight}; -use core::marker::PhantomData; - -/// Weight functions for `pallet_election_provider_multi_phase`. -pub struct WeightInfo(PhantomData); -impl pallet_election_provider_multi_phase::WeightInfo for WeightInfo { - /// Storage: Staking CurrentEra (r:1 w:0) - /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking CurrentPlannedSession (r:1 w:0) - /// Proof: Staking CurrentPlannedSession (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking ErasStartSessionIndex (r:1 w:0) - /// Proof: Staking ErasStartSessionIndex (max_values: None, max_size: Some(16), added: 2491, mode: MaxEncodedLen) - /// Storage: Babe EpochIndex (r:1 w:0) - /// Proof: Babe EpochIndex (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) - /// Storage: Babe GenesisSlot (r:1 w:0) - /// Proof: Babe GenesisSlot (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) - /// Storage: Babe CurrentSlot (r:1 w:0) - /// Proof: Babe CurrentSlot (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) - /// Storage: Staking ForceEra (r:1 w:0) - /// Proof: Staking ForceEra (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) - /// Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) - /// Proof Skipped: ElectionProviderMultiPhase CurrentPhase (max_values: Some(1), max_size: None, mode: Measured) - fn on_initialize_nothing() -> Weight { - // Proof Size summary in bytes: - // Measured: `919` - // Estimated: `3481` - // Minimum execution time: 18_263_000 picoseconds. - Weight::from_parts(19_329_000, 0) - .saturating_add(Weight::from_parts(0, 3481)) - .saturating_add(T::DbWeight::get().reads(8)) - } - /// Storage: ElectionProviderMultiPhase Round (r:1 w:0) - /// Proof Skipped: ElectionProviderMultiPhase Round (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:1) - /// Proof Skipped: ElectionProviderMultiPhase CurrentPhase (max_values: Some(1), max_size: None, mode: Measured) - fn on_initialize_open_signed() -> Weight { - // Proof Size summary in bytes: - // Measured: `6` - // Estimated: `1491` - // Minimum execution time: 9_839_000 picoseconds. - Weight::from_parts(10_245_000, 0) - .saturating_add(Weight::from_parts(0, 1491)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: ElectionProviderMultiPhase Round (r:1 w:0) - /// Proof Skipped: ElectionProviderMultiPhase Round (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:1) - /// Proof Skipped: ElectionProviderMultiPhase CurrentPhase (max_values: Some(1), max_size: None, mode: Measured) - fn on_initialize_open_unsigned() -> Weight { - // Proof Size summary in bytes: - // Measured: `6` - // Estimated: `1491` - // Minimum execution time: 10_981_000 picoseconds. - Weight::from_parts(11_231_000, 0) - .saturating_add(Weight::from_parts(0, 1491)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) - /// Storage: ElectionProviderMultiPhase QueuedSolution (r:0 w:1) - /// Proof Skipped: ElectionProviderMultiPhase QueuedSolution (max_values: Some(1), max_size: None, mode: Measured) - fn finalize_signed_phase_accept_solution() -> Weight { - // Proof Size summary in bytes: - // Measured: `174` - // Estimated: `3593` - // Minimum execution time: 31_786_000 picoseconds. - Weight::from_parts(32_205_000, 0) - .saturating_add(Weight::from_parts(0, 3593)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) - fn finalize_signed_phase_reject_solution() -> Weight { - // Proof Size summary in bytes: - // Measured: `174` - // Estimated: `3593` - // Minimum execution time: 21_236_000 picoseconds. - Weight::from_parts(21_972_000, 0) - .saturating_add(Weight::from_parts(0, 3593)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: ElectionProviderMultiPhase SnapshotMetadata (r:0 w:1) - /// Proof Skipped: ElectionProviderMultiPhase SnapshotMetadata (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ElectionProviderMultiPhase DesiredTargets (r:0 w:1) - /// Proof Skipped: ElectionProviderMultiPhase DesiredTargets (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) - /// Proof Skipped: ElectionProviderMultiPhase Snapshot (max_values: Some(1), max_size: None, mode: Measured) - /// The range of component `v` is `[1000, 2000]`. - /// The range of component `t` is `[500, 1000]`. - fn create_snapshot_internal(v: u32, _t: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 633_519_000 picoseconds. - Weight::from_parts(654_417_363, 0) - .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 25_140 - .saturating_add(Weight::from_parts(454_358, 0).saturating_mul(v.into())) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) - /// Proof Skipped: ElectionProviderMultiPhase SignedSubmissionIndices (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ElectionProviderMultiPhase SignedSubmissionNextIndex (r:1 w:1) - /// Proof Skipped: ElectionProviderMultiPhase SignedSubmissionNextIndex (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ElectionProviderMultiPhase SnapshotMetadata (r:1 w:1) - /// Proof Skipped: ElectionProviderMultiPhase SnapshotMetadata (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ElectionProviderMultiPhase SignedSubmissionsMap (r:1 w:0) - /// Proof Skipped: ElectionProviderMultiPhase SignedSubmissionsMap (max_values: None, max_size: None, mode: Measured) - /// Storage: System BlockWeight (r:1 w:1) - /// Proof: System BlockWeight (max_values: Some(1), max_size: Some(48), added: 543, mode: MaxEncodedLen) - /// Storage: ElectionProviderMultiPhase QueuedSolution (r:1 w:1) - /// Proof Skipped: ElectionProviderMultiPhase QueuedSolution (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ElectionProviderMultiPhase Round (r:1 w:1) - /// Proof Skipped: ElectionProviderMultiPhase Round (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:1) - /// Proof Skipped: ElectionProviderMultiPhase CurrentPhase (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ElectionProviderMultiPhase DesiredTargets (r:0 w:1) - /// Proof Skipped: ElectionProviderMultiPhase DesiredTargets (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) - /// Proof Skipped: ElectionProviderMultiPhase Snapshot (max_values: Some(1), max_size: None, mode: Measured) - /// The range of component `a` is `[500, 800]`. - /// The range of component `d` is `[200, 400]`. - fn elect_queued(a: u32, d: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `229 + a * (768 ±0) + d * (48 ±0)` - // Estimated: `3781 + a * (768 ±0) + d * (49 ±0)` - // Minimum execution time: 397_371_000 picoseconds. - Weight::from_parts(434_700_000, 0) - .saturating_add(Weight::from_parts(0, 3781)) - // Standard Error: 15_899 - .saturating_add(Weight::from_parts(877_242, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(9)) - .saturating_add(Weight::from_parts(0, 768).saturating_mul(a.into())) - .saturating_add(Weight::from_parts(0, 49).saturating_mul(d.into())) - } - /// Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) - /// Proof Skipped: ElectionProviderMultiPhase CurrentPhase (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ElectionProviderMultiPhase SnapshotMetadata (r:1 w:0) - /// Proof Skipped: ElectionProviderMultiPhase SnapshotMetadata (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) - /// Proof: TransactionPayment NextFeeMultiplier (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) - /// Proof Skipped: ElectionProviderMultiPhase SignedSubmissionIndices (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ElectionProviderMultiPhase SignedSubmissionNextIndex (r:1 w:1) - /// Proof Skipped: ElectionProviderMultiPhase SignedSubmissionNextIndex (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ElectionProviderMultiPhase SignedSubmissionsMap (r:0 w:1) - /// Proof Skipped: ElectionProviderMultiPhase SignedSubmissionsMap (max_values: None, max_size: None, mode: Measured) - fn submit() -> Weight { - // Proof Size summary in bytes: - // Measured: `7368` - // Estimated: `8853` - // Minimum execution time: 62_891_000 picoseconds. - Weight::from_parts(68_415_000, 0) - .saturating_add(Weight::from_parts(0, 8853)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) - /// Proof Skipped: ElectionProviderMultiPhase CurrentPhase (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ElectionProviderMultiPhase Round (r:1 w:0) - /// Proof Skipped: ElectionProviderMultiPhase Round (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ElectionProviderMultiPhase DesiredTargets (r:1 w:0) - /// Proof Skipped: ElectionProviderMultiPhase DesiredTargets (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ElectionProviderMultiPhase QueuedSolution (r:1 w:1) - /// Proof Skipped: ElectionProviderMultiPhase QueuedSolution (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ElectionProviderMultiPhase SnapshotMetadata (r:1 w:0) - /// Proof Skipped: ElectionProviderMultiPhase SnapshotMetadata (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) - /// Proof Skipped: ElectionProviderMultiPhase Snapshot (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ElectionProviderMultiPhase MinimumUntrustedScore (r:1 w:0) - /// Proof Skipped: ElectionProviderMultiPhase MinimumUntrustedScore (max_values: Some(1), max_size: None, mode: Measured) - /// The range of component `v` is `[1000, 2000]`. - /// The range of component `t` is `[500, 1000]`. - /// The range of component `a` is `[500, 800]`. - /// The range of component `d` is `[200, 400]`. - fn submit_unsigned(v: u32, t: u32, a: u32, _d: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `110 + t * (32 ±0) + v * (553 ±0)` - // Estimated: `1595 + t * (32 ±0) + v * (553 ±0)` - // Minimum execution time: 6_652_347_000 picoseconds. - Weight::from_parts(7_246_265_000, 0) - .saturating_add(Weight::from_parts(0, 1595)) - // Standard Error: 35_723 - .saturating_add(Weight::from_parts(282_336, 0).saturating_mul(v.into())) - // Standard Error: 105_863 - .saturating_add(Weight::from_parts(6_158_464, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(Weight::from_parts(0, 32).saturating_mul(t.into())) - .saturating_add(Weight::from_parts(0, 553).saturating_mul(v.into())) - } - /// Storage: ElectionProviderMultiPhase DesiredTargets (r:1 w:0) - /// Proof Skipped: ElectionProviderMultiPhase DesiredTargets (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) - /// Proof Skipped: ElectionProviderMultiPhase Snapshot (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ElectionProviderMultiPhase Round (r:1 w:0) - /// Proof Skipped: ElectionProviderMultiPhase Round (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ElectionProviderMultiPhase MinimumUntrustedScore (r:1 w:0) - /// Proof Skipped: ElectionProviderMultiPhase MinimumUntrustedScore (max_values: Some(1), max_size: None, mode: Measured) - /// The range of component `v` is `[1000, 2000]`. - /// The range of component `t` is `[500, 1000]`. - /// The range of component `a` is `[500, 800]`. - /// The range of component `d` is `[200, 400]`. - fn feasibility_check(v: u32, t: u32, a: u32, _d: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `85 + t * (32 ±0) + v * (553 ±0)` - // Estimated: `1570 + t * (32 ±0) + v * (553 ±0)` - // Minimum execution time: 5_508_561_000 picoseconds. - Weight::from_parts(6_001_538_000, 0) - .saturating_add(Weight::from_parts(0, 1570)) - // Standard Error: 34_050 - .saturating_add(Weight::from_parts(712_513, 0).saturating_mul(v.into())) - // Standard Error: 100_904 - .saturating_add(Weight::from_parts(4_080_970, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(Weight::from_parts(0, 32).saturating_mul(t.into())) - .saturating_add(Weight::from_parts(0, 553).saturating_mul(v.into())) - } -} diff --git a/polkadot/runtime/westend/Cargo.toml b/polkadot/runtime/westend/Cargo.toml index 8f0ece8d25617..e945e64e7fc07 100644 --- a/polkadot/runtime/westend/Cargo.toml +++ b/polkadot/runtime/westend/Cargo.toml @@ -64,7 +64,7 @@ pallet-collective = { workspace = true } pallet-conviction-voting = { workspace = true } pallet-delegated-staking = { workspace = true } pallet-democracy = { workspace = true } -pallet-election-provider-multi-block = { workspace = true } +pallet-election-provider-multi-phase = { workspace = true } pallet-elections-phragmen = { workspace = true } pallet-fast-unstake = { workspace = true } pallet-grandpa = { workspace = true } @@ -163,7 +163,7 @@ std = [ "pallet-conviction-voting/std", "pallet-delegated-staking/std", "pallet-democracy/std", - "pallet-election-provider-multi-block/std", + "pallet-election-provider-multi-phase/std", "pallet-election-provider-support-benchmarking?/std", "pallet-elections-phragmen/std", "pallet-fast-unstake/std", @@ -254,7 +254,7 @@ runtime-benchmarks = [ "pallet-conviction-voting/runtime-benchmarks", "pallet-delegated-staking/runtime-benchmarks", "pallet-democracy/runtime-benchmarks", - "pallet-election-provider-multi-block/runtime-benchmarks", + "pallet-election-provider-multi-phase/runtime-benchmarks", "pallet-election-provider-support-benchmarking/runtime-benchmarks", "pallet-elections-phragmen/runtime-benchmarks", "pallet-fast-unstake/runtime-benchmarks", @@ -319,7 +319,7 @@ try-runtime = [ "pallet-conviction-voting/try-runtime", "pallet-delegated-staking/try-runtime", "pallet-democracy/try-runtime", - "pallet-election-provider-multi-block/try-runtime", + "pallet-election-provider-multi-phase/try-runtime", "pallet-elections-phragmen/try-runtime", "pallet-fast-unstake/try-runtime", "pallet-grandpa/try-runtime", diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index b6579086b27d5..3c36453115c79 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -1686,7 +1686,9 @@ mod runtime { #[runtime::pallet_index(23)] pub type Multisig = pallet_multisig; - // RIP EPM 24 + // Election pallet. Only works with staking, but placed here to maintain indices. + #[runtime::pallet_index(24)] + pub type ElectionProviderMultiPhase = pallet_election_provider_multi_phase; // Provides a semi-sorted list of nominators for staking. #[runtime::pallet_index(25)] @@ -1767,15 +1769,6 @@ mod runtime { #[runtime::pallet_index(66)] pub type Coretime = coretime; - #[runtime::pallet_index(67)] - pub type MultiBlock = pallet_election_provider_multi_block; - #[runtime::pallet_index(68)] - pub type MultiBlockVerifier = pallet_election_provider_multi_block::verifier; - #[runtime::pallet_index(69)] - pub type MultiBlockUnsigned = pallet_election_provider_multi_block::unsigned; - #[runtime::pallet_index(70)] - pub type MultiBlockSigned = pallet_election_provider_multi_block::signed; - // Migrations pallet #[runtime::pallet_index(98)] pub type MultiBlockMigrations = pallet_migrations; @@ -1913,10 +1906,7 @@ mod benches { [pallet_balances, Balances] [pallet_beefy_mmr, BeefyMmrLeaf] [pallet_conviction_voting, ConvictionVoting] - [pallet_election_provider_multi_block, MultiBlock] - [pallet_election_provider_multi_block::verifier, MultiBlockVerifier] - [pallet_election_provider_multi_block::unsigned, MultiBlockUnsigned] - [pallet_election_provider_multi_block::signed, MultiBlockSigned] + [pallet_election_provider_multi_phase, ElectionProviderMultiPhase] [frame_election_provider_support, ElectionProviderBench::] [pallet_fast_unstake, FastUnstake] [pallet_identity, Identity] diff --git a/substrate/frame/election-provider-multi-block/src/signed/mod.rs b/substrate/frame/election-provider-multi-block/src/signed/mod.rs index 0953957087fab..fe5f6739fa77c 100644 --- a/substrate/frame/election-provider-multi-block/src/signed/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/signed/mod.rs @@ -214,6 +214,7 @@ pub mod pallet { + MutateHold; /// Base deposit amount for a submission. + // TODO: needs to also be geometric. type DepositBase: Get>; /// Extra deposit per-page. diff --git a/substrate/frame/election-provider-multi-block/src/verifier/impls.rs b/substrate/frame/election-provider-multi-block/src/verifier/impls.rs index ccb923acf16b7..55374c1767239 100644 --- a/substrate/frame/election-provider-multi-block/src/verifier/impls.rs +++ b/substrate/frame/election-provider-multi-block/src/verifier/impls.rs @@ -107,6 +107,7 @@ pub(crate) mod pallet { /// Maximum number of backers, per winner, among all pages of an election. /// /// This can only be checked at the very final step of verification. + // TODO: integrity check for the relation of these bounds. type MaxBackersPerWinnerFinal: Get; /// Maximum number of backers, per winner, per page. From b6410c8a088a8c1362105bde2125358b2352fad3 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 28 Jan 2025 11:35:35 +0000 Subject: [PATCH 161/169] add readme and stuff --- .../assets/asset-hub-next-westend/.gitignore | 1 + .../assets/asset-hub-next-westend/README.md | 24 +++++++++++++++++++ .../zombienet-omni-node.toml | 22 +++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 cumulus/parachains/runtimes/assets/asset-hub-next-westend/.gitignore create mode 100644 cumulus/parachains/runtimes/assets/asset-hub-next-westend/README.md create mode 100644 cumulus/parachains/runtimes/assets/asset-hub-next-westend/zombienet-omni-node.toml diff --git a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/.gitignore b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/.gitignore new file mode 100644 index 0000000000000..cbd536beeb5a6 --- /dev/null +++ b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/.gitignore @@ -0,0 +1 @@ +chain_spec.json diff --git a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/README.md b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/README.md new file mode 100644 index 0000000000000..6caaf92e381b0 --- /dev/null +++ b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/README.md @@ -0,0 +1,24 @@ +# Asset Hub Next + +## Local Development + +In any case, prepare a chain-spec + +``` +# in this directory +cargo build --release +chain-spec-builder create --runtime ../../../../../target/release/wbuild/asset-hub-next-westend-runtime/asset_hub_next_westend_runtime.wasm --relay-chain rococo-local --para-id 1234 named-preset genesis +``` + +Real setup with Zombienet + +``` +zombienet --provider native spawn zombienet-omni-node.toml +``` + +Single-node, single dev mode. This doesn't check things like PoV limits at all, be careful! + +``` +polkadot-omni-node --chain ./chain_spec.json --dev-block-time 1000 --tmp +``` + diff --git a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/zombienet-omni-node.toml b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/zombienet-omni-node.toml new file mode 100644 index 0000000000000..b58940d42c55d --- /dev/null +++ b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/zombienet-omni-node.toml @@ -0,0 +1,22 @@ +[relaychain] +default_command = "polkadot" +chain = "rococo-local" + +[[relaychain.nodes]] +name = "alice" +validator = true +ws_port = 9944 + +[[relaychain.nodes]] +name = "bob" +validator = true +ws_port = 9955 + +[[parachains]] +id = 1000 +chain_spec_path = "chain_spec.json" + +[parachains.collator] +name = "charlie" +ws_port = 9988 +command = "polkadot-omni-node" From 0b8d744109a3c29d97a28e768a027e3438c8a69a Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 28 Jan 2025 11:52:43 +0000 Subject: [PATCH 162/169] Implement pallet view function queries (#4722) Closes #216. This PR allows pallets to define a `view_functions` impl like so: ```rust #[pallet::view_functions] impl Pallet where T::AccountId: From + SomeAssociation1, { /// Query value no args. pub fn get_value() -> Option { SomeValue::::get() } /// Query value with args. pub fn get_value_with_arg(key: u32) -> Option { SomeMap::::get(key) } } ``` ### `QueryId` Each view function is uniquely identified by a `QueryId`, which for this implementation is generated by: ```twox_128(pallet_name) ++ twox_128("fn_name(fnarg_types) -> return_ty")``` The prefix `twox_128(pallet_name)` is the same as the storage prefix for pallets and take into account multiple instances of the same pallet. The suffix is generated from the fn type signature so is guaranteed to be unique for that pallet impl. For one of the view fns in the example above it would be `twox_128("get_value_with_arg(u32) -> Option")`. It is a known limitation that only the type names themselves are taken into account: in the case of type aliases the signature may have the same underlying types but a different id; for generics the concrete types may be different but the signatures will remain the same. The existing Runtime `Call` dispatchables are addressed by their concatenated indices `pallet_index ++ call_index`, and the dispatching is handled by the SCALE decoding of the `RuntimeCallEnum::PalletVariant(PalletCallEnum::dispatchable_variant(payload))`. For `view_functions` the runtime/pallet generated enum structure is replaced by implementing the `DispatchQuery` trait on the outer (runtime) scope, dispatching to a pallet based on the id prefix, and the inner (pallet) scope dispatching to the specific function based on the id suffix. Future implementations could also modify/extend this scheme and routing to pallet agnostic queries. ### Executing externally These view functions can be executed externally via the system runtime api: ```rust pub trait ViewFunctionsApi where QueryId: codec::Codec, Query: codec::Codec, QueryResult: codec::Codec, Error: codec::Codec, { /// Execute a view function query. fn execute_query(query_id: QueryId, query: Query) -> Result; } ``` ### `XCQ` Currently there is work going on by @xlc to implement [`XCQ`](https://github.com/open-web3-stack/XCQ/) which may eventually supersede this work. It may be that we still need the fixed function local query dispatching in addition to XCQ, in the same way that we have chain specific runtime dispatchables and XCM. I have kept this in mind and the high level query API is agnostic to the underlying query dispatch and execution. I am just providing the implementation for the `view_function` definition. ### Metadata Currently I am utilizing the `custom` section of the frame metadata, to avoid modifying the official metadata format until this is standardized. ### vs `runtime_api` There are similarities with `runtime_apis`, some differences being: - queries can be defined directly on pallets, so no need for boilerplate declarations and implementations - no versioning, the `QueryId` will change if the signature changes. - possibility for queries to be executed from smart contracts (see below) ### Calling from contracts Future work would be to add `weight` annotations to the view function queries, and a host function to `pallet_contracts` to allow executing these queries from contracts. ### TODO - [x] Consistent naming (view functions pallet impl, queries, high level api?) - [ ] End to end tests via `runtime_api` - [ ] UI tests - [x] Mertadata tests - [ ] Docs --------- Co-authored-by: kianenigma Co-authored-by: James Wilson Co-authored-by: Giuseppe Re Co-authored-by: Guillaume Thiolliere --- .github/workflows/check-semver.yml | 1 + Cargo.lock | 19 ++ Cargo.toml | 2 + cumulus/pallets/weight-reclaim/src/tests.rs | 3 +- .../chain_spec_runtime/src/runtime.rs | 9 +- polkadot/runtime/westend/src/lib.rs | 9 +- prdoc/pr_4722.prdoc | 33 +++ substrate/bin/node/runtime/src/lib.rs | 9 +- substrate/frame/examples/Cargo.toml | 3 + substrate/frame/examples/src/lib.rs | 3 + .../frame/examples/view-functions/Cargo.toml | 61 ++++ .../frame/examples/view-functions/src/lib.rs | 114 ++++++++ .../examples/view-functions/src/tests.rs | 188 +++++++++++++ .../procedural/examples/proc_main/main.rs | 3 +- .../procedural/examples/proc_main/runtime.rs | 3 +- .../src/construct_runtime/expand/call.rs | 11 +- .../src/construct_runtime/expand/config.rs | 10 +- .../src/construct_runtime/expand/inherent.rs | 10 +- .../src/construct_runtime/expand/metadata.rs | 28 +- .../src/construct_runtime/expand/mod.rs | 2 + .../src/construct_runtime/expand/origin.rs | 19 +- .../construct_runtime/expand/outer_enums.rs | 19 +- .../src/construct_runtime/expand/task.rs | 10 +- .../src/construct_runtime/expand/unsigned.rs | 10 +- .../construct_runtime/expand/view_function.rs | 78 ++++++ .../procedural/src/construct_runtime/mod.rs | 14 +- .../procedural/src/construct_runtime/parse.rs | 13 + substrate/frame/support/procedural/src/lib.rs | 3 +- .../procedural/src/pallet/expand/mod.rs | 3 + .../src/pallet/expand/view_functions.rs | 263 ++++++++++++++++++ .../procedural/src/pallet/parse/mod.rs | 14 + .../src/pallet/parse/view_functions.rs | 155 +++++++++++ .../procedural/src/runtime/expand/mod.rs | 6 + .../src/runtime/parse/runtime_types.rs | 4 + substrate/frame/support/src/lib.rs | 1 + substrate/frame/support/src/tests/mod.rs | 3 +- substrate/frame/support/src/view_functions.rs | 128 +++++++++ .../deprecated_where_block.stderr | 34 +++ .../inject_runtime_type_invalid.stderr | 2 +- substrate/frame/support/test/tests/pallet.rs | 19 +- substrate/frame/support/test/tests/runtime.rs | 3 +- .../test/tests/runtime_legacy_ordering.rs | 3 +- .../invalid_runtime_type_derive.stderr | 2 +- .../test/tests/runtime_ui/pass/basic.rs | 2 +- substrate/primitives/metadata-ir/src/lib.rs | 1 + substrate/primitives/metadata-ir/src/types.rs | 87 +++++- substrate/primitives/metadata-ir/src/v15.rs | 40 +-- templates/minimal/runtime/src/lib.rs | 3 +- .../parachain/pallets/template/src/mock.rs | 3 +- templates/parachain/runtime/src/apis.rs | 6 + templates/parachain/runtime/src/lib.rs | 3 +- .../solochain/pallets/template/src/mock.rs | 3 +- templates/solochain/runtime/src/apis.rs | 6 + templates/solochain/runtime/src/lib.rs | 3 +- 54 files changed, 1349 insertions(+), 135 deletions(-) create mode 100644 prdoc/pr_4722.prdoc create mode 100644 substrate/frame/examples/view-functions/Cargo.toml create mode 100644 substrate/frame/examples/view-functions/src/lib.rs create mode 100644 substrate/frame/examples/view-functions/src/tests.rs create mode 100644 substrate/frame/support/procedural/src/construct_runtime/expand/view_function.rs create mode 100644 substrate/frame/support/procedural/src/pallet/expand/view_functions.rs create mode 100644 substrate/frame/support/procedural/src/pallet/parse/view_functions.rs create mode 100644 substrate/frame/support/src/view_functions.rs diff --git a/.github/workflows/check-semver.yml b/.github/workflows/check-semver.yml index 43c70d6abc78b..df1a1c8be6038 100644 --- a/.github/workflows/check-semver.yml +++ b/.github/workflows/check-semver.yml @@ -76,6 +76,7 @@ jobs: if: ${{ !contains(github.event.pull_request.labels.*.name, 'R0-silent') }} run: | rustup default $TOOLCHAIN + rustup target add wasm32-unknown-unknown --toolchain $TOOLCHAIN rustup component add rust-src --toolchain $TOOLCHAIN - name: install parity-publish diff --git a/Cargo.lock b/Cargo.lock index d04808c0f0896..f12dff812452c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13635,6 +13635,24 @@ dependencies = [ "sp-runtime 31.0.1", ] +[[package]] +name = "pallet-example-view-functions" +version = "1.0.0" +dependencies = [ + "frame-benchmarking 28.0.0", + "frame-metadata 18.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", + "log", + "parity-scale-codec", + "pretty_assertions", + "scale-info", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-metadata-ir 0.6.0", + "sp-runtime 31.0.1", +] + [[package]] name = "pallet-examples" version = "4.0.0-dev" @@ -13649,6 +13667,7 @@ dependencies = [ "pallet-example-single-block-migrations", "pallet-example-split", "pallet-example-tasks", + "pallet-example-view-functions", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 7a906e7c0d64f..36c048d77c8ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -363,6 +363,7 @@ members = [ "substrate/frame/examples/single-block-migrations", "substrate/frame/examples/split", "substrate/frame/examples/tasks", + "substrate/frame/examples/view-functions", "substrate/frame/executive", "substrate/frame/fast-unstake", "substrate/frame/glutton", @@ -941,6 +942,7 @@ pallet-example-offchain-worker = { path = "substrate/frame/examples/offchain-wor pallet-example-single-block-migrations = { path = "substrate/frame/examples/single-block-migrations", default-features = false } pallet-example-split = { path = "substrate/frame/examples/split", default-features = false } pallet-example-tasks = { path = "substrate/frame/examples/tasks", default-features = false } +pallet-example-view-functions = { path = "substrate/frame/examples/view-functions", default-features = false } pallet-examples = { path = "substrate/frame/examples" } pallet-fast-unstake = { path = "substrate/frame/fast-unstake", default-features = false } pallet-glutton = { path = "substrate/frame/glutton", default-features = false } diff --git a/cumulus/pallets/weight-reclaim/src/tests.rs b/cumulus/pallets/weight-reclaim/src/tests.rs index b87c107c7ec71..ce647445b3327 100644 --- a/cumulus/pallets/weight-reclaim/src/tests.rs +++ b/cumulus/pallets/weight-reclaim/src/tests.rs @@ -89,7 +89,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Test; diff --git a/docs/sdk/src/reference_docs/chain_spec_runtime/src/runtime.rs b/docs/sdk/src/reference_docs/chain_spec_runtime/src/runtime.rs index 282fc1ff489c0..3ffa3d61263f2 100644 --- a/docs/sdk/src/reference_docs/chain_spec_runtime/src/runtime.rs +++ b/docs/sdk/src/reference_docs/chain_spec_runtime/src/runtime.rs @@ -57,7 +57,14 @@ type SignedExtra = (); mod runtime { /// The main runtime type. #[runtime::runtime] - #[runtime::derive(RuntimeCall, RuntimeEvent, RuntimeError, RuntimeOrigin, RuntimeTask)] + #[runtime::derive( + RuntimeCall, + RuntimeEvent, + RuntimeError, + RuntimeOrigin, + RuntimeTask, + RuntimeViewFunction + )] pub struct Runtime; /// Mandatory system pallet that should always be included in a FRAME runtime. diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 935b62c23388e..4d5b56bcd911a 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -1613,7 +1613,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; @@ -1975,6 +1976,12 @@ sp_api::impl_runtime_apis! { } } + impl frame_support::view_functions::runtime_api::RuntimeViewFunction for Runtime { + fn execute_view_function(id: frame_support::view_functions::ViewFunctionId, input: Vec) -> Result, frame_support::view_functions::ViewFunctionDispatchError> { + Runtime::execute_view_function(id, input) + } + } + impl sp_block_builder::BlockBuilder for Runtime { fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { Executive::apply_extrinsic(extrinsic) diff --git a/prdoc/pr_4722.prdoc b/prdoc/pr_4722.prdoc new file mode 100644 index 0000000000000..a5bdbbeb3df9a --- /dev/null +++ b/prdoc/pr_4722.prdoc @@ -0,0 +1,33 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Implement pallet view functions + +doc: + - audience: Runtime Dev + description: | + Read-only view functions can now be defined on pallets. These functions provide an interface for querying state, + from both outside and inside the runtime. Common queries can be defined on pallets, without users having to + access the storage directly. + + - audience: Runtime User + description: | + Querying the runtime state is now easier with the introduction of pallet view functions. Clients can call commonly + defined view functions rather than accessing the storage directly. These are similar to the Runtime APIs, but + are defined within the runtime itself. + +crates: + - name: frame-support + bump: minor + - name: sp-metadata-ir + bump: major + - name: frame-support-procedural + bump: patch + - name: pallet-example-view-functions + bump: patch + - name: cumulus-pov-validator + bump: none + - name: cumulus-pallet-weight-reclaim + bump: patch + - name: westend-runtime + bump: minor \ No newline at end of file diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 220929fdfd838..202aee37074db 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -2459,7 +2459,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; @@ -3013,6 +3014,12 @@ impl_runtime_apis! { } } + impl frame_support::view_functions::runtime_api::RuntimeViewFunction for Runtime { + fn execute_view_function(id: frame_support::view_functions::ViewFunctionId, input: Vec) -> Result, frame_support::view_functions::ViewFunctionDispatchError> { + Runtime::execute_view_function(id, input) + } + } + impl sp_block_builder::BlockBuilder for Runtime { fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { Executive::apply_extrinsic(extrinsic) diff --git a/substrate/frame/examples/Cargo.toml b/substrate/frame/examples/Cargo.toml index 9eac53f0d98b0..40d6959378b87 100644 --- a/substrate/frame/examples/Cargo.toml +++ b/substrate/frame/examples/Cargo.toml @@ -26,6 +26,7 @@ pallet-example-offchain-worker = { workspace = true } pallet-example-single-block-migrations = { workspace = true } pallet-example-split = { workspace = true } pallet-example-tasks = { workspace = true } +pallet-example-view-functions = { workspace = true } [features] default = ["std"] @@ -40,6 +41,7 @@ std = [ "pallet-example-single-block-migrations/std", "pallet-example-split/std", "pallet-example-tasks/std", + "pallet-example-view-functions/std", ] try-runtime = [ "pallet-default-config-example/try-runtime", @@ -51,4 +53,5 @@ try-runtime = [ "pallet-example-single-block-migrations/try-runtime", "pallet-example-split/try-runtime", "pallet-example-tasks/try-runtime", + "pallet-example-view-functions/try-runtime", ] diff --git a/substrate/frame/examples/src/lib.rs b/substrate/frame/examples/src/lib.rs index d0d30830f2f04..200e92112a3f3 100644 --- a/substrate/frame/examples/src/lib.rs +++ b/substrate/frame/examples/src/lib.rs @@ -48,6 +48,9 @@ //! //! - [`pallet_example_tasks`]: This pallet demonstrates the use of `Tasks` to execute service work. //! +//! - [`pallet_example_view_functions`]: This pallet demonstrates the use of view functions to query +//! pallet state. +//! //! - [`pallet_example_authorization_tx_extension`]: An example `TransactionExtension` that //! authorizes a custom origin through signature validation, along with two support pallets to //! showcase the usage. diff --git a/substrate/frame/examples/view-functions/Cargo.toml b/substrate/frame/examples/view-functions/Cargo.toml new file mode 100644 index 0000000000000..54d8322643917 --- /dev/null +++ b/substrate/frame/examples/view-functions/Cargo.toml @@ -0,0 +1,61 @@ +[package] +name = "pallet-example-view-functions" +version = "1.0.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +description = "Pallet to demonstrate the usage of view functions to query pallet state" + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.12", default-features = false, workspace = true } +frame-metadata = { features = ["current"], workspace = true } +log = { workspace = true } +scale-info = { version = "2.11.1", default-features = false, features = ["derive"], workspace = true } + +frame-support = { path = "../../support", default-features = false, workspace = true } +frame-system = { path = "../../system", default-features = false, workspace = true } + +sp-core = { default-features = false, path = "../../../primitives/core", workspace = true } +sp-io = { path = "../../../primitives/io", default-features = false, workspace = true } +sp-metadata-ir = { path = "../../../primitives/metadata-ir", default-features = false, workspace = true } +sp-runtime = { path = "../../../primitives/runtime", default-features = false, workspace = true } + +frame-benchmarking = { path = "../../benchmarking", default-features = false, optional = true, workspace = true } + +[dev-dependencies] +pretty_assertions = { version = "1.3.0" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-metadata/std", + "frame-support/std", + "frame-system/std", + "log/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-metadata-ir/std", + "sp-runtime/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/substrate/frame/examples/view-functions/src/lib.rs b/substrate/frame/examples/view-functions/src/lib.rs new file mode 100644 index 0000000000000..e842a718ad334 --- /dev/null +++ b/substrate/frame/examples/view-functions/src/lib.rs @@ -0,0 +1,114 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This pallet demonstrates the use of the `pallet::view_functions_experimental` api for service +//! work. +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod tests; + +use frame_support::Parameter; +use scale_info::TypeInfo; + +pub struct SomeType1; +impl From for u64 { + fn from(_t: SomeType1) -> Self { + 0u64 + } +} + +pub trait SomeAssociation1 { + type _1: Parameter + codec::MaxEncodedLen + TypeInfo; +} +impl SomeAssociation1 for u64 { + type _1 = u64; +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + + #[pallet::error] + pub enum Error {} + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::storage] + pub type SomeValue = StorageValue<_, u32>; + + #[pallet::storage] + pub type SomeMap = StorageMap<_, Twox64Concat, u32, u32, OptionQuery>; + + #[pallet::view_functions_experimental] + impl Pallet + where + T::AccountId: From + SomeAssociation1, + { + /// Query value no args. + pub fn get_value() -> Option { + SomeValue::::get() + } + + /// Query value with args. + pub fn get_value_with_arg(key: u32) -> Option { + SomeMap::::get(key) + } + } +} + +#[frame_support::pallet] +pub mod pallet2 { + use super::*; + use frame_support::pallet_prelude::*; + + #[pallet::error] + pub enum Error {} + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(PhantomData<(T, I)>); + + #[pallet::storage] + pub type SomeValue, I: 'static = ()> = StorageValue<_, u32>; + + #[pallet::storage] + pub type SomeMap, I: 'static = ()> = + StorageMap<_, Twox64Concat, u32, u32, OptionQuery>; + + #[pallet::view_functions_experimental] + impl, I: 'static> Pallet + where + T::AccountId: From + SomeAssociation1, + { + /// Query value no args. + pub fn get_value() -> Option { + SomeValue::::get() + } + + /// Query value with args. + pub fn get_value_with_arg(key: u32) -> Option { + SomeMap::::get(key) + } + } +} diff --git a/substrate/frame/examples/view-functions/src/tests.rs b/substrate/frame/examples/view-functions/src/tests.rs new file mode 100644 index 0000000000000..25f5f094651d6 --- /dev/null +++ b/substrate/frame/examples/view-functions/src/tests.rs @@ -0,0 +1,188 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Tests for `pallet-example-view-functions`. +#![cfg(test)] + +use crate::{ + pallet::{self, Pallet}, + pallet2, +}; +use codec::{Decode, Encode}; +use scale_info::{form::PortableForm, meta_type}; + +use frame_support::{derive_impl, pallet_prelude::PalletInfoAccess, view_functions::ViewFunction}; +use sp_io::hashing::twox_128; +use sp_metadata_ir::{ViewFunctionArgMetadataIR, ViewFunctionGroupIR, ViewFunctionMetadataIR}; +use sp_runtime::testing::TestXt; + +pub type AccountId = u32; +pub type Balance = u32; + +type Block = frame_system::mocking::MockBlock; +frame_support::construct_runtime!( + pub enum Runtime { + System: frame_system, + ViewFunctionsExample: pallet, + ViewFunctionsInstance: pallet2, + ViewFunctionsInstance1: pallet2::, + } +); + +pub type Extrinsic = TestXt; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Runtime { + type Block = Block; +} + +impl pallet::Config for Runtime {} +impl pallet2::Config for Runtime {} + +impl pallet2::Config for Runtime {} + +pub fn new_test_ext() -> sp_io::TestExternalities { + use sp_runtime::BuildStorage; + + let t = RuntimeGenesisConfig { system: Default::default() }.build_storage().unwrap(); + t.into() +} + +#[test] +fn pallet_get_value_query() { + new_test_ext().execute_with(|| { + let some_value = Some(99); + pallet::SomeValue::::set(some_value); + assert_eq!(some_value, Pallet::::get_value()); + + let query = pallet::GetValueViewFunction::::new(); + test_dispatch_view_function(&query, some_value); + }); +} + +#[test] +fn pallet_get_value_with_arg_query() { + new_test_ext().execute_with(|| { + let some_key = 1u32; + let some_value = Some(123); + pallet::SomeMap::::set(some_key, some_value); + assert_eq!(some_value, Pallet::::get_value_with_arg(some_key)); + + let query = pallet::GetValueWithArgViewFunction::::new(some_key); + test_dispatch_view_function(&query, some_value); + }); +} + +#[test] +fn pallet_multiple_instances() { + use pallet2::Instance1; + + new_test_ext().execute_with(|| { + let instance_value = Some(123); + let instance1_value = Some(456); + + pallet2::SomeValue::::set(instance_value); + pallet2::SomeValue::::set(instance1_value); + + let query = pallet2::GetValueViewFunction::::new(); + test_dispatch_view_function(&query, instance_value); + + let query_instance1 = pallet2::GetValueViewFunction::::new(); + test_dispatch_view_function(&query_instance1, instance1_value); + }); +} + +#[test] +fn metadata_ir_definitions() { + new_test_ext().execute_with(|| { + let metadata_ir = Runtime::metadata_ir(); + let pallet1 = metadata_ir + .view_functions + .groups + .iter() + .find(|pallet| pallet.name == "ViewFunctionsExample") + .unwrap(); + + fn view_fn_id(preifx_hash: [u8; 16], view_fn_signature: &str) -> [u8; 32] { + let mut id = [0u8; 32]; + id[..16].copy_from_slice(&preifx_hash); + id[16..].copy_from_slice(&twox_128(view_fn_signature.as_bytes())); + id + } + + let get_value_id = view_fn_id( + ::name_hash(), + "get_value() -> Option", + ); + + let get_value_with_arg_id = view_fn_id( + ::name_hash(), + "get_value_with_arg(u32) -> Option", + ); + + pretty_assertions::assert_eq!( + pallet1.view_functions, + vec![ + ViewFunctionMetadataIR { + name: "get_value", + id: get_value_id, + args: vec![], + output: meta_type::>(), + docs: vec![" Query value no args."], + }, + ViewFunctionMetadataIR { + name: "get_value_with_arg", + id: get_value_with_arg_id, + args: vec![ViewFunctionArgMetadataIR { name: "key", ty: meta_type::() },], + output: meta_type::>(), + docs: vec![" Query value with args."], + }, + ] + ); + }); +} + +#[test] +fn metadata_encoded_to_custom_value() { + new_test_ext().execute_with(|| { + let metadata = sp_metadata_ir::into_latest(Runtime::metadata_ir()); + // metadata is currently experimental so lives as a custom value. + let frame_metadata::RuntimeMetadata::V15(v15) = metadata.1 else { + panic!("Expected metadata v15") + }; + let custom_value = v15 + .custom + .map + .get("view_functions_experimental") + .expect("Expected custom value"); + let view_function_groups: Vec> = + Decode::decode(&mut &custom_value.value[..]).unwrap(); + assert_eq!(view_function_groups.len(), 4); + }); +} + +fn test_dispatch_view_function(query: &Q, expected: V) +where + Q: ViewFunction + Encode, + V: Decode + Eq + PartialEq + std::fmt::Debug, +{ + let input = query.encode(); + let output = Runtime::execute_view_function(Q::id(), input).unwrap(); + let query_result = V::decode(&mut &output[..]).unwrap(); + + assert_eq!(expected, query_result,); +} diff --git a/substrate/frame/support/procedural/examples/proc_main/main.rs b/substrate/frame/support/procedural/examples/proc_main/main.rs index 4bdfc76dd92f0..946bd5ff03ed2 100644 --- a/substrate/frame/support/procedural/examples/proc_main/main.rs +++ b/substrate/frame/support/procedural/examples/proc_main/main.rs @@ -234,7 +234,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; diff --git a/substrate/frame/support/procedural/examples/proc_main/runtime.rs b/substrate/frame/support/procedural/examples/proc_main/runtime.rs index 109ca4f6dc488..8de560555895b 100644 --- a/substrate/frame/support/procedural/examples/proc_main/runtime.rs +++ b/substrate/frame/support/procedural/examples/proc_main/runtime.rs @@ -99,7 +99,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs index f055e8ce28e90..411d74ecbb3d2 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs @@ -18,7 +18,6 @@ use crate::construct_runtime::Pallet; use proc_macro2::TokenStream; use quote::quote; -use std::str::FromStr; use syn::Ident; pub fn expand_outer_dispatch( @@ -40,15 +39,7 @@ pub fn expand_outer_dispatch( let name = &pallet_declaration.name; let path = &pallet_declaration.path; let index = pallet_declaration.index; - let attr = - pallet_declaration.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = pallet_declaration.get_attributes(); variant_defs.extend(quote! { #attr diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/config.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/config.rs index dbbe6ba6e6c32..7a51ba6ecf1da 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/config.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/config.rs @@ -19,7 +19,6 @@ use crate::construct_runtime::Pallet; use inflector::Inflector; use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens}; -use std::str::FromStr; use syn::Ident; pub fn expand_outer_config( @@ -41,14 +40,7 @@ pub fn expand_outer_config( let field_name = &Ident::new(&pallet_name.to_string().to_snake_case(), decl.name.span()); let part_is_generic = !pallet_entry.generics.params.is_empty(); - let attr = &decl.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = &decl.get_attributes(); types.extend(expand_config_types(attr, runtime, decl, &config, part_is_generic)); fields.extend(quote!(#attr pub #field_name: #config,)); diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/inherent.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/inherent.rs index e34c6ac5016a9..e25492802c329 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/inherent.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/inherent.rs @@ -18,7 +18,6 @@ use crate::construct_runtime::Pallet; use proc_macro2::TokenStream; use quote::quote; -use std::str::FromStr; use syn::Ident; pub fn expand_outer_inherent( @@ -36,14 +35,7 @@ pub fn expand_outer_inherent( if pallet_decl.exists_part("Inherent") { let name = &pallet_decl.name; let path = &pallet_decl.path; - let attr = pallet_decl.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = pallet_decl.get_attributes(); pallet_names.push(name); pallet_attrs.push(attr); diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs index 0b3bd51688651..d246c00628640 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs @@ -18,7 +18,6 @@ use crate::construct_runtime::{parse::PalletPath, Pallet}; use proc_macro2::TokenStream; use quote::quote; -use std::str::FromStr; use syn::Ident; pub fn expand_runtime_metadata( @@ -51,14 +50,7 @@ pub fn expand_runtime_metadata( let errors = expand_pallet_metadata_errors(runtime, decl); let associated_types = expand_pallet_metadata_associated_types(runtime, decl); let docs = expand_pallet_metadata_docs(runtime, decl); - let attr = decl.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = decl.get_attributes(); let deprecation_info = expand_pallet_metadata_deprecation(runtime, decl); quote! { #attr @@ -78,6 +70,20 @@ pub fn expand_runtime_metadata( }) .collect::>(); + let view_functions = pallet_declarations.iter().map(|decl| { + let name = &decl.name; + let path = &decl.path; + let instance = decl.instance.as_ref().into_iter(); + let attr = decl.get_attributes(); + + quote! { + #attr + #path::Pallet::<#runtime #(, #path::#instance)*>::pallet_view_functions_metadata( + ::core::stringify!(#name) + ) + } + }); + quote! { impl #runtime { fn metadata_ir() -> #scrate::__private::metadata_ir::MetadataIR { @@ -149,6 +155,10 @@ pub fn expand_runtime_metadata( >(), event_enum_ty: #scrate::__private::scale_info::meta_type::(), error_enum_ty: #scrate::__private::scale_info::meta_type::(), + }, + view_functions: #scrate::__private::metadata_ir::RuntimeViewFunctionsIR { + ty: #scrate::__private::scale_info::meta_type::(), + groups: #scrate::__private::sp_std::vec![ #(#view_functions),* ], } } } diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/mod.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/mod.rs index 88f9a3c6e33fd..823aa69dbdf2b 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/mod.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/mod.rs @@ -28,6 +28,7 @@ mod outer_enums; mod slash_reason; mod task; mod unsigned; +mod view_function; pub use call::expand_outer_dispatch; pub use config::expand_outer_config; @@ -41,3 +42,4 @@ pub use outer_enums::{expand_outer_enum, OuterEnumType}; pub use slash_reason::expand_outer_slash_reason; pub use task::expand_outer_task; pub use unsigned::expand_outer_validate_unsigned; +pub use view_function::expand_outer_query; diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/origin.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/origin.rs index 1c4ab436ad92a..4742e68e2e2a8 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/origin.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/origin.rs @@ -18,7 +18,6 @@ use crate::construct_runtime::{Pallet, SYSTEM_PALLET_NAME}; use proc_macro2::TokenStream; use quote::quote; -use std::str::FromStr; use syn::{Generics, Ident}; pub fn expand_outer_origin( @@ -335,14 +334,7 @@ fn expand_origin_caller_variant( let part_is_generic = !generics.params.is_empty(); let variant_name = &pallet.name; let path = &pallet.path; - let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = pallet.get_attributes(); match instance { Some(inst) if part_is_generic => quote! { @@ -387,14 +379,7 @@ fn expand_origin_pallet_conversions( }; let doc_string = get_intra_doc_string(" Convert to runtime origin using", &path.module_name()); - let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = pallet.get_attributes(); quote! { #attr diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs index 80b242ccbe493..80d3a5af26627 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs @@ -18,7 +18,6 @@ use crate::construct_runtime::Pallet; use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; -use std::str::FromStr; use syn::{Generics, Ident}; /// Represents the types supported for creating an outer enum. @@ -185,14 +184,7 @@ fn expand_enum_variant( let path = &pallet.path; let variant_name = &pallet.name; let part_is_generic = !generics.params.is_empty(); - let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = pallet.get_attributes(); match instance { Some(inst) if part_is_generic => quote! { @@ -224,14 +216,7 @@ fn expand_enum_conversion( enum_name_ident: &Ident, ) -> TokenStream { let variant_name = &pallet.name; - let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = pallet.get_attributes(); quote! { #attr diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/task.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/task.rs index 1302f86455f2c..b9b8efb8c0063 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/task.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/task.rs @@ -16,7 +16,6 @@ // limitations under the License use crate::construct_runtime::Pallet; -use core::str::FromStr; use proc_macro2::{Ident, TokenStream as TokenStream2}; use quote::quote; @@ -42,14 +41,7 @@ pub fn expand_outer_task( let instance = decl.instance.as_ref().map(|instance| quote!(, #path::#instance)); let task_type = quote!(#path::Task<#runtime_name #instance>); - let attr = decl.cfg_pattern.iter().fold(TokenStream2::new(), |acc, pattern| { - let attr = TokenStream2::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = decl.get_attributes(); from_impls.push(quote! { #attr diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/unsigned.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/unsigned.rs index 33aadba0d1f1c..737a39ea681e0 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/unsigned.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/unsigned.rs @@ -18,7 +18,6 @@ use crate::construct_runtime::Pallet; use proc_macro2::TokenStream; use quote::quote; -use std::str::FromStr; use syn::Ident; pub fn expand_outer_validate_unsigned( @@ -34,14 +33,7 @@ pub fn expand_outer_validate_unsigned( if pallet_decl.exists_part("ValidateUnsigned") { let name = &pallet_decl.name; let path = &pallet_decl.path; - let attr = pallet_decl.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); + let attr = pallet_decl.get_attributes(); pallet_names.push(name); pallet_attrs.push(attr); diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/view_function.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/view_function.rs new file mode 100644 index 0000000000000..094dcca4a5b52 --- /dev/null +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/view_function.rs @@ -0,0 +1,78 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License + +use crate::construct_runtime::Pallet; +use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; + +/// Expands implementation of runtime level `DispatchViewFunction`. +pub fn expand_outer_query( + runtime_name: &Ident, + pallet_decls: &[Pallet], + scrate: &TokenStream2, +) -> TokenStream2 { + let runtime_view_function = syn::Ident::new("RuntimeViewFunction", Span::call_site()); + + let prefix_conditionals = pallet_decls.iter().map(|pallet| { + let pallet_name = &pallet.name; + let attr = pallet.get_attributes(); + quote::quote! { + #attr + if id.prefix == <#pallet_name as #scrate::view_functions::ViewFunctionIdPrefix>::prefix() { + return <#pallet_name as #scrate::view_functions::DispatchViewFunction>::dispatch_view_function(id, input, output) + } + } + }); + + quote::quote! { + /// Runtime query type. + #[derive( + Clone, PartialEq, Eq, + #scrate::__private::codec::Encode, + #scrate::__private::codec::Decode, + #scrate::__private::scale_info::TypeInfo, + #scrate::__private::RuntimeDebug, + )] + pub enum #runtime_view_function {} + + const _: () = { + impl #scrate::view_functions::DispatchViewFunction for #runtime_view_function { + fn dispatch_view_function( + id: & #scrate::view_functions::ViewFunctionId, + input: &mut &[u8], + output: &mut O + ) -> Result<(), #scrate::view_functions::ViewFunctionDispatchError> + { + #( #prefix_conditionals )* + Err(#scrate::view_functions::ViewFunctionDispatchError::NotFound(id.clone())) + } + } + + impl #runtime_name { + /// Convenience function for query execution from the runtime API. + pub fn execute_view_function( + id: #scrate::view_functions::ViewFunctionId, + input: #scrate::__private::Vec<::core::primitive::u8>, + ) -> Result<#scrate::__private::Vec<::core::primitive::u8>, #scrate::view_functions::ViewFunctionDispatchError> + { + let mut output = #scrate::__private::vec![]; + <#runtime_view_function as #scrate::view_functions::DispatchViewFunction>::dispatch_view_function(&id, &mut &input[..], &mut output)?; + Ok(output) + } + } + }; + } +} diff --git a/substrate/frame/support/procedural/src/construct_runtime/mod.rs b/substrate/frame/support/procedural/src/construct_runtime/mod.rs index 087faf37252de..c6018e048f2f8 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/mod.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/mod.rs @@ -400,6 +400,7 @@ fn construct_runtime_final_expansion( let dispatch = expand::expand_outer_dispatch(&name, system_pallet, &pallets, &scrate); let tasks = expand::expand_outer_task(&name, &pallets, &scrate); + let query = expand::expand_outer_query(&name, &pallets, &scrate); let metadata = expand::expand_runtime_metadata( &name, &pallets, @@ -492,6 +493,8 @@ fn construct_runtime_final_expansion( #tasks + #query + #metadata #outer_config @@ -650,16 +653,7 @@ pub(crate) fn decl_pallet_runtime_setup( .collect::>(); let pallet_attrs = pallet_declarations .iter() - .map(|pallet| { - pallet.cfg_pattern.iter().fold(TokenStream2::new(), |acc, pattern| { - let attr = TokenStream2::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }) - }) + .map(|pallet| pallet.get_attributes()) .collect::>(); quote!( diff --git a/substrate/frame/support/procedural/src/construct_runtime/parse.rs b/substrate/frame/support/procedural/src/construct_runtime/parse.rs index 729a803a302ed..2df08123821a3 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/parse.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/parse.rs @@ -15,6 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use core::str::FromStr; use frame_support_procedural_tools::syn_ext as ext; use proc_macro2::{Span, TokenStream}; use quote::ToTokens; @@ -609,6 +610,18 @@ impl Pallet { pub fn exists_part(&self, name: &str) -> bool { self.find_part(name).is_some() } + + // Get runtime attributes for the pallet, mostly used for macros + pub fn get_attributes(&self) -> TokenStream { + self.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { + let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) + .expect("was successfully parsed before; qed"); + quote::quote! { + #acc + #attr + } + }) + } } /// Result of a conversion of a declaration of pallets. diff --git a/substrate/frame/support/procedural/src/lib.rs b/substrate/frame/support/procedural/src/lib.rs index c2f546d92048a..26703a2438ef9 100644 --- a/substrate/frame/support/procedural/src/lib.rs +++ b/substrate/frame/support/procedural/src/lib.rs @@ -817,6 +817,7 @@ pub fn inject_runtime_type(_: TokenStream, tokens: TokenStream) -> TokenStream { if item.ident != "RuntimeCall" && item.ident != "RuntimeEvent" && item.ident != "RuntimeTask" && + item.ident != "RuntimeViewFunction" && item.ident != "RuntimeOrigin" && item.ident != "RuntimeHoldReason" && item.ident != "RuntimeFreezeReason" && @@ -826,7 +827,7 @@ pub fn inject_runtime_type(_: TokenStream, tokens: TokenStream) -> TokenStream { return syn::Error::new_spanned( item, "`#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, \ - `RuntimeTask`, `RuntimeOrigin`, `RuntimeParameters` or `PalletInfo`", + `RuntimeTask`, `RuntimeViewFunction`, `RuntimeOrigin`, `RuntimeParameters` or `PalletInfo`", ) .to_compile_error() .into(); diff --git a/substrate/frame/support/procedural/src/pallet/expand/mod.rs b/substrate/frame/support/procedural/src/pallet/expand/mod.rs index 3f9b50f79c0cc..439ec55e269d4 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/mod.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/mod.rs @@ -35,6 +35,7 @@ mod tasks; mod tt_default_parts; mod type_value; mod validate_unsigned; +mod view_functions; mod warnings; use crate::pallet::Def; @@ -66,6 +67,7 @@ pub fn expand(mut def: Def) -> proc_macro2::TokenStream { let error = error::expand_error(&mut def); let event = event::expand_event(&mut def); let storages = storage::expand_storages(&mut def); + let view_functions = view_functions::expand_view_functions(&def); let inherents = inherent::expand_inherents(&mut def); let instances = instances::expand_instances(&mut def); let hooks = hooks::expand_hooks(&mut def); @@ -108,6 +110,7 @@ storage item. Otherwise, all storage items are listed among [*Type Definitions*] #error #event #storages + #view_functions #inherents #instances #hooks diff --git a/substrate/frame/support/procedural/src/pallet/expand/view_functions.rs b/substrate/frame/support/procedural/src/pallet/expand/view_functions.rs new file mode 100644 index 0000000000000..587e74a2ac182 --- /dev/null +++ b/substrate/frame/support/procedural/src/pallet/expand/view_functions.rs @@ -0,0 +1,263 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::pallet::{parse::view_functions::ViewFunctionDef, Def}; +use proc_macro2::{Span, TokenStream}; +use syn::spanned::Spanned; + +pub fn expand_view_functions(def: &Def) -> TokenStream { + let (span, where_clause, view_fns, docs) = match def.view_functions.as_ref() { + Some(view_fns) => ( + view_fns.attr_span, + view_fns.where_clause.clone(), + view_fns.view_functions.clone(), + view_fns.docs.clone(), + ), + None => (def.item.span(), def.config.where_clause.clone(), Vec::new(), Vec::new()), + }; + + let view_function_prefix_impl = + expand_view_function_prefix_impl(def, span, where_clause.as_ref()); + + let view_fn_impls = view_fns + .iter() + .map(|view_fn| expand_view_function(def, span, where_clause.as_ref(), view_fn)); + let impl_dispatch_view_function = + impl_dispatch_view_function(def, span, where_clause.as_ref(), &view_fns); + let impl_view_function_metadata = + impl_view_function_metadata(def, span, where_clause.as_ref(), &view_fns, &docs); + + quote::quote! { + #view_function_prefix_impl + #( #view_fn_impls )* + #impl_dispatch_view_function + #impl_view_function_metadata + } +} + +fn expand_view_function_prefix_impl( + def: &Def, + span: Span, + where_clause: Option<&syn::WhereClause>, +) -> TokenStream { + let pallet_ident = &def.pallet_struct.pallet; + let frame_support = &def.frame_support; + let frame_system = &def.frame_system; + let type_impl_gen = &def.type_impl_generics(span); + let type_use_gen = &def.type_use_generics(span); + + quote::quote! { + impl<#type_impl_gen> #frame_support::view_functions::ViewFunctionIdPrefix for #pallet_ident<#type_use_gen> #where_clause { + fn prefix() -> [::core::primitive::u8; 16usize] { + < + ::PalletInfo + as #frame_support::traits::PalletInfo + >::name_hash::>() + .expect("No name_hash found for the pallet in the runtime! This usually means that the pallet wasn't added to `construct_runtime!`.") + } + } + } +} + +fn expand_view_function( + def: &Def, + span: Span, + where_clause: Option<&syn::WhereClause>, + view_fn: &ViewFunctionDef, +) -> TokenStream { + let frame_support = &def.frame_support; + let pallet_ident = &def.pallet_struct.pallet; + let type_impl_gen = &def.type_impl_generics(span); + let type_decl_bounded_gen = &def.type_decl_bounded_generics(span); + let type_use_gen = &def.type_use_generics(span); + let capture_docs = if cfg!(feature = "no-metadata-docs") { "never" } else { "always" }; + + let view_function_struct_ident = view_fn.view_function_struct_ident(); + let view_fn_name = &view_fn.name; + let (arg_names, arg_types) = match view_fn.args_names_types() { + Ok((arg_names, arg_types)) => (arg_names, arg_types), + Err(e) => return e.into_compile_error(), + }; + let return_type = &view_fn.return_type; + let docs = &view_fn.docs; + + let view_function_id_suffix_bytes_raw = match view_fn.view_function_id_suffix_bytes() { + Ok(view_function_id_suffix_bytes_raw) => view_function_id_suffix_bytes_raw, + Err(e) => return e.into_compile_error(), + }; + let view_function_id_suffix_bytes = view_function_id_suffix_bytes_raw + .map(|byte| syn::LitInt::new(&format!("0x{:X}_u8", byte), Span::call_site())); + + quote::quote! { + #( #[doc = #docs] )* + #[allow(missing_docs)] + #[derive( + #frame_support::RuntimeDebugNoBound, + #frame_support::CloneNoBound, + #frame_support::EqNoBound, + #frame_support::PartialEqNoBound, + #frame_support::__private::codec::Encode, + #frame_support::__private::codec::Decode, + #frame_support::__private::scale_info::TypeInfo, + )] + #[codec(encode_bound())] + #[codec(decode_bound())] + #[scale_info(skip_type_params(#type_use_gen), capture_docs = #capture_docs)] + pub struct #view_function_struct_ident<#type_decl_bounded_gen> #where_clause { + #( + pub #arg_names: #arg_types, + )* + _marker: ::core::marker::PhantomData<(#type_use_gen,)>, + } + + impl<#type_impl_gen> #view_function_struct_ident<#type_use_gen> #where_clause { + /// Create a new [`#view_function_struct_ident`] instance. + pub fn new(#( #arg_names: #arg_types, )*) -> Self { + Self { + #( #arg_names, )* + _marker: ::core::default::Default::default() + } + } + } + + impl<#type_impl_gen> #frame_support::view_functions::ViewFunctionIdSuffix for #view_function_struct_ident<#type_use_gen> #where_clause { + const SUFFIX: [::core::primitive::u8; 16usize] = [ #( #view_function_id_suffix_bytes ),* ]; + } + + impl<#type_impl_gen> #frame_support::view_functions::ViewFunction for #view_function_struct_ident<#type_use_gen> #where_clause { + fn id() -> #frame_support::view_functions::ViewFunctionId { + #frame_support::view_functions::ViewFunctionId { + prefix: <#pallet_ident<#type_use_gen> as #frame_support::view_functions::ViewFunctionIdPrefix>::prefix(), + suffix: ::SUFFIX, + } + } + + type ReturnType = #return_type; + + fn invoke(self) -> Self::ReturnType { + let Self { #( #arg_names, )* _marker } = self; + #pallet_ident::<#type_use_gen> :: #view_fn_name( #( #arg_names, )* ) + } + } + } +} + +fn impl_dispatch_view_function( + def: &Def, + span: Span, + where_clause: Option<&syn::WhereClause>, + view_fns: &[ViewFunctionDef], +) -> TokenStream { + let frame_support = &def.frame_support; + let pallet_ident = &def.pallet_struct.pallet; + let type_impl_gen = &def.type_impl_generics(span); + let type_use_gen = &def.type_use_generics(span); + + let query_match_arms = view_fns.iter().map(|view_fn| { + let view_function_struct_ident = view_fn.view_function_struct_ident(); + quote::quote! { + <#view_function_struct_ident<#type_use_gen> as #frame_support::view_functions::ViewFunctionIdSuffix>::SUFFIX => { + <#view_function_struct_ident<#type_use_gen> as #frame_support::view_functions::ViewFunction>::execute(input, output) + } + } + }); + + quote::quote! { + impl<#type_impl_gen> #frame_support::view_functions::DispatchViewFunction + for #pallet_ident<#type_use_gen> #where_clause + { + #[deny(unreachable_patterns)] + fn dispatch_view_function( + id: & #frame_support::view_functions::ViewFunctionId, + input: &mut &[u8], + output: &mut O + ) -> Result<(), #frame_support::view_functions::ViewFunctionDispatchError> + { + match id.suffix { + #( #query_match_arms )* + _ => Err(#frame_support::view_functions::ViewFunctionDispatchError::NotFound(id.clone())), + } + } + } + } +} + +fn impl_view_function_metadata( + def: &Def, + span: Span, + where_clause: Option<&syn::WhereClause>, + view_fns: &[ViewFunctionDef], + docs: &[syn::Expr], +) -> TokenStream { + let frame_support = &def.frame_support; + let pallet_ident = &def.pallet_struct.pallet; + let type_impl_gen = &def.type_impl_generics(span); + let type_use_gen = &def.type_use_generics(span); + + let view_functions = view_fns.iter().map(|view_fn| { + let view_function_struct_ident = view_fn.view_function_struct_ident(); + let name = &view_fn.name; + let args = view_fn.args.iter().filter_map(|fn_arg| { + match fn_arg { + syn::FnArg::Receiver(_) => None, + syn::FnArg::Typed(typed) => { + let pat = &typed.pat; + let ty = &typed.ty; + Some(quote::quote! { + #frame_support::__private::metadata_ir::ViewFunctionArgMetadataIR { + name: ::core::stringify!(#pat), + ty: #frame_support::__private::scale_info::meta_type::<#ty>(), + } + }) + } + } + }); + + let no_docs = vec![]; + let doc = if cfg!(feature = "no-metadata-docs") { &no_docs } else { &view_fn.docs }; + + quote::quote! { + #frame_support::__private::metadata_ir::ViewFunctionMetadataIR { + name: ::core::stringify!(#name), + id: <#view_function_struct_ident<#type_use_gen> as #frame_support::view_functions::ViewFunction>::id().into(), + args: #frame_support::__private::sp_std::vec![ #( #args ),* ], + output: #frame_support::__private::scale_info::meta_type::< + <#view_function_struct_ident<#type_use_gen> as #frame_support::view_functions::ViewFunction>::ReturnType + >(), + docs: #frame_support::__private::sp_std::vec![ #( #doc ),* ], + } + } + }); + + let no_docs = vec![]; + let doc = if cfg!(feature = "no-metadata-docs") { &no_docs } else { docs }; + + quote::quote! { + impl<#type_impl_gen> #pallet_ident<#type_use_gen> #where_clause { + #[doc(hidden)] + pub fn pallet_view_functions_metadata(name: &'static ::core::primitive::str) + -> #frame_support::__private::metadata_ir::ViewFunctionGroupIR + { + #frame_support::__private::metadata_ir::ViewFunctionGroupIR { + name, + view_functions: #frame_support::__private::sp_std::vec![ #( #view_functions ),* ], + docs: #frame_support::__private::sp_std::vec![ #( #doc ),* ], + } + } + } + } +} diff --git a/substrate/frame/support/procedural/src/pallet/parse/mod.rs b/substrate/frame/support/procedural/src/pallet/parse/mod.rs index c9a150effccbe..89875974b8b5d 100644 --- a/substrate/frame/support/procedural/src/pallet/parse/mod.rs +++ b/substrate/frame/support/procedural/src/pallet/parse/mod.rs @@ -36,6 +36,7 @@ pub mod storage; pub mod tasks; pub mod type_value; pub mod validate_unsigned; +pub mod view_functions; #[cfg(test)] pub mod tests; @@ -70,6 +71,7 @@ pub struct Def { pub frame_system: syn::Path, pub frame_support: syn::Path, pub dev_mode: bool, + pub view_functions: Option, } impl Def { @@ -103,6 +105,7 @@ impl Def { let mut storages = vec![]; let mut type_values = vec![]; let mut composites: Vec = vec![]; + let mut view_functions = None; for (index, item) in items.iter_mut().enumerate() { let pallet_attr: Option = helper::take_first_item_pallet_attr(item)?; @@ -205,6 +208,9 @@ impl Def { } composites.push(composite); }, + Some(PalletAttr::ViewFunctions(span)) => { + view_functions = Some(view_functions::ViewFunctionsImplDef::try_from(span, item)?); + } Some(attr) => { let msg = "Invalid duplicated attribute"; return Err(syn::Error::new(attr.span(), msg)) @@ -250,6 +256,7 @@ impl Def { frame_system, frame_support, dev_mode, + view_functions, }; def.check_instance_usage()?; @@ -563,6 +570,7 @@ mod keyword { syn::custom_keyword!(pallet); syn::custom_keyword!(extra_constants); syn::custom_keyword!(composite_enum); + syn::custom_keyword!(view_functions_experimental); } /// The possible values for the `#[pallet::config]` attribute. @@ -652,6 +660,7 @@ enum PalletAttr { TypeValue(proc_macro2::Span), ExtraConstants(proc_macro2::Span), Composite(proc_macro2::Span), + ViewFunctions(proc_macro2::Span), } impl PalletAttr { @@ -677,6 +686,7 @@ impl PalletAttr { Self::TypeValue(span) => *span, Self::ExtraConstants(span) => *span, Self::Composite(span) => *span, + Self::ViewFunctions(span) => *span, } } } @@ -778,6 +788,10 @@ impl syn::parse::Parse for PalletAttr { Ok(PalletAttr::ExtraConstants(content.parse::()?.span())) } else if lookahead.peek(keyword::composite_enum) { Ok(PalletAttr::Composite(content.parse::()?.span())) + } else if lookahead.peek(keyword::view_functions_experimental) { + Ok(PalletAttr::ViewFunctions( + content.parse::()?.span(), + )) } else { Err(lookahead.error()) } diff --git a/substrate/frame/support/procedural/src/pallet/parse/view_functions.rs b/substrate/frame/support/procedural/src/pallet/parse/view_functions.rs new file mode 100644 index 0000000000000..766bcb13da8b3 --- /dev/null +++ b/substrate/frame/support/procedural/src/pallet/parse/view_functions.rs @@ -0,0 +1,155 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governsing permissions and +// limitations under the License. + +use frame_support_procedural_tools::get_doc_literals; +use inflector::Inflector; +use syn::spanned::Spanned; + +/// Parsed representation of an impl block annotated with `pallet::view_functions_experimental`. +pub struct ViewFunctionsImplDef { + /// The where_clause used. + pub where_clause: Option, + /// The span of the pallet::view_functions_experimental attribute. + pub attr_span: proc_macro2::Span, + /// Docs, specified on the impl Block. + pub docs: Vec, + /// The view function definitions. + pub view_functions: Vec, +} + +impl ViewFunctionsImplDef { + pub fn try_from(attr_span: proc_macro2::Span, item: &mut syn::Item) -> syn::Result { + let syn::Item::Impl(item_impl) = item else { + return Err(syn::Error::new( + item.span(), + "Invalid pallet::view_functions_experimental, expected item impl", + )) + }; + let mut view_functions = Vec::new(); + for item in &mut item_impl.items { + if let syn::ImplItem::Fn(method) = item { + if !matches!(method.vis, syn::Visibility::Public(_)) { + let msg = "Invalid pallet::view_functions_experimental, view function must be public: \ + `pub fn`"; + + let span = match method.vis { + syn::Visibility::Inherited => method.sig.span(), + _ => method.vis.span(), + }; + + return Err(syn::Error::new(span, msg)) + } + + let view_fn_def = ViewFunctionDef::try_from(method.clone())?; + view_functions.push(view_fn_def) + } else { + return Err(syn::Error::new( + item.span(), + "Invalid pallet::view_functions_experimental, expected a function", + )) + } + } + Ok(Self { + view_functions, + attr_span, + where_clause: item_impl.generics.where_clause.clone(), + docs: get_doc_literals(&item_impl.attrs), + }) + } +} + +/// Parsed representation of a view function definition. +#[derive(Clone)] +pub struct ViewFunctionDef { + pub name: syn::Ident, + pub docs: Vec, + pub args: Vec, + pub return_type: syn::Type, +} + +impl TryFrom for ViewFunctionDef { + type Error = syn::Error; + fn try_from(method: syn::ImplItemFn) -> Result { + let syn::ReturnType::Type(_, type_) = method.sig.output else { + return Err(syn::Error::new(method.sig.span(), "view functions must return a value")) + }; + + Ok(Self { + name: method.sig.ident.clone(), + docs: get_doc_literals(&method.attrs), + args: method.sig.inputs.iter().cloned().collect::>(), + return_type: *type_.clone(), + }) + } +} + +impl ViewFunctionDef { + pub fn view_function_struct_ident(&self) -> syn::Ident { + syn::Ident::new( + &format!("{}ViewFunction", self.name.to_string().to_pascal_case()), + self.name.span(), + ) + } + + pub fn view_function_id_suffix_bytes(&self) -> Result<[u8; 16], syn::Error> { + let mut output = [0u8; 16]; + + // concatenate the signature string + let arg_types = self + .args_names_types()? + .1 + .iter() + .map(|ty| quote::quote!(#ty).to_string().replace(" ", "")) + .collect::>() + .join(","); + let return_type = &self.return_type; + let return_type = quote::quote!(#return_type).to_string().replace(" ", ""); + let view_fn_signature = format!( + "{view_function_name}({arg_types}) -> {return_type}", + view_function_name = &self.name, + ); + + // hash the signature string + let hash = sp_crypto_hashing::twox_128(view_fn_signature.as_bytes()); + output.copy_from_slice(&hash[..]); + Ok(output) + } + + pub fn args_names_types(&self) -> Result<(Vec, Vec), syn::Error> { + Ok(self + .args + .iter() + .map(|arg| { + let syn::FnArg::Typed(pat_type) = arg else { + return Err(syn::Error::new( + arg.span(), + "Unsupported argument in view function", + )); + }; + let syn::Pat::Ident(ident) = &*pat_type.pat else { + return Err(syn::Error::new( + pat_type.pat.span(), + "Unsupported pattern in view function argument", + )); + }; + Ok((ident.ident.clone(), *pat_type.ty.clone())) + }) + .collect::, syn::Error>>()? + .into_iter() + .unzip()) + } +} diff --git a/substrate/frame/support/procedural/src/runtime/expand/mod.rs b/substrate/frame/support/procedural/src/runtime/expand/mod.rs index 666bc03aa415d..005b109c0eb5f 100644 --- a/substrate/frame/support/procedural/src/runtime/expand/mod.rs +++ b/substrate/frame/support/procedural/src/runtime/expand/mod.rs @@ -182,6 +182,7 @@ fn construct_runtime_final_expansion( let mut slash_reason = None; let mut lock_id = None; let mut task = None; + let mut query = None; for runtime_type in runtime_types.iter() { match runtime_type { @@ -224,6 +225,9 @@ fn construct_runtime_final_expansion( RuntimeType::RuntimeTask(_) => { task = Some(expand::expand_outer_task(&name, &pallets, &scrate)); }, + RuntimeType::RuntimeViewFunction(_) => { + query = Some(expand::expand_outer_query(&name, &pallets, &scrate)); + }, } } @@ -301,6 +305,8 @@ fn construct_runtime_final_expansion( #task + #query + #metadata #outer_config diff --git a/substrate/frame/support/procedural/src/runtime/parse/runtime_types.rs b/substrate/frame/support/procedural/src/runtime/parse/runtime_types.rs index a4480e2a1fd32..9a385146a811e 100644 --- a/substrate/frame/support/procedural/src/runtime/parse/runtime_types.rs +++ b/substrate/frame/support/procedural/src/runtime/parse/runtime_types.rs @@ -32,6 +32,7 @@ mod keyword { custom_keyword!(RuntimeSlashReason); custom_keyword!(RuntimeLockId); custom_keyword!(RuntimeTask); + custom_keyword!(RuntimeViewFunction); } #[derive(Debug, Clone, PartialEq)] @@ -45,6 +46,7 @@ pub enum RuntimeType { RuntimeSlashReason(keyword::RuntimeSlashReason), RuntimeLockId(keyword::RuntimeLockId), RuntimeTask(keyword::RuntimeTask), + RuntimeViewFunction(keyword::RuntimeViewFunction), } impl Parse for RuntimeType { @@ -69,6 +71,8 @@ impl Parse for RuntimeType { Ok(Self::RuntimeLockId(input.parse()?)) } else if lookahead.peek(keyword::RuntimeTask) { Ok(Self::RuntimeTask(input.parse()?)) + } else if lookahead.peek(keyword::RuntimeViewFunction) { + Ok(Self::RuntimeViewFunction(input.parse()?)) } else { Err(lookahead.error()) } diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs index a6969260e6a26..97d16e2a06d23 100644 --- a/substrate/frame/support/src/lib.rs +++ b/substrate/frame/support/src/lib.rs @@ -87,6 +87,7 @@ pub mod storage; #[cfg(test)] mod tests; pub mod traits; +pub mod view_functions; pub mod weights; #[doc(hidden)] pub mod unsigned { diff --git a/substrate/frame/support/src/tests/mod.rs b/substrate/frame/support/src/tests/mod.rs index 7c90a12d4167e..b10e719b9ac36 100644 --- a/substrate/frame/support/src/tests/mod.rs +++ b/substrate/frame/support/src/tests/mod.rs @@ -237,7 +237,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; diff --git a/substrate/frame/support/src/view_functions.rs b/substrate/frame/support/src/view_functions.rs new file mode 100644 index 0000000000000..dd23fad94a4fd --- /dev/null +++ b/substrate/frame/support/src/view_functions.rs @@ -0,0 +1,128 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License fsor the specific language governing permissions and +// limitations under the License. + +//! Traits for querying pallet view functions. + +use alloc::vec::Vec; +use codec::{Decode, DecodeAll, Encode, Output}; +use scale_info::TypeInfo; +use sp_runtime::RuntimeDebug; + +/// The unique identifier for a view function. +#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ViewFunctionId { + /// The part of the id for dispatching view functions from the top level of the runtime. + /// + /// Specifies which view function grouping this view function belongs to. This could be a group + /// of view functions associated with a pallet, or a pallet agnostic group of view functions. + pub prefix: [u8; 16], + /// The part of the id for dispatching to a view function within a group. + pub suffix: [u8; 16], +} + +impl From for [u8; 32] { + fn from(value: ViewFunctionId) -> Self { + let mut output = [0u8; 32]; + output[..16].copy_from_slice(&value.prefix); + output[16..].copy_from_slice(&value.suffix); + output + } +} + +/// Error type for view function dispatching. +#[derive(Encode, Decode, RuntimeDebug, TypeInfo)] +pub enum ViewFunctionDispatchError { + /// View functions are not implemented for this runtime. + NotImplemented, + /// A view function with the given `ViewFunctionId` was not found + NotFound(ViewFunctionId), + /// Failed to decode the view function input. + Codec, +} + +impl From for ViewFunctionDispatchError { + fn from(_: codec::Error) -> Self { + ViewFunctionDispatchError::Codec + } +} + +/// Implemented by both pallets and the runtime. The runtime is dispatching by prefix using the +/// pallet implementation of `ViewFunctionIdPrefix` then the pallet is dispatching by suffix using +/// the methods implementation of `ViewFunctionIdSuffix`. +pub trait DispatchViewFunction { + fn dispatch_view_function( + id: &ViewFunctionId, + input: &mut &[u8], + output: &mut O, + ) -> Result<(), ViewFunctionDispatchError>; +} + +impl DispatchViewFunction for () { + fn dispatch_view_function( + _id: &ViewFunctionId, + _input: &mut &[u8], + _output: &mut O, + ) -> Result<(), ViewFunctionDispatchError> { + Err(ViewFunctionDispatchError::NotImplemented) + } +} + +/// Automatically implemented for each pallet by the macro [`pallet`](crate::pallet). +pub trait ViewFunctionIdPrefix { + fn prefix() -> [u8; 16]; +} + +/// Automatically implemented for each pallet view function method by the macro +/// [`pallet`](crate::pallet). +pub trait ViewFunctionIdSuffix { + const SUFFIX: [u8; 16]; +} + +/// Automatically implemented for each pallet view function method by the macro +/// [`pallet`](crate::pallet). +pub trait ViewFunction: DecodeAll { + fn id() -> ViewFunctionId; + type ReturnType: Encode; + + fn invoke(self) -> Self::ReturnType; + + fn execute( + input: &mut &[u8], + output: &mut O, + ) -> Result<(), ViewFunctionDispatchError> { + let view_function = Self::decode_all(input)?; + let result = view_function.invoke(); + Encode::encode_to(&result, output); + Ok(()) + } +} + +pub mod runtime_api { + use super::*; + + sp_api::decl_runtime_apis! { + #[api_version(1)] + /// Runtime API for executing view functions + pub trait RuntimeViewFunction { + /// Execute a view function query. + fn execute_view_function( + query_id: ViewFunctionId, + input: Vec, + ) -> Result, ViewFunctionDispatchError>; + } + } +} diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr index 726b09cf54c99..faa9cb558c262 100644 --- a/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr +++ b/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr @@ -561,6 +561,15 @@ note: the trait `Config` must be implemented | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) +error[E0277]: the trait bound `Runtime: Config` is not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:26:3 + | +26 | System: frame_system::{Pallet, Call, Storage, Config, Event}, + | ^^^^^^ the trait `Config` is not implemented for `Runtime`, which is required by `Pallet: ViewFunctionIdPrefix` + | + = help: the trait `ViewFunctionIdPrefix` is implemented for `Pallet` + = note: required for `Pallet` to implement `ViewFunctionIdPrefix` + error[E0599]: the function or associated item `storage_metadata` exists for struct `Pallet`, but its trait bounds were not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | @@ -736,6 +745,31 @@ note: the trait `Config` must be implemented | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) +error[E0599]: the function or associated item `pallet_view_functions_metadata` exists for struct `Pallet`, but its trait bounds were not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | construct_runtime! { + | __^ + | | _| + | || +21 | || pub struct Runtime where + | ||______________________- doesn't satisfy `Runtime: Config` +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |__^ function or associated item cannot be called on `Pallet` due to unsatisfied trait bounds + | + = note: the following trait bounds were not satisfied: + `Runtime: Config` +note: the trait `Config` must be implemented + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub trait Config: 'static + Eq + Clone { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | diff --git a/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr b/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr index c7159b34afb3d..aafc6b5a2c874 100644 --- a/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr +++ b/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr @@ -1,4 +1,4 @@ -error: `#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, `RuntimeTask`, `RuntimeOrigin`, `RuntimeParameters` or `PalletInfo` +error: `#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, `RuntimeTask`, `RuntimeViewFunction`, `RuntimeOrigin`, `RuntimeParameters` or `PalletInfo` --> tests/derive_impl_ui/inject_runtime_type_invalid.rs:32:5 | 32 | type RuntimeInfo = (); diff --git a/substrate/frame/support/test/tests/pallet.rs b/substrate/frame/support/test/tests/pallet.rs index 9df1f461bba25..e45ff64e4c26e 100644 --- a/substrate/frame/support/test/tests/pallet.rs +++ b/substrate/frame/support/test/tests/pallet.rs @@ -461,6 +461,22 @@ pub mod pallet { _myfield: u32, } + #[pallet::view_functions_experimental] + impl Pallet + where + T::AccountId: From + SomeAssociation1, + { + /// Query value no args. + pub fn get_value() -> Option { + Value::::get() + } + + /// Query value with args. + pub fn get_value_with_arg(key: u16) -> Option { + Map2::::get(key) + } + } + #[pallet::genesis_build] impl BuildGenesisConfig for GenesisConfig where @@ -814,7 +830,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; diff --git a/substrate/frame/support/test/tests/runtime.rs b/substrate/frame/support/test/tests/runtime.rs index 5335e08837e4a..cbcdf8d27b39a 100644 --- a/substrate/frame/support/test/tests/runtime.rs +++ b/substrate/frame/support/test/tests/runtime.rs @@ -296,7 +296,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; diff --git a/substrate/frame/support/test/tests/runtime_legacy_ordering.rs b/substrate/frame/support/test/tests/runtime_legacy_ordering.rs index 7b92073a82b1a..1594356ad8fe8 100644 --- a/substrate/frame/support/test/tests/runtime_legacy_ordering.rs +++ b/substrate/frame/support/test/tests/runtime_legacy_ordering.rs @@ -296,7 +296,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; diff --git a/substrate/frame/support/test/tests/runtime_ui/invalid_runtime_type_derive.stderr b/substrate/frame/support/test/tests/runtime_ui/invalid_runtime_type_derive.stderr index 0b128c3dd4579..daa6721ff051d 100644 --- a/substrate/frame/support/test/tests/runtime_ui/invalid_runtime_type_derive.stderr +++ b/substrate/frame/support/test/tests/runtime_ui/invalid_runtime_type_derive.stderr @@ -1,4 +1,4 @@ -error: expected one of: `RuntimeCall`, `RuntimeEvent`, `RuntimeError`, `RuntimeOrigin`, `RuntimeFreezeReason`, `RuntimeHoldReason`, `RuntimeSlashReason`, `RuntimeLockId`, `RuntimeTask` +error: expected one of: `RuntimeCall`, `RuntimeEvent`, `RuntimeError`, `RuntimeOrigin`, `RuntimeFreezeReason`, `RuntimeHoldReason`, `RuntimeSlashReason`, `RuntimeLockId`, `RuntimeTask`, `RuntimeViewFunction` --> tests/runtime_ui/invalid_runtime_type_derive.rs:21:23 | 21 | #[runtime::derive(RuntimeInfo)] diff --git a/substrate/frame/support/test/tests/runtime_ui/pass/basic.rs b/substrate/frame/support/test/tests/runtime_ui/pass/basic.rs index 514f150180153..8350211335a52 100644 --- a/substrate/frame/support/test/tests/runtime_ui/pass/basic.rs +++ b/substrate/frame/support/test/tests/runtime_ui/pass/basic.rs @@ -27,7 +27,7 @@ impl frame_system::Config for Runtime { #[frame_support::runtime] mod runtime { #[runtime::runtime] - #[runtime::derive(RuntimeCall, RuntimeEvent, RuntimeOrigin, RuntimeError, RuntimeTask)] + #[runtime::derive(RuntimeCall, RuntimeEvent, RuntimeOrigin, RuntimeError, RuntimeTask, RuntimeViewFunction)] pub struct Runtime; #[runtime::pallet_index(0)] diff --git a/substrate/primitives/metadata-ir/src/lib.rs b/substrate/primitives/metadata-ir/src/lib.rs index dc01f7eaadb33..e048010a34b75 100644 --- a/substrate/primitives/metadata-ir/src/lib.rs +++ b/substrate/primitives/metadata-ir/src/lib.rs @@ -122,6 +122,7 @@ mod test { event_enum_ty: meta_type::<()>(), error_enum_ty: meta_type::<()>(), }, + view_functions: RuntimeViewFunctionsIR { ty: meta_type::<()>(), groups: vec![] }, } } diff --git a/substrate/primitives/metadata-ir/src/types.rs b/substrate/primitives/metadata-ir/src/types.rs index af217ffe16eeb..0617fc7dfb94f 100644 --- a/substrate/primitives/metadata-ir/src/types.rs +++ b/substrate/primitives/metadata-ir/src/types.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use codec::{Compact, Encode}; +use codec::{Compact, Decode, Encode}; use scale_info::{ form::{Form, MetaForm, PortableForm}, prelude::{collections::BTreeMap, vec::Vec}, @@ -41,6 +41,8 @@ pub struct MetadataIR { pub apis: Vec>, /// The outer enums types as found in the runtime. pub outer_enums: OuterEnumsIR, + /// Metadata of view function queries + pub view_functions: RuntimeViewFunctionsIR, } /// Metadata of a runtime trait. @@ -118,6 +120,89 @@ impl IntoPortable for RuntimeApiMethodParamMetadataIR { } } +/// Metadata of the top level runtime view function dispatch. +#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug)] +pub struct RuntimeViewFunctionsIR { + /// The type implementing the runtime query dispatch. + pub ty: T::Type, + /// The view function groupings metadata. + pub groups: Vec>, +} + +/// Metadata of a runtime view function group. +/// +/// For example, view functions associated with a pallet would form a view function group. +#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug)] +pub struct ViewFunctionGroupIR { + /// Name of the view function group. + pub name: T::String, + /// View functions belonging to the group. + pub view_functions: Vec>, + /// View function group documentation. + pub docs: Vec, +} + +impl IntoPortable for ViewFunctionGroupIR { + type Output = ViewFunctionGroupIR; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + ViewFunctionGroupIR { + name: self.name.into_portable(registry), + view_functions: registry.map_into_portable(self.view_functions), + docs: registry.map_into_portable(self.docs), + } + } +} + +/// Metadata of a runtime view function. +#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug)] +pub struct ViewFunctionMetadataIR { + /// Query name. + pub name: T::String, + /// Query id. + pub id: [u8; 32], + /// Query args. + pub args: Vec>, + /// Query output. + pub output: T::Type, + /// Query documentation. + pub docs: Vec, +} + +impl IntoPortable for ViewFunctionMetadataIR { + type Output = ViewFunctionMetadataIR; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + ViewFunctionMetadataIR { + name: self.name.into_portable(registry), + id: self.id, + args: registry.map_into_portable(self.args), + output: registry.register_type(&self.output), + docs: registry.map_into_portable(self.docs), + } + } +} + +/// Metadata of a runtime method argument. +#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug)] +pub struct ViewFunctionArgMetadataIR { + /// Query argument name. + pub name: T::String, + /// Query argument type. + pub ty: T::Type, +} + +impl IntoPortable for ViewFunctionArgMetadataIR { + type Output = ViewFunctionArgMetadataIR; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + ViewFunctionArgMetadataIR { + name: self.name.into_portable(registry), + ty: registry.register_type(&self.ty), + } + } +} + /// The intermediate representation for a pallet metadata. #[derive(Clone, PartialEq, Eq, Encode, Debug)] pub struct PalletMetadataIR { diff --git a/substrate/primitives/metadata-ir/src/v15.rs b/substrate/primitives/metadata-ir/src/v15.rs index ed315a31e6dc9..7bc76f22b58d0 100644 --- a/substrate/primitives/metadata-ir/src/v15.rs +++ b/substrate/primitives/metadata-ir/src/v15.rs @@ -17,31 +17,39 @@ //! Convert the IR to V15 metadata. -use crate::OuterEnumsIR; - use super::types::{ - ExtrinsicMetadataIR, MetadataIR, PalletMetadataIR, RuntimeApiMetadataIR, + ExtrinsicMetadataIR, MetadataIR, OuterEnumsIR, PalletMetadataIR, RuntimeApiMetadataIR, RuntimeApiMethodMetadataIR, RuntimeApiMethodParamMetadataIR, TransactionExtensionMetadataIR, }; use frame_metadata::v15::{ - CustomMetadata, ExtrinsicMetadata, OuterEnums, PalletMetadata, RuntimeApiMetadata, - RuntimeApiMethodMetadata, RuntimeApiMethodParamMetadata, RuntimeMetadataV15, - SignedExtensionMetadata, + CustomMetadata, CustomValueMetadata, ExtrinsicMetadata, OuterEnums, PalletMetadata, + RuntimeApiMetadata, RuntimeApiMethodMetadata, RuntimeApiMethodParamMetadata, + RuntimeMetadataV15, SignedExtensionMetadata, }; +use scale_info::{IntoPortable, Registry}; impl From for RuntimeMetadataV15 { fn from(ir: MetadataIR) -> Self { - RuntimeMetadataV15::new( - ir.pallets.into_iter().map(Into::into).collect(), - ir.extrinsic.into(), - ir.ty, - ir.apis.into_iter().map(Into::into).collect(), - ir.outer_enums.into(), - // Substrate does not collect yet the custom metadata fields. - // This allows us to extend the V15 easily. - CustomMetadata { map: Default::default() }, - ) + let mut registry = Registry::new(); + let pallets = + registry.map_into_portable(ir.pallets.into_iter().map(Into::::into)); + let extrinsic = Into::::into(ir.extrinsic).into_portable(&mut registry); + let ty = registry.register_type(&ir.ty); + let apis = + registry.map_into_portable(ir.apis.into_iter().map(Into::::into)); + let outer_enums = Into::::into(ir.outer_enums).into_portable(&mut registry); + + let view_function_groups = registry.map_into_portable(ir.view_functions.groups.into_iter()); + let view_functions_custom_metadata = CustomValueMetadata { + ty: ir.view_functions.ty, + value: codec::Encode::encode(&view_function_groups), + }; + let mut custom_map = alloc::collections::BTreeMap::new(); + custom_map.insert("view_functions_experimental", view_functions_custom_metadata); + let custom = CustomMetadata { map: custom_map }.into_portable(&mut registry); + + Self { types: registry.into(), pallets, extrinsic, ty, apis, outer_enums, custom } } } diff --git a/templates/minimal/runtime/src/lib.rs b/templates/minimal/runtime/src/lib.rs index 972c7500f3993..5d549bf1a912d 100644 --- a/templates/minimal/runtime/src/lib.rs +++ b/templates/minimal/runtime/src/lib.rs @@ -138,7 +138,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; diff --git a/templates/parachain/pallets/template/src/mock.rs b/templates/parachain/pallets/template/src/mock.rs index b924428d4145c..3eeb9604f0153 100644 --- a/templates/parachain/pallets/template/src/mock.rs +++ b/templates/parachain/pallets/template/src/mock.rs @@ -18,7 +18,8 @@ mod test_runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Test; diff --git a/templates/parachain/runtime/src/apis.rs b/templates/parachain/runtime/src/apis.rs index 05a508ca655fb..d7da43b86af16 100644 --- a/templates/parachain/runtime/src/apis.rs +++ b/templates/parachain/runtime/src/apis.rs @@ -114,6 +114,12 @@ impl_runtime_apis! { } } + impl frame_support::view_functions::runtime_api::RuntimeViewFunction for Runtime { + fn execute_view_function(id: frame_support::view_functions::ViewFunctionId, input: Vec) -> Result, frame_support::view_functions::ViewFunctionDispatchError> { + Runtime::execute_view_function(id, input) + } + } + impl sp_block_builder::BlockBuilder for Runtime { fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { Executive::apply_extrinsic(extrinsic) diff --git a/templates/parachain/runtime/src/lib.rs b/templates/parachain/runtime/src/lib.rs index 0be27ecce7394..f312e9f80192f 100644 --- a/templates/parachain/runtime/src/lib.rs +++ b/templates/parachain/runtime/src/lib.rs @@ -262,7 +262,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; diff --git a/templates/solochain/pallets/template/src/mock.rs b/templates/solochain/pallets/template/src/mock.rs index 1b86cd9b7709a..44085bc3bff18 100644 --- a/templates/solochain/pallets/template/src/mock.rs +++ b/templates/solochain/pallets/template/src/mock.rs @@ -18,7 +18,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Test; diff --git a/templates/solochain/runtime/src/apis.rs b/templates/solochain/runtime/src/apis.rs index 06c645fa0c539..9dc588c43a2d5 100644 --- a/templates/solochain/runtime/src/apis.rs +++ b/templates/solochain/runtime/src/apis.rs @@ -75,6 +75,12 @@ impl_runtime_apis! { } } + impl frame_support::view_functions::runtime_api::RuntimeViewFunction for Runtime { + fn execute_view_function(id: frame_support::view_functions::ViewFunctionId, input: Vec) -> Result, frame_support::view_functions::ViewFunctionDispatchError> { + Runtime::execute_view_function(id, input) + } + } + impl sp_block_builder::BlockBuilder for Runtime { fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { Executive::apply_extrinsic(extrinsic) diff --git a/templates/solochain/runtime/src/lib.rs b/templates/solochain/runtime/src/lib.rs index 6a2149ec8b637..f25b8413721ea 100644 --- a/templates/solochain/runtime/src/lib.rs +++ b/templates/solochain/runtime/src/lib.rs @@ -196,7 +196,8 @@ mod runtime { RuntimeHoldReason, RuntimeSlashReason, RuntimeLockId, - RuntimeTask + RuntimeTask, + RuntimeViewFunction )] pub struct Runtime; From deb7daa4ecac09c1ec12d9120af3bf279d8c3871 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Tue, 28 Jan 2025 14:21:57 +0000 Subject: [PATCH 163/169] tests pass again --- substrate/frame/election-provider-multi-block/src/mock/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/substrate/frame/election-provider-multi-block/src/mock/mod.rs b/substrate/frame/election-provider-multi-block/src/mock/mod.rs index 9111d4babb1c8..f7bfa98814f49 100644 --- a/substrate/frame/election-provider-multi-block/src/mock/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/mock/mod.rs @@ -390,6 +390,7 @@ impl ExtBuilder { (999, 100), (9999, 100), ], + ..Default::default() } .assimilate_storage(&mut storage); From 72841b731727e69db38f9bd616190aa8d50a56ba Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 29 Jan 2025 15:53:14 +0000 Subject: [PATCH 164/169] refactor into MinerConfig --- substrate/bin/node/runtime/src/lib.rs | 27 +- .../src/helpers.rs | 38 +- .../election-provider-multi-block/src/lib.rs | 121 ++- .../src/mock/mod.rs | 44 +- .../src/mock/signed.rs | 7 +- .../src/signed/mod.rs | 24 +- .../src/types.rs | 93 +- .../src/unsigned/miner.rs | 850 +++++++++--------- .../src/unsigned/mod.rs | 51 +- .../src/verifier/impls.rs | 207 +++-- .../src/verifier/mod.rs | 24 +- .../election-provider-support/src/lib.rs | 83 +- .../election-provider-support/src/onchain.rs | 9 +- .../election-provider-support/src/tests.rs | 5 +- 14 files changed, 834 insertions(+), 749 deletions(-) diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index d844cffd04571..e00680d19545d 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -914,14 +914,31 @@ pub(crate) mod multi_block_impls { parameter_types! { pub Pages: u32 = 4; // nominators snapshot size - pub VoterSnapshotPerBlock: u32 = 22500 / 4; + pub VoterSnapshotPerBlock: u32 = 22500 / Pages::get(); // validator snapshot size pub TargetSnapshotPerBlock: u32 = 1000; pub SignedPhase: u32 = EPOCH_DURATION_IN_BLOCKS / 4; - pub SignedValidation: u32 = 8; + pub SignedValidation: u32 = Pages::get() * 2; pub UnsignedPhase: u32 = EPOCH_DURATION_IN_BLOCKS / 4; } + impl multi_block::unsigned::miner::MinerConfig for Runtime { + type AccountId = AccountId; + type Hash = Hash; + type MaxBackersPerWinner = ::MaxBackersPerWinner; + type MaxBackersPerWinnerFinal = + ::MaxBackersPerWinnerFinal; + type MaxWinnersPerPage = ::MaxWinnersPerPage; + type MaxVotesPerVoter = + <::DataProvider as ElectionDataProvider>::MaxVotesPerVoter; + type MaxLength = MinerMaxLength; + type Solver = ::OffchainSolver; + type Pages = Pages; + type Solution = NposSolution16; + type VoterSnapshotPerBlock = ::VoterSnapshotPerBlock; + type TargetSnapshotPerBlock = ::TargetSnapshotPerBlock; + } + impl multi_block::Config for Runtime { type AdminOrigin = EnsureRoot; type RuntimeEvent = RuntimeEvent; @@ -939,10 +956,10 @@ pub(crate) mod multi_block_impls { // TODO: sanity check that the length of all phases is within reason. type SignedPhase = SignedPhase; type UnsignedPhase = UnsignedPhase; - type Solution = NposSolution16; type TargetSnapshotPerBlock = TargetSnapshotPerBlock; type VoterSnapshotPerBlock = VoterSnapshotPerBlock; type Verifier = MultiBlockVerifier; + type MinerConfig = Self; type WeightInfo = (); } @@ -979,8 +996,6 @@ pub(crate) mod multi_block_impls { impl multi_block::unsigned::Config for Runtime { // TODO: split into MinerConfig so the staker miner can easily configure these. // miner configs. - type MinerMaxLength = MinerMaxLength; - type MinerMaxWeight = MinerMaxWeight; type OffchainSolver = ::Solver; // offchain usage of miner configs @@ -1032,7 +1047,7 @@ parameter_types! { pub ElectionBoundsMultiPhase: ElectionBounds = ElectionBoundsBuilder::default() .voters_count(5000.into()).targets_count(10.into()).build(); pub ElectionBoundsOnChain: ElectionBounds = ElectionBoundsBuilder::default() - .voters_count(50_000.into()).targets_count(1000.into()).build(); + .voters_count(1000.into()).targets_count(1000.into()).build(); pub MaxNominations: u32 = ::LIMIT as u32; /// The maximum winners that can be elected by the Election pallet which is equivalent to the diff --git a/substrate/frame/election-provider-multi-block/src/helpers.rs b/substrate/frame/election-provider-multi-block/src/helpers.rs index 68b514145ab39..9ff694c97c75e 100644 --- a/substrate/frame/election-provider-multi-block/src/helpers.rs +++ b/substrate/frame/election-provider-multi-block/src/helpers.rs @@ -19,7 +19,8 @@ use crate::{ types::{PageIndex, VoterOf}, - AllVoterPagesOf, Config, SolutionTargetIndexOf, SolutionVoterIndexOf, VoteWeight, + unsigned::miner::MinerConfig, + AllVoterPagesOf, SolutionTargetIndexOf, SolutionVoterIndexOf, VoteWeight, }; use frame_support::{traits::Get, BoundedVec}; use sp_runtime::SaturatedConversion; @@ -35,6 +36,7 @@ macro_rules! log { }; } +#[macro_export] macro_rules! sublog { ($level:tt, $sub_pallet:tt, $pattern:expr $(, $values:expr)* $(,)?) => { #[cfg(not(feature = "std"))] @@ -47,8 +49,18 @@ macro_rules! sublog { }; } +#[macro_export] +macro_rules! miner_log { + ($level:tt, $pattern:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: $crate::LOG_PREFIX, + concat!("[⛏️miner] 🗳🗳🗳 ", $pattern) $(, $values)* + ) + }; +} + /// Generate an `efficient closure of voters and the page in which they live in. -pub fn generate_voter_page_fn( +pub fn generate_voter_page_fn( paged_snapshot: &AllVoterPagesOf, ) -> impl Fn(&T::AccountId) -> Option { let mut cache: BTreeMap = BTreeMap::new(); @@ -73,7 +85,7 @@ pub fn generate_voter_page_fn( /// voters. /// /// This can be used to efficiently build index getter closures. -pub fn generate_voter_cache>( +pub fn generate_voter_cache>( snapshot: &BoundedVec, AnyBound>, ) -> BTreeMap { let mut cache: BTreeMap = BTreeMap::new(); @@ -94,7 +106,7 @@ pub fn generate_voter_cache>( /// ## Warning /// /// Note that this will represent the snapshot data from which the `cache` is generated. -pub fn voter_index_fn( +pub fn voter_index_fn( cache: &BTreeMap, ) -> impl Fn(&T::AccountId) -> Option> + '_ { move |who| { @@ -108,7 +120,7 @@ pub fn voter_index_fn( /// /// Same as [`voter_index_fn`] but the returned function owns all its necessary data; nothing is /// borrowed. -pub fn voter_index_fn_owned( +pub fn voter_index_fn_owned( cache: BTreeMap, ) -> impl Fn(&T::AccountId) -> Option> { move |who| { @@ -123,7 +135,7 @@ pub fn voter_index_fn_owned( /// ## Warning /// /// Note that this will represent the snapshot data from which the `cache` is generated. -pub fn voter_index_fn_usize( +pub fn voter_index_fn_usize( cache: &BTreeMap, ) -> impl Fn(&T::AccountId) -> Option + '_ { move |who| cache.get(who).cloned() @@ -136,7 +148,7 @@ pub fn voter_index_fn_usize( /// /// Not meant to be used in production. #[cfg(test)] -pub fn voter_index_fn_linear( +pub fn voter_index_fn_linear( snapshot: &Vec>, ) -> impl Fn(&T::AccountId) -> Option> + '_ { move |who| { @@ -154,7 +166,7 @@ pub fn voter_index_fn_linear( /// Note: to the extent possible, the returned function should be cached and reused. Producing that /// function requires a `O(n log n)` data transform. Each invocation of that function completes /// in `O(log n)`. -pub fn target_index_fn( +pub fn target_index_fn( snapshot: &Vec, ) -> impl Fn(&T::AccountId) -> Option> + '_ { let cache: BTreeMap<_, _> = @@ -174,7 +186,7 @@ pub fn target_index_fn( /// /// Not meant to be used in production. #[cfg(test)] -pub fn target_index_fn_linear( +pub fn target_index_fn_linear( snapshot: &Vec, ) -> impl Fn(&T::AccountId) -> Option> + '_ { move |who| { @@ -187,7 +199,7 @@ pub fn target_index_fn_linear( /// Create a function that can map a voter index ([`SolutionVoterIndexOf`]) to the actual voter /// account using a linearly indexible snapshot. -pub fn voter_at_fn( +pub fn voter_at_fn( snapshot: &Vec>, ) -> impl Fn(SolutionVoterIndexOf) -> Option + '_ { move |i| { @@ -199,7 +211,7 @@ pub fn voter_at_fn( /// Create a function that can map a target index ([`SolutionTargetIndexOf`]) to the actual target /// account using a linearly indexible snapshot. -pub fn target_at_fn( +pub fn target_at_fn( snapshot: &Vec, ) -> impl Fn(SolutionTargetIndexOf) -> Option + '_ { move |i| { @@ -213,7 +225,7 @@ pub fn target_at_fn( /// /// This is not optimized and uses a linear search. #[cfg(test)] -pub fn stake_of_fn_linear( +pub fn stake_of_fn_linear( snapshot: &Vec>, ) -> impl Fn(&T::AccountId) -> VoteWeight + '_ { move |who| { @@ -231,7 +243,7 @@ pub fn stake_of_fn_linear( /// /// The cache need must be derived from the same snapshot. Zero is returned if a voter is /// non-existent. -pub fn stake_of_fn<'a, T: Config, AnyBound: Get>( +pub fn stake_of_fn<'a, T: MinerConfig, AnyBound: Get>( snapshot: &'a BoundedVec, AnyBound>, cache: &'a BTreeMap, ) -> impl Fn(&T::AccountId) -> VoteWeight + 'a { diff --git a/substrate/frame/election-provider-multi-block/src/lib.rs b/substrate/frame/election-provider-multi-block/src/lib.rs index e736d17aaec4d..e86cf76089efb 100644 --- a/substrate/frame/election-provider-multi-block/src/lib.rs +++ b/substrate/frame/election-provider-multi-block/src/lib.rs @@ -230,7 +230,7 @@ impl ElectionProvider for InitiateEmergencyPhase { impl InstantElectionProvider for InitiateEmergencyPhase { fn instant_elect( - _voters: Vec>, + _voters: Vec>, _targets: Vec, _desired_targets: u32, ) -> Result, Self::Error> { @@ -264,7 +264,7 @@ impl ElectionProvider for Continue { impl InstantElectionProvider for Continue { fn instant_elect( - _voters: Vec>, + _voters: Vec>, _targets: Vec, _desired_targets: u32, ) -> Result, Self::Error> { @@ -368,9 +368,11 @@ pub mod pallet { /// The duration of this should not be less than `T::Pages`, and there is no point in it /// being more than `SignedPhase::MaxSubmission::get() * T::Pages`. TODO: integrity test for /// it. + #[pallet::constant] type SignedValidationPhase: Get>; /// The number of snapshot voters to fetch per block. + #[pallet::constant] type VoterSnapshotPerBlock: Get; /// The number of snapshot targets to fetch per block. @@ -392,17 +394,16 @@ pub mod pallet { BlockNumber = BlockNumberFor, >; - /// The solution type. - type Solution: codec::FullCodec - + Default - + PartialEq - + Eq - + Clone - + sp_std::fmt::Debug - + Ord - + NposSolution - + TypeInfo - + MaxEncodedLen; + /// The miner configuration. + type MinerConfig: crate::unsigned::miner::MinerConfig< + Pages = Self::Pages, + AccountId = ::AccountId, + MaxVotesPerVoter = ::MaxVotesPerVoter, + VoterSnapshotPerBlock = Self::VoterSnapshotPerBlock, + TargetSnapshotPerBlock = Self::TargetSnapshotPerBlock, + MaxBackersPerWinner = ::MaxBackersPerWinner, + MaxWinnersPerPage = ::MaxWinnersPerPage, + >; /// The fallback type used for the election. /// @@ -417,8 +418,10 @@ pub mod pallet { >; /// The verifier pallet's interface. - type Verifier: verifier::Verifier, AccountId = Self::AccountId> - + verifier::AsynchronousVerifier; + type Verifier: verifier::Verifier< + Solution = SolutionOf, + AccountId = Self::AccountId, + > + verifier::AsynchronousVerifier; /// The number of blocks ahead of time to try and have the election results ready by. type Lookahead: Get>; @@ -574,13 +577,13 @@ pub mod pallet { use sp_std::mem::size_of; // The index type of both voters and targets need to be smaller than that of usize (very // unlikely to be the case, but anyhow). - assert!(size_of::>() <= size_of::()); - assert!(size_of::>() <= size_of::()); + assert!(size_of::>() <= size_of::()); + assert!(size_of::>() <= size_of::()); // also, because `VoterSnapshotPerBlock` and `TargetSnapshotPerBlock` are in u32, we // assert that both of these types are smaller than u32 as well. - assert!(size_of::>() <= size_of::()); - assert!(size_of::>() <= size_of::()); + assert!(size_of::>() <= size_of::()); + assert!(size_of::>() <= size_of::()); let pages_bn: BlockNumberFor = T::Pages::get().into(); // pages must be at least 1. @@ -593,17 +596,18 @@ pub mod pallet { assert!(pages_bn + lookahead < T::UnsignedPhase::get()); // Based on the requirements of [`sp_npos_elections::Assignment::try_normalize`]. - let max_vote: usize = as NposSolution>::LIMIT; + let max_vote: usize = as NposSolution>::LIMIT; // 2. Maximum sum of [SolutionAccuracy; 16] must fit into `UpperOf`. - let maximum_chain_accuracy: Vec>> = (0..max_vote) + let maximum_chain_accuracy: Vec>> = (0.. + max_vote) .map(|_| { - >>::from( - >::one().deconstruct(), + >>::from( + >::one().deconstruct(), ) }) .collect(); - let _: UpperOf> = maximum_chain_accuracy + let _: UpperOf> = maximum_chain_accuracy .iter() .fold(Zero::zero(), |acc, x| acc.checked_add(x).unwrap()); @@ -614,7 +618,7 @@ pub mod pallet { // solution cannot represent any voters more than `LIMIT` anyhow. assert_eq!( ::MaxVotesPerVoter::get(), - as NposSolution>::LIMIT as u32, + as NposSolution>::LIMIT as u32, ); // The duration of the signed validation phase should be such that at least one solution @@ -637,6 +641,17 @@ pub mod pallet { /// Error of the pallet that can be returned in response to dispatches. #[pallet::error] pub enum Error { + /// Triggering the `Fallback` failed. + Fallback, + /// Unexpected phase + UnexpectedPhase, + /// Snapshot was unavailable. + Snapshot, + } + + /// Common errors in all sub-pallets and miner. + #[derive(PartialEq, Eq, Clone, Encode, Decode, Debug)] + pub enum CommonError { /// Submission is too early (or too late, depending on your point of reference). EarlySubmission, /// The round counter is wrong. @@ -649,28 +664,10 @@ pub mod pallet { WrongWinnerCount, /// The snapshot fingerprint is not a match. The solution is likely outdated. WrongFingerprint, - /// Triggering the `Fallback` failed. - Fallback, - /// Unexpected phase - UnexpectedPhase, - /// Snapshot was unavailable. + /// Snapshot was not available. Snapshot, } - impl PartialEq for Error { - fn eq(&self, other: &Self) -> bool { - use Error::*; - match (self, other) { - (EarlySubmission, EarlySubmission) | - (WrongRound, WrongRound) | - (WeakSubmission, WeakSubmission) | - (WrongWinnerCount, WrongWinnerCount) | - (WrongPageCount, WrongPageCount) => true, - _ => false, - } - } - } - /// Internal counter for the number of rounds. /// /// This is useful for de-duplication of transactions submitted to the pool, and general @@ -729,7 +726,7 @@ pub mod pallet { PagedTargetSnapshotHash::::insert(Pallet::::msp(), hash); } - pub(crate) fn set_voters(page: PageIndex, voters: VoterPageOf) { + pub(crate) fn set_voters(page: PageIndex, voters: VoterPageOf) { let hash = Self::write_storage_with_pre_allocate( &PagedVoterSnapshot::::hashed_key_for(page), voters, @@ -753,7 +750,7 @@ pub mod pallet { DesiredTargets::::get() } - pub(crate) fn voters(page: PageIndex) -> Option> { + pub(crate) fn voters(page: PageIndex) -> Option> { PagedVoterSnapshot::::get(page) } @@ -901,7 +898,7 @@ pub mod pallet { PagedTargetSnapshot::::iter().count().saturated_into::() } - pub(crate) fn voters_iter_flattened() -> impl Iterator> { + pub(crate) fn voters_iter_flattened() -> impl Iterator> { let key_range = (crate::Pallet::::lsp()..=crate::Pallet::::msp()).collect::>(); key_range @@ -943,7 +940,8 @@ pub mod pallet { type DesiredTargets = StorageValue<_, u32>; /// Paginated voter snapshot. At most [`T::Pages`] keys will exist. #[pallet::storage] - type PagedVoterSnapshot = StorageMap<_, Twox64Concat, PageIndex, VoterPageOf>; + type PagedVoterSnapshot = + StorageMap<_, Twox64Concat, PageIndex, VoterPageOf>; /// Same as [`PagedVoterSnapshot`], but it will store the hash of the snapshot. /// /// The hash is generated using [`frame_system::Config::Hashing`]. @@ -1002,34 +1000,34 @@ impl Pallet { /// These compliment a feasibility-check, which is exactly the opposite: snapshot-dependent /// checks. pub(crate) fn snapshot_independent_checks( - paged_solution: &PagedRawSolution, + paged_solution: &PagedRawSolution, maybe_snapshot_fingerprint: Option, - ) -> Result<(), Error> { + ) -> Result<(), CommonError> { // Note that the order of these checks are critical for the correctness and performance of // `restore_or_compute_then_maybe_submit`. We want to make sure that we always check round // first, so that if it has a wrong round, we can detect and delete it from the cache right // from the get go. // ensure round is current - ensure!(Self::round() == paged_solution.round, Error::::WrongRound); + ensure!(Self::round() == paged_solution.round, CommonError::WrongRound); // ensure score is being improved, if the claim is even correct. ensure!( ::ensure_claimed_score_improves(paged_solution.score), - Error::::WeakSubmission, + CommonError::WeakSubmission, ); // ensure solution pages are no more than the snapshot ensure!( paged_solution.solution_pages.len().saturated_into::() <= T::Pages::get(), - Error::::WrongPageCount + CommonError::WrongPageCount ); // finally, check the winner count being correct. if let Some(desired_targets) = Snapshot::::desired_targets() { ensure!( desired_targets == paged_solution.winner_count_single_page_target_snapshot() as u32, - Error::::WrongWinnerCount + CommonError::WrongWinnerCount ) } @@ -1038,7 +1036,7 @@ impl Pallet { maybe_snapshot_fingerprint .map_or(true, |snapshot_fingerprint| Snapshot::::fingerprint() == snapshot_fingerprint), - Error::::WrongFingerprint + CommonError::WrongFingerprint ); Ok(()) @@ -1833,7 +1831,7 @@ mod phase_rotation { #[cfg(test)] mod election_provider { use super::*; - use crate::{mock::*, unsigned::miner::BaseMiner, verifier::Verifier, Phase}; + use crate::{mock::*, unsigned::miner::OffchainWorkerMiner, verifier::Verifier, Phase}; use frame_election_provider_support::{BoundedSupport, BoundedSupports, ElectionProvider}; use frame_support::{ assert_storage_noop, testing_prelude::bounded_vec, unsigned::ValidateUnsigned, @@ -1849,7 +1847,7 @@ mod election_provider { assert_eq!(MultiBlock::current_phase(), Phase::Signed); // load a solution into the verifier - let paged = BaseMiner::::mine_solution(Pages::get(), false).unwrap(); + let paged = OffchainWorkerMiner::::mine_solution(Pages::get(), false).unwrap(); let score = paged.score.clone(); // now let's submit this one by one, into the signed phase. @@ -1943,7 +1941,7 @@ mod election_provider { assert_eq!(MultiBlock::current_phase(), Phase::Signed); // load a solution into the verifier - let paged = BaseMiner::::mine_solution(Pages::get(), false).unwrap(); + let paged = OffchainWorkerMiner::::mine_solution(Pages::get(), false).unwrap(); let score = paged.score.clone(); load_signed_for_verification_and_start(99, paged, 0); @@ -2000,7 +1998,7 @@ mod election_provider { assert_eq!(MultiBlock::current_phase(), Phase::Signed); // load a solution into the verifier - let paged = BaseMiner::::mine_solution(Pages::get(), false).unwrap(); + let paged = OffchainWorkerMiner::::mine_solution(Pages::get(), false).unwrap(); let score = paged.score.clone(); load_signed_for_verification_and_start(99, paged, 0); @@ -2070,7 +2068,7 @@ mod election_provider { let round = MultiBlock::round(); // load a solution into the verifier - let paged = BaseMiner::::mine_solution(Pages::get(), false).unwrap(); + let paged = OffchainWorkerMiner::::mine_solution(Pages::get(), false).unwrap(); load_signed_for_verification_and_start_and_roll_to_verified(99, paged, 0); @@ -2113,7 +2111,7 @@ mod election_provider { assert_eq!(MultiBlock::current_phase(), Phase::Signed); // load a solution into the verifier - let paged = BaseMiner::::mine_solution(Pages::get(), false).unwrap(); + let paged = OffchainWorkerMiner::::mine_solution(Pages::get(), false).unwrap(); load_signed_for_verification_and_start_and_roll_to_verified(99, paged, 0); assert_eq!(MultiBlock::current_phase(), Phase::SignedValidation(20)); @@ -2353,7 +2351,6 @@ mod admin_ops { #[cfg(test)] mod snapshot { - use super::*; #[test] fn fetches_exact_voters() { diff --git a/substrate/frame/election-provider-multi-block/src/mock/mod.rs b/substrate/frame/election-provider-multi-block/src/mock/mod.rs index f7bfa98814f49..c5552b5439af3 100644 --- a/substrate/frame/election-provider-multi-block/src/mock/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/mock/mod.rs @@ -18,13 +18,14 @@ mod signed; mod staking; mod weight_info; + use super::*; use crate::{ self as multi_block, signed::{self as signed_pallet, HoldReason}, unsigned::{ self as unsigned_pallet, - miner::{BaseMiner, MinerError}, + miner::{MinerConfig, OffchainMinerError, OffchainWorkerMiner}, }, verifier::{self as verifier_pallet, AsynchronousVerifier, Status}, }; @@ -35,9 +36,7 @@ use frame_election_provider_support::{ }; pub use frame_support::{assert_noop, assert_ok}; use frame_support::{ - derive_impl, - pallet_prelude::*, - parameter_types, + derive_impl, parameter_types, traits::{fungible::InspectHold, Hooks}, weights::{constants, Weight}, }; @@ -138,7 +137,6 @@ parameter_types! { pub static MinerTxPriority: u64 = 100; pub static SolutionImprovementThreshold: Perbill = Perbill::zero(); pub static OffchainRepeat: BlockNumber = 5; - pub static MinerMaxWeight: Weight = BlockWeights::get().max_block; pub static MinerMaxLength: u32 = 256; pub static MaxVotesPerVoter: u32 = ::LIMIT as u32; @@ -177,14 +175,26 @@ impl crate::unsigned::WeightInfo for MockUnsignedWeightInfo { impl crate::unsigned::Config for Runtime { type OffchainRepeat = OffchainRepeat; - type MinerMaxWeight = MinerMaxWeight; - type MinerMaxLength = MinerMaxLength; type MinerTxPriority = MinerTxPriority; - type OffchainSolver = - frame_election_provider_support::SequentialPhragmen; + type OffchainSolver = SequentialPhragmen; type WeightInfo = MockUnsignedWeightInfo; } +impl MinerConfig for Runtime { + type AccountId = AccountId; + type Hash = ::Hash; + type MaxLength = MinerMaxLength; + type Pages = Pages; + type MaxVotesPerVoter = MaxVotesPerVoter; + type Solution = TestNposSolution; + type Solver = SequentialPhragmen; + type TargetSnapshotPerBlock = TargetSnapshotPerBlock; + type VoterSnapshotPerBlock = VoterSnapshotPerBlock; + type MaxBackersPerWinner = MaxBackersPerWinner; + type MaxBackersPerWinnerFinal = MaxBackersPerWinnerFinal; + type MaxWinnersPerPage = MaxWinnersPerPage; +} + impl crate::Config for Runtime { type RuntimeEvent = RuntimeEvent; type SignedPhase = SignedPhase; @@ -195,7 +205,7 @@ impl crate::Config for Runtime { type TargetSnapshotPerBlock = TargetSnapshotPerBlock; type VoterSnapshotPerBlock = VoterSnapshotPerBlock; type Lookahead = Lookahead; - type Solution = TestNposSolution; + type MinerConfig = Self; type WeightInfo = weight_info::DualMockWeightInfo; type Verifier = VerifierPallet; type AdminOrigin = EnsureRoot; @@ -338,10 +348,6 @@ impl ExtBuilder { VoterSnapshotPerBlock::set(count); self } - pub(crate) fn miner_weight(self, weight: Weight) -> Self { - MinerMaxWeight::set(weight); - self - } pub(crate) fn miner_max_length(self, len: u32) -> Self { MinerMaxLength::set(len); self @@ -519,13 +525,15 @@ pub fn assert_none_snapshot() { /// changes. /// /// For testing, we never want to do reduce. -pub fn mine_full_solution() -> Result, MinerError> { - BaseMiner::::mine_solution(Pages::get(), false) +pub fn mine_full_solution() -> Result, OffchainMinerError> { + OffchainWorkerMiner::::mine_solution(Pages::get(), false) } /// Same as [`mine_full_solution`] but with custom pages. -pub fn mine_solution(pages: PageIndex) -> Result, MinerError> { - BaseMiner::::mine_solution(pages, false) +pub fn mine_solution( + pages: PageIndex, +) -> Result, OffchainMinerError> { + OffchainWorkerMiner::::mine_solution(pages, false) } /// Assert that `count` voters exist across `pages` number of pages. diff --git a/substrate/frame/election-provider-multi-block/src/mock/signed.rs b/substrate/frame/election-provider-multi-block/src/mock/signed.rs index c59648d722b5b..e6e1b5cf3aff2 100644 --- a/substrate/frame/election-provider-multi-block/src/mock/signed.rs +++ b/substrate/frame/election-provider-multi-block/src/mock/signed.rs @@ -22,6 +22,7 @@ use crate::{ AccountId, RuntimeHoldReason, RuntimeOrigin, VerifierPallet, }, signed::{self as signed_pallet, Event as SignedEvent, Submissions}, + unsigned::miner::MinerConfig, verifier::{self, AsynchronousVerifier, SolutionDataProvider, VerificationResult, Verifier}, Event, PadSolutionPages, PagedRawSolution, Pagify, Phase, SolutionOf, }; @@ -44,7 +45,7 @@ parameter_types! { /// Useful for when you don't care too much about the signed phase. pub struct MockSignedPhase; impl SolutionDataProvider for MockSignedPhase { - type Solution = ::Solution; + type Solution = ::Solution; fn get_page(page: PageIndex) -> Option { MockSignedNextSolution::get().map(|i| i.get(page as usize).cloned().unwrap_or_default()) } @@ -96,7 +97,7 @@ pub enum SignedSwitch { pub struct DualSignedPhase; impl SolutionDataProvider for DualSignedPhase { - type Solution = ::Solution; + type Solution = ::Solution; fn get_page(page: PageIndex) -> Option { match SignedPhaseSwitch::get() { SignedSwitch::Mock => MockSignedNextSolution::get() @@ -166,7 +167,7 @@ pub fn load_signed_for_verification(who: AccountId, paged: PagedRawSolution, - round: u32, + _round: u32, ) { load_signed_for_verification(who, paged); diff --git a/substrate/frame/election-provider-multi-block/src/signed/mod.rs b/substrate/frame/election-provider-multi-block/src/signed/mod.rs index 0953957087fab..bd762b3f6c534 100644 --- a/substrate/frame/election-provider-multi-block/src/signed/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/signed/mod.rs @@ -40,7 +40,10 @@ //! //! **Metadata update**: imagine you mis-computed your score. -use crate::verifier::{AsynchronousVerifier, SolutionDataProvider, Status, VerificationResult}; +use crate::{ + types::SolutionOf, + verifier::{AsynchronousVerifier, SolutionDataProvider, Status, VerificationResult}, +}; use codec::{Decode, Encode, MaxEncodedLen}; use frame_election_provider_support::PageIndex; use frame_support::{ @@ -93,7 +96,8 @@ pub struct SubmissionMetadata { } impl SolutionDataProvider for Pallet { - type Solution = T::Solution; + type Solution = SolutionOf; + fn get_page(page: PageIndex) -> Option { // note: a non-existing page will still be treated as merely an empty page. This could be // re-considered. @@ -268,7 +272,7 @@ pub mod pallet { /// - And the value of `metadata.score` must be equal to the score stored in /// `SortedScores`. /// - And visa versa: for any key existing in `SubmissionMetadataStorage`, an item must exist in - /// `SortedScores`. TODO: + /// `SortedScores`. /// - For any first key existing in `SubmissionStorage`, a key must exist in /// `SubmissionMetadataStorage`. /// - For any first key in `SubmissionStorage`, the number of second keys existing should be the @@ -300,7 +304,7 @@ pub mod pallet { NMapKey, NMapKey, ), - T::Solution, + SolutionOf, OptionQuery, >; @@ -460,7 +464,7 @@ pub mod pallet { round: u32, who: &T::AccountId, page: PageIndex, - maybe_solution: Option, + maybe_solution: Option>, ) -> DispatchResultWithPostInfo { Self::mutate_checked(round, || { Self::try_mutate_page_inner(round, who, page, maybe_solution) @@ -471,7 +475,7 @@ pub mod pallet { round: u32, who: &T::AccountId, page: PageIndex, - maybe_solution: Option, + maybe_solution: Option>, ) -> DispatchResultWithPostInfo { let mut metadata = SubmissionMetadataStorage::::get(round, who).ok_or(Error::::NotRegistered)?; @@ -527,7 +531,7 @@ pub mod pallet { round: u32, who: &T::AccountId, page: PageIndex, - ) -> Option { + ) -> Option> { SubmissionStorage::::get((round, who, &page)) } } @@ -536,7 +540,7 @@ pub mod pallet { impl Submissions { pub fn submissions_iter( round: u32, - ) -> impl Iterator { + ) -> impl Iterator)> { SubmissionStorage::::iter_prefix((round,)).map(|((x, y), z)| (x, y, z)) } @@ -553,7 +557,7 @@ pub mod pallet { pub fn pages_of( round: u32, who: T::AccountId, - ) -> impl Iterator { + ) -> impl Iterator)> { SubmissionStorage::::iter_prefix((round, who)) } @@ -700,7 +704,7 @@ pub mod pallet { pub fn submit_page( origin: OriginFor, page: PageIndex, - maybe_solution: Option, + maybe_solution: Option>, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; ensure!(crate::Pallet::::current_phase().is_signed(), Error::::PhaseNotSigned); diff --git a/substrate/frame/election-provider-multi-block/src/types.rs b/substrate/frame/election-provider-multi-block/src/types.rs index 6a4e77843dd6b..2be6705c344d6 100644 --- a/substrate/frame/election-provider-multi-block/src/types.rs +++ b/substrate/frame/election-provider-multi-block/src/types.rs @@ -18,30 +18,19 @@ use frame_support::{ BoundedVec, CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, }; +use sp_core::Get; use sp_std::{collections::btree_set::BTreeSet, fmt::Debug, prelude::*}; -use crate::Verifier; +use crate::unsigned::miner::MinerConfig; use codec::{Decode, Encode, MaxEncodedLen}; -use frame_election_provider_support::{BoundedSupports, ElectionProvider}; +use frame_election_provider_support::ElectionProvider; pub use frame_election_provider_support::{NposSolution, PageIndex}; use scale_info::TypeInfo; pub use sp_npos_elections::{ElectionResult, ElectionScore}; -use sp_runtime::{ - traits::{One, Saturating, Zero}, - SaturatedConversion, -}; - -/// The supports that's returned from a given [`Verifier`]. -/// TODO: rename this -pub type SupportsOf = BoundedSupports< - ::AccountId, - ::MaxWinnersPerPage, - ::MaxBackersPerWinner, ->; +use sp_runtime::SaturatedConversion; /// The solution type used by this crate. -pub type SolutionOf = ::Solution; - +pub type SolutionOf = ::Solution; /// The voter index. Derived from [`SolutionOf`]. pub type SolutionVoterIndexOf = as NposSolution>::VoterIndex; /// The target index. Derived from [`SolutionOf`]. @@ -53,7 +42,7 @@ pub type FallbackErrorOf = <::Fallback as ElectionProvide /// The relative distribution of a voter's stake among the winning targets. pub type AssignmentOf = - sp_npos_elections::Assignment<::AccountId, SolutionAccuracyOf>; + sp_npos_elections::Assignment<::AccountId, SolutionAccuracyOf>; #[derive( TypeInfo, @@ -68,8 +57,8 @@ pub type AssignmentOf = )] #[codec(mel_bound(T: crate::Config))] #[scale_info(skip_type_params(T))] -pub struct PagedRawSolution { - pub solution_pages: BoundedVec, T::Pages>, +pub struct PagedRawSolution { + pub solution_pages: BoundedVec, ::Pages>, pub score: ElectionScore, pub round: u32, } @@ -132,7 +121,7 @@ impl> PadSolu } } -impl PagedRawSolution { +impl PagedRawSolution { /// Get the total number of voters, assuming that voters in each page are unique. pub fn voter_count(&self) -> usize { self.solution_pages @@ -163,20 +152,21 @@ impl PagedRawSolution { // NOTE on naming conventions: type aliases that end with `Of` should always be `Of`. -/// Alias for a voter, parameterized by this crate's config. -pub(crate) type VoterOf = - frame_election_provider_support::VoterOf<::DataProvider>; +/// Alias for a voter, parameterized by the miner config. +pub(crate) type VoterOf = frame_election_provider_support::Voter< + ::AccountId, + ::MaxVotesPerVoter, +>; /// Alias for a page of voters, parameterized by this crate's config. -pub(crate) type VoterPageOf = - BoundedVec, ::VoterSnapshotPerBlock>; +pub(crate) type VoterPageOf = BoundedVec, ::VoterSnapshotPerBlock>; /// Alias for all pages of voters, parameterized by this crate's config. -pub(crate) type AllVoterPagesOf = BoundedVec, ::Pages>; +pub(crate) type AllVoterPagesOf = BoundedVec, ::Pages>; /// Maximum number of items that [`AllVoterPagesOf`] can contain, when flattened. -pub(crate) struct MaxFlattenedVoters(sp_std::marker::PhantomData); -impl frame_support::traits::Get for MaxFlattenedVoters { +pub(crate) struct MaxFlattenedVoters(sp_std::marker::PhantomData); +impl Get for MaxFlattenedVoters { fn get() -> u32 { T::VoterSnapshotPerBlock::get().saturating_mul(T::Pages::get()) } @@ -223,53 +213,6 @@ impl Default for ElectionCompute { } } -// TODO: maybe use it, else remove it. -#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, MaxEncodedLen, Debug, TypeInfo)] -pub enum PhaseExperimental { - Off, - Snapshot(BlockNumber), - Signed(BlockNumber), - SignedValidation(BlockNumber), - Unsigned(BlockNumber), - Emergency, -} - -impl PhaseExperimental { - pub fn tick(self, next_phase_len: BlockNumber) -> Self { - use PhaseExperimental::*; - match self { - Off => Snapshot(next_phase_len), - Snapshot(x) => - if x.is_zero() { - Signed(next_phase_len) - } else { - Snapshot(x.saturating_sub(One::one())) - }, - Signed(x) => - if x.is_zero() { - SignedValidation(next_phase_len) - } else { - Signed(x.saturating_sub(One::one())) - }, - SignedValidation(x) => - if x.is_zero() { - Unsigned(next_phase_len) - } else { - SignedValidation(x.saturating_sub(One::one())) - }, - - Unsigned(x) => - if x.is_zero() { - // note this: unsigned phase does not really end, only elect can end it. - Unsigned(Zero::zero()) - } else { - Unsigned(x.saturating_sub(One::one())) - }, - Emergency => Emergency, - } - } -} - /// Current phase of the pallet. #[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, MaxEncodedLen, Debug, TypeInfo)] pub enum Phase { diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs b/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs index b06482b1ba8a9..4f4ecc270782f 100644 --- a/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs +++ b/substrate/frame/election-provider-multi-block/src/unsigned/miner.rs @@ -15,21 +15,24 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::{Call, Config, Pallet, WeightInfo}; +use super::{Call, Config, Pallet}; use crate::{ helpers, - types::*, + types::{PadSolutionPages, *}, verifier::{self}, + CommonError, }; use codec::Encode; use frame_election_provider_support::{ExtendedBalance, NposSolver, Support, VoteWeight}; use frame_support::{traits::Get, BoundedVec}; use frame_system::pallet_prelude::*; +use scale_info::TypeInfo; +use sp_npos_elections::EvaluateSupport; use sp_runtime::{ offchain::storage::{MutateStorageError, StorageValueRef}, traits::{SaturatedConversion, Saturating, Zero}, }; -use sp_std::prelude::*; +use sp_std::{collections::btree_map::BTreeMap, prelude::*}; // TODO: fuzzer for the miner @@ -49,21 +52,21 @@ pub enum SnapshotType { } /// Error type of the pallet's [`crate::Config::Solver`]. -pub type OffchainSolverErrorOf = <::OffchainSolver as NposSolver>::Error; +pub type MinerSolverErrorOf = <::Solver as NposSolver>::Error; /// The errors related to the [`BaseMiner`]. #[derive( frame_support::DebugNoBound, frame_support::EqNoBound, frame_support::PartialEqNoBound, )] -pub enum MinerError { +pub enum MinerError { /// An internal error in the NPoS elections crate. NposElections(sp_npos_elections::Error), /// An internal error in the generic solver. - Solver(OffchainSolverErrorOf), + Solver(MinerSolverErrorOf), /// Snapshot data was unavailable unexpectedly. SnapshotUnAvailable(SnapshotType), - /// The snapshot-independent checks failed for the mined solution. - SnapshotIndependentChecks(crate::Error), + /// The base, common errors from the pallet. + Common(CommonError), /// The solution generated from the miner is not feasible. Feasibility(verifier::FeasibilityError), /// Some page index has been invalid. @@ -74,28 +77,33 @@ pub enum MinerError { Defensive(&'static str), } -impl From for MinerError { +impl From for MinerError { fn from(e: sp_npos_elections::Error) -> Self { MinerError::NposElections(e) } } -impl From for MinerError { +impl From for MinerError { fn from(e: verifier::FeasibilityError) -> Self { MinerError::Feasibility(e) } } +impl From for MinerError { + fn from(e: CommonError) -> Self { + MinerError::Common(e) + } +} + /// The errors related to the [`OffchainMiner`]. #[derive( frame_support::DebugNoBound, frame_support::EqNoBound, frame_support::PartialEqNoBound, )] pub enum OffchainMinerError { /// An error in the base miner. - BaseMiner(MinerError), - /// unsigned-specific checks failed. - // NOTE: This uses the error type of the parent crate for now. Might be reworked. - UnsignedChecks(crate::Error), + BaseMiner(MinerError), + /// The base, common errors from the pallet. + Common(CommonError), /// Something went wrong fetching the lock. Lock(&'static str), /// Submitting a transaction to the pool failed. @@ -108,25 +116,120 @@ pub enum OffchainMinerError { FailedToStoreSolution, } -impl From> for OffchainMinerError { - fn from(e: MinerError) -> Self { +impl From> for OffchainMinerError { + fn from(e: MinerError) -> Self { OffchainMinerError::BaseMiner(e) } } +impl From for OffchainMinerError { + fn from(e: CommonError) -> Self { + OffchainMinerError::Common(e) + } +} + +/// Configurations for the miner. +/// +/// This is extracted from the main crate's config so that an offchain miner can readily use the +/// [`BaseMiner`] without needing to deal with the rest of the pallet's configuration. +pub trait MinerConfig { + /// The account id type. + type AccountId: Ord + Clone + codec::Codec + core::fmt::Debug; + /// The solution that the miner is mining. + /// The solution type. + type Solution: codec::FullCodec + + Default + + PartialEq + + Eq + + Clone + + sp_std::fmt::Debug + + Ord + + NposSolution + + TypeInfo + + codec::MaxEncodedLen; + /// The solver type. + type Solver: NposSolver; + /// The maximum length that the miner should use for a solution, per page. + type MaxLength: Get; + /// Maximum number of votes per voter. + /// + /// Must be the same as configured in the [`crate::Config::DataProvider`]. + type MaxVotesPerVoter: Get; + /// Maximum number of winners to select per page. + /// + /// The miner should respect this, it is used for trimming, and bounded data types. + /// + /// Should equal to the onchain value set in `Verifier::Config`. + type MaxWinnersPerPage: Get; + /// Maximum number of backers per winner, per page. + /// + /// The miner should respect this, it is used for trimming, and bounded data types. + /// + /// Should equal to the onchain value set in `Verifier::Config`. + type MaxBackersPerWinner: Get; + /// Maximum number of backers, per winner, across all pages. + /// + /// The miner should respect this, it is used for trimming, and bounded data types. + /// + /// Should equal to the onchain value set in `Verifier::Config`. + type MaxBackersPerWinnerFinal: Get; + /// Maximum number of backers, per winner, per page. + + /// Maximum number of pages that we may compute. + /// + /// Must be the same as configured in the [`crate::Config`]. + type Pages: Get; + /// Maximum number of voters per snapshot page. + /// + /// Must be the same as configured in the [`crate::Config`]. + type VoterSnapshotPerBlock: Get; + /// Maximum number of targets per snapshot page. + /// + /// Must be the same as configured in the [`crate::Config`]. + type TargetSnapshotPerBlock: Get; + /// The hash type of the runtime. + type Hash: Eq + PartialEq; +} + /// A base miner that is only capable of mining a new solution and checking it against the state of /// this pallet for feasibility, and trimming its length/weight. /// /// The type of solver is generic and can be provided, as long as it has the same error and account /// id type as the [`crate::Config::OffchainSolver`]. The default is whatever is fed to /// [`crate::unsigned::Config::OffchainSolver`]. -pub struct BaseMiner::OffchainSolver>( - sp_std::marker::PhantomData<(T, Solver)>, -); +pub struct BaseMiner(sp_std::marker::PhantomData); + +/// Parameterized `BoundedSupports` for the miner. +pub type SupportsOfMiner = frame_election_provider_support::BoundedSupports< + ::AccountId, + ::MaxWinnersPerPage, + ::MaxBackersPerWinner, +>; + +/// Aggregator for inputs to [`BaseMiner`]. +pub struct MineInput { + /// Number of winners to pick. + pub desired_targets: u32, + /// All of the targets. + pub all_targets: BoundedVec, + /// Paginated list of voters. + /// + /// Note for staking-miners: How this is calculated is rather delicate, and the order of the + /// nested vectors matter. See carefully how [`OffchainWorkerMiner::mine_solution`] is doing + /// this. + pub voter_pages: AllVoterPagesOf, + /// Number of pages to mind. + /// + /// Note for staking-miner: Always use [`MinerConfig::Pages`] unless explicitly wanted + /// otherwise. + pub pages: PageIndex, + /// Whether to reduce the solution. Almost always`` + pub do_reduce: bool, + /// The current round for which the solution is being calculated. + pub round: u32, +} -impl>> - BaseMiner -{ +impl BaseMiner { /// Mine a new npos solution, with the given number of pages. /// /// This miner is only capable of mining a solution that either uses all of the pages of the @@ -134,9 +237,8 @@ impl, ) -> Result, MinerError> { pages = pages.min(T::Pages::get()); - // read the appropriate snapshot pages. - let desired_targets = crate::Snapshot::::desired_targets() - .ok_or(MinerError::SnapshotUnAvailable(SnapshotType::DesiredTargets))?; - let all_targets = crate::Snapshot::::targets() - .ok_or(MinerError::SnapshotUnAvailable(SnapshotType::Targets))?; - - // This is the range of voters that we are interested in. Mind the second `.rev`, it is - // super critical. - let voter_pages_range = (crate::Pallet::::lsp()..crate::Pallet::::msp() + 1) - .rev() - .take(pages as usize) - .rev(); - - sublog!( - debug, - "unsigned::base-miner", - "mining a solution with {} pages, voter snapshot range will be: {:?}", - pages, - voter_pages_range.clone().collect::>() - ); - - // NOTE: if `pages (2) < T::Pages (3)`, at this point this vector will have length 2, with a - // layout of `[snapshot(1), snapshot(2)]`, namely the two most significant pages of the - // snapshot. - let voter_pages: BoundedVec<_, T::Pages> = - voter_pages_range - .map(|p| { - crate::Snapshot::::voters(p) - .ok_or(MinerError::SnapshotUnAvailable(SnapshotType::Voters(p))) - }) - .collect::, _>>()? - .try_into() - .expect("`voter_pages_range` has `.take(pages)`; it must have length less than pages; it must convert to `BoundedVec`; qed"); - // we also build this closure early, so we can let `targets` be consumed. let voter_page_fn = helpers::generate_voter_page_fn::(&voter_pages); let target_index_fn = helpers::target_index_fn::(&all_targets); @@ -196,7 +265,7 @@ impl::sorted_truncate_from(supports_invalid_score); // now recreated the staked assignments - let staked = supports_to_staked_assignment(supports_invalid_score); + let staked = supports_to_staked_assignment(bounded_invalid_score.into()); let assignments = assignment_staked_to_ratio_normalized(staked) .map_err::, _>(Into::into)?; - (pre_score, num_trimmed, assignments) + (pre_score, assignments, winners_removed, backers_removed) }; - sublog!( + miner_log!( debug, - "unsigned::base-miner", - "initial score = {:?}, reduced {} edges, trimmed {} supports", + "initial score = {:?}, reduced {} edges, trimmed {} winners from supports, trimmed {} backers from support", _pre_score, reduced_count, - trim_support_count, + winners_removed, + backers_removed, ); final_trimmed_assignments @@ -306,27 +376,22 @@ impl::round(); let mut paged = PagedRawSolution { round, solution_pages, score: Default::default() }; // OPTIMIZATION: we do feasibility_check inside `compute_score`, and once later // pre_dispatch. I think it is fine, but maybe we can improve it. - let score = Self::compute_score(&paged).map_err::, _>(Into::into)?; + let score = Self::compute_score(&paged, &voter_pages, &all_targets, desired_targets) + .map_err::, _>(Into::into)?; paged.score = score.clone(); - sublog!( + miner_log!( info, - "unsigned::base-miner", - "mined a solution with score {:?}, {} winners, {} voters, {} edges, and {} bytes", + "mined a solution with {} pages, score {:?}, {} winners, {} voters, {} edges, and {} bytes", + pages, score, paged.winner_count_single_page_target_snapshot(), paged.voter_count(), @@ -337,82 +402,60 @@ impl Result, MinerError> { - let paged_solution = Self::mine_solution(pages, reduce)?; - let _ = Self::check_solution(&paged_solution, None, true, "mined")?; - Ok(paged_solution) - } - - /// Check the solution, from the perspective of the base miner: - /// - /// 1. snapshot-independent checks. - /// - with the fingerprint check being an optional step fo that. - /// 2. optionally, feasibility check. - /// - /// In most cases, you should always use this either with `do_feasibility = true` or - /// `maybe_snapshot_fingerprint.is_some()`. Doing both could be an overkill. The snapshot - /// staying constant (which can be checked via the hash) is a string guarantee that the - /// feasibility still holds. - pub fn check_solution( - paged_solution: &PagedRawSolution, - maybe_snapshot_fingerprint: Option, - do_feasibility: bool, - solution_type: &str, - ) -> Result<(), MinerError> { - let _ = crate::Pallet::::snapshot_independent_checks( - paged_solution, - maybe_snapshot_fingerprint, - ) - .map_err(|pe| MinerError::SnapshotIndependentChecks(pe))?; - - if do_feasibility { - let _ = Self::check_feasibility(&paged_solution, solution_type)?; - } - - Ok(()) - } - /// perform the feasibility check on all pages of a solution, returning `Ok(())` if all good and /// the corresponding error otherwise. pub fn check_feasibility( paged_solution: &PagedRawSolution, + paged_voters: &AllVoterPagesOf, + snapshot_targets: &BoundedVec, + desired_targets: u32, solution_type: &str, - ) -> Result>, MinerError> { + ) -> Result>, MinerError> { // check every solution page for feasibility. + let padded_voters = paged_voters.clone().pad_solution_pages(T::Pages::get()); paged_solution .solution_pages .pagify(T::Pages::get()) .map(|(page_index, page_solution)| { - ::feasibility_check_page( + verifier::feasibility_check_page_inner_with_snapshot::( page_solution.clone(), - page_index as PageIndex, + &padded_voters[page_index as usize], + snapshot_targets, + desired_targets, ) }) .collect::, _>>() .map_err(|err| { - sublog!( + miner_log!( warn, - "unsigned::base-miner", "feasibility check failed for {} solution at: {:?}", solution_type, err ); MinerError::from(err) }) + .and_then(|supports| { + // TODO: Check `MaxBackersPerWinnerFinal` + Ok(supports) + }) } /// Take the given raw paged solution and compute its score. This will replicate what the chain /// would do as closely as possible, and expects all the corresponding snapshot data to be /// available. - fn compute_score(paged_solution: &PagedRawSolution) -> Result> { - use sp_npos_elections::EvaluateSupport; - use sp_std::collections::btree_map::BTreeMap; - - let all_supports = Self::check_feasibility(paged_solution, "mined")?; + fn compute_score( + paged_solution: &PagedRawSolution, + paged_voters: &AllVoterPagesOf, + all_targets: &BoundedVec, + desired_targets: u32, + ) -> Result> { + let all_supports = Self::check_feasibility( + paged_solution, + paged_voters, + all_targets, + desired_targets, + "mined", + )?; let mut total_backings: BTreeMap = BTreeMap::new(); all_supports.into_iter().map(|x| x.0).flatten().for_each(|(who, support)| { let backing = total_backings.entry(who).or_default(); @@ -436,7 +479,7 @@ impl) -> u32 { - let limit = ::MaxBackersPerWinner::get() as usize; + let limit = T::MaxBackersPerWinner::get() as usize; let mut count = 0; supports .iter_mut() @@ -479,54 +522,13 @@ impl, ) -> Result> { debug_assert_eq!(solution_pages.len(), paged_voters.len()); - let size_limit = T::MinerMaxLength::get(); - let weight_limit = T::MinerMaxWeight::get(); - - let all_voters_count = crate::Snapshot::::voters_decode_len(crate::Pallet::::msp()) - .ok_or(MinerError::SnapshotUnAvailable(SnapshotType::Voters( - crate::Pallet::::msp(), - )))? as u32; - let all_targets_count = crate::Snapshot::::targets_decode_len() - .ok_or(MinerError::SnapshotUnAvailable(SnapshotType::Targets))? - as u32; - let desired_targets = crate::Snapshot::::desired_targets() - .ok_or(MinerError::SnapshotUnAvailable(SnapshotType::DesiredTargets))?; - - let winner_count_of = |solution_pages: &Vec>| { - solution_pages - .iter() - .map(|page| page.unique_targets()) - .flatten() - .collect::>() - .len() as u32 - }; - - let voter_count_of = |solution_pages: &Vec>| { - solution_pages - .iter() - .map(|page| page.voter_count()) - .fold(0, |acc, x| acc.saturating_add(x)) as u32 - }; + let size_limit = T::MaxLength::get(); let needs_any_trim = |solution_pages: &mut Vec>| { let size = solution_pages.encoded_size() as u32; - - let next_active_targets = winner_count_of(solution_pages); - if next_active_targets < desired_targets { - sublog!(warn, "unsigned::base-miner", "trimming has cause a solution to have less targets than desired, this might fail feasibility"); - } - - let weight = ::WeightInfo::submit_unsigned( - all_voters_count, - all_targets_count, - // NOTE: we could not re-compute this all the time and instead assume that in each - // round, it is the previous value minus one. - voter_count_of(solution_pages), - next_active_targets, - ); - let needs_weight_trim = weight.any_gt(weight_limit); let needs_len_trim = size > size_limit; - + // a reminder that we used to have weight trimming here, but not more! + let needs_weight_trim = false; needs_weight_trim || needs_len_trim }; @@ -573,9 +575,8 @@ impl(sp_std::marker::PhantomData); impl OffchainWorkerMiner { @@ -623,6 +624,68 @@ impl OffchainWorkerMiner { /// The number of pages that the offchain worker miner will try and mine. const MINING_PAGES: PageIndex = 1; + pub(crate) fn fetch_snapshot( + pages: PageIndex, + ) -> Result< + (AllVoterPagesOf, BoundedVec, u32), + OffchainMinerError, + > { + // read the appropriate snapshot pages. + let desired_targets = crate::Snapshot::::desired_targets() + .ok_or(MinerError::SnapshotUnAvailable(SnapshotType::DesiredTargets))?; + let all_targets = crate::Snapshot::::targets() + .ok_or(MinerError::SnapshotUnAvailable(SnapshotType::Targets))?; + + // This is the range of voters that we are interested in. Mind the second `.rev`, it is + // super critical. + let voter_pages_range = (crate::Pallet::::lsp()..crate::Pallet::::msp() + 1) + .rev() + .take(pages as usize) + .rev(); + + sublog!( + debug, + "unsigned::base-miner", + "mining a solution with {} pages, voter snapshot range will be: {:?}", + pages, + voter_pages_range.clone().collect::>() + ); + + // NOTE: if `pages (2) < T::Pages (3)`, at this point this vector will have length 2, + // with a layout of `[snapshot(1), snapshot(2)]`, namely the two most significant pages + // of the snapshot. + let voter_pages: BoundedVec<_, T::Pages> = voter_pages_range + .map(|p| { + crate::Snapshot::::voters(p) + .ok_or(MinerError::SnapshotUnAvailable(SnapshotType::Voters(p))) + }) + .collect::, _>>()? + .try_into() + .expect( + "`voter_pages_range` has `.take(pages)`; it must have length less than pages; it + must convert to `BoundedVec`; qed", + ); + + Ok((voter_pages, all_targets, desired_targets)) + } + + pub(crate) fn mine_solution( + pages: PageIndex, + do_reduce: bool, + ) -> Result, OffchainMinerError> { + let (voter_pages, all_targets, desired_targets) = Self::fetch_snapshot(pages)?; + let round = crate::Pallet::::round(); + BaseMiner::::mine_solution(MineInput { + desired_targets, + all_targets, + voter_pages, + pages, + do_reduce, + round, + }) + .map_err(Into::into) + } + /// Get a checked solution from the base miner, ensure unsigned-specific checks also pass, then /// return an submittable call. fn mine_checked_call() -> Result, OffchainMinerError> { @@ -631,9 +694,8 @@ impl OffchainWorkerMiner { // NOTE: we don't run any checks in the base miner, and run all of them via // `Self::full_checks`. - let paged_solution = - BaseMiner::::mine_solution(Self::MINING_PAGES, reduce) - .map_err::, _>(Into::into)?; + let paged_solution = Self::mine_solution(Self::MINING_PAGES, reduce) + .map_err::, _>(Into::into)?; // check the call fully, no fingerprinting. let _ = Self::check_solution(&paged_solution, None, true, "mined")?; @@ -660,23 +722,19 @@ impl OffchainWorkerMiner { /// 2. snapshot-independent checks. /// 1. optionally, snapshot fingerprint. pub fn check_solution( - paged_solution: &PagedRawSolution, + paged_solution: &PagedRawSolution, maybe_snapshot_fingerprint: Option, do_feasibility: bool, solution_type: &str, ) -> Result<(), OffchainMinerError> { // NOTE: we prefer cheap checks first, so first run unsigned checks. - Pallet::unsigned_specific_checks(paged_solution) - .map_err(|pe| OffchainMinerError::UnsignedChecks(pe)) - .and_then(|_| { - BaseMiner::::check_solution( - paged_solution, - maybe_snapshot_fingerprint, - do_feasibility, - solution_type, - ) - .map_err(OffchainMinerError::BaseMiner) - }) + Pallet::::unsigned_specific_checks(paged_solution)?; + Self::base_check_solution( + paged_solution, + maybe_snapshot_fingerprint, + do_feasibility, + solution_type, + ) } fn submit_call(call: Call) -> Result<(), OffchainMinerError> { @@ -697,6 +755,45 @@ impl OffchainWorkerMiner { .map_err(|_| OffchainMinerError::PoolSubmissionFailed) } + /// Check the solution, from the perspective of the base miner: + /// + /// 1. snapshot-independent checks. + /// - with the fingerprint check being an optional step fo that. + /// 2. optionally, feasibility check. + /// + /// In most cases, you should always use this either with `do_feasibility = true` or + /// `maybe_snapshot_fingerprint.is_some()`. Doing both could be an overkill. The snapshot + /// staying constant (which can be checked via the hash) is a string guarantee that the + /// feasibility still holds. + /// + /// The difference between this and [`Self::check_solution`] is that this does not run unsigned + /// specific checks. + pub(crate) fn base_check_solution( + paged_solution: &PagedRawSolution, + maybe_snapshot_fingerprint: Option, + do_feasibility: bool, + solution_type: &str, // TODO: remove + ) -> Result<(), OffchainMinerError> { + let _ = crate::Pallet::::snapshot_independent_checks( + paged_solution, + maybe_snapshot_fingerprint, + )?; + + if do_feasibility { + let (voter_pages, all_targets, desired_targets) = + Self::fetch_snapshot(paged_solution.solution_pages.len() as PageIndex)?; + let _ = BaseMiner::::check_feasibility( + &paged_solution, + &voter_pages, + &all_targets, + desired_targets, + solution_type, + )?; + } + + Ok(()) + } + /// Attempt to restore a solution from cache. Otherwise, compute it fresh. Either way, /// submit if our call's score is greater than that of the cached solution. pub fn restore_or_compute_then_maybe_submit() -> Result<(), OffchainMinerError> { @@ -723,17 +820,17 @@ impl OffchainWorkerMiner { } }) .or_else::, _>(|error| { - use MinerError::*; - use OffchainMinerError::*; - + use OffchainMinerError as OE; + use MinerError as ME; + use CommonError as CE; match error { - NoStoredSolution => { + OE::NoStoredSolution => { // IFF, not present regenerate. let call = Self::mine_checked_call()?; Self::save_solution(&call, crate::Snapshot::::fingerprint())?; Ok(call) }, - UnsignedChecks(ref e) => { + OE::Common(ref e) => { sublog!( error, "unsigned::ocw-miner", @@ -743,9 +840,9 @@ impl OffchainWorkerMiner { Self::clear_offchain_solution_cache(); Err(error) }, - BaseMiner(Feasibility(_)) - | BaseMiner(SnapshotIndependentChecks(crate::Error::::WrongRound)) - | BaseMiner(SnapshotIndependentChecks(crate::Error::::WrongFingerprint)) + OE::BaseMiner(ME::Feasibility(_)) + | OE::BaseMiner(ME::Common(CE::WrongRound)) + | OE::BaseMiner(ME::Common(CE::WrongFingerprint)) => { // note that failing `Feasibility` can only mean that the solution was // computed over a snapshot that has changed due to a fork. @@ -865,211 +962,8 @@ mod trim_weight_length { use super::*; use crate::{mock::*, verifier::Verifier}; use frame_election_provider_support::TryFromUnboundedPagedSupports; - use frame_support::pallet_prelude::*; use sp_npos_elections::Support; - #[test] - fn trim_weight_basic() { - // This is just demonstration to show the normal election result with new votes, without any - // trimming. - ExtBuilder::unsigned().build_and_execute(|| { - let mut current_voters = Voters::get(); - current_voters.iter_mut().for_each(|(who, stake, ..)| *stake = *who); - Voters::set(current_voters); - - roll_to_snapshot_created(); - ensure_voters(3, 12); - - let solution = mine_full_solution().unwrap(); - - // 4 of these will be trimmed. - assert_eq!( - solution.solution_pages.iter().map(|page| page.voter_count()).sum::(), - 8 - ); - - load_mock_signed_and_start(solution); - let supports = roll_to_full_verification(); - - // NOTE: this test is a bit funny because our msp snapshot page actually contains voters - // with less stake than lsp.. but that's not relevant here. - assert_eq!( - supports, - vec![ - // supports from 30, 40, both will be removed. - vec![ - (30, Support { total: 30, voters: vec![(30, 30)] }), - (40, Support { total: 40, voters: vec![(40, 40)] }) - ], - // supports from 5, 6, 7. 5 and 6 will be removed. - vec![ - (30, Support { total: 11, voters: vec![(7, 7), (5, 2), (6, 2)] }), - (40, Support { total: 7, voters: vec![(5, 3), (6, 4)] }) - ], - // all will stay - vec![(40, Support { total: 9, voters: vec![(2, 2), (3, 3), (4, 4)] })] - ] - .try_from_unbounded_paged() - .unwrap() - ); - }); - - // now we get to the real test... - ExtBuilder::unsigned() - .miner_weight(Weight::from_parts(4, u64::MAX)) - .build_and_execute(|| { - // first, replace the stake of all voters with their account id. - let mut current_voters = Voters::get(); - current_voters.iter_mut().for_each(|(who, stake, ..)| *stake = *who); - Voters::set(current_voters); - - // with 1 weight unit per voter, this can only support 4 voters, despite having 12 - // in the snapshot. - roll_to_snapshot_created(); - ensure_voters(3, 12); - - let solution = mine_full_solution().unwrap(); - assert_eq!( - solution.solution_pages.iter().map(|page| page.voter_count()).sum::(), - 4 - ); - - load_mock_signed_and_start(solution); - let supports = roll_to_full_verification(); - - // a solution is queued. - assert!(VerifierPallet::queued_score().is_some()); - - assert_eq!( - supports, - vec![ - vec![], - vec![(30, Support { total: 7, voters: vec![(7, 7)] })], - vec![(40, Support { total: 9, voters: vec![(2, 2), (3, 3), (4, 4)] })] - ] - .try_from_unbounded_paged() - .unwrap() - ); - }) - } - - #[test] - fn trim_weight_partial_solution() { - // This is just demonstration to show the normal election result with new votes, without any - // trimming. - ExtBuilder::unsigned().build_and_execute(|| { - let mut current_voters = Voters::get(); - current_voters.iter_mut().for_each(|(who, stake, ..)| *stake = *who); - Voters::set(current_voters); - - roll_to_snapshot_created(); - ensure_voters(3, 12); - - let solution = mine_solution(2).unwrap(); - - // 3 of these will be trimmed. - assert_eq!( - solution.solution_pages.iter().map(|page| page.voter_count()).sum::(), - 7 - ); - - load_mock_signed_and_start(solution); - let supports = roll_to_full_verification(); - - // a solution is queued. - assert!(VerifierPallet::queued_score().is_some()); - - assert_eq!( - supports, - vec![ - vec![], - // 5, 6, 7 will be removed in the next test block - vec![ - (10, Support { total: 10, voters: vec![(8, 8), (5, 2)] }), - (30, Support { total: 16, voters: vec![(6, 6), (7, 7), (5, 3)] }) - ], - vec![ - (10, Support { total: 5, voters: vec![(1, 1), (4, 4)] }), - (30, Support { total: 2, voters: vec![(2, 2)] }) - ] - ] - .try_from_unbounded_paged() - .unwrap() - ); - }); - - // now we get to the real test... - ExtBuilder::unsigned() - .miner_weight(Weight::from_parts(4, u64::MAX)) - .build_and_execute(|| { - // first, replace the stake of all voters with their account id. - let mut current_voters = Voters::get(); - current_voters.iter_mut().for_each(|(who, stake, ..)| *stake = *who); - Voters::set(current_voters); - - roll_to_snapshot_created(); - ensure_voters(3, 12); - - let solution = mine_solution(2).unwrap(); - assert_eq!( - solution.solution_pages.iter().map(|page| page.voter_count()).sum::(), - 4 - ); - - load_mock_signed_and_start(solution); - let supports = roll_to_full_verification(); - - // a solution is queued. - assert!(VerifierPallet::queued_score().is_some()); - - assert_eq!( - supports, - vec![ - vec![], - vec![(10, Support { total: 8, voters: vec![(8, 8)] })], - vec![ - (10, Support { total: 5, voters: vec![(1, 1), (4, 4)] }), - (30, Support { total: 2, voters: vec![(2, 2)] }) - ] - ] - .try_from_unbounded_paged() - .unwrap() - ); - }) - } - - #[test] - fn trim_weight_too_much_makes_solution_invalid() { - // with just 1 units, we can support 1 voter. This is not enough to have 2 winner which we - // want. - ExtBuilder::unsigned() - .miner_weight(Weight::from_parts(1, u64::MAX)) - .build_and_execute(|| { - let mut current_voters = Voters::get(); - current_voters.iter_mut().for_each(|(who, stake, ..)| *stake = *who); - Voters::set(current_voters); - - roll_to_snapshot_created(); - ensure_voters(3, 12); - - let solution = mine_full_solution().unwrap(); - assert_eq!( - solution.solution_pages.iter().map(|page| page.voter_count()).sum::(), - 1 - ); - - load_mock_signed_and_start(solution); - let supports = roll_to_full_verification(); - - // nothing is queued - assert!(VerifierPallet::queued_score().is_none()); - assert_eq!( - supports, - vec![vec![], vec![], vec![]].try_from_unbounded_paged().unwrap() - ); - }) - } - #[test] fn trim_length() { // This is just demonstration to show the normal election result with new votes, without any @@ -1159,6 +1053,8 @@ mod trim_weight_length { #[cfg(test)] mod base_miner { + use std::vec; + use super::*; use crate::{mock::*, Snapshot}; use frame_election_provider_support::TryFromUnboundedPagedSupports; @@ -1217,7 +1113,8 @@ mod base_miner { assert_eq!(paged.solution_pages.len(), 1); // this solution must be feasible and submittable. - BaseMiner::::check_solution(&paged, None, true, "mined").unwrap(); + OffchainWorkerMiner::::base_check_solution(&paged, None, true, "mined") + .unwrap(); // now do a realistic full verification load_mock_signed_and_start(paged.clone()); @@ -1303,7 +1200,8 @@ mod base_miner { ); // this solution must be feasible and submittable. - BaseMiner::::check_solution(&paged, None, false, "mined").unwrap(); + OffchainWorkerMiner::::base_check_solution(&paged, None, false, "mined") + .unwrap(); // it must also be verified in the verifier load_mock_signed_and_start(paged.clone()); @@ -1390,7 +1288,8 @@ mod base_miner { ); // this solution must be feasible and submittable. - BaseMiner::::check_solution(&paged, None, true, "mined").unwrap(); + OffchainWorkerMiner::::base_check_solution(&paged, None, true, "mined") + .unwrap(); // now do a realistic full verification load_mock_signed_and_start(paged.clone()); let supports = roll_to_full_verification(); @@ -1469,7 +1368,8 @@ mod base_miner { ); // this solution must be feasible and submittable. - BaseMiner::::check_solution(&paged, None, true, "mined").unwrap(); + OffchainWorkerMiner::::base_check_solution(&paged, None, true, "mined") + .unwrap(); // now do a realistic full verification. load_mock_signed_and_start(paged.clone()); let supports = roll_to_full_verification(); @@ -1533,7 +1433,8 @@ mod base_miner { let paged = mine_solution(2).unwrap(); // this solution must be feasible and submittable. - BaseMiner::::check_solution(&paged, None, true, "mined").unwrap(); + OffchainWorkerMiner::::base_check_solution(&paged, None, true, "mined") + .unwrap(); assert_eq!( paged.solution_pages, @@ -1563,7 +1464,8 @@ mod base_miner { ); // this solution must be feasible and submittable. - BaseMiner::::check_solution(&paged, None, true, "mined").unwrap(); + OffchainWorkerMiner::::base_check_solution(&paged, None, true, "mined") + .unwrap(); // now do a realistic full verification. load_mock_signed_and_start(paged.clone()); let supports = roll_to_full_verification(); @@ -1599,12 +1501,12 @@ mod base_miner { fn can_reduce_solution() { ExtBuilder::unsigned().build_and_execute(|| { roll_to_snapshot_created(); - let full_edges = BaseMiner::::mine_solution(Pages::get(), false) + let full_edges = OffchainWorkerMiner::::mine_solution(Pages::get(), false) .unwrap() .solution_pages .iter() .fold(0, |acc, x| acc + x.edge_count()); - let reduced_edges = BaseMiner::::mine_solution(Pages::get(), true) + let reduced_edges = OffchainWorkerMiner::::mine_solution(Pages::get(), true) .unwrap() .solution_pages .iter() @@ -1615,7 +1517,7 @@ mod base_miner { } #[test] - fn trim_backings_works() { + fn trim_backers_per_page_works() { ExtBuilder::unsigned() .max_backers_per_winner(5) .voter_per_page(8) @@ -1654,7 +1556,7 @@ mod base_miner { 40, Support { total: 40, - voters: vec![(2, 10), (3, 10), (4, 10), (6, 10)] + voters: vec![(2, 10), (3, 10), (5, 10), (6, 10)] } ) ] @@ -1664,11 +1566,112 @@ mod base_miner { ); }) } + + #[test] + fn trim_backers_final_works() { + ExtBuilder::unsigned() + .max_backers_per_winner_final(3) + .pages(3) + .build_and_execute(|| { + roll_to_snapshot_created(); + + let paged = mine_full_solution().unwrap(); + load_mock_signed_and_start(paged.clone()); + + // this must be correct + let supports = roll_to_full_verification(); + + assert_eq!( + verifier_events(), + vec![ + verifier::Event::Verified(2, 2), + verifier::Event::Verified(1, 2), + verifier::Event::Verified(0, 2), + verifier::Event::VerificationFailed( + 0, + verifier::FeasibilityError::FailedToBoundSupport + ) + ] + ); + todo!("miner should trim max backers final"); + + assert_eq!( + supports, + vec![ + // 1 backing for 10 + vec![(10, Support { total: 8, voters: vec![(104, 8)] })], + // 2 backings for 10 + vec![ + (10, Support { total: 17, voters: vec![(10, 10), (103, 7)] }), + (40, Support { total: 40, voters: vec![(40, 40)] }) + ], + // 20 backings for 10 + vec![ + (10, Support { total: 20, voters: vec![(1, 10), (8, 10)] }), + ( + 40, + Support { + total: 40, + voters: vec![(2, 10), (3, 10), (4, 10), (6, 10)] + } + ) + ] + ] + .try_from_unbounded_paged() + .unwrap() + ); + }); + } + + #[test] + fn trim_backers_final_works_2() { + ExtBuilder::unsigned() + .max_backers_per_winner_final(4) + .max_backers_per_winner(2) + .pages(3) + .build_and_execute(|| { + roll_to_snapshot_created(); + + let paged = mine_full_solution().unwrap(); + load_mock_signed_and_start(paged.clone()); + + // this must be correct + let supports = roll_to_full_verification(); + + // 10 has no more than 5 backings, and from the new voters that we added in this + // test, the most staked ones stayed (103, 104) and the rest trimmed. + assert_eq!( + supports, + vec![ + // 1 backing for 10 + vec![(10, Support { total: 8, voters: vec![(104, 8)] })], + // 2 backings for 10 + vec![ + (10, Support { total: 17, voters: vec![(10, 10), (103, 7)] }), + (40, Support { total: 40, voters: vec![(40, 40)] }) + ], + // 20 backings for 10 + vec![ + (10, Support { total: 20, voters: vec![(1, 10), (8, 10)] }), + ( + 40, + Support { + total: 40, + voters: vec![(2, 10), (3, 10), (4, 10), (6, 10)] + } + ) + ] + ] + .try_from_unbounded_paged() + .unwrap() + ); + }); + } } #[cfg(test)] mod offchain_worker_miner { - use crate::verifier::Verifier; + use crate::{verifier::Verifier, CommonError}; use frame_support::traits::Hooks; use sp_runtime::offchain::storage_lock::{BlockAndTime, StorageLock}; @@ -1934,8 +1937,9 @@ mod offchain_worker_miner { vec![vec![(40, Support { total: 10, voters: vec![(3, 10)] })]], 0, ); - let weak_call = - crate::unsigned::Call::submit_unsigned { paged_solution: Box::new(weak_solution) }; + let weak_call = crate::unsigned::Call::::submit_unsigned { + paged_solution: Box::new(weak_solution), + }; call_cache.set(&weak_call); // run again @@ -2012,9 +2016,7 @@ mod offchain_worker_miner { // beautiful errors, isn't it? assert_eq!( OffchainWorkerMiner::::mine_checked_call().unwrap_err(), - OffchainMinerError::BaseMiner(MinerError::SnapshotIndependentChecks( - crate::Error::::WrongWinnerCount - )) + OffchainMinerError::Common(CommonError::WrongWinnerCount) ); }); } diff --git a/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs b/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs index 471ee54f942c8..87d9754b74e2a 100644 --- a/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/unsigned/mod.rs @@ -71,21 +71,18 @@ mod pallet { types::*, unsigned::miner::{self}, verifier::Verifier, + CommonError, }; use frame_support::pallet_prelude::*; use frame_system::{offchain::CreateInherent, pallet_prelude::*}; use sp_runtime::traits::SaturatedConversion; use sp_std::prelude::*; - /// convert a DispatchError to a custom InvalidTransaction with the inner code being the error - /// number. - fn dispatch_error_to_invalid(error: sp_runtime::DispatchError) -> InvalidTransaction { - use sp_runtime::ModuleError; - let error_number = match error { - DispatchError::Module(ModuleError { error, .. }) => error, - _ => [0u8, 0, 0, 0], - }; - InvalidTransaction::Custom(error_number[0] as u8) + /// convert a [`crate::CommonError`] to a custom InvalidTransaction with the inner code being + /// the index of the variant. + fn base_error_to_invalid(error: CommonError) -> InvalidTransaction { + let index = error.encode().pop().unwrap_or(0); + InvalidTransaction::Custom(index) } pub trait WeightInfo { @@ -114,16 +111,6 @@ mod pallet { /// The priority of the unsigned transaction submitted in the unsigned-phase type MinerTxPriority: Get; - /// Maximum weight that the miner should consume. - /// - /// The miner will ensure that the total weight of the unsigned solution will not exceed - /// this value, based on [`WeightInfo::submit_unsigned`]. - type MinerMaxWeight: Get; - /// Maximum length (bytes) that the mined solution should consume. - /// - /// The miner will ensure that the total length of the unsigned solution will not exceed - /// this value. - type MinerMaxLength: Get; type WeightInfo: WeightInfo; } @@ -145,9 +132,10 @@ mod pallet { #[pallet::call_index(0)] pub fn submit_unsigned( origin: OriginFor, - paged_solution: Box>, + paged_solution: Box>, ) -> DispatchResultWithPostInfo { ensure_none(origin)?; + // TODO: remove the panic from this function for now. let error_message = "Invalid unsigned submission must produce invalid block and \ deprive validator from their authoring reward."; @@ -199,7 +187,7 @@ mod pallet { ); err }) - .map_err(dispatch_error_to_invalid)?; + .map_err(base_error_to_invalid)?; ValidTransaction::with_tag_prefix("OffchainElection") // The higher the score.minimal_stake, the better a paged_solution is. @@ -223,7 +211,7 @@ mod pallet { fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> { if let Call::submit_unsigned { paged_solution, .. } = call { Self::validate_unsigned_checks(paged_solution.as_ref()) - .map_err(dispatch_error_to_invalid) + .map_err(base_error_to_invalid) .map_err(Into::into) } else { Err(InvalidTransaction::Call.into()) @@ -233,6 +221,11 @@ mod pallet { #[pallet::hooks] impl Hooks> for Pallet { + fn integrity_test() { + // TODO: weight of a single page verification should be well below what we desire to + // have. + } + fn offchain_worker(now: BlockNumberFor) { use sp_runtime::offchain::storage_lock::{BlockAndTime, StorageLock}; @@ -311,8 +304,8 @@ mod pallet { /// These check both for snapshot independent checks, and some checks that are specific to /// the unsigned phase. pub(crate) fn validate_unsigned_checks( - paged_solution: &PagedRawSolution, - ) -> DispatchResult { + paged_solution: &PagedRawSolution, + ) -> Result<(), CommonError> { Self::unsigned_specific_checks(paged_solution) .and(crate::Pallet::::snapshot_independent_checks(paged_solution, None)) .map_err(Into::into) @@ -322,20 +315,20 @@ mod pallet { /// /// ensure solution has the correct phase, and it has only 1 page. pub fn unsigned_specific_checks( - paged_solution: &PagedRawSolution, - ) -> Result<(), crate::Error> { + paged_solution: &PagedRawSolution, + ) -> Result<(), CommonError> { ensure!( crate::Pallet::::current_phase().is_unsigned(), - crate::Error::::EarlySubmission + CommonError::EarlySubmission ); - - ensure!(paged_solution.solution_pages.len() == 1, crate::Error::::WrongPageCount); + ensure!(paged_solution.solution_pages.len() == 1, CommonError::WrongPageCount); Ok(()) } #[cfg(test)] pub(crate) fn sanity_check() -> Result<(), &'static str> { + // TODO Ok(()) } } diff --git a/substrate/frame/election-provider-multi-block/src/verifier/impls.rs b/substrate/frame/election-provider-multi-block/src/verifier/impls.rs index ccb923acf16b7..f1f40c3100f8c 100644 --- a/substrate/frame/election-provider-multi-block/src/verifier/impls.rs +++ b/substrate/frame/election-provider-multi-block/src/verifier/impls.rs @@ -16,13 +16,21 @@ // limitations under the License. use super::*; -use crate::{helpers, types::SupportsOf, verifier::Verifier, SolutionOf}; +use crate::{ + helpers, + types::VoterOf, + unsigned::miner::{MinerConfig, SupportsOfMiner}, + verifier::Verifier, + SolutionOf, +}; use codec::{Decode, Encode, MaxEncodedLen}; -use frame_election_provider_support::{ExtendedBalance, NposSolution, PageIndex}; +use frame_election_provider_support::{ + ExtendedBalance, NposSolution, PageIndex, TryFromOtherBounds, +}; use frame_support::{ ensure, pallet_prelude::{ValueQuery, *}, - traits::{Defensive, Get}, + traits::{defensive_prelude::*, Defensive, Get}, }; use frame_system::pallet_prelude::*; use pallet::*; @@ -30,6 +38,12 @@ use sp_npos_elections::{evaluate_support, ElectionScore, EvaluateSupport}; use sp_runtime::{Perbill, RuntimeDebug}; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; +pub(crate) type SupportsOfVerifier = frame_election_provider_support::BoundedSupports< + ::AccountId, + ::MaxWinnersPerPage, + ::MaxBackersPerWinner, +>; + /// The status of this pallet. #[derive(Encode, Decode, scale_info::TypeInfo, Clone, Copy, MaxEncodedLen, RuntimeDebug)] #[cfg_attr(any(test, debug_assertions), derive(PartialEq, Eq))] @@ -119,7 +133,9 @@ pub(crate) mod pallet { /// Something that can provide the solution data to the verifier. /// /// In reality, this will be fulfilled by the signed phase. - type SolutionDataProvider: crate::verifier::SolutionDataProvider; + type SolutionDataProvider: crate::verifier::SolutionDataProvider< + Solution = SolutionOf, + >; /// The weight information of this pallet. type WeightInfo; @@ -247,14 +263,14 @@ pub(crate) mod pallet { /// known to be valid. At this stage, we write to the invalid variant. Once all pages are /// verified, a call to [`finalize_correct`] will seal the correct pages and flip the /// invalid/valid variants. - pub(crate) fn set_invalid_page(page: PageIndex, supports: SupportsOf>) { + pub(crate) fn set_invalid_page(page: PageIndex, supports: SupportsOfVerifier>) { use frame_support::traits::TryCollect; Self::mutate_checked(|| { let backings: BoundedVec<_, _> = supports .iter() .map(|(x, s)| (x.clone(), PartialBackings { total: s.total, backers: s.voters.len() as u32 } )) .try_collect() - .expect("`SupportsOf` is bounded by as Verifier>::MaxWinnersPerPage, which is assured to be the same as `T::MaxWinnersPerPage` in an integrity test"); + .expect("`SupportsOfVerifier` is bounded by as Verifier>::MaxWinnersPerPage, which is assured to be the same as `T::MaxWinnersPerPage` in an integrity test"); QueuedSolutionBackings::::insert(page, backings); match Self::invalid() { @@ -272,7 +288,7 @@ pub(crate) mod pallet { /// solution. pub(crate) fn force_set_single_page_valid( page: PageIndex, - supports: SupportsOf>, + supports: SupportsOfVerifier>, score: ElectionScore, ) { Self::mutate_checked(|| { @@ -351,7 +367,9 @@ pub(crate) mod pallet { } /// Get a page of the current queued (aka valid) solution. - pub(crate) fn get_queued_solution_page(page: PageIndex) -> Option>> { + pub(crate) fn get_queued_solution_page( + page: PageIndex, + ) -> Option>> { match Self::valid() { ValidSolution::X => QueuedSolutionX::::get(page), ValidSolution::Y => QueuedSolutionY::::get(page), @@ -369,21 +387,23 @@ pub(crate) mod pallet { #[cfg(any(test, debug_assertions))] impl QueuedSolution { - pub(crate) fn valid_iter() -> impl Iterator>)> { + pub(crate) fn valid_iter( + ) -> impl Iterator>)> { match Self::valid() { ValidSolution::X => QueuedSolutionX::::iter(), ValidSolution::Y => QueuedSolutionY::::iter(), } } - pub(crate) fn invalid_iter() -> impl Iterator>)> { + pub(crate) fn invalid_iter( + ) -> impl Iterator>)> { match Self::invalid() { ValidSolution::X => QueuedSolutionX::::iter(), ValidSolution::Y => QueuedSolutionY::::iter(), } } - pub(crate) fn get_valid_page(page: PageIndex) -> Option>> { + pub(crate) fn get_valid_page(page: PageIndex) -> Option>> { match Self::valid() { ValidSolution::X => QueuedSolutionX::::get(page), ValidSolution::Y => QueuedSolutionY::::get(page), @@ -451,10 +471,12 @@ pub(crate) mod pallet { /// Writing them to a bugger and copying at the ned is slightly better, but expensive. This flag /// system is best of both worlds. #[pallet::storage] - type QueuedSolutionX = StorageMap<_, Twox64Concat, PageIndex, SupportsOf>>; + type QueuedSolutionX = + StorageMap<_, Twox64Concat, PageIndex, SupportsOfVerifier>>; #[pallet::storage] /// The `Y` variant of the current queued solution. Might be the valid one or not. - type QueuedSolutionY = StorageMap<_, Twox64Concat, PageIndex, SupportsOf>>; + type QueuedSolutionY = + StorageMap<_, Twox64Concat, PageIndex, SupportsOfVerifier>>; /// Pointer to the variant of [`QueuedSolutionX`] or [`QueuedSolutionY`] that is currently /// valid. #[pallet::storage] @@ -600,10 +622,10 @@ impl Pallet { } fn do_verify_synchronous( - partial_solution: T::Solution, + partial_solution: SolutionOf, claimed_score: ElectionScore, page: PageIndex, - ) -> Result, FeasibilityError> { + ) -> Result, FeasibilityError> { // first, ensure this score will be good enough, even if valid.. let _ = Self::ensure_score_quality(claimed_score)?; @@ -691,75 +713,27 @@ impl Pallet { /// - checks the number of winners to be less than or equal to `DesiredTargets` IN THIS PAGE /// ONLY. pub(super) fn feasibility_check_page_inner( - partial_solution: SolutionOf, + partial_solution: SolutionOf, page: PageIndex, - ) -> Result, FeasibilityError> { + ) -> Result, FeasibilityError> { // Read the corresponding snapshots. let snapshot_targets = crate::Snapshot::::targets().ok_or(FeasibilityError::SnapshotUnavailable)?; let snapshot_voters = crate::Snapshot::::voters(page).ok_or(FeasibilityError::SnapshotUnavailable)?; - - // ----- Start building. First, we need some closures. - let cache = helpers::generate_voter_cache::(&snapshot_voters); - let voter_at = helpers::voter_at_fn::(&snapshot_voters); - let target_at = helpers::target_at_fn::(&snapshot_targets); - let voter_index = helpers::voter_index_fn_usize::(&cache); - - // Then convert solution -> assignment. This will fail if any of the indices are - // gibberish. - let assignments = partial_solution - .into_assignment(voter_at, target_at) - .map_err::(Into::into)?; - - // Ensure that assignments are all correct. - let _ = assignments - .iter() - .map(|ref assignment| { - // Check that assignment.who is actually a voter (defensive-only). NOTE: while - // using the index map from `voter_index` is better than a blind linear search, - // this *still* has room for optimization. Note that we had the index when we - // did `solution -> assignment` and we lost it. Ideal is to keep the index - // around. - - // Defensive-only: must exist in the snapshot. - let snapshot_index = - voter_index(&assignment.who).ok_or(FeasibilityError::InvalidVoter)?; - // Defensive-only: index comes from the snapshot, must exist. - let (_voter, _stake, targets) = - snapshot_voters.get(snapshot_index).ok_or(FeasibilityError::InvalidVoter)?; - debug_assert!(*_voter == assignment.who); - - // Check that all of the targets are valid based on the snapshot. - if assignment.distribution.iter().any(|(t, _)| !targets.contains(t)) { - return Err(FeasibilityError::InvalidVote) - } - Ok(()) - }) - .collect::>()?; - - // ----- Start building support. First, we need one more closure. - let stake_of = helpers::stake_of_fn::(&snapshot_voters, &cache); - - // This might fail if the normalization fails. Very unlikely. See `integrity_test`. - let staked_assignments = - sp_npos_elections::assignment_ratio_to_staked_normalized(assignments, stake_of) - .map_err::(Into::into)?; - - let supports = sp_npos_elections::to_supports(&staked_assignments); - - // Ensure some heuristics. These conditions must hold in the **entire** support, this is - // just a single page. But, they must hold in a single page as well. let desired_targets = crate::Snapshot::::desired_targets().ok_or(FeasibilityError::SnapshotUnavailable)?; - ensure!((supports.len() as u32) <= desired_targets, FeasibilityError::WrongWinnerCount); - - // almost-defensive-only: `MaxBackersPerWinner` is already checked. A sane value of - // `MaxWinnersPerPage` should be more than any possible value of `desired_targets()`, which - // is ALSO checked, so this conversion can almost never fail. - let bounded_supports = - supports.try_into().map_err(|_| FeasibilityError::FailedToBoundSupport)?; - Ok(bounded_supports) + + feasibility_check_page_inner_with_snapshot::( + partial_solution, + &snapshot_voters, + &snapshot_targets, + desired_targets, + ) + .and_then(|miner_supports| { + SupportsOfVerifier::::try_from_other_bounds(miner_supports) + .defensive_map_err(|_| FeasibilityError::FailedToBoundSupport) + }) } #[cfg(debug_assertions)] @@ -768,11 +742,82 @@ impl Pallet { } } +/// Same as `feasibility_check_page_inner`, but with a snapshot. +/// +/// This is exported as a standalone function, relying on `MinerConfig` rather than `Config` so that +/// it can be used in any offchain miner. +pub fn feasibility_check_page_inner_with_snapshot( + partial_solution: SolutionOf, + snapshot_voters: &BoundedVec, T::VoterSnapshotPerBlock>, + snapshot_targets: &BoundedVec, + desired_targets: u32, +) -> Result, FeasibilityError> { + // ----- Start building. First, we need some closures. + let cache = helpers::generate_voter_cache::(snapshot_voters); + let voter_at = helpers::voter_at_fn::(snapshot_voters); + let target_at = helpers::target_at_fn::(snapshot_targets); + let voter_index = helpers::voter_index_fn_usize::(&cache); + + // Then convert solution -> assignment. This will fail if any of the indices are + // gibberish. + let assignments = partial_solution + .into_assignment(voter_at, target_at) + .map_err::(Into::into)?; + + // Ensure that assignments are all correct. + let _ = assignments + .iter() + .map(|ref assignment| { + // Check that assignment.who is actually a voter (defensive-only). NOTE: while + // using the index map from `voter_index` is better than a blind linear search, + // this *still* has room for optimization. Note that we had the index when we + // did `solution -> assignment` and we lost it. Ideal is to keep the index + // around. + + // Defensive-only: must exist in the snapshot. + let snapshot_index = + voter_index(&assignment.who).ok_or(FeasibilityError::InvalidVoter)?; + // Defensive-only: index comes from the snapshot, must exist. + let (_voter, _stake, targets) = + snapshot_voters.get(snapshot_index).ok_or(FeasibilityError::InvalidVoter)?; + debug_assert!(*_voter == assignment.who); + + // Check that all of the targets are valid based on the snapshot. + if assignment.distribution.iter().any(|(t, _)| !targets.contains(t)) { + return Err(FeasibilityError::InvalidVote) + } + Ok(()) + }) + .collect::>()?; + + // ----- Start building support. First, we need one more closure. + let stake_of = helpers::stake_of_fn::(&snapshot_voters, &cache); + + // This might fail if the normalization fails. Very unlikely. See `integrity_test`. + let staked_assignments = + sp_npos_elections::assignment_ratio_to_staked_normalized(assignments, stake_of) + .map_err::(Into::into)?; + + let supports = sp_npos_elections::to_supports(&staked_assignments); + + // Ensure some heuristics. These conditions must hold in the **entire** support, this is + // just a single page. But, they must hold in a single page as well. + ensure!((supports.len() as u32) <= desired_targets, FeasibilityError::WrongWinnerCount); + + // almost-defensive-only: `MaxBackersPerWinner` is already checked. A sane value of + // `MaxWinnersPerPage` should be more than any possible value of `desired_targets()`, which + // is ALSO checked, so this conversion can almost never fail. + let bounded_supports = + supports.try_into().map_err(|_| FeasibilityError::FailedToBoundSupport)?; + Ok(bounded_supports) +} + impl Verifier for Pallet { type AccountId = T::AccountId; - type Solution = SolutionOf; + type Solution = SolutionOf; type MaxBackersPerWinner = T::MaxBackersPerWinner; type MaxWinnersPerPage = T::MaxWinnersPerPage; + type MaxBackersPerWinnerFinal = T::MaxBackersPerWinnerFinal; fn set_minimum_score(score: ElectionScore) { MinimumScore::::put(score); @@ -791,7 +836,7 @@ impl Verifier for Pallet { >::put(Status::Nothing); } - fn get_queued_solution_page(page: PageIndex) -> Option> { + fn get_queued_solution_page(page: PageIndex) -> Option> { QueuedSolution::::get_queued_solution_page(page) } @@ -799,7 +844,7 @@ impl Verifier for Pallet { partial_solution: Self::Solution, claimed_score: ElectionScore, page: PageIndex, - ) -> Result, FeasibilityError> { + ) -> Result, FeasibilityError> { let maybe_current_score = Self::queued_score(); match Self::do_verify_synchronous(partial_solution, claimed_score, page) { Ok(supports) => { @@ -831,12 +876,12 @@ impl Verifier for Pallet { fn feasibility_check_page( partial_solution: Self::Solution, page: PageIndex, - ) -> Result, FeasibilityError> { + ) -> Result, FeasibilityError> { Self::feasibility_check_page_inner(partial_solution, page) } fn force_set_single_page_valid( - partial_supports: SupportsOf, + partial_supports: SupportsOfVerifier, page: PageIndex, score: ElectionScore, ) { diff --git a/substrate/frame/election-provider-multi-block/src/verifier/mod.rs b/substrate/frame/election-provider-multi-block/src/verifier/mod.rs index f7b9a6100c7e0..512f37f351ce9 100644 --- a/substrate/frame/election-provider-multi-block/src/verifier/mod.rs +++ b/substrate/frame/election-provider-multi-block/src/verifier/mod.rs @@ -76,9 +76,9 @@ mod impls; mod tests; // internal imports -use crate::SupportsOf; use frame_election_provider_support::PageIndex; -pub use impls::{pallet::*, Status}; +use impls::SupportsOfVerifier; +pub use impls::{feasibility_check_page_inner_with_snapshot, pallet::*, Status}; use sp_core::Get; use sp_npos_elections::ElectionScore; use sp_runtime::RuntimeDebug; @@ -134,17 +134,17 @@ pub trait Verifier { /// The account if type. type AccountId; - /// Maximum number of backers that each winner could have. - /// - /// In multi-block verification, this can only be checked after all pages are known to be valid - /// and are already checked. - type MaxBackersPerWinner: Get; - /// Maximum number of winners that can be represented in each page. /// /// A reasonable value for this should be the maximum number of winners that the election user /// (e.g. the staking pallet) could ever desire. type MaxWinnersPerPage: Get; + /// Maximum number of backers, per winner, among all pages of an election. + /// + /// This can only be checked at the very final step of verification. + type MaxBackersPerWinnerFinal: Get; + /// Maximum number of backers that each winner could have, per page. + type MaxBackersPerWinner: Get; /// Set the minimum score that is acceptable for any solution. /// @@ -165,7 +165,7 @@ pub trait Verifier { /// /// It is the responsibility of the call site to call this function with all appropriate /// `page` arguments. - fn get_queued_solution_page(page: PageIndex) -> Option>; + fn get_queued_solution_page(page: PageIndex) -> Option>; /// Perform the feasibility check on the given single-page solution. /// @@ -182,7 +182,7 @@ pub trait Verifier { partial_solution: Self::Solution, claimed_score: ElectionScore, page: PageIndex, - ) -> Result, FeasibilityError>; + ) -> Result, FeasibilityError>; /// Just perform a single-page feasibility-check, based on the standards of this pallet, without /// writing anything to anywhere. @@ -191,14 +191,14 @@ pub trait Verifier { fn feasibility_check_page( partial_solution: Self::Solution, page: PageIndex, - ) -> Result, FeasibilityError>; + ) -> Result, FeasibilityError>; /// Force set a single page solution as the valid one. /// /// Will erase any previous solution. Should only be used in case of emergency fallbacks and /// similar. fn force_set_single_page_valid( - partial_supports: SupportsOf, + partial_supports: SupportsOfVerifier, page: PageIndex, score: ElectionScore, ); diff --git a/substrate/frame/election-provider-support/src/lib.rs b/substrate/frame/election-provider-support/src/lib.rs index 22e83630a6a86..99d09f4a9b79c 100644 --- a/substrate/frame/election-provider-support/src/lib.rs +++ b/substrate/frame/election-provider-support/src/lib.rs @@ -201,6 +201,7 @@ extern crate alloc; use alloc::{boxed::Box, vec::Vec}; use core::fmt::Debug; +use frame_support::traits::{Defensive, DefensiveResult}; use sp_core::ConstU32; use sp_runtime::{ traits::{Bounded, Saturating, Zero}, @@ -795,13 +796,16 @@ impl> TryFrom> } impl> BoundedSupport { - pub fn sorted_truncate_from(mut support: sp_npos_elections::Support) -> Self { + pub fn sorted_truncate_from(mut support: sp_npos_elections::Support) -> (Self, u32) { // If bounds meet, then short circuit. if let Ok(bounded) = support.clone().try_into() { - return bounded + return (bounded, 0) } + let pre_len = support.voters.len(); // sort support based on stake of each backer, low to high. + // Note: we don't sort high to low and truncate because we would have to track `total` + // updates, so we need one iteration anyhow. support.voters.sort_by(|a, b| a.1.cmp(&b.1)); // then do the truncation. let mut bounded = Self { voters: Default::default(), total: 0 }; @@ -811,7 +815,8 @@ impl> BoundedSupport { } bounded.total += weight; } - bounded + let post_len = bounded.voters.len(); + (bounded, (pre_len - post_len) as u32) } } @@ -829,20 +834,66 @@ pub struct BoundedSupports, BInner: Get>( pub BoundedVec<(AccountId, BoundedSupport), BOuter>, ); +/// Try and build yourself from another `BoundedSupports` with a different set of types. +pub trait TryFromOtherBounds, BOtherInner: Get> { + fn try_from_other_bounds( + other: BoundedSupports, + ) -> Result + where + Self: Sized; +} + +impl< + AccountId, + BOuter: Get, + BInner: Get, + BOtherOuter: Get, + BOuterInner: Get, + > TryFromOtherBounds + for BoundedSupports +{ + fn try_from_other_bounds( + other: BoundedSupports, + ) -> Result { + // TODO: we might as well do this with unsafe rust and do it faster. + if BOtherOuter::get() <= BOuter::get() && BInner::get() <= BOuterInner::get() { + let supports = other + .into_iter() + .map(|(acc, b_support)| { + b_support + .try_into() + .defensive_map_err(|_| Error::BoundsExceeded) + .map(|b_support| (acc, b_support)) + }) + .collect::, _>>() + .defensive()?; + supports.try_into() + } else { + Err(crate::Error::BoundsExceeded) + } + } +} + impl, BInner: Get> BoundedSupports { - pub fn sorted_truncate_from(supports: Supports) -> Self { + /// Two u32s returned are number of winners and backers removed respectively. + pub fn sorted_truncate_from(supports: Supports) -> (Self, u32, u32) { // if bounds, meet, short circuit if let Ok(bounded) = supports.clone().try_into() { - return bounded + return (bounded, 0, 0) } + let pre_winners = supports.len(); + let mut backers_removed = 0; // first, convert all inner supports. let mut inner_supports = supports .into_iter() .map(|(account, support)| { - (account, BoundedSupport::::sorted_truncate_from(support)) + let (bounded, removed) = + BoundedSupport::::sorted_truncate_from(support); + backers_removed += removed; + (account, bounded) }) .collect::>(); @@ -850,11 +901,12 @@ impl, BInner: Get> inner_supports.sort_by(|a, b| b.1.total.cmp(&a.1.total)); // then take the first slice that can fit. - BoundedSupports( - BoundedVec::<(AccountId, BoundedSupport), BOuter>::truncate_from( - inner_supports, - ), - ) + let bounded = BoundedSupports(BoundedVec::< + (AccountId, BoundedSupport), + BOuter, + >::truncate_from(inner_supports)); + let post_winners = bounded.len(); + (bounded, (pre_winners - post_winners) as u32, backers_removed) } } pub trait TryFromUnboundedPagedSupports, BInner: Get> { @@ -912,6 +964,15 @@ impl, BInner: Get> PartialEq } } +impl, BInner: Get> Into> + for BoundedSupports +{ + fn into(self) -> Supports { + // TODO: can be done faster with unsafe code. + self.0.into_iter().map(|(acc, b_support)| (acc, b_support.into())).collect() + } +} + impl, BInner: Get> From), BOuter>> for BoundedSupports diff --git a/substrate/frame/election-provider-support/src/onchain.rs b/substrate/frame/election-provider-support/src/onchain.rs index 3fe8f3b4bc3e9..3478eec6c9db6 100644 --- a/substrate/frame/election-provider-support/src/onchain.rs +++ b/substrate/frame/election-provider-support/src/onchain.rs @@ -143,7 +143,9 @@ impl OnChainExecution { let unbounded = to_supports(&staked); let bounded = if T::Sort::get() { - BoundedSupportsOf::::sorted_truncate_from(unbounded) + let (bounded, _winners_removed, _backers_removed) = + BoundedSupportsOf::::sorted_truncate_from(unbounded); + bounded } else { unbounded.try_into().map_err(|_| Error::FailedToBound)? }; @@ -282,12 +284,11 @@ mod tests { } mod mock_data_provider { + use super::*; + use crate::{data_provider, DataProviderBounds, PageIndex, VoterOf}; use frame_support::traits::ConstU32; use sp_runtime::bounded_vec; - use super::*; - use crate::{data_provider, PageIndex, VoterOf}; - pub struct DataProvider; impl ElectionDataProvider for DataProvider { type AccountId = AccountId; diff --git a/substrate/frame/election-provider-support/src/tests.rs b/substrate/frame/election-provider-support/src/tests.rs index b2bf223ed2fae..de4bac3664bdd 100644 --- a/substrate/frame/election-provider-support/src/tests.rs +++ b/substrate/frame/election-provider-support/src/tests.rs @@ -461,7 +461,8 @@ fn sorted_truncate_from_works() { (3, Support { total: 406, voters: vec![(100, 100), (101, 101), (102, 102), (103, 103)] }), ]; - let bounded = BoundedSupports::, ConstU32<2>>::sorted_truncate_from(supports); + let (bounded, winners_removed, backers_removed) = + BoundedSupports::, ConstU32<2>>::sorted_truncate_from(supports); // we trim 2 as it has least total support, and trim backers based on stake. assert_eq!( bounded @@ -474,4 +475,6 @@ fn sorted_truncate_from_works() { (1, Support { total: 203, voters: vec![(102, 102), (101, 101)] }) ] ); + assert_eq!(winners_removed, 1); + assert_eq!(backers_removed, 3); } From a8445bd7499d8ff1f3aa8724eda44d2e113475bf Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 29 Jan 2025 17:36:37 +0000 Subject: [PATCH 165/169] move dev_accounts to pallet-staking, simplify onchain large chain-spec generation --- Cargo.lock | 1 + substrate/bin/node/cli/src/chain_spec.rs | 50 ++++----- .../election-provider-multi-block/src/lib.rs | 9 +- substrate/frame/staking/Cargo.toml | 9 +- substrate/frame/staking/src/pallet/mod.rs | 100 +++++++++++++++++- 5 files changed, 124 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 473abd348e7ad..f9f8764ebb751 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15479,6 +15479,7 @@ dependencies = [ "pallet-staking-reward-curve", "pallet-timestamp 27.0.0", "parity-scale-codec", + "rand", "rand_chacha", "scale-info", "serde", diff --git a/substrate/bin/node/cli/src/chain_spec.rs b/substrate/bin/node/cli/src/chain_spec.rs index a28ab5784eb86..4c8508f672bc8 100644 --- a/substrate/bin/node/cli/src/chain_spec.rs +++ b/substrate/bin/node/cli/src/chain_spec.rs @@ -357,6 +357,17 @@ pub fn testnet_genesis( let (initial_authorities, endowed_accounts, num_endowed_accounts, stakers) = configure_accounts(initial_authorities, initial_nominators, endowed_accounts); const MAX_COLLECTIVE_SIZE: usize = 50; + let dev_stakers = if cfg!(feature = "staking-playground") { + let random_validators = std::option_env!("AUTHORITIES") + .map(|s| s.parse::().unwrap()) + .unwrap_or(100); + let random_nominators = std::option_env!("NOMINATORS") + .map(|s| s.parse::().unwrap()) + .unwrap_or(3000); + Some((random_validators, random_nominators)) + } else { + None + }; serde_json::json!({ "balances": { @@ -387,6 +398,7 @@ pub fn testnet_genesis( "invulnerables": initial_authorities.iter().map(|x| x.0.clone()).collect::>(), "slashRewardFraction": Perbill::from_percent(10), "stakers": stakers.clone(), + "devStakers": dev_stakers }, "elections": { "members": endowed_accounts @@ -420,38 +432,12 @@ pub fn testnet_genesis( } fn development_config_genesis_json() -> serde_json::Value { - if cfg!(feature = "staking-playground") { - let random_authorities_count = std::option_env!("AUTHORITIES") - .map(|s| s.parse::().unwrap()) - .unwrap_or(100); - let random_nominators_count = std::option_env!("NOMINATORS") - .map(|s| s.parse::().unwrap()) - .unwrap_or(3000); - let mut random_authorities = (0..random_authorities_count) - .map(|i| authority_keys_from_seed(&format!("Random{}", i))) - .collect::>(); - let random_nominators = (0..random_nominators_count) - .map(|i| { - get_public_from_string_or_panic::(&format!("Random{}", i)).into() - }) - .collect::>(); - // Alice should also always be an authority. - random_authorities.push(authority_keys_from_seed("Alice")); - - testnet_genesis( - random_authorities, - random_nominators, - Sr25519Keyring::Alice.to_account_id(), - None, - ) - } else { - testnet_genesis( - vec![authority_keys_from_seed("Alice")], - vec![], - Sr25519Keyring::Alice.to_account_id(), - None, - ) - } + testnet_genesis( + vec![authority_keys_from_seed("Alice")], + vec![], + Sr25519Keyring::Alice.to_account_id(), + None, + ) } fn props() -> Properties { diff --git a/substrate/frame/election-provider-multi-block/src/lib.rs b/substrate/frame/election-provider-multi-block/src/lib.rs index e86cf76089efb..311ea7de7d412 100644 --- a/substrate/frame/election-provider-multi-block/src/lib.rs +++ b/substrate/frame/election-provider-multi-block/src/lib.rs @@ -518,14 +518,11 @@ pub mod pallet { match current_phase { // start and continue snapshot. - Phase::Off - if remaining_blocks <= snapshot_deadline - // && remaining_blocks > signed_deadline // TODO do we need this? - => - { + Phase::Off if remaining_blocks <= snapshot_deadline => { let remaining_pages = Self::msp(); Self::create_targets_snapshot().defensive_unwrap_or_default(); - Self::create_voters_snapshot_paged(remaining_pages).defensive_unwrap_or_default(); + Self::create_voters_snapshot_paged(remaining_pages) + .defensive_unwrap_or_default(); Self::phase_transition(Phase::Snapshot(remaining_pages)); todo_weight }, diff --git a/substrate/frame/staking/Cargo.toml b/substrate/frame/staking/Cargo.toml index 74b1c78e9cbee..30afeea3825b0 100644 --- a/substrate/frame/staking/Cargo.toml +++ b/substrate/frame/staking/Cargo.toml @@ -33,10 +33,12 @@ sp-application-crypto = { features = ["serde"], workspace = true } sp-io = { workspace = true } sp-runtime = { features = ["serde"], workspace = true } sp-staking = { features = ["serde"], workspace = true } +sp-core = { workspace = true } +rand_chacha = { workspace = true } +rand = { features = ["alloc"], workspace = true } # Optional imports for benchmarking frame-benchmarking = { optional = true, workspace = true } -rand_chacha = { optional = true, workspace = true } [dev-dependencies] frame-benchmarking = { workspace = true, default-features = true } @@ -47,7 +49,6 @@ pallet-balances = { workspace = true, default-features = true } pallet-staking-reward-curve = { workspace = true, default-features = true } pallet-timestamp = { workspace = true, default-features = true } rand_chacha = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } sp-npos-elections = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } substrate-test-utils = { workspace = true } @@ -68,9 +69,12 @@ std = [ "pallet-timestamp/std", "scale-info/std", "serde/std", + "rand_chacha/std", + "rand/std", "sp-application-crypto/std", "sp-core/std", "sp-io/std", + "sp-core/std", "sp-npos-elections/std", "sp-runtime/std", "sp-staking/std", @@ -84,7 +88,6 @@ runtime-benchmarks = [ "pallet-bags-list/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", - "rand_chacha", "sp-runtime/runtime-benchmarks", "sp-staking/runtime-benchmarks", ] diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index 09cac208bd027..6c14233db7440 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -17,15 +17,16 @@ //! Staking FRAME Pallet. -use alloc::vec::Vec; +use alloc::{format, vec::Vec}; use codec::Codec; use frame_election_provider_support::{ElectionProvider, SortedListProvider, VoteWeight}; use frame_support::{ + assert_ok, pallet_prelude::*, traits::{ fungible::{ hold::{Balanced as FunHoldBalanced, Mutate as FunHoldMutate}, - Mutate as FunMutate, + Inspect, Mutate, Mutate as FunMutate, }, Defensive, DefensiveSaturating, EnsureOrigin, EstimateNextNewSession, Get, InspectLockableCurrency, OnUnbalanced, UnixTime, @@ -34,6 +35,12 @@ use frame_support::{ BoundedBTreeSet, BoundedVec, }; use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; +use rand::seq::SliceRandom; +use rand_chacha::{ + rand_core::{RngCore, SeedableRng}, + ChaChaRng, +}; +use sp_core::{sr25519::Pair as SrPair, Pair}; use sp_runtime::{ traits::{SaturatedConversion, StaticLookup, Zero}, ArithmeticError, Perbill, Percent, Saturating, @@ -795,6 +802,39 @@ pub mod pallet { pub min_validator_bond: BalanceOf, pub max_validator_count: Option, pub max_nominator_count: Option, + /// Create the given number of validators and nominators. + /// + /// These account need not be in the endowment list of balances, and are auto-topped up + /// here. + /// + /// Useful for testing genesis config. + pub dev_stakers: Option<(u32, u32)>, + } + + impl GenesisConfig { + fn generate_endowed_bonded_account( + derivation: &str, + rng: &mut ChaChaRng, + min_validator_bond: BalanceOf, + ) -> T::AccountId { + let pair: SrPair = Pair::from_string(&derivation, None) + .expect(&format!("Failed to parse derivation string: {derivation}")); + let who = T::AccountId::decode(&mut &pair.public().encode()[..]) + .expect(&format!("Failed to decode public key from pair: {:?}", pair.public())); + + let stake = BalanceOf::::from(rng.next_u64()) + .max(T::Currency::minimum_balance()) + .max(min_validator_bond); + let two: BalanceOf = 2u64.into(); + + assert_ok!(T::Currency::mint_into(&who, stake * two)); + assert_ok!(>::bond( + T::RuntimeOrigin::from(Some(who.clone()).into()), + stake, + RewardDestination::Staked, + )); + who + } } #[pallet::genesis_build] @@ -827,12 +867,12 @@ pub mod pallet { asset::free_to_stake::(stash) >= balance, "Stash does not have enough balance to bond." ); - frame_support::assert_ok!(>::bond( + assert_ok!(>::bond( T::RuntimeOrigin::from(Some(stash.clone()).into()), balance, RewardDestination::Staked, )); - frame_support::assert_ok!(match status { + assert_ok!(match status { crate::StakerStatus::Validator => >::validate( T::RuntimeOrigin::from(Some(stash.clone()).into()), Default::default(), @@ -856,6 +896,58 @@ pub mod pallet { Nominators::::count() + Validators::::count(), "not all genesis stakers were inserted into sorted list provider, something is wrong." ); + + // now generate the dev stakers, after all else is setup + if let Some((validators, nominators)) = self.dev_stakers { + crate::log!( + debug, + "generating dev stakers: validators: {}, nominators: {}", + validators, + nominators + ); + let base_derivation = "//staker//{}"; + + // it is okay for the randomness to be the same on every call. If we want different, + // we can make `base_derivation` configurable. + let mut rng = + ChaChaRng::from_seed(base_derivation.using_encoded(sp_core::blake2_256)); + + let validators = (0..validators) + .map(|index| { + let derivation = + base_derivation.replace("{}", &format!("validator{}", index)); + let who = Self::generate_endowed_bonded_account( + &derivation, + &mut rng, + self.min_validator_bond, + ); + assert_ok!(>::validate( + T::RuntimeOrigin::from(Some(who.clone()).into()), + Default::default(), + )); + who + }) + .collect::>(); + + (0..nominators).for_each(|index| { + let derivation = base_derivation.replace("{}", &format!("nominator{}", index)); + let who = Self::generate_endowed_bonded_account( + &derivation, + &mut rng, + self.min_validator_bond, + ); + + let random_nominations = validators + .choose_multiple(&mut rng, MaxNominationsOf::::get() as usize) + .map(|v| v.clone()) + .collect::>(); + + assert_ok!(>::nominate( + T::RuntimeOrigin::from(Some(who.clone()).into()), + random_nominations.iter().map(|l| T::Lookup::unlookup(l.clone())).collect(), + )); + }) + } } } From 42098f512b70a9371757933bb6ffce017761ccd5 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 29 Jan 2025 17:40:20 +0000 Subject: [PATCH 166/169] chain-spec generation for staking, need to pull now --- .../runtimes/assets/asset-hub-next-westend/README.md | 7 ++++--- .../src/genesis_config_presets.rs | 11 +++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/README.md b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/README.md index 6caaf92e381b0..7eb2bff0f9e8c 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/README.md +++ b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/README.md @@ -2,14 +2,16 @@ ## Local Development -In any case, prepare a chain-spec +In any case, prepare a chain-spec. ``` # in this directory cargo build --release -chain-spec-builder create --runtime ../../../../../target/release/wbuild/asset-hub-next-westend-runtime/asset_hub_next_westend_runtime.wasm --relay-chain rococo-local --para-id 1234 named-preset genesis +chain-spec-builder create --runtime ../../../../../target/release/wbuild/asset-hub-next-westend-runtime/asset_hub_next_westend_runtime.wasm --relay-chain rococo-local --para-id 1100 named-preset genesis ``` +Note that the para-id is set in the chain-spec too and must match this one. + Real setup with Zombienet ``` @@ -21,4 +23,3 @@ Single-node, single dev mode. This doesn't check things like PoV limits at all, ``` polkadot-omni-node --chain ./chain_spec.json --dev-block-time 1000 --tmp ``` - diff --git a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/genesis_config_presets.rs b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/genesis_config_presets.rs index af90357154368..0e301914bf411 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/genesis_config_presets.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/genesis_config_presets.rs @@ -36,6 +36,8 @@ fn asset_hub_next_westend_genesis( endowment: Balance, id: ParaId, ) -> serde_json::Value { + let dev_stakers = std::option_env!("AUTHORITIES").zip(std::option_env!("NOMINATORS")); + build_struct_json_patch!(RuntimeGenesisConfig { balances: BalancesConfig { balances: endowed_accounts.iter().cloned().map(|k| (k, endowment)).collect(), @@ -58,6 +60,15 @@ fn asset_hub_next_westend_genesis( .collect(), }, polkadot_xcm: PolkadotXcmConfig { safe_xcm_version: Some(SAFE_XCM_VERSION) }, + staking: StakingConfig { + // we wish to elect 500 validators, maximum is set to 1000 in the runtime configs. + validator_count: 500, + // smallest validator set we accept, 50 for now? + minimum_validator_count: 50, + // initial stakers + dev_stakers, + ..Default::default() + } }) } From 503651bb3303bc64be657bed8ed2ed0a749bc9e4 Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 29 Jan 2025 17:47:38 +0000 Subject: [PATCH 167/169] merged --- .../src/genesis_config_presets.rs | 2 +- .../assets/asset-hub-next-westend/src/lib.rs | 1 - .../asset-hub-next-westend/src/staking.rs | 23 +++++++++++++++---- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/genesis_config_presets.rs b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/genesis_config_presets.rs index 0e301914bf411..90f4fcd1371ad 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/genesis_config_presets.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/genesis_config_presets.rs @@ -36,7 +36,7 @@ fn asset_hub_next_westend_genesis( endowment: Balance, id: ParaId, ) -> serde_json::Value { - let dev_stakers = std::option_env!("AUTHORITIES").zip(std::option_env!("NOMINATORS")); + let dev_stakers = core::option_env!("AUTHORITIES").zip(core::option_env!("NOMINATORS")); build_struct_json_patch!(RuntimeGenesisConfig { balances: BalancesConfig { diff --git a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/lib.rs index da4e97c23cabd..6dfaf2b45774e 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/lib.rs @@ -1100,7 +1100,6 @@ impl pallet_revive::Config for Runtime { type InstantiateOrigin = EnsureSigned; type RuntimeHoldReason = RuntimeHoldReason; type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; - type Debug = (); type Xcm = pallet_xcm::Pallet; type ChainId = ConstU64<420_420_421>; type NativeToEthRatio = ConstU32<1_000_000>; // 10^(18 - 12) Eth is 10^18, Native is 10^12. diff --git a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/staking.rs b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/staking.rs index b86f2d5cbe947..abe374ed0eef9 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/staking.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/staking.rs @@ -18,7 +18,7 @@ use super::*; use cumulus_primitives_core::relay_chain::SessionIndex; use frame_election_provider_support::{ bounds::{CountBound, ElectionBounds, ElectionBoundsBuilder}, - onchain, NposSolver, SequentialPhragmen, + onchain, ElectionDataProvider, NposSolver, SequentialPhragmen, }; use frame_support::traits::{ConstU128, EitherOf}; use pallet_election_provider_multi_block::{self as multi_block, SolutionAccuracyOf}; @@ -42,6 +42,23 @@ frame_election_provider_support::generate_solution_type!( >(16) ); +impl multi_block::unsigned::miner::MinerConfig for Runtime { + type AccountId = AccountId; + type Hash = Hash; + type MaxBackersPerWinner = ::MaxBackersPerWinner; + type MaxBackersPerWinnerFinal = + ::MaxBackersPerWinnerFinal; + type MaxWinnersPerPage = ::MaxWinnersPerPage; + type MaxVotesPerVoter = + <::DataProvider as ElectionDataProvider>::MaxVotesPerVoter; + type MaxLength = MinerMaxLength; + type Solver = ::OffchainSolver; + type Pages = Pages; + type Solution = NposCompactSolution16; + type VoterSnapshotPerBlock = ::VoterSnapshotPerBlock; + type TargetSnapshotPerBlock = ::TargetSnapshotPerBlock; +} + parameter_types! { pub MaxWinnersPerPage: u32 = 64; pub MaxBackersPerWinnerPerPage: u32 = 64; @@ -69,7 +86,7 @@ impl multi_block::Config for Runtime { type SignedPhase = SignedPhase; type UnsignedPhase = UnsignedPhase; type SignedValidationPhase = SignedValidationPhase; - type Solution = NposCompactSolution16; + type MinerConfig = Self; type VoterSnapshotPerBlock = VoterSnapshotPerBlock; type TargetSnapshotPerBlock = TargetSnapshotPerBlock; type WeightInfo = (); @@ -122,8 +139,6 @@ impl multi_block::unsigned::Config for Runtime { type WeightInfo = (); type OffchainSolver = SequentialPhragmen>; type MinerTxPriority = MinerTxPriority; - type MinerMaxLength = MinerMaxLength; - type MinerMaxWeight = MinerMaxWeight; type OffchainRepeat = ConstU32<5>; } From 9398059d721ca713389a51a147747a721a078ccd Mon Sep 17 00:00:00 2001 From: kianenigma Date: Wed, 29 Jan 2025 18:12:30 +0000 Subject: [PATCH 168/169] works, but omni node cannot produce blocks --- .gitignore | 1 + .../runtimes/assets/asset-hub-next-westend/README.md | 8 ++++---- .../asset-hub-next-westend/src/genesis_config_presets.rs | 9 +++++++-- .../runtimes/assets/asset-hub-next-westend/src/lib.rs | 4 ++++ 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index d482876570857..44302573f1cc2 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ substrate.code-workspace target/ *.scale justfile +chain_spec.json diff --git a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/README.md b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/README.md index 7eb2bff0f9e8c..50aed55c24408 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/README.md +++ b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/README.md @@ -5,9 +5,9 @@ In any case, prepare a chain-spec. ``` -# in this directory -cargo build --release -chain-spec-builder create --runtime ../../../../../target/release/wbuild/asset-hub-next-westend-runtime/asset_hub_next_westend_runtime.wasm --relay-chain rococo-local --para-id 1100 named-preset genesis +VALIDATORS=1000 NOMINATORS=20000 cargo build --release -p asset-hub-westend-runtime +rm target/release/wbuild/asset-hub-next-westend-runtime/asset_hub_next_westend_runtime.wasm +chain-spec-builder create --runtime target/release/wbuild/asset-hub-next-westend-runtime/asset_hub_next_westend_runtime.wasm --relay-chain rococo-local --para-id 1100 named-preset genesis ``` Note that the para-id is set in the chain-spec too and must match this one. @@ -21,5 +21,5 @@ zombienet --provider native spawn zombienet-omni-node.toml Single-node, single dev mode. This doesn't check things like PoV limits at all, be careful! ``` -polkadot-omni-node --chain ./chain_spec.json --dev-block-time 1000 --tmp +polkadot-omni-node --chain ./chain_spec.json --dev-block-time 12000 --tmp ``` diff --git a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/genesis_config_presets.rs b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/genesis_config_presets.rs index 90f4fcd1371ad..5227ecda108d1 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/genesis_config_presets.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/genesis_config_presets.rs @@ -34,10 +34,9 @@ fn asset_hub_next_westend_genesis( invulnerables: Vec<(AccountId, AuraId)>, endowed_accounts: Vec, endowment: Balance, + dev_stakers: Option<(u32, u32)>, id: ParaId, ) -> serde_json::Value { - let dev_stakers = core::option_env!("AUTHORITIES").zip(core::option_env!("NOMINATORS")); - build_struct_json_patch!(RuntimeGenesisConfig { balances: BalancesConfig { balances: endowed_accounts.iter().cloned().map(|k| (k, endowment)).collect(), @@ -80,6 +79,9 @@ mod preset_names { /// Provides the JSON representation of predefined genesis config for given `id`. pub fn get_preset(id: &PresetId) -> Option> { use preset_names::*; + let maybe_dev_stakers = core::option_env!("VALIDATORS") + .map(|x| str::parse::(x).unwrap()) + .zip(core::option_env!("NOMINATORS").map(|x| str::parse::(x).unwrap())); let patch = match id.as_ref() { PRESET_GENESIS => asset_hub_next_westend_genesis( // initial collators. @@ -107,6 +109,7 @@ pub fn get_preset(id: &PresetId) -> Option> { ], Vec::new(), ASSET_HUB_NEXT_WESTEND_ED * 4096, + None, 1100.into(), ), sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET => asset_hub_next_westend_genesis( @@ -117,6 +120,7 @@ pub fn get_preset(id: &PresetId) -> Option> { ], Sr25519Keyring::well_known().map(|k| k.to_account_id()).collect(), WND * 1_000_000, + maybe_dev_stakers, 1100.into(), ), sp_genesis_builder::DEV_RUNTIME_PRESET => asset_hub_next_westend_genesis( @@ -129,6 +133,7 @@ pub fn get_preset(id: &PresetId) -> Option> { Sr25519Keyring::BobStash.to_account_id(), ], WND * 1_000_000, + maybe_dev_stakers, 1100.into(), ), _ => return None, diff --git a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/lib.rs index 6dfaf2b45774e..43a56e0b9ec3a 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-next-westend/src/lib.rs @@ -2189,6 +2189,10 @@ impl_runtime_apis! { Revive::evm_balance(&address) } + fn block_gas_limit() -> U256 { + Revive::evm_block_gas_limit() + } + fn nonce(address: H160) -> Nonce { let account = ::AddressMapper::to_account_id(&address); System::account_nonce(account) From 398382d8c4e3362a3d915d25e11a3b0730ab6d4c Mon Sep 17 00:00:00 2001 From: kianenigma Date: Mon, 3 Feb 2025 12:52:28 +0000 Subject: [PATCH 169/169] rename to validators --- substrate/bin/node/cli/src/chain_spec.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/substrate/bin/node/cli/src/chain_spec.rs b/substrate/bin/node/cli/src/chain_spec.rs index 4c8508f672bc8..774aafec206a9 100644 --- a/substrate/bin/node/cli/src/chain_spec.rs +++ b/substrate/bin/node/cli/src/chain_spec.rs @@ -358,9 +358,8 @@ pub fn testnet_genesis( configure_accounts(initial_authorities, initial_nominators, endowed_accounts); const MAX_COLLECTIVE_SIZE: usize = 50; let dev_stakers = if cfg!(feature = "staking-playground") { - let random_validators = std::option_env!("AUTHORITIES") - .map(|s| s.parse::().unwrap()) - .unwrap_or(100); + let random_validators = + std::option_env!("VALIDATORS").map(|s| s.parse::().unwrap()).unwrap_or(100); let random_nominators = std::option_env!("NOMINATORS") .map(|s| s.parse::().unwrap()) .unwrap_or(3000);